Circle USYC 백서

Circle USYC

원문

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 75쪽 중 33쪽

`1000` for `minFinalityThreshold` to use a fast transfer. For standard finality, use `2000` instead and omit `PRE_FINALITY` from the quote—see [Transfer finality and fast burn allowance](/cctp/expanded-assets/concepts/architecture#transfer-finality-and-fast-burn-allowance). On EVM, `destinationAddress` must be the raw packed 20-byte address. Do not ABI-encode it to 32 bytes. ```typescript TypeScript theme={null} const transferAbi = [ { name: "crossChainTransfer", type: "function", stateMutability: "payable", inputs: [ { name: "tokenId", type: "bytes32" }, { name: "amount", type: "uint256" }, { name: "destinationDomain", type: "uint32" }, { name: "destinationAddress", type: "bytes" }, { name: "destinationCaller", type: "bytes32" }, { name: "minFinalityThreshold", type: "uint32" }, { name: "claim", type: "tuple", components: [ { name: "signedQuote", type: "bytes" }, { name: "refundAddress", type: "address" }, ], }, { name: "autoExecuteHookData", type: "bool" }, { name: "hookData", type: "bytes" }, ], outputs: [], }, ] as const; const destinationAddress = encodePacked(["address"], [recipientOnArc]); const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000" as const; const claim = { signedQuote: quote.signedQuote, refundAddress: zeroAddress, }; const feeTotalAmount = BigInt(quote.feeTotalAmount); const transferHash = await sepoliaWallet.writeContract({ address: SEPOLIA_CCTS, abi: transferAbi, functionName: "crossChainTransfer", args: [ tokenId, transferAmount, ARC_DOMAIN, destinationAddress, ZERO_BYTES32, 1000, claim, false, "0x", ], value: feeTotalAmount, }); console.log(`Source transaction: ${transferHash}`); ``` ## Step 8: Complete the transfer on Arc Testnet Choose the completion path that matches the fee quote you requested in Step 6. <Tabs> <Tab title="Forwarded mint"> When the quote includes `FORWARD`, Circle submits the destination mint. Resolve the token on Arc Testnet and wait until the recipient balance increases. ```typescript TypeScript theme={null} const destinationTokenAddress = await arcPublic.readContract({ address: ARC_CCTS, abi: serviceAbi, functionName: "resolveTokenAddress", args: [tokenId], }); const startingBalance = await arcPublic.readContract({ address: destinationTokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [recipientOnArc], }); async function waitForDestinationBalance( maxAttempts: number = 60, ): Promise<void> { for (let attempt = 0; attempt < maxAttempts; attempt++) { const balance = await arcPublic.readContract({ address: destinationTokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [recipientOnArc], }); if (balance >= startingBalance + transferAmount) { console.log( `Mint complete on Arc Testnet. Destination balance: ${balance}`, ); return; } await new Promise((r) => setTimeout(r, 5000)); } throw new Error("Forwarded mint did not complete in time"); } await waitForDestinationBalance(); ``` </Tab> <Tab title="Direct mint"> Poll the Iris API until the attestation is ready, then submit `receiveMessage` to the Arc Testnet `MessageTransmitterV2`. For the contract address and ABI, see [Contract Addresses](/cctp/expanded-assets/references/contract-addresses) and the [CCTP Contract Interfaces](/cctp/references/contract-interfaces). ```typescript TypeScript theme={null} type IrisMessage = { status: "pending_confirmations" | "complete"; attestation: `0x${string}`; message: `0x${string}`; }; type IrisResponse = { messages: IrisMessage[] }; async function pollAttestation( sourceDomain: number, txHash: `0x${string}`, maxAttempts: number = 60, ): Promise<IrisMessage> { const url = `${IRIS_BASE}/v2/messages/${sourceDomain}?transactionHash=${txHash}`; for (let attempt = 0; attempt < maxAttempts; attempt++) { const res = await fetch(url); if (res.ok) { const body = (await res.json()) as IrisResponse; const msg = body.messages?.[0]; if (msg && msg.status === "complete") { return msg; } } await new Promise((r) => setTimeout(r, 5000)); } throw new Error(`Attestation for ${txHash} did not complete in time`); } const attestation = await pollAttestation(ETHEREUM_DOMAIN, transferHash); const messageTransmitterAbi = [ { name: "receiveMessage", type: "function", stateMutability: "nonpayable", inputs: [ { name: "message", type: "bytes" }, { name: "attestation", type: "bytes" }, ], outputs: [{ name: "success", type: "bool" }], }, ] as const; const receiveHash = await arcWallet.writeContract({ address: ARC_MESSAGE_TRANSMITTER, abi: messageTransmitterAbi, functionName: "receiveMessage", args: [attestation.message, attestation.attestation], }); await arcPublic.waitForTransactionReceipt({ hash: receiveHash }); console.log(`Mint complete on Arc Testnet: ${receiveHash}`); ``` </Tab> </Tabs> After the transfer completes, the recipient holds the transferred EURC on Arc Testnet. Verify by reading the recipient's balance on the Arc Testnet deployment (`0x89b50855Aa3bE2f677Cd6303CeC089b5f319D72A`). # CCTP for non-USDC contract addresses Source: https://developers.circle.com/cctp/expanded-assets/references/contract-addresses Deployed CCTP for non-USDC contract addresses across supported blockchains and environments. `TokenManager` and `CrossChainToken` contracts are deployed per token. To discover their addresses for a given `tokenId`, call [`resolveTokenManager`](/cctp/expanded-assets/references/contract-reference) and [`resolveTokenAddress`](/cctp/expanded-assets/references/contract-reference) on the `CrossChainTokenService` for the local domain. For contract interfaces and method signatures, see [`CrossChainTokenService` Interface](/cctp/expanded-assets/references/contract-reference). ## Mainnet contract addresses ### `CrossChainTokenService` Mainnet `CrossChainTokenService` is deployed via CREATE3 at the same address on each listed chain. | Blockchain | Domain | Address | | - | - | - | | **Ethereum** | 0 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://etherscan.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) | | **Avalanche** | 1 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://snowtrace.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) | | **OP Mainnet** | 2 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://optimistic.etherscan.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) | | **Arbitrum** | 3 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://arbiscan.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) | | **Base** | 6 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://basescan.org/address/0x431871229103b780868f8C6BB820cd16ECf942BC) | | **Polygon PoS** | 7 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://polygonscan.com/address/0x431871229103b780868f8C6BB820cd16ECf942BC) | | **Arc** | 26 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://explorer.arc.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) | ## Testnet contract addresses ### `CrossChainTokenService` Sandbox `CrossChainTokenService` is deployed via CREATE3 at the same address on each listed testnet. | Blockchain | Domain | Address | | - | - | - | | **Ethereum Sepolia** | 0 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://sepolia.etherscan.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) | | **Avalanche Fuji** | 1 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://testnet.snowtrace.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) | | **OP Sepolia** | 2 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://sepolia-optimism.etherscan.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) | | **Arbitrum Sepolia** | 3 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://sepolia.arbiscan.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) | | **Base Sepolia** | 6 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://sepolia.basescan.org/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) | | **Polygon PoS Amoy** | 7 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://amoy.polygonscan.com/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) | | **Arc Testnet** | 26 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://explorer.testnet.arc.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) | ## Underlying CCTP contracts CCTP for non-USDC routes messages through the existing CCTP `MessageTransmitterV2` contract on each domain. The CCTP for non-USDC domain ID matches the [CCTP domain ID](/cctp/concepts/supported-chains-and-domains#domain-identifiers) for the same blockchain. For `MessageTransmitterV2` addresses, see [CCTP Contract Addresses](/cctp/references/contract-addresses). # CCTP for non-USDC contract reference Source: https://developers.circle.com/cctp/expanded-assets/references/contract-reference CCTP for non-USDC contracts, types, errors, and non-obvious behaviors integrators need. The following tables index contracts, types, errors, and sentinels, and document behaviors that aren't obvious from reading the source. For contract addresses, see [Contract Addresses](/cctp/expanded-assets/references/contract-addresses). ## Contracts | Contract | Purpose | | - | - | | `CrossChainTokenService` | Singleton entry point on each domain. Configures tokens, deploys remote token contracts, and routes crosschain transfers. | | `TokenManager` | Deployed per token on each domain. Moves tokens during transfers; enforces rate limit, max transfer amount, and pause. | | `CrossChainToken` | ERC-20 with `ERC20Permit` deployed for natively crosschain tokens. Holders call `crossChainTransfer` directly. | | `CrossChainTokenExecutable` | Optional abstract helper for destination-domain hook receivers. Integrators can implement `executeWithCrossChainToken` directly. | | `IDenylistProvider` | Interface for service-level and per-token denylist providers. One view: `isDenylisted(address)`. | ## Types ### `QuoteClaim` Passed to every payable entrypoint to attach a signed fee quote and a refund address. ```solidity Solidity theme={null} struct QuoteClaim { bytes signedQuote; address payable refundAddress; } ``` * `signedQuote`: version-prefixed, ABI-encoded fee quote returned by the Iris fee-quote endpoint (`POST /v2/quote/cctpx/{tokenId}/{sourceDomain}/{destinationDomain}`). Pass the bytes value verbatim. * `refundAddress`: receives fee refunds. Set to `address(0)` to opt out of refund attribution. Non-zero values are screened against the USDC denylist on collection; the call reverts if denied. ### `DeploymentParams` Passed when configuring or deploying a token. ```solidity Solidity theme={null} struct DeploymentParams { TokenManagerType tokenManagerType; bytes tokenManagerOwner; bytes tokenManagerOperator; bytes tokenOwner; bytes tokenMinter; uint256 initialSupply; TokenManagerSettings tokenManagerSettings; } struct TokenManagerSettings { uint256 rateLimit; uint256 maxTransferAmount; } ``` Address fields are `bytes` rather than `address` so the struct can carry non-EVM addresses for non-EVM domains. Some deployments omit `initialSupply` from the onchain ABI (for example current sandbox `registerCustomToken`). Encoding a field the contract does not expect produces an empty revert. Always verify the live ABI or function selector before copying struct layouts from docs. ### `TokenManagerType` ```solidity Solidity theme={null} enum TokenManagerType { NATIVE_CROSSCHAIN_TOKEN, // 0 BURN_MINT, // 1 LOCK_UNLOCK // 2 } ``` See [Token manager types](/cctp/expanded-assets/concepts/architecture#token-manager-types) for when each applies. ### `ConnectionType` ```solidity Solidity theme={null} enum ConnectionType { OWNERLESS, CUSTOM } ``` Distinguishes the two integration models. See [Ownerless and custom tokens](/cctp/expanded-assets/concepts/ownerless-vs-custom-tokens). ## Errors Integrators should monitor for these reverts: | Error | Source | Meaning | | - | - | - | | `TokenNotRegistered(bytes32 tokenId)` | `CrossChainTokenService` | `resolveTokenManager` or `resolveTokenAddress` called with a `tokenId` that has no deployment. | | `HookDataExecutionFailed(bytes32 tokenId, address recipient, bytes reason)` | `CrossChainTokenService` | Recipient's hook reverted, or returned a value other than the `EXECUTE_SUCCESS` sentinel. | | `DenylistedAddress(address account)` | `CrossChainTokenService` | Service-level denylist rejected `msg.sender`, `tx.origin`, or the inbound recipient. | | `AccountDenylisted(address account)` | `CrossChainToken` | Per-token denylist rejected `from`, `to`, or the spender on an ERC-20 movement. | ## Sentinels ### `EXECUTE_SUCCESS` ```solidity Solidity theme={null} bytes32 constant EXECUTE_SUCCESS = keccak256("circle-cctpx-execute-success"); ``` The value that `executeWithCrossChainToken` must return. The service verifies this on every hook call. Implement the public entrypoint on your receiver, restrict `msg.sender` to the local `CrossChainTokenService`, and return this sentinel. See [Build a hook receiver](/cctp/expanded-assets/howtos/build-hook-receiver). When the contracts are open-sourced, an optional `CrossChainTokenExecutable` base can wrap the same pattern (external entrypoint returns the sentinel; subclasses override an internal hook). Until then, implement the write ABI directly. ## Non-obvious behaviors These behaviors aren't apparent from reading the source but commonly trip up integrators. ### `approve` goes to the `TokenManager`, not the service For `BURN_MINT` and `LOCK_UNLOCK` transfers, the source token is moved by the per-token `TokenManager`. ERC-20 approvals must target the `TokenManager` address (returned by `resolveTokenManager(tokenId)`), not the `CrossChainTokenService`. ### `destinationAddress` is packed address bytes, not ABI-encoded `crossChainTransfer` takes `destinationAddress` as `bytes`. On EVM, pass the raw 20-byte address (`abi.encodePacked(recipient)` / `encodePacked(["address"], [recipient])`). Do **not** use `abi.encode` / `encodeAbiParameters([{ type: "address" }], …)`, which produces a 32-byte left-padded word and breaks destination delivery. This is separate from application `hookData`, which may still be ABI-encoded. ### Custom `tokenId` uses `customTokenDeploySalt` For custom tokens, the `tokenId` is `crossChainTokenId(deployer, customTokenDeploySalt(deployer, salt))`. Calling `crossChainTokenId(deployer, salt)` alone yields a different id. Native crosschain tokens use `crossChainTokenId(deployer, salt)` directly. ### `resolveTokenManager` and `resolveTokenAddress` revert on miss Both revert with `TokenNotRegistered(tokenId)` when the predicted address has no deployed code. They do not return `address(0)`. To probe for existence without throwing, wrap the call in `try`/`catch` or check `extcodesize` on the deterministic CREATE3-predicted address. ### `CrossChainToken` denylist provider replaces, it does not chain `CrossChainToken` holds a single denylist provider slot. Calling `updateDenylistProvider` overwrites the previous value rather than adding to it. To combine multiple lists, deploy a composite provider that internally calls each upstream. The service-level denylist still runs in addition to the per-token layer. ### Ownerless deploys inherit Circle's denylist provider When `deployRemoteOwnerlessToken` lands on a remote domain, the new `CrossChainToken`'s `denylistProvider` is initialized to the service's provider. ### `tokenManagerOwner`, `tokenManagerOperator`, and related fields are `bytes` The fields are typed `bytes` instead of `address` so the same struct works for non-EVM domains in the future. On EVM, pack the address with `abi.encodePacked(addr)` / `encodePacked(["address"], [addr])` (20 bytes). Do not use `encodeAbiParameters([{ type: "address" }], [addr])`, which ABI-pads to 32 bytes and is not equivalent. For an **existing** ERC-20 configured with `registerCustomToken`, pass empty bytes (`0x`) for `tokenOwner` and `tokenMinter`; grant the `TokenManager` minter role on the token in a separate step. ### `sourceAddress` in hook callbacks is `bytes` Same reason as the deployment params: the hook signature carries the source address as `bytes` so receivers can decode senders from non-EVM blockchains. ### Hook execution can be reorged on fast transfers When a hook executes with `finalityThresholdExecuted < 2000`, the source blockchain transaction is still pre-finality and could be reorged. Receivers that opt into fast hook execution must design their logic to tolerate rollback by deferring irreversible side-effects until finality is confirmed. ## Function index ### `CrossChainTokenService` | Group | Functions | | - | - | | Crosschain transfer | `crossChainTransfer`, `transmitCrossChainTransfer` | | Resolve and inspect | `resolveTokenManager`, `resolveTokenAddress`, `getOwnerlessTokenId`, `crossChainTokenId`, `customTokenDeploySalt`, `ownerlessTokenDeploySalt`, `isTrustedDomain` | | Trust and limits | `addTrustedDomain`, `removeTrustedDomain`, `setRateLimits` | | Denylist | `denylistProvider`, `setDenylistProvider`, `initializeDenylistProvider` | | Roles and upgrade | `feeService`, `messageTransmitter`, `operator`, `transferOwnership`, `acceptOwnership`, `pause`, `unpause`, `upgradeToAndCall` | ### `TokenManager` | Group | Functions | | - | - | | Transfer limits | `setRateLimit`, `setMaxTransferAmount`, `setRateLimitWindow` | | Pause | `pause`, `unpause`, `updatePauser` | | Implementation | `upgradeToCustomImplementation`, `resetToDefaultImplementation` | | Ownership | `transferOwnership`, `acceptOwnership`, `transferOperatorship` | | Circle assignment | `assignOwnership`, `assignOperatorship` (only callable on ownerless `TokenManager`s) | | Service-only | `takeToken`, `giveToken` (called by `CrossChainTokenService`; not callable by integrators) | ### `CrossChainToken` | Group | Functions | | - | - | | Crosschain | `crossChainTransfer`, `crossChainTransferFrom` | | Mint and burn | `mint`, `burn`, `burnFrom`, `addMinter`, `removeMinter`, `transferMintership` | | Denylist | `denylistProvider`, `isDenylisted`, `updateDenylistProvider` | | Implementation | `upgradeToCustomImplementation`, `resetToDefaultImplementation` | | Ownership | `transferOwnership`, `acceptOwnership` | | Circle assignment | `assignOwnership`, `assignOperatorship` (only callable on ownerless `CrossChainToken`s) | | ERC-20 surface | Standard ERC-20 (`transfer`, `transferFrom`, `approve`, `allowance`, `balanceOf`, `totalSupply`) plus `ERC20Permit` | ### `CrossChainTokenExecutable` Optional helper (when published). Integrators may instead implement the public entrypoint directly. | Group | Functions | | - | - | | Hook entry point | `executeWithCrossChainToken` (returns `EXECUTE_SUCCESS`) | | Hook implementation | `_executeWithCrossChainToken` (override when using the abstract base) | # Get the fast transfer allowance Source: https://developers.circle.com/cctp/howtos/get-fast-transfer-allowance Check the remaining Fast Transfer allowance for USDC transfers This how-to shows you how to retrieve the remaining Fast Transfer allowance using the [CCTP API](/api-reference/cctp/all/get-fast-burn-usdc-allowance). The Fast Transfer allowance is Circle's mechanism for backing faster-than-finality USDC transfers before burns reach hard finality on source chains. ## Prerequisites Before you begin, ensure you have: * Installed cURL on your development machine ## Get the Fast Transfer allowance Call the [`GET /v2/fastBurn/USDC/allowance`](/api-reference/cctp/all/get-fast-burn-usdc-allowance) endpoint to retrieve the current remaining Fast Transfer allowance. **Example request** ```shell Shell theme={null} curl --request GET \ --url 'https://iris-api-sandbox.circle.com/v2/fastBurn/USDC/allowance' \ --header 'Accept: application/json' ``` **Response** ```json theme={null} { "allowance": 99999999225.24174, "lastUpdated": "2025-12-02T1
프로젝트로 돌아가기