Skip to content

AddInvokeTransaction

Submits an invoke transaction (V3) to the Starknet network for execution. Invoke transactions are used to execute functions on deployed contracts. The transaction must include valid signatures, correct nonce, and sufficient resource bounds to cover execution costs.

Method Signature

func (provider *Provider) AddInvokeTransaction(
	ctx context.Context,
	invokeTxn *BroadcastInvokeTxnV3,
) (AddInvokeTransactionResponse, error)

Source: write.go

Parameters

  • ctx (context.Context): Context for request cancellation and timeout
  • invokeTxn (*BroadcastInvokeTxnV3): The V3 invoke transaction to submit to the network

Returns

  • AddInvokeTransactionResponse: Response containing the submitted transaction hash
  • error: Error if the submission fails

Type Definitions

  1. Input Type (BroadcastInvokeTxnV3) defines the V3 invoke transaction structure with all required fields including sender, calldata, signature, nonce, and resource bounds.
  2. Result Type (AddInvokeTransactionResponse) contains the transaction hash assigned by the network for tracking the submitted transaction.

The method submits the transaction to the network's mempool and returns immediately with a transaction hash, before execution completes.

BroadcastInvokeTxnV3

type BroadcastInvokeTxnV3 = InvokeTxnV3

Source: types_broadcast_transaction.go

InvokeTxnV3

type InvokeTxnV3 struct {
	Type           TransactionType        `json:"type"`
	SenderAddress  *felt.Felt             `json:"sender_address"`
	Calldata       []*felt.Felt           `json:"calldata"`
	Version        TransactionVersion     `json:"version"`
	Signature      []*felt.Felt           `json:"signature"`
	Nonce          *felt.Felt             `json:"nonce"`
	ResourceBounds *ResourceBoundsMapping `json:"resource_bounds"`
	Tip            U64                    `json:"tip"`
	// The data needed to allow the paymaster to pay for the transaction in native tokens
	PayMasterData []*felt.Felt `json:"paymaster_data"`
	// The data needed to deploy the account contract from which this tx will be initiated
	AccountDeploymentData []*felt.Felt `json:"account_deployment_data"`
	// The storage domain of the account's nonce (an account has a nonce per DA mode)
	NonceDataMode DataAvailabilityMode `json:"nonce_data_availability_mode"`
	// The storage domain of the account's balance from which fee will be charged
	FeeMode DataAvailabilityMode `json:"fee_data_availability_mode"`
}

Source: types_transaction.go

AddInvokeTransactionResponse

type AddInvokeTransactionResponse struct {
	Hash *felt.Felt `json:"transaction_hash"`
}

Source: types_transaction_response.go

ResourceBoundsMapping

type ResourceBoundsMapping struct {
	// The max amount and max price per unit of L1 gas used in this tx
	L1Gas ResourceBounds `json:"l1_gas"`
	// The max amount and max price per unit of L1 blob gas used in this tx
	L1DataGas ResourceBounds `json:"l1_data_gas"`
	// The max amount and max price per unit of L2 gas used in this tx
	L2Gas ResourceBounds `json:"l2_gas"`
}

Source: types_transaction.go

ResourceBounds

type ResourceBounds struct {
	// The max amount of the resource that can be used in the tx
	MaxAmount U64 `json:"max_amount"`
	// The max price per unit of this resource for this tx
	MaxPricePerUnit U128 `json:"max_price_per_unit"`
}

Source: types_transaction.go

Usage Example

package main
 
import (
	"context"
	"fmt"
	"log"
	"math/big"
	"os"
	"time"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/account"
	"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 configuration from environment variables
	rpcURL := os.Getenv("STARKNET_RPC_URL")
	if rpcURL == "" {
		log.Fatal("STARKNET_RPC_URL not found in .env file")
	}
 
	privateKeyStr := os.Getenv("ACCOUNT_PRIVATE_KEY")
	if privateKeyStr == "" {
		log.Fatal("ACCOUNT_PRIVATE_KEY not found in .env file")
	}
 
	publicKeyStr := os.Getenv("ACCOUNT_PUBLIC_KEY")
	if publicKeyStr == "" {
		log.Fatal("ACCOUNT_PUBLIC_KEY not found in .env file")
	}
 
	accountAddressStr := os.Getenv("ACCOUNT_ADDRESS")
	if accountAddressStr == "" {
		log.Fatal("ACCOUNT_ADDRESS not found in .env file")
	}
 
	// Initialize provider
	ctx := context.Background()
	client, err := rpc.NewProvider(ctx, rpcURL)
	if err != nil {
		log.Fatal(err)
	}
 
	// Parse account details
	privateKey, _ := new(felt.Felt).SetString(privateKeyStr)
	publicKey, _ := new(felt.Felt).SetString(publicKeyStr)
	accountAddress, _ := new(felt.Felt).SetString(accountAddressStr)
 
	// Create keystore and account instance
	ks := account.NewMemKeystore()
	ks.Put(publicKey.String(), privateKey.BigInt(new(big.Int)))
	acct, err := account.NewAccount(client, accountAddress, publicKey.String(), ks, 2)
	if err != nil {
		log.Fatal(err)
	}
 
	// Get current nonce
	nonce, err := acct.Nonce(ctx)
	if err != nil {
		log.Fatal(err)
	}
 
	// ETH token contract on Sepolia
	ethTokenAddress, _ := new(felt.Felt).SetString("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
 
	// Function selector for "balanceOf"
	balanceOfSelector := utils.GetSelectorFromNameFelt("balanceOf")
 
	// Build calldata for the invoke transaction
	// For account execute: [num_calls, contract_address, selector, calldata_len, ...calldata]
	calldata := []*felt.Felt{
		new(felt.Felt).SetUint64(1),  // Number of calls
		ethTokenAddress,               // Contract address
		balanceOfSelector,             // Function selector
		new(felt.Felt).SetUint64(1),  // Calldata length
		accountAddress,                // Account address as parameter to balanceOf
	}
 
	// Build the invoke transaction
	invokeTx := rpc.InvokeTxnV3{
		Type:          rpc.TransactionTypeInvoke,
		Version:       rpc.TransactionV3,
		SenderAddress: accountAddress,
		Nonce:         nonce,
		Calldata:      calldata,
		Signature:     []*felt.Felt{}, // Will be filled by SignInvokeTransaction
		// Resource bounds - use high initial values for estimation
		ResourceBounds: &rpc.ResourceBoundsMapping{
			L1Gas: rpc.ResourceBounds{
				MaxAmount:       "0x186a0",         // 100000
				MaxPricePerUnit: "0x33a937098d80", // High price for estimation
			},
			L1DataGas: rpc.ResourceBounds{
				MaxAmount:       "0x186a0",         // 100000
				MaxPricePerUnit: "0x33a937098d80", // High price for estimation
			},
			L2Gas: rpc.ResourceBounds{
				MaxAmount:       "0x186a0",      // 100000
				MaxPricePerUnit: "0x10c388d00", // High price for estimation
			},
		},
		Tip:                   "0x0",
		PayMasterData:         []*felt.Felt{},
		AccountDeploymentData: []*felt.Felt{},
		NonceDataMode:         rpc.DAModeL1,
		FeeMode:               rpc.DAModeL1,
	}
 
	// Estimate the fee using the provider (skip validation for estimation)
	simFlags := []rpc.SimulationFlag{rpc.SkipValidate}
	feeEstimate, err := client.EstimateFee(ctx, []rpc.BroadcastTxn{invokeTx}, simFlags, rpc.WithBlockTag(rpc.BlockTagLatest))
	if err != nil {
		log.Fatal(err)
	}
 
	if len(feeEstimate) > 0 {
		// Add buffer to the gas estimate (20% more)
		estimatedL1DataGas := feeEstimate[0].L1DataGasConsumed.Uint64()
		estimatedL2Gas := feeEstimate[0].L2GasConsumed.Uint64()
 
		// Set resource bounds with buffer
		invokeTx.ResourceBounds = &rpc.ResourceBoundsMapping{
			L1Gas: rpc.ResourceBounds{
				MaxAmount:       rpc.U64(fmt.Sprintf("0x%x", estimatedL1DataGas*12/10)), // 20% buffer
				MaxPricePerUnit: rpc.U128("0x33a937098d80"),                            // Max price in STRK
			},
			L1DataGas: rpc.ResourceBounds{
				MaxAmount:       rpc.U64(fmt.Sprintf("0x%x", estimatedL1DataGas*12/10)), // 20% buffer
				MaxPricePerUnit: rpc.U128("0x33a937098d80"),                            // Max price in STRK
			},
			L2Gas: rpc.ResourceBounds{
				MaxAmount:       rpc.U64(fmt.Sprintf("0x%x", estimatedL2Gas*12/10)), // 20% buffer
				MaxPricePerUnit: rpc.U128("0x10c388d00"),                           // Max price in STRK
			},
		}
	}
 
	// Sign the transaction
	err = acct.SignInvokeTransaction(ctx, &invokeTx)
	if err != nil {
		log.Fatal(err)
	}
 
	// Submit the transaction using the provider
	resp, err := client.AddInvokeTransaction(ctx, &invokeTx)
	if err != nil {
		log.Fatal(err)
	}
 
	fmt.Printf("Transaction submitted successfully!\n")
	fmt.Printf("Transaction Hash: %s\n", resp.Hash.String())
 
	// Wait for transaction confirmation
	receipt, err := waitForTransaction(ctx, client, resp.Hash)
	if err != nil {
		log.Fatal(err)
	}
 
	fmt.Printf("Transaction confirmed!\n")
	fmt.Printf("Block Number: %d\n", receipt.BlockNumber)
	fmt.Printf("Status: %s\n", receipt.FinalityStatus)
	fmt.Printf("Actual Fee: %s\n", receipt.ActualFee.Amount.String())
}
 
// waitForTransaction waits for a transaction to be confirmed on the network
func waitForTransaction(ctx context.Context, client *rpc.Provider, txHash *felt.Felt) (*rpc.TransactionReceiptWithBlockInfo, error) {
	for i := 0; i < 60; i++ { // Wait up to 5 minutes
		receipt, err := client.TransactionReceipt(ctx, txHash)
		if err == nil {
			if receipt.FinalityStatus == rpc.TxnFinalityStatusAcceptedOnL2 ||
			   receipt.FinalityStatus == rpc.TxnFinalityStatusAcceptedOnL1 {
				return receipt, nil
			}
		}
 
		time.Sleep(5 * time.Second)
		fmt.Print(".")
	}
 
	return nil, fmt.Errorf("transaction confirmation timeout")
}

Error Handling

result, err := provider.AddInvokeTransaction(ctx, &invokeTx)
if err != nil {
	switch {
	case errors.Is(err, rpc.ErrInsufficientAccountBalance):
		log.Printf("Insufficient balance to pay for transaction fees")
		return
	case errors.Is(err, rpc.ErrInvalidTransactionNonce):
		log.Printf("Invalid nonce - transaction nonce must match account nonce")
		return
	case errors.Is(err, rpc.ErrValidationFailure):
		log.Printf("Transaction validation failed - check signature and parameters")
		return
	case errors.Is(err, rpc.ErrFeeBelowMinimum):
		log.Printf("Resource bounds too low for current network conditions")
		return
	case errors.Is(err, rpc.ErrDuplicateTx):
		log.Printf("Transaction with same hash already submitted")
		return
	default:
		log.Printf("Failed to submit transaction: %v", err)
		return
	}
}
 
fmt.Printf("Transaction Hash: %s\n", result.Hash)

Common Use Cases

  • Executing functions on deployed smart contracts to modify contract state.
  • Performing token transfers by invoking transfer functions on ERC20 contracts.
  • Interacting with DeFi protocols by calling swap, stake, or yield farming functions.
  • Managing multi-signature wallets by submitting transactions that require approval.