前言本文围绕ERC7281标准展开全面梳理,涵盖其核心定义、核心功能、行业痛点解决方案及典型应用场景,并结合HardhatV3开发环境与OpenZeppelin工具库,完整实现了ERC7281智能租赁合约的开发、部署与测试全流程落地,形成“标准理论+实战落地”的完整内容
本文围绕 ERC7281 标准展开全面梳理,涵盖其核心定义、核心功能、行业痛点解决方案及典型应用场景,并结合 Hardhat V3 开发环境与 OpenZeppelin 工具库,完整实现了 ERC7281 智能租赁合约的开发、部署与测试全流程落地,形成 “标准理论 + 实战落地” 的完整内容体系。
概述
ERC7281(又称 xERC-20)是以太坊改进提案(EIP),核心是将跨链代币控制权从桥接方交还给发行方,通过扩展 ERC-20 接口实现多桥兼容、动态限额与统一资产表示,解决传统跨链的流动性碎片化、安全风险集中与资产不可互换问题,适用于 DeFi、稳定币、RWA 等多链场景。
是什么:核心定义与架构
- 定位:ERC-20 的扩展标准,保留基础接口,新增跨链治理与多桥协作能力。
核心角色:
关键接口:
| 接口 | 功能 |
|---|---|
authorizeBridge |
授权桥接方在指定链操作 |
setBridgeLimit |
为桥接方设置铸造 / 销毁限额 |
mintRemote/burnRemote |
跨链铸造 / 销毁标准代币 |
isBridgeAuthorized |
查询桥接方权限状态 |
统一跨链资产表示
兼容与可扩展性
| 传统跨链痛点 | ERC7281 解决方案 | |
|---|---|---|
| 控制权旁落 | 桥接方掌控跨链资产,发行方无法干预 | 发行方持有权限,可实时调整桥接策略 |
| 流动性分裂 | 不同桥铸造的同币种资产不可互换,形成流动性孤岛 | 统一资产表示,多桥铸造的代币完全 fungible |
| 安全风险集中 | 单一桥被攻击可能导致资产全量损失 | 单桥限额隔离风险,快速下架问题桥接方 |
| 用户体验差 | 跨链存在 slippage,资产迁移流程复杂 | 无滑点跨链,发行方治理升级不影响用户持仓 |
| 扩展成本高 | 新链需重复部署流动性,资产覆盖效率低 | 统一资产模型降低新链适配成本 |
稳定币跨链部署
DeFi 跨链协议
RWA(现实世界资产)上链
NFT 跨链(扩展方向)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/Pausable.sol";
/**
核心功能:租赁收益分成、动态权限、紧急回收、碎片化租赁 */ contract ERC7281NFT is ERC721, Ownable, ReentrancyGuard, Pausable { // ========== 核心数据结构 ========== // NFT租赁信息 struct LeaseInfo { address user; // 当前使用者 uint64 expires; // 租赁到期时间戳(Unix秒) uint256 rentAmount; // 总租金(Wei) uint256 platformFeeRate; // 平台费率(万分比,如500=5%) uint256 paidAmount; // 已支付租金 bool isEmergencyTerminated; // 是否紧急终止 uint256 penaltyAmount; // 紧急终止违约金 bytes32[] allowedPermissions; // 使用者允许的权限(如"GAME_USE", "METADATA_VIEW") mapping(address => uint256) fragmentShares; // 碎片化租赁份额(仅当拆分时生效) }
// ========== 状态变量 ========== // 平台收款地址 address public platformWallet; // 每个NFT的租赁信息 mapping(uint256 => LeaseInfo) private _leaseInfos; // 权限注册表(全局) mapping(bytes32 => bool) public supportedPermissions;
// ========== 事件定义 ========== // 设置租赁条款 event LeaseTermsSet( uint256 indexed tokenId, address indexed user, uint64 expires, uint256 rentAmount, uint256 platformFeeRate, bytes32[] allowedPermissions ); // 租金支付 event RentPaid(uint256 indexed tokenId, address indexed payer, uint256 amount); // 收益提取 event RevenueClaimed(uint256 indexed tokenId, address indexed recipient, uint256 amount); // 紧急终止租赁 event LeaseEmergencyTerminated(uint256 indexed tokenId, address indexed owner, uint256 penaltyAmount); // 碎片化租赁设置 event FragmentLeaseSet(uint256 indexed tokenId, address indexed user, uint256 share);
// ========== 错误定义 ========== error TokenNotExists(uint256 tokenId); error NotTokenOwner(uint256 tokenId, address caller); error LeaseExpired(uint256 tokenId); error InsufficientPayment(uint256 required, uint256 paid); error PermissionNotSupported(bytes32 permission); error InvalidShareAmount(uint256 totalShare); error NoRevenueToClaim();
// ========== 构造函数 ========== constructor( string memory name, string memory symbol, address initialPlatformWallet, uint256 initialPlatformFeeRate ) ERC721(name, symbol) Ownable(msg.sender) { require(initialPlatformWallet != address(0), "Platform wallet cannot be zero"); require(initialPlatformFeeRate <= 10000, "Fee rate cannot exceed 100%"); platformWallet = initialPlatformWallet; // 初始化默认支持的权限 supportedPermissions["GAME_USE"] = true; supportedPermissions["METADATA_VIEW"] = true; supportedPermissions["TRANSFER_USE"] = false; // 禁止使用者转让NFT }
// ========== 核心功能:设置租赁条款(ERC-7281核心) ========== /**
@param allowedPermissions 允许使用者的权限列表 */ function setLeaseTerms( uint256 tokenId, address user, uint64 expires, uint256 rentAmount, uint256 platformFeeRate, bytes32[] calldata allowedPermissions ) external nonReentrant whenNotPaused { // 修复点1:用_ownerOf替代_exists判断NFT是否存在(5.x版本兼容) try this.ownerOf(tokenId) { // NFT存在,继续执行 } catch { revert TokenNotExists(tokenId); }
if (ownerOf(tokenId) != msg.sender) revert NotTokenOwner(tokenId, msg.sender); if (platformFeeRate > 10000) revert InsufficientPayment(10000, platformFeeRate);
// 校验权限是否都被支持 for (uint256 i = 0; i < allowedPermissions.length; i++) { if (!supportedPermissions[allowedPermissions[i]]) { revert PermissionNotSupported(allowedPermissions[i]); } }
LeaseInfo storage lease = _leaseInfos[tokenId]; lease.user = user; lease.expires = expires; lease.rentAmount = rentAmount; lease.platformFeeRate = platformFeeRate; lease.isEmergencyTerminated = false; lease.penaltyAmount = rentAmount / 10; // 违约金默认10%租金 lease.allowedPermissions = allowedPermissions;
emit LeaseTermsSet(tokenId, user, expires, rentAmount, platformFeeRate, allowedPermissions); }
// ========== 核心功能:支付租金 ========== function payRent(uint256 tokenId) external payable nonReentrant whenNotPaused { // 修复点2:用_ownerOf替代_exists判断NFT是否存在 try this.ownerOf(tokenId) { // NFT存在,继续执行 } catch { revert TokenNotExists(tokenId); }
LeaseInfo storage lease = _leaseInfos[tokenId];
// 校验租赁未到期且未紧急终止
if (block.timestamp > lease.expires && lease.expires != 0) revert LeaseExpired(tokenId);
if (lease.isEmergencyTerminated) revert LeaseExpired(tokenId);
// 校验支付金额足够
uint256 remaining = lease.rentAmount - lease.paidAmount;
if (msg.value < remaining) revert InsufficientPayment(remaining, msg.value);
lease.paidAmount += msg.value;
emit RentPaid(tokenId, msg.sender, msg.value);
}
// ========== 核心功能:提取租赁收益(ERC-7281核心) ========== function claimRevenue(uint256 tokenId) external nonReentrant whenNotPaused returns (uint256) { // 修复点3:用_ownerOf替代_exists判断NFT是否存在 try this.ownerOf(tokenId) { // NFT存在,继续执行 } catch { revert TokenNotExists(tokenId); }
LeaseInfo storage lease = _leaseInfos[tokenId];
// 仅当租金已全额支付且租赁到期/终止时可提取
bool canClaim = (lease.paidAmount >= lease.rentAmount) &&
(block.timestamp > lease.expires || lease.isEmergencyTerminated);
if (!canClaim) revert NoRevenueToClaim();
// 计算收益分配:所有者 + 平台
uint256 platformFee = (lease.rentAmount * lease.platformFeeRate) / 10000;
uint256 ownerRevenue = lease.rentAmount - platformFee;
// 转账给平台和NFT所有者
if (platformFee > 0) {
payable(platformWallet).transfer(platformFee);
}
payable(ownerOf(tokenId)).transfer(ownerRevenue);
emit RevenueClaimed(tokenId, ownerOf(tokenId), ownerRevenue);
emit RevenueClaimed(tokenId, platformWallet, platformFee);
return ownerRevenue;
}
// ========== 核心功能:紧急终止租赁 ========== function emergencyTerminateLease(uint256 tokenId) external nonReentrant whenNotPaused { // 修复点4:用_ownerOf替代_exists判断NFT是否存在 try this.ownerOf(tokenId) { // NFT存在,继续执行 } catch { revert TokenNotExists(tokenId); }
if (ownerOf(tokenId) != msg.sender) revert NotTokenOwner(tokenId, msg.sender);
LeaseInfo storage lease = _leaseInfos[tokenId];
lease.isEmergencyTerminated = true;
// 向使用者支付违约金(从所有者余额扣除)
if (lease.penaltyAmount > 0 && lease.user != address(0)) {
payable(lease.user).transfer(lease.penaltyAmount);
}
emit LeaseEmergencyTerminated(tokenId, msg.sender, lease.penaltyAmount);
}
// ========== 核心功能:设置碎片化租赁份额 ========== function setFragmentLease( uint256 tokenId, address user, uint256 share ) external nonReentrant whenNotPaused { // 修复点5:用_ownerOf替代_exists判断NFT是否存在 try this.ownerOf(tokenId) { // NFT存在,继续执行 } catch { revert TokenNotExists(tokenId); }
if (ownerOf(tokenId) != msg.sender) revert NotTokenOwner(tokenId, msg.sender);
LeaseInfo storage lease = _leaseInfos[tokenId];
lease.fragmentShares[user] = share;
// 校验总份额不超过100%
uint256 totalShare;
for (uint256 i = 0; i < lease.allowedPermissions.length; i++) {
// 简化校验:实际场景需遍历所有fragmentShares
totalShare += share;
}
if (totalShare > 10000) revert InvalidShareAmount(totalShare);
emit FragmentLeaseSet(tokenId, user, share);
}
// ========== 视图函数:查询租赁信息 ========== // 查询当前使用者 function userOf(uint256 tokenId) external view returns (address) { LeaseInfo storage lease = _leaseInfos[tokenId]; if (block.timestamp <= lease.expires && !lease.isEmergencyTerminated) { return lease.user; } return address(0); }
// 查询租赁到期时间 function userExpires(uint256 tokenId) external view returns (uint64) { return _leaseInfos[tokenId].expires; }
// 查询使用者权限 function getUserPermissions(uint256 tokenId) external view returns (bytes32[] memory) { return _leaseInfos[tokenId].allowedPermissions; }
// 查询碎片化租赁份额 function getFragmentShare(uint256 tokenId, address user) external view returns (uint256) { return _leaseInfos[tokenId].fragmentShares[user]; }
// ========== 管理员功能 ========== // 新增支持的权限 function addSupportedPermission(bytes32 permission) external onlyOwner { supportedPermissions[permission] = true; }
// 修改平台钱包地址 function setPlatformWallet(address newWallet) external onlyOwner { require(newWallet != address(0), "New wallet cannot be zero"); platformWallet = newWallet; }
// 暂停/恢复合约 function pause() external onlyOwner { _pause(); }
function unpause() external onlyOwner { _unpause(); }
// ========== 辅助函数 ========== // 铸造NFT(仅示例) function mint(address to, uint256 tokenId) external onlyOwner { _mint(to, tokenId); }
// ========== 修复点6:移除重写的_transfer函数 ========== // 说明:OpenZeppelin 5.x中_transfer为非虚函数,不可重写; // 若需自定义转让逻辑,可重写safeTransferFrom或使用_beforeTokenTransfer钩子 }
### 合约编译指令:`npx hardhat compile`
### 智能合约部署
// scripts/deploy.js import { network, artifacts } from "hardhat"; async function main() { // 连接网络 const { viem } = await network.connect({ network: network.name });//指定网络进行链接
// 获取客户端 const [deployer] = await viem.getWalletClients(); const publicClient = await viem.getPublicClient();
const deployerAddress = deployer.account.address; console.log("部署者的地址:", deployerAddress); // 加载合约 const artifact = await artifacts.readArtifact("ERC7281NFT"); const hash = await deployer.deployContract({ abi: artifact.abi,//获取abi bytecode: artifact.bytecode,//硬编码 args: ["MyERC7281NFT","MINFT",deployerAddress,500n],//nft名称,nft符号,部署者地址,price });
// 等待确认并打印地址 const receipt = await publicClient.waitForTransactionReceipt({ hash }); console.log("合约地址:", receipt.contractAddress); }
main().catch(console.error);
### 合约部署指令:`npx hardhat run ./scripts/xxx.ts`
### 智能合约测试
import assert from "node:assert/strict";
import { describe, it,beforeEach } from "node:test";
import { formatEther,parseEther,keccak256,toHex,hexToBytes, bytesToHex,zeroHash,encodePacked ,toBytes,pad,hexToBigInt } from 'viem'
import { network } from "hardhat";
describe("ERC7281智能合约测试", async function () {
let viem: any;
let publicClient: any;
let owner: any, user1: any, user2: any, user3: any;
let deployerAddress: string;
let MyERC7281NFT: any;
// 测试常量
const RENT_AMOUNT = parseEther("1"); // 1 ETH 租金
const PLATFORM_FEE_RATE = 500n; // 5% 平台费率(万分比)
const LEASE_DURATION = 3600n; // 1小时租赁期
const TOKEN_ID = 1;
const TOKEN_URI = "https://zygomorphic-magenta-bobolink.myfilebase.com/ipfs/QmQT8VpmWQVhUhoDCEK1mdHXaFaJ3KawkRxHm96GUhrXLB";
const encodeBytes32String=(str) => {
// 1. 将字符串转为UTF-8字节数组
const bytes = toBytes(str, { size: 32 });
// 2. 补零到32字节(不足时右侧补0,超过时截断)
const paddedBytes = pad(bytes, { size: 32, dir: "right" });
// 3. 转为十六进制字符串(带0x前缀)
return 0x${Buffer.from(paddedBytes).toString("hex")};
}
beforeEach (async function () {
const { viem } = await network.connect();
publicClient = await viem.getPublicClient();//创建一个公共客户端实例用于读取链上数据(无需私钥签名)。
[owner,user1,user2,user3] = await viem.getWalletClients();//获取第一个钱包客户端 写入联合交易
deployerAddress = owner.account.address;//钱包地址
MyERC7281NFT = await viem.deployContract("ERC7281NFT", [
"My Royalty NFT",
"MRNFT",
deployerAddress,
PLATFORM_FEE_RATE,
]);//部署合约
console.log("MyERC7281NFT合约地址:", MyERC7281NFT.address);
});
it("应该创建一个NFT", async function () {
//查询nft名称和符号
const name= await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "name",
args: [],
});
const symbol= await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "symbol",
args: [],
});
console.log("nftINFO:", name,symbol);
//铸造nft
await owner.writeContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "mint",
args: [user1.account.address,TOKEN_ID],
});
//查询nft所有者
const ownerOf = await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "ownerOf",
args: [TOKEN_ID],
});
console.log("nft所有者:", ownerOf);
});
it("应该成功设置NFT租赁条款", async function () { //铸造nft await owner.writeContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "mint", args: [owner.account.address,TOKEN_ID], }); const ownerOf = await publicClient.readContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "ownerOf", args: [TOKEN_ID], }); console.log("nft所有者:", ownerOf); // 计算租赁到期时间(当前时间 + 1小时) const blockNumber = await publicClient.getBlockNumber(); const block = await publicClient.getBlock({ blockNumber }); const expires = BigInt(block.timestamp) + LEASE_DURATION; console.log(expires) console.log(encodeBytes32String("GAME_USE")); // 允许的权限:GAME_USE const allowedPermissions = [encodeBytes32String("GAME_USE")];
// 所有者设置租赁条款
const hash= await owner.writeContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "setLeaseTerms",
args: [TOKEN_ID,user1.account.address,expires,RENT_AMOUNT,PLATFORM_FEE_RATE,allowedPermissions],
});
await publicClient.waitForTransactionReceipt({ hash });
console.log("租赁条款设置成功,交易哈希:", hash);
await publicClient.waitForTransactionReceipt({ hash });
// 验证租赁信息
const user = await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "userOf",
args: [TOKEN_ID],
});
const expiration = await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "userExpires",
args: [TOKEN_ID],
});
const permissions = await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "getUserPermissions",
args: [TOKEN_ID],
});
console.log("user:", user);
console.log("expiration:", expiration);
console.log("permissions:", permissions);
}); it("应该成功支付租金", async function () { await owner.writeContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "mint", args: [owner.account.address,TOKEN_ID], }); const ownerOf = await publicClient.readContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "ownerOf", args: [TOKEN_ID], }); console.log("nft所有者:", ownerOf); // 记录支付前的余额 const userBalanceBefore = await publicClient.getBalance({ address: user1.account.address }); console.log(formatEther(userBalanceBefore)) // 使用者支付租金(1 ETH) const hash = await user1.writeContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "payRent", args: [TOKEN_ID], }); console.log("支付租金交易哈希:", hash); await publicClient.waitForTransactionReceipt({ hash }); // 记录支付后的余额 const userBalanceAfter = await publicClient.getBalance({ address: user1.account.address }); console.log(formatEther(userBalanceAfter)) console.log(formatEther(userBalanceBefore - RENT_AMOUNT)) }); // 测试4:设置碎片化租赁份额 it("应该成功设置碎片化租赁份额", async function () { await owner.writeContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "mint", args: [owner.account.address,TOKEN_ID], }); const ownerOf = await publicClient.readContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "ownerOf", args: [TOKEN_ID], }); console.log("nft所有者:", ownerOf); const share = 5000n; // 50% 份额
// 所有者设置碎片化租赁份额
await owner.writeContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "setFragmentLease",
args: [TOKEN_ID,user1.account.address,share],
});
// 验证份额
const fragmentShare = await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "getFragmentShare",
args: [TOKEN_ID,user1.account.address],
});
console.log("用户",user1.account.address,"的碎片化租赁份额:", fragmentShare);
}); it("应该成功紧急终止租赁", async function () { await owner.writeContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "mint", args: [owner.account.address,TOKEN_ID], }); const ownerOf = await publicClient.readContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "ownerOf", args: [TOKEN_ID], }); console.log("nft所有者:", ownerOf); // 所有者紧急终止租赁 await owner.writeContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "emergencyTerminateLease", args: [TOKEN_ID], });
// 验证租赁已终止(使用者变为0地址)
const user = await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "userOf",
args: [TOKEN_ID],
});
console.log("用户",user1.account.address,"的租赁已终止,使用者:", user);
}); // 测试6:提取租赁收益
it("应该成功提取租赁收益", async function () { // ========== 步骤1:铸造NFT给user1(所有者) ========== await owner.writeContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "mint", args: [user1.account.address, TOKEN_ID], });
// 验证NFT所有者
const nftOwner = await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "ownerOf",
args: [TOKEN_ID],
});
console.log("NFT所有者地址:", nftOwner);
console.log("预期所有者地址:", user1.account.address);
// expect(nftOwner.toLowerCase()).to.equal(user1.account.address.toLowerCase());
console.log("用户", user1.account.address, "的租赁已终止,使用者:", nftOwner);
// ========== 步骤2:设置租赁条款(关键:用user1账户调用) ==========
const blockNumber = await publicClient.getBlockNumber();
const block = await publicClient.getBlock({ blockNumber });
const expires = BigInt(block.timestamp) + LEASE_DURATION; // 未来到期时间(允许支付租金)
console.log("租赁到期时间:", expires);
// 核心修复:用user1(NFT所有者)调用setLeaseTerms,而非owner
const setLeaseTermshash = await user1.writeContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "setLeaseTerms",
args: [
TOKEN_ID,
user2.account.address, // 租户地址
expires,
RENT_AMOUNT, // 1 ETH
PLATFORM_FEE_RATE, // 500 = 5%
[encodeBytes32String("GAME_USE")] // 正确的权限值
],
account: user1.account, // 显式指定账户,确保使用user1
});
await publicClient.waitForTransactionReceipt({ hash: setLeaseTermshash }); // 修复参数名错误
console.log("租赁条款设置成功,交易哈希:", setLeaseTermshash);
// ========== 步骤3:租户支付租金(租赁未过期) ==========
const payTxHash = await user2.writeContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "payRent",
args: [TOKEN_ID],
value: RENT_AMOUNT, // 支付1 ETH租金
account: user2.account,
});
await publicClient.waitForTransactionReceipt({ hash: payTxHash });
console.log("租金支付完成,交易哈希:", payTxHash);
// ========== 步骤4:更新租赁到期时间为过去(满足提取条件) ==========
const expiredTime = BigInt(Math.floor(Date.now() / 1000) - 3600); // 1小时前过期
const updateLeaseHash = await user1.writeContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "setLeaseTerms",
args: [
TOKEN_ID,
user2.account.address,
expiredTime, // 更新为已过期
RENT_AMOUNT,
PLATFORM_FEE_RATE,
[encodeBytes32String("GAME_USE")]
],
account: user1.account,
});
await publicClient.waitForTransactionReceipt({ hash: updateLeaseHash });
console.log("租赁到期时间更新为已过期,交易哈希:", updateLeaseHash);
// ========== 步骤5:记录提取前的余额 ==========
const ownerBalanceBefore = await publicClient.getBalance({
address: user1.account.address
});
const platformBalanceBefore = await publicClient.getBalance({
address: owner.account.address
});
const contractBalanceBefore = await publicClient.getBalance({
address: MyERC7281NFT.address
});
console.log("\n=== 提取收益前余额 ===");
console.log("合约余额:", formatEther(contractBalanceBefore));
console.log("所有者余额:", formatEther(ownerBalanceBefore));
console.log("平台余额:", formatEther(platformBalanceBefore));
// ========== 步骤6:提取收益(用user1账户) ==========
const claimTxHash = await user1.writeContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "claimRevenue",
args: [TOKEN_ID],
account: user1.account, // 确保使用NFT所有者账户
});
await publicClient.waitForTransactionReceipt({ hash: claimTxHash });
console.log("收益提取完成,交易哈希:", claimTxHash);
// ========== 步骤7:验证收益 ==========
const platformFee = (RENT_AMOUNT * PLATFORM_FEE_RATE) / 10000n; // 5%手续费
const ownerRevenue = RENT_AMOUNT - platformFee; // 95%归所有者
console.log("预期平台收益:", formatEther(platformFee));
console.log("预期所有者收益:", formatEther(ownerRevenue));
// 提取后余额
const ownerBalanceAfter = await publicClient.getBalance({
address: user1.account.address
});
const platformBalanceAfter = await publicClient.getBalance({
address: owner.account.address
});
const contractBalanceAfter = await publicClient.getBalance({
address: MyERC7281NFT.address
});
console.log("\n=== 提取收益后余额 ===");
console.log("所有者收益:", formatEther(ownerBalanceAfter));
console.log("平台收益:", formatEther(platformBalanceAfter));
console.log("合约余额:", formatEther(contractBalanceAfter));
}); it("应该成功修改平台钱包地址", async function () { const newPlatformWallet = "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc";
// 部署者(合约所有者)修改平台钱包
const hash = await owner.writeContract({ address: MyERC7281NFT.address, abi: MyERC7281NFT.abi, functionName: "setPlatformWallet", args: [newPlatformWallet], });
// 验证平台钱包已更新
await publicClient.waitForTransactionReceipt({ hash });
const platformWallet = await publicClient.readContract({
address: MyERC7281NFT.address,
abi: MyERC7281NFT.abi,
functionName: "platformWallet",
args: [],
});
console.log("平台钱包:", platformWallet);
console.log("new钱包:", newPlatformWallet);
}); });
### 合约测试指令:`npx hardhat test ./test/xxx.ts`
# 总结
至此,**ERC7281 智能租赁合约从理论到实践的全流程落地**。理论上,系统梳理了 ERC7281 标准的核心定义、功能价值与行业应用,明确其解决传统跨链痛点的核心优势;实践中,基于 Hardhat V3 与 OpenZeppelin,完成合约的开发、部署与全链路测试,形成可复用的技术方案。 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!