Skip to content

StorageProof

Get merkle paths in one of the state tries: global state, classes, or individual contract storage. A single request can query for any mix of the three types of storage proofs (classes, contracts, and storage). This is useful for verifying state without trusting the RPC provider.

Method Signature

func (provider *Provider) StorageProof(
    ctx context.Context,
    storageProofInput StorageProofInput,
) (*StorageProofResult, error)

Source: contract.go

Parameters

  • ctx (context.Context): Context for request cancellation and timeouts
  • storageProofInput (StorageProofInput): Input containing the block ID and optional proof requests for classes, contracts, and storage

Returns

  • *StorageProofResult: The requested storage proofs with merkle paths and global roots
  • error: Error if the request fails

Type Definitions

  1. Input Layer (StorageProofInput) - Specifies what you want to prove: class hashes, contract addresses, or storage keys
  2. Result Layer (StorageProofResult) - Contains the actual proofs organized by type: classes, contracts, and storage
  3. Merkle Tree Layer (NodeHashToNode, MerkleNode) - The cryptographic proof nodes that form the verification path
  4. Node Types (EdgeNode, BinaryNode) - Two types of nodes in the Merkle-Patricia tree structure
  5. Verification Data (GlobalRoots, ContractLeavesData) - Root hashes and leaf data needed to verify the proofs

Each proof traces a path from the leaf (your requested data) through intermediate nodes up to the global root, allowing you to verify state without trusting the RPC provider.

StorageProofInput

type StorageProofInput struct {
    // Required. The hash of the requested block, or number (height) of the
    // requested block, or a block tag
    BlockID BlockID `json:"block_id"`
 
    // Optional. A list of the class hashes for which we want to prove
    // membership in the classes trie
    ClassHashes []*felt.Felt `json:"class_hashes,omitempty"`
 
    // Optional. A list of contracts for which we want to prove membership in the
    // global state trie
    ContractAddresses []*felt.Felt `json:"contract_addresses,omitempty"`
 
    // Optional. A list of (contract_address, storage_keys) pairs
    ContractsStorageKeys []ContractStorageKeys `json:"contracts_storage_keys,omitempty"`
}

Source: types_contract.go

BlockID

The blockID parameter specifies which block state to query. You can identify a block in three ways:

type BlockID struct {
    Number *uint64    `json:"block_number,omitempty"`
    Hash   *felt.Felt `json:"block_hash,omitempty"`
    Tag    BlockTag   `json:"tag,omitempty"`
}

Source: types_block.go

There are helper functions for creating BlockID. So Instead of manually creating the BlockID struct, use these convenience functions.

  • By Block Number - Get a specific block by its height

    blockID := rpc.WithBlockNumber(123456)

    Source: block.go

  • By Block Hash - Get a specific block by its hash

    hash, _ := new(felt.Felt).SetString("0x1234...")
    blockID := rpc.WithBlockHash(hash)

    Source: block.go

  • By Block Tag - Get a dynamic block reference

    blockID := rpc.WithBlockTag("latest")      // Latest accepted block

    Source: block.go

ContractStorageKeys

type ContractStorageKeys struct {
    ContractAddress *felt.Felt   `json:"contract_address"`
    StorageKeys     []*felt.Felt `json:"storage_keys"`
}

Source: types_contract.go

StorageProofResult

type StorageProofResult struct {
    ClassesProof           []NodeHashToNode   `json:"classes_proof"`
    ContractsProof         ContractsProof     `json:"contracts_proof"`
    ContractsStorageProofs [][]NodeHashToNode `json:"contracts_storage_proofs"`
    GlobalRoots            GlobalRoots        `json:"global_roots"`
}

Source: types_contract.go

ContractsProof

type ContractsProof struct {
    // The nodes in the union of the paths from the contracts tree root to the
    // requested leaves
    Nodes              []NodeHashToNode     `json:"nodes"`
    ContractLeavesData []ContractLeavesData `json:"contract_leaves_data"`
}

Source: types_contract.go

ContractLeavesData

The nonce and class hash for each requested contract address, in the order in which they appear in the request. These values are needed to construct the associated leaf node.

type ContractLeavesData struct {
    Nonce       *felt.Felt `json:"nonce"`
    ClassHash   *felt.Felt `json:"class_hash"`
    StorageRoot *felt.Felt `json:"storage_root,omitempty"`
}

Source: types_contract.go

GlobalRoots

type GlobalRoots struct {
    ContractsTreeRoot *felt.Felt `json:"contracts_tree_root"`
    ClassesTreeRoot   *felt.Felt `json:"classes_tree_root"`
    // the associated block hash (needed in case the caller used a block tag
    // for the block_id parameter)
    BlockHash *felt.Felt `json:"block_hash"`
}

Source: types_contract.go

NodeHashToNode

A node_hash -> node mapping of all the nodes in the union of the paths between the requested leaves and the root.

type NodeHashToNode struct {
    NodeHash *felt.Felt `json:"node_hash"`
    Node     MerkleNode `json:"node"`
}

Source: types_contract.go

MerkleNode

A node in the Merkle-Patricia tree, can be a binary node or an edge node.

type MerkleNode struct {
    Type string
    Data any
}

Source: types_contract.go

The MerkleNode unmarshals into either an EdgeNode or BinaryNode based on the JSON structure.

EdgeNode

Represents a path to the highest non-zero descendant node.

type EdgeNode struct {
    // an unsigned integer whose binary representation represents the path from
    // the current node to its highest non-zero descendant (bounded by 2^251)
    Path NumAsHex `json:"path"`
    // the length of the path (bounded by 251)
    Length uint `json:"length"`
    // the hash of the unique non-zero maximal-height descendant node
    Child *felt.Felt `json:"child"`
}

Source: types_contract.go

BinaryNode

An internal node whose both children are non-zero.

type BinaryNode struct {
    // the hash of the left child
    Left *felt.Felt `json:"left"`
    // the hash of the right child
    Right *felt.Felt `json:"right"`
}

Source: types_contract.go

Usage Example

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
    "github.com/joho/godotenv"
)
 
func main() {
    // Load environment variables from .env file
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
 
    // Get RPC URL from environment variable
    rpcURL := os.Getenv("STARKNET_RPC_URL")
    if rpcURL == "" {
        log.Fatal("STARKNET_RPC_URL not found in .env file")
    }
 
    // Initialize provider
    provider, err := rpc.NewProvider(context.Background(), rpcURL)
    if err != nil {
        log.Fatal(err)
    }
 
    ctx := context.Background()
 
    // Contract address to get storage proof for
    contractAddr, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
 
    // Storage keys to prove
    storageKey1, _ := new(felt.Felt).SetString("0x1")
    storageKey2, _ := new(felt.Felt).SetString("0x2")
 
    // Create storage proof input
    input := rpc.StorageProofInput{
        BlockID: rpc.WithBlockTag("latest"),
        ContractsStorageKeys: []rpc.ContractStorageKeys{
            {
                ContractAddress: contractAddr,
                StorageKeys:     []*felt.Felt{storageKey1, storageKey2},
            },
        },
    }
 
    // Get storage proof
    proof, err := provider.StorageProof(ctx, input)
    if err != nil {
        log.Fatal(err)
    }
 
    // Access proof data
    fmt.Printf("Contracts Tree Root: %s\n", proof.GlobalRoots.ContractsTreeRoot)
    fmt.Printf("Classes Tree Root: %s\n", proof.GlobalRoots.ClassesTreeRoot)
    fmt.Printf("Block Hash: %s\n", proof.GlobalRoots.BlockHash)
    fmt.Printf("Number of contract proof nodes: %d\n", len(proof.ContractsProof.Nodes))
    fmt.Printf("Number of storage proofs: %d\n", len(proof.ContractsStorageProofs))
}

Error Handling

proof, err := provider.StorageProof(ctx, input)
if err != nil {
    if errors.Is(err, rpc.ErrBlockNotFound) {
        log.Printf("Block not found")
        return
    }
    if errors.Is(err, rpc.ErrStorageProofNotSupported) {
        log.Printf("Node doesn't support storage proofs")
        return
    }
    log.Printf("Error retrieving storage proof: %v", err)
    return
}
 
// Verify proof has expected data
if len(proof.ContractsStorageProofs) == 0 {
    log.Printf("Warning: No storage proofs returned")
}

Common Use Cases

  • Trustless State Verification - Verify contract storage values without trusting the RPC provider. See example.
  • Light Client Implementation - Build light clients that can verify state independently. See example.
  • Cross-Chain Bridges - Prove contract state for cross-chain communication. See example.

Related Methods

  • StorageAt - Get storage value without proof
  • StateUpdate - Get all state changes in a block
  • ClassAt - Get class definition at a contract address