Skip to main content

Overview

CashmereCCTP is a comprehensive multi-chain implementation supporting zero-slippage native USDC transfers across EVM and non-EVM chains. All contracts follow Circle’s CCTP standard for secure cross-chain messaging.

Architecture

All CashmereCCTP contracts implement the same core functionality across different chain environments:
cashmere-contracts/
├── evm/src/CashmereCCTP.sol       # Solidity (Ethereum, L2s)
├── solana/programs/cashmere_cctp/ # Rust/Anchor (Solana)
├── aptos/sources/transfer.move    # Move (Aptos)
└── sui/sources/transfer.move      # Move (Sui)

Core Transfer Surface

Transfer Methods

transfer

Base burn + mint flow. Supported on all chains (EVM, Solana, Aptos, Sui).

transferV2

Circle CCTP v2 flow with maxFee and minFinalityThreshold. Implemented on EVM and Solana.

transferWithPermit

EIP-2612 approval helper. Available on EVM only.

transferV2WithPermit

Combines permit + CCTP v2. Available on EVM only.
ChainEntrypointsNotes
EVMtransfer, transferV2, transferWithPermit, transferV2WithPermittransferV2* routes through tokenMessengerV2.depositForBurnWithHook, hookData forwards to the remote router.
Solanatransfer, transfer_v2Anchor contexts mirror the EVM structs. transfer_v2 targets the CCTP v2 program and enforces denylist + finality threshold.
Aptostransfer_outer, transfertransfer_outer withdraws coins for you, transfer expects pre-withdrawn USDC/native coins. Only CCTP v1 is implemented today.
Suiprepare_deposit_for_burn_ticket, post_deposit_for_burnTwo-phase flow: create the burn ticket then emit Cashmere event when Circle finalizes the burn.

Signature Payload

Every quote produced by the Cashmere API signs the same logical fields: local domain, remote domain, relayer fee, deadline, and whether native gas is requested. The on-chain encoding differs per VM.
// contracts-prod/evm/src/CashmereCCTP.sol
bytes32 digest = keccak256(
    abi.encodePacked(
        localDomain,           // set from Circle's MessageTransmitter
        destinationDomain,
        fee,                   // relayer fee in 6-decimals
        deadline,              // unix seconds
        isNative,              // fee+gas charged in native token?
        uint8(cctpVersion)     // 1 = transfer, 2 = transferV2
    )
);
  • Solana serialises the same fields with Borsh (TransferParams in common.rs) and prepends cctp_version so the backend cannot replay a v1 quote through the v2 entrypoint.
  • Aptos (TransferParams in sources/transfer.move) and Sui (TransferParams in sources/transfer.move) encode with BCS and currently only expose the v1 path, so the version byte is omitted.
gasDropAmount is never part of the signature. Instead, each contract enforces max_native_gas_drop / max_usdc_gas_drop caps to protect user funds.

Recipient Encoding

CashmereCCTP.transfer* surfaces two address parameters: recipient (bytes32) and solanaOwner (bytes32). Correctly formatting these fields is critical for routes that involve Solana.

Solana as Destination

When invoking transfer or transferV2 on any non-Solana chain with destinationDomain = 5, set the recipient field to the hex-encoded Solana USDC token account that should receive funds. Populate solanaOwner with the Solana wallet address (ATA owner) encoded as 32-byte hex. If that token account does not exist when the relayer executes receive_message on Solana, Cashmere relayers bootstrap it automatically. The helper below converts a Base58 token account into the required hex string for the Solidity call.
import bs58 from "bs58";
import { hexlify } from "ethers";

export const solanaAddressToHex = (solanaAddress: string): string =>
  hexlify(bs58.decode(solanaAddress));

Solana as Source

When calling the Solana program’s transfer or transfer_v2 instructions with a non-Solana destination, provide recipient as the Base58 representation of the 32-byte hex address expected by the remote chain. The snippet below mirrors the Solana quickstart tutorial and produces a PublicKey that can be passed directly into Anchor account serializers.
import { getBytes } from "ethers";
import { PublicKey } from "@solana/web3.js";

const evmAddressToBytes32 = (address: string): string =>
  `0x000000000000000000000000${address.replace("0x", "")}`;

export const evmAddressToBase58PublicKey = (addressHex: string): PublicKey =>
  new PublicKey(getBytes(evmAddressToBytes32(addressHex)));

EVM, Sui, and Aptos Destinations

For transfers targeting EVM, Sui, or Aptos, ensure the recipient field is the 32-byte hex form of the destination account (padded with leading zeros, 64 hex characters after the 0x prefix). In these routes set solanaOwner to an all-zero 32-byte value (0x0000000000000000000000000000000000000000000000000000000000000000).

Fees, Gas Drops, and Events

  • Protocol fee: capped at 100 bp (MAX_FEE_BP) on every chain for security and collected in USDC. Using 0 bp now.
  • Relayer fee + gas drop: charged in native tokens when isNative=true, otherwise deducted from the USDC that is being burnt.
  • All implementations emit CashmereTransfer with matching fields so downstream indexers can treat transfers uniformly.

Obtaining Quotes & Signatures

  • Use the Gas API at https://gas.cashmere.exchange/getEcdsaSig_native (isV2=true) for EVM TransferV2/permit flows; /getEd25519Sig_native signs the same payload for Solana/Aptos/Sui.
  • Responses include the relayer fee (6-dec USDC or native units), an expiry deadline (now + 100s), and the signature bytes expected by each chain.
  • Example workflow:
    1. Query the Gas API with localDomain / destinationDomain / isNative.
    2. Populate Transfer(V2)Params.fee, gasDropAmount, and PermitParams.value using the quoted amounts.
    3. Forward the returned signature to the contract call (or inject it into the Ed25519 helper instruction on Solana).
  • Full endpoint docs live at Backend APIs.

EVM Implementation

Contract Addresses: For all Cashmere CCTP contracts, USDC/USDT token addresses, and domain IDs across all chains, see Contract Addresses.

Interface

interface ICashmereCCTP {
    struct TransferParams {
        uint64 amount;              // USDC amount in 6 decimals
        uint128 fee;                // Fee in USDC (if isNative=false)
        uint64 deadline;             // Signature expiration timestamp
        uint128 gasDropAmount;       // Gas to drop on destination
        uint32 destinationDomain;    // Circle domain ID
        bytes32 recipient;           // 32-byte recipient address
        bytes32 solanaOwner;         // Solana-specific (use empty if not Solana)
        bool isNative;               // Fee paid in native token or USDC
        bytes signature;             // Off-chain relayer signature
    }
    
    struct TransferV2Params {
        uint64 amount;
        uint256 maxFee;              // Maximum acceptable fee
        uint128 fee;
        uint64 deadline;
        uint128 gasDropAmount;
        uint32 destinationDomain;
        uint32 minFinalityThreshold; // Minimum finality required
        bytes32 recipient;
        bytes32 solanaOwner;
        bool isNative;
        bytes hookData;              // Optional hook data
        bytes signature;
    }
    
    struct PermitParams {
        uint256 value;               // Approval amount
        uint256 deadline;             // Permit expiration
        bytes signature;             // EIP-2612 permit signature
    }
    
    function transfer(TransferParams memory _params) payable external;
    function transferV2(TransferV2Params memory _params) payable external;
    function transferWithPermit(
        TransferParams memory _params,
        PermitParams memory _permitParams
    ) payable external;
    function transferV2WithPermit(
        TransferV2Params memory _params,
        PermitParams memory _permitParams
    ) payable external;
    
    // Returns the dynamic fee in USDC units after applying protocol and relayer components.
    function getFee(uint64 _amount, uint256 _staticFee) external view returns (uint64);
    function state() external view returns (State memory);
}

Usage Examples

Basic Transfer

import { ethers } from 'ethers';

const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer);
const usdc = new ethers.Contract(USDC_ADDRESS, ['function approve(address,uint256) returns (bool)'], signer);

const params = {
  amount: ethers.parseUnits('100', 6), // 100 USDC
  fee: ethers.parseUnits('1', 6),      // 1 USDC fee
  deadline: Math.floor(Date.now() / 1000) + 300, // 5 min
  gasDropAmount: ethers.parseUnits('5', 6), // 5 USDC for gas
  destinationDomain: 3, // Arbitrum
  recipient: ethers.zeroPadValue(recipientAddress, 32),
  solanaOwner: ethers.ZeroHash, // Empty for EVM transfers
  isNative: false, // Fee in USDC
  signature: signatureBytes // From relayer API
};

const totalUsdc = params.amount + (params.isNative ? 0n : params.gasDropAmount);
await usdc.approve(CONTRACT_ADDRESS, totalUsdc);

// Execute transfer
const tx = await contract.transfer(params, { value: params.isNative ? params.fee + params.gasDropAmount : 0n });
await tx.wait();

Transfer with Permit

// Get permit signature from user wallet
const permitSignature = await getPermitSignature(
  userWallet,
  CONTRACT_ADDRESS,
  amount,
  deadline
); // Implement EIP-2612 helper (e.g., signTypedData) based on your wallet provider.

const permitParams = {
  value: amount,
  deadline: deadline,
  signature: permitSignature
};

// Transfer without pre-approval
const tx = await contract.transferWithPermit(
  params,
  permitParams,
  { value: params.isNative ? params.fee + params.gasDropAmount : 0n }
);
await tx.wait();

TransferV2 with Max Fee Protection

const paramsV2 = {
  amount: ethers.parseUnits('100', 6),
  maxFee: ethers.parseUnits('2', 6),
  fee: ethers.parseUnits('1', 6),
  deadline: Math.floor(Date.now() / 1000) + 300,
  gasDropAmount: ethers.parseUnits('5', 6),
  destinationDomain: 3,
  minFinalityThreshold: 1000, // Fast path (CCTP v2 recommendation)
  recipient: ethers.zeroPadValue(recipientAddress, 32),
  solanaOwner: ethers.ZeroHash,
  isNative: false,
  hookData: '0x', // Optional hook data
  signature: signatureBytes
};

const totalUsdcV2 = paramsV2.amount + (paramsV2.isNative ? 0n : paramsV2.gasDropAmount);
await usdc.approve(CONTRACT_ADDRESS, totalUsdcV2);

const tx = await contract.transferV2(
  paramsV2,
  { value: paramsV2.isNative ? paramsV2.fee + paramsV2.gasDropAmount : 0n }
);
await tx.wait();
Circle publishes recommended maxFee values per route via the IRIS API. Query https://iris-api.circle.com/v2/burn/USDC/fees/{sourceDomainId}/{destDomainId} to receive the current minimumFee for each finalityThreshold. Example response:
{
  "data": [
    { "finalityThreshold": 1000, "minimumFee": 1 },
    { "finalityThreshold": 2000, "minimumFee": 0 }
  ]
}
Align maxFee and minFinalityThreshold with these values before submitting transferV2* transactions.

TransferV2WithPermit (Native Fee Example)

import { ethers } from 'ethers';

const paramsV2Permit = {
  amount: BigInt(quote.amount),              // 6-dec USDC
  maxFee: BigInt(quote.maxFee),              // 6-dec USDC cap
  fee: BigInt(quote.fee),                    // native decimals when isNative = true
  deadline: BigInt(quote.deadline),
  gasDropAmount: BigInt(quote.gasDropAmount ?? 0),
  destinationDomain: 0,                      // Ethereum mainnet
  minFinalityThreshold: 1000,
  recipient: ethers.zeroPadValue('0x11111111111111...11111111111111111111111', 32),
  solanaOwner: ethers.ZeroHash,
  isNative: true,
  hookData: '0x',                            // optional; pass encoded instructions when needed
  signature: quote.signature,
};

const permitParams = {
  value: paramsV2Permit.amount,              // same 6-dec USDC amount
  deadline: BigInt(Math.floor(Date.now() / 1000) + 600),
  signature: permitSignature,                // wallet-signed permit
};

const tx = await contract.transferV2WithPermit(
  paramsV2Permit,
  permitParams,
  { value: paramsV2Permit.fee + paramsV2Permit.gasDropAmount }
);
await tx.wait();
  • recipient is zero-padded to 32 bytes because Circle expects fixed-length addresses on every chain.
  • solanaOwner stays 0x00…00 when the destination is EVM/Sui/Aptos; set it to the Solana wallet address (ATA owner) only when destinationDomain = 5.
  • fee is denominated in native token units when isNative = true; include fee + gasDropAmount in the transaction value so the contract can forward native gas on the caller’s behalf.

Quote-to-Param Checklist

  • amount – USDC to burn (6 decimals); should match the quote’s amount and the ERC-20 permit value.
  • maxFee – Upper bound you are willing to pay in USDC; compare against Circle IRIS guidance for the chosen minFinalityThreshold.
  • fee – Relayer fee; denominated in USDC when isNative=false, native token units when isNative=true.
  • deadline – Unix timestamp from the quote; protect against replay by rejecting stale values.
  • gasDropAmount – Native gas to forward on destination; leave 0 for pure USDC routes.
  • destinationDomain – Circle domain ID (see table below); ensure UI selection matches this ID.
  • minFinalityThreshold – 1,000 (Fast) or 2,000 (Normal) for CCTP v2 routes.
  • recipient – Always a 32-byte value; pad EVM addresses, convert Solana token accounts to hex, or encode Move addresses as 32-byte hex.
  • solanaOwner – Solana ATA owner (32-byte hex) when destinationDomain=5, otherwise 0x00…00.
  • isNative – When true, send fee + gasDropAmount as msg.value; when false, ensure those amounts are funded with USDC approvals.
  • hookData – ABI-encoded payload for downstream hooks; 0x when unused.
  • signature – Raw bytes from the Gas API quote (ECDSA for EVM, Ed25519 for Solana/Move chains).
  • PermitParams – Tie the EIP-2612 approval to the same amount/deadline; reject mismatched values client-side.

Security Features

  • Reentrancy Protection (EVM): Transfer functions on EVM chains use OpenZeppelin’s ReentrancyGuard
  • Deadline Validation: Prevents replay attacks with time-limited signatures
  • Fee Limits: Maximum 1% protocol fee (100 basis points)
  • Gas Drop Limits: Configurable maximum gas drop amounts
  • Signature Verification: ECDSA signature validation for relayer quotes
  • Pause Mechanism: Emergency pause functionality

Solana Implementation

Program Addresses: See Contract Addresses for Solana program IDs and USDC mint address. Domain ID: 5

Instructions

pub struct Transfer<'info> {
    pub config: Account<'info, Config>,
    pub owner: Signer<'info>,
    pub owner_token_account: Account<'info, TokenAccount>,
    pub fee_collector_sol_account: Account<'info, SystemAccount>,
    pub fee_collector_usdc_account: Account<'info, TokenAccount>,
    pub custodian: Account<'info, Custodian>,
    // ... additional accounts
}

pub fn transfer(
    ctx: Context<Transfer>,
    usdc_amount: u64,
    destination_domain: u32,
    recipient: [u8; 32],
    solana_owner: [u8; 32],
    fee: u64,
    deadline: u64,
    gas_drop_amount: u64,
    fee_is_native: bool,
) -> Result<()> {
    // Implementation
}

Ed25519 Verification

The program does not accept the relayer signature as an argument. Instead, it inspects the previous instruction in the transaction and replays the Ed25519 verifier proof.
  1. Borsh-encode the signature payload.
  2. Add an ed25519 program instruction with the backend public key and message bytes.
  3. Include Sysvar1nstructions11111111111111111111111111111111 (system constant) so the program can read the verifier instruction.
import { Ed25519Program } from '@solana/web3.js';
import { struct, u8, u32, u64, bool } from '@coral-xyz/borsh';

const payloadLayout = struct([
  u8('cctpVersion'),
  u32('localDomain'),
  u32('destinationDomain'),
  u64('fee'),
  u64('deadline'),
  bool('feeIsNative'),
]);

const payload = Buffer.alloc(payloadLayout.span);
payloadLayout.encode({
  cctpVersion: 1,        // 1 for transfer, 2 for transfer_v2
  localDomain: 5,
  destinationDomain: params.destinationDomain,
  fee: params.fee,
  deadline: params.deadline,
  feeIsNative: params.feeIsNative,
}, payload);

transaction.add(
  // signerKeyBytes = Buffer.from(relayerPublicKey, 'hex')
  // quoteSignature = Buffer.from(quote.signature.slice(2), 'hex')
  Ed25519Program.createInstructionWithPublicKey({
    publicKey: signerKeyBytes,
    message: payload,
    signature: quoteSignature,
  }),
  await program.methods.transfer(/* ... */).accounts(/* ... */).instruction()
);

Usage Example

import { Program, AnchorProvider } from '@coral-xyz/anchor';
import { PublicKey, Transaction } from '@solana/web3.js';

const program = new Program(IDL, PROGRAM_ID, provider);

const tx = await program.methods
  .transfer(
    new BN(100_000_000), // 100 USDC
    new BN(5), // Ethereum domain
    recipientBytes,
    solanaOwnerBytes,
    new BN(1_000_000), // 1 USDC fee
    new BN(Math.floor(Date.now() / 1000) + 300),
    new BN(5_000_000), // 5 USDC gas drop
    false // Fee in USDC
  )
  .accounts({
    config: configPDA,
    owner: userWallet.publicKey,
    ownerTokenAccount: userTokenAccount,
    // ... additional accounts
  })
  .rpc();

transfer_v2 Details

  • Call program.methods.transferV2(...) with the same signature payload but cctpVersion = 2.
  • Two extra arguments are enforced: max_fee (relayer cap in USDC) and min_finality_threshold (Circle’s notarisation target).
  • Circle currently defines two thresholds: 1000 for Fast transfers and 2000 for Normal transfers. Use the value that matches the quote returned by your backend/UI.
  • Additional accounts such as denylist_account and the CCTP v2 program IDs must be supplied; Anchor types in transfer_v2_ix.rs show the full list.

Aptos Implementation

Module Addresses: See Contract Addresses for Aptos module addresses. Domain ID: 9

Entry Functions

module cashmere_cctp::transfer {
    public entry fun transfer_outer(
        sender: &signer,
        usdc_amount: u64,
        native_amount: u64,
        destination_domain: u32,
        recipient: address,
        solana_owner: address,
        fee: u64,
        deadline: u64,
        gas_drop_amount: u64,
        fee_is_native: bool,
        signature: vector<u8>
    );

    public fun transfer(
        sender: &signer,
        usdc_asset: FungibleAsset,
        native_fee: Coin<0x1::aptos_coin::AptosCoin>,
        destination_domain: u32,
        recipient: address,
        solana_owner: address,
        fee: u64,
        deadline: u64,
        gas_drop_amount: u64,
        fee_is_native: bool,
        signature: vector<u8>
    );
}

Signature Payload (BCS)

struct TransferParams has drop, copy {
    local_domain: u32,        // 9 on Aptos mainnet
    destination_domain: u32,
    fee: u64,
    deadline: u64,
    fee_is_native: bool,
}
The relayer signs the BCS bytes of TransferParams. Pass that signature to either transfer_outer or transfer. When fee_is_native is false, the module automatically withdraws usdc_amount + gas_drop_amount from the caller and burns usdc_amount.
native_amount should equal fee + gas_drop_amount when fee_is_native = true; otherwise pass 0.

Usage Example

import { AptosAccount, AptosClient } from 'aptos';

const params = {
  fee: 1_000_000, // 1 USDC in 6 decimals
  gasDropAmount: 5_000_000, // 5 USDC
  feeIsNative: false,
};

const payload = {
  type: 'entry_function_payload',
  function: `${CONTRACT_ADDRESS}::transfer::transfer_outer`,
  arguments: [
    100_000_000, // usdc_amount to burn
    params.feeIsNative ? params.fee + params.gasDropAmount : 0, // native_amount
    5, // destination_domain
    recipientHex, // address on the remote chain
    solanaOwnerHex, // only used when sending to Solana
    params.fee,
    Math.floor(Date.now() / 1000) + 300, // deadline (seconds)
    5_000_000, // gas_drop_amount
    params.feeIsNative,
    Array.from(signatureBytes)
  ],
  type_arguments: []
};

const transaction = await client.generateTransaction(
  userAccount.address(),
  payload
);
const signedTxn = await client.signTransaction(userAccount, transaction);
const pendingTxn = await client.submitTransaction(signedTxn);
  • recipientHex should be a 0x-prefixed 32-byte address encoded for the destination chain (Circle expects 32 bytes).
  • Set solanaOwnerHex when sending into Solana; otherwise pass 0x0.
  • signatureBytes is the byte array returned by the Cashmere quote API.

Sui Implementation

Module Addresses: See Contract Addresses for Sui module addresses. Domain ID: 8

Entry Functions

module cashmere_cctp::transfer {
    public fun prepare_deposit_for_burn_ticket<T: drop>(
        usdc_coin: Coin<T>,
        native_fee_coin: Coin<SUI>,
        destination_domain: u32,
        recipient: address,
        solana_owner: address,
        fee: u64,
        deadline: u64,
        gas_drop_amount: u64,
        fee_is_native: bool,
        signature: vector<u8>,
        config: &Config,
        clock: &Clock,
        ctx: &mut TxContext,
    ): (DepositForBurnTicket<T, Auth>, DepositInfo);

    public fun post_deposit_for_burn(
        burn_message: BurnMessage,
        message: Message,
        deposit_info: DepositInfo,
        config: &mut Config,
    );
}

Signature Payload (BCS)

struct TransferParams has drop, copy {
    local_domain: u32,     // 8 on Sui mainnet
    destination_domain: u32,
    fee: u64,
    deadline: u64,
    fee_is_native: bool,
}
prepare_deposit_for_burn_ticket enforces the signature above and moves protocol + relayer fees before returning a DepositForBurnTicket. The ticket must then be consumed by Circle’s deposit_for_burn Move call; afterwards call post_deposit_for_burn to emit the Cashmere event and bump the nonce.

Usage Example

import { SuiClient } from '@mysten/sui.js/client';
import { TransactionBlock } from '@mysten/sui/transactions';

const params = {
  fee: 1_000_000n,
  gasDropAmount: 5_000_000n,
  feeIsNative: false,
};
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300);

const txb = new TransactionBlock();

const [ticket, depositInfo] = txb.moveCall({
  target: `${PACKAGE_ID}::transfer::prepare_deposit_for_burn_ticket`,
  arguments: [
    txb.object(usdcCoinId),
    txb.object(nativeFeeCoinId), // pass a zero-value coin if feeIsNative=false
    txb.pure.u32(destinationDomain),
    txb.pure.address(recipient),
    txb.pure.address(solanaOwner),
    txb.pure.u64(params.fee),
    txb.pure.u64(deadline),
    txb.pure.u64(params.gasDropAmount),
    txb.pure.bool(params.feeIsNative),
    txb.pure.vector('u8', Array.from(signatureBytes)),
    txb.object(configId),
    txb.object(clockId),
  ],
});

txb.moveCall({
  target: `${CIRCLE_PACKAGE}::token_messenger_minter::deposit_for_burn`,
  arguments: [ticket],
});

txb.moveCall({
  target: `${PACKAGE_ID}::transfer::post_deposit_for_burn`,
  arguments: [
    txb.object(burnMessageId),
    txb.object(circleMessageId),
    depositInfo,
    txb.objectMut(configId),
  ],
});

const result = await client.signAndExecuteTransaction({
  signer,
  transactionBlock: txb,
});
  • The same signature is reused for the prepare_deposit_for_burn_ticket call; no signature is needed when finalising.
  • native_fee_coin must contain fee + gas_drop_amount SUI when fee_is_native = true; otherwise supply an empty SUI coin object.
  • signatureBytes is the Ed25519 quote returned by Cashmere; pass it as raw bytes, not hex.

Domain IDs

Complete list of Circle CCTP domain IDs for all supported chains. These values are immutable and differ from chain IDs—use them in every Gas API request and contract call.

Admin Functions

All contracts support administrative functions via role-based access control:

EVM

function setFeeBP(uint16 _feeBP) external onlyRole(DEFAULT_ADMIN_ROLE);
function setSigner(address _signer) external onlyRole(DEFAULT_ADMIN_ROLE);
function setMaxNativeGasDrop(uint128 _newLimit) external onlyRole(DEFAULT_ADMIN_ROLE);
function setMaxUSDCGasDrop(uint32 _newLimit) external onlyRole(DEFAULT_ADMIN_ROLE);
function setPaused(bool _paused) external onlyRole(DEFAULT_ADMIN_ROLE);
function withdrawFee(uint256 _usdcAmount, uint256 _nativeAmount, address _destination) external;

Solana

pub fn set_fee_bp(ctx: Context<SetFeeBp>, fee_bp: u64) -> Result<()>;
pub fn set_signer_key(ctx: Context<SetSignerKey>, signer_key: [u8; 32]) -> Result<()>;
pub fn set_max_native_gas_drop(ctx: Context<SetMaxNativeGasDrop>, max_gas: u64) -> Result<()>;
pub fn set_max_usdc_gas_drop(ctx: Context<SetMaxUSDCGasDrop>, max_gas: u64) -> Result<()>;
pub fn set_paused(ctx: Context<SetPaused>, paused: bool) -> Result<()>;

Error Codes

EVM Errors

  • FeeExceedsAmount() - Fee is greater than transfer amount
  • TransferFailed() - USDC transfer failed
  • DeadlineExpired() - Signature has expired
  • InvalidSignature() - Signature verification failed
  • GasDropLimitExceeded() - Gas drop exceeds maximum limit
  • ReentrancyError() - Reentrancy attempt detected
  • Paused() - Contract is paused

Solana Errors

  • NotSigVerified (6000) - Signature not verified
  • InvalidSignatureData (6001) - Invalid signature data
  • InvalidDataFormat (6002) - Invalid data format
  • EpochTooLarge (6004) - Epoch too large
  • InvalidSignature (6006) - Invalid signer

Events

CashmereTransfer Event

Emitted on all successful transfers:
event CashmereTransfer(
    uint32 destinationDomain,
    uint256 indexed nonce,
    bytes32 recipient,
    bytes32 solanaOwner,
    address indexed user,
    uint64 amount,
    uint256 gasDropAmount,
    bool isNative
);

Chain IDs

EVM chain IDs for RPC configuration:

Testing

Unit Tests

# EVM
cd evm && npm test

# Solana
cd solana && anchor test

# Aptos
cd aptos && aptos move test

# Sui
cd sui && sui move test

Best Practices

  1. Always validate signatures before submitting transactions
  2. Use deadline parameters to prevent replay attacks
  3. Approve sufficient USDC before transfers
  4. Handle revert reasons for better UX
  5. Monitor events for transfer status
  6. Use transferV2 for max fee protection
  7. Leverage permits for gas-efficient approvals

View Source Code

Browse the contract repository