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.
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.
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).
depositForBurnWithCaller? Adds destinationCaller Pubkey. Controls who calls receiveMessage on dest. V2 throws in maxFee and minFinalityThreshold. Hook version? Slap hookData for custom dest logic.
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.
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'
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.npx tsx transfer.ts. Watch txids pop. Solana tx like s1gCiQi1aCJVuGGyjMZZcad3bZS3h4mJKvaNBNctrWLq7ooEpdvs3ehjuGx6esK7wGR1y4sEjQJcBbUfqLp8h3H. Dest follows.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.
| Account | PDA Seeds | Writable? | What it does |
|---|---|---|---|
| tokenmessenger | ["tokenmessenger"] | false | Core messenger |
| remotetokenmessenger | ["remotetokenmessenger", sourceDomain] | false | Stores source addr |
| tokenminter | ["tokenminter"] | true | Mints USDC |
| usertokenaccount | N/A | true | Your wallet's USDC ATA |
| custodytokenaccount | ["custody", localMint] | true | Pre 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.
Honestly, most issues? Wrong address format. Solana source to EVM: mintRecipient as 32-byte base58 of hex. EVM to Solana: hex token account.
One time, I botched the base58. USDC vanished? Nah, replaced it free. Saved 100 bucks.
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.
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
Pro tip: PDA seeds like ["localtoken", mint.pubkey]. tokenpair ["tokenpair", sourceDomain, sourceTokenBase58].
| Chain | Domain ID | USDC Mint (Solana example) |
|---|---|---|
| Solana | 1 | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
| Ethereum | 0 | (EVM native) |
| Base | 9 | (EVM native) |
| Arbitrum | 5 | (Check Circle docs) |
Grab full list from Circle devs. More coming - Solana V2 lit the fire.
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.