# $5 of USDC Was Stuck on Base With Zero ETH. We Rescued It Cross-Chain for $0.21.

> Published: 2026-10-24 | Author: AgentBadge Team | Canonical: https://agentbadge.xyz/blog/usdc-rescue-base-to-arc

An agent burner wallet ended up holding **5 USDC on Base mainnet and exactly 0 ETH**. That combination is a trap: moving ERC-20 tokens requires native gas, and the wallet that holds the money cannot pay for the transaction that would free it. On 2026-10-24 we ran the rescue live — a ~$0.17 gas refuel via a bridge aggregator, then Circle's CCTP to carry the 5 USDC from Base to Arc. Twenty-six minutes later the funds were back on our operator wallet, on a chain that pays gas in USDC itself. This is the field report, with every transaction hash and every failure included.

![Relay refuel — 0.185 USDC on Arc became 0.0000691 ETH on Base in ~11 seconds](/images/blog/usdc-rescue-relay.webp)

## The trap

The burner 0x3bF7…b89f is a throwaway key we use for agent experiments. It accumulated 5 USDC on Base mainnet — and held zero ETH. Every exit path needs gas: USDC.approve() then TokenMessengerV2.depositForBurn() for CCTP, or a plain transfer(). All of them are ERC-20 calls the burner signs — and Base charges ETH for that signature, not USDC.

The fix is a two-hop strategy:

1. **Refuel**: get a drip of ETH to the burner on Base, paid for with USDC we hold elsewhere (on Arc, where USDC *is* the gas token).

2. **Rescue**: CCTP v2 depositForBurn on Base → Circle Iris attestation → receiveMessage mint on Arc, recipient = our operator wallet.

## Plan A died on a provider minimum

Our first candidate was GasZip through the LI.FI aggregator — purpose-built for exactly this: send USDC on one chain, receive native gas on another. Quotes looked perfect: 0.5 USDC → 0.000211 ETH, ~2 s execution.

![GasZip rejected — have 0.190 USDC, want 0.504](/images/blog/usdc-rescue-blocked.webp)

```
insufficient funds for gas * price + value:
  have 190242293492133071   (0.190 USDC)
  want 504019005393246480   (0.504 USDC)
```

Probing li.quest/v1/quote at 0.05 / 0.1 / 0.15 / 0.2 / 0.25 / 0.3 / 0.35 / 0.4 / 0.45 USDC — every amount below ~0.5 returns No available quotes. Our operator held **0.190242 USDC** on Arc. All our Arc wallets combined held ~0.295. GasZip's floor was simply unreachable.

**Lesson one:** on dust-balance source chains, a bridge's minimum size matters more than its quoted rate. Check your balance against the floor *before* picking the provider, not after the RPC rejection.

## The pivot: Relay took what GasZip wouldn't

Relay Protocol (api.relay.link) quoted the same route — USDC on Arc → ETH on Base — starting from amounts GasZip refused. We sent **0.185 USDC** through its two-step flow (ERC-20 approve + deposit, since Relay uses the token interface rather than GasZip's native value):

- approve 0xf307371832133bd3c8d8d3b4679127a400ce28a64554230ca708f0abca56c601 (Arc)

- deposit 0x7a01bca83dfb1aae5d1aab8205ce467e0b0d015f2e79e7c1b8c537ac29600f5b (Arc)

Eleven seconds after the deposit confirmed, the burner had **0.0000691 ETH** on Base — fee ~0.02 USDC. More than enough: Base gas for approve + depositForBurn costs roughly 0.00001 ETH.

![Sequence diagram — operator, GasZip/LiFi rejection, Relay approve+deposit, burner burn on Base, Iris attestation ~24 min, permissionless receiveMessage mint on Arc](/images/blog/d1-en.webp)

## CCTP: burn, wait, mint

With gas in place, the standard Circle CCTP path ran:

- USDC.approve(TokenMessengerV2, 5_000_000) — 0xb37458778f83a8c45c762f6dd09bf45e6f25aef7d2d22405be2ca6ac763d5af1 (Base)

- depositForBurn — 0x2c60097d665bf5234de80bb1189efe20166b108819e124f129033ceae44dbc8c (Base, mined 11:00:50 UTC)

![CCTP burn submitted on Base](/images/blog/usdc-rescue-burn.webp)

Then the part nobody can speed up: Circle's Iris attestation. Our script polled /v2/messages/6?transactionHash=… and hit its 20-minute timeout while status was still pending_confirmations. The burn was mined, nothing was lost — the resume mode re-polled and found status: complete at ~24 minutes. Attestation waits are a scheduling fact, not a failure mode: **build resume, don't rebuild the burn**.

One more production wrinkle: by the time attestation landed, our operator wallet had ~0.003 USDC left on Arc — spent on the Relay steps — not enough for the mint's gas. receiveMessage is **permissionless**, so a different funded wallet (ARC_OPS_KEY) paid the mint. The USDC still went to the mintRecipient encoded in the message — the caller only pays gas.

![Mint confirmed — OPERATOR Arc balance 0.19 → 5.002 USDC](/images/blog/usdc-rescue-mint.webp)

- mint 0xe9057fd74703296c76b09f28c3687e83d7005890ab59c6b55e8dcf9286bbb8ea (Arc, confirmed 11:24:43 UTC)

![Full rescue process flowchart — stranded state, aggregator probe, Relay refuel, CCTP burn, attestation wait with resume loop, permissionless mint, final balances](/images/blog/d2-en.webp)

*The whole run in one picture: probe → dead plan A → Relay refuel → burn → attestation wait (with resume) → permissionless mint.*

## The ledger

| Item | Value |
| --- | --- |
| Rescued | **5.00 USDC** (burner Base → OPERATOR Arc) |
| Refuel spend | 0.185 USDC in, ~0.02 fee + ~0.005 gas |
| Delivered gas | 0.0000691 ETH (~$0.17) on Base |
| Dust left | 0.000068 ETH on burner Base |
| Burn→attestation | ~24 min (finality threshold, unskippable) |
| Attestation→mint | ~1 s |
| **E2E mainnet** | **~26 min, ~0.21 USDC total (~4%)** |
| Net on Arc | 0.190 → 5.002 USDC |

## Friction table — spec vs. production

| # | What the docs implied | What actually happened | Fix |
| --- | --- | --- | --- |
| 1 | GasZip handles any dust amount | LiFi route floor ≈0.5 USDC — quotes <0.45 don't exist | Relay API, accepts 0.185 |
| 2 | Refuel is one tx | Relay needs ERC-20 approve + deposit (unlike GasZip's native value) | Two steps, both on Arc |
| 3 | Attestation ~15–20 min | ~24 min — script timed out at 20 | resume <burnTx> mainnet re-polls, no re-burn |
| 4 | Flag style --mainnet | Positional arg mainnet; --mainnet parsed as testnet flag → Iris sandbox 404 | Pass bare mainnet |
| 5 | Operator pays the mint | Operator had ~0.003 USDC left, mint needs ~0.003 | Permissionless receiveMessage — paid by second key |

## Why this pattern matters

Any agent fleet will eventually hold stranded ERC-20 balances — the wallet dies before its tokens do. The recovery recipe that worked here is small enough to script: probe aggregator quotes *with minimums in mind*, refuel ~$0.20 of native gas, then let CCTP do the heavy lifting while you wait for attestation. And a detail that makes Arc interesting for this exact problem: **gas is paid in USDC there**, so the rescued funds land on a chain where the wallet can never fall into the zero-native-token trap again.

![Full recap — five transactions, one pivot, 5 USDC saved](/images/blog/usdc-rescue-recap.webp)

All transactions, timings and raw logs: docs/SWAP/ in our monorepo — SPEC, CASE-LOG, structured events, and the ten terminal screenshots used in this post. The script (swap-rescue.mts) is a Bun/viem CLI with quote | relay | gaszip | testnet-cctp | cctp | resume modes.

---

## For AI Agents

- Companion guide: https://agentbadge.xyz/agent-guide/articles/usdc-rescue-base-to-arc
- Knowledge Index: https://agentbadge.xyz/agent-guide/
- LLM entry point: https://agentbadge.xyz/llms.txt
- Engineering services: https://agentbadge.xyz/agent-guide/team/services
