Bridge Kit

Error Recovery and Troubleshooting

Bridge transfers can encounter two error types. Hard errors stop execution. Soft errors let you recover and retry the transfer. Bridge Kit provides error handling that helps you respond in both cases.

Hard errors throw exceptions such as validation errors, configuration issues, and authentication problems. Soft errors occur during transfer execution but return enough transaction information for recovery. Examples include insufficient balance, network timeouts, and RPC connectivity issues.

This guide helps you identify failure points, recover partial transfers, and implement error handling patterns.

This section explains how to identify where a transfer failed and resume the transfer manually.

Bridge transfers use Circle's CCTP protocol provider, which breaks each transaction into steps:

  • approve: Allows the contract to spend USDC.
  • burn: Burns USDC on the source blockchain and generates an attestation.
  • fetchAttestation: Waits for Circle to sign the burn proof.
  • mint: Mints USDC on the destination blockchain with the attestation.

When a transfer fails, Bridge Kit returns a BridgeResult object showing which steps completed and which failed. This lets you resume the transfer manually using the CCTPv2BridgingProvider, as shown in examples for failed attestation fetch and failed mint.

Focus on these BridgeResult properties during recovery:

  • result.state - shows whether the transfer succeeded or failed (pending, success, error)
  • result.steps - each object contains:
    • name: the name of the step
    • state: the status of the step
    • txHash: the transaction hash if the step completed
    • error: an error message if the step failed

This example shows a returned result object for a transaction that failed when fetching an attestation:

Shell
result.state: 'error'
result.steps: [
  { name: 'approve', state: 'success', txHash: '0x123...' },
  { name: 'burn', state: 'success', txHash: '0x456...' },
  { name: 'fetchAttestation', state: 'error', error: 'Network timeout' },
]

This example shows how to check for completed steps and use a helper function to find specific steps:

Typescript
// Start a transfer that might fail
const result = await kit.bridge({
  from: { adapter: sourceAdapter, chain: "Ethereum" },
  to: { adapter: destAdapter, chain: "Arbitrum" },
  amount: "100.00",
});

// Check which steps completed successfully
console.log("Transfer state:", result.state);
console.log("Steps:", result.steps);

// Helper function to find specific steps
const getStep = (stepName: string) =>
  result.steps.find((step) => step.name === stepName);
const approveStep = getStep("approve");
const burnStep = getStep("burn");
const attestationStep = getStep("fetchAttestation");
const mintStep = getStep("mint");

This section describes how you can implement recovery patterns.

If a transfer fails, you can retry it with the retry method. Pass the failed BridgeResult and the to and from adapters.

This example shows how the retry method works:

Typescript
const result = await kit.bridge({
  from: { adapter, chain: "Ethereum" },
  to: { adapter, chain: "Arbitrum" },
  amount: "10.00",
});

if (result.state === "error") {
  const retryResult = await kit.retry(result, {
    from: adapter,
    to: adapter,
  });
  console.log(inspect(retryResult, false, null, true));
} else {
  console.log(inspect(result, false, null, true));
}

This example forces the mint step to fail and then retries with a valid to adapter:

Typescript
const adapter = createAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as string,
});

// This is a fake adapter to force an error at the mint step
const fakeAdapter = createAdapterFromPrivateKey({
  privateKey: ("0x" + "1".repeat(64)) as string,
});

const findErrorStep = (result: BridgeResult) => {
  if (result.state === "error") {
    return result.steps.find((step) => step.state === "error");
  }
  return null;
};

const kit = new BridgeKit();

const result = await kit.bridge({
  from: { adapter, chain: "Ethereum_Sepolia" },
  to: { adapter: fakeAdapter, chain: "Arbitrum_Sepolia" },
  amount: "1.00",
});

console.log("INITIAL RESULT", inspect(result, false, null, true));

if (result.state === "error") {
  const errorStep = findErrorStep(result);
  if (
    errorStep &&
    errorStep.errorMessage?.includes("gas required exceeds allowance") // This is an example error message
  ) {
    const retryResult = await kit.retry(result, {
      from: adapter,
      to: adapter, // To succeed this time we're using the correct adapter
    });
    console.log("RETRY RESULT", inspect(retryResult, false, null, true));
  }
}

This section lists common issues and solutions.

Ensure you have enough USDC in your wallet before transferring to avoid an insufficient balance error.

This example checks your wallet balance:

Typescript
import "dotenv/config";
import { createAdapterFromPrivateKey as createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2";
import { formatUnits } from "viem";

const adapter = createViemAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as string,
});

const balanceAction = await adapter.prepareAction(
  "usdc.balanceOf",
  {},
  { chain: "Ethereum" },
);
const balance = await balanceAction.execute();
console.log(`USDC balance: ${formatUnits(BigInt(balance), 6)}`);

If a transaction is stuck or failed, check the transaction on a block explorer with the returned txHash. For Solana transfers, use Solana Explorer or SolScan.

If the transaction failed mid-transfer, check the returned result.steps to see which transaction steps completed.

Follow these practices for prevention, recovery, and monitoring to improve reliability.

Prevention

  • Test your integration on testnets before deploying on mainnet.
  • Monitor gas prices and adjust during network congestion.
  • Use dedicated RPC providers such as Alchemy or QuickNode.
  • Implement multiple RPC fallbacks.
  • Wrap all transfer operations in try-catch including adapter setup and bridge calls.

Recovery

  • Always save the transfer state for recovery scenarios.
  • Verify which steps completed before attempting recovery.
  • Use appropriate timeouts and give network operations enough time to complete.
  • Implement exponential backoff and use increasing delays for retry logic.

Monitoring and debugging

  • Use block explorers to verify transaction status.
  • Save intermediate results and persist transfer state for recovery scenarios.
Did this page help you?
© 2023-2025 Circle Technology Services, LLC. All rights reserved.