Skip to content
Go back

CCTP v2 in Production: Cross-Chain Payments Under 60 Seconds

Edit page

Circle’s Cross-Chain Transfer Protocol (CCTP) is the cleanest way to move USDC between chains. Version 2 made it faster and added a feature we use heavily in HashPay: fast finality attestations.

Here’s what changed and what you need to know for production use.

CCTP v1 Recap

CCTP v1 works like this:

  1. Burn USDC on source chain → emits a MessageSent event
  2. Wait for Circle’s attestation service to sign the burn proof (~13s on ETH mainnet, ~2s on fast chains)
  3. Call receiveMessage() on destination with the attestation → mint USDC

The bottleneck is Ethereum’s finality (~65 blocks / ~13 minutes for “safe” finality, though Circle uses faster thresholds in practice).

What v2 Changed

Fast Burns: CCTP v2 introduces a two-phase finality model. For amounts under a risk-threshold, Circle’s attestors sign after ~3 confirmations instead of waiting for full finality. This gets you sub-60-second settlement on ETH→any-chain routes.

Hook Support: v2 messages can carry arbitrary calldata that executes on the destination after minting. This means you can mint USDC and call a contract in one atomic transaction on the destination side.

We use hooks in HashPay to mint USDC and immediately execute the swap to the user’s target token in the same destination transaction.

Solana Integration

The tricky part is Solana. The EVM CCTP interface is relatively uniform, but the Solana SDK has quirks:

import { MessageTransmitterProgram } from "@circle-fin/cctp-lib/solana";

const tx = await messageTransmitter.receiveMessage({
  message: attestedMessage.message,
  attestation: attestedMessage.attestation,
  // Solana PDAs need to be derived per message nonce
  usedNonces: deriveUsedNoncePda(nonce, sourceDomain),
});

The usedNonces PDA is per-nonce, which means you need to derive and pass it correctly for every message. Miss this and you’ll get a cryptic AccountNotFound error that doesn’t point to the nonce PDA.

Production Edge Cases

Attestation delays: Circle’s attestation service occasionally lags during high network activity. Build retry logic with exponential backoff. In HashPay we poll every 2 seconds with a 5-minute timeout before surfacing an error to the user.

Destination gas: The most common failure in our early prod runs was the destination transaction running out of gas because the hook calldata was larger than estimated. Always add a buffer — we use estimatedGas * 1.3.

Nonce reuse protection: CCTP v2 handles this on-chain, but you still need idempotency in your off-chain relayer. If the attestation is fetched twice and your relayer submits twice, the second call will fail. Log the nonce and check before submitting.

Timing Numbers (Mainnet, May 2026)

RouteMedian Time
ETH → Base38s
ETH → Polygon29s
ETH → Arbitrum22s
ETH → Solana41s
Solana → ETH14s

Solana→ETH is fast because Solana finalizes quickly and Circle’s attestors treat it as low-risk. ETH→anything is bounded by Ethereum’s block time.

Is It Worth It?

For a payment gateway, yes. The UX of “your payment settled” in under a minute is meaningfully better than “check back in 15-30 minutes”. The reliability is also better than most bridge alternatives — CCTP is just Circle’s own protocol and the only moving part is their attestation service, which has been very solid.

The main limitation: USDC only. If your users want to pay in ETH or arbitrary tokens, you need a swap layer in front. That’s exactly what HashPay does — accept any token, swap to USDC, bridge via CCTP, swap to the merchant’s preferred token on destination.


Edit page
Share this post:

Next Post
Exact-Output Swaps in Move: Reverse AMM Math on Aptos