Solana - SPL Initialized Account

SPL Initialized Account Foundational Store

A specialized foundational store for tracking SPL token account initializations on Solana. This store provides the essential account-to-owner mappings needed to resolve SPL token transfers, since transfer instructions only contain account addresses without owner information.

Overview

The SPL Initialized Account foundational store provides efficient storage and retrieval of:

  • Account-Owner Mappings: Relationship between SPL token accounts and their owners

  • Mint Associations: Which token mint each account is associated with

  • Initialization Events: Tracking of newly created SPL token accounts

SPL token transfer instructions on Solana only contain account addresses, not the wallet owners. To determine who actually sent/received tokens, you need to resolve account ownership. This foundational store provides that critical mapping.

Consuming Account Owner Data

use substreams::store::FoundationalStore;
use std::collections::HashSet;

#[substreams::handlers::map]
fn map_spl_instructions(
    params: String,
    transactions: SolanaTransactions,
    foundational_store: FoundationalStore,
) -> Result<SplInstructions, Error> {
    // ... extract transfer instructions from transactions

    // Collect account addresses that need owner lookup
    let mut accounts_to_lookup = HashSet::<String>::new();
    accounts_to_lookup.insert(transfer.from.clone());
    accounts_to_lookup.insert(transfer.to.clone());

    // Convert addresses to bytes and query store
    let account_bytes: Vec<Vec<u8>> = accounts_to_lookup
        .iter()
        .filter_map(|addr| bs58::decode(addr).into_vec().ok())
        .collect();

    let resp = foundational_store.get(&account_bytes);

    // Process responses and decode owner data
    for queried_entry in resp.entries {
        if queried_entry.code != ResponseCode::Found as i32 {
            continue;
        }
        let Some(entry) = &queried_entry.entry else { continue; };
        let Some(value) = &entry.value else { continue; };

        let Ok(account_owner) = AccountOwner::decode(value.value.as_slice()) else {
            continue;
        };

        // Use owner data to enrich transfers
        let owner_b58 = bs58::encode(&account_owner.owner).into_string();
        transfer.from_owner = owner_b58;
    }

    Ok(SplInstructions { instructions })
}

Consumer Module (uses foundational store as input):

Complete Example: See the map_spl_instructions implementation.

Data Model

Key Structure

The store uses SPL token account addresses as keys:

Value Schema

AccountOwner (type.googleapis.com/sf.substreams.solana.spl.v1.AccountOwner)

Implementation details

The foundational store processes SPL token account initialization instructions to extract account-to-owner mappings. It tracks three instruction types:

  1. InitializeAccount - Basic account initialization with separate owner account

  2. InitializeAccount2 - Account initialization with embedded owner in instruction data

  3. InitializeAccount3 - Newer variant of account initialization with embedded owner

Each SPL token account address becomes a key, with the corresponding AccountOwner protobuf message containing mint and owner information as the value.

Implementation

Creating Foundational Store Entries

Substreams Manifest Configuration

Producer Module (creates foundational store entries):

Complete Example: See the map_spl_initialized_account implementation.

Last updated

Was this helpful?