Circle USYC 백서

Circle USYC

원문

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

ce<br/>(singleton)"] TM1["TokenManager<br/>tokenId 0x1234..."] TM2["TokenManager<br/>tokenId 0x5678..."] TM3["TokenManager<br/>tokenId 0xabcd..."] Token1[ERC-20 token A] Token2[ERC-20 token B] CCT["CrossChainToken<br/>(deployed by CCTP for non-USDC)"] CCTS --> TM1 CCTS --> TM2 CCTS --> TM3 TM1 -.manages.-> Token1 TM2 -.manages.-> Token2 TM3 -.manages.-> CCT ``` The `CrossChainTokenService` is one contract per domain. It fronts every `TokenManager` on that domain. A `TokenManager` is created for each configured token and is identified across all domains by its `tokenId`. ## Token manager types Every `TokenManager` has a type that determines how it moves tokens on the source and destination blockchains during a transfer. The type is set when the token is configured. ```solidity Solidity theme={null} enum TokenManagerType { NATIVE_CROSSCHAIN_TOKEN, // 0 BURN_MINT, // 1 LOCK_UNLOCK // 2 } ``` | Type | Source domain behavior | Destination domain behavior | When to use | | - | - | - | - | | `NATIVE_CROSSCHAIN_TOKEN` | Token self-burns from the sender | Token self-mints to the recipient | The token is a `CrossChainToken` deployed by the protocol | | `BURN_MINT` | `TokenManager` pulls tokens from sender and burns them | `TokenManager` mints tokens to the recipient | An existing issuer-controlled token where the issuer can grant minter rights | | `LOCK_UNLOCK` | `TokenManager` pulls tokens from sender and locks them | `TokenManager` unlocks tokens to the recipient | An existing token the protocol cannot mint or burn | Most tokens use the same type on all blockchains. Exception: the ownerless model pairs `LOCK_UNLOCK` on the home blockchain with `NATIVE_CROSSCHAIN_TOKEN` on remote blockchains. See [Ownerless versus custom tokens](/cctp/expanded-assets/concepts/ownerless-vs-custom-tokens). ## How tokens are identified Each token is identified by its `tokenId`, a `bytes32` value derived from the address that configured the token and a caller-chosen salt. The same `tokenId` identifies the same token across every blockchain where it is configured. You do not construct a `tokenId` manually. The `CrossChainTokenService` returns it when you configure or deploy a token, and exposes read helpers to rederive it later: * `getOwnerlessTokenId(tokenAddress)` for ownerless tokens * `crossChainTokenId(deployer, salt)` for native `CrossChainToken` deployments (`deployCrossChainToken`) * `crossChainTokenId(deployer, customTokenDeploySalt(deployer, salt))` for custom tokens. Calling `crossChainTokenId(deployer, salt)` alone yields a different id. `customTokenDeploySalt` is also the storage salt used to derive the custom token's `TokenManager`. Because the derivation uses `(msg.sender, salt)`, using the same deployer address and the same `salt` on every blockchain produces the same `tokenId` everywhere. This is what lets a single value identify a token on every supported blockchain without an offchain registry. Once you have a `tokenId`, look up the local contracts: * `resolveTokenManager(tokenId)` returns the `TokenManager` address. * `resolveTokenAddress(tokenId)` returns the underlying ERC-20 address. Both revert with `TokenNotRegistered(tokenId)` if no token manager has been deployed for the supplied `tokenId` on the local domain. The predicted address is deterministic through CREATE3, so callers that need a non-throwing existence check can wrap the call in `try`/`catch` or perform their own `extcodesize` check. ### Decimal consistency The protocol does not scale token amounts when delivering transfers. A token must use the same number of decimals on every blockchain where it is deployed. ## Transfer finality and fast burn allowance Two transfer speeds are available, set by the `minFinalityThreshold` parameter on `crossChainTransfer`: * **Standard transfer** (`minFinalityThreshold = 2000`) waits for source blockchain finality before attestation. * **Fast transfer** (`minFinalityThreshold = 1000`) attests on soft finality and consumes a per-token fast burn allowance. Each token has a fast burn allowance maintained by Circle. The allowance is denominated in USD and limits the total value that can be moved through fast transfers of that token before the allowance refills. Standard transfers do not consume the allowance. When a fast transfer is initiated, the protocol consumes more than the face value of the transfer from the allowance to account for short-term price movement of the token. Before initiating a fast transfer, query `GET /v2/cctpx/allowances` to confirm remaining capacity and that the token has FX pricing configured. If allowance is insufficient or pricing data is unavailable (for example `FX_SYMBOL_NOT_CONFIGURED`), Iris rejects the fast quote; there is no automatic fallback. Request a standard quote (omit `PRE_FINALITY`) and use `minFinalityThreshold = 2000` instead. USDC and non-USDC fast transfers share the same `MessageTransmitterV2` and finality thresholds, but each tracks its own allowance pool. USDC transfers consume the USDC allowance; non-USDC transfers consume a per-token allowance. ## Forwarding service An optional forwarding service delivers completed transfers to the recipient on the destination blockchain. When forwarding is used, Circle relays the Iris attestation and calls `receiveMessage` on the destination `MessageTransmitterV2` on behalf of the sender, so neither the sender nor the recipient needs to submit the attestation manually. Forwarding is opt-in and must be requested when you obtain the fee quote from the Iris API. The fee quote response includes a signed claim that you pass as the `claim` parameter on `crossChainTransfer`. Transfers that omit the claim proceed without automatic relay, and you or the recipient are responsible for submitting the attestation to `MessageTransmitterV2`. You might choose not to use forwarding if you manage delivery yourself, if you are performing complex hook-based transactions that require manual relay sequencing, or if you have your own relayer infrastructure. ## Reuse of CCTP infrastructure No separate attestation service runs for non-USDC transfers. Every message flows through the same `MessageTransmitterV2` and Iris (Circle's attestation API) that CCTP uses for USDC transfers, including the same set of attesters, the same finality thresholds, and the same message verification logic. USDC and non-USDC transfers share the attestation pipeline but are otherwise independent. USDC transfer behavior is unchanged. # CCTP for non-USDC compared to CCTP Source: https://developers.circle.com/cctp/expanded-assets/concepts/cctp-differences Key integration differences for developers already familiar with CCTP who want to transfer existing configured tokens. Integrating CCTP for non-USDC when you already know CCTP for USDC is mostly a matter of switching entry contracts and adjusting for a different fee model. The following sections document the key differences for developers integrating pre-configured or third-party-configured tokens. ## What stays the same This layer shares CCTP's core infrastructure: * **Message transmitter**: Both use `MessageTransmitterV2`. Attestations are delivered to the same contract on the destination. * **Attestation service**: Both use Iris (Circle's attestation API). Polling `/v2/messages/{sourceDomain}` works the same way. * **Finality thresholds**: `minFinalityThreshold = 2000` for standard, `1000` for fast. Same semantics as CCTP fast transfers. * **Hooks**: Available on both protocols. The `CrossChainTokenExecutable` interface is analogous to CCTP's `IMessageHandlerV2`. ## Key integration differences ### Entry contract and approval target | | CCTP | CCTP for non-USDC | | - | - | - | | Source entry | `TokenMessengerV2` | `CrossChainTokenService` | | Approval target | `TokenMessengerV2` | Per-token `TokenManager` (not the service) | For CCTP, you approve `TokenMessengerV2` and call `depositForBurn`. For non-USDC transfers, resolve the per-token `TokenManager` address from `CrossChainTokenService` using `resolveTokenManager(tokenId)`, approve that address, then call `crossChainTransfer` on the service. Approving the service contract directly does not work; the approval must go to the `TokenManager`. ### Fee model CCTP deducts its fee from the transferred USDC amount; the recipient receives slightly less than the sender transferred. Non-USDC fees are paid in native gas on the source blockchain at transfer time, so the recipient receives the full transferred amount. ### Forwarding CCTP forwarding (auto-delivery of the destination mint) is compulsory whenever the forwarding service is used. For non-USDC transfers, forwarding is opt-in: include a `FORWARD` entry in the `requests` array when you fetch the Iris fee quote. Transfers that omit `FORWARD` require you or the recipient to submit `receiveMessage` on the destination `MessageTransmitterV2` manually. ### Allowance pools for fast transfers Both protocols support fast transfers, but they track allowance separately. USDC fast transfers consume the USDC allowance; non-USDC fast transfers consume a per-token allowance maintained by Circle. Check the per-token allowance using `GET /v2/cctpx/allowances` before initiating a fast transfer; if the allowance is insufficient or FX pricing is not configured for the token, the quote returns `FX_SYMBOL_NOT_CONFIGURED` and the fast transfer cannot proceed. ### Token prerequisite USDC works on CCTP without setup. Non-USDC transfers require the token to be configured before it can be transferred. If you are using a pre-configured token (such as EURC), the token is already available and no configuration step is needed on your end. To check which tokens are pre-configured, see [Supported blockchains](/cctp/expanded-assets/concepts/supported-chains-and-domains). # Denylist layers Source: https://developers.circle.com/cctp/expanded-assets/concepts/denylist-layers The two independent address-screening layers enforced on every non-USDC transfer. CCTP for non-USDC enforces two independent address-screening layers on every non-USDC crosschain transfer. Both must clear before a transfer succeeds. They differ in scope, in who manages them, and in where they execute. ## Layer 1: service-level denylist Every transfer on this blockchain—outbound and inbound, regardless of `TokenManagerType`—is screened against an `IDenylistProvider` configured on the local `CrossChainTokenService`. The provider exposes a single view: ```solidity Solidity theme={null} interface IDenylistProvider { function isDenylisted(address account) external view returns (bool); } ``` * **Outbound:** every payable transfer entrypoint screens both `msg.sender` and `tx.origin`. Denied addresses cause the call to revert before the burn or lock happens. * **Inbound:** the service screens the transfer recipient before delivering tokens. Denied recipients cause the inbound `receiveMessage` to revert with `DenylistedAddress(recipient)`. The provider is set by the service operator using [`setDenylistProvider`](/cctp/expanded-assets/references/contract-reference). The check is fail-closed: if the provider's `staticcall` reverts, the transfer reverts. This layer applies to every token on this blockchain. There is no opt-out at the token level. ## Layer 2: per-token denylist on the `CrossChainToken` Each `CrossChainToken` (CCT) deployed as `NATIVE_CROSSCHAIN_TOKEN` can carry its own `IDenylistProvider`. The CCT owner sets it by calling [`updateDenylistProvider`](/cctp/expanded-assets/references/contract-reference). Setting `address(0)` removes per-token screening; the service-level layer still applies. The check runs inside the ERC-20 `_update` and screens both `from` and `to` on every transfer, mint, and burn. `transferFrom` additionally screens the spender at the entrypoint. Denied accounts cause `AccountDenylisted(account)` to revert. Layer 2 exists only for protocol-deployed tokens. `BURN_MINT` and `LOCK_UNLOCK` configurations of pre-existing ERC-20 tokens do not have this layer; whatever screening the underlying token implements applies independently. ## Initial provider on a new `CrossChainToken` When the service deploys a `CrossChainToken`, the new contract's initial `denylistProvider` value depends on the deploy path: | Deploy path | Initial `denylistProvider` | | - | - | | Remote ownerless token (`deployRemoteOwnerlessToken`) | Service's `denylistProvider` (inherited) | | Remote custom token (`deployRemoteCrossChainToken`) | `address(0)` (owner opts in) | | Local custom token (`deployCrossChainToken`) | `address(0)` (owner opts in) | In other words: ownerless crosschain tokens get Circle's screening list out of the box on every remote domain. Custom tokens always start with no per-token screening; the issuer opts in by calling `updateDenylistProvider` themselves. ## Comparison | Aspect | Layer 1 (service-level) | Layer 2 (per-token) | | - | - | - | | Where it lives | Service storage; queried by `CrossChainTokenService` | The `CrossChainToken` (only `NATIVE_CROSSCHAIN_TOKEN`) | | Configured by | Service operator | The CCT owner | | Applies to | Every flow on this blockchain (outbound and inbound) | ERC-20 movements of the specific CCT, when its provider is non-zero | | Initial value | Set during service initialization | Inherited from the service for remote ownerless; `address(0)` otherwise | | Provider interface | `isDenylisted(address)` | `isDenylisted(address)` | | Revert | `DenylistedAddress(account)` (service) / fail-closed on provider error | `AccountDenylisted(account)` (CCT) | ## Composition The two layers are independent. Layer 1 fires at the service boundary; layer 2 fires inside the `CrossChainToken`'s `_update`. A transfer must clear both. Layer 2 has a single provider slot. Setting a new provider replaces the previous one—it does not chain. An owner who wants both Circle's screening and a custom list must implement a composite provider that calls both upstream lists. The service-level layer at the boundary is unaffected by what the owner does on the CCT; layer 1 always runs. # Hooks Source: https://developers.circle.com/cctp/expanded-assets/concepts/hooks Atomically execute logic on the destination blockchain when a CCTP for non-USDC transfer arrives. CCTP for non-USDC transfers can carry arbitrary `hookData` that is delivered together with the tokens on the destination blockchain. When `autoExecuteHookData` is `true` and the recipient implements `executeWithCrossChainToken`, CCTP for non-USDC invokes that entrypoint in the same transaction that delivers the tokens. ## Enabling hook execution A sender provides three parameters on [`crossChainTransfer`](/cctp/expanded-assets/references/contract-reference) to control hook behavior: | Parameter | Purpose | | - | - | | `destinationAddress` | The contract that receives the tokens and the hook callback | | `hookData` | Arbitrary `bytes` payload passed to the recipient's hook | | `autoExecuteHookData` | When `true` and `hookData` is non-empty, the destination `CrossChainTokenService` executes the hook | If `autoExecuteHookData` is `false`, the tokens are delivered but the hook is not called. If `hookData` is empty, the hook is not called even when `autoExecuteHookData` is `true`. <Warning> Hook receivers execute arbitrary logic in the same transaction that delivers tokens. A vulnerable hook can lock or redirect funds permanently. Thoroughly audit any hook contract before deploying to mainnet—incorrect access control, unchecked external calls, and missing input validation are common failure modes. </Warning> ## Receiver contract requirement When `autoExecuteHookData` is `true` and `hookData` is non-empty, the `destinationAddress` **must** be a contract that implements `executeWithCrossChainToken` and returns the [`EXECUTE_SUCCESS`](/cctp/expanded-assets/references/contract-reference#execute_success) sentinel. Restrict the caller to the local `CrossChainTokenService`. ```solidity Solidity theme={null} function executeWithCrossChainToken( uint32 sourceDomain, bytes calldata sourceAddress, bytes32 tokenId, address token, uint256 amount, uint32 finalityThresholdExecuted, bytes calldata hookData ) external returns (bytes32); ``` Return `keccak256("circle-cctpx-execute-success")`. See [Build a hook receiver](/cctp/expanded-assets/howtos/build-hook-receiver) for a full example. If the destination is an externally owned account, doesn't implement the interface, or returns a value other than `EXECUTE_SUCCESS`, the destination service reverts with `HookDataExecutionFailed`. Sending hook data to an address that can't satisfy the interface will fail the transfer at delivery. ## Atomicity The hook executes in the same transaction as token delivery. If the hook reverts, the entire transfer reverts. Tokens are not delivered and the source-domain transfer remains intact. Designing hooks that consider their own failure modes is the receiver's responsibility. ## Hook parameters The `sourceAddress` is encoded as `bytes` rather than `address` to support source blockchains where addresses do not fit in 20 bytes. `finalityThresholdExecuted` is the finality threshold at which the inbound message was attested: `2000` for finalized transfers, `1000` for fast (pre-finalized) transfers. Receivers can branch on this value to decide whether to perform side-effects immediately or defer them. ## Finality threshold and rollback Hooks may execute on pre-finalized messages when the sender opts into `minFinalityThreshold < 2000`. At that finality, the source blockchain transaction can still be reorged, which would invalidate the delivery. Integrators using `autoExecuteHookData` with `minFinalityThreshold < 2000` must treat hook execution on unfinalized messages as provisional and design their hook contracts to tolerate potential rollback—for example, by deferring irreversible side-effects until `finalityThresholdExecuted` equals `2000` or until finality is otherwise confirmed. Prefer standard finality (`2000`) for hook transfers unless you have explicitly designed for provisional execution. ## Self-relay required for hook transfers Iris does not relay transfers that carry hook data with `autoExecuteHookData = true`. Do not include `FORWARD` in the fee quote for these transfers. You or your own relayer infrastructure must call `receiveMessage` on the destination `MessageTransmitterV2` to complete the transfer. Plan for this when designing a hook-based integration: every hook transfer requires the sender (or a relayer the sender operates) to drive the destination call. # Ownerless and custom tokens Source: https://developers.circle.com/cctp/expanded-assets/concepts/ownerless-vs-custom-tokens The two integration models CCTP for non-USDC supports for making a token crosschain. Two integration models exist for taking an existing token crosschain: ownerless tokens and custom tokens. Anyone can configure either type. The two models differ in who controls the resulting bridge connection and whether Circle can intervene. ## Ownerless tokens An ownerless token is an existing token configured for crosschain use under an ownerless connection. Ownerless tokens are intended for community assets without an active issuer. Connections start ownerless. At configuration, no owner or operator can be set on the resulting `TokenManager`. Circle can assume operatorship or ownership later in special circumstances. Ownerless connections have no rate limits or maximum transfer amount by default. Circle can add controls only if the bridged token reaches a meaningful total value locked (TVL) threshold and has a reliable price oracle. On the home blockchain the `TokenManager` uses `LOCK_UNLOCK`. On remote blockchains, CCTP for non-USDC deploys a wrapped `CrossChainToken` and the `TokenManager` uses `NATIVE_CROSSCHAIN_TOKEN`, which uses mint and burn. <Warning> Ownerless tokens are
프로젝트로 돌아가기