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.