The DEX Trades Substreams, developed by TopLedger, extracts trades from different Solana DEXs (decentralized exchanges).
About TopLedger
TopLedger is SQL-based data discovery and analytics platform focused on Solana. By using Substreams, TopLedger has been able to extract data from the main Solana dapps, thus providing rich analytics products.
TopLedger is an active contributor to the Substreams community and has developed several useful ready-to-use Substreams.
Before You Begin
The DEX Trades 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.
Then, clone the TopLedger Solana Programs project and navigate to the dex-trades folder, which contains the code of the Substreams.
Inspect the Substreams
The Substreams contains only one module, map_block:
The module receives a raw Solana block as a parameter (sf.solana.type.v1.Block) and emits a custom object containing the trades data (sf.solana.dex.trades.v1.Output). The output is a Protobut object defined in the proto/output.proto file:
The Substreams extracts trades from different DEXs. Because every DEX handles the data diffrerently, it is necessary to create a custom decoding function for every DEX. Every supported DEX has its correponding function in the dapps directory of the project.
Run the Substreams
You can run the Substreams against the Solana StreamingFast endpoint by using the Substreams CLI:
Initialize an array to keep the trades data extracted.
Iterate over the transactions.
Get accounts of the transaction (the resolved_accounts() method contains also accounts stored in the Address Lookup Tables).
Iterate over the instructions within the transaction.
Keep only inner instructions beloging to the current top-level instruction. Becuse the inner instructions are at the transaction level, you must filter filter which inner instruction belong to the current instruction by using the index property.
Get the program account.
Process trade instruction by calling the get_trade_instruction(...) function.
Every DEX handles the data differently, so it is necessary to create a decoding function for every exchange supported. The get_trade_instruction(...) function receives the data of every top-level instruction and figures out whether the instruction belongs to any of the supported DEXs. If the instruction is part of a known DEX, the corresponding DEX decoding function is called and a TradeInstruction object is returned.
Excuted if the program account is CLMM9tUoggJu2wagPkkqs9eFG4BWhVBZWkP1qv3Sp7tR (Crema Finance).
Call the decoding function of Crema Finance (parse_trade_instruction(...)).
Executed if the program account is Dooar9JkhdZ7J3LHN3A7YCuoGRUggXhQaG4kijfLGU2j (Dooar Exchange).
Executed if the program account is Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB (Meteora).
You can find the decoding functions in the dapps folder of the project.
Back in the main function (process_block), if the instruction processed matches any of the supported DEXs, then the trade_data variable will be populated. A new TradeData object is created with the corresponding trading information and is added to the array as part of the output of the Substreams.
Until now, the code logic has taken care of the top-level instructions. However, it is also necessary to verify if any of the inner instructions contain relevant DEX information.
The logic for the inner instruction is analogus to the logic of top-level instructions:
Iterate over the inner instructions.
Pass the data to the get_trade_instruction(...) function.
If the get_trade_instruction(...) function returns a TradeInstruction object, then a new TradeData object is created and added to the array.