跳到主要内容

部署第一个合约(Hardhat)

本指南带你用 Hardhat 把一个简单的 ERC-20 部署到 OXN 测试网。预估时间:钱包充值好后 5 分钟

前置条件

  • Node.js 18+ 和 npm
  • 一个持有测试网 TBLUAI 的钱包——见配置钱包领取测试币
  • 私钥导出到环境变量 PRIVATE_KEY(或临时硬编码在 hardhat.config.js 里,仅限本地测试)

1. 创建工程

mkdir my-oxn-dapp && cd my-oxn-dapp
npm init -y
npm install --save-dev hardhat@^2.22.0
npx hardhat init
# 选 "Create a JavaScript project",所有选项按默认

2. 安装 OXN Hardhat 插件与 OpenZeppelin

npm install --save-dev @oasisprotocol/sapphire-hardhat@^2.22.0 @openzeppelin/contracts

插件会自动包裹 Hardhat 的 provider 和 signer,所以每一笔 --network oxn_testnet 的交易都是端到端加密的,不需要按调用手动 opt-in。

3. 配置 hardhat.config.js

用下面的内容替换生成的文件:

hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@oasisprotocol/sapphire-hardhat"); // 必须在 toolbox 之后 require

const PRIVATE_KEY = process.env.PRIVATE_KEY
|| "0x0000000000000000000000000000000000000000000000000000000000000001"; // 替换!

module.exports = {
solidity: {
version: "0.8.24",
settings: {
evmVersion: "paris", // 必需——OXN 尚未支持 PUSH0
optimizer: { enabled: true, runs: 200 },
},
},
networks: {
oxn_testnet: {
url: "https://rpc.bout.network",
chainId: 186,
accounts: [PRIVATE_KEY],
},
},
};
两个必调的设置
  1. evmVersion: "paris"——OXN 运行时尚未支持 Shanghai 引入的 PUSH0 opcode。用了 PUSH0 的合约会在运行时以 invalid code revert。见 EVM 兼容性
  2. require("@oasisprotocol/sapphire-hardhat") 必须在 hardhat-toolbox 之后——否则 toolbox 会覆盖插件的 provider wrap。

4. 写合约

创建 contracts/MyToken.sol

contracts/MyToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply);
}
}

5. 写部署脚本

创建 scripts/deploy.js

scripts/deploy.js
const hre = require("hardhat");

async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("deployer:", deployer.address);

const Token = await hre.ethers.getContractFactory("MyToken");

// 始终显式传 gasLimit——这是 OXN 的推荐路径。
// 必须显式传 gasLimit。
const token = await Token.deploy(
hre.ethers.parseUnits("1000000", 18),
{ gasLimit: 3_000_000n }
);

await token.waitForDeployment();
console.log("MyToken deployed at:", await token.getAddress());
}

main().catch((e) => { console.error(e); process.exit(1); });

6. 编译并部署

export PRIVATE_KEY=0x... # 你充过币的测试账户
npx hardhat compile
npx hardhat run scripts/deploy.js --network oxn_testnet

预期输出:

deployer: 0xYourAddress...
MyToken deployed at: 0xNewContractAddress...

记下这个合约地址——下一步要用。也可以在区块浏览器上看部署交易。

7. 与合约交互

创建 scripts/transfer.js

scripts/transfer.js
const hre = require("hardhat");

// 把第 6 步部署得到的地址填进来
const TOKEN_ADDR = "0xYourDeployedTokenAddress";
const RECIPIENT = "0x000000000000000000000000000000000000dEaD";

async function main() {
const [sender] = await hre.ethers.getSigners();
const token = await hre.ethers.getContractAt("MyToken", TOKEN_ADDR);

console.log("sender:", sender.address);
console.log("balance before:", (await token.balanceOf(sender.address)).toString());

const tx = await token.transfer(
RECIPIENT,
hre.ethers.parseUnits("100", 18),
{ gasLimit: 500_000n }
);
console.log("tx hash:", tx.hash);
await tx.wait();

console.log("balance after:", (await token.balanceOf(sender.address)).toString());
}

main().catch((e) => { console.error(e); process.exit(1); });

跑:

npx hardhat run scripts/transfer.js --network oxn_testnet

8. 验证交易确实被加密了

用 OXN 的关键就是你的 transfer(recipient, amount) calldata 在链上不可见。可以这样确认:

打开 explorer.bout.network,搜索你的交易哈希,看 Input Data 字段。它应该是一段以 0xa20xa3 开头的十六进制(CBOR 信封),而不是明文的 0xa9059cbbtransfer 的函数选择器)。

程序化验证:

const raw = new hre.ethers.JsonRpcProvider("https://rpc.bout.network");
const onchain = await raw.getTransaction(tx.hash);
const firstByte = parseInt(onchain.data.slice(2, 4), 16);
console.assert((firstByte & 0xe0) === 0xa0, "not encrypted!");

如果断言失败,说明插件的 wrap 没生效——检查 hardhat.config.jsrequire("@oasisprotocol/sapphire-hardhat") 是否在 hardhat-toolbox 之后

推荐 gas limit

OXN 上每次调用都要显式固定 gasLimit,客户端估算不是推荐路径。推荐默认值:

调用类型推荐 gasLimit
原生代币转账100_000n
ERC-20 transfer500_000n
简单合约部署3_000_000n
大合约部署5_000_000n+

未消耗的 gas 会退还,多给一些是安全的。

常见问题

"MyToken deployed at: null"——部署交易成功了但 getCode 返回空。把 gasLimit 调到 5_000_000n 重来。

合约调用 revert invalid code——你用了比 paris 更新的 evmVersion 编译。含 PUSH0 的合约在某些代码路径会失败。在 hardhat.config.js 里锁定 evmVersion: "paris" 重新编译。

"Insufficient balance to pay fees"——重新领水龙头,或者测试期间用 gasPrice: 0n(测试网链层接受)。

交易成功但链上看到的是明文数据——插件的 wrap 没生效。确认 hardhat.config.jsrequire("@oasisprotocol/sapphire-hardhat") 存在,并且 require("@nomicfoundation/hardhat-toolbox") 之后

下一步