$5 of USDC Was Stuck on Base With Zero ETH. We Rescued It Cross-Chain for $0.21.
An agent burner wallet held 5 USDC on Base mainnet and 0 ETH — a trap: ERC-20 transfers need native gas the wallet cannot pay. Live rescue on 2026-10-24: Relay refuel (~11 s ETH delivery) then Circle CCTP burn→attestation→mint into our operator on Arc. Every txhash and every failure included.
On 2026-10-24 we rescued 5 USDC from a Base burner wallet with zero ETH: Relay Protocol refueled 0.0000691 ETH in ~11 s (cost 0.185 USDC — GasZip rejected us, its LiFi floor is ~0.5 USDC), then CCTP v2 burned on Base, Iris attested in ~24 min, and a permissionless receiveMessage minted 5 USDC to our operator on Arc. Total cost ~0.21 USDC (~4%), end-to-end ~26 min.
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.

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:
- 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).
- Rescue: CCTP v2
depositForBurnon Base → Circle Iris attestation →receiveMessagemint 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.

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.

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)

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
0xe9057fd74703296c76b09f28c3687e83d7005890ab59c6b55e8dcf9286bbb8ea(Arc, confirmed 11:24:43 UTC)

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.

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.