React Native DApp 开发全栈实战·从 0 到 1 系列(永续合约交易-前端部分)

前言

继上一篇React Native DApp 开发全栈实战·从 0 到 1 系列(永续合约交易-合约部分)本文进入"前端交互"环节,本文带你把「开仓-行情剧变-平仓/清算」完整踩一遍:

  1. 10×杠杆做多,价格拉涨 20%,落袋为安;
  2. 同样仓位,价格反杀 20%,忍痛割肉;
  3. 极端暴跌 75%,触发清算,系统自动接管。

前期准备

  • hardhat启动网络节点:npx hardhat node
  • 合约编译:npx hardhat compile 生成对应的xxx.json用获取abi等相关信息
  • 合约部署:npx hardhat deploy --tags token2,MockV3Aggregator,PerpTrade 获取合约地址(代币、喂价和永续合约地址)
  • 节点的私钥导入钱包:用来与合约交互时支付对应的gas费

核心代码

公共代码
javascript 复制代码
import { abi as MockV3AggregatorAbi } from '@/abi/MockV3Aggregator.json';
import { abi as MyToken2Abi } from '@/abi/MyToken2.json';
import { abi as PerpTradeAbi } from '@/abi/PerpTrade.json';
import * as ethers from 'ethers';
场景1(开仓10x + 涨价 20%+平仓 盈利场景)
ini 复制代码
const OpenPositionProfitFn=async()=>{
   try{
    const provider = new ethers.providers.Web3Provider(window.ethereum);
        /* 0. 连接 MetaMask 并确保 Alice 在账户列表里 */
    await provider.send('eth_requestAccounts', []);
    const signer = await provider.getSigner();
    const userAddr = await signer.getAddress();//用户地址
    const PerpTradeAddress="0x998abeb3E57409262aE5b751f60747921B33613E";//永续合约
    const MyToken2Address ="0xf5059a5D33d5853360D16C683c16e67980206f36";//保证金
    const MockV3AggregatorAddress="0x95401dc811bb5740090279Ba06cfA8fcF6113778";//喂价格合约地址
    const perpTradeContract = new ethers.Contract(PerpTradeAddress, PerpTradeAbi, signer);
    const MyToken2Contract = new ethers.Contract(MyToken2Address, MyToken2Abi, signer);
    const MockV3AggregatorContract = new ethers.Contract(MockV3AggregatorAddress, MockV3AggregatorAbi, signer);
    console.log(perpTradeContract.address,MyToken2Contract,MockV3AggregatorContract)
    /* 4. 常量 */
    const collateral  = ethers.utils.parseUnits("1000", 6);   // 1000 USDC
    const leverage    = ethers.utils.parseEther("10");        // 10×
    const extraUSDC   = ethers.utils.parseUnits("10000", 6);   // 额外打给合约做准备
    /* 5. 给用户打钱(仅测试币) */
    const mintTx = await MyToken2Contract.mint(userAddr, collateral.add(extraUSDC));
    await mintTx.wait();
    console.log("USDC 已 mint",ethers.utils.formatUnits(await MyToken2Contract.balanceOf(userAddr),6));

    /* 6. 用户把 3000 USDC 打进永续合约(赔付准备金) */
    const transferTx = await MyToken2Contract.transfer(PerpTradeAddress, extraUSDC);
    await transferTx.wait();
    console.log("赔付准备金已转入合约",ethers.utils.formatUnits(await MyToken2Contract.balanceOf(PerpTradeAddress),6));

    /* 7. 授权永续合约可扣保证金 */
    const allowTx = await MyToken2Contract.approve(PerpTradeAddress, collateral);
    await allowTx.wait();
    console.log("授权成功");
    /* 8. 开仓:10×杠杆开多 */
    const openTx = await perpTradeContract.open(true, collateral, leverage);
    await openTx.wait();
    console.log("开仓成功,当前价格:", ethers.utils.formatUnits(await perpTradeContract.getPrice(), 6));

    /* 9. 把预言机价格拉涨 20 %(假设原 2200 → 2640) */
    const newAnswer = 2640_0000_0000; // 根据你合约的 decimals 调整
    const priceTx = await MockV3AggregatorContract.updateAnswer(newAnswer);
    await priceTx.wait();
    console.log("价格已拉升,最新价格:", ethers.utils.formatUnits(await perpTradeContract.getPrice(), 6));

    /* 10. 平仓 */
    try{
        const count = await perpTradeContract.positionCount(userAddr);
        console.log('仓位总数:', count.toString());
        const pos=await perpTradeContract.positions(userAddr,count.sub(1))
        const markPrice= await perpTradeContract.getPrice();
        console.log('pos:',pos)
        // 统一精度:价格 1e8 → 1e18
const entryPrice = pos.entryPrice.mul(ethers.BigNumber.from(10).pow(10));
const markPrice18= markPrice.mul(ethers.BigNumber.from(10).pow(10));

const delta = pos.isLong
  ? (markPrice18.gte(entryPrice)
      ? markPrice18.sub(entryPrice)
      : entryPrice.sub(markPrice18))
  : (markPrice18.lte(entryPrice)
      ? entryPrice.sub(markPrice18)
      : markPrice18.sub(entryPrice));

const pnlValue = pos.size.mul(delta).div(entryPrice);
const profit   = pos.isLong ? markPrice18.gte(entryPrice) : markPrice18.lte(entryPrice);
const unsafe   = pnlValue.mul(100).gte(pos.collateral.mul(5)); // 5 % 维持保证金

console.log('盈利', profit, '金额', ethers.utils.formatUnits(pnlValue, 18), '可清算', unsafe);
        const role = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("LIQUIDATOR_ROLE"));
        console.log(role);
        const admin = await perpTradeContract.getRoleAdmin(role);
        console.log('owner:', admin);
        console.log('当前 signer:', await signer.getAddress());
    const closeTx = await perpTradeContract.close(count.sub(1)); // positionId 为 0
    await closeTx.wait();
    }catch(err){
        console.log("平仓失败",err)
    }
     /* 11. 打印最终余额 */
    const endBal = await MyToken2Contract.balanceOf(userAddr);
    console.log("Alice 最终 USDC 余额:", ethers.utils.formatUnits(endBal, 6));
   }catch(err){
    console.log(err)
   }
}
场景2("开仓10x + 降价 20%+平仓 亏损场景")
ini 复制代码
const OpenPositionLossFn=async()=>{
   try{
    const provider = new ethers.providers.Web3Provider(window.ethereum);
        /* 0. 连接 MetaMask 并确保 Alice 在账户列表里 */
    await provider.send('eth_requestAccounts', []);
    const signer = await provider.getSigner();
    const userAddr = await signer.getAddress();//用户地址
    const PerpTradeAddress="0x998abeb3E57409262aE5b751f60747921B33613E";//永续合约
    const MyToken2Address ="0xf5059a5D33d5853360D16C683c16e67980206f36";//保证金
    const MockV3AggregatorAddress="0x95401dc811bb5740090279Ba06cfA8fcF6113778";//喂价格合约地址
    const perpTradeContract = new ethers.Contract(PerpTradeAddress, PerpTradeAbi, signer);
    const MyToken2Contract = new ethers.Contract(MyToken2Address, MyToken2Abi, signer);
    const MockV3AggregatorContract = new ethers.Contract(MockV3AggregatorAddress, MockV3AggregatorAbi, signer);
    console.log(perpTradeContract.address,MyToken2Contract,MockV3AggregatorContract)
    /* 4. 常量 */
    const collateral  = ethers.utils.parseUnits("1000", 6);   // 1000 USDC
    const leverage    = ethers.utils.parseEther("10");        // 10×
    const extraUSDC   = ethers.utils.parseUnits("10000", 6);   // 额外打给合约做准备
    /* 5. 给用户打钱(仅测试币) */
    const mintTx = await MyToken2Contract.mint(userAddr, collateral.add(extraUSDC));
    await mintTx.wait();
    console.log("USDC 已 mint",ethers.utils.formatUnits(await MyToken2Contract.balanceOf(userAddr),6));

    /* 6. 用户把 3000 USDC 打进永续合约(赔付准备金) */
    const transferTx = await MyToken2Contract.transfer(PerpTradeAddress, extraUSDC);
    await transferTx.wait();
    console.log("赔付准备金已转入合约",ethers.utils.formatUnits(await MyToken2Contract.balanceOf(PerpTradeAddress),6));

    /* 7. 授权永续合约可扣保证金 */
    const allowTx = await MyToken2Contract.approve(PerpTradeAddress, collateral);
    await allowTx.wait();
    console.log("授权成功");
    /* 8. 开仓:10×杠杆开多 */
    const openTx = await perpTradeContract.open(true, collateral, leverage);
    await openTx.wait();
    console.log("开仓成功,当前价格:", ethers.utils.formatUnits(await perpTradeContract.getPrice(), 6));

    /* 9. 把预言机价格拉涨 20 %(假设原 2200 → 2640) */
    const newAnswer = 1600_0000_0000; // 根据你合约的 decimals 调整
    const priceTx = await MockV3AggregatorContract.updateAnswer(newAnswer);
    await priceTx.wait();
    console.log("价格已拉升,最新价格:", ethers.utils.formatUnits(await perpTradeContract.getPrice(), 6));

    /* 10. 平仓 */
    try{
        const count = await perpTradeContract.positionCount(userAddr);
        console.log('仓位总数:', count.toString());
        const pos=await perpTradeContract.positions(userAddr,count.sub(1))
        const markPrice= await perpTradeContract.getPrice();
        console.log('pos:',pos)
        // 统一精度:价格 1e8 → 1e18
const entryPrice = pos.entryPrice.mul(ethers.BigNumber.from(10).pow(10));
const markPrice18= markPrice.mul(ethers.BigNumber.from(10).pow(10));

const delta = pos.isLong
  ? (markPrice18.gte(entryPrice)
      ? markPrice18.sub(entryPrice)
      : entryPrice.sub(markPrice18))
  : (markPrice18.lte(entryPrice)
      ? entryPrice.sub(markPrice18)
      : markPrice18.sub(entryPrice));

const pnlValue = pos.size.mul(delta).div(entryPrice);
const profit   = pos.isLong ? markPrice18.gte(entryPrice) : markPrice18.lte(entryPrice);
const unsafe   = pnlValue.mul(100).gte(pos.collateral.mul(5)); // 5 % 维持保证金

console.log('盈利', profit, '金额', ethers.utils.formatUnits(pnlValue, 18), '可清算', unsafe);
//
        const role = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("LIQUIDATOR_ROLE"));
        console.log(role);
        const admin = await perpTradeContract.getRoleAdmin(role);
        console.log('owner:', admin);
        console.log('当前 signer:', await signer.getAddress());
    const closeTx = await perpTradeContract.close(count.sub(1)); // positionId 为 0
    await closeTx.wait();
    }catch(err){
        console.log("平仓失败",err)
    }
     /* 11. 打印最终余额 */
    const endBal = await MyToken2Contract.balanceOf(userAddr);
    console.log("Alice 最终 USDC 余额:", ethers.utils.formatUnits(endBal, 6));
   }catch(err){
    console.log(err)
   }
}
场景3("开仓10x + 降价 75% 清算场景")
ini 复制代码
const OpenPositionClearanceFn = async () => {
  try {
    /* 0. 连接钱包 */
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    await provider.send('eth_requestAccounts', []);
    const signer = provider.getSigner();
    const user = await signer.getAddress();

    /* 1. 合约地址(👈 换成你的) */
    const PerpAddr = '0x998abeb3E57409262aE5b751f60747921B33613E';
    const USDCAddr = '0xf5059a5D33d5853360D16C683c16e67980206f36';
    const PriceAddr = '0x95401dc811bb5740090279Ba06cfA8fcF6113778';

    /* 2. 合约实例 */
    const perp = new ethers.Contract(PerpAddr, PerpTradeAbi, signer);
    const usdc = new ethers.Contract(USDCAddr, MyToken2Abi, signer);
    const price = new ethers.Contract(PriceAddr, MockV3AggregatorAbi, signer);

    /* 3. 常量 */
    const collateral = ethers.utils.parseUnits('1000', 6);
    const leverage   = ethers.utils.parseEther('10');
    const reserve    = ethers.utils.parseUnits('10000', 6); // 赔付金

    /* 4. 管理员:一次性打开开关 & 授 LIQUIDATOR_ROLE 给当前钱包 */
    const LIQUIDATOR_ROLE = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LIQUIDATOR_ROLE'));
    const isAdmin = await perp.hasRole(LIQUIDATOR_ROLE, user);
    if (isAdmin) {
  // 1. 激活合约(你的合约需要有一个 public view active() + setActive(bool))
//   if (!(await perp.active())) {
//     await (await perp.connect(signer).setActive(true)).wait();
//     console.log('✅ 合约已激活');
//   }

  // 2. 授予清算角色
  if (!(await perp.hasRole(LIQUIDATOR_ROLE, user))) {
    await (await perp.connect(signer).grantRole(LIQUIDATOR_ROLE, user)).wait();
    console.log('✅ 已授予自己 LIQUIDATOR_ROLE');
  }
} else {
  console.warn('当前地址不是 DEFAULT_ADMIN_ROLE,请用部署者地址执行');
}

    /* 5. 给用户打钱 & 准备金 */
    await (await usdc.mint(user, collateral.add(reserve))).wait();
    await (await usdc.transfer(PerpAddr, reserve)).wait();
    console.log('准备金已到位');

    /* 6. 授权 & 开仓 */
    await (await usdc.approve(PerpAddr, collateral)).wait();
    await (await perp.connect(signer).open(true, collateral, leverage)).wait();
    console.log('开仓成功,当前价格', ethers.utils.formatUnits(await perp.getPrice(), 6));

    /* 7. 把价格打到 500(约 75% 跌幅) */
    const newPrice = 500_0000_0000; // 按你合约 decimals 来
    await (await price.updateAnswer(newPrice)).wait();
    console.log('价格已暴跌 →', ethers.utils.formatUnits(await perp.getPrice(), 6));

    /* 8. 自清算 */
    const posId = (await perp.positionCount(user)).sub(1);
    const balBefore = await usdc.balanceOf(user);
    await (await perp.connect(signer).liquidate(user, posId)).wait();
    const balAfter = await usdc.balanceOf(user);
    console.log('清算完成,钱包余额增加:', ethers.utils.formatUnits(balAfter.sub(balBefore), 6), 'USDC');

    /* 9. 最终余额 */
    console.log('最终 USDC 余额', ethers.utils.formatUnits(balAfter, 6));
  } catch (e) {
    console.error(e);
  }
};
查看持仓信息
csharp 复制代码
# 获取仓位数组长度
const count = await perpTradeContract.positionCount(userAddr);
console.log('仓位总数:', count.toString());
# 获取指定仓位的详情
const pos=await perpTradeContract.positions(userAddr,count.sub(1))
# 通过循环返回 所有仓位的详情列表

# 返回的仓位数据结构 整理后
{
  "positionId": 0,
  "isLong": true,
  "isActive": true,
  "nominalValueUSD": 10000,
  "collateralUSD": 1000,
  "entryPriceUSD": 175,
  "lastUpdate": "2024-07-20 12:15:02 UTC",
  "leverage": 10,
  "direction": "LONG",
  "status": "OPEN"
}
# 关于 做多,做空前端可以通过计算获取也可以通不过合约添加一个方法也可以

效果图

总结

本文把「永续合约+前端交互」最后一环补齐:

  1. 环境一键启动------hardhat node + 编译 + 部署,三条命令本地链就绪;
  2. 三大极端场景------盈利、亏损、清算,全部用 ethers.js 脚本跑通,可直接复制到 React-Native/Web 项目;
  3. 通用工具函数------positionCount + positions 循环拉取,链上 struct 秒变前端 JSON,仓位卡片即插即用。

至此,「合约-喂价-前端」全链路打通:

  • 本地能 10×杠杆做多;
  • 能手动改价体验 20% 涨跌盈亏;
  • 能一键清算看 75% 爆仓。

后续改造:

  • 把 MockV3Aggregator 换成主网/测试网 Price Feed;
  • 把 mint 换成真实 USDC 入口;
  • 再加上止盈止损、部分平仓、资金费率------就是一个可上线的永续 DApp。
相关推荐
歪歪1004 小时前
Redux和MobX在React Native状态管理中的优缺点对比
前端·javascript·react native·react.js·架构·前端框架
带娃的IT创业者4 小时前
《AI大模型应知应会100篇》第68篇:移动应用中的大模型功能开发 —— 用 React Native 打造你的语音笔记摘要 App
人工智能·笔记·react native
sylvia_08154 小时前
react native 初次使用Android Studio 打包
android·react native·android studio
索马里亚纳海参炒贩19 小时前
useCallback useMemo memo 三个区别和作用
前端·react native
OpenBuild.xyz1 天前
Web3 开发者周刊 66 | 重新审视L2:跨链桥与定序器背后的信任裂痕
web3·区块链·业界资讯·周报·数字货币
天涯学馆1 天前
在Solidity中实现DAO:从概念到代码的全面剖析
智能合约·solidity·以太坊
冰冷的bin2 天前
【React Native】点赞特效动画组件FlowLikeView
react native·react.js·typescript
懒人村杂货铺2 天前
[特殊字符] 跨端视频通话实战:腾讯云 TRTC + IM(React Native & Web)
react native·音视频·腾讯云
木西3 天前
React Native DApp 开发全栈实战·从 0 到 1 系列(流动性挖矿-前端部分)
react native·web3·智能合约