SPL Token Tracker
The Solana Token Tracker Substreams allows you to extract transfers from Solana Token Programs. You can simply provide the address of the token you want to track as an input to the Substreams.
Before You Begin
The Solana Token Tracker Substreams requires medium to advanced Substreams knowledge. If this is the first time you are using Substreams, make sure you:
Read the Develop Substreams section, which will teach you the basics of the developing Substreams modules.
Complete the Explore Solana tutorial, which will assist you in understanding the main pieces of the Solana Substreams.
If you already have the required knowledge, clone the Solana Token Tracker GitHub repository. You will go through the code in the following steps.
Inspect the Project
The Substreams has only one module: map_solana_token_events
, as you can check in the Substreams manifest (substreams.yaml
):
The module receives two inputs (defined in the inputs
section of the YAML):
A string containing a couple of parameters: this parameter is defined in the
params
section of the YAML, and defines the token that you want to extract data from:token_contract=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&token_decimals=6
token_contract=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
is the address of the USDC contract in Solana mainnet andtoken_decimals=6
is the number of decimals used the USDC token.A raw Solana block.
You can update the token_contract
parameter to track any token of your choice. You can also use the -p
option in the Substreams GUI to dynamically override the parameters of the Substreams.
Run the Substreams
You can run the Substreams by using the Substreams CLI. As specified in the manifest by default, the USDC data will be retrieved.
You can also override the parameters of the manifest by using the -p
option of the CLI. For example, if you want to track the transfer of the USDT token (Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
):
Inspect the Code
Open the
lib.rs
file, which contains the code for themap_solana_token_events
module. The function receives two parameters: the raw Solana block object and the parameters provided in the Substreams manifest.The
parse_parameters
function converts the parameters string passed to the module and converts it into aTokenParams
object. This object contains two fields:token_contract
(representing the address of the token to track) andtoken_decimals
(representing the number of decimals used in the token).
Then, you iterate over all the transactions
Create an
Output
object, which is the container of all the events extracted.Iterate over the confirmed transactions of the block.
Get the accounts of the transaction. The
resolved_accounts()
method contains also accounts stored in the Address Lookup Tables.Unwrap the transaction if it is available.
Unwrap the transaction message.
Unwrap the transaction metadata.
Iterate over the instructions contained within the transaction.
For every instruction, call the
process_compiled_instruction(...)
function to process the instruction further.
The
process_compiled_instruction(...)
function is defined in theutil.rs
file.
The
instruction.program_id_index
indicates the position of the program account in the accounts array. For example, ifprogram_index_id = 5
, it means that the program account will be at position number 5 in theaccounts
array.If the instruction account is the Token Program Account (i.e.
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
), this means that the instruction executed in the transaction has been produced by the Token Program. Therefore, you process the instruction further by calling theprocess_token_instruction(...)
function to extract token-related information, such as transfers or mints.Every top-level instruction holds inner instructions. If the top-level instruction is not from the Token Program, you check if any Inner Instruction is from the Token Program by calling the
process_inner_instructions(...)
function.
A top-level instruction could hold a Token Program instruction within its inner instructions. The
process_inner_instructions(...)
checks if there are Token Program among the inner instructions of every top-level instruction.
The
TransactionStatusMeta
object holds an array with the inner instructions of the transaction (an array ofInnerTransactions
objects).Because the inner instructions are at the transaction level (contained within the
TransactionStatusMeta
), you keep only the inner transactions belonging to the current top-level instruction. For this purpose, an index variable (instruction_index
) is passed as a parameter. Essentially, you are matching every top-level instruction with its correspondingInnerTransactions
object. The filtering should only keep oneInnerTransactions
object, as every top-level instruction should only have oneInnerTransactions
object.The
InnerTransactions
object is a just wrapper for the array of inner transactions. For everyInnerTransactions
object filtered (which should be only one), you actually extract the inner instructions.You iterate over the array of inner instructions.
You only keep Token Program inner instructions.
You process every Token Program inner instruction found further by calling the
process_token_instruction(...)
.
Once you have identified all the Token Program instructions, the process_token_instruction(...)
function extracts transfer or mint data from these instructions. To easily extract data from a Token Program instruction, the Substreams relies on the substreams-solana-program-instructions
Rust crate, which provides useful helper functions.
The
TokenInstruction::unpack(...)
function decodes the instruction and allows you to identify the action executed:Transfer
,TransferChecked
,Mint
, orBurn
.Controlled way to handle errors from the
unpack(...)
function.If there are no errors, then you can handle every action (
Transfer
,Mint
...) differently.Handle the
Transfer
instruction.Call the
is_token_transfer(...)
to verify if the transfer is from the specified in the parameters of the Substreams module. Note that you passparameters.token_contract
as a parameter to the function.Create a new
Transfer
object from the Protobuf with the corresponding data. This object is added to theOutput
object and will be emitted as the output of the Substreams.
The code for other actions (Mint
, Burn
...) is analogous to the code of the Transfer
instructions.
Last updated