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:1for success,0for failuregasUsed: Gas consumed by the transactionblockNumber: Block where the transaction was includedcontractAddress: 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
waitForTransactionReceiptis the polling abstraction ingenlayer-jsthat converts atxHashinto a confirmedTransactionReceipt- 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 === 1before treating operations as successful - Tune
timeoutandpollIntervalbased on expected block times and your application's responsiveness requirements - The method works identically in browser and Node.js environments through the same
createClientconfiguration
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →