Replace stuck transactions by preparing and sending calls again.
Key use cases:
- Replace transactions stuck due to low gas prices
- Speed up pending transactions by increasing gas fees
- Override transactions that have been pending for too long
- Cancel stuck transactions by replacing with a no-op
- Send the initial transaction
- Monitor the transaction status
- If the transaction is stuck/pending too long, re-prepare the same call
- Send the transaction with higher gas to replace the stuck transaction
- The original transaction gets dropped from mempool
- An Alchemy API Key
- A Gas Manager policy
- A signer to own the account and sign messages
If the original transaction is already being mined, the replacement transaction may be dropped. In this case, you won't be able to retrieve data using the replacement's call ID, and the original transaction will be included!
@alchemy/wallet-apis (v5.x.x) is currently in beta but is the recommended replacement for @account-kit/wallet-client (v4.x.x). If you run into any issues, please reach out.When re-sending calls without a nonceOverride, the client automatically uses the same nonce as the pending transaction, replacing it. See the sendCalls SDK reference for full parameter descriptions.
You'll need the following env variables:
ALCHEMY_API_KEY: An Alchemy API KeyALCHEMY_POLICY_ID: A Gas Manager policy IDPRIVATE_KEY: A private key for a signer
import { client, config } from "./client.ts";
// Send initial transaction
const { id } = await client.sendCalls({
capabilities: {
paymaster: {
policyId: config.policyId,
},
},
calls: [
{
to: "0x0000000000000000000000000000000000000000",
value: BigInt(0),
data: "0x",
},
],
});
console.log("Initial transaction:", id);
// Check status
const status = await client.getCallsStatus(id);
console.log("Transaction status:", status.status);
// If still pending (status 100), send replacement
if (status.status === 100) {
console.log("Transaction pending, sending replacement...");
// Re-send without nonceOverride to replace stuck transaction
const { id: replacementId } = await client.sendCalls({
capabilities: {
paymaster: {
policyId: config.policyId,
},
},
calls: [
{
to: "0x0000000000000000000000000000000000000000",
value: BigInt(0),
data: "0x",
},
],
});
console.log("Replacement transaction:", replacementId);
// Wait for replacement to confirm
const result = await client.waitForCallsStatus({ id: replacementId });
console.log("Replacement confirmed:", result);
}Using @account-kit/wallet-client (v4.x.x)?
The examples on this page use @alchemy/wallet-apis (v5.x.x). If you're using @account-kit/wallet-client (v4.x.x), the client setup looks like this:
import { LocalAccountSigner } from "@aa-sdk/core";
import { createSmartWalletClient } from "@account-kit/wallet-client";
import { alchemy, sepolia } from "@account-kit/infra";
const signer = LocalAccountSigner.privateKeyToAccountSigner("0xYOUR_PRIVATE_KEY" as const);
export const client = createSmartWalletClient({
transport: alchemy({ apiKey: "YOUR_API_KEY" }),
chain: sepolia,
signer,
account: signer.address, // can also be passed per action as `from` or `account`
// Optional: sponsor gas for your users (see "Sponsor gas" guide)
policyId: "YOUR_POLICY_ID",
});Key v4.x.x differences:
- Account address must be specified on the client or per action (
fromoraccount). In v5.x.x, the client automatically uses the signer's address as the account address via EIP-7702. - Chain imports come directly from
@account-kit/infrainstead ofviem/chains. - Numeric values use hex strings:
value: "0x0"instead ofvalue: BigInt(0). - In v4.x.x, the paymaster capability on
prepareCallsorsendCallsis calledpaymasterServiceinstead ofpaymaster, or you can set thepolicyIddirectly on the client. - Signers use
LocalAccountSigner/WalletClientSignerfrom@aa-sdk/core. In v5.x.x, a viemLocalAccountorWalletClientis used directly.
See the full migration guide for a complete cheat sheet.