> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arc.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshoot Unified Balance deposits

> Identify deposit failures, retry quote errors safely, and recover fast deposits without submitting duplicate source transactions

Recovery depends on the source transaction receipt. Before a fast deposit burns
USDC, you can correct the request and try again. After a successful burn,
monitor the existing deposit or escalate it. If the submission result is
unclear, check the source blockchain before you try again.

Quote and progress states apply only to crosschain `FAST` deposits. `STANDARD`
same blockchain deposits throw structured errors and don't return `progress`.

<Warning>
  Don't call `deposit()` or `depositFor()` again while the source transaction is
  pending or after its receipt confirms the burn. A `PENDING` result means that
  the kit submitted the source transaction, but it doesn't prove that the
  transaction succeeded. Check the receipt before you choose a recovery action.
</Warning>

The snippets assume that you've configured `kit` and a source `adapter`. See
[Use fast deposits](/app-kit/tutorials/unified-balance/use-fast-deposits) for
the complete deposit flow.

## Identify the failure stage

Start with the result. Use it and the source blockchain history to choose a safe
next step.

| Observation                                          | Source transaction status                     | Action                                                                               |
| ---------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------ |
| `estimateDeposit()` throws                           | Not submitted                                 | Correct the request, then estimate again.                                            |
| `deposit()` throws and no burn transaction was sent  | Not submitted                                 | Correct the error, request a fresh estimate, and submit again.                       |
| Wallet or RPC error while submitting the transaction | Unknown until you check the source blockchain | Check the wallet activity or block explorer before you submit another deposit.       |
| `progress.status` is `PENDING`                       | Submitted; receipt not verified by the result | Check the source receipt before you poll IRIS or retry.                              |
| `progress.status` is `FAILED`                        | Burn confirmed; destination relay failed      | Check the destination balance, then contact Circle support with the source `txHash`. |
| `progress.status` is `DONE`                          | Burn and destination deposit confirmed        | Save the destination relay `txHash`. No recovery action is required.                 |

For a fast deposit, `txHash` identifies different transactions depending on the
result:

* `DONE`: the destination relay transaction.
* `PENDING` or `FAILED`: the source burn transaction.

When the result is `PENDING`, look up the source `txHash` and use the source
transaction state to choose the next step:

| Source transaction state | Action                                                                                                                                              |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Successful receipt       | The burn occurred. Poll IRIS and don't submit another deposit.                                                                                      |
| Reverted receipt         | The burn didn't occur. Correct the cause, request a fresh estimate, and submit again.                                                               |
| Pending                  | Keep monitoring the source transaction. Don't submit another deposit.                                                                               |
| Replaced                 | Inspect the replacement transaction before you act. A successful receipt alone doesn't prove that the burn occurred.                                |
| Dropped or not found     | Check the signing wallet's activity and nonce. Don't retry until you confirm that the transaction can't be executed. Then request a fresh estimate. |

Check what the replacement transaction did. Compare its contract address and
call data with the original burn, or look for the burn event:

* Same burn succeeded: Poll IRIS with the replacement transaction hash. Don't
  submit another deposit.
* Still pending: Keep monitoring the replacement. Don't submit another deposit.
* Canceled, unrelated, or reverted: The burn didn't occur. Request a fresh
  estimate before you retry.

If a wallet or Remote Procedure Call (RPC) request fails while it submits the
burn, the transaction might still have reached the source blockchain. Don't
automatically retry an ambiguous submission. First check the signing wallet's
recent transactions for a matching USDC burn.

## Inspect structured errors

Unified Balance operations throw a `KitError` for request validation, balance,
RPC, and onchain failures. Record the error name, message, `recoverability`, and
cause. These fields help you tell a request error from an infrastructure error.

```typescript TypeScript theme={null}
import { isKitError, type DepositResult } from "@circle-fin/app-kit";

type DepositEstimate = Awaited<
  ReturnType<typeof kit.unifiedBalance.estimateDeposit>
>;

try {
  const estimate: DepositEstimate = await kit.unifiedBalance.estimateDeposit({
    from: { adapter, chain: "Ethereum_Sepolia" },
    amount: "10",
    token: "USDC",
    to: { chain: "Arc_Testnet" },
    config: { transferSpeed: "FAST" },
  });

  const result: DepositResult = await kit.unifiedBalance.deposit({
    ...estimate,
    from: { adapter, chain: "Ethereum_Sepolia" },
  });

  console.log(result.progress?.status, result.txHash);
} catch (error: unknown) {
  if (isKitError(error)) {
    console.error({
      name: error.name,
      code: error.code,
      recoverability: error.recoverability,
      message: error.message,
      cause: error.cause,
    });
  }

  throw error;
}
```

Don't use `recoverability` alone to decide whether to submit another deposit.
First verify whether the source burn occurred.

## Resolve request validation errors

Fast deposit configuration errors use the `INPUT_VALIDATION_FAILED` error name.
Fast deposits require an Ethereum Virtual Machine (EVM) source blockchain. Use
the field and reason in the error message to correct the request.

| Condition                                                 | Resolution                                                                                                                              |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `transferSpeed` is `FAST`, but `to` is omitted            | Add a supported destination blockchain.                                                                                                 |
| `to` is set, but `transferSpeed` is omitted or `STANDARD` | Set `config.transferSpeed` to `FAST`, or remove `to` for a standard deposit.                                                            |
| `to.chain` isn't a supported fast deposit destination     | Select a destination from the [fast deposit support table](/app-kit/tutorials/unified-balance/use-fast-deposits#supported-blockchains). |
| The source is a fast finality destination                 | Select a supported source blockchain. The kit doesn't fall back to a standard deposit.                                                  |
| The source isn't an EVM blockchain                        | Use a supported EVM source blockchain.                                                                                                  |
| The source and destination use different environments     | Use either two mainnet blockchains or two testnet blockchains.                                                                          |
| `allowanceStrategy` is set with `to`                      | Omit `allowanceStrategy`. Fast deposits manage the USDC approval.                                                                       |
| `quote` is set for a standard deposit                     | Remove `quote`, or configure a fast deposit and request a new estimate.                                                                 |

If the preflight check reports an insufficient USDC balance, fund the source
wallet with the required amount shown in the error. The required balance
includes the deposit amount and forwarder fee. The wallet also needs enough of
the source blockchain's native token to pay for any required approval and the
burn gas.

## Resolve fee quote errors

The kit validates a supplied fast deposit quote before it submits the source
burn. Quote rejection reasons aren't `KitError` names. The kit translates them
into either `INPUT_VALIDATION_FAILED` messages that request a new estimate or a
fatal internal error.

### Resolve caller quote errors

The kit detects these reasons before it submits a source burn for the current
call:

| Quote rejection reason   | Meaning                                               | Action                                                                                                   |
| ------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `QUOTE_EXPIRED`          | The quote's validity window ended.                    | Request a new estimate.                                                                                  |
| `NON_ZERO_NONCE`         | The quote was already used.                           | Find the transaction that consumed it. Recover that deposit instead if it matches the current intent.    |
| `SOURCE_DOMAIN_MISMATCH` | The quote belongs to a different source blockchain.   | Request an estimate for the intended source blockchain.                                                  |
| `SIGNATURE_INVALID`      | The quote signature is invalid.                       | Discard the quote and request a new estimate.                                                            |
| `QUOTE_ARGS_MISMATCH`    | The quote doesn't match the final deposit parameters. | Request a new estimate after you finalize the amount, source, destination, recipient, and configuration. |

Call `estimateDeposit()` again with the final amount, source, destination,
recipient, and transfer configuration. Pass the returned estimate to `deposit()`
without changing its quoted fields:

```typescript TypeScript theme={null}
import type { DepositResult } from "@circle-fin/app-kit";

type DepositEstimate = Awaited<
  ReturnType<typeof kit.unifiedBalance.estimateDeposit>
>;

const estimate: DepositEstimate = await kit.unifiedBalance.estimateDeposit({
  from: { adapter, chain: "Ethereum_Sepolia" },
  amount: "10",
  token: "USDC",
  to: { chain: "Arc_Testnet" },
  config: { transferSpeed: "FAST" },
});

const result: DepositResult = await kit.unifiedBalance.deposit({
  ...estimate,
  from: { adapter, chain: "Ethereum_Sepolia" },
});

console.log(result.progress?.status, result.txHash);
```

Request a quote shortly before submission instead of caching it. If a new quote
fails with the same reason, stop retrying and contact Circle support with the
full structured error. For `NON_ZERO_NONCE`, request a new quote only after you
confirm that you aren't trying to recover the deposit that used the old quote.

### Report an internal quote error

The following reasons indicate that the kit constructed an invalid forwarding
payload:

* `EMPTY_HOOK_DATA`
* `FORWARD_FEE_WITHOUT_HOOK`
* `FORWARD_HOOK_WITHOUT_FEE`

These errors have `SERVICE_INTERNAL_ERROR` as their error name and `FATAL` as
their `recoverability`. Requesting another quote with the same SDK and
parameters won't correct the payload. Stop retrying and report the error to
Circle support.

## Handle fast deposit progress

After it submits the source transaction, the kit waits about 60 seconds for
Circle's relayer. It then returns one of these states:

| Status    | Meaning                                                                                  | Action                                                                         |
| --------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `DONE`    | The relayer confirmed the destination deposit.                                           | Store the destination relay `txHash`.                                          |
| `PENDING` | The wait ended without a confirmed relay. The source transaction receipt isn't verified. | Check the returned source `txHash`. Poll IRIS only after the receipt succeeds. |
| `FAILED`  | The source burn completed, but the relayer reported a terminal destination failure.      | Check the destination balance, then contact Circle support.                    |

`PENDING` doesn't by itself mean that the deposit failed or that funds are in
transit. The source transaction can still be pending, reverted, or dropped when
the kit returns.

### Poll a pending deposit

After the source receipt succeeds, query the IRIS messages endpoint with the
source Cross-Chain Transfer Protocol (CCTP) domain ID and the source burn
transaction hash. For domain IDs, see
[Supported chains and domains](https://developers.circle.com/cctp/concepts/supported-chains-and-domains#cctp-domains).

**Mainnet:**

```bash Shell theme={null}
curl "https://iris-api.circle.com/v2/messages/{sourceDomainId}?transactionHash={sourceTxHash}"
```

**Testnet:**

```bash Shell theme={null}
curl "https://iris-api-sandbox.circle.com/v2/messages/{sourceDomainId}?transactionHash={sourceTxHash}"
```

Use `messages[].forwardState` to choose the next action:

| `forwardState`                | Action                                                                    |
| ----------------------------- | ------------------------------------------------------------------------- |
| `PENDING`, `null`, or omitted | Continue polling with backoff.                                            |
| `CONFIRMED` or `COMPLETE`     | Treat the deposit as complete when `forwardTxHash` is present.            |
| `FAILED`                      | Stop polling. Check the destination balance, then contact Circle support. |

The top-level message `status` describes the CCTP attestation. It doesn't
confirm that the forwarding transaction completed. Use `forwardState` and
`forwardTxHash` for the destination deposit.

### Escalate a failed deposit

An IRIS `FAILED` forward state means that Circle's forwarding attempt failed.
Another party might still complete the destination mint using the same CCTP
message. Before you contact Circle support,
[check the recipient's Unified Balance](/app-kit/tutorials/unified-balance/check-unified-balance).

If the balance wasn't credited, provide:

* The source burn transaction hash from the deposit result.
* The source and destination blockchains.
* The deposit account and amount.
* The approximate submission time and SDK version.
* The structured error or IRIS response, with secrets removed.

Circle support can inspect the existing burn and attestation and determine
whether the destination relay can be submitted again. This recovery reuses the
existing burn; it doesn't require a new deposit.

The Unified Balance deposit API doesn't provide a public retry method for an
existing source burn. Don't reuse Bridge Kit's `retry()` method with a Unified
Balance deposit result, and don't submit a new deposit as a recovery attempt.

## Prevent duplicate deposits

* Persist the source transaction hash and deposit state before starting
  background monitoring.
* Prevent duplicate submissions while a wallet request or deposit is pending.
* Request a fresh estimate immediately before `deposit()` or `depositFor()`.
* Pass quoted fields to the deposit unchanged.
* Use backoff when polling IRIS, and stop when the state is terminal.
* Alert on repeated internal quote errors and `FAILED` relay states instead of
  retrying them automatically.
