Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

将 USDT0 跨链到 Stable

在本教程中,你将学习如何使用 TypeScript 和 ethers v6 通过编程方式将 USDT0 从以太坊 Sepolia 跨链到 Stable 测试网。你将逐步构建脚本,每一步添加一个函数。

本教程使用 OFT Mesh 路径。Sepolia 上的 OFT 适配器锁定你的代币,LayerZero 的双 DVN 验证确认消息,然后在 Stable 上铸造 USDT0。有关其工作原理的完整解释,请参阅将 USDT0 跨链到 Stable

前提条件

  • Node.js 18.0.0 或更高版本(通过 node --version 验证)
  • 一个你控制私钥的 Sepolia 钱包(切勿使用持有真实资金的私钥)
  • 用于 gas 的 SepoliaETH(可以从 sepoliafaucet.comfaucets.chain.link/sepolia 获取)
  • 对从终端运行脚本有基本了解

1. 设置项目

mkdir stable-bridge && cd stable-bridge
npm init -y
npm install ethers@6 @layerzerolabs/lz-v2-utilities
npm install -D tsx

你的 package.json 应包含:

{
  "name": "stable-bridge",
  "version": "1.0.0",
  "scripts": {
    "bridge": "tsx --env-file=.env bridge.ts"
  },
  "dependencies": {
    "@layerzerolabs/lz-v2-utilities": "^2.3.39",
    "ethers": "^6.13.0"
  },
  "devDependencies": {
    "tsx": "^4.19.0"
  }
}

2. 配置你的环境

创建一个 .env 文件,其中包含你的凭据:

PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE
SEPOLIA_RPC_URL=https://rpc.sepolia.org

对于 SEPOLIA_RPC_URL,以下任何一个都有效:

  • 公共节点:https://rpc.sepolia.orghttps://ethereum-sepolia-rpc.publicnode.com
  • Alchemy:https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
  • Infura:https://sepolia.infura.io/v3/YOUR_KEY

3. 搭建脚本骨架

创建 bridge.ts,包含导入、配置和 main 函数。你将在接下来的步骤中向此文件添加函数,并从 main 调用它们。

import { ethers, Contract, Wallet, JsonRpcProvider } from "ethers";
import { Options } from "@layerzerolabs/lz-v2-utilities";
 
const PRIVATE_KEY = process.env.PRIVATE_KEY!;
const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "https://rpc.sepolia.org";
 
// 合约地址
const SEPOLIA_USDT0 = "0xc4DCC311c028e341fd8602D8eB89c5de94625927";
const SEPOLIA_OFT_ADAPTER = "0xc099cD946d5efCC35A99D64E808c1430cEf08126";
const STABLE_USDT0 = "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9";
 
// 目标:Stable 测试网
const STABLE_TESTNET_EID = 40374;
 
// 最小 ABI — 只包含我们调用的函数
const ERC20_ABI = [
  "function balanceOf(address) view returns (uint256)",
  "function approve(address, uint256) returns (bool)",
  "function allowance(address, address) view returns (uint256)",
  "function mint(address, uint256)",
];
 
const OFT_ADAPTER_ABI = [
  "function quoteSend((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd), bool) view returns ((uint256 nativeFee, uint256 lzTokenFee))",
  "function send((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd), (uint256 nativeFee, uint256 lzTokenFee), address) payable returns ((bytes32, uint64, (uint256, uint256)), (uint256, uint256))",
];
 
function addressToBytes32(addr: string): string {
  return ethers.zeroPadValue(ethers.getBytes(ethers.getAddress(addr)), 32);
}
 
// 你将在这里添加函数。
 
async function main() {
  const provider = new JsonRpcProvider(SEPOLIA_RPC_URL);
  const wallet = new Wallet(PRIVATE_KEY, provider);
 
  const usdt0 = new Contract(SEPOLIA_USDT0, ERC20_ABI, wallet);
  const oftAdapter = new Contract(SEPOLIA_OFT_ADAPTER, OFT_ADAPTER_ABI, wallet);
 
  const amount = ethers.parseEther("1"); // 1 USDT0 (18 位小数)
 
  // 你将在这里添加函数调用。
}
 
main().catch((err) => {
  console.error(err.message);
  process.exit(1);
});

4. 在 Sepolia 上铸造测试 USDT0

Sepolia 上的测试 USDT0 合约暴露了一个公共 mint 函数。将以下函数添加到 bridge.ts 中的 main 函数上方:

async function mint(usdt0: Contract, receiver: string, amount: bigint) {
  console.log(`在 Sepolia 上铸造 ${ethers.formatEther(amount)} USDT0...`);
  const tx = await usdt0.mint(receiver, amount);
  await tx.wait();
  console.log(`铸造交易: ${tx.hash} 已确认`);
 
  const balance = await usdt0.balanceOf(receiver);
  console.log(`USDT0 余额: ${ethers.formatEther(balance)}`);
}

然后从 main 调用它:

  await mint(usdt0, wallet.address, amount);

运行脚本:

npx tsx --env-file=.env bridge.ts

检查点: 在铸币确认后,你应该看到记录的非零 USDT0 余额。


5. 批准 OFT 适配器

在 OFT 适配器转移你的代币之前,它需要一个 ERC-20 授权。将此函数添加到 main 上方:

async function approve(usdt0: Contract, spender: string, owner: string, amount: bigint) {
  console.log("正在批准 OFT 适配器...");
  const tx = await usdt0.approve(spender, amount);
  await tx.wait();
  console.log(`批准交易: ${tx.hash} 已确认`);
 
  const allowance = await usdt0.allowance(owner, spender);
  console.log(`授权额度: ${ethers.formatEther(allowance)}`);
}

main 中的 mint 后添加调用:

  // await mint(usdt0, wallet.address, amount);
  await approve(usdt0, SEPOLIA_OFT_ADAPTER, wallet.address, amount);

运行脚本。如果你已经有上次运行的代币,可以注释掉 await mint(...) 调用。


检查点: 脚本应该在批准确认后记录非零授权额度。


6. 估算费用并发送桥接交易

quoteSend 调用返回 SepoliaETH 中的 LayerZero 消息费用,你将其作为 msg.value 传递给 send。将此函数添加到 main 上方:

async function send(oftAdapter: Contract, receiver: string, amount: bigint) {
  const options = Options.newOptions().addExecutorLzReceiveOption(0, 0).toBytes();
 
  const sendParams = {
    dstEid: STABLE_TESTNET_EID,
    to: addressToBytes32(receiver),
    amountLD: amount,
    minAmountLD: amount,
    extraOptions: options,
    composeMsg: "0x",
    oftCmd: "0x",
  };
 
  console.log("正在估算桥接费用...");
  const feeResult = await oftAdapter.quoteSend(sendParams, false);
  const fee = { nativeFee: feeResult.nativeFee, lzTokenFee: feeResult.lzTokenFee };
  console.log(`桥接费用: ${ethers.formatEther(fee.nativeFee)} ETH`);
 
  console.log("正在发送桥接交易...");
  const tx = await oftAdapter.send(sendParams, fee, receiver, {
    value: fee.nativeFee,
  });
  await tx.wait();
  console.log(`桥接交易: ${tx.hash} 已确认`);
  console.log(`Sepolia Etherscan: https://sepolia.etherscan.io/tx/${tx.hash}`);
  console.log(`LayerZero Scan: https://testnet.layerzeroscan.com/tx/${tx.hash}`);
}

main 中的 approve 后添加调用:

  // await mint(usdt0, wallet.address, amount);
  // await approve(usdt0, SEPOLIA_OFT_ADAPTER, wallet.address, amount);
  await send(oftAdapter, wallet.address, amount);

7. 验证 Stable 测试网上的到账情况

发送后,脚本可以轮询 Stable 测试网 RPC,直到代币到达。将此函数添加到 main 上方:

async function verify(receiver: string) {
  console.log("等待 DVN 验证 (~2 分钟)...");
  const stableProvider = new JsonRpcProvider("https://rpc.testnet.stable.xyz");
  const stableUsdt0 = new Contract(STABLE_USDT0,
    ["function balanceOf(address) view returns (uint256)"], stableProvider);
 
  const before: bigint = await stableUsdt0.balanceOf(receiver);
  for (let i = 0; i < 24; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const current: bigint = await stableUsdt0.balanceOf(receiver);
    if (current > before) {
      console.log(`\nStable 上的 USDT0: ${ethers.formatEther(current)}`);
      console.log(`浏览器: https://testnet.stablescan.xyz/address/${receiver}`);
      return;
    }
    process.stdout.write(".");
  }
  console.log("\n代币尚未到账。请手动检查:");
  console.log(`浏览器: https://testnet.stablescan.xyz/address/${receiver}`);
}

main 中的 send 后添加调用:

  // await mint(usdt0, wallet.address, amount);
  // await approve(usdt0, SEPOLIA_OFT_ADAPTER, wallet.address, amount);
  // await send(oftAdapter, wallet.address, amount);
  await verify(wallet.address);

8. 运行完整的桥接

你的 main 函数现在应该如下所示:

async function main() {
  const provider = new JsonRpcProvider(SEPOLIA_RPC_URL);
  const wallet = new Wallet(PRIVATE_KEY, provider);
 
  const usdt0 = new Contract(SEPOLIA_USDT0, ERC20_ABI, wallet);
  const oftAdapter = new Contract(SEPOLIA_OFT_ADAPTER, OFT_ADAPTER_ABI, wallet);
 
  const amount = ethers.parseEther("1"); // 1 USDT0 (18 位小数)
 
  await mint(usdt0, wallet.address, amount);
  await approve(usdt0, SEPOLIA_OFT_ADAPTER, wallet.address, amount);
  await send(oftAdapter, wallet.address, amount);
  await verify(wallet.address);
}

运行它:

npx tsx --env-file=.env bridge.ts

检查点: 你应该看到如下输出:

Minting 1.0 USDT0 on Sepolia...
Mint tx: 0x3a1f...c9d2  confirmed
USDT0 balance: 1.0
Approving OFT Adapter...
Approve tx: 0x7b2e...f401  confirmed
Allowance: 1.0
Quoting bridge fee...
Bridge fee: 0.000101 ETH
Sending bridge transaction...
Bridge tx: 0xa94f...8c11  confirmed
Sepolia Etherscan: https://sepolia.etherscan.io/tx/0xa94f...8c11
LayerZero Scan: https://testnet.layerzeroscan.com/tx/0xa94f...8c11
Waiting for DVN verification (~2 minutes)...
......
USDT0 on Stable: 1.0

你还可以在 Stable 测试网浏览器上搜索你的钱包地址以确认铸造事件。


你已经构建了什么

你已将 USDT0 从以太坊 Sepolia 桥接到 Stable 测试网。你现在知道如何:

  • 使用合约的公共 mint 函数在 Sepolia 上铸造测试 USDT0
  • 授权 OFT 适配器代表你花费 ERC-20 代币
  • 使用 32 字节地址编码和执行器选项构造 LayerZero sendParams
  • 在提交资金之前使用 quoteSend 估算跨链消息费用
  • 使用 send 执行跨链代币转移并确认在目标链上的交付
  • 使用 Stable 的 RPC (https://rpc.testnet.stable.xyz,链 ID 2201) 和 Stablescan 验证链上状态

接下来推荐