Documentation Index
Fetch the complete documentation index at: https://docs.goldsky.com/llms.txt
Use this file to discover all available pages before exploring further.
Compose has built-in functionality for handling mempool eviction and re-orgs, making it easy to get strong guarantees about your transactions
without lots of bespoke code. Both wallet.writeContract() and wallet.sendTransaction() support confirmation and reorg handling.
Confirmations
By default all transactions via wallet.writeContract() or state-changing methods on contracts will wait for one confirmation before
resolving the method’s promise. This means that the transaction will be seen in at least one block by the time your task execution advances. You
can configure this further with the optional TransactionConfirmation logic. If your transaction doesn’t make it into the number of blocks specified
the context function call will fail and retry logic will kick in. If it still doesn’t make it into the desired number of blocks after retries are exhausted
then the promise will reject.
Here’s an example for both wallet.writeContract() and on a contract class method:
import { TaskContext } from "compose";
export async function main({ evm, env }: TaskContext) {
const wallet = await evm.wallet();
const resultId = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
const payouts = [1000n, 2000n, 3000n];
const { hash } = await wallet.writeContract(
evm.chains.polygon,
env.CONTRACT_ADDRESS as `0x${string}`,
"reportPayouts(bytes32,uint256[])",
[resultId, payouts],
{
// this will not resolve the promise until the transaction has been seen in 5 blocks
// if the transaction doesn't make it into five blocks (maybe is evicted from mempool, etc),
// then your retry config will determine whether to retry the transaction
confirmations: 5,
},
{
max_attempts: 5,
initial_interval_ms: 1000,
backoff_factor: 2,
}
);
}
Reorgs
Reorgs could revert your transaction after the promise is resolved and the confirmations are all seen. For example, if you have 5 confirmations
but later on a 100 block reorg occurs, you can configure your transaction with various retry “behaviors” to handle that situation. Like with confirmations,
reorg handling can be used both with wallet.writeContract() as well as any state-changing methods on a smart contract class.
There are several behaviors that we support currently:
| behavior | description | other params |
|---|
| replay | re-executes transaction with fresh gas parameters and nonce (recalculated at replay time) | n/a |
| log | just logs the reorg to your standard app logs | logLevel (optional) |
| task | allows you to pass a compose task to call for custom handling | task (name of compose task) |
Examples
import { TaskContext } from "compose";
export async function main({ evm, env }: TaskContext) {
const wallet = await evm.wallet();
const resultId = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
const payouts = [1000n, 2000n, 3000n];
// Replay on reorg
const { hash } = await wallet.writeContract(
evm.chains.polygon,
env.CONTRACT_ADDRESS as `0x${string}`,
"reportPayouts(bytes32,uint256[])",
[resultId, payouts],
{
confirmations: 5,
onReorg: {
action: {
// this will replay the transaction with a fresh nonce and gas if the transaction is reorged off chain after the 5 confirmations specified below
// it will then start watching again for the number of blocks specified in the "depth" property
type: "replay",
},
// we'll watch this transaction in the background for 200 blocks and replay it if the transaction receipt disappears
depth: 200,
},
},
{
max_attempts: 5,
initial_interval_ms: 1000,
backoff_factor: 2,
}
);
// Log on reorg
const { hash: hash2 } = await wallet.writeContract(
evm.chains.polygon,
env.CONTRACT_ADDRESS as `0x${string}`,
"reportPayouts(bytes32,uint256[])",
[resultId, payouts],
{
confirmations: 5,
onReorg: {
action: {
// this will just log in your normal app logs if the transaction is reorged off chain after the 5 confirmations specified below
type: "log",
logLevel: "warn", // defaults to "error"
},
// we'll watch this transaction in the background for 200 blocks and log if the transaction receipt disappears
depth: 200,
},
},
{
max_attempts: 5,
initial_interval_ms: 1000,
backoff_factor: 2,
}
);
// Custom task on reorg
const { hash: hash3 } = await wallet.writeContract(
evm.chains.polygon,
env.CONTRACT_ADDRESS as `0x${string}`,
"reportPayouts(bytes32,uint256[])",
[resultId, payouts],
{
confirmations: 5,
onReorg: {
action: {
// this will send the transaction as a payload to the specified task if the transaction is reorged off chain after the 5 confirmations specified below
type: "task",
task: "reorg-reconciler", // the name of your compose task with custom re-org handling logic
},
// we'll watch this transaction in the background for 200 blocks and call your "reorg-reconciler" if the transaction receipt disappears
depth: 200,
},
},
{
max_attempts: 5,
initial_interval_ms: 1000,
backoff_factor: 2,
}
);
}
Monitored transactions are checked for reorgs every 5 minutes. When a reorg is detected and the replay action is configured, the transaction is re-submitted with fresh gas parameters and nonce (not the original values, since the post-reorg state may differ).
Full TransactionConfirmation type
export type ReplayOnReorg = {
type: "replay";
};
export type LogOnReorg = {
type: "log";
logLevel?: "error" | "info" | "warn"; // defaults to "error"
};
export type CustomReorgAction = {
type: "task";
// your task will be sent with a payload the full transaction minus gas and nonce
task: string;
};
export type OnReorgOptions = ReplayOnReorg | LogOnReorg | CustomReorgAction;
export type OnReorgConfig = {
action: OnReorgOptions;
depth: number;
};
export interface TransactionConfirmation {
// this the number of block confirmations before we resolve the promise
// i.e. "wait 5 blocks before proceeding to the next step in my task"
confirmations?: number;
onReorg?: OnReorgConfig;
}