walletarmy

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 10, 2026 License: MIT Imports: 27 Imported by: 1

README

WalletArmy

A robust, production-ready Go library for managing Ethereum wallets and executing transactions with automatic retry logic, gas management, and resilience features.

Features

  • Multi-wallet Management: Manage multiple wallets across multiple networks simultaneously
  • Hardware Wallet Support: Sign transactions with Ledger, Trezor, keystore files, or raw private keys via the jarvis account system
  • Automatic Nonce Management: Race-safe nonce acquisition with automatic release on failure
  • Smart Gas Handling: Automatic gas estimation, price suggestions, and dynamic gas bumping for slow transactions
  • Retry Logic: Configurable retry mechanism with exponential backoff for failed transactions
  • Circuit Breaker: Protects against cascading failures from unreliable RPC nodes
  • Idempotency: Prevents duplicate transaction submissions with idempotency keys
  • EIP-1559 Support: Full support for dynamic fee transactions
  • Hook System: Extensible hooks for custom logic at various transaction lifecycle stages
  • Context Support: Full context.Context integration for cancellation and timeouts
  • Builder Pattern API: Fluent, easy-to-use API similar to go-resty

Installation

go get github.com/tranvictor/walletarmy

Quick Start

Basic Usage
package main

import (
    "fmt"
    "math/big"

    "github.com/ethereum/go-ethereum/common"
    "github.com/tranvictor/jarvis/networks"
    "github.com/tranvictor/jarvis/util/account"
    "github.com/tranvictor/walletarmy"
)

func main() {
    // Create a new wallet manager with default configuration
    wm := walletarmy.NewWalletManager(
        walletarmy.WithDefaultNumRetries(5),
        walletarmy.WithDefaultNetwork(networks.EthereumMainnet),
    )

    // Create account from private key (without 0x prefix)
    privateKey := "your_private_key_hex_without_0x_prefix"
    acc, err := account.NewPrivateKeyAccount(privateKey)
    if err != nil {
        panic(err)
    }

    // Register the account with the wallet manager
    wm.SetAccount(acc)

    // Get the wallet address
    wallet := acc.Address()
    fmt.Printf("Wallet address: %s\n", wallet.Hex())

    // Execute a transaction using the builder pattern
    tx, receipt, err := wm.R().
        SetFrom(wallet).
        SetTo(common.HexToAddress("0xRecipientAddress")).
        SetValue(big.NewInt(1e18)). // 1 ETH
        SetNetwork(networks.EthereumMainnet).
        Execute()

    // Note: err can be non-nil even when the tx was mined. A mined-but-reverted
    // tx returns ErrTxReverted alongside valid tx and receipt. Always check
    // errors.Is before assuming the tx wasn't mined.
    if err != nil && !errors.Is(err, walletarmy.ErrTxReverted) {
        panic(err)
    }

    fmt.Printf("Transaction mined: %s\n", tx.Hash().Hex())
    fmt.Printf("Gas used: %d\n", receipt.GasUsed)
}
Using Context for Cancellation
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetNetwork(networks.EthereumMainnet).
    ExecuteContext(ctx)
Contract Interaction
// Prepare contract call data
data := contractABI.Pack("transfer", recipient, amount)

tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(contractAddress).
    SetData(data).
    SetNetwork(networks.EthereumMainnet).
    SetAbis(contractABI). // For better error decoding
    Execute()

Managing Wallets and Networks

Adding Wallets at Runtime

WalletArmy supports adding wallets dynamically. Accounts are created via the jarvis account package, which supports multiple signing backends — private keys, keystore files, and hardware wallets (Ledger, Trezor). The signing backend is transparent to WalletArmy; once registered, all accounts sign transactions the same way.

import "github.com/tranvictor/jarvis/util/account"

wm := walletarmy.NewWalletManager()

// Method 1: From private key string (hex without 0x prefix)
acc, err := account.NewPrivateKeyAccount("abc123...")
if err != nil {
    panic(err)
}
wm.SetAccount(acc)

// Method 2: From an encrypted keystore file
acc, err := account.NewKeystoreAccount("/path/to/keystore.json", "passphrase")
if err != nil {
    panic(err)
}
wm.SetAccount(acc)

// Method 3: Hardware wallet — Ledger
// The derivation path follows BIP-44 (e.g., "m/44'/60'/0'/0/0")
// The address must match what the Ledger derives at that path
acc, err := account.NewLedgerAccount("m/44'/60'/0'/0/0", "0xYourLedgerAddress")
if err != nil {
    panic(err)
}
wm.SetAccount(acc)

// Method 4: Hardware wallet — Trezor
acc, err := account.NewTrezorAccount("m/44'/60'/0'/0/0", "0xYourTrezorAddress")
if err != nil {
    panic(err)
}
wm.SetAccount(acc)

// Method 5: Using jarvis wallet store (auto-detects signing backend)
// This looks up the account in jarvis's local wallet store and unlocks it.
// Works with any backend that jarvis knows about (keystore, Ledger, Trezor).
walletAddress := common.HexToAddress("0x...")
acc, err := wm.UnlockAccount(walletAddress)
if err != nil {
    panic(err)
}
// Account is automatically registered — no need to call SetAccount

// Add multiple wallets in bulk
privateKeys := []string{"key1...", "key2...", "key3..."}
for _, pk := range privateKeys {
    acc, err := account.NewPrivateKeyAccount(pk)
    if err != nil {
        panic(err)
    }
    wm.SetAccount(acc)
}

// Access a registered account later
acc = wm.Account(walletAddress)
if acc == nil {
    fmt.Println("Account not found")
}

Note: All account types expose the same SignTx(tx, chainID) method. WalletArmy does not need to know the signing backend — it just calls SignTx and the jarvis account handles the rest (including USB communication for hardware wallets).

Working with Networks

WalletArmy uses the jarvis networks package which supports many EVM networks out of the box:

import "github.com/tranvictor/jarvis/networks"

// Built-in networks
networks.EthereumMainnet
networks.Goerli
networks.Sepolia
networks.BSCMainnet
networks.Polygon
networks.Arbitrum
networks.Optimism
networks.Avalanche
networks.Fantom
// ... and many more

// Get network by name
network, err := networks.GetNetwork("mainnet")

// Get network by chain ID
network, err := networks.GetNetworkByID(1)

// Use in transactions
tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetNetwork(networks.Polygon). // Use Polygon network
    Execute()
Creating Custom Networks

You can create custom networks for any EVM-compatible chain. Here's an example for creating a custom Optimism L2 network:

import (
    "github.com/ethereum/go-ethereum/common"
    "github.com/tranvictor/jarvis/networks"
)

// Create a custom Optimism L2 network
customNetwork := networks.NewGenericOptimismNetwork(networks.GenericOptimismNetworkConfig{
    // Name: Primary identifier for the network
    // Used in logs, UI, and as the main lookup key
    Name: "rise-testnet",

    // AlternativeNames: Other names that can be used to look up this network
    // Useful for aliases like "rise", "risetestnet", etc.
    AlternativeNames: []string{"rise", "riselabs-testnet"},

    // ChainID: The unique chain identifier (EIP-155)
    // Must match the chain ID returned by the RPC node
    ChainID: 11155931,

    // NativeTokenSymbol: Symbol of the native gas token
    // Usually "ETH" for Optimism L2s, but could be custom
    NativeTokenSymbol: "ETH",

    // NativeTokenDecimal: Decimal places for the native token
    // Almost always 18 for ETH-based chains
    NativeTokenDecimal: 18,

    // BlockTime: Average block time in seconds
    // Used for estimating confirmation times
    BlockTime: 1,

    // NodeVariableName: Environment variable name for custom RPC URL
    // If set, the system will check os.Getenv("RISE_TESTNET_NODE") for a custom node URL
    NodeVariableName: "RISE_TESTNET_NODE",

    // DefaultNodes: Map of node name -> RPC URL
    // These are the default RPC endpoints if no custom node is configured
    DefaultNodes: map[string]string{
        "rise-public": "https://testnet.riselabs.xyz",
        // You can add multiple nodes for redundancy:
        // "backup-node": "https://backup.riselabs.xyz",
    },

    // BlockExplorerAPIKeyVariableName: Environment variable for block explorer API key
    // Used for fetching ABIs and verifying contracts
    BlockExplorerAPIKeyVariableName: "RISE_TESTNET_SCAN_API_KEY",

    // BlockExplorerAPIURL: Base URL for the block explorer API (Etherscan-compatible)
    // Leave empty if no explorer API is available
    BlockExplorerAPIURL: "",

    // MultiCallContractAddress: Address of the Multicall3 contract
    // Used for batching multiple read calls into one RPC request
    // Multicall3 is deployed at the same address on most chains
    MultiCallContractAddress: common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11"),

    // SyncTxSupported: Whether the network supports eth_sendRawTransactionSync
    // This is an Optimism-specific RPC that returns the receipt immediately
    // Set to true for Optimism-based L2s that support this feature
    SyncTxSupported: true,
})

// Use the custom network in transactions
tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetNetwork(customNetwork).
    Execute()
Network Types

The jarvis networks package supports different network types:

// For standard EVM networks (Ethereum, BSC, Polygon, etc.)
networks.NewGenericNetwork(networks.GenericNetworkConfig{...})

// For Optimism-based L2s (Optimism, Base, custom OP Stack chains)
networks.NewGenericOptimismNetwork(networks.GenericOptimismNetworkConfig{...})

// For Arbitrum-based L2s
networks.NewGenericArbitrumNetwork(networks.GenericArbitrumNetworkConfig{...})
Network Resolver (for Custom Networks)

When you create custom networks, they work immediately with methods that accept a network parameter (like EnsureTx, BuildTx, etc.). However, some operations need to look up a network by chain ID:

  • Crash recovery: Resuming pending transactions after a restart
  • BroadcastTx/BroadcastTxSync: Broadcasting raw transactions that only contain chain ID

By default, WalletArmy uses jarvis's built-in network registry, which supports standard EVM networks (Ethereum, Polygon, Arbitrum, Optimism, etc.). For custom networks, you need to provide a network resolver:

// Create your custom network
customNetwork := networks.NewGenericOptimismNetwork(networks.GenericOptimismNetworkConfig{
    Name:    "my-private-chain",
    ChainID: 12345,
    // ... other config ...
})

// Provide a resolver that knows about your custom network
wm := walletarmy.NewWalletManager(
    walletarmy.WithNetworkResolver(func(chainID uint64) (networks.Network, error) {
        switch chainID {
        case 12345:
            return customNetwork, nil
        default:
            // Fallback to jarvis for standard networks
            return networks.GetNetworkByID(chainID)
        }
    }),
    // ... other options ...
)

If you're only using standard networks (Ethereum, Polygon, etc.), you don't need to configure a network resolver.

Using Environment Variables for RPC URLs

For production, it's recommended to use environment variables for RPC URLs:

# Set custom RPC URL
export RISE_TESTNET_NODE="https://your-private-rpc.com"

# Set block explorer API key (if available)
export RISE_TESTNET_SCAN_API_KEY="your-api-key"

The network will automatically use the environment variable if set, falling back to DefaultNodes otherwise.

Synchronous Transaction Mining (eth_sendRawTransactionSync)

Some networks support eth_sendRawTransactionSync, a special RPC method that broadcasts a transaction and waits for it to be mined in a single call. This is particularly useful for L2 networks with fast block times.

How It Works
Standard Flow (eth_sendRawTransaction):
1. Build transaction
2. Sign transaction
3. Broadcast transaction → returns tx hash immediately
4. Poll for receipt (multiple RPC calls over time)
5. Transaction mined → return receipt

Sync Flow (eth_sendRawTransactionSync):
1. Build transaction
2. Sign transaction
3. Broadcast transaction → waits for mining → returns receipt immediately
   (Single RPC call that blocks until mined)
Benefits
  • Faster execution: No polling loop required
  • Fewer RPC calls: Single call instead of broadcast + multiple receipt checks
  • Simpler flow: Immediate confirmation in the response
  • Ideal for L2s: Fast block times (1-2 seconds) make sync calls practical
Supported Networks

Networks that support this feature include:

  • Optimism and OP Stack chains (Base, Zora, Mode, Rise, etc.)
  • Arbitrum chains
  • Many custom L2 networks
Enabling Sync Transactions

When creating a custom network, set SyncTxSupported: true:

customNetwork := networks.NewGenericOptimismNetwork(networks.GenericOptimismNetworkConfig{
    Name:    "my-l2-network",
    ChainID: 12345,
    // ... other config ...
    
    // Enable synchronous transaction support
    SyncTxSupported: true,
})
Automatic Detection

WalletArmy automatically uses sync transactions when available:

// WalletArmy checks network.IsSyncTxSupported() internally
// If true, it uses BroadcastTxSync which returns the receipt immediately
// If false, it uses BroadcastTx and polls for the receipt

tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetNetwork(optimismNetwork). // Supports sync tx
    Execute()

// receipt is available immediately after broadcast on sync-supported networks
// No additional polling was needed
Manual Sync Broadcast

You can also use sync broadcast directly:

// Build and sign transaction
tx, err := wm.BuildTx(...)
_, signedTx, err := wm.SignTx(wallet, tx, network)

// Broadcast synchronously - blocks until mined
receipt, err := wm.BroadcastTxSync(signedTx)
if err != nil {
    // Handle error
}

// Receipt is immediately available
fmt.Printf("Mined in block: %d\n", receipt.BlockNumber)
fmt.Printf("Gas used: %d\n", receipt.GasUsed)
fmt.Printf("Status: %d\n", receipt.Status) // 1 = success, 0 = revert
Performance Comparison
Network Type Block Time Sync Support Typical Confirmation
Ethereum Mainnet ~12s No 12-24 seconds
Polygon ~2s No 4-6 seconds
Optimism ~2s Yes 2 seconds (single call)
Arbitrum ~0.25s Yes <1 second (single call)
Base ~2s Yes 2 seconds (single call)
Parallel Multi-Wallet Transactions

Execute transactions from multiple wallets concurrently:

import "sync"

func executeParallelTransfers(wm *walletarmy.WalletManager, wallets []common.Address, recipient common.Address, network networks.Network) error {
    var wg sync.WaitGroup
    errChan := make(chan error, len(wallets))

    for _, wallet := range wallets {
        wg.Add(1)
        go func(from common.Address) {
            defer wg.Done()
            
            _, _, err := wm.R().
                SetFrom(from).
                SetTo(recipient).
                SetValue(big.NewInt(1e17)). // 0.1 ETH
                SetNetwork(network).
                Execute()
            
            if err != nil {
                errChan <- fmt.Errorf("transfer from %s failed: %w", from.Hex(), err)
            }
        }(wallet)
    }

    wg.Wait()
    close(errChan)

    // Collect errors
    var errors []error
    for err := range errChan {
        errors = append(errors, err)
    }

    if len(errors) > 0 {
        return fmt.Errorf("%d transfers failed", len(errors))
    }
    return nil
}
Cross-Network Operations

Work with multiple networks simultaneously:

wm := walletarmy.NewWalletManager()

// Register the same wallet for use on multiple networks
acc, _ := account.NewPrivateKeyAccount(privateKey)
wm.SetAccount(acc)
wallet := acc.Address()

// Send on Ethereum mainnet
wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetNetwork(networks.EthereumMainnet).
    Execute()

// Send on Polygon
wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetNetwork(networks.Polygon).
    Execute()

// Send on Arbitrum
wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetNetwork(networks.Arbitrum).
    Execute()

// Each network maintains its own:
// - Nonce tracking
// - Gas price cache
// - RPC connections
// - Circuit breaker state
Network Infrastructure Access

Access lower-level network components when needed:

// Get the EthReader for a network (for read operations)
reader, err := wm.Reader(networks.EthereumMainnet)
if err != nil {
    panic(err)
}
balance, err := reader.GetBalance(wallet.Hex())

// Get the Broadcaster (for custom broadcast logic)
broadcaster, err := wm.Broadcaster(networks.EthereumMainnet)

// Get the TxAnalyzer (for transaction analysis)
analyzer, err := wm.Analyzer(networks.EthereumMainnet)

// Get current gas settings
gasInfo, err := wm.GasSetting(networks.EthereumMainnet)
fmt.Printf("Gas Price: %.2f gwei\n", gasInfo.GasPrice)
fmt.Printf("Tip Cap: %.2f gwei\n", gasInfo.MaxPriorityPrice)

Configuration

WalletManager Options
wm := walletarmy.NewWalletManager(
    // Retry configuration
    walletarmy.WithDefaultNumRetries(9),
    walletarmy.WithDefaultSleepDuration(5*time.Second),
    walletarmy.WithDefaultTxCheckInterval(5*time.Second),

    // Gas configuration
    walletarmy.WithDefaultExtraGasLimit(10000),
    walletarmy.WithDefaultExtraGasPrice(1.0),    // Extra gwei
    walletarmy.WithDefaultExtraTipCap(0.5),       // Extra gwei
    walletarmy.WithDefaultMaxGasPrice(100.0),     // Max gwei
    walletarmy.WithDefaultMaxTipCap(10.0),        // Max gwei

    // Network
    walletarmy.WithDefaultNetwork(networks.EthereumMainnet),

    // Transaction type (0 = legacy, 2 = EIP-1559)
    walletarmy.WithDefaultTxType(2),

    // Idempotency store (optional)
    walletarmy.WithDefaultIdempotencyStore(24*time.Hour),
)
Per-Request Configuration

The builder pattern allows overriding defaults per request:

tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetNumRetries(3).                    // Override default
    SetMaxGasPrice(50.0).                // Gas price protection
    SetMaxTipCap(5.0).                   // Tip cap protection
    SetGasLimit(21000).                  // Fixed gas limit
    SetIdempotencyKey("unique-tx-id").   // Prevent duplicates
    Execute()
Forcing Transactions Through (Skip Simulation)

By default, WalletArmy runs an eth_call simulation after building a transaction but before signing and broadcasting. If the simulation shows the transaction would revert, execution stops (or retries, if a SimulationFailedHook requests it).

To bypass this check and force-broadcast regardless of simulation results, use SetSkipSimulation(true). This is useful when you know the simulation will fail but still want the transaction submitted — for example, transactions that depend on state changes within the same block, or contracts with view-only revert guards that don't apply during actual execution.

When skipping simulation, you should also provide a manual gas limit via SetGasLimit(), since gas estimation may fail for the same reason the simulation would revert.

tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(contractAddress).
    SetData(calldata).
    SetGasLimit(500000).         // Required — estimation likely fails too
    SetSkipSimulation(true).     // Bypass eth_call simulation
    Execute()

Warning: Skipping simulation removes a safety net. The transaction will be broadcast even if it would revert on-chain, consuming gas. Use this only when you understand why the simulation fails and are confident the transaction should proceed.

Hooks

Hooks allow you to inject custom logic at various stages of transaction execution:

BeforeSignAndBroadcast Hook

Called after the transaction is built but before signing:

wm.R().
    SetBeforeSignAndBroadcastHook(func(tx *types.Transaction, err error) error {
        log.Printf("About to sign tx with nonce: %d", tx.Nonce())
        // Return an error to abort the transaction
        return nil
    }).
    // ... other settings ...
    Execute()
AfterSignAndBroadcast Hook

Called after successful broadcast:

wm.R().
    SetAfterSignAndBroadcastHook(func(tx *types.Transaction, err error) error {
        log.Printf("Broadcasted tx: %s", tx.Hash().Hex())
        return nil
    }).
    // ... other settings ...
    Execute()
GasEstimationFailed Hook

Called when gas estimation fails (usually means the tx would revert):

wm.R().
    SetGasEstimationFailedHook(func(tx *types.Transaction, abiError *abi.Error, revertParams any, revertMsgError, gasEstimationError error) (*big.Int, error) {
        log.Printf("Gas estimation failed: %v", gasEstimationError)
        // Return a gas limit to override and continue, or an error to stop
        return nil, nil // Continue with default behavior
    }).
    // ... other settings ...
    Execute()
SimulationFailed Hook

Called when eth_call simulation shows the tx would revert:

wm.R().
    SetSimulationFailedHook(func(tx *types.Transaction, revertData []byte, abiError *abi.Error, revertParams any, err error) (shouldRetry bool, retErr error) {
        log.Printf("Simulation failed: %v", err)
        // Return shouldRetry=true to retry, or an error to stop
        return false, err // Stop and return the error
    }).
    // ... other settings ...
    Execute()
TxMined Hook

Called when a transaction is mined (success or revert):

wm.R().
    SetTxMinedHook(func(tx *types.Transaction, receipt *types.Receipt) error {
        if receipt.Status == 0 {
            log.Printf("Transaction reverted: %s", tx.Hash().Hex())
        } else {
            log.Printf("Transaction succeeded: %s", tx.Hash().Hex())
        }
        return nil
    }).
    // ... other settings ...
    Execute()

Idempotency

Prevent duplicate transactions with idempotency keys:

// Configure idempotency store
wm := walletarmy.NewWalletManager(
    walletarmy.WithDefaultIdempotencyStore(24*time.Hour),
)

// Use the same key for retried requests
tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetIdempotencyKey("payment-order-12345").
    Execute()

// Calling again with the same key returns the cached result
tx2, receipt2, err2 := wm.R().
    SetFrom(wallet).
    SetTo(recipient).
    SetValue(amount).
    SetIdempotencyKey("payment-order-12345"). // Same key
    Execute()
// tx2 and receipt2 will be the same as tx and receipt

Persistence & Crash Recovery

WalletArmy supports optional persistence for crash-resilient transaction management. This allows your application to recover gracefully after unexpected restarts.

Why Persistence?

Without persistence, if your application crashes:

  • Nonce tracking is lost: On restart, nonces are re-queried from the network. If the RPC's pending state lags, you might reuse a nonce that was already broadcast.
  • In-flight transactions are forgotten: Pending transactions won't be monitored, and you won't know their final status.
  • Idempotency is lost: Duplicate transactions may be created if business logic retries.
Using Persistence

Implement the NonceStore and TxStore interfaces, or use a provided implementation:

// Option 1: Use provided SQLite store (coming in store/sqlite package)
// import "github.com/tranvictor/walletarmy/store/sqlite"
// nonceStore, _ := sqlite.NewNonceStore("./walletarmy.db")
// txStore, _ := sqlite.NewTxStore("./walletarmy.db")

// Option 2: Implement your own stores
type myNonceStore struct { /* ... */ }
func (s *myNonceStore) Get(ctx context.Context, wallet common.Address, chainID uint64) (*walletarmy.NonceState, error) { /* ... */ }
// ... implement other methods

// Configure WalletManager with stores
wm := walletarmy.NewWalletManager(
    walletarmy.WithNonceStore(myNonceStore),
    walletarmy.WithTxStore(myTxStore),
)
Recovery on Startup

Call Recover() during application startup before processing new transactions:

func main() {
    wm := walletarmy.NewWalletManager(
        walletarmy.WithNonceStore(nonceStore),
        walletarmy.WithTxStore(txStore),
    )

    // Recover from any previous crash
    ctx := context.Background()
    result, err := wm.Recover(ctx)
    if err != nil {
        log.Fatalf("Recovery failed: %v", err)
    }

    log.Printf("Recovery complete: %d txs recovered, %d already mined, %d dropped, %d nonces reconciled",
        result.RecoveredTxs, result.MinedTxs, result.DroppedTxs, result.ReconciledNonces)

    // Now safe to process new transactions
    // ...
}
Recovery with Custom Options

For more control over the recovery process:

opts := walletarmy.RecoveryOptions{
    ResumeMonitoring:      true,  // Resume monitoring recovered pending txs
    TxCheckInterval:       5 * time.Second,
    MaxConcurrentMonitors: 10,
    
    // Callbacks for application-specific logic
    OnTxRecovered: func(tx *walletarmy.PendingTx) {
        log.Printf("Recovered pending tx: %s", tx.Hash.Hex())
    },
    OnTxMined: func(tx *walletarmy.PendingTx, receipt *types.Receipt) {
        log.Printf("Recovered tx was mined: %s, status: %d", tx.Hash.Hex(), receipt.Status)
    },
    OnTxDropped: func(tx *walletarmy.PendingTx) {
        log.Printf("Recovered tx was dropped: %s", tx.Hash.Hex())
        // Maybe retry the transaction
    },
}

result, err := wm.RecoverWithOptions(ctx, opts)
Persistence Interfaces

NonceStore - Persists nonce tracking state:

type NonceStore interface {
    Get(ctx context.Context, wallet common.Address, chainID uint64) (*NonceState, error)
    Save(ctx context.Context, state *NonceState) error
    SavePendingNonce(ctx context.Context, wallet common.Address, chainID uint64, nonce uint64) error
    AddReservedNonce(ctx context.Context, wallet common.Address, chainID uint64, nonce uint64) error
    RemoveReservedNonce(ctx context.Context, wallet common.Address, chainID uint64, nonce uint64) error
    ListAll(ctx context.Context) ([]*NonceState, error)
}

TxStore - Tracks in-flight transactions:

type TxStore interface {
    Save(ctx context.Context, tx *PendingTx) error
    Get(ctx context.Context, hash common.Hash) (*PendingTx, error)
    GetByNonce(ctx context.Context, wallet common.Address, chainID uint64, nonce uint64) ([]*PendingTx, error)
    ListPending(ctx context.Context, wallet common.Address, chainID uint64) ([]*PendingTx, error)
    ListAllPending(ctx context.Context) ([]*PendingTx, error)
    UpdateStatus(ctx context.Context, hash common.Hash, status PendingTxStatus, receipt *types.Receipt) error
    Delete(ctx context.Context, hash common.Hash) error
    DeleteOlderThan(ctx context.Context, age time.Duration) (int, error)
}
What Gets Persisted

When persistence is enabled, WalletArmy automatically persists:

  1. Nonce acquisitions: When a nonce is reserved for a transaction
  2. Nonce releases: When an unused nonce is released
  3. Transaction broadcasts: When a transaction is broadcast to the network
  4. Transaction completions: When a transaction is mined or reverted

Circuit Breaker

The circuit breaker protects against cascading failures from unreliable RPC nodes:

// Check circuit breaker status
stats := wm.GetCircuitBreakerStats(networks.EthereumMainnet)
fmt.Printf("State: %s, Failures: %d\n", stats.State, stats.ConsecutiveFailures)

// Manually reset if needed
wm.ResetCircuitBreaker(networks.EthereumMainnet)

// Record success/failure manually (usually automatic)
wm.RecordNetworkSuccess(networks.EthereumMainnet)
wm.RecordNetworkFailure(networks.EthereumMainnet)

Transaction Gas Management & Blocking Nonce Detection

WalletArmy has intelligent gas management that distinguishes between blocking and non-blocking transactions to avoid wasting gas while ensuring the nonce sequence stays unblocked.

What is a Blocking Nonce?

A nonce is blocking when it equals the chain's mined nonce — i.e., it's the next nonce the chain expects to process. This transaction must be confirmed before any higher-nonce transactions can proceed.

For example, if the chain has mined up to nonce 36, then nonce 37 is blocking — it must be included in a block before nonces 38, 39, etc. can be mined.

Why Only Bump the Blocking Nonce?

Since gas bumping is applied one nonce at a time (only the blocking nonce), you should set your gas protection limits (MaxGasPrice, MaxTipCap) aggressively enough to resolve stuck transactions.

Slow Transaction Handling

Note: "Slow" is a walletarmy-internal concept, not a status from the node or transaction monitor. WalletArmy generates TxStatusSlow when a broadcasted transaction is not mined within SlowTxTimeout (default: 5s) after the monitor has checked the node at least once. The external TxMonitor only reports terminal statuses like "done", "reverted", and "lost".

The slow timer is deferred until the monitor delivers its first non-terminal status (e.g., "pending"). This ensures that a SlowTxTimeout shorter than TxCheckInterval does not produce false "slow" signals before the node is ever queried. The timeout counts from the first check, not from broadcast time.

When a transaction is detected as slow (not confirmed within SlowTxTimeout after the first monitor check):

Scenario Behavior
Blocking nonce Gas price and tip cap are bumped (default: +20% gas price, +10% tip cap) and the transaction is re-broadcast with the same nonce
Non-blocking nonce No gas bump — the transaction simply continues waiting. It will get mined once the blocking nonce ahead of it is resolved

This avoids unnecessarily spending gas on transactions that are only slow because they're waiting for an earlier nonce to be confirmed.

Lost Transaction Handling

When a transaction is detected as lost (dropped from the node's mempool):

Scenario Behavior
Normal case Re-broadcast with the same nonce and bumped gas price. Unlike the old behavior (which acquired a new nonce and created an unfillable gap), the lost transaction is retried in place
Gas limit reached If bumping would exceed MaxGasPrice / MaxTipCap, the transaction gives up with ErrGasPriceLimitReached
Example: Gas Escalation for a Blocking Nonce
Initial state:
  MaxGasPrice: 100 gwei, MaxTipCap: 10 gwei
  Mined nonce: 37, Tx nonce: 37 (blocking)

Nonce 37 tx is lost from mempool:
  1. Retry with same nonce, gas bumped to 55 gwei (+10%)  → broadcast
  2. Lost again, gas bumped to 60.5 gwei (+10%)           → broadcast
  3. Lost again, gas bumped to 66.5 gwei (+10%)           → broadcast
  ...continues until MaxGasPrice (100 gwei) is reached
  N. Gas bumped to 102 gwei > MaxGasPrice (100)
     → ErrGasPriceLimitReached, stops retrying
Configuration
wm := walletarmy.NewWalletManager(
    // Gas price protection — max gas price and tip cap per transaction.
    // Set these aggressively since gas bumping only applies to the single
    // blocking nonce, not all queued transactions.
    walletarmy.WithDefaultMaxGasPrice(100.0), // gwei
    walletarmy.WithDefaultMaxTipCap(10.0),    // gwei

    // Gas bumping rate when a blocking tx is slow/lost
    walletarmy.WithDefaultGasPriceIncreasePercent(1.2), // 20% increase per bump
    walletarmy.WithDefaultTipCapIncreasePercent(1.1),   // 10% increase per bump

    // How long to wait (after the first monitor check) before walletarmy
    // considers a broadcasted tx "slow". The timer starts after the monitor
    // confirms the tx is still pending, not from broadcast time.
    walletarmy.WithDefaultSlowTxTimeout(5*time.Second),
)

Note: When MaxGasPrice or MaxTipCap is set to 0, WalletArmy defaults to 5x the initial gas price / tip cap as the protection limit (MaxCapMultiplier = 5.0).

Enabling Debug Logs

WalletArmy uses the github.com/KyberNetwork/logger package. Enable debug logging to troubleshoot nonce issues and transaction flow:

import "github.com/KyberNetwork/logger"

func init() {
    // Set log level to debug
    logger.SetLevel(logger.DebugLevel)
}

Debug logs include:

  • Nonce acquisition: What nonce was chosen and why
  • Nonce release: When unused nonces are released
  • Transaction lifecycle: Build, sign, broadcast, and mining status
  • Retry decisions: Why retries are happening

Example debug output:

DEBUG acquireNonce: nonce acquired and reserved wallet=0x123... network=mainnet chain_id=1 acquired_nonce=42 mined_nonce=40 remote_pending=41 local_pending=41 decision="pending on nodes, using remote (>= local)"
DEBUG ReleaseNonce: nonce released successfully wallet=0x123... network=mainnet chain_id=1 released_nonce=42 new_stored=41

Error Handling

WalletArmy provides specific error types for different failure scenarios:

import "errors"

tx, receipt, err := wm.R()./* ... */.Execute()

if err != nil {
    switch {
    case errors.Is(err, walletarmy.ErrTxReverted):
        // Transaction was mined but reverted (tx and receipt are still returned)
    case errors.Is(err, walletarmy.ErrEstimateGasFailed):
        // Gas estimation failed - tx would likely revert
    case errors.Is(err, walletarmy.ErrAcquireNonceFailed):
        // Couldn't get nonce from network
    case errors.Is(err, walletarmy.ErrGetGasSettingFailed):
        // Couldn't get gas price from network
    case errors.Is(err, walletarmy.ErrEnsureTxOutOfRetries):
        // Exhausted all retries
    case errors.Is(err, walletarmy.ErrGasPriceLimitReached):
        // Gas price protection limit hit
    case errors.Is(err, walletarmy.ErrSimulatedTxReverted):
        // Simulation showed tx would revert
    case errors.Is(err, walletarmy.ErrCircuitBreakerOpen):
        // Network circuit breaker is open
    case errors.Is(err, context.Canceled):
        // Context was cancelled
    case errors.Is(err, context.DeadlineExceeded):
        // Context deadline exceeded
    }
}
Structured Error Types

When you provide ABIs via SetAbis(), WalletArmy automatically decodes Solidity revert reasons and returns them in typed errors. Use errors.As to extract the structured error — no hooks or closure variables needed:

tx, receipt, err := wm.R().
    SetFrom(wallet).
    SetTo(contractAddress).
    SetData(calldata).
    SetAbis(contractABI). // Enable error decoding
    Execute()

// Simulation revert — eth_call showed the tx would revert
var simErr *walletarmy.SimulationRevertError
if errors.As(err, &simErr) {
    fmt.Printf("Tx: %s\n", simErr.Tx.Hash().Hex())
    fmt.Printf("Revert data: 0x%x\n", simErr.RevertData)
    if simErr.AbiError != nil {
        fmt.Printf("Contract error: %s(%v)\n", simErr.AbiError.Name, simErr.RevertParams)
    }
}

// Gas estimation failure — gas estimation failed (possibly due to a revert)
var gasErr *walletarmy.GasEstimationError
if errors.As(err, &gasErr) {
    if gasErr.AbiError != nil {
        fmt.Printf("Contract error: %s(%v)\n", gasErr.AbiError.Name, gasErr.RevertParams)
    }
}

These structured errors are returned consistently on every error exit path:

  • SimulationRevertError — when eth_call shows the tx would revert (ErrSimulatedTxReverted)
  • GasEstimationError — when gas estimation fails due to a revert (ErrEstimateGasFailed)

For mined-but-reverted transactions, ErrTxReverted is returned along with the tx and receipt. The on-chain revert data is not available from the receipt alone.

Hooks Are for Control Flow Only

Hooks (SimulationFailedHook, GasEstimationFailedHook, TxMinedHook) are intended for control flow decisions — whether to retry, abort, override gas limits, etc. You do NOT need hooks to inspect error details. All error information is available through the returned err.

Architecture

Key Components
  • WalletManager: Central component managing wallets, networks, and transaction execution
  • TxRequest: Builder pattern for constructing transaction requests
  • TxExecutionContext: Holds mutable state during transaction execution
  • CircuitBreaker: Protects against cascading RPC failures
  • IdempotencyStore: Prevents duplicate transaction submissions
Project Structure

The codebase is organized for maintainability and reusability:

walletarmy/
├── manager.go              # Core WalletManager struct, constructors, account management
├── manager_nonce.go        # Nonce acquisition/release (delegates to internal/nonce)
├── manager_network.go      # Network infrastructure (reader, broadcaster, analyzer)
├── manager_tx.go           # Transaction building, signing, broadcasting, EnsureTx
├── request.go              # TxRequest builder pattern
├── execution_context.go    # TxExecutionContext for transaction state
├── interface.go            # Manager interface for mockability
├── hook.go                 # Hook interfaces (Hook, TxMinedHook, SimulationFailedHook)
├── errors.go               # Error definitions
├── types.go                # Core types (TxStatus, TxExecutionResult, ManagerDefaults)
├── options.go              # WalletManagerOption functional options
├── broadcast_error.go      # Broadcast error handling and detection
├── error_decoder.go        # ABI error decoding for contract reverts
├── gas_info.go             # Gas information types
├── idempotency/            # Idempotency store subpackage (public)
│   └── idempotency.go      # Store interface and InMemoryStore implementation
├── internal/
│   ├── circuitbreaker/
│   │   └── circuitbreaker.go   # Circuit breaker implementation
│   └── nonce/
│       └── tracker.go          # Thread-safe nonce tracking
├── examples/
│   └── fund_distribution/      # Example application
└── README.md
File Responsibilities
File Responsibility
manager.go WalletManager struct definition, NewWalletManager, account management, defaults access
manager_nonce.go Public nonce API, delegates to internal/nonce tracker
manager_network.go Reader, Broadcaster, Analyzer, initNetwork, GasSetting, circuit breaker access
manager_tx.go BuildTx, SignTx, BroadcastTx, EnsureTx*, MonitorTx, transaction handlers
request.go TxRequest builder with fluent API, Execute/ExecuteContext
execution_context.go TxExecutionContext for managing retry state and gas adjustments
interface.go Manager interface for dependency injection and mocking
options.go Functional options for WalletManager configuration
errors.go All error sentinel values (ErrEstimateGasFailed, etc.)
types.go Core types and constants (TxStatus, ManagerDefaults, etc.)
idempotency/ Public subpackage: Store interface and InMemoryStore
internal/nonce/ Thread-safe nonce tracking with Tracker struct
internal/circuitbreaker/ Circuit breaker pattern for RPC resilience
Concurrency Safety

WalletArmy is designed for concurrent use:

  • Per-wallet locks: Nonce operations are locked per wallet, not globally
  • Per-network locks: Network infrastructure is locked per network
  • Atomic nonce acquisition: Nonces are reserved atomically to prevent races
  • Thread-safe defaults: Configuration access is protected by RWMutex
  • sync.Map usage: Internal maps use sync.Map for lock-free concurrent access

Contributing

Contributions are welcome! Please follow these guidelines:

Development Setup
# Clone the repository
git clone https://github.com/tranvictor/walletarmy.git
cd walletarmy

# Install dependencies
go mod download

# Set up git hooks (runs tests and linter before each push)
make setup

# Run tests
go test ./...

# Build
go build ./...
Available Make Commands
make setup    # Configure git hooks for pre-push checks
make test     # Run all tests with race detection
make lint     # Run golangci-lint
make vet      # Run go vet
make check    # Run all checks (vet, test, lint)
make build    # Build all packages
make clean    # Remove generated files

The pre-push hook will automatically run go vet, go test -race, and golangci-lint before allowing pushes. This ensures code quality is maintained.

Code Style
  • Follow standard Go conventions
  • Use gofmt for formatting
  • Add comments for exported functions
  • Include debug logging for critical operations
Testing
# Run all tests
go test ./...

# Run tests with race detection
go test -race ./...

# Run tests with coverage
go test -cover ./...

# Run specific package tests
go test ./internal/circuitbreaker/...
Internal Packages

The internal/ directory contains packages that are not part of the public API:

  • circuitbreaker: A thread-safe circuit breaker implementation for RPC failover protection. Not exported to allow internal refactoring without breaking changes.
  • nonce: Thread-safe nonce tracking for multiple wallets across multiple networks. Encapsulates all nonce acquisition, release, and tracking logic.
Public Subpackages
  • idempotency: Provides Store interface and InMemoryStore for preventing duplicate transactions. Import as github.com/tranvictor/walletarmy/idempotency.
Pull Request Process
  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add/update tests as needed
  5. Run go test ./... to ensure tests pass
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request
Reporting Issues

When reporting issues, please include:

  • Go version
  • WalletArmy version
  • Network (mainnet, testnet, etc.)
  • Debug logs if available
  • Minimal reproduction case

License

This project is open source. See the LICENSE file for details.

Acknowledgments

  • Built on top of jarvis for Ethereum interactions
  • Inspired by go-resty for the builder pattern API

Documentation

Overview

adapters.go provides adapter implementations that wrap jarvis types to implement the minimal interfaces defined in deps.go.

deps.go defines minimal interfaces for external dependencies. This allows for easy mocking in tests and decouples the library from specific implementations.

Package walletarmy provides persistence interfaces for crash-resilient transaction management. Implement these interfaces to persist nonce state and in-flight transactions across restarts.

Index

Constants

View Source
const (
	DefaultNumRetries      = 9
	DefaultSleepDuration   = 5 * time.Second
	DefaultTxCheckInterval = 5 * time.Second
	DefaultSlowTxTimeout   = 5 * time.Second // Time before considering a tx "slow"

	// Default gas adjustment values (can be overridden via ManagerDefaults)
	DefaultGasPriceBumpFactor = 1.2 // 20% increase when tx is slow
	DefaultTipCapBumpFactor   = 1.1 // 10% increase when tx is slow
	MaxCapMultiplier          = 5.0 // Multiplier for default max gas price/tip cap when not set

	// Deprecated: Use DefaultGasPriceBumpFactor instead.
	DefaultGasPriceIncreasePercent = DefaultGasPriceBumpFactor
	// Deprecated: Use DefaultTipCapBumpFactor instead.
	DefaultTipCapIncreasePercent = DefaultTipCapBumpFactor
)

Constants for transaction execution

Variables

View Source
var (
	ErrInsufficientFund       = BroadcastError(fmt.Errorf("insufficient fund"))
	ErrNonceIsLow             = BroadcastError(fmt.Errorf("nonce is low"))
	ErrReplacementUnderpriced = BroadcastError(fmt.Errorf("replacement transaction underpriced"))
	ErrGasLimitIsTooLow       = BroadcastError(fmt.Errorf("gas limit is too low"))
	ErrTxIsKnown              = BroadcastError(fmt.Errorf("tx is known"))
)
View Source
var (
	ErrEstimateGasFailed    = fmt.Errorf("estimate gas failed")
	ErrAcquireNonceFailed   = fmt.Errorf("acquire nonce failed")
	ErrGetGasSettingFailed  = fmt.Errorf("get gas setting failed")
	ErrEnsureTxOutOfRetries = fmt.Errorf("ensure tx out of retries")
	ErrGasPriceLimitReached = fmt.Errorf("gas price protection limit reached")
	ErrFromAddressZero      = fmt.Errorf("from address cannot be zero")
	ErrNetworkNil           = fmt.Errorf("network cannot be nil")
	ErrSimulatedTxReverted  = fmt.Errorf("tx will be reverted")
	ErrSimulatedTxFailed    = fmt.Errorf("couldn't simulate tx at pending state")
	ErrCircuitBreakerOpen   = fmt.Errorf("circuit breaker is open: network temporarily unavailable")
	ErrSyncBroadcastTimeout = fmt.Errorf("sync broadcast timed out, falling back to async monitoring")
	ErrTxReverted           = fmt.Errorf("transaction was mined but reverted")
)

Transaction execution errors

View Source
var GAS_INFO_TTL = 60 * time.Second
View Source
var SyncBroadcastTimeout = 5 * time.Second

SyncBroadcastTimeout is the maximum time to wait for BroadcastTxSync before falling back to async monitoring. This is a variable (not const) to allow tests to override it for faster testing.

Functions

func DefaultNetworkResolver

func DefaultNetworkResolver(chainID uint64) (networks.Network, error)

DefaultNetworkResolver is the default resolver that uses jarvis networks.GetNetworkByID. This supports standard EVM networks (Ethereum, Polygon, Arbitrum, Optimism, etc.). For custom networks, use WithNetworkResolver to provide a custom resolver.

func IsGasLimitIsTooLow

func IsGasLimitIsTooLow(err error) bool

func IsInsufficientFund

func IsInsufficientFund(err error) bool

func IsNonceIsLow

func IsNonceIsLow(err error) bool

func IsReplacementUnderpriced

func IsReplacementUnderpriced(err error) bool

func IsTxIsKnown

func IsTxIsKnown(err error) bool

Types

type BroadcastError

type BroadcastError error

func NewBroadcastError

func NewBroadcastError(err error) BroadcastError

type BroadcasterFactory

type BroadcasterFactory func(chainID uint64, networkName string) (EthBroadcaster, error)

BroadcasterFactory creates an EthBroadcaster for a given network. This allows injecting mock broadcasters for testing.

type DecodedError deprecated added in v1.2.0

type DecodedError struct {
	// AbiError is the matched ABI error definition, or nil if decoding failed.
	AbiError *abi.Error
	// RevertParams contains the decoded parameters of the revert, or nil.
	RevertParams any
	// RevertData is the raw revert bytes from the node.
	RevertData []byte
	// Err is the underlying error being wrapped.
	Err error
}

Deprecated: Use GasEstimationError or SimulationRevertError instead. These dedicated types carry richer context (the built transaction, raw revert bytes, etc.) and are returned consistently on every error exit path.

DecodedError wraps a contract error with its decoded ABI information.

func (*DecodedError) Error added in v1.2.0

func (e *DecodedError) Error() string

func (*DecodedError) Unwrap added in v1.2.0

func (e *DecodedError) Unwrap() error

type ErrorDecoder

type ErrorDecoder struct {
	// contains filtered or unexported fields
}

func NewErrorDecoder

func NewErrorDecoder(abis ...abi.ABI) (*ErrorDecoder, error)

func (*ErrorDecoder) Decode

func (d *ErrorDecoder) Decode(err error) (abiError *abi.Error, errorParams any, resultErr error)

Decode decodes the error from a contract call. It should always wrap the original error (using %w). It can only decode Solidity custom errors https://soliditylang.org/blog/2021/04/21/custom-errors/

type EthBroadcaster

type EthBroadcaster interface {
	// BroadcastTx broadcasts a signed transaction to the network
	// Returns the tx hash, whether it was broadcast successfully, and any errors
	BroadcastTx(tx *types.Transaction) (hash string, broadcasted bool, err error)

	// BroadcastTxSync broadcasts and waits for the transaction to be mined (for L2s that support it)
	BroadcastTxSync(tx *types.Transaction) (receipt *types.Receipt, err error)
}

EthBroadcaster defines the minimal interface for broadcasting transactions.

func DefaultBroadcasterFactory

func DefaultBroadcasterFactory(network networks.Network) (EthBroadcaster, error)

DefaultBroadcasterFactory is the default factory that creates jarvis broadcasters

func NewBroadcasterAdapter

func NewBroadcasterAdapter(b *broadcaster.Broadcaster) EthBroadcaster

NewBroadcasterAdapter creates an EthBroadcaster from a jarvis broadcaster

type EthReader

type EthReader interface {
	// GetMinedNonce returns the nonce of the last mined transaction for the address
	GetMinedNonce(addr string) (uint64, error)

	// GetPendingNonce returns the nonce of the next pending transaction for the address
	GetPendingNonce(addr string) (uint64, error)

	// EstimateExactGas estimates the gas required for a transaction
	EstimateExactGas(from, to string, gasPrice float64, value *big.Int, data []byte) (uint64, error)

	// SuggestedGasSettings returns suggested gas price and tip cap in gwei
	SuggestedGasSettings() (gasPrice float64, tipCapGwei float64, err error)

	// EthCall simulates a transaction execution without sending it
	// The overrides parameter allows for state overrides during the call simulation
	EthCall(from, to string, data []byte, overrides *map[common.Address]gethclient.OverrideAccount) ([]byte, error)

	// TxInfoFromHash returns transaction info for a given hash
	TxInfoFromHash(hash string) (TxInfo, error)
}

EthReader defines the minimal interface for reading blockchain state. This abstracts away the concrete jarvis reader implementation.

func DefaultReaderFactory

func DefaultReaderFactory(network networks.Network) (EthReader, error)

DefaultReaderFactory is the default factory that creates jarvis readers

func NewReaderAdapter

func NewReaderAdapter(r *reader.EthReader) EthReader

NewReaderAdapter creates an EthReader from a jarvis reader

type GasBounds

type GasBounds struct {
	ExtraGasLimit      uint64  // Extra gas limit added to estimates
	ExtraGasPrice      float64 // Extra gas price added to suggestions (gwei)
	ExtraTipCap        float64 // Extra tip cap added to suggestions (gwei)
	MaxGasPrice        float64 // Maximum gas price protection limit (gwei)
	MaxTipCap          float64 // Maximum tip cap protection limit (gwei)
	GasPriceBumpFactor float64 // Multiplier for gas price when tx is slow (e.g., 1.2 = 20% increase)
	TipCapBumpFactor   float64 // Multiplier for tip cap when tx is slow (e.g., 1.1 = 10% increase)
}

GasBounds holds immutable gas configuration and protection limits.

type GasEstimationError added in v1.1.9

type GasEstimationError struct {
	AbiError     *abi.Error // decoded ABI error, nil if unavailable
	RevertParams any        // decoded parameters, nil if AbiError is nil
	Err          error      // underlying error chain (preserves ErrEstimateGasFailed, ErrEnsureTxOutOfRetries, etc.)
}

GasEstimationError is a structured error returned when gas estimation fails. Callers can extract it with errors.As to access ABI-decoded error details.

AbiError and RevertParams are populated only when ABIs are provided via SetAbis() and the RPC error contains decodable Solidity custom error data.

func (*GasEstimationError) Error added in v1.1.9

func (e *GasEstimationError) Error() string

func (*GasEstimationError) Unwrap added in v1.1.9

func (e *GasEstimationError) Unwrap() error

type GasEstimationFailedHook

type GasEstimationFailedHook func(tx *types.Transaction, abiError *abi.Error, revertParams any, revertMsgError, gasEstimationError error) (gasLimit *big.Int, err error)

GasEstimationFailedHook is called when gas estimation fails during transaction building. This typically means the transaction would revert or there is a network error.

This hook is always called when set, regardless of whether ABIs were provided.

Parameters:

  • tx: always nil (the transaction could not be built due to gas estimation failure).
  • abiError: the decoded ABI error if ABIs were provided via SetAbis() and the error could be decoded, nil otherwise (including when no ABIs are set).
  • revertParams: the decoded revert parameters, nil when abiError is nil.
  • revertMsgError: the error from ABI decoding itself, nil when no ABIs are set or decoding was not attempted.
  • gasEstimationError: the original gas estimation error, always non-nil (wraps ErrEstimateGasFailed).

Return values:

  • gasLimit non-nil: the returned gas limit overrides the estimate for the next retry attempt, allowing the caller to force a specific gas limit.
  • gasLimit nil: the next retry will attempt gas estimation again.
  • err non-nil: the execution stops immediately with this error.

type GasInfo

type GasInfo struct {
	GasPrice         float64
	BaseGasPrice     *big.Int
	MaxPriorityPrice float64
	FeePerGas        float64
	Timestamp        time.Time
}

type Hook

type Hook func(tx *types.Transaction, err error) error

Hook is called before signing/broadcasting and after signing/broadcasting.

Before sign and broadcast (BeforeSignAndBroadcast):

  • tx: the built (unsigned) transaction, always non-nil.
  • err: always nil (reserved for future use).
  • Return a non-nil error to abort the entire EnsureTx flow immediately.

After sign and broadcast (AfterSignAndBroadcast):

  • tx: the signed transaction that was successfully broadcast, always non-nil.
  • err: the broadcast error if any nodes rejected the tx, or nil on full success.
  • Return a non-nil error to abort the entire EnsureTx flow immediately. The transaction was already broadcast, so returning an error here does NOT prevent it from being mined.

type LoopAction

type LoopAction int

LoopAction represents what the transaction execution loop should do next.

const (
	// ActionContinueToMonitor proceeds to the monitoring phase of the loop.
	ActionContinueToMonitor LoopAction = iota
	// ActionRetry goes back to the top of the loop for a new attempt.
	ActionRetry
	// ActionReturn exits the loop entirely, returning the result to the caller.
	ActionReturn
)

type Manager

type Manager interface {
	// Account Management
	SetAccount(acc *account.Account)
	UnlockAccount(addr common.Address) (*account.Account, error)
	Account(wallet common.Address) *account.Account

	// Network Infrastructure
	Reader(network networks.Network) (EthReader, error)
	Broadcaster(network networks.Network) (EthBroadcaster, error)
	Analyzer(network networks.Network) (*txanalyzer.TxAnalyzer, error)

	// Gas Settings
	GasSetting(network networks.Network) (*GasInfo, error)

	// Nonce Management
	// ReleaseNonce releases a previously acquired nonce that was not used.
	ReleaseNonce(wallet common.Address, network networks.Network, nonce uint64)

	// Circuit Breaker
	GetCircuitBreakerStats(network networks.Network) circuitbreaker.Stats
	ResetCircuitBreaker(network networks.Network)
	RecordNetworkSuccess(network networks.Network)
	RecordNetworkFailure(network networks.Network)

	// Idempotency
	IdempotencyStore() idempotency.Store

	// Default Configuration
	Defaults() ManagerDefaults
	SetDefaults(defaults ManagerDefaults)

	// Transaction Building
	BuildTx(
		txType uint8,
		from, to common.Address,
		nonce *big.Int,
		value *big.Int,
		gasLimit uint64,
		extraGasLimit uint64,
		gasPrice float64,
		extraGasPrice float64,
		tipCapGwei float64,
		extraTipCapGwei float64,
		data []byte,
		network networks.Network,
	) (*types.Transaction, error)

	// Transaction Signing
	SignTx(
		wallet common.Address,
		tx *types.Transaction,
		network networks.Network,
	) (signedAddr common.Address, signedTx *types.Transaction, err error)

	// Transaction Broadcasting
	BroadcastTx(tx *types.Transaction) (hash string, broadcasted bool, err BroadcastError)
	BroadcastTxSync(tx *types.Transaction) (receipt *types.Receipt, err error)

	// Transaction Monitoring
	// MonitorTx is deprecated, use MonitorTxContext instead for better cancellation support.
	MonitorTx(tx *types.Transaction, network networks.Network, txCheckInterval time.Duration) <-chan TxInfo
	// MonitorTxContext is a context-aware version of MonitorTx that supports cancellation.
	MonitorTxContext(ctx context.Context, tx *types.Transaction, network networks.Network, txCheckInterval time.Duration) <-chan TxInfo

	// High-Level Transaction Execution
	EnsureTx(
		txType uint8,
		from, to common.Address,
		value *big.Int,
		gasLimit uint64,
		extraGasLimit uint64,
		gasPrice float64,
		extraGasPrice float64,
		tipCapGwei float64,
		extraTipCapGwei float64,
		data []byte,
		network networks.Network,
	) (*types.Transaction, *types.Receipt, error)

	EnsureTxWithHooks(
		numRetries int,
		sleepDuration time.Duration,
		txCheckInterval time.Duration,
		txType uint8,
		from, to common.Address,
		value *big.Int,
		gasLimit uint64, extraGasLimit uint64,
		gasPrice float64, extraGasPrice float64,
		tipCapGwei float64, extraTipCapGwei float64,
		maxGasPrice float64, maxTipCap float64,
		data []byte,
		network networks.Network,
		beforeSignAndBroadcastHook Hook,
		afterSignAndBroadcastHook Hook,
		abis []abi.ABI,
		gasEstimationFailedHook GasEstimationFailedHook,
	) (*types.Transaction, *types.Receipt, error)

	EnsureTxWithHooksContext(
		ctx context.Context,
		numRetries int,
		sleepDuration time.Duration,
		txCheckInterval time.Duration,
		txType uint8,
		from, to common.Address,
		value *big.Int,
		gasLimit uint64, extraGasLimit uint64,
		gasPrice float64, extraGasPrice float64,
		tipCapGwei float64, extraTipCapGwei float64,
		maxGasPrice float64, maxTipCap float64,
		data []byte,
		network networks.Network,
		beforeSignAndBroadcastHook Hook,
		afterSignAndBroadcastHook Hook,
		abis []abi.ABI,
		gasEstimationFailedHook GasEstimationFailedHook,
		simulationFailedHook SimulationFailedHook,
		txMinedHook TxMinedHook,
	) (*types.Transaction, *types.Receipt, error)

	// Builder Pattern Entry Point
	R() *TxRequest
}

Manager defines the interface for wallet management operations. This interface allows for easy mocking in tests and provides a stable API contract.

type ManagerDefaults

type ManagerDefaults struct {
	// Retry configuration
	NumRetries      int
	SleepDuration   time.Duration
	TxCheckInterval time.Duration
	SlowTxTimeout   time.Duration // Time before considering a tx "slow" during monitoring

	// Gas configuration
	ExtraGasLimit uint64
	ExtraGasPrice float64
	ExtraTipCap   float64
	MaxGasPrice   float64
	MaxTipCap     float64

	// Gas bumping configuration (for slow tx retry)
	GasPriceBumpFactor float64 // Multiplier for gas price when tx is slow (e.g., 1.2 = 20% increase)
	TipCapBumpFactor   float64 // Multiplier for tip cap when tx is slow (e.g., 1.1 = 10% increase)

	// Default network (if not specified in request)
	Network networks.Network

	// Default transaction type
	TxType uint8
}

ManagerDefaults holds default configuration values that are inherited by TxRequest

type NetworkBroadcasterFactory

type NetworkBroadcasterFactory func(network networks.Network) (EthBroadcaster, error)

NetworkBroadcasterFactory creates an EthBroadcaster for a given network. This allows injecting mock broadcasters for testing.

type NetworkReaderFactory

type NetworkReaderFactory func(network networks.Network) (EthReader, error)

NetworkReaderFactory creates an EthReader for a given network. This allows injecting mock readers for testing.

type NetworkResolver

type NetworkResolver func(chainID uint64) (networks.Network, error)

NetworkResolver resolves a network by chain ID. This is used by recovery and broadcast methods that need to look up a network from a chain ID. The default implementation uses jarvis networks.GetNetworkByID.

type NetworkTxMonitorFactory

type NetworkTxMonitorFactory func(reader EthReader) TxMonitor

NetworkTxMonitorFactory creates a TxMonitor for a given reader. This allows injecting mock monitors for testing.

type NonceState

type NonceState struct {
	// Wallet is the wallet address
	Wallet common.Address
	// ChainID is the network chain ID
	ChainID uint64
	// LocalPendingNonce is the highest nonce we've used locally (exclusive - next nonce to use is this value)
	// If nil, no local state is tracked
	LocalPendingNonce *uint64
	// ReservedNonces are nonces that have been acquired but not yet confirmed
	// This is used to prevent reuse after crash
	ReservedNonces []uint64
	// UpdatedAt is when this state was last updated
	UpdatedAt time.Time
}

NonceState represents the persisted nonce state for a wallet on a network

type NonceStore

type NonceStore interface {
	// Get retrieves the nonce state for a wallet on a network.
	// Returns nil, nil if no state exists (not an error).
	Get(ctx context.Context, wallet common.Address, chainID uint64) (*NonceState, error)

	// Save persists the nonce state. Creates or updates as needed.
	Save(ctx context.Context, state *NonceState) error

	// SavePendingNonce is a convenience method to update just the pending nonce.
	// Implementations should use atomic updates if possible.
	SavePendingNonce(ctx context.Context, wallet common.Address, chainID uint64, nonce uint64) error

	// AddReservedNonce adds a nonce to the reserved set.
	// This should be called when a nonce is acquired for a transaction.
	AddReservedNonce(ctx context.Context, wallet common.Address, chainID uint64, nonce uint64) error

	// RemoveReservedNonce removes a nonce from the reserved set.
	// This should be called when a transaction is confirmed or abandoned.
	RemoveReservedNonce(ctx context.Context, wallet common.Address, chainID uint64, nonce uint64) error

	// ListAll returns all stored nonce states.
	// Used during recovery to reconcile state.
	ListAll(ctx context.Context) ([]*NonceState, error)
}

NonceStore provides persistence for nonce tracking state. Implement this interface to persist nonce state across process restarts.

Thread Safety: Implementations MUST be safe for concurrent use. The WalletManager will call these methods from multiple goroutines.

type PendingTx

type PendingTx struct {
	// Hash is the transaction hash
	Hash common.Hash
	// Wallet is the sender address
	Wallet common.Address
	// ChainID is the network chain ID
	ChainID uint64
	// Nonce is the transaction nonce
	Nonce uint64
	// Status is the current status of the transaction
	Status PendingTxStatus
	// Transaction is the full transaction object (optional, for re-monitoring)
	Transaction *types.Transaction
	// Receipt is the transaction receipt (set when mined/reverted)
	Receipt *types.Receipt
	// CreatedAt is when the transaction was first tracked
	CreatedAt time.Time
	// UpdatedAt is when the transaction was last updated
	UpdatedAt time.Time
	// Metadata allows storing arbitrary application-specific data
	Metadata map[string]string
}

PendingTx represents a transaction that is being tracked for recovery

type PendingTxStatus

type PendingTxStatus string

PendingTxStatus represents the status of a pending transaction

const (
	// PendingTxStatusPending means the transaction is waiting to be mined
	PendingTxStatusPending PendingTxStatus = "pending"
	// PendingTxStatusBroadcasted means the transaction has been broadcast but status is unknown
	PendingTxStatusBroadcasted PendingTxStatus = "broadcasted"
	// PendingTxStatusMined means the transaction has been mined successfully
	PendingTxStatusMined PendingTxStatus = "mined"
	// PendingTxStatusReverted means the transaction was mined but reverted
	PendingTxStatusReverted PendingTxStatus = "reverted"
	// PendingTxStatusDropped means the transaction was dropped from the mempool
	PendingTxStatusDropped PendingTxStatus = "dropped"
	// PendingTxStatusReplaced means the transaction was replaced by another with same nonce
	PendingTxStatusReplaced PendingTxStatus = "replaced"
)

type ReaderFactory

type ReaderFactory func(chainID uint64, networkName string) (EthReader, error)

ReaderFactory creates an EthReader for a given network. This allows injecting mock readers for testing.

type RecoveryHandler

type RecoveryHandler struct {
	// contains filtered or unexported fields
}

RecoveryHandler manages the recovery process for a WalletManager

func (*RecoveryHandler) Recover

Recover performs recovery after a crash or restart. It reconciles nonce state with the chain and resumes monitoring pending transactions.

This method should be called once during application startup, before processing new transactions.

type RecoveryOptions

type RecoveryOptions struct {
	// ResumeMonitoring determines whether to start monitoring recovered pending transactions.
	// If false, transactions are just marked with their current status.
	// Default: true
	ResumeMonitoring bool

	// TxCheckInterval is the interval for checking transaction status during monitoring.
	// Default: 5 seconds
	TxCheckInterval time.Duration

	// MaxConcurrentMonitors is the maximum number of transactions to monitor concurrently.
	// Default: 10
	MaxConcurrentMonitors int

	// OnTxRecovered is called for each recovered transaction.
	// Can be used to resume application-specific logic.
	OnTxRecovered func(tx *PendingTx)

	// OnTxMined is called when a recovered transaction is mined.
	OnTxMined func(tx *PendingTx, receipt *types.Receipt)

	// OnTxDropped is called when a recovered transaction is determined to be dropped.
	OnTxDropped func(tx *PendingTx)
}

RecoveryOptions configures the recovery process

func DefaultRecoveryOptions

func DefaultRecoveryOptions() RecoveryOptions

DefaultRecoveryOptions returns the default recovery options

type RecoveryResult

type RecoveryResult struct {
	// RecoveredTxs is the number of transactions that were recovered and re-monitored
	RecoveredTxs int
	// MinedTxs is the number of transactions that were found to be already mined
	MinedTxs int
	// DroppedTxs is the number of transactions that were found to be dropped
	DroppedTxs int
	// ReconciledNonces is the number of wallet/network pairs whose nonce state was reconciled
	ReconciledNonces int
	// Errors contains any non-fatal errors encountered during recovery
	Errors []error
}

RecoveryResult contains the results of a recovery operation

type ResumeTransactionOptions

type ResumeTransactionOptions struct {
	// NumRetries is the maximum number of retries for gas bumping
	// Default: 9
	NumRetries int
	// SleepDuration is the duration to sleep between retries
	// Default: 5 seconds
	SleepDuration time.Duration
	// TxCheckInterval is the interval for checking transaction status
	// Default: 5 seconds
	TxCheckInterval time.Duration
	// MaxGasPrice is the maximum gas price (in gwei) to use when bumping gas
	// Default: 5x the original tx's gas price
	MaxGasPrice float64
	// MaxTipCap is the maximum tip cap (in gwei) to use when bumping gas
	// Default: 5x the original tx's tip cap
	MaxTipCap float64
	// TxMinedHook is called when the transaction is mined
	TxMinedHook TxMinedHook
}

ResumeTransactionOptions configures how a pending transaction should be resumed

func DefaultResumeTransactionOptions

func DefaultResumeTransactionOptions() ResumeTransactionOptions

DefaultResumeTransactionOptions returns the default resume options

type RetryConfig

type RetryConfig struct {
	MaxAttempts     int           // Maximum number of retry attempts (renamed from NumRetries)
	SleepDuration   time.Duration // Sleep between retry attempts
	TxCheckInterval time.Duration // Interval for checking tx status
	SlowTxTimeout   time.Duration // Time before considering a tx "slow" during monitoring
}

RetryConfig holds immutable retry/timing configuration.

type SimulationFailedHook

type SimulationFailedHook func(tx *types.Transaction, revertData []byte, abiError *abi.Error, revertParams any, err error) (shouldRetry bool, retErr error)

SimulationFailedHook is called when eth_call simulation detects that the transaction would revert. This is called AFTER the transaction is built but BEFORE it is signed or broadcast.

This hook is distinct from GasEstimationFailedHook: gas estimation failure means the node could not estimate gas at all, while simulation failure means the tx was estimated successfully but eth_call showed it would revert at the current state.

Parameters:

  • tx: the built (unsigned) transaction that would revert, always non-nil.
  • revertData: raw revert bytes from the node, always non-nil (may be empty).
  • abiError: the decoded ABI error if ABIs were provided and decoding succeeded, nil otherwise.
  • revertParams: the decoded revert parameters if ABIs were provided and decoding succeeded, nil otherwise.
  • err: the wrapped simulation error (always non-nil, wraps ErrSimulatedTxReverted).

Return values:

  • shouldRetry=true: the execution will retry (counts against MaxAttempts).
  • shouldRetry=false: the execution stops and returns the simulation error.
  • retErr non-nil: the execution stops immediately with retErr, regardless of shouldRetry.

Note: each retry still counts against MaxAttempts even when shouldRetry is true. The nonce acquired for this attempt is preserved for the retry.

type SimulationRevertError added in v1.1.9

type SimulationRevertError struct {
	Tx           *types.Transaction // the built tx that would revert
	RevertData   []byte             // raw revert bytes from the node
	AbiError     *abi.Error         // decoded ABI error, nil if unavailable
	RevertParams any                // decoded parameters, nil if AbiError is nil
	Err          error              // underlying error chain (preserves ErrSimulatedTxReverted, ErrEnsureTxOutOfRetries, etc.)
}

SimulationRevertError is a structured error returned when eth_call simulation detects that a transaction would revert. Callers can extract it with errors.As to access the built transaction, raw revert data, and ABI-decoded error details.

AbiError and RevertParams are populated only when ABIs are provided via SetAbis() and the revert data contains a decodable Solidity custom error.

func (*SimulationRevertError) Error added in v1.1.9

func (e *SimulationRevertError) Error() string

func (*SimulationRevertError) Unwrap added in v1.1.9

func (e *SimulationRevertError) Unwrap() error

type TxExecutionContext

type TxExecutionContext struct {
	Params TxParams
	Retry  RetryConfig
	Gas    GasBounds
	Hooks  TxHooks
	State  TxRetryState // The only mutable part
}

TxExecutionContext holds the state and parameters for transaction execution. It composes immutable configuration (Params, Retry, Gas, Hooks) with mutable retry state (State). Only State fields should be mutated during execution.

func NewTxExecutionContext

func NewTxExecutionContext(
	params TxParams,
	retry RetryConfig,
	gas GasBounds,
	hooks TxHooks,
	initialGasPrice float64,
	initialTipCap float64,
) (*TxExecutionContext, error)

NewTxExecutionContext creates a new transaction execution context from structured sub-types. initialGasPrice and initialTipCap are the starting gas prices (gwei) for the first attempt.

func (*TxExecutionContext) AdjustGasPricesForSlowTx

func (ctx *TxExecutionContext) AdjustGasPricesForSlowTx(tx *types.Transaction) bool

AdjustGasPricesForSlowTx is a deprecated alias for BumpGasForSlowTx.

func (*TxExecutionContext) BumpGasForSlowTx

func (ctx *TxExecutionContext) BumpGasForSlowTx(tx *types.Transaction) bool

BumpGasForSlowTx adjusts gas prices when a transaction is slow. It reads from Gas (immutable bounds) and writes to State (mutable). Returns true if adjustment was applied, false if limits were reached.

func (*TxExecutionContext) IncrementRetryAndCheck

func (ctx *TxExecutionContext) IncrementRetryAndCheck(errorMsg string) *TxExecutionResult

IncrementRetryAndCheck increments retry count and checks if we've exceeded max attempts. Returns a TxExecutionResult with ActionReturn if retries are exhausted, nil otherwise.

func (*TxExecutionContext) IncrementRetryCountAndCheck

func (ctx *TxExecutionContext) IncrementRetryCountAndCheck(errorMsg string) *TxExecutionResult

IncrementRetryCountAndCheck is a deprecated alias for IncrementRetryAndCheck.

type TxExecutionResult

type TxExecutionResult struct {
	Transaction *types.Transaction
	Receipt     *types.Receipt
	Action      LoopAction
	Error       error
}

TxExecutionResult represents the outcome of a transaction execution step.

type TxHooks

type TxHooks struct {
	BeforeSignAndBroadcast Hook
	AfterSignAndBroadcast  Hook
	GasEstimationFailed    GasEstimationFailedHook
	SimulationFailed       SimulationFailedHook
	TxMined                TxMinedHook
	ABIs                   []abi.ABI
}

TxHooks holds all callback hooks (set once at construction).

type TxInfo

type TxInfo struct {
	Status  TxInfoStatus
	Receipt *types.Receipt
}

TxInfo represents transaction information returned by the reader. This mirrors the essential fields from jarviscommon.TxInfo.

type TxInfoStatus

type TxInfoStatus string

TxInfoStatus represents the status of a transaction in the monitoring/execution flow.

Statuses are categorized by origin:

From the external TxMonitor (node/mempool state):

  • TxStatusMined, TxStatusReverted, TxStatusLost, TxStatusPending, TxStatusDone

Generated internally by walletarmy (NOT from the monitor):

  • TxStatusSlow: fired when a broadcasted tx is not mined within SlowTxTimeout. This is a walletarmy-level timeout, not a status reported by any node or monitor.
  • TxStatusCancelled: fired when the caller's context is cancelled.
const (
	// TxStatusMined indicates the transaction was mined successfully.
	// Origin: mapped from TxMonitor "done" status.
	TxStatusMined TxInfoStatus = "mined"
	// TxStatusReverted indicates the transaction was mined but execution reverted.
	// Origin: mapped from TxMonitor "reverted" status.
	TxStatusReverted TxInfoStatus = "reverted"
	// TxStatusLost indicates the transaction was dropped from the mempool.
	// Origin: mapped from TxMonitor "lost" status.
	TxStatusLost TxInfoStatus = "lost"
	// TxStatusSlow indicates the transaction has not been mined within SlowTxTimeout
	// after the monitor has checked the node at least once.
	// Origin: generated internally by MonitorTxContext. The slow timer is deferred
	// until the first non-terminal monitor event, then fires after SlowTxTimeout.
	// This is NOT a status from the TxMonitor — it is a walletarmy-level timeout signal.
	TxStatusSlow TxInfoStatus = "slow"
	// TxStatusCancelled indicates the monitoring was cancelled via context.
	// Origin: generated internally by MonitorTxContext when ctx.Done() fires.
	TxStatusCancelled TxInfoStatus = "cancelled"
	// TxStatusPending indicates the transaction is still pending
	TxStatusPending TxInfoStatus = "pending"
	// TxStatusDone is the raw status from jarvis monitor indicating success
	TxStatusDone TxInfoStatus = "done"
)

type TxMinedHook

type TxMinedHook func(tx *types.Transaction, receipt *types.Receipt) error

TxMinedHook is called when a transaction is mined (either successfully or reverted).

Parameters:

  • tx: the mined transaction, always non-nil.
  • receipt: the transaction receipt, always non-nil. Check receipt.Status (types.ReceiptStatusSuccessful or types.ReceiptStatusFailed) to determine whether the transaction succeeded or reverted.

Return a non-nil error to propagate it to the EnsureTx caller. The transaction and receipt are still returned alongside the error.

type TxMonitor

type TxMonitor interface {
	// MakeWaitChannelWithInterval creates a channel that receives status updates
	MakeWaitChannelWithInterval(txHash string, interval time.Duration) <-chan TxMonitorStatus
}

TxMonitor defines the minimal interface for monitoring transaction status.

func DefaultTxMonitorFactory

func DefaultTxMonitorFactory(r EthReader) TxMonitor

DefaultTxMonitorFactory is the default factory that creates jarvis tx monitors. If the reader is a readerAdapter (wrapping a jarvis reader), it uses the underlying reader. Otherwise, it returns nil (custom implementations should provide their own monitor factory).

func NewTxMonitorAdapter

func NewTxMonitorAdapter(m *monitor.TxMonitor) TxMonitor

NewTxMonitorAdapter creates a TxMonitor from a jarvis monitor

type TxMonitorFactory

type TxMonitorFactory func(reader EthReader) TxMonitor

TxMonitorFactory creates a TxMonitor for a given network. This allows injecting mock monitors for testing.

type TxMonitorStatus

type TxMonitorStatus struct {
	Status  string
	Receipt *types.Receipt
}

TxMonitorStatus represents the status of a monitored transaction.

type TxParams

type TxParams struct {
	TxType  uint8
	From    common.Address
	To      common.Address
	Value   *big.Int
	Data    []byte
	Network networks.Network
	// SkipSimulation, when true, skips the eth_call simulation that normally runs
	// after BuildTx succeeds but before signing and broadcasting. This allows
	// forcing a transaction through even when the simulation indicates it would
	// revert. The caller should typically also provide a manual gas limit
	// (via TxRequest.SetGasLimit or the GasLimit field in TxRetryState), since
	// gas estimation may fail for the same reason the simulation would revert.
	SkipSimulation bool
}

TxParams holds immutable transaction parameters (set once, never mutated).

type TxRequest

type TxRequest struct {
	// contains filtered or unexported fields
}

TxRequest represents a transaction request with builder pattern

func (*TxRequest) Execute

func (r *TxRequest) Execute() (*types.Transaction, *types.Receipt, error)

Execute executes the transaction request using a background context. For production use, prefer ExecuteContext to allow cancellation.

func (*TxRequest) ExecuteContext

func (r *TxRequest) ExecuteContext(ctx context.Context) (*types.Transaction, *types.Receipt, error)

ExecuteContext executes the transaction request with context support. The context allows the caller to cancel long-running retry loops.

func (*TxRequest) SetAbis

func (r *TxRequest) SetAbis(abis ...abi.ABI) *TxRequest

SetAbis sets the abis

func (*TxRequest) SetAfterSignAndBroadcastHook

func (r *TxRequest) SetAfterSignAndBroadcastHook(hook Hook) *TxRequest

SetAfterSignAndBroadcastHook sets the hook to be called after signing and broadcasting

func (*TxRequest) SetBeforeSignAndBroadcastHook

func (r *TxRequest) SetBeforeSignAndBroadcastHook(hook Hook) *TxRequest

SetBeforeSignAndBroadcastHook sets the hook to be called before signing and broadcasting

func (*TxRequest) SetData

func (r *TxRequest) SetData(data []byte) *TxRequest

SetData sets the transaction data

func (*TxRequest) SetExtraGasLimit

func (r *TxRequest) SetExtraGasLimit(extraGasLimit uint64) *TxRequest

SetExtraGasLimit sets the extra gas limit

func (*TxRequest) SetExtraGasPrice

func (r *TxRequest) SetExtraGasPrice(extraGasPrice float64) *TxRequest

SetExtraGasPrice sets the extra gas price

func (*TxRequest) SetExtraTipCapGwei

func (r *TxRequest) SetExtraTipCapGwei(extraTipCapGwei float64) *TxRequest

SetExtraTipCapGwei sets the extra tip cap in gwei

func (*TxRequest) SetFrom

func (r *TxRequest) SetFrom(from common.Address) *TxRequest

SetFrom sets the from address

func (*TxRequest) SetGasEstimationFailedHook

func (r *TxRequest) SetGasEstimationFailedHook(hook GasEstimationFailedHook) *TxRequest

SetGasEstimationFailedHook sets the hook to be called when gas estimation fails

func (*TxRequest) SetGasLimit

func (r *TxRequest) SetGasLimit(gasLimit uint64) *TxRequest

SetGasLimit sets the gas limit

func (*TxRequest) SetGasPrice

func (r *TxRequest) SetGasPrice(gasPrice float64) *TxRequest

SetGasPrice sets the gas price

func (*TxRequest) SetIdempotencyKey

func (r *TxRequest) SetIdempotencyKey(key string) *TxRequest

SetIdempotencyKey sets a unique key to prevent duplicate transaction submissions. If the same key is used again, the previous transaction result will be returned instead of submitting a new transaction. Requires an IdempotencyStore to be configured on the WalletManager.

func (*TxRequest) SetMaxGasPrice

func (r *TxRequest) SetMaxGasPrice(maxGasPrice float64) *TxRequest

SetMaxGasPrice sets the maximum gas price limit

func (*TxRequest) SetMaxTipCap

func (r *TxRequest) SetMaxTipCap(maxTipCap float64) *TxRequest

SetMaxTipCap sets the maximum tip cap limit

func (*TxRequest) SetNetwork

func (r *TxRequest) SetNetwork(network networks.Network) *TxRequest

SetNetwork sets the network

func (*TxRequest) SetNumRetries

func (r *TxRequest) SetNumRetries(numRetries int) *TxRequest

SetNumRetries sets the number of retries

func (*TxRequest) SetSimulationFailedHook

func (r *TxRequest) SetSimulationFailedHook(hook SimulationFailedHook) *TxRequest

SetSimulationFailedHook sets the hook to be called when eth_call simulation fails. This hook is called when the transaction would revert, allowing the caller to decide whether to retry or handle the error.

func (*TxRequest) SetSkipSimulation

func (r *TxRequest) SetSkipSimulation(skip bool) *TxRequest

SetSkipSimulation skips the eth_call simulation that runs before signing and broadcasting. When set to true, the transaction will be signed and broadcast even if it would revert. This is useful when you know the simulation will fail but want to force the transaction through. Note: you likely also need to set a manual gas limit via SetGasLimit, since gas estimation may fail for the same reason the simulation would revert.

func (*TxRequest) SetSleepDuration

func (r *TxRequest) SetSleepDuration(sleepDuration time.Duration) *TxRequest

SetSleepDuration sets the sleep duration

func (*TxRequest) SetTipCapGwei

func (r *TxRequest) SetTipCapGwei(tipCapGwei float64) *TxRequest

SetTipCapGwei sets the tip cap in gwei

func (*TxRequest) SetTo

func (r *TxRequest) SetTo(to common.Address) *TxRequest

SetTo sets the to address

func (*TxRequest) SetTxCheckInterval

func (r *TxRequest) SetTxCheckInterval(txCheckInterval time.Duration) *TxRequest

SetTxCheckInterval sets the transaction check interval

func (*TxRequest) SetTxMinedHook

func (r *TxRequest) SetTxMinedHook(hook TxMinedHook) *TxRequest

SetTxMinedHook sets the hook to be called when a transaction is mined. This hook is called for both successful and reverted transactions.

func (*TxRequest) SetTxType

func (r *TxRequest) SetTxType(txType uint8) *TxRequest

SetTxType sets the transaction type

func (*TxRequest) SetValue

func (r *TxRequest) SetValue(value *big.Int) *TxRequest

SetValue sets the transaction value

type TxRetryState

type TxRetryState struct {
	AttemptCount int      // Number of retry attempts so far
	GasPrice     float64  // Current gas price for next attempt (gwei)
	TipCap       float64  // Current tip cap for next attempt (gwei)
	GasLimit     uint64   // May be overridden by hooks
	Nonce        *big.Int // nil = acquire new, non-nil = reuse this nonce
	OldTxs       map[string]*types.Transaction
	ResumeWith   *types.Transaction // If non-nil, skip build/broadcast and go straight to monitoring
}

TxRetryState holds all mutable state that changes during the retry loop. This is the ONLY part of TxExecutionContext that should be mutated during execution.

type TxStore

type TxStore interface {
	// Save persists a pending transaction. Creates or updates based on hash.
	Save(ctx context.Context, tx *PendingTx) error

	// Get retrieves a pending transaction by hash.
	// Returns nil, nil if not found (not an error).
	Get(ctx context.Context, hash common.Hash) (*PendingTx, error)

	// GetByNonce retrieves pending transactions by wallet, chainID, and nonce.
	// There may be multiple if a nonce was reused (replacement tx).
	GetByNonce(ctx context.Context, wallet common.Address, chainID uint64, nonce uint64) ([]*PendingTx, error)

	// ListPending returns all transactions with pending/broadcasted status for a wallet on a network.
	ListPending(ctx context.Context, wallet common.Address, chainID uint64) ([]*PendingTx, error)

	// ListAllPending returns all pending/broadcasted transactions across all wallets/networks.
	// Used during recovery.
	ListAllPending(ctx context.Context) ([]*PendingTx, error)

	// UpdateStatus updates the status of a transaction and optionally sets the receipt.
	UpdateStatus(ctx context.Context, hash common.Hash, status PendingTxStatus, receipt *types.Receipt) error

	// Delete removes a transaction record.
	Delete(ctx context.Context, hash common.Hash) error

	// DeleteOlderThan removes transactions older than the given duration.
	// Used for cleanup of old completed transactions.
	DeleteOlderThan(ctx context.Context, age time.Duration) (int, error)
}

TxStore provides persistence for in-flight transaction tracking. Implement this interface to track and recover pending transactions across restarts.

Thread Safety: Implementations MUST be safe for concurrent use.

type WalletManager

type WalletManager struct {
	// contains filtered or unexported fields
}

WalletManager manages

  1. multiple wallets and their informations in its life time. It basically gives next nonce to do transaction for specific wallet and specific network. It queries the node to check the nonce in lazy maner, it also takes mining txs into account.
  2. multiple networks gas price. The gas price will be queried lazily prior to txs and will be stored as cache for a while
  3. txs in the context manager's life time
  4. circuit breakers for RPC node failover
  5. idempotency keys for preventing duplicate transactions
  6. default configuration that TxRequest inherits

func NewWalletManager

func NewWalletManager(opts ...WalletManagerOption) *WalletManager

NewWalletManager creates a new WalletManager with optional configuration

func (*WalletManager) Account

func (wm *WalletManager) Account(wallet common.Address) *account.Account

Account returns the account for the given wallet address

func (*WalletManager) Analyzer

func (wm *WalletManager) Analyzer(network networks.Network) (*txanalyzer.TxAnalyzer, error)

Analyzer returns the transaction analyzer for the given network. Returns an error if the network could not be initialized or if the circuit breaker is open.

func (*WalletManager) BroadcastTx

func (wm *WalletManager) BroadcastTx(
	tx *types.Transaction,
) (hash string, broadcasted bool, err BroadcastError)

func (*WalletManager) BroadcastTxSync

func (wm *WalletManager) BroadcastTxSync(
	tx *types.Transaction,
) (receipt *types.Receipt, err error)

func (*WalletManager) Broadcaster

func (wm *WalletManager) Broadcaster(network networks.Network) (EthBroadcaster, error)

Broadcaster returns the broadcaster for the given network. Returns an error if the network could not be initialized or if the circuit breaker is open.

func (*WalletManager) BuildTx

func (wm *WalletManager) BuildTx(
	txType uint8,
	from, to common.Address,
	nonce *big.Int,
	value *big.Int,
	gasLimit uint64,
	extraGasLimit uint64,
	gasPrice float64,
	extraGasPrice float64,
	tipCapGwei float64,
	extraTipCapGwei float64,
	data []byte,
	network networks.Network,
) (tx *types.Transaction, err error)

func (*WalletManager) Defaults

func (wm *WalletManager) Defaults() ManagerDefaults

Defaults returns the current default configuration. All fields are guaranteed to be non-zero (resolved at construction time).

func (*WalletManager) EnsureTx

func (wm *WalletManager) EnsureTx(
	txType uint8,
	from, to common.Address,
	value *big.Int,
	gasLimit uint64,
	extraGasLimit uint64,
	gasPrice float64,
	extraGasPrice float64,
	tipCapGwei float64,
	extraTipCapGwei float64,
	data []byte,
	network networks.Network,
) (tx *types.Transaction, receipt *types.Receipt, err error)

func (*WalletManager) EnsureTxWithHooks

func (wm *WalletManager) EnsureTxWithHooks(
	numRetries int,
	sleepDuration time.Duration,
	txCheckInterval time.Duration,
	txType uint8,
	from, to common.Address,
	value *big.Int,
	gasLimit uint64, extraGasLimit uint64,
	gasPrice float64, extraGasPrice float64,
	tipCapGwei float64, extraTipCapGwei float64,
	maxGasPrice float64, maxTipCap float64,
	data []byte,
	network networks.Network,
	beforeSignAndBroadcastHook Hook,
	afterSignAndBroadcastHook Hook,
	abis []abi.ABI,
	gasEstimationFailedHook GasEstimationFailedHook,
) (tx *types.Transaction, receipt *types.Receipt, err error)

EnsureTxWithHooks ensures the tx is broadcasted and mined, it will retry until the tx is mined. This is a convenience wrapper that uses context.Background(). For production use, prefer EnsureTxWithHooksContext to allow cancellation.

func (*WalletManager) EnsureTxWithHooksContext

func (wm *WalletManager) EnsureTxWithHooksContext(
	ctx context.Context,
	numRetries int,
	sleepDuration time.Duration,
	txCheckInterval time.Duration,
	txType uint8,
	from, to common.Address,
	value *big.Int,
	gasLimit uint64, extraGasLimit uint64,
	gasPrice float64, extraGasPrice float64,
	tipCapGwei float64, extraTipCapGwei float64,
	maxGasPrice float64, maxTipCap float64,
	data []byte,
	network networks.Network,
	beforeSignAndBroadcastHook Hook,
	afterSignAndBroadcastHook Hook,
	abis []abi.ABI,
	gasEstimationFailedHook GasEstimationFailedHook,
	simulationFailedHook SimulationFailedHook,
	txMinedHook TxMinedHook,
) (tx *types.Transaction, receipt *types.Receipt, err error)

EnsureTxWithHooksContext ensures the tx is broadcasted and mined, it will retry until the tx is mined. The context allows the caller to cancel the operation at any point.

It returns nil and error if:

  1. the tx couldn't be built
  2. the tx couldn't be broadcasted and get mined after numRetries retries
  3. the context is cancelled

It always returns the tx that was mined, either if the tx was successful or reverted.

Possible errors:

  1. ErrEstimateGasFailed
  2. ErrAcquireNonceFailed
  3. ErrGetGasSettingFailed
  4. ErrEnsureTxOutOfRetries
  5. ErrGasPriceLimitReached
  6. context.Canceled or context.DeadlineExceeded

If the caller wants to know the reason of the error, they can use errors.Is to check if the error is one of the above

After building the tx and before signing and broadcasting, the caller can provide a function hook to receive the tx and building error, if the hook returns an error, the process will be stopped and the error will be returned. If the hook returns nil, the process will continue even if the tx building failed, in this case, it will retry with the same data up to numRetries times and the hook will be called again.

After signing and broadcasting successfully, the caller can provide a function hook to receive the signed tx and broadcast error, if the hook returns an error, the process will be stopped and the error will be returned. If the hook returns nil, the process will continue to monitor the tx to see if the tx is mined or not. If the tx is not mined, the process will retry either with a new nonce or with higher gas price and tip cap to ensure the tx is mined. Hooks will be called again in the retry process.

func (*WalletManager) GasSetting

func (wm *WalletManager) GasSetting(network networks.Network) (*GasInfo, error)

GasSetting returns cached gas settings for the network, refreshing if stale.

func (*WalletManager) GetCircuitBreakerStats

func (wm *WalletManager) GetCircuitBreakerStats(network networks.Network) circuitbreaker.Stats

GetCircuitBreakerStats returns the circuit breaker statistics for a network

func (*WalletManager) IdempotencyStore

func (wm *WalletManager) IdempotencyStore() idempotency.Store

IdempotencyStore returns the configured idempotency store, or nil if not configured

func (*WalletManager) MonitorTx deprecated

func (wm *WalletManager) MonitorTx(tx *types.Transaction, network networks.Network, txCheckInterval time.Duration) <-chan TxInfo

MonitorTx non-blocking way to monitor the tx status, it returns a channel that will be closed when the tx monitoring is done the channel is supposed to receive the following values:

  1. "mined" if the tx is mined
  2. "slow" if the tx is too slow to be mined (so receiver might want to retry with higher gas price)
  3. other strings if the tx failed and the reason is returned by the node or other debugging error message that the node can return

Deprecated: Use MonitorTxContext instead for better cancellation support.

func (*WalletManager) MonitorTxContext

func (wm *WalletManager) MonitorTxContext(ctx context.Context, tx *types.Transaction, network networks.Network, txCheckInterval time.Duration) <-chan TxInfo

MonitorTxContext is a context-aware version of MonitorTx that supports cancellation. When the context is cancelled, the monitoring goroutine will exit and close the channel.

Status mapping:

  • The external TxMonitor returns raw statuses ("done", "reverted", "lost"). These are mapped to TxStatusMined, TxStatusReverted, TxStatusLost respectively.
  • TxStatusSlow is NOT from the monitor. It is generated internally when the monitor does not return a terminal status within SlowTxTimeout.
  • TxStatusCancelled is generated when the caller's context is cancelled.
  • Any unrecognized status from the monitor is treated as "still pending" and the slow timeout is allowed to fire.

Slow timer behavior:

The slow timer is NOT started immediately. It is deferred until the monitor
delivers its first non-terminal event (e.g., "pending", unknown status, or
channel close). This ensures the monitor has checked the node at least once
before judging the transaction as slow. Without this, a SlowTxTimeout shorter
than txCheckInterval would always fire before the first check, producing false
"slow" signals.

func (*WalletManager) NonceStore

func (wm *WalletManager) NonceStore() NonceStore

NonceStore returns the configured nonce store, or nil if not configured

func (*WalletManager) R

func (wm *WalletManager) R() *TxRequest

R creates a new transaction request (similar to go-resty's R() method). The request inherits default configuration from the WalletManager.

func (*WalletManager) Reader

func (wm *WalletManager) Reader(network networks.Network) (EthReader, error)

Reader returns the reader for the given network. Returns an error if the network could not be initialized or if the circuit breaker is open.

func (*WalletManager) RecordNetworkFailure

func (wm *WalletManager) RecordNetworkFailure(network networks.Network)

RecordNetworkFailure records a failed network operation for the circuit breaker

func (*WalletManager) RecordNetworkSuccess

func (wm *WalletManager) RecordNetworkSuccess(network networks.Network)

RecordNetworkSuccess records a successful network operation for the circuit breaker

func (*WalletManager) Recover

func (wm *WalletManager) Recover(ctx context.Context) (*RecoveryResult, error)

Recover performs recovery after a crash or restart. It reconciles nonce state with the chain and resumes monitoring pending transactions.

This should be called once during application startup before processing new transactions. If no persistence stores are configured, this is a no-op.

func (*WalletManager) RecoverWithOptions

func (wm *WalletManager) RecoverWithOptions(ctx context.Context, opts RecoveryOptions) (*RecoveryResult, error)

RecoverWithOptions performs recovery with custom options.

func (*WalletManager) ReleaseNonce

func (wm *WalletManager) ReleaseNonce(wallet common.Address, network networks.Network, nonce uint64)

ReleaseNonce releases a previously acquired nonce that was not used. This allows the nonce to be reused by subsequent transactions. Note: This only affects local tracking. If the transaction was already broadcast to some nodes, calling this may cause issues.

func (*WalletManager) ResetCircuitBreaker

func (wm *WalletManager) ResetCircuitBreaker(network networks.Network)

ResetCircuitBreaker resets the circuit breaker for a network

func (*WalletManager) ResumePendingTransaction

func (wm *WalletManager) ResumePendingTransaction(
	ctx context.Context,
	pendingTx *PendingTx,
	opts ResumeTransactionOptions,
) (*types.Transaction, *types.Receipt, error)

ResumePendingTransaction resumes the EnsureTx flow for a previously broadcast transaction. This is used during crash recovery to continue monitoring and retry logic (including gas bumping) for transactions that were pending when the application crashed.

The pendingTx.Transaction field must be non-nil as it contains the original transaction data.

This method enters the same execution flow as EnsureTxWithHooksContext, starting from the monitoring phase. If the transaction is slow, it will bump gas and retry. If it's lost, it will retry with a new nonce.

func (*WalletManager) SetAccount

func (wm *WalletManager) SetAccount(acc *account.Account)

SetAccount registers an account with the wallet manager

func (*WalletManager) SetDefaults

func (wm *WalletManager) SetDefaults(defaults ManagerDefaults)

SetDefaults updates the default configuration. Zero-value fields are filled with package-level constants automatically.

func (*WalletManager) SignTx

func (wm *WalletManager) SignTx(
	wallet common.Address,
	tx *types.Transaction,
	network networks.Network,
) (signedAddr common.Address, signedTx *types.Transaction, err error)

func (*WalletManager) TxStore

func (wm *WalletManager) TxStore() TxStore

TxStore returns the configured transaction store, or nil if not configured

func (*WalletManager) UnlockAccount

func (wm *WalletManager) UnlockAccount(addr common.Address) (*account.Account, error)

UnlockAccount unlocks an account from the jarvis wallet store and registers it

type WalletManagerOption

type WalletManagerOption func(*WalletManager)

WalletManagerOption is a function that configures a WalletManager

func WithBroadcasterFactory

func WithBroadcasterFactory(factory NetworkBroadcasterFactory) WalletManagerOption

WithBroadcasterFactory sets a custom broadcaster factory for testing or alternative implementations

func WithDefaultExtraGasLimit

func WithDefaultExtraGasLimit(extraGasLimit uint64) WalletManagerOption

WithDefaultExtraGasLimit sets the default extra gas limit added to estimates

func WithDefaultExtraGasPrice

func WithDefaultExtraGasPrice(extraGasPrice float64) WalletManagerOption

WithDefaultExtraGasPrice sets the default extra gas price added to suggestions

func WithDefaultExtraTipCap

func WithDefaultExtraTipCap(extraTipCap float64) WalletManagerOption

WithDefaultExtraTipCap sets the default extra tip cap added to suggestions

func WithDefaultGasPriceBumpFactor

func WithDefaultGasPriceBumpFactor(factor float64) WalletManagerOption

WithDefaultGasPriceBumpFactor sets the gas price multiplier when tx is slow. For example, 1.2 means 20% increase.

func WithDefaultGasPriceIncreasePercent deprecated

func WithDefaultGasPriceIncreasePercent(percent float64) WalletManagerOption

Deprecated: Use WithDefaultGasPriceBumpFactor instead.

func WithDefaultIdempotencyStore

func WithDefaultIdempotencyStore(ttl time.Duration) WalletManagerOption

WithDefaultIdempotencyStore sets up an in-memory idempotency store with the given TTL

func WithDefaultMaxGasPrice

func WithDefaultMaxGasPrice(maxGasPrice float64) WalletManagerOption

WithDefaultMaxGasPrice sets the default maximum gas price protection limit

func WithDefaultMaxTipCap

func WithDefaultMaxTipCap(maxTipCap float64) WalletManagerOption

WithDefaultMaxTipCap sets the default maximum tip cap protection limit

func WithDefaultNetwork

func WithDefaultNetwork(network networks.Network) WalletManagerOption

WithDefaultNetwork sets the default network for transactions

func WithDefaultNumRetries

func WithDefaultNumRetries(numRetries int) WalletManagerOption

WithDefaultNumRetries sets the default number of retries for transactions

func WithDefaultSleepDuration

func WithDefaultSleepDuration(duration time.Duration) WalletManagerOption

WithDefaultSleepDuration sets the default sleep duration between retries

func WithDefaultSlowTxTimeout

func WithDefaultSlowTxTimeout(timeout time.Duration) WalletManagerOption

WithDefaultSlowTxTimeout sets the timeout before considering a transaction "slow"

func WithDefaultTipCapBumpFactor

func WithDefaultTipCapBumpFactor(factor float64) WalletManagerOption

WithDefaultTipCapBumpFactor sets the tip cap multiplier when tx is slow. For example, 1.1 means 10% increase.

func WithDefaultTipCapIncreasePercent deprecated

func WithDefaultTipCapIncreasePercent(percent float64) WalletManagerOption

Deprecated: Use WithDefaultTipCapBumpFactor instead.

func WithDefaultTxCheckInterval

func WithDefaultTxCheckInterval(interval time.Duration) WalletManagerOption

WithDefaultTxCheckInterval sets the default transaction check interval

func WithDefaultTxType

func WithDefaultTxType(txType uint8) WalletManagerOption

WithDefaultTxType sets the default transaction type

func WithDefaults

func WithDefaults(defaults ManagerDefaults) WalletManagerOption

WithDefaults sets all default configuration at once

func WithIdempotencyStore

func WithIdempotencyStore(store idempotency.Store) WalletManagerOption

WithIdempotencyStore sets a custom idempotency store

func WithNetworkResolver

func WithNetworkResolver(resolver NetworkResolver) WalletManagerOption

WithNetworkResolver sets a custom network resolver for looking up networks by chain ID. This is required when using custom networks that are not built into jarvis (e.g., private chains, custom testnets).

The resolver is used by:

  • Crash recovery (to resume pending transactions)
  • BroadcastTx and BroadcastTxSync (to determine which network to broadcast to)

If not set, defaults to jarvis networks.GetNetworkByID which supports standard EVM networks (Ethereum, Polygon, Arbitrum, Optimism, etc.).

Example for custom networks:

wm := walletarmy.NewWalletManager(
    walletarmy.WithNetworkResolver(func(chainID uint64) (networks.Network, error) {
        switch chainID {
        case 12345:
            return myCustomNetwork, nil
        default:
            return networks.GetNetworkByID(chainID) // fallback to jarvis
        }
    }),
)

func WithNonceStore

func WithNonceStore(store NonceStore) WalletManagerOption

WithNonceStore sets a custom nonce store for persisting nonce state across restarts. This enables crash recovery for nonce tracking.

func WithReaderFactory

func WithReaderFactory(factory NetworkReaderFactory) WalletManagerOption

WithReaderFactory sets a custom reader factory for testing or alternative implementations

func WithTxMonitorFactory

func WithTxMonitorFactory(factory NetworkTxMonitorFactory) WalletManagerOption

WithTxMonitorFactory sets a custom tx monitor factory for testing or alternative implementations

func WithTxStore

func WithTxStore(store TxStore) WalletManagerOption

WithTxStore sets a custom transaction store for tracking in-flight transactions. This enables crash recovery for pending transactions.

Directories

Path Synopsis
examples
racing command
racing runs two independent WalletManager instances in parallel using the same ephemeral wallet on the same chain.
racing runs two independent WalletManager instances in parallel using the same ephemeral wallet on the same chain.
Package idempotency provides an idempotency key store for preventing duplicate transaction submissions.
Package idempotency provides an idempotency key store for preventing duplicate transaction submissions.
internal
circuitbreaker
Package circuitbreaker provides a simple circuit breaker implementation for protecting RPC node calls from cascading failures.
Package circuitbreaker provides a simple circuit breaker implementation for protecting RPC node calls from cascading failures.
nonce
Package nonce provides thread-safe nonce tracking for Ethereum wallets.
Package nonce provides thread-safe nonce tracking for Ethereum wallets.
Package testutil provides testing utilities for walletarmy.
Package testutil provides testing utilities for walletarmy.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL