This guide demonstrates how to transfer USDC from Ethereum Sepolia to Arc testnet using CCTP. You use the viem framework to interact with CCTP contracts and the CCTP API to retrieve attestations.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.
Prerequisites
Before you begin, ensure that you’ve:- Installed Node.js v22+
- Prepared an EVM testnet wallet with the private key available
- Added Arc testnet network to your wallet (network details)
- Funded your wallet with the following testnet tokens:
- Sepolia ETH (native token) from a public faucet
- Sepolia USDC from the Circle Faucet
- Arc testnet USDC from the Circle Faucet if you
choose the direct mint path below, because the destination wallet must pay
gas to call
receiveMessage
Step 1. Set up the project
1.1. Create the project and install dependencies
1.2. Configure TypeScript (optional)
Create atsconfig.json file:
tsconfig.json file:
1.3. Set environment variables
Open.env in your editor and add:
PRIVATE_KEYis the private key for the Ethereum Sepolia EOA that signs the source-chain approval and burn transactions. The direct-mint path also uses the same key to submit the destination mint on Arc.
npm run start command loads variables from .env using Node.js native
env-file support.
Step 2: Configure the script
This section covers the necessary setup for the transfer script, including defining keys and addresses, and configuring the wallet client for interacting with the source and destination chains.2.1. Define configuration constants
The script predefines the contract addresses, transfer amount, and maximum fee. Update theDESTINATION_ADDRESS with your wallet address.
For simplicity, this quickstart uses the same EOA as the Ethereum Sepolia source
signer and the Arc recipient. In production, these can be different addresses.
TypeScript
2.2. Set up wallet clients
The wallet client configures the appropriate network settings usingviem. The
direct-mint path below uses clients for both Ethereum Sepolia and Arc testnet.
The Forwarding Service path only needs the
source-chain client on Ethereum Sepolia.
TypeScript
Step 3: Implement the transfer logic
The following sections outline the core transfer logic. The path diverges at the source-chain burn transaction:- Direct mint uses
depositForBurn, then retrieves an attestation and callsreceiveMessageon Arc. - Forwarding Service uses
depositForBurnWithHook, then lets Circle handle the destination-side mint on Arc.
- Forwarding Service
- Direct mint
3.1. Get forwarding fees and calculate the burn amount
Before you burn USDC with the Forwarding Service, query the CCTP fee endpoint withforward=true. The forwarding fee is dynamic, so fetch it immediately
before the transfer. The returned maxFee must cover both the CCTP protocol fee
and the forwarding fee.TypeScript
3.2. Approve the total burn amount
Approve the total amount you will burn on the source chain. For the forwarding path, that is the transfer amount plus the forwarding and protocol fees.TypeScript
3.3. Burn USDC with the Forwarding Service hook
UsedepositForBurnWithHook on the source chain. The forwarding hook data tells
Circle to handle the destination-side receiveMessage call on Arc.TypeScript
3.4. Verify the forwarded mint
After the burn is confirmed, poll the Iris API until it returns aforwardTxHash. That hash is the Arc destination mint transaction submitted by
Circle. In the forwarding path, forwardTxHash is the completion signal for the
destination-side mint. You do not need to retrieve an attestation and call
receiveMessage yourself.TypeScript
Step 4: Complete script
Create aindex.ts file in your project directory and populate it with the
complete code below for the path you want to test.
- Forwarding Service
- Direct mint
index.ts
Step 5: Test the script
Run the following command to execute the script:Shell
Rate limit: The attestation service rate limit is 35 requests per second. If
you exceed this limit, the service blocks all API requests for the next 5
minutes and returns an HTTP 429 (Too Many Requests) response.