Solana Analytics Starter Guide (Part 4): Solana Events (Logs, Anchor Logs, Event CPI)
Learn how logs work, with Lifinty, Tensorswap, and Jupiter as examples.
This is the final part of a series covering basic Solana analysis
part 3: Solana NFT structure and mints (NFT, pNFT, xNFT, cNFT)
→ part 4: Solana Events (Logs, Anchor Logs, Event CPI)
Coming from an Ethereum background? Start with this guide translating EVM concepts to their Solana counterparts.
For this guide, you should already be familiar with the structure of Solana instructions, how a token transfer works, and NFT account patterns.
By the end of the four guides you should be able to understand the core concepts of any of the covered queries. My DMs on Twitter are always open for questions or ideas.
Basic SQL knowledge will be helpful. If you’re unfamiliar, go through this tutorial.
Table of Contents:
You’ll learn to understand and query three types of Solana events:
Basic log: the default Solana program log
Anchor log: the base64 encoded Anchor program log
Event CPI: the base64 encoded log, stored inside a self CPI
I’ll dissect one example for each log type as we go.
Interpreting Log Messages:
First, we need to build an intuition around the logs you see on an explorer. You’ll see something like this for any transaction:
You should already be familiar with outer and inner instructions. The outer instruction is the top level program call from the user/wallet, and then the inner instructions are “Cross Program Invocations” (CPI) from that top level program. So we can see there were 3 instructions here, two for computing/validating the instruction and then one for the main logic.
The tabbing here reflects the CPI depth - so for #3 we can see that tensorswap calls the metaplex token metadata program which then calls the spl token program to thaw and transfer the NFT.
What does this look like in the raw data? We can look at the “log_messages” column on solana.transactions to see this:
It’s not as nicely formatted, you can see that the CPI depth is reflected in the [1], [2], and [3] instead. But everything else is the same!
Decoding the Basic Log:
Some of these logs are there by default, such as invoke, consumed, and success. The others are added manually by developers. The most basic addition is this “Program Log: Instruction: SellNftTradePool”, reflecting the “SellNftTradePool” instruction was called on TSWAP.
You’ll commonly see text based logs like this, sometimes as JSON like Lifinity swap (a DEX) does:
I pulled the JSON out by filtering with LIKE on 'Program log: %Amount: {%' and then using json_extract_scalar() and json_parse():
These basic logs are very unstandardized. Sometimes it won’t be formatted as JSON (or formatted incorrectly). You’ll see that some people are even using logs to upload chapters of books to the Solana blockchain:
Decoding Anchor Logs
If you’re used the “event topic signatures” from EVM, then I have news for you: they don’t exist in basic logs.
Thankfully, a few smart devs came along and created the anchor program framework. This framework comes with event encoding and decoding. If you don’t know what native, enum, and anchor programs are then check out this guide first.
Going back to the Tensorswap transaction from earlier, you’ll see at the bottom there is this long string of gibberish:
This is the “BuySellEvent” event, which we can see in the verified IDL:
The string is base64 encoded, but otherwise behaves just like instruction data with an 8 byte discriminator and borsh serialized data. The discriminator acts as your “event topic signature” in Solana.
You’ll see that in the tensorswap_v1_solana.trades table I created, that I use the following filtering to get all BuySellEvents:
If you’re having trouble decoding an event and want to double check the values, the new Quantum block explorer decodes them by default for you:
Note that filtering on anchor logs can get screwed up because multiple programs will emit a similar event/types (like a dex swap event), giving you duplicates. In these situations, you’ll want to tie the exact instruction index and program id to the event log:
If you look at the query, you’ll see it is actually pretty complex and expensive to run. In the actual tensorswap_v1_solana.trades table I used a row_number() aliased “log_order” to track BuySellEvents, and then inner joined the log_order to the call_order of swap instructions. I use this same shortcut method for Magiceden tables as well.
For those who want to get really technical: the row_number() method assumes one event emitted per swap instruction - if you’re working with something like Jupiter that emits multiple SwapEvents per route instruction, then this method will get messed up when there are multiple route instructions in one transaction. In such cases, you will need to use the more expensive query format.
Decoding Event CPIs
There are two issues with logs from log_messages:
they can end up truncated (cut off) if there are too many logs in one transaction
they can get spoofed (fake the same discriminator) by other programs, which can’t be filtered unless you run my expensive log invoke tracker query
Thankfully, again some smart devs discovered that a program can CPI itself, fixing both problems we have above. Let’s look at events for Jupiter v6 (a DEX aggregator):
Looking at an example swap below, you’ll see that after the Orca whirlpool swap there is a self CPI - this is the “SwapEvent” event! It’s emitted once after every swap in a route.
The swap above decodes into:
You’ll see that there are a few key differences between this and the default Anchor log:
there is an instruction discriminator appended before the event discriminator. So the data doesn’t start until 16 bytes into the data string.
the data is base58 encoded (like all instruction data), instead of base64 encoded.
And that covers all the event types (currently) on Solana! 😊
You may have seen that some programs call a “noop” program. I’ve found that the call data here is usually used to store a ton of data that would go over the length limit in logs. One example is in cNFT minting where the merkle proof is stored with noop.
Across the dozens of programs I’ve analyzed so far, I have not ran into a situation where I needed the “noop” call data for events. If you have a good example, please share it with me and I’ll update this guide.
Congrats on learning the basics of Solana 👑
You’ve made it through all four parts of my basic Solana guide! It’s truly no easy feat to understand everything I’ve covered, so take your time to digest and play around.
If you’ve found these guides helpful, give them a heart and share them with a friend (or on Twitter).
Go pick a program, plug it into this dashboard, and start analyzing!
Some relevant dashboards for you to check out are: