Created
October 10, 2025 05:37
-
-
Save sedhu-orbitx/07c5e82327bc91277eb76e23f1ddf2b4 to your computer and use it in GitHub Desktop.
evm usdt0 poc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Options } from "@layerzerolabs/lz-v2-utilities"; | |
| import { ethers, MaxUint256, parseEther, parseUnits, zeroPadValue } from "ethers"; | |
| import { LAYERZERO_EIDS, USDT0_CONTRACTS } from "./constants"; | |
| const MAX_UINT256 = MaxUint256; | |
| // Helper function to calculate minimum amount with slippage | |
| function calculateMinAmount(amountLD: bigint, slippageBps: number): bigint { | |
| if (slippageBps >= 10000) { | |
| throw new Error("Slippage cannot be 100% or more"); | |
| } | |
| const slippageAmount = (amountLD * BigInt(slippageBps)) / 10000n; | |
| return amountLD - slippageAmount; | |
| } | |
| // Helper function to build LayerZero transfer options | |
| function buildTransferOptions( | |
| recipient: string, | |
| options?: { | |
| nativeDropAmount?: string; | |
| composeGasLimit?: number; | |
| lzReceiveGasLimit?: number; | |
| } | |
| ): string { | |
| const builder = Options.newOptions(); | |
| // Add lzReceive option with gas limit | |
| const lzReceiveGasLimit = options?.lzReceiveGasLimit || 200_000; | |
| builder.addExecutorLzReceiveOption(lzReceiveGasLimit, 0); | |
| // Add compose option if needed (for multi-hop scenarios) | |
| const composeGasLimit = options?.composeGasLimit || 500_000; | |
| builder.addExecutorComposeOption(0, composeGasLimit, 0); | |
| // Add native drop option if specified | |
| if (options?.nativeDropAmount && options.nativeDropAmount !== "0") { | |
| const nativeDropWei = parseEther(options.nativeDropAmount); | |
| const recipientBytes32 = zeroPadValue(recipient, 32); | |
| builder.addExecutorNativeDropOption(nativeDropWei, recipientBytes32); | |
| } | |
| return builder.toHex(); | |
| } | |
| const OFT_ABI = [ | |
| "function quoteOFT(tuple(uint32,bytes32,uint256,uint256,bytes,bytes,bytes)) view returns (tuple(uint256,uint256), tuple(int256,string)[], tuple(uint256,uint256))", | |
| "function quoteSend(tuple(uint32,bytes32,uint256,uint256,bytes,bytes,bytes), bool) view returns (tuple(uint256,uint256))", | |
| "function send(tuple(uint32,bytes32,uint256,uint256,bytes,bytes,bytes), tuple(uint256,uint256), address) payable returns (tuple(bytes32,uint64,tuple(uint256,uint256)), tuple(uint256,uint256))" | |
| ]; | |
| async function sendUSDT0ToAnyEVM( | |
| signer: ethers.Signer, | |
| amount: string, | |
| recipient: string, | |
| eid: number, | |
| srcOftAddress: string, | |
| usdtAddress: string, | |
| options?: { | |
| slippageBps?: number; // Default: 50 (0.5%) | |
| nativeDropAmount?: string; // Default: undefined (disabled) | |
| composeGasLimit?: number; // Default: 500_000 (only if compose needed) | |
| lzReceiveGasLimit?: number; // Default: 200_000 | |
| } | |
| ) { | |
| const usdt = new ethers.Contract(usdtAddress, ["function approve(address,uint256)"], signer); | |
| const oft = new ethers.Contract(srcOftAddress, OFT_ABI, signer); | |
| const amountWei = parseUnits(amount, 6); | |
| const slippageBps = options?.slippageBps || 50; // Default 0.5% | |
| // Approve max amount | |
| // const approveTx = await usdt.approve(srcOftAddress, MAX_UINT256); | |
| // await approveTx.wait(); | |
| // console.log(`Approved ${amount} USDT for ${approveTx.hash}`); | |
| // Step 1: Create initial sendParam with minAmountLD = 0, extraOptions = "0x" | |
| const initialSendParam = [ | |
| eid, // β Correct destination | |
| zeroPadValue(recipient, 32), | |
| amountWei, | |
| 0, // minAmountLD = 0 for initial quote | |
| "0x", // extraOptions = "0x" for initial quote | |
| "0x", // composeMsg = "0x" | |
| "0x" // oftCmd = "0x" | |
| ]; | |
| // Step 2: Call quoteOFT to get oftReceipt | |
| const [, , oftReceipt] = await oft.quoteOFT(initialSendParam); | |
| // Step 3: Calculate slippage-adjusted minAmountLD from oftReceipt.amountReceivedLD | |
| // This ensures we apply slippage to the actual amount that will be received | |
| const minAmountLD = calculateMinAmount(oftReceipt[1], slippageBps); | |
| // Step 4: Build extraOptions with native drop to recipient | |
| const extraOptions = buildTransferOptions(recipient, { | |
| nativeDropAmount: options?.nativeDropAmount, | |
| composeGasLimit: options?.composeGasLimit, | |
| lzReceiveGasLimit: options?.lzReceiveGasLimit | |
| }); | |
| console.log(`π Transfer Details:`); | |
| console.log(` Amount Sent: ${amount} USDT0 (${amountWei.toString()} wei)`); | |
| console.log(` Amount Received: ${ethers.formatUnits(oftReceipt[1], 6)} USDT`); | |
| console.log(` Min Amount: ${ethers.formatUnits(minAmountLD, 6)} USDT0 (${minAmountLD.toString()} wei)`); | |
| console.log(` Slippage: ${slippageBps / 100}%`); | |
| console.log(` Native Drop: ${options?.nativeDropAmount || 'disabled'}`); | |
| console.log(` Extra Options: ${extraOptions}`); | |
| // Step 5: Update sendParam with minAmountLD and extraOptions | |
| const finalSendParam = [ | |
| eid, | |
| zeroPadValue(recipient, 32), | |
| amountWei, | |
| minAmountLD, | |
| extraOptions, | |
| "0x", // composeMsg | |
| "0x" // oftCmd | |
| ]; | |
| // Step 6: Call quoteSend with updated sendParam to get final msgFee | |
| let msgFee = await oft.quoteSend(finalSendParam, false); | |
| msgFee = [msgFee[0].toString(), msgFee[1].toString()]; | |
| // Step 7: Execute send() with final sendParam, msgFee, and recipient | |
| console.log(`π Executing transfer...`); | |
| const tx = await oft.send( | |
| finalSendParam, | |
| msgFee, | |
| recipient, | |
| { value: msgFee[0] } // Add native fee payment | |
| ); | |
| // const tx = await oft.send.populateTransaction( | |
| // finalSendParam, | |
| // msgFee, | |
| // recipient | |
| // ); | |
| // console.log(tx); | |
| console.log(`β Transfer initiated!`); | |
| console.log(` Transaction Hash: ${tx.hash}`); | |
| console.log(` Native Fee: ${ethers.formatEther(msgFee[0])} ETH`); | |
| console.log(` LZ Token Fee: ${ethers.formatEther(msgFee[1])} LZ`); | |
| return tx; | |
| } | |
| async function main() { | |
| try { | |
| if (!process.env.EVM_PRIVATE_KEY || !process.env.POLYGON_RPC_URL) { | |
| throw new Error("EVM_PRIVATE_KEY and POLYGON_RPC_URL must be set"); | |
| } | |
| const provider = new ethers.JsonRpcProvider(process.env.POLYGON_RPC_URL); | |
| provider.on("debug", (log) => { | |
| console.log(JSON.stringify(log, null, 2)); | |
| }); | |
| const signer = new ethers.Wallet(process.env.EVM_PRIVATE_KEY, provider); | |
| // Transfer USDT0 from Polygon to Optimism with configurable options | |
| await sendUSDT0ToAnyEVM( | |
| signer, | |
| "0.1", // Amount: 0.1 USDT0 | |
| "0x3C351B147B578E65078626eCfa4606dDC6990318", // Recipient | |
| LAYERZERO_EIDS.OPTIMISM, // Destination: Optimism | |
| USDT0_CONTRACTS.POLYGON.OUpgradeable, // Source OFT contract | |
| USDT0_CONTRACTS.POLYGON.UChildUSDT0, // USDT contract | |
| { | |
| slippageBps: 50, // 0.5% slippage protection | |
| nativeDropAmount: "0.00001", // Drop 0.001 ETH to recipient on Optimism | |
| lzReceiveGasLimit: 200_000, // Gas for lzReceive | |
| composeGasLimit: 500_000 // Gas for compose (if needed) | |
| } | |
| ); | |
| } catch (error) { | |
| console.error(error); | |
| } | |
| } | |
| main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment