or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

contract-deployment.mdcontract-interaction.mdevent-management.mdindex.mdtransaction-handling.md
tile.json

transaction-handling.mddocs/

Transaction Handling

Transaction management including overrides, gas estimation, and transaction population. Provides comprehensive control over transaction parameters with support for EIP-1559, access lists, and custom data.

Capabilities

Transaction Overrides

Control transaction parameters through override objects.

/**
 * Basic transaction overrides for gas and execution parameters
 */
interface Overrides {
  /** Gas limit for transaction execution */
  gasLimit?: BigNumberish | Promise<BigNumberish>;
  /** Gas price (legacy transactions) */
  gasPrice?: BigNumberish | Promise<BigNumberish>;
  /** Maximum fee per gas (EIP-1559) */
  maxFeePerGas?: BigNumberish | Promise<BigNumberish>;
  /** Maximum priority fee per gas (EIP-1559) */
  maxPriorityFeePerGas?: BigNumberish | Promise<BigNumberish>;
  /** Transaction nonce */
  nonce?: BigNumberish | Promise<BigNumberish>;
  /** Transaction type (0 = legacy, 1 = EIP-2930, 2 = EIP-1559) */
  type?: number;
  /** EIP-2930 access list */
  accessList?: AccessListish;
  /** Custom data for L2/sidechain compatibility */
  customData?: Record<string, any>;
  /** Enable CCIP-Read (EIP-3668) support */
  ccipReadEnabled?: boolean;
}

/**
 * Transaction overrides with value field for payable functions
 */
interface PayableOverrides extends Overrides {
  /** Ether value to send with transaction */
  value?: BigNumberish | Promise<BigNumberish>;
}

/**
 * Transaction overrides for read-only calls
 */
interface CallOverrides extends PayableOverrides {
  /** Block tag for historical state queries */
  blockTag?: BlockTag | Promise<BlockTag>;
  /** Address to simulate call from */
  from?: string | Promise<string>;
}

Usage Examples:

import { Contract } from "@ethersproject/contracts";
import { parseEther, parseUnits } from "@ethersproject/units";

const contract = new Contract(address, abi, signer);

// Basic transaction with gas overrides
await contract.transfer("0x...", parseEther("1.0"), {
  gasLimit: 21000,
  gasPrice: parseUnits("20", "gwei")
});

// EIP-1559 transaction
await contract.transfer("0x...", parseEther("1.0"), {
  maxFeePerGas: parseUnits("30", "gwei"),
  maxPriorityFeePerGas: parseUnits("2", "gwei")
});

// Payable function with value
await contract.deposit({
  value: parseEther("1.0"),
  gasLimit: 100000
});

// Read call with historical state
const balance = await contract.balanceOf("0x...", {
  blockTag: 12000000,
  from: "0x..." // Simulate call from different address
});

// Transaction with access list (EIP-2930)
await contract.complexFunction("param1", "param2", {
  type: 1,
  accessList: [
    {
      address: "0x...",
      storageKeys: ["0x..."]
    }
  ]
});

Gas Estimation

Estimate gas required for contract method calls.

/**
 * Gas estimation methods for all contract functions
 * Accessible via contract.estimateGas[methodName]
 */
readonly estimateGas: { [name: string]: ContractFunction<BigNumber> };

Usage Examples:

// Estimate gas for specific method
const gasEstimate = await contract.estimateGas.transfer("0x...", parseEther("1.0"));
console.log("Estimated gas:", gasEstimate.toString());

// Estimate with overrides
const gasWithOverrides = await contract.estimateGas.transfer(
  "0x...", 
  parseEther("1.0"),
  { value: parseEther("0.1") } // If transfer is payable
);

// Use estimate in actual transaction
await contract.transfer("0x...", parseEther("1.0"), {
  gasLimit: gasEstimate.mul(120).div(100) // Add 20% buffer
});

// Estimate complex function with parameters
const complexGas = await contract.estimateGas.complexFunction(
  "param1",
  ["array", "values"],
  { nested: "object" },
  {
    from: wallet.address, // Override sender for estimation
    value: parseEther("1.0")
  }
);

Transaction Population

Prepare transactions without sending them.

/**
 * Transaction population methods for all contract functions
 * Accessible via contract.populateTransaction[methodName]
 */
readonly populateTransaction: { [name: string]: ContractFunction<PopulatedTransaction> };

/**
 * Populated transaction object ready for signing and sending
 */
interface PopulatedTransaction {
  /** Recipient address */
  to?: string;
  /** Sender address */
  from?: string;
  /** Transaction nonce */
  nonce?: number;
  /** Gas limit */
  gasLimit?: BigNumber;
  /** Gas price (legacy) */
  gasPrice?: BigNumber;
  /** Transaction data (encoded function call) */
  data?: string;
  /** Ether value */
  value?: BigNumber;
  /** Chain ID */
  chainId?: number;
  /** Transaction type */
  type?: number;
  /** Access list */
  accessList?: AccessList;
  /** Max fee per gas (EIP-1559) */
  maxFeePerGas?: BigNumber;
  /** Max priority fee per gas (EIP-1559) */
  maxPriorityFeePerGas?: BigNumber;
  /** Custom data */
  customData?: Record<string, any>;
  /** CCIP-Read enabled flag */
  ccipReadEnabled?: boolean;
}

Usage Examples:

// Populate transaction without sending
const populatedTx = await contract.populateTransaction.transfer(
  "0x...", 
  parseEther("1.0")
);

console.log("Transaction data:", populatedTx.data);
console.log("To address:", populatedTx.to);

// Add custom overrides to populated transaction
populatedTx.gasLimit = BigNumber.from(25000);
populatedTx.gasPrice = parseUnits("25", "gwei");

// Send populated transaction manually
const sentTx = await signer.sendTransaction(populatedTx);
await sentTx.wait();

// Use with multisig or custom signing
const multisigTx = await contract.populateTransaction.adminFunction("param");
// Send to multisig contract for signing...

// Batch multiple transactions
const tx1 = await contract.populateTransaction.method1("param1"); 
const tx2 = await contract.populateTransaction.method2("param2");
// Submit to batch transaction contract...

Read-Only Calls

Call contract methods without sending transactions (for view/pure functions).

/**
 * Read-only method calls for all contract functions
 * Accessible via contract.callStatic[methodName]
 */
readonly callStatic: { [name: string]: ContractFunction };

Usage Examples:

// Force read-only call (even for non-view functions)
const result = await contract.callStatic.someFunction("param");

// Read-only call with block tag
const historicalBalance = await contract.callStatic.balanceOf("0x...", {
  blockTag: 12000000
});

// Simulate transaction without sending
const simulationResult = await contract.callStatic.transfer("0x...", parseEther("1.0"), {
  from: "0x..." // Simulate from different address
});

// Check if transaction would succeed
try {
  await contract.callStatic.complexFunction("param");
  console.log("Transaction would succeed");
} catch (error) {
  console.log("Transaction would fail:", error.reason);
}

Enhanced Transaction Responses

Contract transactions return enhanced response objects with event decoding.

/**
 * Enhanced transaction response with contract-specific wait method
 */
interface ContractTransaction extends TransactionResponse {
  /**
   * Wait for transaction confirmation with automatic event decoding
   * @param confirmations - Number of confirmations to wait for
   * @returns Promise resolving to ContractReceipt with decoded events
   */
  wait(confirmations?: number): Promise<ContractReceipt>;
}

/**
 * Enhanced transaction receipt with decoded contract events
 */
interface ContractReceipt extends TransactionReceipt {
  /** Array of decoded contract events from transaction */
  events?: Array<Event>;
}

Usage Examples:

// Send transaction and wait for confirmation
const tx = await contract.transfer("0x...", parseEther("1.0"));
console.log("Transaction hash:", tx.hash);

// Wait for confirmation with event decoding
const receipt = await tx.wait();

// Access decoded events
if (receipt.events) {
  for (const event of receipt.events) {
    if (event.event === "Transfer") {
      console.log("Transfer event:", event.args);
    }
  }
}

// Wait for multiple confirmations
const confirmedReceipt = await tx.wait(3);

// Access event utility methods
if (receipt.events && receipt.events.length > 0) {
  const event = receipt.events[0];
  
  // Get additional blockchain data
  const block = await event.getBlock();
  const transaction = await event.getTransaction();
  
  console.log("Block timestamp:", block.timestamp);
  console.log("Transaction from:", transaction.from);
}

Direct Function Access

Access contract functions through multiple interfaces for different use cases.

/**
 * Direct access to contract functions without result processing
 * Accessible via contract.functions[methodName]
 */
readonly functions: { [name: string]: ContractFunction };

Usage Examples:

// Direct function access (returns raw result)
const rawResult = await contract.functions.balanceOf("0x...");
console.log("Raw result:", rawResult); // Array format

// Compare with processed result
const processedResult = await contract.balanceOf("0x...");
console.log("Processed result:", processedResult); // Single value if function returns one value

// Multiple return values
const [reserve0, reserve1, timestamp] = await contract.functions.getReserves();
console.log("Reserve 0:", reserve0.toString());
console.log("Reserve 1:", reserve1.toString());
console.log("Timestamp:", timestamp.toString());

// Access function that conflicts with contract properties
const result = await contract.functions["name"](); // If "name" conflicts with contract.name property