Circle CCTP on Solana: A Quick Guide.

Okay, so you're that guy. Wallet fat with USDC on Solana. Friend hits you up: "Yo, send me 100 USDC on Base quick." You fumble through some sketchy bridge, pay 5 bucks in fees, wait 20 minutes. Sound familiar? Circle's CCTP fixes that. It's this burn and mint magic for USDC that zips it cross chain natively. No wrapped tokens. No middlemen liquidity pools. Just burn here, mint there. Super cheap, like ~0.00295 SOL rent on Solana side.

And on Solana? It's live with V1 and now V2. V2's faster, got hooks for extra data, fees if they turn 'em on (right now zero for standard). I usually use it via wallets like Phantom or apps like Drift that integrated it. But today? We're doing it raw. Hands on. Your Solana wallet funded with USDC and a sprinkle of SOL for gas. Let's roll.

What the hell is CCTP anyway?

Circle's Cross Chain Transfer Protocol. USDC only, for now. You burn USDC on one chain (Solana), Circle attests it, then mints fresh USDC on the destination. Domains are chain IDs - Solana's 1. Ethereum's 0. Base is 9. Why does this matter? It's trustless ish. Circle signs the attestation, but no custody of your funds mid flight.

The thing is, Solana's programs split it up. TokenMessengerMinter handles burn and mint. MessageTransmitter sends the signals. V2 adds stuff like maxFee (u64, in token units), minFinalityThreshold for attestation speed. Fees? Zero standard, but watch for changes. In my experience, attestation takes 5-30 mins depending on chain.

Solana as source: Burning your USDC

You're starting on Solana. Want to send to EVM? Hit depositForBurn. Params: amount (u64, like 1000000 for 1 USDC), destinationDomain (u32, EVM=0), mintRecipient (your Pubkey on dest, base58 encoded 32-byte hex).

  • Costs ~0.00295104 SOL rent for the event account. Paid by you or subsidizer.
  • After attestation, reclaim that SOL with reclaimEventAccount. Only eventrentpayer can do it.
  • Screw up the mintRecipient? Use replaceDepositForBurn. Reuse the burn, swap recipient or caller. No new deposit needed. Game changer if you fat fingered the address.

depositForBurnWithCaller? Adds destinationCaller Pubkey. Controls who calls receiveMessage on dest. V2 throws in maxFee and minFinalityThreshold. Hook version? Slap hookData for custom dest logic.

Hands on: Send USDC from Solana to Base using Wormhole SDK

Don't wanna code Rust? Use Wormhole's TypeScript SDK. It's dead simple. I did this last week on devnet. Took 2 mins. Here's the play by play. Grab Node.js, npm. Wallets with test USDC/SOL on Solana devnet, ETH on Base Sepolia.

  1. Init Wormhole. npm i @wormhole foundation/sdk. Make a transfer.ts.
  2. Paste this starter - tweak for mainnet later:
    import { Wormhole, circle } from '@wormhole foundation/sdk';
    import evm from '@wormhole foundation/sdk/platforms/evm';
    import solana from '@wormhole foundation/sdk/platforms/solana'; const network = 'Testnet'; // Swap to 'Mainnet' when ready
    const wh = new Wormhole(network, [evm.Platform, solana.Platform]); const src = wh.getChain('Solana');
    const dst = wh.getChain('BaseSepolia'); // Your target, like 'Ethereum' or 'Base'
    
  3. Get signers and USDC. Need helper for keys. Solana keypair file, EVM private.
    const srcSigner = await getSigner(src); // Your func
    const dstSigner = await getSigner(dst);
    const srcUsdc = circle.usdcContract.get(network, 'Solana');
    const dstUsdc = circle.usdcContract.get(network, dst.chain);
    
    Why separate signers? Src burns, dst claims sometimes.
  4. Transfer it. wh.transfer(srcUsdc, amount, srcSigner, { destination: dst.chain }).then(handle). Relayer auto attests and redeems. Boom.
  5. Run npx tsx transfer.ts. Watch txids pop. Solana tx like s1gCiQi1aCJVuGGyjMZZcad3bZS3h4mJKvaNBNctrWLq7ooEpdvs3ehjuGx6esK7wGR1y4sEjQJcBbUfqLp8h3H. Dest follows.

Reverse it: Claim on Solana from EVM

Now flip. EVM depositForBurn to Solana (domain 1). mintRecipient? Hex encoded USDC token account on Solana. Must exist before receiveMessage.

On Solana, someone calls receiveMessage or V2's handleReceiveFinalizedMessage. Needs message/attestation bytes. Remaining accounts galore: tokenmessenger, remotetokenmessenger, tokenminter (writable), localtoken, tokenpair, your usertokenaccount (must match mintRecipient), custody, SPL token program, etc.

AccountPDA SeedsWritable?What it does
tokenmessenger["tokenmessenger"]falseCore messenger
remotetokenmessenger["remotetokenmessenger", sourceDomain]falseStores source addr
tokenminter["tokenminter"]trueMints USDC
usertokenaccountN/AtrueYour wallet's USDC ATA
custodytokenaccount["custody", localMint]truePre minted pool

FeeExecuted? Mints extra to feeRecipient if nonzero. Expiration on unfinalized? Check blockNumber. Revert if expired - resend.

? Use relayers like Wormhole or apps. Drift's got a wallet menu button for it. Click, pick chains, send. Done.

Troubleshooting the crap that goes wrong

Honestly, most issues? Wrong address format. Solana source to EVM: mintRecipient as 32-byte base58 of hex. EVM to Solana: hex token account.

  • Event rent stuck? Call reclaimEventAccount as payer. Gets your 0.00295 SOL back.
  • No attestation? Wait 5-30 mins. Check Circle's service. V2 minFinalityThreshold too high? Lower it.
  • Replace needed? Grab originalMessage/attestation bytes from explorer. New depositForBurn params. No reburn.
  • V1 vs V2? V2 everywhere now. Faster attestation, hooks. Build from GitHub solana cctp contracts if devving./run.sh build_v2.
  • Fees bite? Standard zero. But maxFee param caps it. Watch CCTP fees page.

One time, I botched the base58. USDC vanished? Nah, replaced it free. Saved 100 bucks.

Apps making it dummy proof

Don't code? Wallets and DEXes got you.

Drift: Connect wallet, wallet menu, CCTP tab. Pick source/dest, amount. Integrates V2.

Phantom/Jupiter? Aggregators route thru CCTP for USDC swaps cross chain. Cheaper than portals.

Wormhole or Chainport apps. Instant UI. I usually hit Eco or OKX Wallet guides for screenshots - step 1: connect, etc.

Devving deeper? Rust and Anchor time

Want raw power? Clone Circle's GitHub. Rust, Anchor. Two programs: MessageTransmitter(V2), TokenMessengerMinter(V2).

depositForBurn: CPI to burn tokens, emit MessageSent event. Attested by Iris.

receiveMessage: Validate, CPI to mint. Custody holds pre mints.

Build: ./run.sh setup, buildv2. Vendored deps for reproducible. VSCode rust analyzer. Deploy mainnet? Follow their guide, but fund with SOL/USDC.

sendMessage? destinationDomain, recipient Pubkey, messageBody Vec. WithCaller adds control.

Pro tip: PDA seeds like ["localtoken", mint.pubkey]. tokenpair ["tokenpair", sourceDomain, sourceTokenBase58].

Domain cheat sheet

ChainDomain IDUSDC Mint (Solana example)
Solana1EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Ethereum0(EVM native)
Base9(EVM native)
Arbitrum5(Check Circle docs)

Grab full list from Circle devs. More coming - Solana V2 lit the fire.

Why bother over bridges?

Bridges? Liquidity risks, hacks. CCTP? Native USDC end to end. Capital efficient. V2 hooks let you attach data - trigger DEX orders on arrival. Fees microscopic. Speed? Lightning now.

What's next for you? Test small. 10 USDC devnet. Then mainnet flip. Hit snags? Explorers like Solscan for tx details. Attestations on Circle endpoint.

Scale it. Build a dApp? CCTP's your USDC highway on Solana. Pretty much unbeatable.