Cashmere CCTP Cross-Chain USDC Transfer

VerifiedSafe

Skill for transferring USDC cross-chain using Circle's CCTP protocol via the Cashmere SDK.

Sby Skills Guide Bot
DevelopmentIntermediate
007/23/2026
Claude Code
#cross-chain#usdc-transfer#cctp#cashmere#bridging

Recommended for

Our review

This skill enables USDC transfers across different blockchains using Circle's CCTP protocol, leveraging Cashmere wrapper contracts.

Strengths

  • Simplified SDK for cross-chain USDC transfers
  • Support for many blockchains (EVM, Solana, Aptos, Sui)
  • Automatic attestation and relaying on the destination side
  • Clear documentation of CCTP domains and versions

Limitations

  • Only handles source-side burns; relaying is automated but relies on Cashmere's service
  • Requires external Gas API for fee quotes
  • V1 attestation times can be long on certain chains
When to use it

Use this skill to send USDC from one chain to another via Cashmere CCTP, using the SDK or raw transactions.

When not to use it

Do not use it for non-USDC transfers, or if you need full control over the destination mint process.

Security analysis

Safe
Quality score82/100

The skill is purely informational, describing a cross-chain USDC transfer protocol and SDK usage. It does not contain any destructive commands, obfuscated payloads, or instructions to exfiltrate data. The use of environment variables for private keys is a standard practice and not a security concern for a listed skill.

No concerns found

Examples

Basic cross-chain USDC transfer
Send 100 USDC from Ethereum to Arbitrum using Cashmere CCTP.
SDK usage inquiry
How do I transfer USDC cross-chain using the Cashmere SDK?
Domain ID lookup
What is the Circle CCTP domain ID for Solana?

Cashmere CCTP Cross-Chain USDC Transfer Skill

Use this skill when the user asks about Cashmere cross-chain USDC transfers, CCTP contract calls, bridging between EVM / Solana / Aptos / Sui, or building transactions against the Cashmere wrapper contracts.

This skill covers source-side burns only (sending USDC cross-chain). Destination-side claim/mint is handled automatically by Cashmere's own relayers -- no user or SDK action is needed after the source transaction confirms.

Security: No private keys, mnemonics, or signer secrets are stored here. All addresses below are public on-chain data. The Gas API URL is https://gas.cashmere.exchange (production).


SDK Usage (Programmatic)

This skill is bundled with @cashmerelabs/cashmere-agent-sdk. If available, prefer calling the SDK directly instead of building raw transactions:

import { CashmereCCTP } from '@cashmerelabs/cashmere-agent-sdk';

const cctp = new CashmereCCTP({
  evm: { privateKey: process.env.EVM_PRIVATE_KEY },
  solana: { privateKey: process.env.SOLANA_PRIVATE_KEY },
  aptos: { privateKey: process.env.APTOS_PRIVATE_KEY },
  sui: { privateKey: process.env.SUI_PRIVATE_KEY },
});

const result = await cctp.transfer({
  from: 'ethereum',
  to: 'arbitrum',
  amount: '100',
  recipient: '0xDestinationAddress',
  version: 'v2-fast',
  feeMode: 'native',
});
// result.txHash, result.explorerUrl

Install: npm install @cashmerelabs/cashmere-agent-sdk


Architecture Overview

Every chain follows the same pattern:

User/Agent ──► Gas API (fee quote + signature)
          ──► Cashmere Wrapper Contract (verify sig, collect fees)
                  ──► Circle CCTP depositForBurn / depositForBurnWithHook
                          ──► Circle attestation (off-chain)
                                  ──► Cashmere relayers auto-mint on destination

1. Circle CCTP Domain IDs

| Domain | Chain | Native | Notes | |--------|---------------|--------|--------------------------------| | 0 | Ethereum | ETH | | | 1 | Avalanche | AVAX | | | 2 | Optimism | ETH | | | 3 | Arbitrum | ETH | | | 5 | Solana | SOL | | | 6 | Base | ETH | | | 7 | Polygon | POL | | | 8 | Sui | SUI | | | 9 | Aptos | APT | | | 10 | Unichain | ETH | | | 11 | Linea | ETH | | | 13 | Sonic | S | | | 14 | Worldchain | ETH | | | 15 | Monad | MON | | | 16 | Sei | SEI | | | 19 | HyperEVM | HYPE | Same domain as Hyperliquid Core|


2. CCTP Versions, Durations, and Fee Structures

V1 (Legacy Attestation-Based)

Requires Circle attestation before destination mint. Supported on EVM <-> EVM (older chains), EVM <-> Solana, EVM <-> Aptos, EVM <-> Sui, Aptos <-> Solana, Aptos <-> Sui, Sui <-> Solana.

Not supported on: Linea, Worldchain, Sei, Sonic, HyperEVM, Monad (V2-only chains).

| Source Chain | Estimated Duration | |---------------|--------------------| | Ethereum | ~15 minutes | | Arbitrum | ~15 minutes | | Base | ~15 minutes | | Optimism | ~15 minutes | | Unichain | ~15 minutes | | Polygon | ~2 minutes | | Avalanche | ~8 seconds | | Solana | ~25 seconds | | Sui | ~8 seconds | | Aptos | ~8 seconds |

Circle burn fee: 0 BPS (no Circle burn fee on V1).

V2 Fast (Threshold 1000)

Attestation-free fast finality. Supported on EVM <-> EVM and EVM <-> Solana only.

| Source Chain | Estimated Duration | |---------------|--------------------| | Ethereum | ~15 seconds | | Arbitrum | ~15 seconds | | Base | ~15 seconds | | Optimism | ~15 seconds | | Unichain | ~15 seconds | | Worldchain | ~15 seconds | | Linea | ~15 seconds | | Polygon | ~8 seconds | | Avalanche | ~8 seconds | | Sonic | ~8 seconds | | Monad | ~8 seconds | | HyperEVM | ~5 seconds | | Sei | ~5 seconds | | Solana | ~15 seconds |

Circle burn fee: Variable BPS from Circle Iris API. Fetched per route. minFinalityThreshold = 1000.

Not supported from: Avalanche, Sei, Sonic, Polygon, Solana, HyperEVM, Monad (these only support V2 Norm, not V2 Fast).

V2 Norm (Threshold 2000)

Standard finality V2. Supported on all EVM <-> EVM and EVM <-> Solana routes.

| Source Chain | Estimated Duration | |---------------|--------------------| | Ethereum | ~15 minutes | | Arbitrum | ~15 minutes | | Base | ~15 minutes | | Optimism | ~15 minutes | | Unichain | ~15 minutes | | Worldchain | ~15 minutes | | Linea | ~24 hours | | Polygon | ~8 seconds | | Avalanche | ~8 seconds | | Sonic | ~8 seconds | | Monad | ~8 seconds | | HyperEVM | ~5 seconds | | Sei | ~5 seconds | | Solana | ~25 seconds |

Circle burn fee: Variable BPS from Circle Iris API (typically lower than V2 Fast). minFinalityThreshold = 2000.

Fee Calculation Summary

Cashmere protocol fee (all chains, all versions):

total_fee = (amount * fee_bp / 10000) + static_fee
  • fee_bp: On-chain basis points, max 100 (1%).
  • static_fee: From the Gas API quote.
    • Native mode (isNative=true): static fee paid in native token (ETH/SOL/APT/SUI/etc.), USDC formula uses static_fee=0.
    • Stable mode (isNative=false): static fee added to USDC deduction.
  • Gas drop: Currently disabled. Pass gasDropAmount=0 in all transfers.

Circle V2 burn fee (maxFee passed on-chain):

bpsTenths = ceil(minimumFee_from_iris * 10)
maxFee = ceil(amount * bpsTenths / 100000)

Where minimumFee comes from Circle Iris API (can be fractional BPS like 1.3).


3. Contract Addresses

EVM — CashmereCCTP Wrapper

| Chain | CashmereCCTP Address | |------------|----------------------------------------------| | Ethereum | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | Avalanche | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | Polygon | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | Arbitrum | 0x3412ef459221d1581a08dcD56Ee55B8FaeBf5eEA | | Optimism | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | Base | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | Unichain | 0xd002a7172Ac6f90657FCb918B3f7e36372a4bA80 | | Linea | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | Worldchain | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | Sei | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | Sonic | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 | | HyperEVM | 0x15b2810232ec96ff083ca6d8b785cb930d241d83 | | Monad | 0xD156fFB54871F4562744d6Be5d6321B5BffCa3B6 |

Solana — Anchor Program

| Item | Address / Key | |-----------------------------------|------------------------------------------------------| | Cashmere CCTP Program | 5RsvKL6LFq6yEFiAXEwgYHAN3aLFypeB4AaafdeDnHqM | | Address Lookup Table | 865YCTTsymGpBjLMTvjd3T3RCGT8yvxnAHhyKZqRFfLi | | Circle Message Transmitter V1 | CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd | | Circle Token Messenger Minter V1 | CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3 | | Circle Message Transmitter V2 | CCTPV2Sm4AdWt5296sk4P66VBZ7bEhcARwFaaS9YPbeC | | Circle Token Messenger Minter V2 | CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe | | USDC Mint | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |

PDAs:

  • Config: seeds = ["config"] under Cashmere program
  • Custodian: seeds = ["emitter"] under Cashmere program
  • Custody ATA: seeds = ["__custody"] (transient, closed after burn)

Aptos — Move Module

| Item | Address | |---------------------|----------------------------------------------------------------------| | Transfer Module | 0xe49afb1896889e3e1dd7bcbb9e31d1df8221899d3d368e98588ac6b4d84b136d | | Entry Function | {module}::transfer::transfer_outer | | USDC Coin Type | 0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b |

Sui — Move Package

| Item | Address / Object ID | |-------------------------------|--------------------------------------------------------------------------| | Cashmere Transfer Package | 0xdf37112aacb7d3535f7b3b754b9a7b9fa1a6b3a756288dc734522c603c6f11a4 | | Cashmere Config (shared) | 0x5f1b2e205777713f44a99894d98e1e66e33dc11ff88e50108c94ee79b7ea3d67 | | Circle TokenMessengerMinter | 0x2aa6c5d56376c371f88a6cc42e852824994993cb9bab8d3e6450cbe3cb32b94e | | Message Transmitter State | 0xf68268c3d9b1df3215f2439400c1c4ea08ac4ef4bb7d6f3ca6a2a239e17510af | | TokenMessengerMinter State | 0x45993eecc0382f37419864992c12faee2238f5cfe22b98ad3bf455baf65c8a2f | | USDC Treasury | 0x57d6725e7a8b49a7b2a612f6bd66ab5f39fc95332ca48be421c3229d514a6de7 | | DenyList | 0x403 | | USDC Type | 0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC |

USDC Addresses (per chain)

| Chain | USDC Address | |------------|-----------------------------------------------------| | Ethereum | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | | Avalanche | 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E | | Polygon | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 | | Arbitrum | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 | | Optimism | 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85 | | Base | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | | Unichain | 0x078D782b760474a361dDA0AF3839290b0EF57AD6 | | Linea | 0x176211869cA2b568f2A7D4EE941E073a821EE1ff | | Worldchain | 0x79A02482A880bCe3F13E09da970dC34dB4cD24D1 | | Sei | 0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392 | | Sonic | 0x29219dd400f2Bf60E5a23d13Be72B486D4038894 | | HyperEVM | 0xb88339CB7199b77E23DB6E890353E22632Ba630f | | Monad | 0x754704Bc059F8C67012fEd69BC8A327a5aafb603 | | Solana | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | | Aptos | 0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b | | Sui | 0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC |


4. Gas API — Fee Quotes and Signatures

Base URL: https://gas.cashmere.exchange

ECDSA Signature (EVM chains)

GET /getEcdsaSig_native?localDomain={domain}&destinationDomain={domain}&isNative={true|false}&isV2={true|false}

Response:

{
  "fee": "128000",
  "deadline": "1713400000",
  "signature": "0x...",
  "cctpVersion": "1"
}

Frontend converts: fee = BigInt(fee), deadline = BigInt(deadline), signature used as-is (hex string).

Ed25519 Signature (Solana, Aptos, Sui)

GET /getEd25519Sig_native?localDomain={domain}&destinationDomain={domain}&isNative={true|false}&version={1|2}

Response:

{
  "signature": "0x...",
  "message": "0x...",
  "publicKey": "0x...",
  "destinationDomain": "3",
  "fee": "128000",
  "deadline": "1713400000"
}

Frontend converts hex strings to Buffer: Buffer.from(sig.slice(2), 'hex').

Circle V2 Burn Fee (via Netlify proxy to Iris)

GET /.netlify/functions/get-burn-fees?localDomain={d}&destinationDomain={d}&threshold={1000|2000}

Proxies to: https://iris-api.circle.com/v2/burn/USDC/fees/{localDomain}/{destinationDomain}

Response:

{
  "finalityThreshold": 1000,
  "minimumFee": 1.3
}

minimumFee is in BPS (can be fractional).


5. EVM Transfer Pattern

ABI Functions

// V1
function transfer(TransferParams memory _params) external payable;
function transferWithPermit(TransferParams memory _params, PermitParams memory _permitParams) external payable;

// V2
function transferV2(TransferV2Params memory _params) external payable;
function transferV2WithPermit(TransferV2Params memory _params, PermitParams memory _permitParams) external payable;

TransferParams (V1)

struct TransferParams {
    uint64  amount;            // USDC amount in 6-decimal units
    uint128 fee;               // Static fee from Gas API
    uint64  deadline;          // Unix timestamp
    uint128 gasDropAmount;     // Pass 0 (gas drop currently disabled)
    uint32  destinationDomain; // Circle CCTP domain
    bytes32 recipient;         // 32-byte padded destination address
    bytes32 solanaOwner;       // Solana wallet owner (0x0 if not Solana dest)
    bool    isNative;          // true = fees in native token
    bytes   signature;         // ECDSA from Gas API
}

TransferV2Params

Same as V1 plus:

    uint256 maxFee;                // Calculated from Circle Iris minimumFee
    uint32  minFinalityThreshold;  // 1000 (fast) or 2000 (norm)
    bytes   hookData;              // Always "0x00" for standard transfers

PermitParams (for WithPermit variants)

struct PermitParams {
    uint256 value;      // USDC amount to permit
    uint256 deadline;   // Permit deadline (transfer deadline + 6000)
    bytes   signature;  // EIP-2612 permit signature
}

TypeScript Example (viem)

import { writeContract, simulateContract } from '@wagmi/core';

const GAS_API = 'https://gas.cashmere.exchange';

async function evmCctpTransfer(
  srcChain: string, // e.g. 'ethereum'
  srcDomain: number, // e.g. 0
  dstDomain: number, // e.g. 3 (arbitrum)
  amount: bigint, // USDC in 6 decimals, e.g. 100_000_000n for 100 USDC
  recipientBytes32: `0x${string}`,
  isNative: boolean,
  isV2: boolean,
) {
  // 1. Get fee quote + signature from Gas API
  const sigResp = await fetch(
    `${GAS_API}/getEcdsaSig_native?localDomain=${srcDomain}&destinationDomain=${dstDomain}&isNative=${isNative}&isV2=${isV2}`
  ).then(r => r.json());

  const fee = BigInt(sigResp.fee);
  const deadline = BigInt(sigResp.deadline);
  const signature = sigResp.signature;

  // 2. Calculate msg.value (native fee only when isNative=true)
  const value = isNative ? fee : 0n;

  // 3. Build args
  if (!isV2) {
    const args = [{
      amount,
      fee,
      deadline,
      gasDropAmount: 0n, // gas drop currently disabled
      destinationDomain: dstDomain,
      recipient: recipientBytes32,
      solanaOwner: '0x' + '0'.repeat(64),
      isNative,
      signature,
    }];

    // Simulate then send
    await simulateContract(config, {
      address: CASHMERE_CCTP_ADDRESS, // per chain
      abi: cashmereCctpAbi,
      functionName: 'transfer', // or 'transferWithPermit'
      args,
      value,
    });

    return writeContract(config, { /* same params */ });
  } else {
    // V2: fetch burn fee from Circle Iris
    const burnFee = await fetch(
      `/.netlify/functions/get-burn-fees?localDomain=${srcDomain}&destinationDomain=${dstDomain}&threshold=1000`
    ).then(r => r.json());

    const bpsTenths = Math.ceil((burnFee.minimumFee ?? 100) * 10);
    const maxFee = (amount * BigInt(bpsTenths) + 99999n) / 100000n;

    const args = [{
      amount,
      maxFee,
      fee,
      deadline,
      gasDropAmount: 0n, // gas drop currently disabled
      destinationDomain: dstDomain,
      minFinalityThreshold: 1000, // or 2000 for norm
      recipient: recipientBytes32,
      solanaOwner: '0x' + '0'.repeat(64),
      isNative,
      hookData: '0x00',
      signature,
    }];

    // Simulate then send (transferV2 or transferV2WithPermit)
  }
}

On-Chain Signature Verification (EVM)

The contract verifies ecrecover on:

keccak256(abi.encodePacked(
  localDomain,        // uint32
  destinationDomain,  // uint32
  fee,                // uint128
  deadline,           // uint64
  isNative,           // bool
  uint8(1)            // version: 1 for V1, 2 for V2
))

6. Solana Transfer Pattern

Instructions (Anchor IDL)

  • transfer (V1): usdc_amount, destination_domain, recipient: [u8;32], solana_owner: [u8;32], fee, deadline, gas_drop_amount (pass 0), fee_is_native
  • transfer_v2: Same as above plus max_fee, min_finality_threshold

Transaction Structure

A Solana CCTP transfer is a VersionedTransaction with these instructions:

  1. ComputeBudgetProgram.setComputeUnitLimit({ units: 160_000 })
  2. Ed25519Program.createInstructionWithPublicKey({ publicKey, message, signature }) — from Gas API response
  3. program.methods.transfer(...) or program.methods.transferV2(...) — Anchor instruction

Signed by: wallet first (allows Phantom Lighthouse), then cctpMessageSigner keypair.

Uses Address Lookup Table 865YCTTsymGpBjLMTvjd3T3RCGT8yvxnAHhyKZqRFfLi for compressed V0 messages.

Key Accounts

{
  ownerTokenAccount: userUsdcAta,
  feeCollectorSolAccount: config.feeCollectorSol,
  feeCollectorUsdcAccount: config.feeCollectorUsdc,
  gasDropCollectorSolAccount: config.gasDropCollectorSol,   // still required by IDL
  gasDropCollectorUsdcAccount: config.gasDropCollectorUsdc, // still required by IDL
  owner: walletPublicKey,
  remoteTokenMessenger: PDA("remote_token_messenger", [destDomain.toString()]),
  burnSource: userUsdcAta,
  messageSentEventData: cctpMessageSigner.publicKey, // new Keypair per tx
  // V2 only:
  tokenMessengerMinterProgram: TOKEN_MESSENGER_MINTER_V2_PROGRAM_ID,
  messageTransmitterProgram: MESSAGE_TRANSMITTER_V2_PROGRAM_ID,
}

TypeScript Example

import { Program } from '@coral-xyz/anchor';
import { Ed25519Program, Keypair, PublicKey, VersionedTransaction, TransactionMessage, ComputeBudgetProgram } from '@solana/web3.js';

const GAS_API = 'https://gas.cashmere.exchange';
const PROGRAM_ID = new PublicKey('5RsvKL6LFq6yEFiAXEwgYHAN3aLFypeB4AaafdeDnHqM');

async function solanaCctpTransfer(
  connection: Connection,
  wallet: WalletAdapter,
  amount: bigint,
  destDomain: number,
  recipientBytes32: number[],
  isNative: boolean,
  isV2: boolean,
) {
  // 1. Get Ed25519 signature from Gas API
  const sigResp = await fetch(
    `${GAS_API}/getEd25519Sig_native?localDomain=5&destinationDomain=${destDomain}&isNative=${isNative}&version=${isV2 ? 2 : 1}`
  ).then(r => r.json());

  const signature = Buffer.from(sigResp.signature.slice(2), 'hex');
  const message = Buffer.from(sigResp.message.slice(2), 'hex');
  const publicKey = Buffer.from(sigResp.publicKey.slice(2), 'hex');

  // 2. Build Ed25519 verify instruction
  const signatureIx = Ed25519Program.createInstructionWithPublicKey({
    publicKey, message, signature
  });

  // 3. Build Anchor transfer instruction
  const cctpMessageSigner = new Keypair();
  const transferIx = await program.methods
    .transfer(
      new BN(amount.toString()),
      sigResp.destinationDomain,
      recipientBytes32,
      solanaOwnerBytes32,
      new BN(sigResp.fee),
      new BN(sigResp.deadline),
      new BN(0), // gas drop disabled
      isNative,
    )
    .accounts({ /* see Key Accounts above */ })
    .instruction();

  // 4. Assemble and sign
  const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 160_000 });
  const lookupTable = await connection.getAddressLookupTable(ALT_ADDRESS);

  const tx = new VersionedTransaction(
    new TransactionMessage({
      recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
      payerKey: wallet.publicKey,
      instructions: [computeIx, signatureIx, transferIx],
    }).compileToV0Message([lookupTable.value])
  );

  tx = await wallet.signTransaction(tx);
  tx.sign([cctpMessageSigner]);

  return connection.sendTransaction(tx);
}

7. Aptos Transfer Pattern

Entry Function

{MODULE_ADDR}::transfer::transfer_outer

Where MODULE_ADDR = 0xe49afb1896889e3e1dd7bcbb9e31d1df8221899d3d368e98588ac6b4d84b136d

Arguments (positional)

| Index | Type | Description | |-------|------------|-------------------------------------------------| | 0 | u64 | USDC amount (6 decimals) | | 1 | u64 | Native fee amount (0 if stable mode) | | 2 | u32 | Destination domain | | 3 | address | Recipient (bytes32 hex) | | 4 | address | Solana owner (0x0 if not Solana destination) | | 5 | u64 | Static fee from Gas API | | 6 | u64 | Deadline from Gas API | | 7 | u64 | Gas drop amount (pass 0, currently disabled) | | 8 | bool | isNative | | 9 | vector<u8> | Ed25519 signature bytes |

TypeScript Example

import { InputEntryFunctionData } from '@aptos-labs/ts-sdk';

const GAS_API = 'https://gas.cashmere.exchange';
const MODULE = '0xe49afb1896889e3e1dd7bcbb9e31d1df8221899d3d368e98588ac6b4d84b136d';

async function aptosCctpTransfer(
  aptosClient: AptosClient,
  senderAddress: string,
  amount: bigint,
  destDomain: number,
  recipientBytes32: string,
  isNative: boolean,
) {
  // 1. Get Ed25519 signature from Gas API (domain 9 = Aptos)
  const sigResp = await fetch(
    `${GAS_API}/getEd25519Sig_native?localDomain=9&destinationDomain=${destDomain}&isNative=${isNative}`
  ).then(r => r.json());

  const signature = Buffer.from(sigResp.signature.slice(2), 'hex');
  const fee = sigResp.fee;
  const deadline = sigResp.deadline;

  // 2. Build transaction
  const txData: InputEntryFunctionData = {
    function: `${MODULE}::transfer::transfer_outer`,
    typeArguments: [],
    functionArguments: [
      amount.toString(),
      isNative ? BigInt(fee).toString() : '0', // native fee amount
      destDomain,
      recipientBytes32,
      '0x' + '0'.repeat(64), // solanaOwner
      fee,
      deadline,
      '0', // gas drop disabled
      isNative,
      Array.from(signature), // Pontem: Array, others: Uint8Array
    ],
  };

  // 3. Simulate and submit
  const transaction = await aptosClient.transaction.build.simple({
    sender: senderAddress,
    data: txData,
  });

  // Sign and submit via wallet adapter
}

Note: Pontem wallet requires Array.from(signature) format; other wallets (Petra, Nightly) accept Uint8Array directly.


8. Sui Transfer Pattern

Three-Step PTB (Programmable Transaction Block)

Sui uses a single atomic PTB with three moveCall steps:

Step 1: Cashmere prepare

target: {package}::transfer::prepare_deposit_for_burn_ticket
typeArguments: [USDC_TYPE]
arguments: [usdcCoin, nativeFeeCoin, destDomain, recipient, solanaOwner,
            fee, deadline, gasDropAmount (pass 0), isNative, signatureVector,
            configObject, clockObject]
returns: [ticket, depositInfo]

Step 2: Circle deposit

target: {tokenMessengerMinter}::deposit_for_burn::deposit_for_burn_with_package_auth
typeArguments: [USDC_TYPE, AUTH_STRUCT]
arguments: [ticket, tmmState, mtState, denyList, usdcTreasury]
returns: [burnMessage, message]

Step 3: Cashmere finalize

target: {package}::transfer::post_deposit_for_burn
arguments: [burnMessage, message, depositInfo, configObject]

TypeScript Example

import { Transaction, coinWithBalance } from '@mysten/sui/transactions';
import { SUI_CLOCK_OBJECT_ID } from '@mysten/sui/utils';

const GAS_API = 'https://gas.cashmere.exchange';
const CASHMERE_PKG = '0xdf37112aacb7d3535f7b3b754b9a7b9fa1a6b3a756288dc734522c603c6f11a4';
const CASHMERE_CONFIG = '0x5f1b2e205777713f44a99894d98e1e66e33dc11ff88e50108c94ee79b7ea3d67';
const TMM_PKG = '0x2aa6c5d56376c371f88a6cc42e852824994993cb9bab8d3e6450cbe3cb32b94e';
const MT_STATE = '0xf68268c3d9b1df3215f2439400c1c4ea08ac4ef4bb7d6f3ca6a2a239e17510af';
const TMM_STATE = '0x45993eecc0382f37419864992c12faee2238f5cfe22b98ad3bf455baf65c8a2f';
const USDC_TREASURY = '0x57d6725e7a8b49a7b2a612f6bd66ab5f39fc95332ca48be421c3229d514a6de7';
const USDC_TYPE = '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';

async function suiCctpTransfer(
  suiClient: SuiClient,
  senderAddress: string,
  amount: bigint,
  destDomain: number,
  recipientBytes32: string,
  isNative: boolean,
) {
  // 1. Get Ed25519 signature from Gas API (domain 8 = Sui)
  const sigResp = await fetch(
    `${GAS_API}/getEd25519Sig_native?localDomain=8&destinationDomain=${destDomain}&isNative=${isNative}`
  ).then(r => r.json());

  const signature = Buffer.from(sigResp.signature.slice(2), 'hex');

  // 2. Build PTB
  const tx = new Transaction();

  const usdcCoin = coinWithBalance({
    type: USDC_TYPE,
    balance: amount,
  });

  const nativeCoin = tx.splitCoins(tx.gas, [
    tx.pure.u64(isNative ? BigInt(sigResp.fee).toString() : '0')
  ]);

  // Step 1: prepare
  const [ticket, depositInfo] = tx.moveCall({
    target: `${CASHMERE_PKG}::transfer::prepare_deposit_for_burn_ticket`,
    typeArguments: [USDC_TYPE],
    arguments: [
      usdcCoin, nativeCoin,
      tx.pure.u32(destDomain),
      tx.pure.address(recipientBytes32),
      tx.pure.address('0x' + '0'.repeat(64)),
      tx.pure.u64(sigResp.fee),
      tx.pure.u64(sigResp.deadline),
      tx.pure.u64('0'), // gas drop disabled
      tx.pure.bool(isNative),
      tx.pure.vector('u8', Array.from(signature)),
      tx.object(CASHMERE_CONFIG),
      tx.object(SUI_CLOCK_OBJECT_ID),
    ],
  });

  // Step 2: Circle burn
  const [burnMessage, message] = tx.moveCall({
    target: `${TMM_PKG}::deposit_for_burn::deposit_for_burn_with_package_auth`,
    typeArguments: [USDC_TYPE, `${CASHMERE_PKG}::transfer::Auth`],
    arguments: [
      ticket,
      tx.object(TMM_STATE),
      tx.object(MT_STATE),
      tx.object('0x403'),
      tx.object(USDC_TREASURY),
    ],
  });

  // Step 3: finalize
  tx.moveCall({
    target: `${CASHMERE_PKG}::transfer::post_deposit_for_burn`,
    arguments: [burnMessage, message, depositInfo, tx.object(CASHMERE_CONFIG)],
  });

  tx.setSenderIfNotSet(senderAddress);

  // 3. Sign and execute via wallet
  return suiSignAndExecuteTransaction({ transaction: tx });
}

9. Recipient Address Encoding

All chains encode the destination address as bytes32:

  • EVM -> EVM: Left-pad 20-byte address to 32 bytes.
  • Any -> Solana: Compute the recipient's USDC Associated Token Account (ATA) and encode as bytes32. Also pass solanaOwner as the wallet pubkey in bytes32. Use getAssociatedTokenAddressSync(USDC_MINT, walletPubkey).
  • Any -> Aptos/Sui: The address is already 32 bytes.
  • Solana owner field: Set to 0x0...0 (64 hex zeros) when destination is not Solana.

10. Version Routing Rules — Per Chain

Source Chain Capabilities

Which CCTP versions each chain can send from:

| Source Chain | V1 (0 BPS) | V2 Fast (1000) | V2 Norm (2000) | Possible Destinations | |--------------|------------|----------------|----------------|-------------------------------------------------| | Ethereum | Y | Y | Y | All EVM, Solana, Aptos, Sui | | Arbitrum | Y | Y | Y | All EVM, Solana, Aptos, Sui | | Base | Y | Y | Y | All EVM, Solana, Aptos, Sui | | Optimism | Y | Y | Y | All EVM, Solana, Aptos, Sui | | Unichain | Y | Y | Y | All EVM, Solana, Aptos, Sui | | Polygon | Y | N | Y | All EVM, Solana, Aptos, Sui | | Avalanche | Y | N | Y | All EVM, Solana, Aptos, Sui | | Linea | N | Y | Y | All EVM, Solana only (V2-only chain) | | Worldchain | N | Y | Y | All EVM, Solana only (V2-only chain) | | Sei | N | N | Y | All EVM, Solana only (V2-only, no V2 Fast) | | Sonic | N | N | Y | All EVM, Solana only (V2-only, no V2 Fast) | | HyperEVM | N | N | Y | All EVM, Solana only (V2-only, no V2 Fast) | | Monad | N | N | Y | All EVM, Solana only (V2-only, no V2 Fast) | | Solana | Y | N | Y | All EVM (V1+V2 Norm), Aptos (V1), Sui (V1) | | Aptos | Y | N | N | V1-legacy EVM, Solana, Sui (V1 only) | | Sui | Y | N | N | V1-legacy EVM, Solana, Aptos (V1 only) |

Destination Chain Constraints

Which versions can send to each destination:

| Destination | Can receive V1 | Can receive V2 | Notes | |--------------|---------------|----------------|----------------------------------------------| | Ethereum | Y | Y | | | Arbitrum | Y | Y | | | Base | Y | Y | | | Optimism | Y | Y | | | Unichain | Y | Y | | | Polygon | Y | Y | | | Avalanche | Y | Y | | | Linea | N | Y | V2-only destination | | Worldchain | N | Y | V2-only destination | | Sei | N | Y | V2-only destination | | Sonic | N | Y | V2-only destination | | HyperEVM | N | Y | V2-only destination | | Monad | N | Y | V2-only destination | | Solana | Y | Y | V1 from any; V2 from EVM only | | Aptos | Y | N | V1 only (from V1-legacy EVM, Solana, Sui) | | Sui | Y | N | V1 only (from V1-legacy EVM, Solana, Aptos) |

V1-Unsupported Chains (V2-only)

These chains have no V1 support — neither as source nor destination: linea, worldchain, sei, sonic, hyperevm, monad, stable, megaeth

V2 Fast-Unsupported Sources

These chains can only send V2 Norm (threshold 2000), not V2 Fast (threshold 1000): avalanche, sei, sonic, polygon, solana, hyperevm, monad


11. Source Code References

| Area | Path | |------------|----------------------------------------------------------| | EVM Contract | evm/src/CashmereCCTP.sol | | Solana Program | sol/programs/cashmere_cctp/src/ | | Solana IDL | sol/target/idl/cashmere_cctp.json | | Aptos Module | aptos/build/cashmere_cctp/sources/transfer.move | | Sui Module | sui/sources/transfer.move |

Related skills