Circle USYC 백서

Circle USYC

원문

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

e: "hyperliquidChain", type: "string" }, { name: "destination", type: "string" }, { name: "sourceDex", type: "string" }, { name: "destinationDex", type: "string" }, { name: "token", type: "string" }, { name: "amount", type: "string" }, { name: "fromSubAccount", type: "string" }, { name: "nonce", type: "uint64" }, ], }; // Message to sign (only fields defined in EIP-712 types, not signatureChainId) const message = { hyperliquidChain: actionForSigning.hyperliquidChain, destination: actionForSigning.destination, sourceDex: actionForSigning.sourceDex, destinationDex: actionForSigning.destinationDex, token: actionForSigning.token, amount: actionForSigning.amount, fromSubAccount: actionForSigning.fromSubAccount, nonce: BigInt(actionForSigning.nonce), }; // Sign the message using EIP-712 const sigHex = await wallet.signTypedData(domain, types, message); const sig = Signature.from(sigHex); // Build action payload for API (includes type and signatureChainId) const action: any = { type: "sendAsset", ...actionForSigning, }; // Submit to Hyperliquid exchange API const response = await fetch(`${apiUrl}/exchange`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, nonce: timestamp, signature: { r: sig.r, s: sig.s, v: sig.v }, }), }); const result = await response.json(); console.log("\nStatus:", response.status); console.log("Response:", JSON.stringify(result, null, 2)); if (response.status === 200 && result.status === "ok") { console.log("\nWithdrawal initiated successfully"); } else { throw new Error(`Withdrawal failed: ${JSON.stringify(result)}`); } } // Run main().catch((error) => { console.error("Error:", error.message); process.exit(1); }); ``` Run the script: ```bash theme={null} node --env-file=.env script.ts ``` # How-to: Withdraw USDC from HyperCore to Solana Source: https://developers.circle.com/cctp/howtos/withdraw-usdc-from-hypercore-to-solana Withdraw USDC from a HyperCore spot or perp balance to a Solana associated token account using CCTP. Withdraw USDC from a HyperCore `spot` or `perp` balance directly to a Solana associated token account (ATA) using CCTP. The withdrawal burns USDC on HyperEVM and mints it on Solana in a single operation. For withdrawals to EVM blockchains, see [Withdraw USDC from HyperCore to EVM chains](./withdraw-usdc-from-hypercore-to-evm). For CCTP domain IDs, see [Supported blockchains and domains](../concepts/supported-chains-and-domains). ## Prerequisites Before you begin, ensure that you've: * Installed [Node.js v22+](https://nodejs.org/) * Prepared an EVM wallet with the private key available * Funded your HyperCore account with USDC in either `spot` or `perp` balance * Created a new Node project and installed dependencies: ```bash theme={null} npm install ethers @solana/web3.js @solana/spl-token npm install -D tsx typescript @types/node ``` * Created a `.env` file with required environment variables. Open the file in your editor and add your private key and Solana addresses: ```text theme={null} PRIVATE_KEY=0x... SOLANA_ATA_OWNER=... # Solana wallet address that owns the recipient ATA SOLANA_USDC_MINT=... # USDC mint address on the target Solana network ``` Keep your private key safe and never commit `.env` to version control. Add `.env` to your `.gitignore` file. <Warning> Withdrawals to Solana have a higher CCTP forwarding fee for handling Associated Token Account creation. If the withdrawal amount is less than this fee, the transaction reverts on HyperEVM. </Warning> ## Steps ### Step 1. Derive the recipient ATA and createATA hook data For Solana, the mint recipient is the associated token account for the USDC mint and recipient owner. The `createATA` hook data tells CCTP to create that ATA when needed. ```ts TypeScript theme={null} import { getAssociatedTokenAddressSync } from "@solana/spl-token"; import { PublicKey } from "@solana/web3.js"; function encodeCreateAtaHookData(ataOwner: PublicKey): `0x${string}` { const magic = Buffer.from("cctp-forward", "utf-8") .toString("hex") .padEnd(48, "0"); const createAta = "01"; const owner = Buffer.from(ataOwner.toBytes()).toString("hex"); return `0x${magic}${createAta}${owner}`; } const ataOwner = new PublicKey(process.env.SOLANA_ATA_OWNER as string); const usdcMint = new PublicKey(process.env.SOLANA_USDC_MINT as string); const recipientAta = getAssociatedTokenAddressSync(usdcMint, ataOwner); const createAtaHookData = encodeCreateAtaHookData(ataOwner); ``` ### Step 2. Construct the `sendToEvmWithData` action Create a `sendToEvmWithData` action object with the following parameters: * `type`: `sendToEvmWithData` * `hyperliquidChain`: `Mainnet` (or `Testnet` for testnet) * `signatureChainId`: The ID of the chain used when signing in hexadecimal format (for example, `"0xa4b1"` for Arbitrum). * `token`: `USDC` * `amount`: The amount of USDC as a string (for example, `"10"` for 10 USDC, `"1.5"` for 1.5 USDC) * `sourceDex`: `"spot"` to withdraw from spot balance, or `""` for `perp` balance * `destinationRecipient`: The recipient ATA on Solana. This is the ATA for the recipient owner and the USDC mint, not the owner wallet address. * `addressEncoding`: `base58` for Solana recipients * `destinationChainId`: `5`, the CCTP destination domain ID for Solana * `gasLimit`: Gas limit for the transaction on the destination blockchain * `data`: The `createATA` hook data from the previous step, which includes the recipient owner public key as `ataOwner`. The CCTP `destinationCaller` is always set to the zero address, so any caller can submit the CCTP receive instruction on Solana. The USDC still mints only to `destinationRecipient`. * `nonce`: Current timestamp in milliseconds ```ts TypeScript theme={null} // Example action payload for sendToEvmWithData const action = { type: "sendToEvmWithData", hyperliquidChain: "Mainnet", signatureChainId: "0xa4b1", // Chain ID used when signing token: "USDC", amount: "10", // 10 USDC sourceDex: "spot", // or "" for perp destinationRecipient: recipientAta.toBase58(), addressEncoding: "base58", destinationChainId: 5, // Solana CCTP domain gasLimit: 200000, data: createAtaHookData, nonce: Date.now(), }; ``` ### Step 3. Sign the action using EIP-712 Sign the action using the EIP-712 typed data signing standard. The signature proves that you authorize this withdrawal. The signing domain should include: * `name`: `"HyperliquidSignTransaction"` * `version`: `"1"` * `chainId`: The chain ID from `signatureChainId` (as a number) * `verifyingContract`: `"0x0000000000000000000000000000000000000000"` ```ts TypeScript theme={null} import { Wallet, Signature } from "ethers"; // Sign the action using EIP-712 const wallet = new Wallet(privateKey); const chainId = Number(BigInt(action.signatureChainId)); const domain = { name: "HyperliquidSignTransaction", version: "1", chainId, verifyingContract: "0x0000000000000000000000000000000000000000", }; const types = { "HyperliquidTransaction:SendToEvmWithData": [ { name: "hyperliquidChain", type: "string" }, { name: "token", type: "string" }, { name: "amount", type: "string" }, { name: "sourceDex", type: "string" }, { name: "destinationRecipient", type: "string" }, { name: "addressEncoding", type: "string" }, { name: "destinationChainId", type: "uint32" }, { name: "gasLimit", type: "uint64" }, { name: "data", type: "bytes" }, { name: "nonce", type: "uint64" }, ], }; const message = { hyperliquidChain: "Mainnet", token: "USDC", amount: "10", sourceDex: "spot", destinationRecipient: recipientAta.toBase58(), addressEncoding: "base58", destinationChainId: 5, gasLimit: BigInt(200000), data: createAtaHookData, nonce: BigInt(Date.now()), }; const sigHex = await wallet.signTypedData(domain, types, message); const sig = Signature.from(sigHex); const signature = { r: sig.r, s: sig.s, v: sig.v }; ``` ### Step 4. Submit the signed action to the exchange API Call the [exchange](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint) endpoint with the action, nonce, and signature. ```ts TypeScript theme={null} const response = await fetch("https://api.hyperliquid.xyz/exchange", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, nonce: action.nonce, signature, }), }); const result = await response.json(); if (response.status === 200 && result.status === "ok") { console.log("Withdrawal initiated successfully:", result); } else { throw new Error(`Withdrawal failed: ${JSON.stringify(result)}`); } ``` ## Full example code The following is a complete example of how to withdraw USDC from HyperCore to Solana. By default, it withdraws 10 USDC from your perp balance. Update `destinationChainId` only if the CCTP Solana domain changes, and set `signatureChainId` to the ID of the chain used when signing in hexadecimal format. ```ts TypeScript expandable theme={null} /** * Script: Withdraw USDC from HyperCore to Solana * - Derives the recipient associated token account * - Encodes createATA hook data * - Signs EIP-712 sendToEvmWithData action * - Submits to Hyperliquid /exchange API */ import { getAssociatedTokenAddressSync } from "@solana/spl-token"; import { PublicKey } from "@solana/web3.js"; import { Wallet, Signature } from "ethers"; function encodeCreateAtaHookData(ataOwner: PublicKey): `0x${string}` { const magic = Buffer.from("cctp-forward", "utf-8") .toString("hex") .padEnd(48, "0"); const createAta = "01"; const owner = Buffer.from(ataOwner.toBytes()).toString("hex"); return `0x${magic}${createAta}${owner}`; } // -------- Configuration -------- const config = { privateKey: process.env.PRIVATE_KEY as string, // Transfer parameters amount: process.env.AMOUNT || "10", // 10 USDC sourceDex: process.env.SOURCE_DEX || "", // "" for perp, "spot" for spot // Solana destination parameters ataOwner: process.env.SOLANA_ATA_OWNER as string, usdcMint: process.env.SOLANA_USDC_MINT as string, destinationChainId: 5, // Solana CCTP domain addressEncoding: "base58", gasLimit: Number(process.env.GAS_LIMIT || 200000), // Hyperliquid environment isMainnet: String(process.env.HL_IS_MAINNET || "false").toLowerCase() === "true", signatureChainId: "0xa4b1", // Chain ID used when signing }; // -------- Main Function -------- async function main() { // Validate required parameters if (!config.privateKey) { throw new Error("Set PRIVATE_KEY"); } if (!config.ataOwner) { throw new Error("Set SOLANA_ATA_OWNER"); } if (!config.usdcMint) { throw new Error("Set SOLANA_USDC_MINT"); } const ataOwner = new PublicKey(config.ataOwner); const usdcMint = new PublicKey(config.usdcMint); const recipientAta = getAssociatedTokenAddressSync(usdcMint, ataOwner); const createAtaHookData = encodeCreateAtaHookData(ataOwner); const apiUrl = config.isMainnet ? "https://api.hyperliquid.xyz" : "https://api.hyperliquid-testnet.xyz"; const hyperliquidChain = config.isMainnet ? "Mainnet" : "Testnet"; const chainId = parseInt(config.signatureChainId, 16); const timestamp = Date.now(); console.log("Withdrawing from HyperCore:", hyperliquidChain); console.log("Source balance:", config.sourceDex || "perp"); console.log("Amount (USDC):", config.amount); console.log("Solana ATA owner:", ataOwner.toBase58()); console.log("Destination recipient ATA:", recipientAta.toBase58()); console.log("Destination chain ID:", config.destinationChainId); console.log("Gas limit:", config.gasLimit); // EIP-712 Domain const domain = { name: "HyperliquidSignTransaction", version: "1", chainId, verifyingContract: "0x0000000000000000000000000000000000000000", }; // EIP-712 Types const types = { "HyperliquidTransaction:SendToEvmWithData": [ { name: "hyperliquidChain", type: "string" }, { name: "token", type: "string" }, { name: "amount", type: "string" }, { name: "sourceDex", type: "string" }, { name: "destinationRecipient", type: "string" }, { name: "addressEncoding", type: "string" }, { name: "destinationChainId", type: "uint32" }, { name: "gasLimit", type: "uint64" }, { name: "data", type: "bytes" }, { name: "nonce", type: "uint64" }, ], }; // Message to sign const message = { hyperliquidChain, token: "USDC", amount: config.amount, sourceDex: config.sourceDex, destinationRecipient: recipientAta.toBase58(), addressEncoding: config.addressEncoding, destinationChainId: config.destinationChainId, gasLimit: BigInt(config.gasLimit), data: createAtaHookData, nonce: BigInt(timestamp), }; // Sign the message using EIP-712 const wallet = new Wallet(config.privateKey); const sigHex = await wallet.signTypedData(domain, types, message); const sig = Signature.from(sigHex); // Build action payload const action = { type: "sendToEvmWithData", hyperliquidChain, signatureChainId: config.signatureChainId, token: "USDC", amount: config.amount, sourceDex: config.sourceDex, destinationRecipient: recipientAta.toBase58(), addressEncoding: config.addressEncoding, destinationChainId: config.destinationChainId, gasLimit: config.gasLimit, data: createAtaHookData, nonce: timestamp, }; // Submit to Hyperliquid exchange API const response = await fetch(`${apiUrl}/exchange`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, nonce: timestamp, signature: { r: sig.r, s: sig.s, v: sig.v }, }), }); const result = await response.json(); console.log("\nStatus:", response.status); console.log("Response:", JSON.stringify(result, null, 2)); if (response.status === 200 && result.status === "ok") { console.log("\nWithdrawal initiated successfully"); } else { throw new Error(`Withdrawal failed: ${JSON.stringify(result)}`); } } // Run main().catch((error) => { console.error("Error:", error.message); process.exit(1); }); ``` Run the script: ```bash theme={null} npx tsx script.ts ``` # Migrate from CCTP V1 (Legacy) to V2 Source: https://developers.circle.com/cctp/migration-from-v1-to-v2 Complete migration guide for developers upgrading CCTP integrations This guide provides a summary of the breaking changes when migrating from Cross-Chain Transfer Protocol (CCTP) V1 to V2. CCTP V2 introduces enhancements including Fast Transfer, Hooks features, and improved API endpoints, but requires updating your integration due to breaking changes. <Important> **Important**: CCTP V2 isn't backward compatible with V1. It uses separate contracts, APIs, and transfer speeds. It also introduces new blockchain support, while deprecating some chains. Plan for a complete integration update rather than incremental changes. Failure to migrate will eventually result in loss of crosschain capabilities for your integration. </Important> The [Bridge](https://docs.arc.io/app-kit/bridge) capability in Arc App Kits can help simplify your migration to CCTP V2. See the [Migrating with App Kits](#migrating-with-app-kits) section for more information. ## V1 deprecation Circle is deprecating CCTP V1 to focus on the newer version, which is upgradable and provides a faster, more secure, and more robust crosschain experience across a wider network of blockchains. ### Naming changes CCTP V2 is now referred to as CCTP (except in this document). The V1 version of CCTP is now CCTP V1 (Legacy). ### Deprecation timeline CCTP V1 (Legacy) deprecation begins October 31, 2026 and completes on December 1, 2026, in favor of CCTP V2. CCTP V2 contracts are available on all CCTP V1 chains except for Noble and Sui. Sui will be supported by V2 before the deprecation begins. ### Access to funds You will not lose access to funds during the V1 phase out. All pending redemptions will remain available as CCTP V1 (legacy) begins its phase out. Circle will maintain minter allowances greater than the total of pending attestations, ensuring every redemption can be processed before V1 contracts are fully paused. The deprecation process is designed to wind down activity gradually, message limits will tighten over time until no new burns can be initiated, bringing transfer volume to zero before contracts are fully paused. ### Additional resources In addition to this guide and [Arc App Kits](https://docs.arc.io/app-kit), you can contact the Circle team on the [BuildOnCircle Discord](https://discord.com/invite/buildoncircle) for questions and migration support. ## Summary of breaking changes The latest version of CCTP introduces architectural changes that make it incompatible with V1 integrations. You must update your implementation to use the new contracts, APIs, and transfer speeds. Additionally, the overall flow of the protocol has been streamlined, which means you need to update your integration to use the new functions. * Contracts are deployed at [different addresses](/cctp/references/contract-addresses) than V1 contracts. You should update your integration to point to the new contract addresses. * [Contract interfaces](/cctp/references/contract-interfaces) have changed. Importantly, the [`depositForBurn` function](/cctp/references/contract-interfaces#depositforburn) now takes additional parameters. You should update your integration to use the new ABIs and contract calls. * CCTP now allows you to specify a transfer speed. The `finalityThreshold` parameter specifies whether the transfer should be a [Fast Transfer](/cctp/concepts/finality-and-block-confirmations#fast-transfer-attestation-times) or a [Standard Transfer](/cctp/concepts/finality-and-block-confirmations#standard-transfer-attestation-times). * You no longer need to extract the message from the onchain transaction to fetch an attestation. Instead, you can call the new `/v2/messages/{sourceDomainId}` endpoint with the transaction hash to get the message and attestation in a single call. * API endpoints have changed. The new `/v2/` endpoints have different functions than the old `/v1/` endpoints. You should update your integration to use the new endpoints. Review the [CCTP API reference](/api-reference/cctp/all/get-public-keys-v2) for details on the changes to the CCTP offchain API. * [Fees](/cctp/concepts/fees) have been introduced. Fast Transfer has a variable fee based on the source chain. You should update your integration to account for the new fees. ## Migrating with App Kits [Arc App Kits](https://docs.arc.io/app-kit) provides a simplified migration path by abstracting routine setup steps and standardizing bridging flows. This enables you to integrate bridging operations with minimal code. ### Benefits of using App Kits to bridge * **No contract management**: App Kits handles contract addresses, ABIs, and function calls for you. * **No attestation polling**: Automatically retrieves attestations without manual API calls. * **Built-in CCTP features**: Access Fast Transfer and other capabilities through simple configuration. * **Type-safe interface**: Compatible with `viem` and `ethers` for safer development. * **Fee collection**: Optionally collect fees from transfers to monetize your application. ### Example migration Replace manual contract calls and API polling with a single method: ```typescript theme={null} import { AppKit } from "@circle-fin/app-kit"; import { createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2"; // Initialize the App Kit SDK const kit = new AppKit(); // Create adapter for your wallet const adapter = createAdapterFromPrivateKey({ privateKey: process.env.PRIVATE_KEY as string, }); // Transfer USDC with Fast Transfer const result = await kit.bridge({ from: { adapter, chain: "Ethereum" }, to: { adapter, chain: "Base" }, amount: "100", config: { transferSpeed: "FAST", // Use Fast Transfer maxFee: "5000000", // Max 5 USDC fee (optional) }, }); // Result includes transaction details and explorer URLs console.log("Transfer complete:", result.steps); ```
프로젝트로 돌아가기