bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 75쪽 중 41쪽
For more information, see the [Bridge](https://docs.arc.io/app-kit/bridge)
capability in Arc App Kits.
## Changes to smart contracts
CCTP uses new smart contracts with different names, addresses, and interfaces.
You must update your integration to use the new contracts and their new function
signatures.
### Contract name and address changes
All legacy contracts have V2 equivalents deployed at new addresses:
| Legacy contract | V2 contract | Documentation |
| - | - | - |
| `TokenMessenger` | `TokenMessengerV2` | [V2 Interface](/cctp/evm-smart-contracts#tokenmessengerv2) |
| `MessageTransmitter` | `MessageTransmitterV2` | [V2 Interface](/cctp/evm-smart-contracts#messagetransmitterv2) |
| `TokenMinter` | `TokenMinterV2` | [V2 Addresses](/cctp/evm-smart-contracts#tokenminterv2-mainnet) |
| `Message` | `MessageV2` | [V2 Addresses](/cctp/evm-smart-contracts#messagev2-mainnet) |
<Important>
**Important**: V2 contracts are deployed at different addresses than V1
contracts. See the
[CCTP Contract Addresses](/cctp/evm-smart-contracts#mainnet-contract-addresses)
for the complete list of mainnet and testnet addresses.
</Important>
### `TokenMessengerV2` changes
**Modified functions:**
* `depositForBurn()` now requires three additional parameters:
* `destinationCaller` (bytes32) - Address that can call `receiveMessage` on
destination
* `maxFee` (uint256) - Maximum fee for Fast Transfer in units of burn token
* `minFinalityThreshold` (uint32) - Minimum finality level (1000 for Fast,
2000 for Standard)
**New functions:**
* `depositForBurnWithHook()` - Enables custom logic execution on destination
chain using hook data
* `getMinFeeAmount()` - Calculates minimum fee for Standard Transfer (on
supported chains only)
**Removed functions:**
* `depositForBurnWithCaller()` - Use `destinationCaller` parameter in
`depositForBurn()` instead
* `replaceDepositForBurn()` - No V2 equivalent available
### Contract source code
Full contract source code is available on GitHub:
* [CCTP EVM Contracts](https://github.com/circlefin/evm-cctp-contracts) - Main
repository
* [Contract ABIs](https://github.com/circlefin/evm-cctp-contracts/tree/master/docs/abis/cctp/v2) -
Interface definitions
## API migration guide
CCTP streamlines the API workflow by combining message retrieval and attestation
into single calls, while introducing new endpoints for features like Fast
Transfer monitoring and re-attestation.
### Workflow changes
The API eliminates the need to extract the message emitted by the onchain
transaction:
**Legacy workflow:**
1. Get the transaction receipt from the onchain transaction
2. Find the MessageSent event in the transaction receipt
3. Hash the message bytes emitted by the MessageSent event
4. Call `/v1/attestations/{messageHash}` to get an attestation
**V2 workflow:**
1. Call `/v2/messages/{sourceDomainId}` with transaction hash or nonce to get
message, attestation, and decoded data
#### Legacy workflow example
```javascript theme={null}
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
// V1 requires multiple steps to extract message and get attestation
const burnTxHash = "0x1234..."; // Transaction hash from depositForBurn
// Step 1: Get the transaction receipt from the onchain transaction
const client = createPublicClient({
chain: sepolia,
transport: http(),
});
const transactionReceipt = await client.getTransactionReceipt({
hash: burnTxHash,
});
// Step 2: Find the MessageSent event in the transaction receipt
const eventTopic = keccak256(toBytes("MessageSent(bytes)"));
const log = transactionReceipt.logs.find((l) => l.topics[0] === eventTopic);
const messageBytes = decodeAbiParameters([{ type: "bytes" }], log.data)[0];
// Step 3: Hash the message bytes emitted by the MessageSent event
const messageHash = keccak256(messageBytes);
// Step 4: Call attestation API with the message hash
let attestationResponse = { status: "pending" };
while (attestationResponse.status !== "complete") {
const response = await fetch(
`https://iris-api-sandbox.circle.com/attestations/${messageHash}`,
);
attestationResponse = await response.json();
await new Promise((r) => setTimeout(r, 2000));
}
const attestation = attestationResponse.attestation;
// Now you can use messageBytes and attestation to call receiveMessage
```
#### V2 workflow example
```javascript theme={null}
// V2 gets message and attestation in a single call
const sourceDomainId = 0; // Ethereum mainnet
const transactionHash = "0x1234...";
// Single step: Get message, attestation, and decoded data
const response = await fetch(
`https://iris-api.circle.com/v2/messages/${sourceDomainId}?transactionHash=${transactionHash}`,
);
const data = await response.json();
// All data available in single response
const message = data.messages[0].message;
const attestation = data.messages[0].attestation;
const decodedMessage = data.messages[0].decodedMessage;
// Now you can use message and attestation to call receiveMessage
// You can also access decoded fields without manual parsing
console.log(`Amount: ${decodedMessage.decodedMessageBody.amount}`);
console.log(`Recipient: ${decodedMessage.decodedMessageBody.mintRecipient}`);
```
### Endpoint migration mapping
| Legacy endpoint | V2 replacement | Migration notes |
| - | - | - |
| `GET /v1/attestations/{messageHash}` | `GET /v2/messages/{sourceDomainId}?transactionHash={hash}` | Combined into messages endpoint with enhanced response |
| `GET /v1/messages/{sourceDomainId}/{transactionHash}` | `GET /v2/messages/{sourceDomainId}?transactionHash={hash}` | Enhanced with decoded data and attestation |
| `GET /v1/publicKeys` | `GET /v2/publicKeys` | Multi-version support, backward compatible |
### New V2-only endpoints
V2 introduces additional endpoints for advanced features:
| Endpoint | Purpose | Use case |
| - | - | - |
| `POST /v2/reattest/{nonce}` | Re-attest messages for edge cases | Handle expired Fast Transfer burns or finality changes |
| `GET /v2/fastBurn/USDC/allowance` | Monitor Fast Transfer allowance | Check remaining Fast Transfer capacity in real-time |
| `GET /v2/burn/USDC/fees/{sourceDomainId}/{destDomainId}` | Get current transfer fees | Calculate fees before initiating transfers |
### Message data changes
V2 message responses now include the decoded message data and attestation:
#### V1 messages response
```json theme={null}
{
"messages": [
{
"attestation": "0xdc485fb2f9a8f68c871f4ca7386dee9086ff9d4387756990c9c4b9280338325252866861f9495dce3128cd524d525c44e8e7b731dedd3098a618dcc19c45be1e1c",
"message": "0x00000000000000050000000300000000000194c2...",
"eventNonce": "9682"
}
]
}
```
#### V2 messages response
```json theme={null}
{
"messages": [
{
"message": "0x00000000000000050000000300000000000194c2...",
"eventNonce": "9682",
"attestation": "0x6edd90f4a0ad0212fd9fbbd5058a25aa8ee10ce77e4fc143567bbe73fb6e164f384a3e14d350c8a4fc50b781177297e03c16b304e8d7656391df0f59a75a271f1b",
"decodedMessage": {
"sourceDomain": "7",
"destinationDomain": "5",
"nonce": "569",
"sender": "0xca9142d0b9804ef5e239d3bc1c7aa0d1c74e7350",
"recipient": "0xb7317b4EFEa194a22bEB42506065D3772C2E95EF",
"destinationCaller": "0xf2Edb1Ad445C6abb1260049AcDDCA9E84D7D8aaA",
"messageBody": "0x00000000000000050000000300000000000194c2...",
"decodedMessageBody": {
"burnToken": "0x4Bc078D75390C0f5CCc3e7f59Ae2159557C5eb85",
"mintRecipient": "0xb7317b4EFEa194a22bEB42506065D3772C2E95EF",
"amount": "5000",
"messageSender": "0xca9142d0b9804ef5e239d3bc1c7aa0d1c74e7350"
}
},
"cctpVersion": 2,
"status": "complete"
}
]
}
```
<Note>
On Stellar, USDC precision and address encoding differ from other CCTP-supported
blockchains. For inbound transfers, use
[`CctpForwarder`](/cctp/references/stellar#use-cctpforwarder-for-stellar-recipients)
so funds reach the correct recipient. See
[CCTP on Stellar](/cctp/references/stellar).
</Note>
# Transfer USDC between Aptos and Arc
Source: https://developers.circle.com/cctp/quickstarts/transfer-usdc-aptos-arc
Build scripts to transfer USDC between Aptos Testnet and Arc Testnet using CCTP
Transfer USDC between Aptos Testnet and Arc Testnet using the CCTP
burn-attest-mint flow.
<Note>
Move scripts for Aptos CCTP transfers are precompiled. They are located in the
[aptos-cctp GitHub
repository](https://github.com/circlefin/aptos-cctp/tree/master/typescript/example/precompiled-move-scripts/testnet/v2).
You submit these compiled scripts directly instead of compiling Move source
code.
</Note>
Pick the tab that matches the direction of your transfer.
<Tabs>
<Tab title="Aptos to Arc">
This quickstart demonstrates how to transfer USDC from Aptos Testnet to Arc
Testnet using CCTP. You use the
[`@aptos-labs/ts-sdk`](https://github.com/aptos-labs/aptos-ts-sdk) library to
submit the precompiled Aptos Move script that burns USDC, and
[`viem`](https://viem.sh/) to mint USDC on Arc Testnet. When you finish, you
will have executed a full burn-attest-mint flow.
You should be comfortable using a terminal and Node.js. Familiarity with Aptos
transactions and basic EVM usage helps you follow and adapt the script. Examples
use Arc Testnet as the destination, but you can use any
[supported EVM blockchain](/cctp/concepts/supported-chains-and-domains).
<Note>
The Aptos burn transaction uses a precompiled Move script. Download
`deposit_for_burn.mv` from the
[aptos-cctp repository](https://github.com/circlefin/aptos-cctp/tree/master/typescript/example/precompiled-move-scripts/testnet/v2)
and place it in a `precompiled-move-scripts` directory in your project.
</Note>
## Prerequisites
Before you begin this tutorial, ensure you have:
* Installed [Node.js v22.18+](https://nodejs.org/)
* Prepared an Aptos Testnet wallet with the private key available
* Funded your account with testnet APT from the
[Aptos Faucet](https://aptos.dev/en/network/faucet) for transaction fees
* Funded your account with Aptos Testnet USDC from the
[Circle Faucet](https://faucet.circle.com)
* Prepared an EVM wallet with the private key available for Arc Testnet
* Added the Arc Testnet network to your wallet
([network details](https://docs.arc.io/arc/references/connect-to-arc#wallet-setup))
* Funded your wallet with Arc Testnet ETH for gas fees
## Step 1: Set up the project
This step shows you how to prepare your project and environment.
### 1.1. Set up your development environment
Create a new directory, initialize and set up a new Node.js project, and install
the required dependencies:
```bash Shell theme={null}
mkdir cctp-aptos-to-arc
cd cctp-aptos-to-arc
npm init -y
npm pkg set type=module
npm pkg set scripts.start="node --env-file=.env --import=tsx index.ts"
npm install @aptos-labs/ts-sdk viem
npm install --save-dev tsx typescript @types/node
```
Copy
[`deposit_for_burn.mv`](https://github.com/circlefin/aptos-cctp/tree/master/typescript/example/precompiled-move-scripts/testnet/v2/deposit_for_burn.mv)
into `precompiled-move-scripts/` in your project. The full script in Step 4
reads the bytecode relative to `index.ts`.
### 1.2. Initialize and configure the project
<Tip>
This step is optional. It helps prevent missing types in your IDE or editor.
</Tip>
Create a `tsconfig.json` file:
```shell theme={null}
npx tsc --init
```
Then, update the `tsconfig.json` file:
```shell theme={null}
cat <<'EOF' > tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"types": ["node"]
}
}
EOF
```
### 1.3. Configure environment variables
Open `.env` in your editor and add:
```text theme={null}
APTOS_PRIVATE_KEY=YOUR_APTOS_PRIVATE_KEY
EVM_PRIVATE_KEY=YOUR_EVM_PRIVATE_KEY
```
* `APTOS_PRIVATE_KEY` is the private key for the Aptos Testnet account used to
sign the burn transaction.
* `EVM_PRIVATE_KEY` is the private key for the EVM wallet used to receive USDC
on Arc Testnet.
<Tip>
Open `.env` in your editor rather than writing values with shell commands, and
add `.env` to your `.gitignore`. This prevents credentials from leaking into
your shell history or version control.
</Tip>
The `npm run start` command loads variables from `.env` using Node.js native
env-file support.
<Warning>
This example uses one or more private keys for local testing. In production,
use a secure key management solution and never expose or share private keys.
</Warning>
## Step 2: Configure the script
Define the Aptos and Arc Testnet parameters, then configure the wallet clients
for each chain.
### 2.1. Define configuration constants
The script predefines the USDC address, CCTP contract address, transfer amount,
and CCTP domain IDs.
```ts TypeScript theme={null}
import {
Account,
AccountAddress,
Aptos,
AptosConfig,
Ed25519PrivateKey,
Network,
U32,
U64,
} from "@aptos-labs/ts-sdk";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import {
createPublicClient,
createWalletClient,
http,
parseAbi,
type Address,
type Hex,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { arcTestnet } from "viem/chains";
type Attestation = {
message: string;
attestation: string;
};
const APTOS_DOMAIN = 9; // Source domain ID for Aptos Testnet
const APTOS_USDC_ADDRESS =
"0x69091fbab5f7d635ee7ac5098cf0c1efbe31d68fec0f2cd565e8d168daf52832";
const BYTECODE_PATH = fileURLToPath(
new URL("./precompiled-move-scripts/deposit_for_burn.mv", import.meta.url),
);
const EVM_DESTINATION_DOMAIN = 26; // Arc Testnet
const MESSAGE_TRANSMITTER_V2_ADDRESS =
"0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275" as Address;
const AMOUNT = 1_000_000; // 1 USDC, 6 decimals
const MAX_FEE = 0;
const MIN_FINALITY_THRESHOLD = 2_000; // Standard Transfer
const IRIS_API_URL = "https://iris-api-sandbox.circle.com";
```
`APTOS_DOMAIN` is the source domain used when requesting the attestation. The
destination domain for Arc Testnet is 26. Aptos Testnet USDC uses 6 decimals, so
`1_000_000` represents 1 USDC.
### 2.2. Set up wallet clients
The EVM clients connect to Arc Testnet with `viem`. The Aptos client uses the
Aptos Testnet network and signs transactions with the Aptos account's Ed25519
private key.
```ts TypeScript theme={null}
const evmPrivateKey = process.env.EVM_PRIVATE_KEY;
const aptosPrivateKey = process.env.APTOS_PRIVATE_KEY;
if (!evmPrivateKey || !aptosPrivateKey) {
throw new Error("Set EVM_PRIVATE_KEY and APTOS_PRIVATE_KEY in .env");
}
const account = privateKeyToAccount(evmPrivateKey as Hex);
const walletClient = createWalletClient({
account,
chain: arcTestnet,
transport: http(),
});
const publicClient = createPublicClient({
chain: arcTestnet,
transport: http(),
});
const aptosClient = new Aptos(new AptosConfig({ network: Network.TESTNET }));
const aptosAccount = Account.fromPrivateKey({
privateKey: new Ed25519PrivateKey(aptosPrivateKey),
});
const messageTransmitterAbi = parseAbi([
"function receiveMessage(bytes message, bytes attestation) returns (bool success)",
]);
```
### 2.3. Format the Arc recipient for Aptos
The Aptos `deposit_for_burn.mv` script accepts the mint recipient as an Aptos
address. An EVM address is converted to a 32-byte Aptos address by left-padding
it with zeros.
```ts TypeScript theme={null}
function toAptosAddress(evmAddress: Address): AccountAddress {
return AccountAddress.from(`0x${evmAddress.slice(2).padStart(64, "0")}`);
}
```
## Step 3: Implement the transfer logic
This step implements the core transfer logic: burn on Aptos, poll for an
attestation, then mint on Arc. The precompiled Aptos script withdraws USDC from
the account's primary fungible store, so there is no separate approval
transaction.
### 3.1. Burn USDC on Aptos
Submit `deposit_for_burn.mv` to burn USDC on Aptos Testnet. The script submits
the following parameters:
* **Burn amount**: The amount of USDC to burn in Aptos subunits (6 decimals)
* **Destination domain**: The target blockchain for minting USDC (26 for Arc
Testnet)
* **Mint recipient**: The EVM wallet address that receives minted USDC on Arc,
represented as a 32-byte Aptos address
* **Destination caller**: The Aptos zero address, allowing any caller to submit
the receive transaction on Arc
* **Burn token**: The Aptos Testnet USDC address
* **Max fee**: The maximum [fee](/cctp/concepts/fees) allowed for the transfer
* **Finality threshold**: `2000` for a
[Standard Transfer](/cctp/concepts/finality-and-block-confirmations#standard-transfer-attestation-times)
```ts TypeScript theme={null}
async function burnUSDC() {
console.log(`Burning ${AMOUNT / 1_000_000} USDC on Aptos...`);
const bytecode = new Uint8Array(await readFile(BYTECODE_PATH));
const transaction = await aptosClient.transaction.build.simple({
sender: aptosAccount.accountAddress,
data: {
bytecode,
functionArguments: [
new U64(AMOUNT),
new U32(EVM_DESTINATION_DOMAIN),
toAptosAddress(account.address),
AccountAddress.from("0x0"),
AccountAddress.from(APTOS_USDC_ADDRESS),
new U64(MAX_FEE),
new U32(MIN_FINALITY_THRESHOLD),
],
},
});
const pendingTransaction = await aptosClient.signAndSubmitTransaction({
signer: aptosAccount,
transaction,
});
const depositForBurnTransaction = await aptosClient.waitForTransaction({
transactionHash: pendingTransaction.hash,
});
console.log(
`Deposit-for-burn confirmed: https://explorer.aptoslabs.com/txn/${depositForBurnTransaction.hash}`,
);
return depositForBurnTransaction;
}
```
### 3.2. Retrieve attestation
Retrieve the attestation required to complete the CCTP transfer by calling
Circle's attestation API.
* Call Circle's [`GET /v2/messages`](/api-reference/cctp/all/get-messages-v2)
API endpoint to retrieve the attestation.
* Pass `APTOS_DOMAIN` for the `sourceDomain` path parameter, using the
[CCTP domain](/cctp/concepts/supported-chains-and-domains#domain-identifiers)
for Aptos Testnet (9).
* Pass the Aptos transaction hash returned by `burnUSDC` as `transactionHash`.
```ts TypeScript theme={null}
async function retrieveAttestation(
transactionHash: string,
): Promise<Attestation> {
const url = `${IRIS_API_URL}/v2/messages/${APTOS_DOMAIN}?transactionHash=${transactionHash}`;
console.log(`Polling Iris V2 for attestation: ${url}`);
while (true) {
const response = await fetch(url);
if (!response.ok && response.status !== 404) {
throw new Error(`Iris V2 returned HTTP ${response.status}`);
}
if (response.ok) {
const data = (await response.json()) as {
messages?: Array<{ message?: string; attestation?: string }>;
};
const result = data.messages?.[0];
if (
result?.message &&
result.attestation &&
result.attestation !== "PENDING"
) {
return {
message: result.message,
attestation: result.attestation,
};
}
}
await new Promise((resolve) => setTimeout(resolve, 2_000));
}
}
```
### 3.3. Mint USDC on Arc
Call the
[`receiveMessage` function](/cctp/references/contract-interfaces#receivemessage)
from the [`MessageTransmitterV2` contract](/cctp/references/contract-addresses)
deployed on Arc Testnet.
* Pass the signed attestation and message bytes as parameters.
* The contract verifies the attestation and mints USDC to the recipient encoded
in the CCTP message.
```ts TypeScript theme={null}
async function mintUSDC({ message, attestation }: Attestation) {
console.log("Receiving the message on EVM...");
const receiveTransactionHash = await walletClient.writeContract({
account,
chain: arcTestnet,
address: MESSAGE_TRANSMITTER_V2_ADDRESS,
abi: messageTransmitterAbi,
functionName: "receiveMessage",
args: [message as Hex, attestation as Hex],
});
const receiveReceipt = await publicClient.waitForTransactionReceipt({
hash: receiveTransactionHash,
});
if (receiveReceipt.status !== "success") {
throw new Error(
`EVM receive transaction reverted: ${receiveTransactionHash}`,
프로젝트로 돌아가기