Skip to content

RPC Examples

Comprehensive examples for using Starknet RPC methods.

Blockchain Indexing

Index all state changes for analytics and querying:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "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()
 
    // Get state update for specific block
    blockID := rpc.WithBlockNumber(100000)
    stateUpdate, err := provider.StateUpdate(ctx, blockID)
    if err != nil {
        log.Fatal(err)
    }
 
    fmt.Printf("=== State Update for Block #100000 ===\n")
    fmt.Printf("Block Hash: %s\n", stateUpdate.BlockHash)
    fmt.Printf("Old Root: %s\n", stateUpdate.OldRoot)
    fmt.Printf("New Root: %s\n", stateUpdate.NewRoot)
    fmt.Printf("\n")
 
    // Index storage changes
    fmt.Printf("=== Storage Changes ===\n")
    for i, storageDiff := range stateUpdate.StateDiff.StorageDiffs {
        fmt.Printf("%d. Contract: %s\n", i+1, storageDiff.Address)
        fmt.Printf("   Storage entries modified: %d\n", len(storageDiff.StorageEntries))
 
        // Show first few storage entries
        for j, entry := range storageDiff.StorageEntries {
            if j >= 3 {
                fmt.Printf("   ... and %d more entries\n", len(storageDiff.StorageEntries)-3)
                break
            }
            fmt.Printf("   Key: %s -> Value: %s\n", entry.Key, entry.Value)
        }
    }
    fmt.Printf("\n")
 
    // Index declared classes
    fmt.Printf("=== Declared Classes ===\n")
    for i, declaredClass := range stateUpdate.StateDiff.DeclaredClasses {
        fmt.Printf("%d. Class Hash: %s\n", i+1, declaredClass.ClassHash)
        fmt.Printf("   Compiled Class Hash: %s\n", declaredClass.CompiledClassHash)
    }
    fmt.Printf("\n")
 
    // Index deployed contracts
    fmt.Printf("=== Deployed Contracts ===\n")
    for i, deployed := range stateUpdate.StateDiff.DeployedContracts {
        fmt.Printf("%d. Address: %s\n", i+1, deployed.Address)
        fmt.Printf("   Class Hash: %s\n", deployed.ClassHash)
    }
    fmt.Printf("\n")
 
    // Index nonce updates
    fmt.Printf("=== Nonce Updates ===\n")
    for i, nonceUpdate := range stateUpdate.StateDiff.Nonces {
        fmt.Printf("%d. Contract: %s\n", i+1, nonceUpdate.ContractAddress)
        fmt.Printf("   New Nonce: %s\n", nonceUpdate.Nonce)
    }
    fmt.Printf("\n")
 
    // Summary
    fmt.Printf("=== Summary ===\n")
    fmt.Printf("Total contracts with storage changes: %d\n", len(stateUpdate.StateDiff.StorageDiffs))
    fmt.Printf("Total declared classes: %d\n", len(stateUpdate.StateDiff.DeclaredClasses))
    fmt.Printf("Total deployed contracts: %d\n", len(stateUpdate.StateDiff.DeployedContracts))
    fmt.Printf("Total nonce updates: %d\n", len(stateUpdate.StateDiff.Nonces))
}

Transaction Volume Monitoring

Track network activity by monitoring transaction counts over time:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
    "time"
 
    "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()
 
    // Monitor transaction volume every 10 seconds
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
 
    fmt.Println("Monitoring transaction volume...")
    var txCounts []uint64
    var blockNumbers []uint64
 
    for range ticker.C {
        // Get latest block number
        blockNum, err := provider.BlockNumber(ctx)
        if err != nil {
            log.Printf("Error getting block number: %v", err)
            continue
        }
 
        // Get transaction count
        blockID := rpc.WithBlockNumber(blockNum)
        txCount, err := provider.BlockTransactionCount(ctx, blockID)
        if err != nil {
            log.Printf("Error getting tx count: %v", err)
            continue
        }
 
        blockNumbers = append(blockNumbers, blockNum)
        txCounts = append(txCounts, txCount)
 
        fmt.Printf("Block #%d: %d transactions\n", blockNum, txCount)
 
        // Calculate average over last 10 blocks
        if len(txCounts) >= 10 {
            var sum uint64
            for i := len(txCounts) - 10; i < len(txCounts); i++ {
                sum += txCounts[i]
            }
            avg := float64(sum) / 10.0
            fmt.Printf("Average tx/block (last 10): %.2f\n\n", avg)
        }
    }
}

Block Analysis

Quickly identify busy blocks without fetching full transaction data:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "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()
 
    // Analyze last 100 blocks
    latestBlock, err := provider.BlockNumber(ctx)
    if err != nil {
        log.Fatal(err)
    }
 
    fmt.Printf("Analyzing blocks %d to %d...\n\n", latestBlock-99, latestBlock)
 
    var maxCount uint64
    var maxBlockNum uint64
    var emptyBlocks int
    var totalTxs uint64
 
    for i := latestBlock - 99; i <= latestBlock; i++ {
        blockID := rpc.WithBlockNumber(i)
        txCount, err := provider.BlockTransactionCount(ctx, blockID)
        if err != nil {
            log.Printf("Error at block %d: %v", i, err)
            continue
        }
 
        totalTxs += txCount
 
        // Track busiest block
        if txCount > maxCount {
            maxCount = txCount
            maxBlockNum = i
        }
 
        // Track empty blocks
        if txCount == 0 {
            emptyBlocks++
        }
    }
 
    // Statistics
    fmt.Printf("=== Block Analysis Summary ===\n")
    fmt.Printf("Blocks analyzed: 100\n")
    fmt.Printf("Total transactions: %d\n", totalTxs)
    fmt.Printf("Average tx/block: %.2f\n", float64(totalTxs)/100.0)
    fmt.Printf("Busiest block: #%d with %d transactions\n", maxBlockNum, maxCount)
    fmt.Printf("Empty blocks: %d\n", emptyBlocks)
}

Transaction Outcome Analysis

Analyze complete transaction execution including gas costs and events:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "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()
 
    // Get specific block with receipts
    blockID := rpc.WithBlockNumber(100000)
    result, err := provider.BlockWithReceipts(ctx, blockID)
    if err != nil {
        log.Fatal(err)
    }
 
    // Type assert to BlockWithReceipts
    block, ok := result.(*rpc.BlockWithReceipts)
    if !ok {
        log.Fatal("Unexpected block type")
    }
 
    fmt.Printf("=== Block #%d Analysis ===\n", block.Number)
    fmt.Printf("Hash: %s\n", block.Hash)
    fmt.Printf("Total Transactions: %d\n\n", len(block.Transactions))
 
    // Analyze each transaction and its receipt
    var totalGasUsed uint64
    var successCount, failCount int
 
    for i, txWithReceipt := range block.Transactions {
        receipt := txWithReceipt.Receipt
 
        fmt.Printf("Transaction %d:\n", i+1)
        fmt.Printf("  Hash: %s\n", receipt.TransactionHash)
        fmt.Printf("  Type: %s\n", receipt.Type())
        fmt.Printf("  Status: %s\n", receipt.ExecutionStatus())
        fmt.Printf("  Finality: %s\n", receipt.FinalityStatus())
 
        // Track execution status
        if receipt.ExecutionStatus() == "SUCCEEDED" {
            successCount++
        } else {
            failCount++
        }
 
        // Gas analysis
        if execReceipt, ok := receipt.(interface{ ExecutionResources() rpc.ExecutionResources }); ok {
            resources := execReceipt.ExecutionResources()
            l2Gas := resources.L2Gas
            totalGasUsed += l2Gas
            fmt.Printf("  L2 Gas Used: %d\n", l2Gas)
        }
 
        // Events emitted
        if execReceipt, ok := receipt.(interface{ Events() []rpc.Event }); ok {
            events := execReceipt.Events()
            fmt.Printf("  Events Emitted: %d\n", len(events))
        }
 
        fmt.Println()
    }
 
    // Summary
    fmt.Printf("=== Block Summary ===\n")
    fmt.Printf("Successful Transactions: %d\n", successCount)
    fmt.Printf("Failed Transactions: %d\n", failCount)
    fmt.Printf("Total L2 Gas Used: %d\n", totalGasUsed)
}

Lightweight Block Monitoring

Monitor blocks efficiently without downloading full transaction data:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
    "time"
 
    "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()
 
    // Monitor blocks every 10 seconds
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
 
    fmt.Println("Monitoring blocks...")
    for range ticker.C {
        blockID := rpc.WithBlockTag("latest")
        result, err := provider.BlockWithTxHashes(ctx, blockID)
        if err != nil {
            log.Printf("Error: %v", err)
            continue
        }
 
        // Type assert to BlockTxHashes
        block, ok := result.(*rpc.BlockTxHashes)
        if !ok {
            log.Printf("Unexpected block type")
            continue
        }
 
        fmt.Printf("\nBlock #%d\n", block.Number)
        fmt.Printf("Hash: %s\n", block.Hash)
        fmt.Printf("Transactions: %d\n", len(block.Transactions))
        fmt.Printf("Status: %s\n", block.Status)
    }
}

Transaction Inclusion Check

Quickly verify if a transaction hash is included in a block:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
    "github.com/joho/godotenv"
)
 
func isTransactionInBlock(ctx context.Context, provider *rpc.Provider, blockNum uint64, txHash string) (bool, error) {
    blockID := rpc.WithBlockNumber(blockNum)
    result, err := provider.BlockWithTxHashes(ctx, blockID)
    if err != nil {
        return false, fmt.Errorf("failed to get block: %w", err)
    }
 
    // Type assert to BlockTxHashes
    block, ok := result.(*rpc.BlockTxHashes)
    if !ok {
        return false, fmt.Errorf("unexpected block type")
    }
 
    // Check if transaction hash is in the block
    for _, hash := range block.Transactions {
        if hash.String() == txHash {
            return true, nil
        }
    }
 
    return false, nil
}
 
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()
 
    // Check if a specific transaction is in a block
    txHash := "0x233dc4da3ba77d376a8a1fcf5148a363d3e74f4e9e2b067639423256dc6ba05"
    blockNum := uint64(100000)
 
    found, err := isTransactionInBlock(ctx, provider, blockNum, txHash)
    if err != nil {
        log.Fatal(err)
    }
 
    if found {
        fmt.Printf("โœ“ Transaction %s found in block #%d\n", txHash, blockNum)
    } else {
        fmt.Printf("โœ— Transaction %s not found in block #%d\n", txHash, blockNum)
    }
}

Transaction Analysis

Analyze all transactions in a block with full details:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "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()
 
    // Get latest block with full transactions
    blockID := rpc.WithBlockTag("latest")
    result, err := provider.BlockWithTxs(ctx, blockID)
    if err != nil {
        log.Fatal(err)
    }
 
    // Type assert to Block
    block, ok := result.(*rpc.Block)
    if !ok {
        log.Fatal("Unexpected block type")
    }
 
    fmt.Printf("Block #%d (%s)\n", block.Number, block.Hash)
    fmt.Printf("Total transactions: %d\n\n", len(block.Transactions))
 
    // Analyze transactions by type
    txTypes := make(map[string]int)
    for _, tx := range block.Transactions {
        txType := tx.Type()
        txTypes[txType]++
    }
 
    fmt.Println("Transaction types:")
    for txType, count := range txTypes {
        fmt.Printf("  %s: %d\n", txType, count)
    }
}

Block Explorer

Display complete block information including all transaction data:

package main
 
import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "os"
 
    "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()
 
    // Get specific block by number
    blockID := rpc.WithBlockNumber(100000)
    result, err := provider.BlockWithTxs(ctx, blockID)
    if err != nil {
        log.Fatal(err)
    }
 
    // Type assert to Block
    block, ok := result.(*rpc.Block)
    if !ok {
        log.Fatal("Unexpected block type")
    }
 
    // Display block header information
    fmt.Printf("=== Block #%d ===\n", block.Number)
    fmt.Printf("Hash: %s\n", block.Hash)
    fmt.Printf("Parent Hash: %s\n", block.ParentHash)
    fmt.Printf("Timestamp: %d\n", block.Timestamp)
    fmt.Printf("Sequencer: %s\n", block.SequencerAddress)
    fmt.Printf("Status: %s\n", block.Status)
    fmt.Printf("Starknet Version: %s\n", block.StarknetVersion)
    fmt.Printf("\n")
 
    // Display transactions
    fmt.Printf("Transactions (%d):\n", len(block.Transactions))
    for i, tx := range block.Transactions {
        fmt.Printf("\n%d. Type: %s\n", i+1, tx.Type())
 
        // Pretty print transaction details
        txJSON, _ := json.MarshalIndent(tx, "   ", "  ")
        fmt.Printf("   %s\n", txJSON)
    }
}

Efficient Block Tracking

Get both hash and number in one call instead of making two separate requests:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
    "time"
 
    "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()
 
    // Track block changes efficiently
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
 
    var lastBlockHash string
    fmt.Println("Tracking blocks...")
 
    for range ticker.C {
        result, err := provider.BlockHashAndNumber(ctx)
        if err != nil {
            log.Printf("Error: %v", err)
            continue
        }
 
        currentHash := result.Hash.String()
 
        // Detect new block
        if currentHash != lastBlockHash {
            fmt.Printf("New block detected!\n")
            fmt.Printf("  Number: %d\n", result.Number)
            fmt.Printf("  Hash: %s\n", currentHash)
            lastBlockHash = currentHash
        } else {
            fmt.Printf("Still on block %d\n", result.Number)
        }
    }
}

Block Verification

Verify you have the correct block by checking both its number and hash:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
    "github.com/joho/godotenv"
)
 
func verifyBlock(ctx context.Context, provider *rpc.Provider, expectedNumber uint64, expectedHash string) error {
    result, err := provider.BlockHashAndNumber(ctx)
    if err != nil {
        return fmt.Errorf("failed to get block info: %w", err)
    }
 
    // Verify block number
    if result.Number != expectedNumber {
        return fmt.Errorf("block number mismatch: expected %d, got %d", expectedNumber, result.Number)
    }
 
    // Verify block hash
    if result.Hash.String() != expectedHash {
        return fmt.Errorf("block hash mismatch: expected %s, got %s", expectedHash, result.Hash.String())
    }
 
    fmt.Printf("โœ“ Block verified: #%d with hash %s\n", result.Number, result.Hash.String())
    return nil
}
 
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()
 
    // Get current block to use as reference
    current, err := provider.BlockHashAndNumber(ctx)
    if err != nil {
        log.Fatal(err)
    }
 
    fmt.Printf("Current block: #%d, hash: %s\n", current.Number, current.Hash.String())
 
    // Verify the block
    err = verifyBlock(ctx, provider, current.Number, current.Hash.String())
    if err != nil {
        log.Fatalf("Verification failed: %v", err)
    }
}

Monitor Network Height

Poll for new blocks at regular intervals:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
    "time"
 
    "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()
 
    // Poll for new blocks every 10 seconds
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
 
    fmt.Println("Monitoring network height...")
    for range ticker.C {
        blockNum, err := provider.BlockNumber(ctx)
        if err != nil {
            log.Printf("Error: %v", err)
            continue
        }
        fmt.Printf("Current block: %d\n", blockNum)
    }
}

Wait for Specific Block Height

Wait until the network reaches a specific block number:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
    "time"
 
    "github.com/NethermindEth/starknet.go/rpc"
    "github.com/joho/godotenv"
)
 
func waitForBlock(ctx context.Context, provider *rpc.Provider, targetBlock uint64) error {
    fmt.Printf("Waiting for block %d...\n", targetBlock)
    for {
        currentBlock, err := provider.BlockNumber(ctx)
        if err != nil {
            return err
        }
 
        if currentBlock >= targetBlock {
            fmt.Printf("Reached block %d\n", currentBlock)
            return nil
        }
 
        fmt.Printf("Current block: %d, waiting...\n", currentBlock)
        time.Sleep(2 * time.Second)
    }
}
 
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()
 
    // Get current block
    currentBlock, err := provider.BlockNumber(ctx)
    if err != nil {
        log.Fatal(err)
    }
 
    // Wait for 5 blocks ahead
    targetBlock := currentBlock + 5
    err = waitForBlock(ctx, provider, targetBlock)
    if err != nil {
        log.Fatal(err)
    }
}

Proving Class Membership

Prove that specific classes exist in the classes trie:

package main
 
import (
    "context"
    "fmt"
    "log"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
)
 
func main() {
    provider, err := rpc.NewProvider(context.Background(), "https://starknet-sepolia.public.blastapi.io/rpc/v0_7")
    if err != nil {
        log.Fatal(err)
    }
 
    // Class hashes to prove membership for
    classHash1, _ := new(felt.Felt).SetString("0x01a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003")
    classHash2, _ := new(felt.Felt).SetString("0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918")
 
    input := rpc.StorageProofInput{
        BlockID:     rpc.BlockID{Tag: "latest"},
        ClassHashes: []*felt.Felt{classHash1, classHash2},
    }
 
    proof, err := provider.StorageProof(context.Background(), input)
    if err != nil {
        log.Fatal(err)
    }
 
    fmt.Printf("Classes proof nodes: %d\n", len(proof.ClassesProof))
 
    // Iterate through class proofs
    for i, nodeMapping := range proof.ClassesProof {
        fmt.Printf("Class proof node %d:\n", i)
        fmt.Printf("  Node Hash: %s\n", nodeMapping.NodeHash)
        fmt.Printf("  Node Type: %s\n", nodeMapping.Node.Type)
    }
}

Proving Contract Existence

Prove that specific contracts exist in the global state trie:

package main
 
import (
    "context"
    "fmt"
    "log"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
)
 
func main() {
    provider, err := rpc.NewProvider(context.Background(), "https://starknet-sepolia.public.blastapi.io/rpc/v0_7")
    if err != nil {
        log.Fatal(err)
    }
 
    // Contract addresses to prove membership for
    contract1, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
    contract2, _ := new(felt.Felt).SetString("0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d")
 
    input := rpc.StorageProofInput{
        BlockID:           rpc.BlockID{Tag: "latest"},
        ContractAddresses: []*felt.Felt{contract1, contract2},
    }
 
    proof, err := provider.StorageProof(context.Background(), input)
    if err != nil {
        log.Fatal(err)
    }
 
    // Access contract leaves data
    for i, leafData := range proof.ContractsProof.ContractLeavesData {
        fmt.Printf("Contract %d:\n", i)
        fmt.Printf("  Nonce: %s\n", leafData.Nonce)
        fmt.Printf("  Class Hash: %s\n", leafData.ClassHash)
        if leafData.StorageRoot != nil {
            fmt.Printf("  Storage Root: %s\n", leafData.StorageRoot)
        }
    }
}

Mixed Proof Request

Request all three types of proofs (classes, contracts, and storage) in a single call:

package main
 
import (
    "context"
    "fmt"
    "log"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
)
 
func main() {
    provider, err := rpc.NewProvider(context.Background(), "https://starknet-sepolia.public.blastapi.io/rpc/v0_7")
    if err != nil {
        log.Fatal(err)
    }
 
    classHash, _ := new(felt.Felt).SetString("0x01a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003")
    contractAddr, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
    storageKey, _ := new(felt.Felt).SetString("0x1")
 
    input := rpc.StorageProofInput{
        BlockID:           rpc.BlockID{Number: 100000},
        ClassHashes:       []*felt.Felt{classHash},
        ContractAddresses: []*felt.Felt{contractAddr},
        ContractsStorageKeys: []rpc.ContractStorageKeys{
            {
                ContractAddress: contractAddr,
                StorageKeys:     []*felt.Felt{storageKey},
            },
        },
    }
 
    proof, err := provider.StorageProof(context.Background(), input)
    if err != nil {
        log.Fatal(err)
    }
 
    // You now have proofs for all three: classes, contracts, and storage
    fmt.Printf("Classes proof nodes: %d\n", len(proof.ClassesProof))
    fmt.Printf("Contracts proof nodes: %d\n", len(proof.ContractsProof.Nodes))
    fmt.Printf("Storage proofs: %d\n", len(proof.ContractsStorageProofs))
 
    // Access global roots
    fmt.Printf("\nGlobal Roots:\n")
    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)
}

Processing Merkle Nodes

Handle different Merkle node types (EdgeNode and BinaryNode):

package main
 
import (
    "context"
    "fmt"
    "log"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
)
 
func main() {
    provider, err := rpc.NewProvider(context.Background(), "https://starknet-sepolia.public.blastapi.io/rpc/v0_7")
    if err != nil {
        log.Fatal(err)
    }
 
    contractAddr, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
 
    input := rpc.StorageProofInput{
        BlockID:           rpc.BlockID{Tag: "latest"},
        ContractAddresses: []*felt.Felt{contractAddr},
    }
 
    proof, err := provider.StorageProof(context.Background(), input)
    if err != nil {
        log.Fatal(err)
    }
 
    // Iterate through contract proof nodes
    for i, nodeMapping := range proof.ContractsProof.Nodes {
        fmt.Printf("Node %d hash: %s\n", i, nodeMapping.NodeHash)
 
        // Check the type of merkle node
        switch nodeMapping.Node.Type {
        case "BinaryNode":
            binaryNode := nodeMapping.Node.Data.(rpc.BinaryNode)
            fmt.Printf("  Binary node - Left: %s, Right: %s\n", binaryNode.Left, binaryNode.Right)
        case "EdgeNode":
            edgeNode := nodeMapping.Node.Data.(rpc.EdgeNode)
            fmt.Printf("  Edge node - Path: %s, Length: %d, Child: %s\n",
                edgeNode.Path, edgeNode.Length, edgeNode.Child)
        }
    }
}

Historical State Proofs

Get proofs for a specific block in the past:

package main
 
import (
    "context"
    "fmt"
    "log"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
)
 
func main() {
    provider, err := rpc.NewProvider(context.Background(), "https://starknet-sepolia.public.blastapi.io/rpc/v0_7")
    if err != nil {
        log.Fatal(err)
    }
 
    contractAddr, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
    storageKey1, _ := new(felt.Felt).SetString("0x1")
    storageKey2, _ := new(felt.Felt).SetString("0x2")
 
    // Get proof at block height 100000
    input := rpc.StorageProofInput{
        BlockID: rpc.BlockID{Number: 100000},
        ContractsStorageKeys: []rpc.ContractStorageKeys{
            {
                ContractAddress: contractAddr,
                StorageKeys:     []*felt.Felt{storageKey1, storageKey2},
            },
        },
    }
 
    proof, err := provider.StorageProof(context.Background(), input)
    if err != nil {
        log.Fatal(err)
    }
 
    // The proof is valid for the state at block 100000
    fmt.Printf("Proof for block: %s\n", proof.GlobalRoots.BlockHash)
    fmt.Printf("Storage proofs count: %d\n", len(proof.ContractsStorageProofs))
 
    // Each storage proof corresponds to a contract's storage
    for i, storageProof := range proof.ContractsStorageProofs {
        fmt.Printf("Storage proof %d has %d nodes\n", i, len(storageProof))
    }
}

Multiple Contracts with Multiple Storage Keys

Prove storage for multiple contracts with multiple keys each:

package main
 
import (
    "context"
    "fmt"
    "log"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
)
 
func main() {
    provider, err := rpc.NewProvider(context.Background(), "https://starknet-sepolia.public.blastapi.io/rpc/v0_7")
    if err != nil {
        log.Fatal(err)
    }
 
    // ETH token contract
    ethContract, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
    // STRK token contract
    strkContract, _ := new(felt.Felt).SetString("0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d")
 
    // Storage keys for ETH contract
    ethKey1, _ := new(felt.Felt).SetString("0x1")
    ethKey2, _ := new(felt.Felt).SetString("0x2")
 
    // Storage keys for STRK contract
    strkKey1, _ := new(felt.Felt).SetString("0x1")
    strkKey2, _ := new(felt.Felt).SetString("0x2")
 
    input := rpc.StorageProofInput{
        BlockID: rpc.BlockID{Tag: "latest"},
        ContractsStorageKeys: []rpc.ContractStorageKeys{
            {
                ContractAddress: ethContract,
                StorageKeys:     []*felt.Felt{ethKey1, ethKey2},
            },
            {
                ContractAddress: strkContract,
                StorageKeys:     []*felt.Felt{strkKey1, strkKey2},
            },
        },
    }
 
    proof, err := provider.StorageProof(context.Background(), input)
    if err != nil {
        log.Fatal(err)
    }
 
    fmt.Printf("Total storage proofs: %d\n", len(proof.ContractsStorageProofs))
 
    // First proof is for ETH contract
    fmt.Printf("ETH contract storage proof nodes: %d\n", len(proof.ContractsStorageProofs[0]))
 
    // Second proof is for STRK contract
    if len(proof.ContractsStorageProofs) > 1 {
        fmt.Printf("STRK contract storage proof nodes: %d\n", len(proof.ContractsStorageProofs[1]))
    }
}

Light Client Verification Pattern

Example pattern for light client to verify storage without full state:

package main
 
import (
    "context"
    "fmt"
    "log"
 
    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/rpc"
)
 
func main() {
    provider, err := rpc.NewProvider(context.Background(), "https://starknet-sepolia.public.blastapi.io/rpc/v0_7")
    if err != nil {
        log.Fatal(err)
    }
 
    // Light client scenario: verify a storage value
    contractAddr, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
    storageKey, _ := new(felt.Felt).SetString("0x1")
 
    input := rpc.StorageProofInput{
        BlockID: rpc.BlockID{Tag: "latest"},
        ContractsStorageKeys: []rpc.ContractStorageKeys{
            {
                ContractAddress: contractAddr,
                StorageKeys:     []*felt.Felt{storageKey},
            },
        },
    }
 
    proof, err := provider.StorageProof(context.Background(), input)
    if err != nil {
        log.Fatal(err)
    }
 
    // Light client now has:
    // 1. Global roots (contracts tree root, classes tree root, block hash)
    // 2. Contract proof nodes
    // 3. Storage proof nodes
 
    fmt.Printf("Verification data:\n")
    fmt.Printf("  Block Hash: %s\n", proof.GlobalRoots.BlockHash)
    fmt.Printf("  Contracts Tree Root: %s\n", proof.GlobalRoots.ContractsTreeRoot)
    fmt.Printf("  Contract Proof Nodes: %d\n", len(proof.ContractsProof.Nodes))
    fmt.Printf("  Storage Proof Nodes: %d\n", len(proof.ContractsStorageProofs[0]))
 
    // Light client can now verify the storage value against the trusted block hash
    // without downloading the entire state
}

TransactionReceipt

Gas Analysis and Fee Tracking

Analyze actual gas consumption, fees paid, and execution resources:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "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()
 
    // First, get a transaction hash from the latest block
    blockID := rpc.WithBlockTag("latest")
    blockResult, err := provider.BlockWithTxHashes(ctx, blockID)
    if err != nil {
        log.Fatal(err)
    }
 
    // Type assert to BlockTxHashes
    block, ok := blockResult.(*rpc.BlockTxHashes)
    if !ok {
        log.Fatal("Unexpected block type")
    }
 
    if len(block.Transactions) == 0 {
        log.Fatal("No transactions in latest block")
    }
 
    // Get receipt for the first transaction
    txHash := block.Transactions[0]
    receipt, err := provider.TransactionReceipt(ctx, txHash)
    if err != nil {
        log.Fatal(err)
    }
 
    // Display transaction information
    fmt.Printf("=== Transaction Receipt ===\n")
    fmt.Printf("Transaction Hash: %s\n", receipt.Hash)
    fmt.Printf("Transaction Type: %s\n", receipt.Type)
    fmt.Printf("Block Number: %d\n", receipt.BlockNumber)
    fmt.Printf("Block Hash: %s\n", receipt.BlockHash)
    fmt.Printf("\n")
 
    // Display execution status
    fmt.Printf("=== Execution Status ===\n")
    fmt.Printf("Finality Status: %s\n", receipt.FinalityStatus)
    fmt.Printf("Execution Status: %s\n", receipt.ExecutionStatus)
    if receipt.RevertReason != "" {
        fmt.Printf("Revert Reason: %s\n", receipt.RevertReason)
    }
    fmt.Printf("\n")
 
    // Display gas and fee information
    fmt.Printf("=== Gas and Fee Analysis ===\n")
    fmt.Printf("Actual Fee: %s %s\n", receipt.ActualFee.Amount, receipt.ActualFee.Unit)
    fmt.Printf("\n")
 
    // Display execution resources
    fmt.Printf("=== Execution Resources ===\n")
    resources := receipt.ExecutionResources
    fmt.Printf("L1 Gas: %d\n", resources.L1Gas)
    fmt.Printf("L1 Data Gas: %d\n", resources.L1DataGas)
    fmt.Printf("L2 Gas: %d\n", resources.L2Gas)
    fmt.Printf("\n")
 
    // Display events emitted
    fmt.Printf("=== Events Emitted ===\n")
    fmt.Printf("Total Events: %d\n", len(receipt.Events))
    for i, event := range receipt.Events {
        if i >= 3 {
            fmt.Printf("... and %d more events\n", len(receipt.Events)-3)
            break
        }
        fmt.Printf("\nEvent %d:\n", i+1)
        fmt.Printf("  From Contract: %s\n", event.FromAddress)
        fmt.Printf("  Keys: %d\n", len(event.Keys))
        fmt.Printf("  Data: %d elements\n", len(event.Data))
    }
    fmt.Printf("\n")
 
    // Display messages sent to L1
    if len(receipt.MessagesSent) > 0 {
        fmt.Printf("=== Messages to L1 ===\n")
        fmt.Printf("Total Messages: %d\n", len(receipt.MessagesSent))
        for i, msg := range receipt.MessagesSent {
            fmt.Printf("\nMessage %d:\n", i+1)
            fmt.Printf("  From Address: %s\n", msg.FromAddress)
            fmt.Printf("  To Address: %s\n", msg.ToAddress)
            fmt.Printf("  Payload Length: %d\n", len(msg.Payload))
        }
        fmt.Printf("\n")
    }
 
    // Calculate cost efficiency metrics
    if receipt.ExecutionStatus == rpc.TxnExecutionStatusSUCCEEDED {
        fmt.Printf("=== Cost Efficiency ===\n")
        totalL2Gas := resources.L2Gas
        if totalL2Gas > 0 {
            fmt.Printf("Total L2 Gas: %d\n", totalL2Gas)
            fmt.Printf("Events Emitted per 1000 L2 Gas: %.2f\n",
                float64(len(receipt.Events))*1000/float64(totalL2Gas))
        }
    }
}

Contract Upgrade Detection

Track when a contract's implementation changes by monitoring its class hash over time:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
    "time"
 
    "github.com/NethermindEth/starknet.go/rpc"
    "github.com/NethermindEth/starknet.go/utils"
    "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 monitor (e.g., ETH on Sepolia)
    contractAddress, err := utils.HexToFelt("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
    if err != nil {
        log.Fatal(err)
    }
 
    fmt.Printf("Monitoring contract for upgrades: %s\n\n", contractAddress)
 
    // Get current block number
    currentBlock, err := provider.BlockNumber(ctx)
    if err != nil {
        log.Fatal(err)
    }
 
    // Track class hash across recent blocks (last 100 blocks)
    var previousClassHash string
    upgradeDetected := false
 
    startBlock := currentBlock - 100
    if startBlock < 0 {
        startBlock = 0
    }
 
    for blockNum := startBlock; blockNum <= currentBlock; blockNum++ {
        blockID := rpc.WithBlockNumber(blockNum)
 
        classHash, err := provider.ClassHashAt(ctx, blockID, contractAddress)
        if err != nil {
            // Contract might not exist in earlier blocks
            continue
        }
 
        classHashStr := classHash.String()
 
        if previousClassHash == "" {
            previousClassHash = classHashStr
            fmt.Printf("Block %d: Initial class hash: %s\n", blockNum, classHashStr)
        } else if classHashStr != previousClassHash {
            fmt.Printf("\n*** UPGRADE DETECTED at block %d! ***\n", blockNum)
            fmt.Printf("  Previous class hash: %s\n", previousClassHash)
            fmt.Printf("  New class hash:      %s\n", classHashStr)
            previousClassHash = classHashStr
            upgradeDetected = true
        }
    }
 
    if !upgradeDetected {
        fmt.Printf("\nNo upgrades detected in the last %d blocks\n", currentBlock-startBlock)
        fmt.Printf("Current class hash: %s\n", previousClassHash)
    }
 
    // Real-time monitoring example (monitor for 60 seconds)
    fmt.Printf("\n=== Starting Real-Time Monitoring ===\n")
    fmt.Printf("Monitoring for 60 seconds...\n\n")
 
    monitoringStart := time.Now()
    lastCheckedBlock := currentBlock
 
    for time.Since(monitoringStart) < 60*time.Second {
        time.Sleep(5 * time.Second)
 
        latestBlock, err := provider.BlockNumber(ctx)
        if err != nil {
            log.Printf("Error getting latest block: %v", err)
            continue
        }
 
        if latestBlock > lastCheckedBlock {
            blockID := rpc.WithBlockNumber(latestBlock)
 
            classHash, err := provider.ClassHashAt(ctx, blockID, contractAddress)
            if err != nil {
                log.Printf("Error getting class hash: %v", err)
                continue
            }
 
            classHashStr := classHash.String()
 
            if classHashStr != previousClassHash {
                fmt.Printf("\n*** UPGRADE DETECTED at block %d! ***\n", latestBlock)
                fmt.Printf("  Previous class hash: %s\n", previousClassHash)
                fmt.Printf("  New class hash:      %s\n", classHashStr)
                previousClassHash = classHashStr
            } else {
                fmt.Printf("Block %d: No change (class hash: %s...)\n",
                    latestBlock, classHashStr[:10])
            }
 
            lastCheckedBlock = latestBlock
        }
    }
 
    fmt.Printf("\nMonitoring complete.\n")
}

Implementation Verification

Verify that a contract is using the expected implementation class:

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
 
    "github.com/NethermindEth/starknet.go/rpc"
    "github.com/NethermindEth/starknet.go/utils"
    "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()
 
    // Define contracts to verify
    type ContractInfo struct {
        Name              string
        Address           string
        ExpectedClassHash string
    }
 
    contracts := []ContractInfo{
        {
            Name:              "ETH Token",
            Address:           "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
            ExpectedClassHash: "0x076791ef97c042f81fbf352ad95f39a22554ee8d7927b2ce3c681f3418b5206a",
        },
        // Add more contracts as needed
    }
 
    fmt.Printf("=== Contract Implementation Verification ===\n\n")
 
    blockID := rpc.WithBlockTag("latest")
    allValid := true
 
    for _, contract := range contracts {
        fmt.Printf("Checking: %s\n", contract.Name)
        fmt.Printf("  Address: %s\n", contract.Address)
 
        // Parse contract address
        contractAddr, err := utils.HexToFelt(contract.Address)
        if err != nil {
            log.Printf("  [ERROR] Error parsing address: %v\n\n", err)
            allValid = false
            continue
        }
 
        // Get actual class hash
        actualClassHash, err := provider.ClassHashAt(ctx, blockID, contractAddr)
        if err != nil {
            log.Printf("  [ERROR] Error getting class hash: %v\n\n", err)
            allValid = false
            continue
        }
 
        actualClassHashStr := actualClassHash.String()
 
        // Parse expected class hash
        expectedClassHash, err := utils.HexToFelt(contract.ExpectedClassHash)
        if err != nil {
            log.Printf("  [ERROR] Error parsing expected class hash: %v\n\n", err)
            allValid = false
            continue
        }
 
        expectedClassHashStr := expectedClassHash.String()
 
        // Verify
        if actualClassHashStr == expectedClassHashStr {
            fmt.Printf("  [OK] Implementation verified!\n")
            fmt.Printf("  Class Hash: %s\n\n", actualClassHashStr)
        } else {
            fmt.Printf("  [MISMATCH] Implementation mismatch!\n")
            fmt.Printf("  Expected: %s\n", expectedClassHashStr)
            fmt.Printf("  Actual:   %s\n\n", actualClassHashStr)
            allValid = false
        }
    }
 
    fmt.Printf("=== Verification Summary ===\n")
    if allValid {
        fmt.Printf("[SUCCESS] All contracts are using the expected implementations\n")
    } else {
        fmt.Printf("[FAILED] Some contracts have unexpected implementations\n")
        fmt.Printf("[WARNING] Please review the mismatches above\n")
    }
 
    // Additional: Get class definition for verification
    fmt.Printf("\n=== Fetching Class Definition ===\n")
 
    contractAddr, _ := utils.HexToFelt(contracts[0].Address)
    classHash, _ := provider.ClassHashAt(ctx, blockID, contractAddr)
 
    // Get the full class definition
    _, err = provider.Class(ctx, blockID, classHash)
    if err != nil {
        log.Printf("Error getting class: %v\n", err)
    } else {
        fmt.Printf("Successfully retrieved class definition for verification\n")
        fmt.Printf("Class Hash: %s\n", classHash)
 
        // You can now inspect the class definition (ABI, entry points, etc.)
        fmt.Printf("Class retrieved - ready for detailed verification\n")
    }
}