How to Wait for Transaction Receipts Using `waitForTransactionReceipt` in GenLayer

Use client.waitForTransactionReceipt({ txHash, timeout?, pollInterval? }) to poll the GenLayer blockchain until a transaction is mined and returns a TransactionReceipt with status, gas usage, and return data.

The genlayer-js SDK provides this method on any client instance created with createClient(). It is the standard pattern for confirming that write operations—such as creating bets, resolving outcomes, or deploying contracts—have been finalized on-chain before your application proceeds. This article walks through concrete implementations found in the genlayerlabs/genlayer-project-boilerplate repository.

When to Use waitForTransactionReceipt

Any time you invoke a state-changing operation on a GenLayer contract, you receive a txHash immediately but must wait for miners to process the transaction. Waiting ensures:

  • The transaction succeeded (status === 1) or failed (status === 0)
  • You have access to return values, gas usage, and block number
  • Subsequent operations or UI updates depend on confirmed state

The boilerplate demonstrates this pattern in three production contexts.

Creating a Bet and Waiting for Confirmation

In frontend/lib/contracts/FootballBets.ts, the createBet method submits a bet to the FootballBets contract and blocks until receipt. This prevents UI race conditions where a user might refresh before the bet is recorded.

// FootballBets.ts line 210 (excerpt)
const txHash = await this.client.writeContract({
  address: this.contractAddress,
  functionName: "create_bet",
  args: [gameDate, team1, team2, predictedWinner],
});

const receipt = await this.client.waitForTransactionReceipt({
  txHash,
  timeout: 60_000,      // 60 seconds maximum wait
  pollInterval: 2_000,  // check every 2 seconds
});

if (receipt.status === 0) {
  throw new Error("Bet creation failed");
}
// Receipt contains: status, gasUsed, blockNumber, returnData, etc.

The timeout and pollInterval parameters are optional. Omitting them uses SDK defaults (typically 2-second polling with a reasonable timeout).

Resolving a Bet and Confirming Finality

The same pattern appears in resolveBet at line 241, where the application must confirm that a bet resolution is immutable before updating user balances.

// FootballBets.ts line 241 (excerpt)
const txHash = await this.client.writeContract({
  address: this.contractAddress,
  functionName: "resolve_bet",
  args: [betId],
});

const receipt = await this.client.waitForTransactionReceipt({
  txHash,
});

if (receipt.status === 0) {
  throw new Error("Bet resolution failed");
}
// Safe to proceed with UI refresh or dependent calls

Notice this call uses default timing parameters. Choose explicit values when your UX demands faster feedback or longer tolerance for network congestion.

Deploying Contracts and Capturing the Address

The deployment script at deploy/deployScript.ts uses waitForTransactionReceipt to obtain the deployed contract address—critical for subsequent contract interactions.

// deployScript.ts line 25 (excerpt)
const deployment = await client.deployContract({
  abi,
  bytecode,
  constructorArgs: [],
});

const receipt = await client.waitForTransactionReceipt({
  txHash: deployment.txHash,
});

console.log("Contract deployed at:", receipt.contractAddress);

Without awaiting the receipt, the script would exit before the deployment transaction is mined, leaving no reliable way to reference the new contract.

Understanding the waitForTransactionReceipt Parameters

Parameter Type Required Description
txHash string Yes The transaction hash returned from writeContract, deployContract, or similar
timeout number (ms) No Maximum time to wait before throwing an error
pollInterval number (ms) No Polling frequency; default ~2000ms

The method returns a Promise<TransactionReceipt> with these key fields:

  • status: 1 for success, 0 for failure
  • gasUsed: Gas consumed by the transaction
  • blockNumber: Block where the transaction was included
  • contractAddress: Deployed address (for deployments only)
  • returnData: ABI-encoded return values from the contract method

Client Setup Prerequisites

waitForTransactionReceipt is available on any client created through genlayer-js. The boilerplate configures this in frontend/lib/genlayer/client.ts:

import { createClient, simulator } from "genlayer-js";

export const client = createClient({
  chain: simulator,
  endpoint: process.env.NEXT_PUBLIC_GENLAYER_RPC,
  account: {
    address: "0x...", // or derived from private key
    privateKey: "0x...",
  },
});

The client exposes waitForTransactionReceipt as a bound method, so it inherits your configured endpoint and account context automatically.

Error Handling Patterns

Always inspect receipt.status before proceeding. A failed transaction consumes gas but produces no state changes. Common handling patterns include:

const receipt = await client.waitForTransactionReceipt({ txHash });

if (receipt.status === 0) {
  // Log for debugging, alert user, or trigger rollback
  console.error("Transaction failed:", txHash);
  throw new Error(`Transaction failed: ${receipt.errorMessage || "unknown"}`);
}

// Transaction confirmed—safe to use receipt.returnData
const [betId] = decodeReturnData(receipt.returnData, "uint256");

For deployment failures, check that receipt.contractAddress is defined and valid.

Summary

  • waitForTransactionReceipt is the polling abstraction in genlayer-js that converts a txHash into a confirmed TransactionReceipt
  • Three canonical use cases appear in the boilerplate: bet creation (FootballBets.ts:210), bet resolution (FootballBets.ts:241), and contract deployment (deployScript.ts:25)
  • Always verify receipt.status === 1 before treating operations as successful
  • Tune timeout and pollInterval based on expected block times and your application's responsiveness requirements
  • The method works identically in browser and Node.js environments through the same createClient configuration

Frequently Asked Questions

What happens when waitForTransactionReceipt times out?

The promise rejects with an error indicating the timeout was exceeded. Wrap the call in try/catch to handle network congestion or stalled transactions gracefully. You may then choose to retry, alert the user, or mark the operation as pending for later reconciliation.

Can I use waitForTransactionReceipt for read-only calls?

No. Read-only methods like readContract return data immediately without generating a transaction hash. waitForTransactionReceipt requires a txHash from a state-changing operation such as writeContract or deployContract.

Does polling with waitForTransactionReceipt consume rate limits or incur costs?

Polling is a client-side operation that queries the RPC endpoint. It does not consume gas or submit transactions. However, frequent polling against a hosted endpoint may count toward rate limits—tune pollInterval accordingly, especially in production deployments.

How do I extract custom return values from the receipt?

Decode receipt.returnData using the same ABI as your contract. The genlayer-js package provides utility functions compatible with viem-style encoding, or you can use standard Ethereum ABI decoding libraries to parse the byte array into typed values.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →