Skip to content

Utils Examples

This page provides comprehensive examples demonstrating common patterns and use cases for the utils package, from basic type conversions to advanced transaction building and deployment workflows.

Smart Contract Integration

This example demonstrates the essential operations for integrating with Starknet smart contracts: generating function selectors, converting addresses, and handling token amounts with proper decimal precision.

package main
 
import (
	"fmt"
	"math/big"
 
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	// Get function selector for calling contracts
	transferSelector := utils.GetSelectorFromNameFelt("transfer")
	fmt.Printf("Transfer selector: %s\n", transferSelector)
 
	// Convert recipient address from hex string
	recipientAddr, err := utils.HexToFelt("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
	if err != nil {
		fmt.Printf("Error converting address: %v\n", err)
		return
	}
	fmt.Printf("Recipient address: %s\n", recipientAddr)
 
	// Convert amount (1000 tokens with 18 decimals)
	amount := new(big.Int)
	amount.SetString("1000000000000000000000", 10) // 1000 * 10^18
	amountFelt := utils.BigIntToFelt(amount)
	fmt.Printf("Amount (as Felt): %s\n", amountFelt)
 
	// Convert back to verify
	amountBigInt := utils.FeltToBigInt(amountFelt)
	fmt.Printf("Amount (as big.Int): %s\n", amountBigInt.String())
}

Transaction Construction

This example shows how to build a complete invoke transaction for executing contract calls on Starknet, including proper resource bounds configuration and optional transaction parameters.

package main
 
import (
	"fmt"
	"log"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/rpc"
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	// Sender account address
	senderAddress, err := utils.HexToFelt("0x01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab")
	if err != nil {
		log.Fatal("Failed to convert sender address:", err)
	}
 
	// Nonce for the transaction
	nonce := new(felt.Felt).SetUint64(5)
 
	// Contract address to call
	contractAddress, _ := utils.HexToFelt("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7")
 
	// Function selector
	transferSelector := utils.GetSelectorFromNameFelt("transfer")
 
	// Recipient and amount
	recipientAddress, _ := utils.HexToFelt("0xabc...")
	amountLow := new(felt.Felt).SetUint64(1000000000000000000) // 1 ETH in Wei
	amountHigh := new(felt.Felt).SetUint64(0)
 
	// Build calldata
	calldata := []*felt.Felt{
		contractAddress,
		transferSelector,
		new(felt.Felt).SetUint64(3), // calldata length
		recipientAddress,
		amountLow,
		amountHigh,
	}
 
	// Define resource bounds
	resourceBounds := &rpc.ResourceBoundsMapping{
		L1Gas: rpc.ResourceBounds{
			MaxAmount:       rpc.U64("0x2710"),    // 10000
			MaxPricePerUnit: rpc.U128("0x5f5e100"), // 100000000
		},
		L2Gas: rpc.ResourceBounds{
			MaxAmount:       rpc.U64("0x0"),
			MaxPricePerUnit: rpc.U128("0x0"),
		},
		L1DataGas: rpc.ResourceBounds{
			MaxAmount:       rpc.U64("0x80"),
			MaxPricePerUnit: rpc.U128("0x1"),
		},
	}
 
	// Build invoke transaction
	tx := utils.BuildInvokeTxn(
		senderAddress,
		nonce,
		calldata,
		resourceBounds,
		&utils.TxnOptions{
			Tip:         rpc.U64("0x0"),
			UseQueryBit: false,
		},
	)
 
	fmt.Printf("Transaction built successfully!\n")
	fmt.Printf("  Type: %s\n", tx.Type)
	fmt.Printf("  Version: %s\n", tx.Version)
	fmt.Printf("  Sender: %s\n", tx.SenderAddress)
	fmt.Printf("  Nonce: %s\n", tx.Nonce)
	fmt.Printf("  Calldata length: %d\n", len(tx.Calldata))
}

Fee Estimation and Resource Bounds

This example demonstrates how to estimate transaction fees and convert them into properly formatted resource bounds with safety multipliers for successful transaction execution.

package main
 
import (
	"context"
	"fmt"
	"log"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/rpc"
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	// Create RPC provider
	provider, err := rpc.NewProvider("https://starknet-sepolia.public.blastapi.io")
	if err != nil {
		log.Fatal("Failed to create provider:", err)
	}
 
	ctx := context.Background()
 
	// Prepare a sample transaction for fee estimation
	senderAddress, _ := utils.HexToFelt("0x01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab")
	nonce := new(felt.Felt).SetUint64(1)
	calldata := []*felt.Felt{
		utils.GetSelectorFromNameFelt("transfer"),
	}
 
	// Create estimation request
	requests := []rpc.BroadcastTxn{
		rpc.BroadcastInvokeTxnV3{
			Type:          rpc.TransactionTypeInvoke,
			SenderAddress: senderAddress,
			Calldata:      calldata,
			Version:       rpc.TransactionV3WithQueryBit, // Use query version for estimation
			Signature:     []*felt.Felt{},
			Nonce:         nonce,
			ResourceBounds: &rpc.ResourceBoundsMapping{
				L1Gas: rpc.ResourceBounds{
					MaxAmount:       rpc.U64("0x0"),
					MaxPricePerUnit: rpc.U128("0x0"),
				},
				L2Gas: rpc.ResourceBounds{
					MaxAmount:       rpc.U64("0x0"),
					MaxPricePerUnit: rpc.U128("0x0"),
				},
			},
		},
	}
 
	// Estimate fees
	blockID := rpc.WithBlockTag("latest")
	feeEstimate, err := provider.EstimateFee(ctx, requests, []rpc.SimulationFlag{}, blockID)
	if err != nil {
		log.Fatal("Failed to estimate fee:", err)
	}
 
	fmt.Printf("Fee Estimation Results:\n")
	fmt.Printf("  L1 Gas consumed: %s\n", feeEstimate[0].L1GasConsumed)
	fmt.Printf("  L1 Gas price: %s\n", feeEstimate[0].L1GasPrice)
	fmt.Printf("  L2 Gas consumed: %s\n", feeEstimate[0].L2GasConsumed)
	fmt.Printf("  L2 Gas price: %s\n", feeEstimate[0].L2GasPrice)
	fmt.Printf("  Overall fee: %s\n\n", feeEstimate[0].OverallFee)
 
	// Convert to resource bounds with 1.5x multiplier for safety
	resourceBounds := utils.FeeEstToResBoundsMap(feeEstimate[0], 1.5)
 
	fmt.Printf("Resource Bounds (with 1.5x multiplier):\n")
	fmt.Printf("  L1 Gas MaxAmount: %s\n", resourceBounds.L1Gas.MaxAmount)
	fmt.Printf("  L1 Gas MaxPricePerUnit: %s\n", resourceBounds.L1Gas.MaxPricePerUnit)
	fmt.Printf("  L2 Gas MaxAmount: %s\n", resourceBounds.L2Gas.MaxAmount)
	fmt.Printf("  L2 Gas MaxPricePerUnit: %s\n\n", resourceBounds.L2Gas.MaxPricePerUnit)
 
	// Calculate overall fee from resource bounds
	overallFee, err := utils.ResBoundsMapToOverallFee(resourceBounds, 1.0, "0x0")
	if err != nil {
		log.Fatal("Failed to calculate overall fee:", err)
	}
 
	fmt.Printf("Calculated Overall Fee: %s FRI\n", overallFee)
 
	// Convert to STRK for display
	strkFee := utils.FRIToSTRK(overallFee)
	fmt.Printf("Fee in STRK: %.6f STRK\n", strkFee)
}

Token Amount Conversions

This example shows how to convert between different token denominations for user-friendly display and precise on-chain operations.

package main
 
import (
	"fmt"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	fmt.Println("=== ETH/Wei Conversions ===")
 
	// Convert ETH to Wei
	ethAmount := 1.5
	weiAmount := utils.ETHToWei(ethAmount)
	fmt.Printf("%.2f ETH = %s Wei\n", ethAmount, weiAmount)
 
	// Convert Wei back to ETH for display
	displayETH := utils.WeiToETH(weiAmount)
	fmt.Printf("%s Wei = %.6f ETH\n\n", weiAmount, displayETH)
 
	fmt.Println("=== STRK/FRI Conversions ===")
 
	// Convert STRK to FRI
	strkAmount := 100.0
	friAmount := utils.STRKToFRI(strkAmount)
	fmt.Printf("%.2f STRK = %s FRI\n", strkAmount, friAmount)
 
	// Convert FRI back to STRK for display
	displaySTRK := utils.FRIToSTRK(friAmount)
	fmt.Printf("%s FRI = %.6f STRK\n\n", friAmount, displaySTRK)
 
	fmt.Println("=== User Input Scenario ===")
 
	// User enters amount in UI (e.g., 0.5 ETH)
	userInput := 0.5
 
	// Convert to Wei for contract call
	weiForContract := utils.ETHToWei(userInput)
	fmt.Printf("User input: %.2f ETH\n", userInput)
	fmt.Printf("Contract parameter: %s Wei\n\n", weiForContract)
 
	fmt.Println("=== Balance Display Scenario ===")
 
	// Contract returns balance in Wei
	balanceWei, _ := new(felt.Felt).SetString("2500000000000000000") // 2.5 ETH
 
	// Convert to ETH for display
	balanceETH := utils.WeiToETH(balanceWei)
	fmt.Printf("Contract balance: %s Wei\n", balanceWei)
	fmt.Printf("Display to user: %.4f ETH\n", balanceETH)
}

Contract Deployment with UDC

This example demonstrates deploying contracts using Starknet's Universal Deployer Contract (UDC), including address precomputation and both origin-dependent and origin-independent deployment modes.

package main
 
import (
	"fmt"
	"log"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/rpc"
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	fmt.Println("=== Contract Deployment with UDC ===\n")
 
	// Class hash of the contract to deploy
	classHash, err := utils.HexToFelt("0x07aeb292ac3925f3d18f82c77c6e81fa9f8dec3ba77c4f41be61c61b87b011fd")
	if err != nil {
		log.Fatal("Failed to parse class hash:", err)
	}
 
	// Constructor calldata (e.g., initial_value, owner)
	initialValue := utils.Uint64ToFelt(100)
	ownerAddress, _ := utils.HexToFelt("0x01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab")
 
	constructorCalldata := []*felt.Felt{
		initialValue,
		ownerAddress,
	}
 
	fmt.Println("Example 1: Origin-Independent Deployment (CREATE2-like)")
	fmt.Println("------------------------------------------------")
 
	// Origin-independent deployment (deterministic address)
	opts := &utils.UDCOptions{
		Salt:              nil, // Random salt will be generated
		OriginIndependent: true,
		UDCVersion:        utils.UDCCairoV2,
	}
 
	// Build UDC calldata
	udcCall, salt, err := utils.BuildUDCCalldata(classHash, constructorCalldata, opts)
	if err != nil {
		log.Fatal("Failed to build UDC calldata:", err)
	}
 
	fmt.Printf("UDC Address: %s\n", udcCall.ContractAddress)
	fmt.Printf("Function: %s\n", udcCall.FunctionName)
	fmt.Printf("Generated Salt: %s\n", salt)
	fmt.Printf("Calldata length: %d\n\n", len(udcCall.CallData))
 
	// Precompute the deployment address
	deployAddress := utils.PrecomputeAddressForUDC(
		classHash,
		salt,
		constructorCalldata,
		utils.UDCCairoV2,
		nil, // nil for origin-independent
	)
 
	fmt.Printf("Precomputed Contract Address: %s\n\n", deployAddress)
 
	fmt.Println("Example 2: Origin-Dependent Deployment")
	fmt.Println("---------------------------------------")
 
	// Custom salt for reproducibility
	customSalt, _ := utils.HexToFelt("0x123456789")
 
	// Origin-dependent deployment (includes deployer address)
	opts2 := &utils.UDCOptions{
		Salt:              customSalt,
		OriginIndependent: false, // Include deployer address
		UDCVersion:        utils.UDCCairoV2,
	}
 
	udcCall2, salt2, err := utils.BuildUDCCalldata(classHash, constructorCalldata, opts2)
	if err != nil {
		log.Fatal("Failed to build UDC calldata:", err)
	}
 
	fmt.Printf("UDC Address: %s\n", udcCall2.ContractAddress)
	fmt.Printf("Custom Salt: %s\n", salt2)
 
	// Precompute address (origin-dependent requires deployer address)
	deployerAddress, _ := utils.HexToFelt("0x01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab")
 
	deployAddress2 := utils.PrecomputeAddressForUDC(
		classHash,
		salt2,
		constructorCalldata,
		utils.UDCCairoV2,
		deployerAddress, // Include deployer for origin-dependent
	)
 
	fmt.Printf("Precomputed Contract Address (origin-dependent): %s\n\n", deployAddress2)
 
	fmt.Println("Example 3: Building Complete Invoke Transaction")
	fmt.Println("-----------------------------------------------")
 
	// Build the invoke transaction to deploy via UDC
	accountAddress, _ := utils.HexToFelt("0x01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab")
	nonce := new(felt.Felt).SetUint64(1)
 
	resourceBounds := &rpc.ResourceBoundsMapping{
		L1Gas: rpc.ResourceBounds{
			MaxAmount:       rpc.U64("0x2710"),
			MaxPricePerUnit: rpc.U128("0x5f5e100"),
		},
		L2Gas: rpc.ResourceBounds{
			MaxAmount:       rpc.U64("0x0"),
			MaxPricePerUnit: rpc.U128("0x0"),
		},
	}
 
	// Prepare calldata for account's execute function
	// Format: [target_contract, selector, calldata_len, ...calldata]
	executeCalldata := append(
		[]*felt.Felt{
			udcCall.ContractAddress,
			utils.GetSelectorFromNameFelt(udcCall.FunctionName),
			new(felt.Felt).SetUint64(uint64(len(udcCall.CallData))),
		},
		udcCall.CallData...,
	)
 
	tx := utils.BuildInvokeTxn(
		accountAddress,
		nonce,
		executeCalldata,
		resourceBounds,
		nil,
	)
 
	fmt.Printf("Deployment transaction built!\n")
	fmt.Printf("  Sender: %s\n", tx.SenderAddress)
	fmt.Printf("  Nonce: %s\n", tx.Nonce)
	fmt.Printf("  Expected contract address: %s\n", deployAddress)
}

Custom Resource Bounds

This example shows how to define custom limits for resource bounds when you need more control over gas limits than the default Starknet limits provide.

package main
 
import (
	"context"
	"fmt"
	"log"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/rpc"
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	// Create RPC provider for fee estimation
	provider, err := rpc.NewProvider("https://starknet-sepolia.public.blastapi.io")
	if err != nil {
		log.Fatal("Failed to create provider:", err)
	}
 
	ctx := context.Background()
 
	// Prepare transaction for estimation
	senderAddress, _ := utils.HexToFelt("0x01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab")
	nonce := new(felt.Felt).SetUint64(1)
	calldata := []*felt.Felt{utils.GetSelectorFromNameFelt("transfer")}
 
	requests := []rpc.BroadcastTxn{
		rpc.BroadcastInvokeTxnV3{
			Type:          rpc.TransactionTypeInvoke,
			SenderAddress: senderAddress,
			Calldata:      calldata,
			Version:       rpc.TransactionV3WithQueryBit,
			Signature:     []*felt.Felt{},
			Nonce:         nonce,
			ResourceBounds: &rpc.ResourceBoundsMapping{
				L1Gas:     rpc.ResourceBounds{MaxAmount: rpc.U64("0x0"), MaxPricePerUnit: rpc.U128("0x0")},
				L2Gas:     rpc.ResourceBounds{MaxAmount: rpc.U64("0x0"), MaxPricePerUnit: rpc.U128("0x0")},
				L1DataGas: rpc.ResourceBounds{MaxAmount: rpc.U64("0x0"), MaxPricePerUnit: rpc.U128("0x0")},
			},
		},
	}
 
	// Estimate fees
	feeEstimate, err := provider.EstimateFee(ctx, requests, []rpc.SimulationFlag{}, rpc.WithBlockTag("latest"))
	if err != nil {
		log.Fatal("Failed to estimate fee:", err)
	}
 
	fmt.Println("=== Default Resource Bounds ===")
	defaultBounds := utils.FeeEstToResBoundsMap(feeEstimate[0], 1.5)
	fmt.Printf("L1 Gas MaxAmount: %s\n", defaultBounds.L1Gas.MaxAmount)
	fmt.Printf("L1 Gas MaxPricePerUnit: %s\n", defaultBounds.L1Gas.MaxPricePerUnit)
	fmt.Printf("L2 Gas MaxAmount: %s\n", defaultBounds.L2Gas.MaxAmount)
	fmt.Printf("L2 Gas MaxPricePerUnit: %s\n\n", defaultBounds.L2Gas.MaxPricePerUnit)
 
	fmt.Println("=== Custom Resource Bounds ===")
 
	// Define custom limits (more conservative)
	customLimits := &utils.FeeLimits{
		L1GasPriceLimit:      "0x1000000000000",  // Custom L1 gas price limit
		L1GasAmountLimit:     "0x5000",           // 20480 (more conservative than default)
		L2GasPriceLimit:      "0x1000000000000",
		L2GasAmountLimit:     "0x186a0",          // 100000
		L1DataGasPriceLimit:  "0x1000000000000",
		L1DataGasAmountLimit: "0x2000",           // 8192
	}
 
	// Apply custom limits with multiplier
	customBounds := utils.CustomFeeEstToResBoundsMap(
		feeEstimate[0],
		1.5, // multiplier
		customLimits,
	)
 
	fmt.Printf("L1 Gas MaxAmount: %s (capped at %s)\n", customBounds.L1Gas.MaxAmount, customLimits.L1GasAmountLimit)
	fmt.Printf("L1 Gas MaxPricePerUnit: %s (capped at %s)\n", customBounds.L1Gas.MaxPricePerUnit, customLimits.L1GasPriceLimit)
	fmt.Printf("L2 Gas MaxAmount: %s (capped at %s)\n", customBounds.L2Gas.MaxAmount, customLimits.L2GasAmountLimit)
	fmt.Printf("L2 Gas MaxPricePerUnit: %s (capped at %s)\n", customBounds.L2Gas.MaxPricePerUnit, customLimits.L2GasPriceLimit)
}

Transaction Version Selection

This example demonstrates when to use query bit transactions for fee estimation versus regular transactions for actual execution.

package main
 
import (
	"fmt"
 
	"github.com/NethermindEth/juno/core/felt"
	"github.com/NethermindEth/starknet.go/rpc"
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	fmt.Println("=== Transaction Version Selection ===\n")
 
	senderAddress, _ := utils.HexToFelt("0x01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab")
	nonce := new(felt.Felt).SetUint64(1)
	calldata := []*felt.Felt{utils.GetSelectorFromNameFelt("transfer")}
 
	resourceBounds := &rpc.ResourceBoundsMapping{
		L1Gas: rpc.ResourceBounds{
			MaxAmount:       rpc.U64("0x2710"),
			MaxPricePerUnit: rpc.U128("0x5f5e100"),
		},
		L2Gas: rpc.ResourceBounds{
			MaxAmount:       rpc.U64("0x0"),
			MaxPricePerUnit: rpc.U128("0x0"),
		},
	}
 
	fmt.Println("1. Query Transaction (for fee estimation)")
	fmt.Println("------------------------------------------")
 
	queryOpts := &utils.TxnOptions{
		UseQueryBit: true,
		Tip:         rpc.U64("0x0"),
	}
 
	queryTx := utils.BuildInvokeTxn(senderAddress, nonce, calldata, resourceBounds, queryOpts)
 
	fmt.Printf("Transaction Version: %s\n", queryTx.Version)
	fmt.Printf("Version (hex): 0x%s\n", queryTx.Version)
	fmt.Printf("Has query bit: true\n")
	fmt.Printf("Use case: Fee estimation without state changes\n\n")
 
	fmt.Println("2. Regular Transaction (for execution)")
	fmt.Println("---------------------------------------")
 
	regularOpts := &utils.TxnOptions{
		UseQueryBit: false,
		Tip:         rpc.U64("0x0"),
	}
 
	regularTx := utils.BuildInvokeTxn(senderAddress, nonce, calldata, resourceBounds, regularOpts)
 
	fmt.Printf("Transaction Version: %s\n", regularTx.Version)
	fmt.Printf("Version (hex): 0x%s\n", regularTx.Version)
	fmt.Printf("Has query bit: false\n")
	fmt.Printf("Use case: Actual transaction execution\n\n")
 
	fmt.Println("3. Default Options (nil)")
	fmt.Println("-------------------------")
 
	defaultTx := utils.BuildInvokeTxn(senderAddress, nonce, calldata, resourceBounds, nil)
 
	fmt.Printf("Transaction Version: %s\n", defaultTx.Version)
	fmt.Printf("Tip: %s\n", defaultTx.Tip)
	fmt.Printf("UseQueryBit defaults to: false\n")
	fmt.Printf("Tip defaults to: 0x0\n")
}

Keccak State for Incremental Hashing

This example shows how to use KeccakState for efficient incremental hashing when processing data in chunks or computing multiple related hashes.

package main
 
import (
	"encoding/hex"
	"fmt"
 
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	fmt.Println("=== Keccak State: Incremental Hashing ===\n")
 
	fmt.Println("Example 1: Single Hash")
	fmt.Println("----------------------")
 
	// Standard single-pass hash
	data := []byte("Hello, Starknet!")
	hash := utils.Keccak256(data)
	fmt.Printf("Data: %s\n", string(data))
	fmt.Printf("Hash: 0x%s\n\n", hex.EncodeToString(hash))
 
	fmt.Println("Example 2: Incremental Hashing")
	fmt.Println("-------------------------------")
 
	// Create Keccak state for incremental hashing
	state := utils.NewKeccakState()
 
	// Write data incrementally
	state.Write([]byte("Hello, "))
	state.Write([]byte("Starknet"))
	state.Write([]byte("!"))
 
	// Get final hash
	incrementalHash := state.Sum(nil)
	fmt.Printf("Parts: ['Hello, ', 'Starknet', '!']\n")
	fmt.Printf("Hash: 0x%s\n", hex.EncodeToString(incrementalHash))
	fmt.Printf("Matches single-pass: %v\n\n", hex.EncodeToString(hash) == hex.EncodeToString(incrementalHash))
 
	fmt.Println("Example 3: Multiple Hashes from Same State")
	fmt.Println("-------------------------------------------")
 
	state2 := utils.NewKeccakState()
	state2.Write([]byte("Base data"))
 
	// Sum doesn't modify state
	hash1 := state2.Sum(nil)
	hash2 := state2.Sum(nil)
 
	fmt.Printf("Hash 1: 0x%s\n", hex.EncodeToString(hash1))
	fmt.Printf("Hash 2: 0x%s\n", hex.EncodeToString(hash2))
	fmt.Printf("Hashes match: %v\n\n", hex.EncodeToString(hash1) == hex.EncodeToString(hash2))
 
	fmt.Println("Example 4: Reset and Reuse")
	fmt.Println("--------------------------")
 
	state3 := utils.NewKeccakState()
	state3.Write([]byte("First data"))
	firstHash := state3.Sum(nil)
 
	// Reset for new hash
	state3.Reset()
	state3.Write([]byte("Second data"))
	secondHash := state3.Sum(nil)
 
	fmt.Printf("First hash: 0x%s\n", hex.EncodeToString(firstHash))
	fmt.Printf("Second hash: 0x%s\n", hex.EncodeToString(secondHash))
	fmt.Printf("Hashes differ: %v\n\n", hex.EncodeToString(firstHash) != hex.EncodeToString(secondHash))
 
	fmt.Println("Example 5: Read vs Sum")
	fmt.Println("----------------------")
 
	state4 := utils.NewKeccakState()
	state4.Write([]byte("Test data"))
 
	// Sum doesn't modify internal state
	sumHash := state4.Sum(nil)
	sumHash2 := state4.Sum(nil)
 
	fmt.Printf("Sum() call 1: 0x%s\n", hex.EncodeToString(sumHash))
	fmt.Printf("Sum() call 2: 0x%s\n", hex.EncodeToString(sumHash2))
	fmt.Printf("Both calls return same hash: %v\n\n", hex.EncodeToString(sumHash) == hex.EncodeToString(sumHash2))
 
	// Read modifies internal state (faster but destructive)
	state5 := utils.NewKeccakState()
	state5.Write([]byte("Test data"))
 
	buf := make([]byte, 32)
	state5.Read(buf)
	fmt.Printf("Read() result: 0x%s\n", hex.EncodeToString(buf))
	fmt.Printf("Read() is faster but modifies state\n")
 
	fmt.Println("\nKeccakState Properties:")
	fmt.Printf("  Hash size: %d bytes\n", state.Size())
	fmt.Printf("  Block size: %d bytes\n", state.BlockSize())
}

Type Conversions Comprehensive Example

This example demonstrates all major type conversion functions in the utils package for common Starknet development scenarios.

package main
 
import (
	"fmt"
	"log"
	"math/big"
 
	"github.com/NethermindEth/starknet.go/utils"
)
 
func main() {
	fmt.Println("=== Comprehensive Type Conversions ===\n")
 
	// 1. Hex to Felt conversions
	fmt.Println("1. Hex String Conversions")
	fmt.Println("-------------------------")
 
	hexAddr := "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"
	feltAddr, err := utils.HexToFelt(hexAddr)
	if err != nil {
		log.Fatal("HexToFelt failed:", err)
	}
	fmt.Printf("Hex: %s\n", hexAddr)
	fmt.Printf("Felt: %s\n\n", feltAddr)
 
	// 2. Array conversions
	fmt.Println("2. Array Conversions")
	fmt.Println("--------------------")
 
	hexArray := []string{"0x1", "0x2", "0x3", "0x64", "0xff"}
	feltArray, err := utils.HexArrToFelt(hexArray)
	if err != nil {
		log.Fatal("HexArrToFelt failed:", err)
	}
 
	fmt.Printf("Hex array: %v\n", hexArray)
	fmt.Print("Felt array: [")
	for i, f := range feltArray {
		if i > 0 {
			fmt.Print(", ")
		}
		fmt.Print(f.String())
	}
	fmt.Print("]\n\n")
 
	// Convert Felt array to BigInt array
	bigIntArray := utils.FeltArrToBigIntArr(feltArray)
	fmt.Print("BigInt array: [")
	for i, b := range bigIntArray {
		if i > 0 {
			fmt.Print(", ")
		}
		fmt.Print(b.String())
	}
	fmt.Print("]\n\n")
 
	// 3. Numeric conversions
	fmt.Println("3. Numeric Conversions")
	fmt.Println("----------------------")
 
	// Small numbers: use Uint64ToFelt
	smallNum := uint64(12345)
	smallFelt := utils.Uint64ToFelt(smallNum)
	fmt.Printf("uint64(%d) → Felt: %s\n", smallNum, smallFelt)
 
	// Large numbers: use BigIntToFelt
	largeNum := new(big.Int)
	largeNum.SetString("123456789012345678901234567890", 10)
	largeFelt := utils.BigIntToFelt(largeNum)
	fmt.Printf("big.Int → Felt: %s\n", largeFelt)
 
	// Convert Felt back to BigInt
	backToBigInt := utils.FeltToBigInt(largeFelt)
	fmt.Printf("Felt → big.Int: %s\n", backToBigInt.String())
	fmt.Printf("Round-trip successful: %v\n\n", largeNum.Cmp(backToBigInt) == 0)
 
	// 4. String conversions
	fmt.Println("4. String/Hex Conversions")
	fmt.Println("-------------------------")
 
	// String to hex
	name := "MyToken"
	hexName := utils.StrToHex(name)
	fmt.Printf("String '%s' → Hex: %s\n", name, hexName)
 
	// Hex to short string
	backToStr := utils.HexToShortStr(hexName)
	fmt.Printf("Hex → String: '%s'\n", backToStr)
	fmt.Printf("Round-trip successful: %v\n\n", name == backToStr)
 
	// 5. Hex number conversions
	fmt.Println("5. Hex Number Conversions")
	fmt.Println("-------------------------")
 
	hexNumber := "0x1234abcdef"
	bigInt := utils.HexToBN(hexNumber)
	fmt.Printf("Hex: %s\n", hexNumber)
	fmt.Printf("BigInt: %s\n", bigInt.String())
 
	// Convert back to hex
	backToHex := utils.BigToHex(bigInt)
	fmt.Printf("Back to hex: %s\n\n", backToHex)
 
	// 6. Bytes conversions
	fmt.Println("6. Bytes Conversions")
	fmt.Println("--------------------")
 
	hexBytes := "0x48656c6c6f"
	byteData, err := utils.HexToBytes(hexBytes)
	if err != nil {
		log.Fatal("HexToBytes failed:", err)
	}
	fmt.Printf("Hex: %s\n", hexBytes)
	fmt.Printf("Bytes: %v\n", byteData)
	fmt.Printf("String: %s\n", string(byteData))
 
	// Bytes to BigInt
	bytesBigInt := utils.BytesToBig(byteData)
	fmt.Printf("As BigInt: %s\n\n", bytesBigInt.String())
 
	// 7. String to BigInt (numeric string)
	fmt.Println("7. String to BigInt")
	fmt.Println("-------------------")
 
	numericStr := "999999999999999999999"
	strBigInt := utils.StrToBig(numericStr)
	fmt.Printf("String: %s\n", numericStr)
	fmt.Printf("BigInt: %s\n", strBigInt.String())
}