RPC Examples
Comprehensive examples for using Starknet RPC methods.
BlockWithTxs Examples
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)
}
}BlockHashAndNumber Examples
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)
}
}BlockNumber Examples
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)
}
}StorageProof Examples
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
}
