Skip to main content
The SDK bridges native USDC between Base and Solana using Circle CCTP V2. Burn-and-mint means no wrapped token, no pool, no slippage: USDC is burned on the source chain and freshly minted on the destination. Your integration is five calls; the user’s part is one signature on the source chain. Everything after that — attestation, the destination mint, destination gas — is Cesto’s background job.
Bridging needs a write-scoped API key. It delivers USDC only — investing is a separate step and still needs a Solana wallet (see Bridge, then invest and Integration Models). Bridging runs on mainnet only — every example on this page moves real USDC; test with small amounts first.

Modes

With fast, invest what arrived (netOut), not what you burned.

Networks

Bridging is currently mainnet-only, between two chain keys: Additional chains will be added as they launch — quote / initiate reject unsupported chain keys with a 400.

Quickstart (EVM → Solana)

All amounts are USDC base units (6 decimals) as strings — "1000000" is 1 USDC.
Confirm the approval before burning. Wait for the approvalTx receipt and for the allowance to be readable on-chain before sending burnTx — load-balanced RPCs lag a few seconds, and an early burn reverts with ERC20: transfer amount exceeds allowance.

Solana-source burns

How a Solana-side burn gets signed depends on whose wallet is burning:
When sourceAddress is a Cesto-managed Solana wallet (provisioned via users.create under your API key), Cesto signs and lands the burn server-side — the sponsor pays the transaction fee and the CCTP message-account rent, so managed wallets need no SOL. initiate returns { status: 'BURN_SUBMITTED', burnTxHash, burn: null } instead of an unsigned burn, so skip submitBurn (re-submitting the same hash is safe/idempotent) and go straight to waitForTransfer:
See Managed wallets — Withdrawing for the full withdrawal flow.

The recipient address

recipient is always a wallet — hex account on EVM, owner wallet on Solana. CCTP mints into a token account, so for Solana destinations Cesto derives the USDC ATA itself and, if it’s the user’s first transfer, creates it at mint time (relayer pays the rent — no SOL needed). quote’s ataExists tells you which case you’re in.

Bridge, then invest

Once a transfer completes, open the position with the arrived amount (netOut, not the burn amount — fast mode deducts its fee at mint):
No signature needed — Cesto signs for the managed wallet. See Managed Wallets.
Withdrawing is the mirror image: close the position, then bridge solana → base back to the user’s EVM wallet (see Solana-source burns for who signs that burn).

Method reference

bridge.quote(params) → BridgeQuote

BridgeChain
required
base or solana (mainnet).
BridgeChain
required
Chain the USDC arrives on.
string
required
USDC base units (6 decimals) as a decimal string.
string
Destination wallet — enables ataExists for Solana destinations.
'standard' | 'fast'
default:"'standard'"
Speed / fee trade-off.
Returns { mode, amount, maxFee, estimatedFee, netOut, etaSeconds, minFinalityThreshold, ataExists? }. Creates nothing.

bridge.initiate(params) → { transferId, status, burn, burnTxHash? }

All quote fields (recipient is required here), plus:
string
required
Source-chain wallet that will sign the burn — or a Cesto-managed Solana wallet, in which case Cesto signs for it.
string
Your user reference, stored on the transfer for reconciliation.
Two response shapes, distinguished by status:
  • status: 'BURN_TX_BUILT' (default) — burn is { kind: 'evm', chainId, approvalTx?, burnTx } or { kind: 'solana', transaction, additionalSignerSecrets }. approvalTx is present only when the current USDC allowance is insufficient — confirm it before burnTx.
  • status: 'BURN_SUBMITTED' (managed Solana source wallet) — Cesto signed and landed the burn itself, sponsor paying gas: burn is null and burnTxHash holds the landed burn signature. Skip submitBurn and poll waitForTransfer.

bridge.submitBurn(transferId, { burnTxHash })

Reports the landed burn. Cesto verifies it against Circle’s attestation (amount, chains, recipient) and drives the rest in the background.
Circle can take a few seconds to index a fresh burn — short lags are absorbed internally; on a 404, retry after a few seconds. Re-submitting the same hash is safe (it echoes the status), and submitBurn is never auto-retried — poll getTransfer instead of re-sending after a client-side timeout.

bridge.getTransfer(transferId) / bridge.waitForTransfer(transferId, options?)

Current status, tx hashes, and — once attested — feeExecuted / netOut decoded from the CCTP message. waitForTransfer resolves on COMPLETED and rejects with Cesto.BridgeTransferFailedError on FAILED.
number
default:"5000"
Delay between status polls.
number
default:"1800000"
Max total wait (30 min — beyond standard-mode worst case). A timeout doesn’t stop the transfer; keep polling getTransfer.

Transfer lifecycle

Most of these pass in seconds on fast. The two you’ll surface to users: ATTESTING (“waiting for Circle”, up to ~20 min on standard) and COMPLETED (funds arrived — netOut, feeExecuted, mintTxHash are final). FAILED is terminal with the reason in error — a burned amount is never lost (attestations don’t expire); contact Cesto support with the transferId.

Common errors

Security model

  • Non-custodial burn — Cesto builds the burn unsigned; only the user’s wallet signs. (Exception: Cesto-managed Solana wallets, which Cesto signs for by design — the key never leaves Cesto’s custody.)
  • Gas-only relayer — the destination mint is permissionless by protocol design; the relayer pays gas but can only deliver to the mintRecipient baked into the message.
  • Verified before driven — every burn is matched against Circle’s attested message before a mint is attempted; nonces make double-mints impossible.
  • Key-scoped — a transfer is visible only to the API key that created it.

Constraints

  • USDC only, base units as strings everywhere.
  • Relayer-dependent — mints need a configured relayer on the destination chain; without one, initiate works but the mint fails fast with a configuration error.
  • Funds only — positions are opened separately via Open a Position.