Circle USYC 백서

Circle USYC

원문

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

f it failed, retry the burn. | | Attestation status remains `pending` | Waiting for block confirmations | Wait for sufficient confirmations based on finality threshold | | Have attestation but mint fails | Gas issues, incorrect parameters, or nonce already used | See [Retry a failed mint](/cctp/howtos/retry-failed-mint) | | `expirationBlock` has passed before you submitted the mint | Attestations encode an `expirationBlock` 24 hours in the future. Once that block passes on the destination blockchain, the existing attestation can no longer be used to mint. | Call [`POST /v2/reattest/{nonce}`](/api-reference/cctp/all/reattest-message) to request a new attestation with a refreshed `expirationBlock`, then poll [`GET /v2/messages`](/api-reference/cctp/all/get-messages-v2) and re-mint. | <Note> **Expired messages are not permanently stuck** There is no deadline after which re-attestation becomes unavailable. As long as the original burn transaction still exists on the source blockchain, you can request a fresh attestation at any time using the [re-attest endpoint](/api-reference/cctp/all/reattest-message). </Note> # Withdraw USDC from HyperCore to EVM chains Source: https://developers.circle.com/cctp/howtos/withdraw-usdc-from-hypercore-to-evm This guide shows how to withdraw USDC from a HyperCore `spot` or `perp` balance to an external EVM blockchain (such as Arbitrum, Ethereum, or Base) using the HyperCore API. Withdrawals from HyperCore to EVM chains default to the Fast Transfer method, due to the fast finality of HyperEVM. The withdrawal process: 1. Debits your HyperCore balance (`spot` or `perp`) 2. Routes through HyperEVM where USDC is burned via CCTP 3. CCTP attests to the burn and mints on the destination chain 4. If automatic forwarding is enabled, the recipient receives funds directly <Note> Withdrawals include a HyperCore fee and (if using the Forwarding Service) a CCTP forwarding fee. Ensure your withdrawal amount exceeds combined fees depending on your transfer. </Note> ## Important considerations Keep these things in mind when withdrawing USDC from HyperCore to EVM chains: * **Data field:** If the data field is empty, the `CoreDepositWallet` automatically sets a default hook that enables automatic message forwarding on the destination blockchain, provided that the blockchain supports CCTP forwarding. If the data field is not empty, its contents are passed to the CCTP protocol as the value of the `hookData` field. * **Destination caller:** The CCTP `destinationCaller` is always set to the zero address. Passing your own hook data means that anyone can receive the message on the destination blockchain. * **Withdrawal fees:** In addition to the `maxFee` charged by the HyperCore blockchain, an additional fixed forwarding fee may be charged by CCTP if automatic forwarding is enabled. The forwarding fee amount depends on the destination blockchain and can be viewed by querying the `CoreDepositWallet` smart contract. Initially, the fee for forwarding to Arbitrum is 0.2 USDC. If the withdrawal includes custom hook data, the forwarding fee is not set and users have to receive the message on the destination blockchain themselves. * **Minimum withdrawal amount:** If the withdrawal amount is less than the required forwarding fee, the transaction on HyperEVM reverts. Make sure the withdrawal amount is larger than the fees. ## Prerequisites Before you begin, ensure that you've: * Installed [Node.js v22.18+](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 npm install -D typescript @types/node ``` * Created a `.env` file with required environment variables: ```text theme={null} PRIVATE_KEY=0x... DESTINATION_RECIPIENT=0x... # Recipient address on destination chain ``` ## Steps Use the following steps to withdraw USDC from HyperCore to an EVM blockchain. ### Step 1. 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 address on the destination blockchain * `addressEncoding`: `hex` for EVM chains or `base58` for Solana * `destinationChainId`: The CCTP destination domain ID (for example, `3` for Arbitrum, `0` for Ethereum, `6` for Base) * `gasLimit`: Gas limit for the transaction on the destination chain * `data`: CCTP hook data (use `"0x"` for automatic forwarding) * `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: "0x1234567890123456789012345678901234567890", addressEncoding: "hex", destinationChainId: 3, // Arbitrum CCTP domain gasLimit: 200000, data: "0x", // "0x" enables automatic forwarding on the destination nonce: Date.now(), }; ``` ### Step 2. 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 = parseInt(signatureChainId, 16); 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: "0x...", addressEncoding: "hex", destinationChainId: 3, gasLimit: BigInt(200000), data: "0x", 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 3. 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: timestamp, 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 an external EVM blockchain. By default, it withdraws 10 USDC from your perp balance to Arbitrum testnet with automatic forwarding enabled. For other destination chains, update `destinationChainId` (CCTP domain ID) and `signatureChainId` (the ID of the chain used when signing in hexadecimal format) accordingly. ```ts TypeScript expandable theme={null} /** * Script: Withdraw USDC from HyperCore to EVM chain * - Signs EIP-712 sendToEvmWithData action * - Submits to Hyperliquid /exchange API */ import { Wallet, Signature } from "ethers"; // -------- 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 // Destination parameters destinationRecipient: process.env.DESTINATION_RECIPIENT as string, destinationChainId: Number(process.env.DESTINATION_CHAIN_ID || 3), // 3 = Arbitrum addressEncoding: process.env.ADDRESS_ENCODING || "hex", gasLimit: Number(process.env.GAS_LIMIT || 200000), data: process.env.DATA || "0x", // "0x" enables automatic forwarding // 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.destinationRecipient) { throw new Error("Set DESTINATION_RECIPIENT"); } 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("Destination recipient:", config.destinationRecipient); 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: config.destinationRecipient, addressEncoding: config.addressEncoding, destinationChainId: config.destinationChainId, gasLimit: BigInt(config.gasLimit), data: config.data, 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: config.destinationRecipient, addressEncoding: config.addressEncoding, destinationChainId: config.destinationChainId, gasLimit: config.gasLimit, data: config.data, 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} node --env-file=.env script.ts ``` # Withdraw USDC from HyperCore to HyperEVM Source: https://developers.circle.com/cctp/howtos/withdraw-usdc-from-hypercore-to-hyperevm This guide shows how to withdraw USDC from a HyperCore `spot` or `perp` balance to HyperEVM using the HyperCore API. <Note> You can only withdraw USDC from HyperCore to the same address on HyperEVM. It's not possible to specify a different recipient address. </Note> ## Prerequisites Before you begin, ensure that you've: * Installed [Node.js v22.18+](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 npm install -D typescript @types/node ``` * Created a `.env` file with required environment variables: ```text theme={null} PRIVATE_KEY=0x... ``` ## Steps Use the following steps to withdraw USDC from HyperCore to HyperEVM. ### Step 1. Construct the `sendAsset` action Create a `sendAsset` action object with the following parameters: * `type`: `sendAsset` * `hyperliquidChain`: `Mainnet` (or `Testnet` for testnet) * `signatureChainId`: An EVM chain ID used for EIP-712 replay protection. Must match between signing and the action payload, but can be any valid chain ID (for example, `"0xa4b1"` for Arbitrum) * `destination`: The USDC token system address (`0x2000000000000000000000000000000000000000`) * `sourceDex`: `"spot"` to withdraw from spot balance, or `""` for perp balance * `destinationDex`: `"spot"` * `token`: `USDC` * `amount`: The amount of USDC as a human-readable string (for example, `"10"` for 10 USDC) * `fromSubAccount`: Set to `""` for main account, or the subaccount address * `nonce`: Current timestamp in milliseconds ```ts TypeScript theme={null} const action = { type: "sendAsset", hyperliquidChain: "Testnet", signatureChainId: "0xa4b1", // EVM chain ID for EIP-712 replay protection destination: "0x2000000000000000000000000000000000000000", sourceDex: "", // "" for perp, "spot" for spot destinationDex: "spot", token: "USDC", amount: "10", // 10 USDC (human-readable) fromSubAccount: "", nonce: Date.now(), }; ``` ### Step 2. 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"; async function signSendAssetAction( action: any, privateKey: string, ): Promise<{ r: string; s: string; v: number }> { const wallet = new Wallet(privateKey); // Convert chainId from hex to number const chainId = parseInt(action.signatureChainId, 16); // EIP-712 domain const domain = { name: "HyperliquidSignTransaction", version: "1", chainId, verifyingContract: "0x0000000000000000000000000000000000000000", }; // EIP-712 types (must match Hyperliquid SDK's SEND_ASSET_SIGN_TYPES) const types = { "HyperliquidTransaction:SendAsset": [ { name: "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 value = { hyperliquidChain: action.hyperliquidChain, destination: action.destination, sourceDex: action.sourceDex, destinationDex: action.destinationDex, token: action.token, amount: action.amount, fromSubAccount: action.fromSubAccount, nonce: BigInt(action.nonce), }; // Sign the typed data const signature = await wallet.signTypedData(domain, types, value); // Split signature into r, s, v components const sig = Signature.from(signature); return { r: sig.r, s: sig.s, v: sig.v, }; } ``` ### Step 3. Submit the signed action to the exchange API Call the [exchange](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#send-asset) endpoint with the action, nonce, and signature. ```ts TypeScript theme={null} async function submitSendAsset( action: any, signature: { r: string; s: string; v: number }, ) { // Use https://api.hyperliquid.xyz for mainnet const response = await fetch("https://api.hyperliquid-testnet.xyz/exchange", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ action: action, nonce: action.nonce, signature: signature, }), }); const data = await response.json(); if (data.status === "ok") { console.log("Withdrawal successful:", data); return data; } else { throw new Error(`Withdrawal failed: ${JSON.stringify(data)}`); } } ``` ## Full example code The following is a complete example of how to withdraw USDC from HyperCore to HyperEVM. By default, it withdraws 10 USDC from your perp balance to HyperEVM testnet. ```ts TypeScript expandable theme={null} /** * Script: Withdraw USDC from HyperCore to HyperEVM * - Constructs a SendAsset action * - Signs the action using EIP-712 * - Submits the signed action to the HyperCore API */ import { Wallet, Signature } from "ethers"; // -------- Configuration -------- const config = { privateKey: process.env.PRIVATE_KEY as string, // Transfer parameters amount: process.env.AMOUNT || "10", // 10 USDC (human-readable) sourceDex: process.env.SOURCE_DEX || "", // "" for perp, "spot" for spot // Hyperliquid environment isMainnet: String(process.env.HL_IS_MAINNET || "false").toLowerCase() === "true", }; // System address for USDC token on HyperCore const USDC_SYSTEM_ADDRESS = "0x2000000000000000000000000000000000000000"; // -------- Main Function -------- async function main() { if (!config.privateKey) { throw new Error("Set PRIVATE_KEY"); } const apiUrl = config.isMainnet ? "https://api.hyperliquid.xyz" : "https://api.hyperliquid-testnet.xyz"; const hyperliquidChain = config.isMainnet ? "Mainnet" : "Testnet"; const signingChainId = "0xa4b1"; // EVM chain ID for EIP-712 signing (any valid chain ID works) const chainId = parseInt(signingChainId, 16); const timestamp = Date.now(); const wallet = new Wallet(config.privateKey); console.log("Withdrawing from HyperCore to HyperEVM:", hyperliquidChain); console.log("User Address:", wallet.address); console.log("Source balance:", config.sourceDex || "perp"); console.log("Amount (USDC):", config.amount); // EIP-712 Domain const domain = { name: "HyperliquidSignTransaction", version: "1", chainId, verifyingContract: "0x0000000000000000000000000000000000000000", }; // Build action for signing const actionForSigning = { hyperliquidChain, signatureChainId: signingChainId, destination: USDC_SYSTEM_ADDRESS, sourceDex: config.sourceDex, destinationDex: "spot", token: "USDC", amount: config.amount, fromSubAccount: "", nonce: timestamp, }; // EIP-712 Types (must match Hyperliquid SDK's SEND_ASSET_SIGN_TYPES) const types = { "HyperliquidTransaction:SendAsset": [ { nam
프로젝트로 돌아가기