Circle USYC 백서

Circle USYC

원문

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

ssageTransmitterV2 interface The `MessageTransmitterV2` contract provides the core messaging layer for CCTP on Starknet. It is responsible for emitting, receiving, and validating crosschain messages, enforcing attestation rules, and ensuring message uniqueness. | Function | Description | Notes | | :- | :- | :- | | `send_message` | Sends a crosschain message with specified domain, recipient, and message body. | Core function for outgoing CCTP messages. | | `receive_message` | Validates a message and its attestation; delivers message body to the recipient. | Called by an offchain forwarding service with an attestation from Circle to complete the transfer. | | `get_max_message_body_size` | Returns the maximum allowed message size. | Used by offchain components for validation. | | `is_nonce_used` | Checks if a message nonce has been processed already. | Prevents message replay. | | `get_local_domain` | Returns this contract's domain ID. | Expected to be 25 for Starknet. | | `get_version` | Returns protocol version supported by this transmitter. | Used by Iris attestation service. | # CCTP on Stellar Source: https://developers.circle.com/cctp/references/stellar Learn how to send funds to Stellar addresses and how CCTP handles Stellar USDC. CCTP on Stellar has two behaviors you must account for when integrating: a 32-byte address format that does not distinguish accounts from contracts, and a seven-decimal USDC precision that differs from other CCTP supported blockchains. See [CCTP Stellar contracts and interfaces](/cctp/references/stellar-contracts) for the contract addresses and interfaces. <Warning> Always [use `CctpForwarder`](/cctp/references/stellar#use-cctpforwarder-for-stellar-recipients) when routing CCTP USDC to a Stellar address. Set both `mintRecipient` and `destinationCaller` to the `CctpForwarder` [contract address](/cctp/references/stellar-contracts). * If `destinationCaller` is wrong, the forwarder cannot complete the transfer. * If `mintRecipient` is set to a user account or muxed address, USDC is not sent to the forwarder. In either case, funds become permanently stuck and **cannot be recovered**. </Warning> ## Stellar address types On Stellar, addresses are `strkey` strings composed of a type identifier and a 32-byte payload. The type identifier determines the account type: user accounts (`G`) carry an Ed25519 public key, contracts (`C`) carry a contract ID hash. Muxed accounts (`M`) are a type of `G` account that additionally embeds a numeric identifier alongside the Ed25519 public key (see Stellar's [muxed accounts documentation](https://developers.stellar.org/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos#muxed-accounts)). CCTP messages store only the raw 32-byte payload without the type identifier, so the protocol cannot distinguish between address types and assumes the `mintRecipient` is always a contract. Use `CctpForwarder` when transferring to Stellar to ensure funds are forwarded to the intended recipient. ## Use `CctpForwarder` for Stellar recipients `CctpForwarder` is a publicly callable onchain contract that receives minted USDC on Stellar and atomically forwards it to `forwardRecipient`. Encode `forwardRecipient` in hook data as a Stellar `strkey`. The prefix `G`, `M`, or `C` identifies the recipient address type. <Warning> On the source burn, both `mintRecipient` and `destinationCaller` must be set to the `CctpForwarder` [contract address](/cctp/references/stellar-contracts). * If `destinationCaller` is wrong, the forwarder cannot complete the transfer. * If `mintRecipient` is set to a user account or muxed address, USDC is not sent to the forwarder. In either case, funds become permanently stuck and **cannot be recovered**. </Warning> ### How it works Call `mint_and_forward` on `CctpForwarder` through the Stellar Soroban client for your language. Pass the raw CCTP message and attestation bytes. The following shows the onchain contract interface. It is not a TypeScript or JavaScript function you call directly. Your Soroban client builds an `invokeHostFunction` operation from these arguments. ```text theme={null} mint_and_forward(message: Bytes, attestation: Bytes) ``` For TypeScript, [`@stellar/stellar-sdk`](https://github.com/stellar/js-stellar-sdk) documents how to encode arguments and how to simulate, sign, and submit transactions against [Stellar RPC](https://developers.stellar.org/docs/data/rpc). Inside `mint_and_forward`, `CctpForwarder` does the following: 1. Validates the message. 2. Extracts `forwardRecipient` from hook data. 3. Calls `receive_message` on `MessageTransmitter`, which mints USDC to `CctpForwarder`. 4. Transfers the minted USDC to `forwardRecipient`. 5. Runs atomically. Any failure reverts the invocation. <Note> The `CctpForwarder` flow is non-custodial. The mint and the payout to `forwardRecipient` both run onchain in that single Soroban invocation. Circle does not take custody of the minted balance in between. </Note> ### Hook format The hook data begins with the reserved magic bytes, followed by versioning and payload fields. On Stellar, bytes 28 onward carry the length of `forwardRecipient`, the `forwardRecipient` `strkey`, and any optional trailing bytes for integrator use. | Bytes | Type | Data | | - | - | - | | 0-23 | `bytes24` | Magic. Circle-reserved bytes; use all zero bytes | | 24-27 | `uint32` | Version; set to `0` | | 28-31 | `uint32` | `L`: length of `forwardRecipient` in bytes | | `32..(32+L-1)` | `bytes` | `forwardRecipient` as a `strkey` | | `(32+L)..` | `bytes` | Optional integrator-defined payload; omit if unused | #### Building forwarder hook data (example) The following helper functions validate Stellar contract `strkey` inputs and build the `hookData` payload for an EVM `depositForBurnWithHook` call: ```ts TypeScript theme={null} import { StrKey } from "@stellar/stellar-sdk"; /** * Validates that the input is a Stellar contract address (C…) and decodes it * to a 0x-prefixed bytes32 hex string suitable for EVM contract calls. * * @param strkey - Stellar contract address (C…) * @returns 0x-prefixed 64-character hex string * @throws If the input is not a valid contract address */ function contractStrkeyToBytes32(strkey: string): `0x${string}` { if (!StrKey.isValidContract(strkey)) { throw new Error(`Invalid contract strkey: ${strkey}`); } return `0x${Buffer.from(StrKey.decodeContract(strkey)).toString("hex")}`; } /** * Builds the hookData buffer for a CCTP Forwarder burn message. * * Hook data layout: * bytes 0–23: reserved (zeroed) * bytes 24–27: hook data version (u32 BE, currently 0) * bytes 28–31: forward_recipient byte length (u32 BE) * bytes 32+ : forward_recipient (UTF-8 encoded Stellar strkey) * * @param forwardRecipientStrkey - Stellar strkey of the final token recipient (C…, G…, or M…) * @returns Hook data as a 0x-prefixed hex string */ function buildCctpForwarderHookData( forwardRecipientStrkey: string, ): `0x${string}` { const isValid = StrKey.isValidEd25519PublicKey(forwardRecipientStrkey) || StrKey.isValidContract(forwardRecipientStrkey) || StrKey.isValidMed25519PublicKey(forwardRecipientStrkey); if (!isValid) { throw new Error( `Invalid forward recipient: ${forwardRecipientStrkey} (expected G..., C..., or M... address)`, ); } const recipientBytes = Buffer.from(forwardRecipientStrkey, "utf8"); const hookData = Buffer.alloc(32 + recipientBytes.length); hookData.writeUInt32BE(0, 24); // hook version = 0 hookData.writeUInt32BE(recipientBytes.length, 28); // recipient byte length recipientBytes.copy(hookData, 32); // recipient strkey as UTF-8 return `0x${hookData.toString("hex")}`; } interface DepositForBurnWithHookParams { amount: bigint; destinationDomain: number; mintRecipient: `0x${string}`; burnToken: `0x${string}`; destinationCaller: `0x${string}`; maxFee: bigint; minFinalityThreshold: number; hookData: `0x${string}`; } /** * Prepares all arguments for an EVM `depositForBurnWithHook` call targeting * Stellar via the CCTP Forwarder. Converts Stellar strkeys to 0x-prefixed * bytes32 hex strings and encodes the hook data. * * @param amount - Token amount to burn (in EVM token decimals) * @param cctpForwarderStrkey - Stellar strkey of the CCTP Forwarder contract (C…), used as mintRecipient and destinationCaller * @param burnToken - EVM address of the token to burn * @param maxFee - Maximum fee for the burn * @param minFinalityThreshold - Minimum finality threshold (1000 = fast, 2000 = standard) * @param forwardRecipientStrkey - Stellar strkey of the final token recipient (C…, G…, or M…), encoded in hookData */ function prepareEvmDepositForBurnWithHookToStellar( amount: bigint, cctpForwarderStrkey: string, burnToken: `0x${string}`, maxFee: bigint, minFinalityThreshold: number, forwardRecipientStrkey: string, ): DepositForBurnWithHookParams { const cctpForwarderHex = contractStrkeyToBytes32(cctpForwarderStrkey); const hookData = buildCctpForwarderHookData(forwardRecipientStrkey); return { amount, destinationDomain: 27, mintRecipient: cctpForwarderHex, burnToken, destinationCaller: cctpForwarderHex, maxFee, minFinalityThreshold, hookData, }; } ``` ## Stellar addresses in CCTP messages and API responses [Stellar addresses](#stellar-address-types) are `strkey` strings. CCTP message fields store only 32-byte address payloads. They omit the `strkey` encoding, including the `G`, `M`, or `C` type marker, so the raw bytes in the message do not say whether the address is an account or a contract. `mintRecipient` is always assumed to be a contract address. You must [use `CctpForwarder`](#use-cctpforwarder-for-stellar-recipients) to make transfers to Stellar. ### CCTP message fields The following tables describe each address field in the CCTP message, explain how Stellar uses it during a mint (inbound) or burn (outbound), and indicate whether you need to design around the address type. For the full message layout, see the [CCTP technical guide](/cctp/references/technical-guide#message-format). #### Inbound transfers to Stellar destination | Field | Operation | Must design around address type? | | - | - | - | | `sender` | Validate against the source domain `TokenMessenger` mapping. | No | | `recipient` | Select the Stellar contract that handles the destination `receive_message`. | No, always a contract (`C`) | | `destinationCaller` | Restrict who may call `receive_message` (`require_auth` compares bytes). | No, compare raw bytes | | `burnToken` | Map the burned token identifier to Stellar USDC. | No, known asset contract | | `messageSender` | Not used operationally on Stellar. | No | | `mintRecipient` | Mint USDC to this 32-byte destination on Stellar. | Yes, always assumed to be a contract; [use `CctpForwarder` for Stellar recipients](#use-cctpforwarder-for-stellar-recipients) | #### Outbound transfers from Stellar source | Field | Operation | Must design around address type? | | - | - | - | | `sender` | 32 byte address payload of the Stellar `TokenMessengerMinterV2` used to perform the burn. | No | | `burnToken` | Identify the Stellar USDC contract that is burned. | No | | `mintRecipient` | Encode the recipient on the destination blockchain. | No | | `messageSender` | Record caller context (not used operationally on Stellar). | No | | `destinationCaller` | Encode which address may call receive on the destination blockchain. | No | | `recipient` | Encode the handler contract on the destination blockchain. | No | ### Null address fields in API responses When a CCTP message involves Stellar, the [Get messages](/api-reference/cctp/all/get-messages-v2) endpoint returns all address fields in `decodedMessage` and `decodedMessageBody` as `null` because the API cannot distinguish a 32-byte Stellar account from a contract. To read those addresses, parse the raw hex in the `message` field directly. The following example shows an Ethereum-to-Stellar transfer response with typical `null` address fields: ```json JSON theme={null} { "messages": [ { "message": "0x...", "eventNonce": "0", "attestation": "0x...", "cctpVersion": 2, "status": "complete", "decodedMessage": { "sourceDomain": "0", "destinationDomain": "27", "nonce": "0x0000000000000000000000000000000000000000000000000000000000000000", "sender": null, "recipient": null, "destinationCaller": null, "minFinalityThreshold": "1000", "finalityThresholdExecuted": "2000", "messageBody": "0x...", "decodedMessageBody": { "burnToken": null, "mintRecipient": null, "amount": "1000000", "messageSender": null, "maxFee": "0", "feeExecuted": "0", "expirationBlock": "0", "hookData": null } } } ] } ``` ## USDC precision for CCTP and Stellar Stellar represents USDC in seven-decimal subunits while other CCTP-supported blockchains use six. How CCTP handles that difference depends on whether Stellar is the source or destination blockchain. Regardless of direction, the `amount` field in a CCTP message is always in six-decimal subunits. <Note> Stellar wallets and SDKs often display seven fractional digits. Use six-decimal subunits in `amount` when handling CCTP messages offchain. </Note> ### Stellar as the source When Stellar is the source blockchain, the burn debits only through the sixth decimal digit of the user's balance. Anything in the seventh decimal place stays in the user's account. 1. A user bridges **0.1234567 USDC** from Stellar to the destination blockchain. 2. Stellar burns **0.1234560 USDC**. 3. **0.0000007 USDC** stays in the user's Stellar account. 4. The CCTP message `amount` is **123456** (six-decimal subunits). 5. The destination blockchain mints **0.123456 USDC** to the recipient. ### Stellar as the destination When Stellar is the destination blockchain, the mint converts the six-decimal message `amount` into seven by scaling the integer by 10 (for example, `123456` becomes `1234560` seven-decimal subunits). 1. A user bridges **0.123456 USDC** from the source blockchain to Stellar. 2. The CCTP message `amount` is **123456** (six-decimal subunits). 3. Stellar mints **0.1234560 USDC** to the recipient. # CCTP Stellar contracts and interfaces Source: https://developers.circle.com/cctp/references/stellar-contracts Contracts for CCTP support on the Stellar network ## Overview Stellar CCTP contracts run on Soroban, Stellar's smart contracts platform. CCTP message fields use 32-byte address encodings. CCTP treats `mintRecipient` as a contract address. If the recipient is a Stellar user or [muxed](https://developers.stellar.org/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos#muxed-accounts) account instead, hook data can carry a `forwardRecipient` `strkey` so the forwarder can send funds to that address. To align with Stellar address encoding while keeping parity with EVM and other non-EVM blockchains, CCTP uses three contracts: * `TokenMessengerMinter`: consolidates the responsibilities of `TokenMessengerV2` (burn + send) and `TokenMinterV2` (receive + mint). On mint, `mintRecipient` is treated as a contract address and hooks supply the `forwardRecipient` when needed. * `MessageTransmitter`: provides the messaging layer that emits or receives attested messages and delivers them to `TokenMessengerMinter` (including `receive_message` for forwarder flows). * `CctpForwarder`: receives minted USDC and forwards it to `forwardRecipient` in hook data. ## Mainnet contract addresses | Contract | [Domain](/cctp/concepts/supported-chains-and-domains#domain-identifiers) | Address | | :- | :- | :- | | `TokenMessengerMinter` | 27 | [`CAE2G5Z77UP7GYPYGFOWFGW7C7J6I4YP2AFGSADRKQY62SYUFLPNFTXL`](https://stellar.expert/explorer/public/contract/CAE2G5Z77UP7GYPYGFOWFGW7C7J6I4YP2AFGSADRKQY62SYUFLPNFTXL) | | `MessageTransmitter` | 27 | [`CACMENFFJPJMSDAJQLX4R7K3SFZIW2LJSE3R2UMLGSWHFHS353FVXAZV`](https://stellar.expert/explorer/public/contract/CACMENFFJPJMSDAJQLX4R7K3SFZIW2LJSE3R2UMLGSWHFHS353FVXAZV) | | `CctpForwarder` | 27 | [`CBZL2IH7F6BIDAA3WBNXYKIXSATJGMSW7K5P5MJ6STX5RXN47TZJDF5T`](https://stellar.expert/explorer/public/contract/CBZL2IH7F6BIDAA3WBNXYKIXSATJGMSW7K5P5MJ6STX5RXN47TZJDF5T) | ## Testnet contract addresses | Contract | [Domain](/cctp/concepts/supported-chains-and-domains#domain-identifiers) | Address | | :- | :- | :- | | `TokenMessengerMinter` | 27 | [`CDNG7HXAPBWICI2E3AUBP3YZWZELJLYSB6F5CC7WLDTLTHVM74SLRTHP`](https://stellar.expert/explorer/testnet/contract/CDNG7HXAPBWICI2E3AUBP3YZWZELJLYSB6F5CC7WLDTLTHVM74SLRTHP) | | `MessageTransmitter` | 27 | [`CBJ6MTCKKZG73PMDZCJMSFRD7DQEMI4FKDH7CGDSV4W6FHCRBCQAVVJY`](https://stellar.expert/explorer/testnet/contract/CBJ6MTCKKZG73PMDZCJMSFRD7DQEMI4FKDH7CGDSV4W6FHCRBCQAVVJY) | | `CctpForwarder` | 27 | [`CA66Q2WFBND6V4UEB7RD4SAXSVIWMD6RA4X3U32ELVFGXV5PJK4T4VSZ`](https://stellar.expert/explorer/testnet/contract/CA66Q2WFBND6V4UEB7RD4SAXSVIWMD6RA4X3U32ELVFGXV5PJK4T4VSZ) | ## CCTP interface * `TokenMessengerMinter`: initiates crosschain burns and mints tokens upon attested message receipt. * `MessageTransmitter`: emits messages, verifies attestations, and routes verified messages to the recipient contract. * `CctpForwarder`: completes mint and forward in one transaction when hook data supplies a `forwardRecipient` `strkey`. ### TokenMessengerMinter interface The `TokenMessengerMinter` contract consolidates the roles of both `TokenMessengerV2` and `TokenMinterV2` found on EVM chains. It handles USDC burns, message emission, and token minting once crosschain messages are attested by Circle's Iris service. On Stellar it assumes `mintRecipient` is a contract. Account recipients use `CctpForwarder` and hook-qualified `forwardRecipient` bytes. | Function | Description | Notes | | :- | :- | :- | | `deposit_for_burn` | Burns USDC and emits a crosschain message for minting on another domain. | Standard CCTP transfer initiation. | | `deposit_for_burn_with_hook` | Same as `deposit_for_burn`, but attaches custom metadata (`hook_data`). | Used for programmable transfers and Stellar forwarding. | | `handle_receive_finalized_message` | Mints USDC upon receiving a fully finalized message. | Called by `MessageTransmitter`. | | `handle_receive_unfinalized_message` | Processes partially finalized ("Fast Burn") messages. | Enables faster crosschain transfers. | | `message_body_version` | Returns supported message format version. | Used for compatibility checks. | | `local_message_transmitter` | Returns the linked `MessageTransmitter` address. | Must match configured domain transmitter. | ### MessageTransmitter interface The `MessageTransmitter` contract provides the core messaging layer for CCTP on Stellar. It is responsible for emitting, receiving, and validating crosschain messages, enforcing attestation rules, and ensuring message uniqueness. | Function | Description | Notes | | :- | :- | :- | | `send_message` | Sends a crosschain message with specified domain, recipient, and message body. | Core function for outgoing CCTP messages. | | `receive_message` | Validates a message and its attestation; delivers message body to the recipient. | Called by an offchain forwarding service (or by `CctpForwarder` in the forwarder flow) with an attestation from Circle. | | `get_max_message_body_size` | Returns the maximum allowed message size. | Used by offchain components for validation. | | `is_nonce_used` | Checks if a message nonce has been processed already. | Prevents message replay. | | `get_local_domain` | Returns this contract's domain ID. | Expected to be 27 for Stellar. | | `get_version` | Returns protocol version supported by this transmitter. | Used by Iris attestation service. | ### CctpForwarder interface The `CctpForwarder` contract calls `receive_message` on `MessageTransmitter`, takes the mint, and transfers USDC to `forwardRecipient` parsed from `hook_data`. See [Hook format](/cctp/references/stellar#hook-format) for details. | Function | Description |
프로젝트로 돌아가기