Cross-Chain Transfer Protocol (CCTP) là giao thức chuyển giao chuỗi chéo do Circle phát triển nhằm di chuyển USDC gốc (Native USDC) giữa các mạng lưới blockchain khác nhau một cách an toàn, chi phí thấp và tối ưu hóa tính thanh khoản. Bài này chúng ta tìm hiểu kỹ hơn về góc độ kỹ thuật, cách Dev thực hiện chuyển chéo chuỗi trực tiếp mà không cần qua bên trung gian thứ ba.
Mục lục
Mô hình dịch chuyển của CCTP: Burn & Mint
Vấn đề của các Bridge truyền thống
Trước khi CCTP xuất hiện, người dùng muốn chuyển USDC từ blockchain A sang blockchain B thường phải dùng các cầu nối (bridge) bên thứ ba theo mô hình Lock & Mint:
- Cơ chế: Khóa USDC gốc trên chain A → Phát hành một phiên bản USDC bọc (Wrapped USDC như
wUSDC,axlUSDC,…) trên chain B. - Hạn chế:
- Phân mảnh thanh khoản: Xuất hiện nhiều bản sao USDC không đồng nhất giữa các chain.
- Rủi ro an ninh cao: Các hợp đồng thông minh lưu giữ tài sản bị khóa (Lock Box) thường là mục tiêu hàng đầu của các vụ hack cross-chain.
Mô hình dịch chuyển của CCTP: Burn & Mint
Khác với phương pháp truyền thống, CCTP sử dụng cơ chế Burn & Mint trực tiếp từ cấp độ giao thức của Circle:

USDC (Source Chain) → Circle Attestation} \xrightarrow{\text{Mint}} \text{USDC (Destination Chain)}$$
Quy trình 3 bước chính:
- Đốt token (Burn): Người dùng yêu cầu chuyển USDC qua ứng dụng (DApp/Ví). CCTP gọi hợp đồng thông minh trên chuỗi nguồn (Source Chain) để tiêu hủy (burn) đúng số lượng USDC đó.
- Xác thực (Attestation): Circle giám sát sự kiện đốt tiền trên chuỗi nguồn và phát hành một chứng thực ký số (attestation) xác nhận giao dịch hợp lệ.
- Phát hành (Mint): Chứng thực được gửi tới chuỗi đích (Destination Chain). Hợp đồng CCTP trên chuỗi đích kiểm tra chứng thực và in mới (mint) đúng số lượng USDC gốc vào ví người nhận.
CCTP vận hành dựa trên hai Smart Contract chính do Circle triển khai trên mỗi blockchain hỗ trợ:
- TokenMessenger: Tiếp nhận yêu cầu chuyển tiền từ ứng dụng/ví, kích hoạt lệnh tiêu hủy token và tương tác với chuỗi đích để mint lại USDC.
- MessageTransmitter: Đóng vai trò là lớp truyền tin nhắn chung (Generic Messaging Layer), gửi thông điệp cross-chain từ chuỗi nguồn và tiếp nhận thông điệp ở chuỗi đích.
Ưu điểm và ứng dụng thực tế của CCTP
Ưu điểm nổi bật của CCTP
- Thanh khoản thống nhất: Không cần tạo ra các phiên bản Wrapped USDC. Người dùng luôn nhận được USDC gốc 1:1 trên mọi chuỗi.
- An toàn & An ninh cao: Loại bỏ hoàn toàn rủi ro bị rút ruột khỏi bể khóa tài sản của các cầu nối.
- Tối ưu chi phí: Không yêu cầu phí trượt giá hay phí thanh khoản cho các nhà cung cấp LP.
- Tính mở rộng cho nhà phát triển: CCTP v2 hỗ trợ tính năng Hooks, cho phép tự động hóa các thao tác tiếp theo sau khi mint (ví dụ: chuyển đổi tài sản, nạp tiền vào vị thế cho vay/sàn DEX ngay lập tức chỉ trong một giao dịch đơn lẻ).
Ứng dụng thực tế: CCTP được tích hợp vào các ví (MetaMask, OKX Wallet), sàn DEX và các giao thức hạ tầng cross-chain (LayerZero, Wormhole, Chainlink CCIP) để phục vụ:
- Cross-chain Swaps: Đổi ETH (trên Ethereum) lấy ARB (trên Arbitrum) thông qua USDC làm đồng tiền trung gian một cách mượt mà.
- Giao dịch DeFi xuyên chuỗi: Dùng USDC từ Solana để thế chấp nạp tiền vào sàn giao dịch hợp đồng tương lai trên Arbitrum mà không cần thao tác cầu nối thủ công.
So sánh CCTP V1 và CCTP V2
Hai phiên bản chính của Circle CCTP bao gồm CCTP V1 (Legacy) và CCTP V2 (Canonical CCTP). Tháng 3/2025, Circle chính thức ra mắt CCTP V2, tích hợp nhiều cải tiến lớn về tốc độ, tính năng và trải nghiệm lập trình (DevEx). Circle đã nâng V2 thành phiên bản chuẩn (Canonical CCTP) và lên kế hoạch ngưng hỗ trợ V1. Chi tiết xem bài viết: CCTP V1 deprecation: CCTP V2 is now the canonical CCTP
| Đặc tính / Tính năng | CCTP V1 (Legacy) | CCTP V2 (Canonical) |
| Trạng thái | Phiên bản cũ (đang rút dần hỗ trợ) | Phiên bản chính thức chuẩn hiện tại |
| Tốc độ chuyển (Transfer Speed) | Standard Transfer: Bắt buộc chờ chuỗi nguồn đạt Block Finality đầy đủ (thường mất từ vài phút đến hơn chục phút tùy chain). | Hỗ trợ 2 chế độ: • Standard Transfer như V1 • Fast Transfer: Xác thực siêu tốc ngay khi giao dịch được đưa vào block (vượt mức finality), hỗ trợ nhu cầu tức thì. |
| Lập trình hậu xử lý (Hooks) | Không hỗ trợ. USDC chỉ đơn thuần cập bến ví/mạng lưới đích. | Hỗ trợ Hooks: Cho phép kích hoạt tự động các logic Smart Contract trên chuỗi đích ngay sau khi mint. |
| Quy trình API (Off-chain API) | Phức tạp hơn: Dev phải trích xuất message byte từ logs giao dịch on-chain, sau đó gọi /v1/attestations/{messageHash}. | Tối ưu hóa: Gọi API /v2/messages/{sourceDomainId} bằng ngay txHash là nhận về cả message lẫn chứng thực. |
| Hạn mức giao dịch | Tối đa 1 triệu USD / giao dịch. | Nâng lên tối đa 10 triệu USD / giao dịch. |
| Nâng cấp Smart Contract | Cố định, khó nâng cấp tính năng mới. | Khả năng tự nâng cấp, xử lý lỗi và retry tự động tốt hơn. |
Khuyến nghị cho Lập trình viên:
- Nếu bạn đang xây dựng ứng dụng mới: Bắt buộc sử dụng các hợp đồng và API của CCTP V2.
- Nếu bạn đang chạy ứng dụng dùng CCTP V1: Circle đã chính thức thông báo lộ trình ngừng hỗ trợ V1 (Deprecation). Bạn nên cập nhật lại địa chỉ hợp đồng thông minh, nâng cấp ABI (đặc biệt là các tham số mới của hàm
depositForBurn) và chuyển hướng endpoint API từ/v1/sang/v2/.
Hướng dẫn chuyển USDC giữa nhiều chain dựa trên CCTP V1
Cách Dev làm việc với CCTP V1 (Legacy)
Do V2 đã ra mắt và V1 dần dần sẽ không được hỗ trợ, tôi không khuyến khích sử dụng CCTP V1. Ở đây tôi chỉ nói về mô hình phát triển chung với CCTP V1 chứ không đi sâu:
- Gọi Smart Contract chuỗi nguồn: Dev tương tác với hàm depositForBurn() trên TokenMessengerV1 để hủy token.
- Lắng nghe Event: Bắt sự kiện MessageSent trên blockchain nguồn để trích xuất mảng byte message.
- Lấy Attestation: Gửi mảng byte này lên Circle API v1 (/v1/attestations/…) và đợi Circle trả về chữ ký xác thực.
- Gọi Smart Contract chuỗi đích: Đưa message và attestation vào hàm receiveMessage() trên MessageTransmitterV1 để mint USDC vào ví người nhận.
Một số thông tin cho cần cho Dev khi làm việc với giao thức CCTP V1:
- Tổng quan & Hướng dẫn CCTP: Circle CCTP Overview & Getting Started
- Tài liệu chuyển đổi CCTP V1 sang V2: Circle CCTP Migration Guide
- Địa chỉ Contract V1 (Legacy Protocol): CCTP V1 EVM Smart Contracts Reference
- CCTP V1 Attestation API (Legacy):
- Mainnet:
https://iris-api.circle.com/v1/attestations/){messageHash - Testnet (Sandbox):
https://iris-api-sandbox.circle.com/v1/attestations/){messageHash}
- Mainnet:
Hướng dẫn viết code tương tác với CCTP V1
Vì Circle đã triển khai phiên bản V2 một cách đầy đủ và hoàn thiện, còn phiên bản V1 dần không còn sử dụng nữa. Nên việc tìm hiểu viết code tương tác với CCTP V1 là điều không cần thiết và vô nghĩa.
Hướng dẫn chuyển USDC giữa nhiều chain dựa trên CCTP V2
Cách Dev làm việc với CCTP V2 (Canonical CCTP)
Với CCTP V2, trải nghiệm lập trình đã được nâng cấp rõ rệt:
- Cấu hình chi tiết giao dịch (Smart Contract):
- Hàm depositForBurn() trên V2 nhận thêm tham số finalityThreshold để chọn tốc độ chuyển đổi (Fast hoặc Standard).
- Truyền thêm dữ liệu byte vào tham số
hookData(Hooks). Ví dụ:
Gửi lệnh “Sau khi mint USDC xong, tự động swap sang ETH trên Uniswap rồi nạp vào Aave”.
- Lấy Attestation qua API tinh gọn:
- Không cần đọc log thủ công, Dev chỉ cần truyền Hash của giao dịch đốt tiền (txHash) lên API endpoint mới:
https://iris-api.circle.com/v2/messages/{sourceDomainId}?transactionHash={txHash} - Hệ thống trả về cả thông điệp đã mã hóa và chứng thực cùng một lúc.
- Không cần đọc log thủ công, Dev chỉ cần truyền Hash của giao dịch đốt tiền (txHash) lên API endpoint mới:
- Tích hợp nhanh bằng SDK/Bridge Kit:
- Circle phát triển SDK mới (Bridge Kit) cho phép các ứng dụng DApp/ví tích hợp luồng chuyển tiền cross-chain của V2 chỉ với dưới 10 dòng code.
Thông tin cần thiết cho Dev khi làm việc với CCTP V2:
- Công cụ Bridge Kit (SDK V2): Circle Bridge Kit Documentation
- Địa chỉ Contract V2 (Canonical CCTP – All Chains): CCTP V2 Contract Addresses
- CCTP V2 Attestation API:
- Mainnet:
https://iris-api.circle.com/v2/messages/{sourceDomainId}?transactionHash={txHash} - Testnet (Sandbox):
https://iris-api-sandbox.circle.com/v2/messages/{sourceDomainId}?transactionHash={txHash}
- Mainnet:
Hướng dẫn viết code tương tác với CCTP V2
Bạn có thể sử dụng Circle Bridge Kit do Circle cung cấp, đây là SDK chính thức mới giúp gói gọn toàn bộ quy trình chỉ trong vài dòng code JavaScript/TypeScript. Nhưng tôi sẽ sử dụng giải pháp tương tác trực tiếp để có thể kiểm soát sâu hơn vào logic trong tương lai. Luồng xử lý của chúng ta sẽ như sau:

Chúng ta sẽ sử dụng testnet trong quá trình phát triển để tránh trường hợp bị mất tiền một cách oan uổng không cần thiết.
Mã nguồn code demo như sau (Để trong tệp index.js):
require('dotenv').config();
const ethers = require('ethers');
const axios = require('axios').default;
// https://developers.circle.com/cctp/references/contract-addresses
const chainInfos = {
ALL: {
TESTNET: {
tokenMessengerV2: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
messageTransmitterV2: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275"
},
MAINNET: {
tokenMessengerV2: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
messageTransmitterV2: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64"
}
},
SEPOLIA: {
domain: 0,
USDC: "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
rpc: "https://ethereum-sepolia-rpc.publicnode.com",
},
BASE_SEPOLIA: {
domain: 6,
USDC: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
rpc: "https://base-sepolia-rpc.publicnode.com"
}
};
const USDC_DECIMALS = 6;
// ABI V2
const ERC20_ABI = [
"function allowance(address owner, address spender) external view returns (uint256)",
"function approve(address spender, uint256 amount) returns (bool)"
];
const TOKEN_MESSENGER_V2_ABI = [
`function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 finalityThreshold) returns (uint64 nonce)`
];
const MESSAGE_TRANSMITTER_V2_ABI = [
"function receiveMessage(bytes message, bytes attestation) returns (bool success)"
];
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function getTokenMessengerV2(type, network) {
if (chainInfos[network] && chainInfos[network].tokenMessengerV2) return chainInfos[network].tokenMessengerV2;
if (type == "MAINNET") {
return chainInfos.ALL.MAINNET.tokenMessengerV2;
}
return chainInfos.ALL.TESTNET.tokenMessengerV2;
}
function getMessageTransmitterV2(type, network) {
if (chainInfos[network] && chainInfos[network].messageTransmitterV2) return chainInfos[network].messageTransmitterV2;
if (type == "MAINNET") {
return chainInfos.ALL.MAINNET.messageTransmitterV2;
}
return chainInfos.ALL.TESTNET.messageTransmitterV2;
}
async function burnUSDC(type, srcNetwork, dstNetwork, amount, recipientAddress) {
// Init variables
const botAddr = process.env.BOT_ADDR;
const provider = new ethers.JsonRpcProvider(chainInfos[srcNetwork].rpc);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const usdcAddr = chainInfos[srcNetwork].USDC;
const messengerAddr = getTokenMessengerV2(type, srcNetwork);
// Init contracts
const usdc = new ethers.Contract(usdcAddr, ERC20_ABI, signer);
const messenger = new ethers.Contract(messengerAddr, TOKEN_MESSENGER_V2_ABI, signer);
// Call approve() function to allow tokenMessengerV2 using USDC
const amountBn = ethers.parseUnits(amount.toFixed(6), USDC_DECIMALS);
const allowance = await usdc.allowance(botAddr, messengerAddr);
console.log(new Date(), "Allowance:", allowance);
if (allowance < amountBn) {
console.log(new Date(), "Approving USDC: Processing...");
const approveTx = await usdc.approve(messengerAddr, amountBn - allowance);
let receipt = await approveTx.wait();
console.log(new Date(), `Approving USDC: DONE with tx ${receipt.hash}`);
}
// Call depositForBurn() to burn USDC
const finalityThreshold = 0; // 0 = Fast Transfer, 1 = Standard Finality
const maxFee = "500";
let recipient = ethers.zeroPadValue(botAddr, 32);
const burnTx = await messenger.depositForBurn(
amountBn,
chainInfos[dstNetwork].domain,
recipient,
usdcAddr,
ethers.ZeroHash,
maxFee,
finalityThreshold
);
let receipt = await burnTx.wait();
console.log(new Date(), `Burn USDC: SUCCESS with tx ${receipt.hash}`);
return receipt.hash;
}
// https://developers.circle.com/api-reference/cctp/all/reattest-message
async function fetchAttestation(type, srcNetwork, transactionHash) {
// Endpoint CCTP V2
let url = null;
let srcDomainId = chainInfos[srcNetwork].domain;
if (type=="MAINNET") {
url = `https://iris-api.circle.com/v2/messages/${srcDomainId}?transactionHash=${transactionHash}`;
} else {
url = `https://iris-api-sandbox.circle.com/v2/messages/${srcDomainId}?transactionHash=${transactionHash}`;
}
// Get attestation
console.log(new Date(), "Waiting for verification from Circle V2 API...");
while (true) {
try {
const resp = await axios.get(url);
if (resp.status==200) {
const data = resp.data;
// console.log("DATA", data);
if (data.messages && data.messages.length > 0) {
const messageObj = data.messages[0];
console.log(new Date(), "Verification status:", messageObj.status);
if (messageObj.status === "complete") {
console.log(new Date(), "Verification from Circle V2 API: DONE");
return {
messageBytes: messageObj.message,
attestation: messageObj.attestation
};
}
}
}
} catch (error) {
console.error(new Date(), "Error to get verification from Circle V2 API", error);
}
// Wait 60 seconds and try again
await delay(60000);
}
}
async function mintUSDC(type, dstNetwork, attestation, messageBytes) {
const destProvider = new ethers.JsonRpcProvider(chainInfos[dstNetwork].rpc);
const destSigner = new ethers.Wallet(process.env.PRIVATE_KEY, destProvider);
const msgTransmitterV2 = getMessageTransmitterV2(type, dstNetwork);
const transmitter = new ethers.Contract(msgTransmitterV2, MESSAGE_TRANSMITTER_V2_ABI, destSigner);
console.log(new Date(), "Minting USDC on the destination chain...");
const mintTx = await transmitter.receiveMessage(messageBytes, attestation);
const receipt = await mintTx.wait();
console.log(new Date(), `Minting USDC on the destination chain: SUCCESS! Tx Hash: ${receipt.hash}`);
}
async function main() {
let type = "TESTNET";
let srcNetwork = "SEPOLIA";
let dstNetwork = "BASE_SEPOLIA";
let amount = 10;
let startTime = Date.now();
let txHash = await burnUSDC(type, srcNetwork, dstNetwork, amount);
let msg = await fetchAttestation(type, srcNetwork, txHash);
await mintUSDC(type, dstNetwork, msg.attestation, msg.messageBytes);
let endTime = Date.now();
let diffTime = (endTime - startTime)/(60*1000);
console.log(new Date(), `Total time: ${diffTime.toFixed(2)} minutes!!!`);
}
main();
Để chạy bạn cần đánh lệnh sau để cài đặt thư viện cần thiết:
npm i dotenv axios ethers
Tạo tệp .env:
BOT_ADDR = <Địa chỉ account test>
PRIVATE_KEY = <Private Key của account test>
Bơm ít ETH cho hai account này trên 2 mạng lưới testnet: Ethereum Sepolia và Base Sepolia. Sau đó vào trang https://faucet.circle.com, để faucet một ít USDC trên mạng lưới Ethereum Sepolia.
Sau đó đánh lệnh sau để chạy thử:
node index.js
Đây là kết quả hiển thị trên console của mã nguồn demo trên:

Bạn có thể kiểm tra 2 giao dịch trên Blockchain Explorer:
- Giao dịch “Burn USDC”: 0x7c3802559334ff3085710ba572fa7537d1f96780dcc0220bbbac53016fcb0356
- Giao dịch “Mint USDC”: 0x2bd77b8974d5fcbfce46352cdb076fc6d89aaf46d589fc8d343d9dfc45cba756
Trong code demo trên, tôi sử dụng hàm depositForBurn(), bạn có thể sử dụng hàm depositForBurnWithHook() để có thể thực hiện thêm các tác vụ khác ở chain đích.
TokenMinterV2 và MessageV2 để làm gì?
Khi lập trình viên viết code tương tác với CCTP, chúng ta hầu như chỉ làm việc trực tiếp với 2 hợp đồng TokenMessenger và MessageTransmitter. Tuy nhiên, ở kiến trúc bên dưới, kiến trúc CCTP của Circle được thiết kế theo dạng phân tách trách nhiệm. Hai hợp đồng TokenMinter và Message đóng vai trò là lớp quản trị tài sản và định dạng dữ liệu.
Chi tiết vai trò của 2 hợp đồng này:
TokenMinterV2 (Hoạt động ở Tầng Quản Lý Tài Sản & Phân Quyền)
Nếu TokenMessenger là “nhân viên thu ngân” giao tiếp với người dùng, thì TokenMinter chính là “kho tiền” có chìa khóa để in/hủy tiền thực sự.
- Lý do tồn tại: Để đảm bảo an toàn tuyệt đối cho hợp đồng USDC gốc. Hợp đồng USDC gốc (MasterMinter) chỉ cấp quyền in tiền (Minter Role) cho duy nhất hợp đồng TokenMinter. TokenMessenger không có quyền trực tiếp ra lệnh in USDC.
- Chức năng chính của TokenMinterV2:
- Thực thi Mint/Burn thực tế: Khi TokenMessengerV2.depositForBurn() được gọi, nó sẽ ủy quyền sang TokenMinterV2 để thực hiện thao tác tiêu hủy USDC. Tương tự, ở chuỗi đích, MessageTransmitter sau khi xác minh xong sẽ gọi TokenMinterV2 để mint USDC mới ra.
- Quản lý Hạn mức: Quản lý giới hạn tối đa số lượng USDC có thể mint/burn trong một khoảng thời gian để tránh trường hợp nếu CCTP bị tấn công thì hệ thống cũng không bị rút cạn tiền.
- Quản lý danh sách Local Tokens: TokenMinter lưu giữ mapping giữa các token USDC gốc trên các chuỗi khác nhau.
MessageV2 (Lớp Định Dạng & Mã Hóa Thông Điệp)
MessageV2 không phải là một hợp đồng thông minh độc lập chạy giao dịch, mà nó đóng vai trò là một Thư viện mã hóa/giải mã.
- Lý do tồn tại: Để gửi dữ liệu an toàn giữa các blockchain khác nhau (vốn có cấu trúc dữ liệu khác nhau), mọi thông tin như: ai gửi, ai nhận, số lượng bao nhiêu, domain nào, hookData là gì… phải được đóng gói thành một mảng byte chuẩn hóa (Raw Byte Array).
- Chức năng chính của MessageV2:
- Đóng gói: Chuyển đổi các tham số input từ TokenMessengerV2 thành dạng chuỗi Byte theo đúng cấu trúc chuẩn của Circle V2.
- Trích xuất: Khi chuỗi đích nhận được một chuỗi byte từ Circle API (messageBytes), thư viện MessageV2 sẽ “bẻ” chuỗi byte đó ra lại thành các thông tin ban đầu:
- Format V2 bổ sung thêm các trường mới như: destinationCaller, finalityThreshold, hookData… mà V1 không có.
- Tạo Hash: Tính toán mã Hash của thông điệp để Circle Attestation Service dùng làm chỉ mục ký xác thực.
Tóm tắt Luồng Tương Tác Giữa 4 Smart Contracts
Sơ đồ dưới đây minh họa cách 4 hợp đồng này phối hợp với nhau bên dưới hậu trường:
* Luồng thực hiện burn ở chuỗi nguồn:

* Luồng thực hiện mint ở chuỗi đích:

Mình là lập trình viên, mặc dù cũng đã khá lớn tuổi nhưng vẫn thích Lập trình. Gần đây mình tập trung tìm hiểu nhiều hơn về Lĩnh vực Blockchain. Với kiến thức tìm hiểu được, mình muốn viết ra để lưu lại cũng như để chia sẻ cho những người quan tâm. Mong mọi người góp ý và có thể cùng mình chia sẻ nhiều kiến thức hơn cho cộng đồng.







Trả lời