# Solana - SPL Initialized Account

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

```rust
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):

```yaml
specVersion: v0.1.0
package:
  name: solana-spl-token
  version: v0.2.0

imports:
  solana_common: solana-common@v0.3.0
  spl_initialized_account: spl-initialized-account@v0.2.0

modules:
  - name: map_spl_instructions
    kind: map
    initialBlock: 158569587
    inputs:
      - params: string
      - map: solana_common:transactions_by_programid_and_account_without_votes
      - foundational-store: spl-initialized-account@v0.1.2
    output:
      type: proto:sf.solana.spl.v1.type.SplInstructions

params:
  map_spl_instructions: "spl_token_address=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v|spl_token_decimal=6"
  solana_common:transactions_by_programid_and_account_without_votes: "program:TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA || program:TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
```

> **Complete Example**: See the [map\_spl\_instructions](https://github.com/streamingfast/substreams-spl-token/blob/main/src/lib.rs#L49) implementation.

## Data Model

### Key Structure

The store uses SPL token account addresses as keys:

```
{spl_token_account_address} (bytes)
```

### Value Schema

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

```protobuf
syntax = "proto3";

package sf.substreams.solana.spl.v1;

// Represents ownership relationship between a mint and its owner
// Both in raw bytes
message AccountOwner {
  bytes mint_address = 2;
  bytes owner = 3;
}
```

## 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

```rust
use prost::Message;

#[substreams::handlers::map]
pub fn map_spl_initialized_account(
    transactions: SolanaTransactions
) -> Result<SinkEntries, Error> {
    let mut entries: Vec<Entry> = vec![];

    for transaction in transactions.transactions {
        if !transaction.is_successful() {
            continue;
        }

        for instruction in transaction.walk_instructions() {
            // Decode SPL token instruction
            let token_instruction = TokenInstruction::unpack(instruction.data().as_slice())?;

            match token_instruction {
                TokenInstruction::InitializeAccount {} => {
                    let account_owner = AccountOwner {
                        mint_address: instruction.accounts()[1].clone(),
                        owner: instruction.accounts()[2].clone(),
                    };

                    let mut buf = Vec::new();
                    prost::Message::encode(&account_owner, &mut buf).unwrap();

                    entries.push(Entry {
                        key: Some(Key {
                            bytes: instruction.accounts()[0].to_vec(),
                        }),
                        value: Some(Any {
                            type_url: "type.googleapis.com/sf.substreams.solana.spl.v1.AccountOwner".to_string(),
                            value: buf,
                        }),
                    });
                }
                TokenInstruction::InitializeAccount2 { owner } |
                TokenInstruction::InitializeAccount3 { owner } => {
                    // Handle embedded owner in instruction data
                    // ... similar to InitializeAccount
                }
                _ => {}
            }
        }
    }

    Ok(SinkEntries {
        entries,
        if_not_exist: true,
    })
}
```

### Substreams Manifest Configuration

**Producer Module** (creates foundational store entries):

```yaml
specVersion: v0.1.0
package:
  name: spl-initialized-account
  version: v0.2.1

network: solana

imports:
  solana_common: solana-common@v0.3.0

modules:
  - name: map_spl_initialized_account
    kind: map
    initialBlock: 31310775
    inputs:
      - params: string
      - map: solana_common:transactions_by_programid_without_votes
    output:
      type: proto:sf.substreams.foundational_store.model.v2.SinkEntries

params:
  solana_common:transactions_by_programid_without_votes: "program:TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA || program:TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
```

> **Complete Example**: See the [map\_spl\_initialized\_account](https://github.com/streamingfast/substreams-foundational-modules/blob/develop/solana/spl-initialized-account/src/lib.rs#L23) implementation.

## Related Resources

* [Hosting a Foundational Store](/reference-material/operators/hosting-foundational-stores.md)
* [Consuming a Foundational Store](/tutorials/consuming-foundational-store.md)
* [Foundational Stores Architecture](/reference-material/core-concepts/foundational-store-reference.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.substreams.dev/how-to-guides/composing-substreams/foundational-stores/spl-initialized-account.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
