This Substreams module extracts SPL token instructions from Solana transactions, resolving account ownership through the SPL Initialized Account Foundational Store to provide complete transfer information.
If you are a Substreams developer, jump to Using SPL Token Module for details about how to consume SPL token instruction data with resolved ownership information.
This module processes SPL token instructions and enriches them with account ownership data to create complete transfer records. The ownership resolution is critical since SPL transfer instructions only contain account addresses, not the actual wallet owners.
[!NOTE] Ensure your Cargo
substreams
andsubstreams-solana
dependencies are up to datecargo update substreams substreams-solana
.
First, add the SPL Token import to your substreams.yaml
manifest:
...
imports:
...
spl_token: solana-spl-token@v0.1.4
modules:
...
- name: <your-module>
kind: map
inputs:
...
- map: spl_token:map_spl_instructions
output:
...
Then in your code, use the module output:
...
use sf::solana::spl::v1::type::SplInstructions;
#[substreams::handlers::map]
fn process_spl_data(spl_instructions: SplInstructions) -> Result<YourOutputType, Error> {
...
// Process transfers with resolved ownership
for transfer in spl_instructions.transfers {
substreams::log::info!("Transfer from {} to {}",
Address(&transfer.from_owner).to_string(),
Address(&transfer.to_owner).to_string()
);
}
// Process mints
for mint in spl_instructions.mints {
substreams::log::info!("Mint to owner {}",
Address(&mint.to_owner).to_string()
);
}
// Process burns
for burn in spl_instructions.burns {
substreams::log::info!("Burn from owner {}",
Address(&burn.from_owner).to_string()
);
}
}
See https://github.com/streamingfast/substreams-foundational-modules/tree/main/solana/spl-initialized-account for the foundational store that resolves account ownership.
The module processes SPL token instructions and extracts information from these instruction types:
Transfer
- Token transfers between accounts (with resolved sender/receiver ownership)Mint
- New token creation (with destination owner)Burn
- Token destruction (with source owner)InitializeAccount*
- Account initialization eventsFor each instruction, it resolves account ownership using the SPL Initialized Account Foundational Store to provide:
Configure the module to track a specific SPL token by editing the parameters in substreams.yaml
:
params:
map_spl_instructions: "spl_token_address=YOUR_TOKEN_ADDRESS|spl_token_decimal=DECIMALS"
solana_common:transactions_by_programid_and_account_without_votes: "program:TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA && account:YOUR_TOKEN_ADDRESS"
The module supports both SPL Token programs:
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
(original)TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
(Token-2022)This project includes a complete data pipeline from Solana blockchain to ClickHouse database with pre-built materialized views for analytics.
git clone https://github.com/streamingfast/substreams-spl-token.git
cd substreams-spl-token
# Start ClickHouse database
make db-up
This will:
localhost:8123
(HTTP) and localhost:9000
(native)spl2
databasesubstreams build
# Consume a subset of the chain just for demonstration purposes
substreams-sink-sql from-proto clickhouse://default:@localhost:9000/spl2 ./solana-spl-token-v0.1.0.spkg -s 356312000 -t +10000
# Apply views using the db-query command
docker exec -i spl-token-clickhouse clickhouse-client --database spl2 --multiquery < views.sql
# Open ClickHouse client and enter query there (SELECT count() FROM mints)
make db-shell
# Or run example queries
make db-query QUERY="SELECT count() FROM mints"
The pipeline creates the following tables in ClickHouse:
mints
: Token mint operationsburns
: Token burn operationstransfers
: Token transfer operationsinitialized_accounts
: Account initialization eventsinstructions
: Instruction metadata_blocks_
: Block informationmv_all_mints
: Enhanced mint data with account owner informationmv_all_burns
: Enhanced burn data with account owner informationmv_mint_per_month
: Monthly mint aggregationsmv_burn_per_month
: Monthly burn aggregationsmv_supply
: Current total supply calculationSee example-queries.md for comprehensive query examples. Here are a few quick ones:
SELECT * FROM spl2.mv_supply;
SELECT
block_time,
to_derive_address,
amount / 1000000000 as tokens_minted
FROM spl2.mv_all_mints
ORDER BY block_number DESC
LIMIT 10;
SELECT
m.month,
m.total / 1000000000 as tokens_minted,
COALESCE(b.total, 0) / 1000000000 as tokens_burned,
(m.total - COALESCE(b.total, 0)) / 1000000000 as net_change
FROM spl2.mv_mint_per_month m
LEFT JOIN spl2.mv_burn_per_month b ON m.month = b.month
ORDER BY m.month DESC;
# Database management
make db-up # Start ClickHouse
make db-down # Stop ClickHouse
make db-setup # Initialize database
make db-shell # Open database shell
make db-reset # Reset database (removes all data)
The substreams-sink-sql
tool is required to stream data from Substreams to ClickHouse.
Option 1: Download Pre-built Binary
# Download from GitHub releases
curl -L https://github.com/streamingfast/substreams-sink-sql/releases/latest/download/substreams-sink-sql-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m).tar.gz | tar -xz
sudo mv substreams-sink-sql /usr/local/bin/
Option 2: Build from Source
git clone https://github.com/streamingfast/substreams-sink-sql.git
cd substreams-sink-sql
go build -o substreams-sink-sql ./cmd/substreams-sink-sql
For detailed installation instructions, see the official documentation.
substreams gui solana-spl-token@v0.1.4 map_spl_instructions
substreams gui solana-spl-token@v0.1.4 solana_common:blocks_without_votes
substreams gui solana-spl-token@v0.1.4 solana_common:transactions_by_programid_and_account_without_votes
substreams gui solana-spl-token@v0.1.4 solana_common:program_ids_and_accounts_without_votes