Skip to content

SimulateTransactions

Simulates a sequence of transactions on a specified state and generates execution traces and fee estimates without submitting the transactions to the network. Each transaction is simulated on the state resulting from applying all previous transactions in the sequence.

Method Signature

func (provider *Provider) SimulateTransactions(
	ctx context.Context,
	blockID BlockID,
	txns []BroadcastTxn,
	simulationFlags []SimulationFlag,
) ([]SimulatedTransaction, error)

Source: trace.go

Parameters

  • ctx (context.Context): Context for request cancellation and timeout
  • blockID (BlockID): Block identifier specifying the state to simulate against
  • txns ([]BroadcastTxn): Array of transactions to simulate sequentially
  • simulationFlags ([]SimulationFlag): Flags controlling simulation behavior (e.g., skip validation, skip fee charging)

Returns

  • []SimulatedTransaction: Array of simulation results with traces and fee estimates for each transaction
  • error: Error if the request fails

Type Definitions

  1. Result Type (SimulatedTransaction) embeds both TxnTrace (execution details) and FeeEstimation (gas and fee information) for each simulated transaction.
  2. Trace Types (TxnTrace) can be InvokeTxnTrace, DeclareTxnTrace, DeployAccountTxnTrace, or L1HandlerTxnTrace with complete call flow and revert information.
  3. Input Types (BroadcastTxn, SimulationFlag, BlockID) define the transactions to simulate, simulation behavior, and the state to simulate against.
  4. Fee Estimation (FeeEstimation) provides detailed gas consumption and pricing for each simulated transaction.

The method accepts an array of BroadcastTxn and returns an array of SimulatedTransaction, each containing both the execution trace and fee estimate without actually submitting transactions to the network.

SimulatedTransaction

type SimulatedTransaction struct {
	TxnTrace      `json:"transaction_trace"`
	FeeEstimation `json:"fee_estimation"`
}

Source: types_trace.go

TxnTrace

type TxnTrace interface{}
 
var (
	_ TxnTrace = (*InvokeTxnTrace)(nil)
	_ TxnTrace = (*DeclareTxnTrace)(nil)
	_ TxnTrace = (*DeployAccountTxnTrace)(nil)
	_ TxnTrace = (*L1HandlerTxnTrace)(nil)
)

Source: types_trace.go

BroadcastTxn

type BroadcastTxn interface{}
 
var (
	_ BroadcastTxn = (*BroadcastInvokeTxnV3)(nil)
	_ BroadcastTxn = (*BroadcastDeclareTxnV3)(nil)
	_ BroadcastTxn = (*BroadcastDeployAccountTxnV3)(nil)
)

Source: types_broadcast_transaction.go

SimulationFlag

type SimulationFlag string
 
const (
	SkipFeeCharge SimulationFlag = "SKIP_FEE_CHARGE"
	SkipValidate  SimulationFlag = "SKIP_VALIDATE"
)

Source: types_trace.go

FeeEstimation

type FeeEstimation struct {
	FeeEstimationCommon
	// Units in which the fee is given, can only be FRI
	Unit PriceUnitFri `json:"unit"`
}

Source: types.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
    blockID := rpc.WithBlockTag("pending")     // Block currently being built

    Source: block.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()
 
	// Get account address and current nonce
	accountAddress, _ := new(felt.Felt).SetString("0x36d67ab362562a97f9fba8a1051cf8e37ff1a1449530fb9f1f0e32ac2da7d06")
	nonce, err := provider.Nonce(ctx, rpc.WithBlockTag(rpc.BlockTagLatest), accountAddress)
	if err != nil {
		log.Fatal(err)
	}
 
	// Build an invoke transaction
	contractAddress, _ := new(felt.Felt).SetString("0x669e24364ce0ae7ec2864fb03eedbe60cfbc9d1c74438d10fa4b86552907d54")
	entrypoint, _ := new(felt.Felt).SetString("0x2f0b3c5710379609eb5495f1ecd348cb28167711b73609fe565a72734550354")
 
	invokeTx := rpc.BroadcastInvokeTxnV3{
		Type:          rpc.TransactionTypeInvoke,
		Version:       rpc.TransactionV3,
		Nonce:         nonce,
		SenderAddress: accountAddress,
		Signature:     []*felt.Felt{},
		Calldata: []*felt.Felt{
			new(felt.Felt).SetUint64(1),
			contractAddress,
			entrypoint,
			new(felt.Felt).SetUint64(2),
			new(felt.Felt).SetUint64(256),
			new(felt.Felt).SetUint64(0),
		},
		ResourceBounds: &rpc.ResourceBoundsMapping{
			L1DataGas: rpc.ResourceBounds{
				MaxAmount:       "0x1e0",
				MaxPricePerUnit: "0x922",
			},
			L1Gas: rpc.ResourceBounds{
				MaxAmount:       "0x0",
				MaxPricePerUnit: "0xfbfdefe2186",
			},
			L2Gas: rpc.ResourceBounds{
				MaxAmount:       "0x16eea0",
				MaxPricePerUnit: "0x1830e58f7",
			},
		},
		Tip:                   "0x0",
		PayMasterData:         []*felt.Felt{},
		AccountDeploymentData: []*felt.Felt{},
		NonceDataMode:         rpc.DAModeL1,
		FeeMode:               rpc.DAModeL1,
	}
 
	// Simulate with SKIP_VALIDATE and SKIP_FEE_CHARGE flags
	// SKIP_VALIDATE skips signature and nonce validation
	// SKIP_FEE_CHARGE skips fee charging, useful when resource bounds may be outdated
	simulationFlags := []rpc.SimulationFlag{rpc.SkipValidate, rpc.SkipFeeCharge}
	results, err := provider.SimulateTransactions(
		ctx,
		rpc.WithBlockTag(rpc.BlockTagLatest),
		[]rpc.BroadcastTxn{&invokeTx},
		simulationFlags,
	)
	if err != nil {
		log.Fatal(err)
	}
 
	// Analyze simulation results
	for i, result := range results {
		fmt.Printf("\nTransaction %d:\n", i+1)
 
		// Check fee estimate
		fmt.Printf("Fee Estimate:\n")
		fmt.Printf("  Overall Fee: %s FRI\n", result.FeeEstimation.OverallFee)
		fmt.Printf("  L2 Gas Consumed: %s\n", result.FeeEstimation.L2GasConsumed)
		fmt.Printf("  L2 Gas Price: %s\n", result.FeeEstimation.L2GasPrice)
 
		// Check for reverts
		if invokeTrace, ok := result.TxnTrace.(rpc.InvokeTxnTrace); ok {
			if invokeTrace.ExecuteInvocation.IsReverted {
				fmt.Printf("  Status: REVERTED\n")
				fmt.Printf("  Revert Reason: %s\n", invokeTrace.ExecuteInvocation.RevertReason)
			} else {
				fmt.Printf("  Status: SUCCESS\n")
				fmt.Printf("  Nested Calls: %d\n", len(invokeTrace.ExecuteInvocation.NestedCalls))
			}
		}
	}
}

Error Handling

results, err := provider.SimulateTransactions(ctx, blockID, []rpc.BroadcastTxn{tx}, flags)
if err != nil {
	if errors.Is(err, rpc.ErrTxnExec) {
		log.Printf("Unexpected execution error during simulation")
		return
	}
	if errors.Is(err, rpc.ErrBlockNotFound) {
		log.Printf("Block not found")
		return
	}
	log.Printf("Error simulating transactions: %v", err)
	return
}
 
// Check for reverted transactions in results
for i, result := range results {
	if invokeTrace, ok := result.TxnTrace.(rpc.InvokeTxnTrace); ok {
		if invokeTrace.ExecuteInvocation.IsReverted {
			fmt.Printf("Transaction %d reverted: %s\n", i, invokeTrace.ExecuteInvocation.RevertReason)
		}
	}
}

Common Use Cases

  • Testing transactions before submitting them to the network to avoid failures and wasted fees.
  • Estimating gas costs for transactions with accurate fee calculations.
  • Debugging transaction execution by examining detailed traces and identifying revert reasons.
  • Simulating sequential transactions to verify state transitions across multiple operations.