> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cashmere.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Smart Contracts

> CashmereCCTP smart contract integration guide

import { Card, CardGroup, AccordionGroup, Accordion, Callout, Code } from 'mintlify';

## 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.

<Callout type="info">
  **Contract Repository:** [GitHub - cashmere-prod/contracts-prod](https://github.com/cashmere-prod/contracts-prod)
</Callout>

## 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

<CardGroup cols={2}>
  <Card title="transfer" icon="arrows-rotate">
    Base burn + mint flow. Supported on all chains (EVM, Solana, Aptos, Sui).
  </Card>

  <Card title="transferV2" icon="arrows-rotate">
    Circle CCTP v2 flow with `maxFee` and `minFinalityThreshold`. Implemented on EVM and Solana.
  </Card>

  <Card title="transferWithPermit" icon="arrows-rotate">
    EIP-2612 approval helper. Available on EVM only.
  </Card>

  <Card title="transferV2WithPermit" icon="arrows-rotate">
    Combines permit + CCTP v2. Available on EVM only.
  </Card>
</CardGroup>

| Chain      | Entrypoints                                                            | Notes                                                                                                                            |
| ---------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **EVM**    | `transfer`, `transferV2`, `transferWithPermit`, `transferV2WithPermit` | `transferV2*` routes through `tokenMessengerV2.depositForBurnWithHook`, `hookData` forwards to the remote router.                |
| **Solana** | `transfer`, `transfer_v2`                                              | Anchor contexts mirror the EVM structs. `transfer_v2` targets the CCTP v2 program and enforces denylist + finality threshold.    |
| **Aptos**  | `transfer_outer`, `transfer`                                           | `transfer_outer` withdraws coins for you, `transfer` expects pre-withdrawn USDC/native coins. Only CCTP v1 is implemented today. |
| **Sui**    | `prepare_deposit_for_burn_ticket`, `post_deposit_for_burn`             | Two-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.

```solidity theme={null}
// 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.

<Callout type="info">
  `gasDropAmount` is never part of the signature. Instead, each contract enforces `max_native_gas_drop` / `max_usdc_gas_drop` caps to protect user funds.
</Callout>

### 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.

```ts theme={null}
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.

```ts theme={null}
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](/developers/backend-apis).

## EVM Implementation

<Callout type="info">
  **Contract Addresses:** For all Cashmere CCTP contracts, USDC/USDT token addresses, and domain IDs across all chains, see [Contract Addresses](/developers/contract-addresses).
</Callout>

### Interface

```solidity theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
// 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

```javascript theme={null}
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();
```

<Callout type="info">
  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:

  ```json theme={null}
  {
    "data": [
      { "finalityThreshold": 1000, "minimumFee": 1 },
      { "finalityThreshold": 2000, "minimumFee": 0 }
    ]
  }
  ```

  Align `maxFee` and `minFinalityThreshold` with these values before submitting `transferV2*` transactions.
</Callout>

#### TransferV2WithPermit (Native Fee Example)

```ts theme={null}
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

<Callout type="info">
  **Program Addresses:** See [Contract Addresses](/developers/contract-addresses) for Solana program IDs and USDC mint address. Domain ID: `5`
</Callout>

### Instructions

```rust theme={null}
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](https://docs.solana.com/developing/runtime-facilities/programs#ed25519-program) instruction with the backend public key and message bytes.
3. Include `Sysvar1nstructions11111111111111111111111111111111` (system constant) so the program can read the verifier instruction.

```ts theme={null}
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

```typescript theme={null}
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

<Callout type="info">
  **Module Addresses:** See [Contract Addresses](/developers/contract-addresses) for Aptos module addresses. Domain ID: `9`
</Callout>

### Entry Functions

```move theme={null}
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)

```move theme={null}
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`.

<Callout type="info">
  `native_amount` should equal `fee + gas_drop_amount` when `fee_is_native = true`; otherwise pass `0`.
</Callout>

### Usage Example

```typescript theme={null}
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

<Callout type="info">
  **Module Addresses:** See [Contract Addresses](/developers/contract-addresses) for Sui module addresses. Domain ID: `8`
</Callout>

### Entry Functions

```move theme={null}
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)

```move theme={null}
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

```typescript theme={null}
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.

<Table>
  <TableHead>
    <TableRow>
      <TableHeader>Chain</TableHeader>
      <TableHeader>Domain ID</TableHeader>
      <TableHeader>Status</TableHeader>
    </TableRow>
  </TableHead>

  <TableBody>
    <TableRow>
      <TableCell>Ethereum</TableCell>
      <TableCell>0</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Avalanche</TableCell>
      <TableCell>1</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Optimism</TableCell>
      <TableCell>2</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Arbitrum</TableCell>
      <TableCell>3</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Solana</TableCell>
      <TableCell>5</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Base</TableCell>
      <TableCell>6</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Polygon</TableCell>
      <TableCell>7</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Sui</TableCell>
      <TableCell>8</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Aptos</TableCell>
      <TableCell>9</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Unichain</TableCell>
      <TableCell>10</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Linea</TableCell>
      <TableCell>11</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Sonic</TableCell>
      <TableCell>13</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Worldchain</TableCell>
      <TableCell>14</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Sei</TableCell>
      <TableCell>16</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>HyperEVM</TableCell>
      <TableCell>19</TableCell>
      <TableCell>✅ Live</TableCell>
    </TableRow>
  </TableBody>
</Table>

## Admin Functions

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

### EVM

```solidity theme={null}
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

```rust theme={null}
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:

```solidity theme={null}
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:

<Table>
  <TableHead>
    <TableRow>
      <TableHeader>Chain</TableHeader>
      <TableHeader>Chain ID</TableHeader>
    </TableRow>
  </TableHead>

  <TableBody>
    <TableRow>
      <TableCell>Ethereum</TableCell>
      <TableCell>1</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Avalanche</TableCell>
      <TableCell>43114</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Polygon</TableCell>
      <TableCell>137</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Arbitrum</TableCell>
      <TableCell>42161</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Optimism</TableCell>
      <TableCell>10</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Base</TableCell>
      <TableCell>8453</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Unichain</TableCell>
      <TableCell>130</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Linea</TableCell>
      <TableCell>59144</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Worldchain</TableCell>
      <TableCell>480</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Sei</TableCell>
      <TableCell>1329</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Sonic</TableCell>
      <TableCell>146</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>HyperEVM</TableCell>
      <TableCell>999</TableCell>
    </TableRow>
  </TableBody>
</Table>

## Testing

### Unit Tests

```bash theme={null}
# 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

<Card title="View Source Code" icon="github" href="https://github.com/cashmere-prod/contracts-prod">
  Browse the contract repository
</Card>
