React Native DApp 开发全栈实战·从 0 到 1 系列(兑换-合约部分)
2025年9月15日 21:25
前言 智能合约 代币合约 喂价合约 兑换合约 编译指令:npx hardhat compile 测试合约 测试指令:npx hardhat test ./test/xxxx.js 部署合约 部署指令:
继上一篇《React Native DApp 开发全栈实战·从 0 到 1 系列(流动性挖矿-合约部分)》,本文进入“前端交互”环节:把 Hardhat 测试脚本里那套「mint → approve → deposit → evm_increaseTime → harvest」的自动化流程,
原封不动地搬到浏览器/移动端,并通过 MetaMask 实现「真钱包、真签名、真 Gas」的交互体验。
同时,我们还会解决 Web3Provider 无法时间快进、授权额度不足、奖励误差等 3 个常见踩坑点,让你真正做到「开发时秒级验证,上线后零改动」。
const url = 'http://localhost:8545'; // 以 Hardhat 启动日志为准
const raw = new ethers.providers.JsonRpcProvider(url);
const id = await raw.send('eth_chainId', []);
console.log('chainId', parseInt(id, 16));
await raw.send('evm_increaseTime', [10]);
await raw.send('evm_mine');
console.log('✅ 直连成功');
说明:把测试操作集中在一个函数中
allowance
,杜绝「额度不足」导致的 deposit 失败。JsonRpcProvider
直连接口,调用 evm_increaseTime
+ evm_mine
;Web3Provider
无此 API。deposit(0)
)让合约重新计算 earned
。console.log
并 .wait()
回执,开发阶段一眼定位失败点;上线前把 try/catch
细化到业务层即可直接复用。import { abi as LiquidityMiningVaultABI } from "@/abi/LiquidityMiningVault.json";
import { abi as StakeTokenABI } from "@/abi/MyToken.json";
import { abi as REWARDTokenABI } from "@/abi/MyToken1.json"; //代币
import * as ethers from 'ethers';
const withdrawToken = async () => {
try {
const provider = new ethers.providers.Web3Provider(window.ethereum);
/* 0. 连接 MetaMask 并确保 Alice 在账户列表里 */
await provider.send('eth_requestAccounts', []);
const ALICE_ADDR = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8';
const VAULT_ADDR = '0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8';
const STAKE_ADDR = '0x5FbDB2315678afecb367f032d93F642f64180aa3';
const REWARD_ADDR = '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853';//奖励币
let accounts = await provider.listAccounts();
if (!accounts.map(a => a.toLowerCase()).includes(ALICE_ADDR.toLowerCase())) {
await window.ethereum.request({
method: 'wallet_requestPermissions',
params: [{ eth_accounts: {} }]
});
accounts = await provider.send('eth_requestAccounts', []);
}
const aliceIndex = accounts.findIndex(
a => a.toLowerCase() === ALICE_ADDR.toLowerCase()
);
if (aliceIndex === -1) throw new Error('MetaMask 中未找到 Alice 地址');
const aliceSigner = provider.getSigner(aliceIndex);
const ownerSigner = provider.getSigner(0);
const DEPOSIT = ethers.utils.parseEther('1000');
const SPEED = ethers.utils.parseEther('10');
const stakeToken = new ethers.Contract(STAKE_ADDR, StakeTokenABI, ownerSigner);
const rewardToken = new ethers.Contract(REWARD_ADDR, REWARDTokenABI, ownerSigner);
const vault = new ethers.Contract(VAULT_ADDR, LiquidityMiningVaultABI, ownerSigner);
/* 1. vault 预充奖励 */
console.log('1. vault 预充奖励');
try{
await (await rewardToken.mint(VAULT_ADDR, ethers.utils.parseEther('2000'))).wait();
}catch(err){
console.error('❌ 预充奖励失败', err);
}
/* 2. 给 alice 发币 */
console.log('2. alice mint');
await (await stakeToken.mint(ALICE_ADDR, DEPOSIT)).wait();
/* 3. alice 授权 —— 用 alice 自己的签名器 */
console.log('3. alice 授权');
const stakeForAlice = stakeToken.connect(aliceSigner);
const approveTx = await stakeForAlice.approve(VAULT_ADDR, DEPOSIT);
const approveRcpt = await approveTx.wait();
console.log('approve receipt status:', approveRcpt.status);
/* 4. 再读一次授权,确认额度足够 */
const allowance = await stakeToken.allowance(ALICE_ADDR, VAULT_ADDR);
console.log('allowance (枚)', ethers.utils.formatEther(allowance));
if (!allowance.gte(DEPOSIT)) throw new Error('授权额仍不足');
/* 5. alice 质押 */
console.log('4. alice deposit');
try{
await (await vault.connect(aliceSigner).deposit(DEPOSIT, ALICE_ADDR)).wait();
}catch(err){
console.error('❌ 质押失败', err);
}
/* 6. owner 设奖励速度 */
console.log('5. 设奖励速度');
await (await vault.connect(ownerSigner).setRewardPerSecond(SPEED)).wait();
/* 7. 时间快进 */
console.log('6. evm+100s');
try{
const url = 'http://localhost:8545'; // 以 Hardhat 启动日志为准
const raw = new ethers.providers.JsonRpcProvider(url);
const id = await raw.send('eth_chainId', []);
console.log('chainId', parseInt(id, 16));
await raw.send('evm_increaseTime', [10]);
await raw.send('evm_mine');
console.log('✅ 直连成功');
}catch(err){
console.error('❌ 时间快进失败', err);
}
/* 8. 触发更新 */
console.log('7. 再存 0 触发更新');
await (await vault.connect(aliceSigner).deposit(0, ALICE_ADDR)).wait();
/* 9. 查询收益 */
const earned = await vault.earned(ALICE_ADDR);
console.log('earned(枚)', ethers.utils.formatEther(earned));
// 10. 提取奖励
console.log('8. 提取奖励');
try{
await (await vault.connect(aliceSigner).harvest()).wait();
}catch(err){
console.error('❌ 提取奖励失败', err);
}
console.log('9. 提取奖励后查询余额');
const balance = await rewardToken.balanceOf(ALICE_ADDR);
console.log('balance(枚)', ethers.utils.formatEther(balance));
} catch (err) {
console.error('❌ 流程中断', err.message ?? err);
}
};
npx hardhat node
本地链 + 合约地址 + 节点私钥导入钱包,即可完成「开发 ⇄ 钱包」的双向通信。time.increase()
,但可以通过直连 JsonRpcProvider
调用 evm_increaseTime
与 evm_mine
,在 UI 上实现「一键跳 100 秒」的测试快感。deposit(0)
触发 updateReward
,能把区块时间误差压到 1 s 以内,肉眼可见「earned ≈ 1000 枚」。window.ethereum
换成 @walletconnect/web3wallet
或 WalletConnectModal
,即可原地切换主网、测试网或移动端钱包,真正做到「开发即生产」。至此,「合约开发 → 单元测试 → 前端交互」完整链路已跑通。