This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.
Polling
Poll operations until they succeed or timeout, with optional exponential backoff. Essential for waiting on asynchronous Ethereum operations like transaction confirmations, block finalization, or contract state changes.
Overview
Polling utilities provide:
- Configurable intervals: Control polling frequency
- Exponential backoff: Progressively longer intervals
- Timeout handling: Fail after maximum duration
- Validation: Custom success conditions
- Progress callbacks: Monitor polling attempts
Basic Usage
Simple Polling
Poll until result is truthy:
import { poll } from '@tevm/voltaire/utils';
const receipt = await poll(
() => provider.eth_getTransactionReceipt(txHash),
{
interval: 1000,
timeout: 60000
}
);
With Validation
Poll until validation passes:
const block = await poll(
() => provider.eth_getBlockByNumber('latest'),
{
interval: 1000,
validate: (block) => block.number >= targetBlock
}
);
Poll Options
PollOptions
interface PollOptions<T> {
interval?: number; // Polling interval in ms (default: 1000)
timeout?: number; // Max duration in ms (default: 60000)
backoff?: boolean; // Use exponential backoff (default: false)
backoffFactor?: number; // Backoff factor (default: 1.5)
maxInterval?: number; // Max interval with backoff (default: 10000)
validate?: (result: T) => boolean;
onPoll?: (result: T, attempt: number) => void;
}
Polling Functions
poll
Core polling function with full configuration:
const data = await poll(
() => provider.eth_call({ to, data }),
{
interval: 500,
timeout: 30000,
validate: (result) => result !== '0x',
onPoll: (result, attempt) => {
console.log(`Attempt ${attempt}: ${result}`);
}
}
);
pollUntil
More expressive when checking specific conditions:
import { pollUntil } from '@tevm/voltaire/utils';
const blockNumber = await pollUntil(
() => provider.eth_blockNumber(),
(num) => num >= 1000000n,
{ interval: 500, timeout: 30000 }
);
pollForReceipt
Convenience function for transaction receipts:
import { pollForReceipt } from '@tevm/voltaire/utils';
const receipt = await pollForReceipt(
txHash,
(hash) => provider.eth_getTransactionReceipt(hash),
{ timeout: 120000 } // 2 minutes
);
if (receipt.status === '0x1') {
console.log('Transaction successful!');
}
pollWithBackoff
Poll with exponential backoff enabled by default:
import { pollWithBackoff } from '@tevm/voltaire/utils';
const data = await pollWithBackoff(
() => provider.eth_call({ to, data }),
{
interval: 100, // Start at 100ms
backoffFactor: 2, // Double each time
maxInterval: 5000, // Cap at 5s
timeout: 30000
}
);
Exponential Backoff
How It Works
With backoff enabled, intervals increase exponentially:
interval = min(interval * backoffFactor, maxInterval)
Example Timeline
Configuration: { interval: 1000, backoffFactor: 1.5, maxInterval: 10000 }
| Attempt | Interval (ms) |
|---|
| 1st | 1000 |
| 2nd | 1500 |
| 3rd | 2250 |
| 4th | 3375 |
| 5th | 5062 |
| 6th | 7593 |
| 7th+ | 10000 (capped) |
When to Use Backoff
Use backoff when:
- Operation may take progressively longer
- Want to reduce server load over time
- Waiting for finality (takes longer as confirmations increase)
Don’t use backoff when:
- Need consistent check frequency
- Time-sensitive operations
- Known fixed interval required
Real-World Examples
Wait for Transaction
Wait for transaction confirmation with backoff:
async function waitForTransaction(
txHash: string,
confirmations: number = 1
): Promise<TransactionReceipt> {
const receipt = await pollForReceipt(
txHash,
(hash) => provider.eth_getTransactionReceipt(hash),
{
interval: 1000,
timeout: 120000,
backoff: true,
backoffFactor: 1.5
}
);
if (confirmations > 1) {
const targetBlock = receipt.blockNumber + BigInt(confirmations - 1);
await pollUntil(
() => provider.eth_blockNumber(),
(num) => num >= targetBlock,
{ interval: 12000 } // Block time
);
}
return receipt;
}
Wait for Contract Deployment
Poll until contract code is deployed:
const code = await pollUntil(
() => provider.eth_getCode(contractAddress),
(code) => code.length > 2, // More than '0x'
{
interval: 1000,
timeout: 60000,
backoff: true
}
);
console.log('Contract deployed!');
Wait for State Change
Poll contract state until condition met:
import { Abi } from '@tevm/voltaire/Abi';
const abi = [
{
type: 'function',
name: 'balanceOf',
inputs: [{ type: 'address', name: 'account' }],
outputs: [{ type: 'uint256', name: 'balance' }]
}
] as const;
const targetBalance = 1000000n;
await poll(
async () => {
const data = Abi.Function.encodeParams(abi[0], [address]);
const result = await provider.eth_call({
to: tokenAddress,
data
});
return Abi.Function.decodeResult(abi[0], result)[0];
},
{
interval: 2000,
validate: (balance: bigint) => balance >= targetBalance,
onPoll: (balance, attempt) => {
console.log(
`Attempt ${attempt}: ${balance}/${targetBalance}`
);
}
}
);
Wait for Block Number
Wait for specific block with progress:
async function waitForBlock(targetBlock: bigint): Promise<void> {
await pollUntil(
() => provider.eth_blockNumber(),
(current) => current >= targetBlock,
{
interval: 12000, // Average block time
timeout: 300000, // 5 minutes
onPoll: (current, attempt) => {
const remaining = targetBlock - current;
console.log(
`Block ${current}/${targetBlock} (${remaining} remaining)`
);
}
}
);
}
Multiple Conditions
Poll until multiple conditions satisfied:
await poll(
async () => {
const [receipt, blockNumber] = await Promise.all([
provider.eth_getTransactionReceipt(txHash),
provider.eth_blockNumber()
]);
return { receipt, blockNumber };
},
{
interval: 1000,
validate: ({ receipt, blockNumber }) => {
if (!receipt) return false;
if (receipt.status !== '0x1') return false;
const confirmations = blockNumber - receipt.blockNumber;
return confirmations >= 3;
}
}
);
Combining with Other Utils
Poll with Retry
Retry each poll attempt:
import { poll, retryWithBackoff } from '@tevm/voltaire/utils';
const data = await poll(
() => retryWithBackoff(
() => provider.eth_call({ to, data }),
{ maxRetries: 3 }
),
{
interval: 1000,
validate: (result) => result !== '0x'
}
);
Poll with Timeout Per Attempt
Add timeout to each poll attempt:
import { poll, withTimeout } from '@tevm/voltaire/utils';
const result = await poll(
() => withTimeout(
provider.eth_getBlockByNumber('latest'),
{ ms: 5000 }
),
{
interval: 1000,
timeout: 60000
}
);
Poll with Rate Limiting
Rate limit polling attempts:
import { poll, RateLimiter } from '@tevm/voltaire/utils';
const limiter = new RateLimiter({
maxRequests: 10,
interval: 1000
});
const receipt = await poll(
() => limiter.execute(
() => provider.eth_getTransactionReceipt(txHash)
),
{
interval: 500,
timeout: 60000
}
);
Error Handling
Handle Polling Errors
Errors during polling don’t stop the poll:
try {
const receipt = await poll(
() => provider.eth_getTransactionReceipt(txHash),
{ timeout: 60000 }
);
} catch (error) {
if (error.message.includes('timeout')) {
console.log('Transaction not confirmed within 60s');
}
}
Validation Errors
Distinguish between errors and unmet conditions:
const block = await poll(
async () => {
const block = await provider.eth_getBlockByNumber('latest');
if (!block) {
throw new Error('Failed to fetch block');
}
return block;
},
{
validate: (block) => block.number >= targetBlock,
interval: 1000
}
);
Best Practices
Choose Appropriate Intervals
- Transaction receipts: 1000ms (typical block time ~12s)
- Block numbers: 12000ms (matches block time)
- Contract state: 2000-5000ms (depends on update frequency)
- Off-chain data: 5000-10000ms (depends on API)
Set Reasonable Timeouts
- Fast operations: 30000ms (30s)
- Transaction confirmation: 120000ms (2 minutes)
- Multi-block operations: 300000ms (5 minutes)
Use Backoff for Long Operations
Enable backoff when:
- Operation may take minutes
- Checking less frequently over time is acceptable
- Want to reduce server load
Monitor Progress
Always use onPoll callback for observability:
const receipt = await pollForReceipt(
txHash,
(hash) => provider.eth_getTransactionReceipt(hash),
{
timeout: 120000,
onPoll: (receipt, attempt) => {
logger.info('Polling for receipt', {
txHash,
attempt,
found: receipt !== null
});
}
}
);
API Reference
poll
async function poll<T>(
fn: () => Promise<T>,
options?: PollOptions<T>
): Promise<T>
pollUntil
async function pollUntil<T>(
fn: () => Promise<T>,
predicate: (result: T) => boolean,
options?: Omit<PollOptions<T>, 'validate'>
): Promise<T>
pollForReceipt
async function pollForReceipt<TReceipt>(
txHash: string,
getReceipt: (hash: string) => Promise<TReceipt | null>,
options?: Omit<PollOptions<TReceipt | null>, 'validate'>
): Promise<TReceipt>
pollWithBackoff
async function pollWithBackoff<T>(
fn: () => Promise<T>,
options?: PollOptions<T>
): Promise<T>
See Also