CCIP, CCT, 범용 크로스체인
도입
CCTP는 native USDC burn-and-mint에 특화되어 있다. 반면 CCIP는 token, arbitrary message, token+message를 함께 다루는 범용 interoperability protocol이다. RWA token, vault share, governance action, merchant settlement callback처럼 "토큰 이동과 destination action"이 함께 필요할 때는 CCTP만으로는 충분하지 않을 수 있다.
범용 메시징은 강력하지만, 제품 상태도 복잡해진다. message가 도착했는가, token이 이동했는가, destination receiver가 성공했는가, product ledger가 닫혔는가를 분리해야 한다. 이 강의에서는 CCIP/CCT를 기능 소개가 아니라 운영 의존성 지도와 실패 처리 기준으로 읽는다.
학습 목표
- CCIP의 arbitrary messaging, token transfer, programmable token transfer를 구분한다.
- CCT token pool, rate limit, owner/admin 권한을 운영 리스크로 기록한다.
- message success와 product success를 분리해 상태머신을 설계한다.
- retry, manual execution, route disable, refund/settlement 정책을 작성한다.
개념 설명
범용 메시징과 토큰 전송 모델의 선택 기준을 세운다.
finality와 attestation 조건이 명확한가
범용 메시징의 위험을 설명했다.
1. CCIP는 message와 token을 함께 다룰 수 있다
| capability | 보내는 것 | receiving account | 제품 예시 | 완료 조건 |
|---|---|---|---|---|
| Arbitrary Messaging | data only | smart contract/program/module | compliance state sync, governance action | destination app execution |
| Token Transfer | token only | EOA 또는 contract 계열 | cross-chain asset movement | token delivery |
| Programmable Token Transfer | token + data | destination contract/program/module | tokenized payment + settlement action | token delivery + receiver execution |
Chainlink 문서 기준 EVM에서 arbitrary messaging과 programmable token transfer의 receiver는 smart contract여야 한다. 그래서 checkout 설계에서는 "merchant address로 token만 보낼 것인가"와 "merchant settlement contract를 호출할 것인가"를 분리해야 한다.
2. CCIP message lifecycle을 제품 상태로 바꾼다
이 흐름에서 product ledger는 CCIP message status를 그대로 Completed로 쓰면 안 된다. destination receiver가 revert했는지, token만 도착했는지, app-level receipt가 기록됐는지 확인해야 한다.
3. CCT는 token 운영 권한을 더 명확히 기록해야 한다
Cross-Chain Token(CCT)은 token developer가 CCIP 기반으로 여러 chain에서 token을 운영할 수 있게 하는 모델이다. token pool은 burn/mint, lock/mint, burn/unlock, lock/unlock 같은 방식을 구성할 수 있고 rate limit을 가질 수 있다.
| 권한/구성 | 질문 | 운영 증거 |
|---|---|---|
| Token owner | token contract upgrade와 ownership은 누가 갖는가 | multisig, timelock, owner address |
| Token administrator | token과 token pool mapping을 누가 관리하는가 | TokenAdminRegistry state |
| Token pool owner | pool pause/upgrade/rate limit 변경 권한은 누구에게 있는가 | pool config, event log |
| Rate limit | lane별 최대 capacity와 refill rate는 얼마인가 | CCIP Directory 또는 pool config |
| Token handling | burn/mint인지 lock/mint인지 | supply reconciliation rule |
| Lane support | source/destination pair가 production 지원인가 | CCIP Directory, release note |
스테이블코인이나 RWA token에서 이 권한표가 없으면 chain 추가가 보안 리뷰를 통과할 수 없다. 특히 token pool pause, rate limit exhaustion, destination receiver revert는 사용자 결제 상태에 직접 영향을 준다.
코드로 확인하기
위 신뢰 모델을 코드와 운영 규칙으로 확인한다. 메시지 상태, 최종성, 재시도, 관측 지점이 어디서 분리되는지 보는 것이 목적이다.
컨트랙트CCIP Router 인터페이스 — ccipSend
Chainlink CCIP는 router 컨트랙트로 메시지를 보내고, destination chain에서 receiver가 받는다. 토큰 전송과 임의 메시지를 한 호출에 묶을 수 있다.
struct EVMTokenAmount { address token; uint256 amount;}struct EVM2AnyMessage { bytes receiver; // destination 주소 (abi.encode 된 형태) bytes data; // 임의 페이로드 EVMTokenAmount[] tokenAmounts; address feeToken; // address(0) = native, 또는 LINK bytes extraArgs; // EVMExtraArgsV2 (gasLimit, allowOutOfOrderExecution)}interface IRouterClient { function getFee(uint64 destinationChainSelector, EVM2AnyMessage calldata message) external view returns (uint256); function ccipSend(uint64 destinationChainSelector, EVM2AnyMessage calldata message) external payable returns (bytes32 messageId);}contract CcipSender { IRouterClient public router; function sendUsdcWithMessage( uint64 destChain, address recipient, address usdc, uint256 amount, bytes calldata orderPayload ) external returns (bytes32 messageId) { EVMTokenAmount[] memory tokenAmounts = new EVMTokenAmount[](1); tokenAmounts[0] = EVMTokenAmount({ token: usdc, amount: amount }); EVM2AnyMessage memory message = EVM2AnyMessage({ receiver: abi.encode(recipient), data: orderPayload, tokenAmounts: tokenAmounts, feeToken: address(0), extraArgs: abi.encodePacked(uint16(0x181d), uint256(200_000)) // V1 ExtraArgs gasLimit }); IERC20(usdc).approve(address(router), amount); uint256 fee = router.getFee(destChain, message); messageId = router.ccipSend{value: fee}(destChain, message); }}컨트랙트CCIPReceiver — destination 측 수신
destination 컨트랙트는
CCIPReceiver를 상속하고_ccipReceive를 구현한다. router 호출만 허용하도록 modifier로 보호.
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";contract MerchantReceiver is CCIPReceiver { event OrderReceived(bytes32 indexed messageId, uint64 sourceChain, address sender, uint256 amount, bytes payload); constructor(address router) CCIPReceiver(router) {} function _ccipReceive(Client.Any2EVMMessage memory message) internal override { address sender = abi.decode(message.sender, (address)); uint256 received = message.destTokenAmounts.length > 0 ? message.destTokenAmounts[0].amount : 0; // 핵심: receiver 측 비즈니스 로직 — revert 가능 // revert 시 manual execution 으로 재실행 가능 require(received > 0, "no token received"); emit OrderReceived(message.messageId, message.sourceChainSelector, sender, received, message.data); }}인덱서CCIP message → product status 매핑
CCIP Explorer API 상태와 자체 ledger를 매시간 동기화. failed/blessed/executed를 product 상태로 변환.
type CcipMessageState = "untouched" | "in-progress" | "blessed" | "executed" | "failed";type ProductOrderState = | "Submitted" | "InTransit" | "AwaitingDestination" | "DeliveredOnDestination" | "ReceiverReverted" | "ManualExecutionPending";export function ccipToProduct(state: CcipMessageState, destReceived: boolean, receiverReverted: boolean): ProductOrderState { if (state === "executed" && destReceived && !receiverReverted) return "DeliveredOnDestination"; if (state === "executed" && receiverReverted) return "ReceiverReverted"; if (state === "failed") return "ManualExecutionPending"; if (state === "blessed") return "AwaitingDestination"; if (state === "in-progress") return "InTransit"; return "Submitted";}강의 포인트
| 관점 | 수업 중 확인할 질문 | 산출물 |
|---|---|---|
| capability | message, token, token+message 중 무엇을 쓰는가 | capability decision table |
| dependency | DON/RMN, router, token pool, receiver, explorer를 추적하는가 | dependency map |
| 권한 | token owner/admin/pool owner/rate limit admin이 누구인가 | CCT authority table |
| 실패 처리 | destination execution 실패를 어떻게 복구하는가 | manual execution runbook |
| reconciliation | CCIP Explorer와 자체 ledger를 어떻게 맞추는가 | reconciliation dashboard spec |
실무 예시
백엔드CCTP vs CCIP/CCT vs ERC-7683
| 모델 | 핵심 | 좋은 경우 | 조심할 위험 |
|---|---|---|---|
| CCTP | USDC source burn, destination mint | native USDC 이동 | attestation, finality, supported domain |
| CCIP/CCT | token/message transfer protocol | RWA, vault token, token+destination action | DON/RMN, token pool, lane support, destination gas |
| ERC-7683 intents | user order를 solver/filler가 실행 | UX abstraction, solver competition | resolver trust, solver failure, settlement dispute |
운영[CONTRACT] Programmable token transfer 실패 처리
| 상태 | 의미 | 사용자 표시 | 운영 행동 |
|---|---|---|---|
SourceSent | source router에 message 제출 | Processing | message ID 저장 |
Committed | CCIP network가 message를 관찰/commit | Processing | CCIP Explorer와 자체 indexer 확인 |
TokenDelivered | destination token 이동 성공 | Processing 또는 PaidSoft | receiver execution 여부 확인 |
ReceiverReverted | destination app call 실패 | Delayed | manual execution 또는 compensation 검토 |
ManualExecutionReady | 재실행 가능 | Under review | gas limit, receiver patch, operator action |
ProductSettled | ledger와 merchant state 완료 | Completed | reconciliation close |
흔한 오해와 실패 시나리오
| 오해 | 실제 기준 |
|---|---|
| CCIP message success면 제품도 성공이다 | product ledger와 receiver execution까지 확인해야 한다 |
| token transfer와 message delivery는 항상 같이 성공한다 | programmable transfer에서는 부분 성공/실패 상태를 설계해야 한다 |
| CCT는 token bridge를 자동으로 안전하게 만든다 | token owner, pool owner, rate limit, pause/upgrade 권한 검수가 필요하다 |
| retry는 그냥 다시 보내면 된다 | replay, duplicate claim, idempotency key, receiver side effect를 확인해야 한다 |
| explorer를 보면 reconciliation이 끝난다 | CCIP Explorer, contract event, product DB를 모두 맞춰야 한다 |
실습 과제
- 운영CCIP dependency map 작성: source router, destination router, DON/RMN, token pool, receiver, rate limit, lane support, CCIP Explorer, 자체 indexer를 하나의 dependency map으로 정리한다.
- 컨트랙트[OPS] Programmable token transfer runbook 작성: token transfer는 성공했지만 destination receiver execution이 실패한 경우의 상태, retry/manual execution, refund/settlement 정책을 작성한다.
- 컨트랙트CCT token 운영 권한표 만들기: token owner, token administrator, token pool owner, rate limit admin, pause/upgrade 권한과 multisig/timelock 요구사항을 표로 만든다.
완료 기준
- CCIP capability별 완료 조건을 구분했다.
- CCT token 운영 권한과 rate limit을 release checklist에 넣었다.
- destination receiver 실패와 manual execution 대응을 runbook으로 작성했다.
- CCIP Explorer와 자체 ledger를 reconcile하는 증거 목록을 만들었다.
근거 자료
- CCIP CCT 범용 크로스체인: 03-크로스체인-L2/08-CCIP-CCT-범용-크로스체인.md
- Chainlink CCIP Documentation: https://docs.chain.link/ccip
- Chainlink Cross-Chain Token Overview: https://docs.chain.link/ccip/concepts/cross-chain-token/overview
- CCIP Manual Execution: https://docs.chain.link/ccip/concepts/manual-execution
- CCIP Execution Latency: https://docs.chain.link/ccip/ccip-execution-latency
- CCIP Explorer: https://ccip.chain.link/