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

> ## Agent Instructions
> Before reading these docs, install the relevant Circle Skill to get pre-loaded context — skills are LLM-optimized and cover architecture decisions, correct flows, and common pitfalls. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills. Available skills: use-usdc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md (balances, transfers, approvals on EVM and Solana); EURC has no skill — see https://developers.circle.com/stablecoins/what-is-eurc.md; bridge-stablecoin https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md (crosschain transfers via CCTP, USDC, EURC, and third-party assets); use-arc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md (Arc chain config, contract deployment, USDC bridging — Arc docs: https://docs.arc.io/llms.txt); use-circle-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md (choose wallet type); use-developer-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md (custodial wallets for payouts and treasury); use-user-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md (embedded wallets with Web2 login); use-modular-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md (smart contract wallets with passkeys and ERC-4337); use-gateway https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md (unified USDC balance, nanopayments); use-smart-contract-platform https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md (deploy, interact, monitor contracts). Pair any skill with the Circle MCP server for live SDK signatures, contract addresses, and chain IDs: {"mcpServers":{"circle":{"url":"https://api.circle.com/v1/codegen/mcp"}}}.
> Read the product overview first. Each product has an overview explaining when and why to use it. Read it before quickstarts.
> Pick the right wallet type. Developer-controlled (you hold keys), user-controlled (end user holds keys via PIN/social login), or modular (smart contract wallets with passkeys). See https://developers.circle.com/wallets.md.
> Use CCTP V2 for crosschain transfers. CCTP natively transfers USDC and EURC via burn-and-mint, and supports permissionless wrapping of third-party assets via lock-and-mint. V1 is legacy; always use V2 unless V1 is specified or the chain requires it (Noble, Sui, and Aptos still require V1). Migration guide: https://developers.circle.com/cctp/migration-from-v1-to-v2.md.
> Use Bridge Kit for frontend bridging. Wraps CCTP with a simpler SDK for user-facing apps. Use CCTP directly for backend transfers.
> Gas Station vs Paymaster. Gas Station sponsors gas for Circle Wallet transactions. Paymaster lets users pay gas in USDC. Different use cases — read both overviews.
> Gateway Nanopayments for sub-cent payments. Gasless USDC micropayments down to $0.000001 via x402 and batched settlement. For pay-per-request APIs, AI agent payments, streaming.
> Look up USDC addresses per chain. Never hardcode — use https://developers.circle.com/stablecoins/usdc-contract-addresses.md.
> Prefer SDKs over raw API calls. Node.js and Python SDKs handle auth, retries, and errors.
> API key required. Bearer token in Authorization header. Testnet and mainnet use separate keys and may use different base URLs depending on the product.
> Set up webhooks when available. Most operations are async. Webhooks deliver transaction confirmations and state changes.
> When calling list endpoints, paginate using pageSize and pageAfter until no nextPageAfter cursor is returned—stopping at the first page silently misses records.
> Building an AI agent? Start with the Agent Stack—Circle CLI, agent wallets, and nanopayments built for autonomous use cases: https://developers.circle.com/agent-stack.md.

# Transfer USDC on testnet between Sui and Ethereum using CCTP V1

> Explore this tutorial for transferring USDC between Sui testnet and Ethereum Sepolia Testnet via CCTP V1

<Warning>
  **This is CCTP V1 (Legacy) version. For the latest version, see [CCTP](/cctp)**.
</Warning>

To get started with CCTP V1 on Sui testnet, follow the example scripts provided
[on GitHub](https://github.com/circlefin/sui-cctp/tree/master/scripts/sui-scripts).
The
[README](https://github.com/circlefin/sui-cctp?tab=readme-ov-file#run-localnet-example-scripts)
contains instructions for running the scripts. The examples use the
[Sui SDK](https://www.npmjs.com/package/@mysten/sui), to transfer USDC to and
from an address on Sui testnet and an address on an external blockchain.

<Warning>
  **Do not reuse keys** As a security measure, these scripts should only be used
  on a testnet for testing purposes. It is not recommended to reuse private keys
  across mainnet and testnet.
</Warning>

Summary of calling `deposit_for_burn()` (full runnable script can be found in
the sui-cctp repository):

```javascript JavaScript theme={null}
// Create DepositForBurn tx
const depositForBurnTx = new Transaction();

// Split USDC to send in depositForBurn call
const ownedCoins = await client.getAllCoins({owner: signer.toSuiAddress()})
const usdcStruct = ownedCoins.data.find(c => c.coinType.includes(usdcId));
if (!usdcStruct || Number(usdcStruct.balance) < USDC_AMOUNT) {
  throw new Error("Insufficient tokens in wallet to initiate transfer.");
}

const [coin] = depositForBurnTx.splitCoins(
  usdcStruct.coinObjectId,
  [USDC_AMOUNT]
);

// Create the deposit_for_burn move call
depositForBurnTx.moveCall({
  target: `${tokenMessengerMinterId}::deposit_for_burn::deposit_for_burn`,
  arguments: [
    depositForBurnTx.object(coin), // Coin<USDC>
    depositForBurnTx.pure.u32(DESTINATION_DOMAIN), // destination_domain
    depositForBurnTx.pure.address(evmUserAddress), // mint_recipient
    depositForBurnTx.object(tokenMessengerMinterStateId), // token_messenger_minter state
    depositForBurnTx.object(messageTransmitterStateId), // message_transmitter state
    depositForBurnTx.object("0x403"), // deny_list id, fixed address
    depositForBurnTx.object(treasuryId) // treasury object Treasury<USDC>
  ],
  typeArguments: [`${usdcId}::usdc::USDC`],
});

// Broadcast the transaction
console.log("Broadcasting sui deposit_for_burn tx...");
const depositForBurnOutput = await executeTransactionHelper({
  client: client,
  signer: signer,
  transaction: depositForBurnTx,
});
assert(!depositForBurnOutput.errors);
console.log(`deposit_for_burn transaction successful: 0x${depositForBurnOutput.digest} \n`);

// Get USDC balance changes (optional)
const suiUsdcBalanceChange = depositForBurnOutput.balanceChanges?.find(b => b.coinType.includes(usdcId))
const balances = await client.getAllBalances({ owner: signer.toSuiAddress() });
const usdcBalance = balances.find(b => b.coinType.includes(usdcId))?.totalBalance;

// Get the message emitted from the tx
const messageRaw: Uint8Array = (depositForBurnOutput.events?.find((event) =>
  event.type.includes("send_message::MessageSent")
)?.parsedJson as any).message;
const messageBuffer = Buffer.from(messageRaw);
const messageHex = `0x${messageBuffer.toString("hex")}`;
const messageHash = web3.utils.keccak256(messageHex);
console.log(`Message hash: ${messageHash}`);
```

Summary of calling `receive_message()` (full runnable script can be found in the
sui-cctp repository):

```javascript JavaScript theme={null}
// Create receiveMessage transaction
const receiveMessageTx = new Transaction();

// Add receive_message move call to MessageTransmitter
const [receipt] = receiveMessageTx.moveCall({
  target: `${messageTransmitterId}::receive_message::receive_message`,
  arguments: [
    receiveMessageTx.pure.vector(
      "u8",
      Buffer.from(evmBurnTx.message.replace("0x", ""), "hex"),
    ), // message as byte array
    receiveMessageTx.pure.vector(
      "u8",
      Buffer.from(attestation.replace("0x", ""), "hex"),
    ), // attestation as byte array
    receiveMessageTx.object(messageTransmitterStateId), // message_transmitter state
  ],
});

// Add handle_receive_message call to TokenMessengerMinter with Receipt from receive_message call
const [stampReceiptTicketWithBurnMessage] = receiveMessageTx.moveCall({
  target: `${tokenMessengerMinterId}::handle_receive_message::handle_receive_message`,
  arguments: [
    receipt, // Receipt object returned from receive_message call
    receiveMessageTx.object(tokenMessengerMinterStateId), // token_messenger_minter state
    receiveMessageTx.object("0x403"), // deny list, fixed address
    receiveMessageTx.object(treasuryId), // usdc treasury object Treasury<T>
  ],
  typeArguments: [`${usdcId}::usdc::USDC`],
});

// Add deconstruct_stamp_receipt_ticket_with_burn_message call
const [stampReceiptTicket] = receiveMessageTx.moveCall({
  target: `${tokenMessengerMinterId}::handle_receive_message::deconstruct_stamp_receipt_ticket_with_burn_message`,
  arguments: [stampReceiptTicketWithBurnMessage],
});

// Add stamp_receipt call
const [stampedReceipt] = receiveMessageTx.moveCall({
  target: `${messageTransmitterId}::receive_message::stamp_receipt`,
  arguments: [
    stampReceiptTicket, // Receipt ticket returned from deconstruct_stamp_receipt_ticket_with_burn_message call
    receiveMessageTx.object(messageTransmitterStateId), // message_transmitter state
  ],
  typeArguments: [
    `${tokenMessengerMinterId}::message_transmitter_authenticator::MessageTransmitterAuthenticator`,
  ],
});

// Add complete_receive_message call to MessageTransmitter with StampedReceipt from stamp_receipt call.
// Receipt and StampedReceipt are Hot Potatoes so they must be destroyed for the
// transaction to succeed.
receiveMessageTx.moveCall({
  target: `${messageTransmitterId}::receive_message::complete_receive_message`,
  arguments: [
    stampedReceipt, // Stamped receipt object returned from handle_receive_message call
    receiveMessageTx.object(messageTransmitterStateId), // message_transmitter state
  ],
});

// Broadcast the transaction
console.log("Broadcasting Sui receive_message tx...");
const receiveMessageOutput = await executeTransactionHelper({
  client: client,
  signer: signer,
  transaction: receiveMessageTx,
});
```
