RWA 合规租赁利器:基于 ERC-7281 的现实世界资产代币化合约开发

36 阅读14分钟

前言

本文围绕 ERC7281 标准展开全面梳理,涵盖其核心定义、核心功能、行业痛点解决方案及典型应用场景,并结合 Hardhat V3 开发环境与 OpenZeppelin 工具库,完整实现了 ERC7281 智能租赁合约的开发、部署与测试全流程落地,形成 “标准理论 + 实战落地” 的完整内容体系。

概述

ERC7281(又称 xERC-20)是以太坊改进提案(EIP),核心是将跨链代币控制权从桥接方交还给发行方,通过扩展 ERC-20 接口实现多桥兼容、动态限额与统一资产表示,解决传统跨链的流动性碎片化、安全风险集中与资产不可互换问题,适用于 DeFi、稳定币、RWA 等多链场景。

是什么:核心定义与架构

  • 定位:ERC-20 的扩展标准,保留基础接口,新增跨链治理与多桥协作能力。

  • 核心角色

    1. 发行方:掌控跨链权限,可授权 / 撤销桥接方、设置单桥铸造限额。
    2. 桥接方:经授权后在目标链铸造 / 销毁代币,受额度与权限约束。
    3. Lockbox(可选) :兼容层合约,将传统 ERC-20 封装为 xERC-20,支持存量资产升级。
  • 关键接口

    接口功能
    authorizeBridge授权桥接方在指定链操作
    setBridgeLimit为桥接方设置铸造 / 销毁限额
    mintRemote/burnRemote跨链铸造 / 销毁标准代币
    isBridgeAuthorized查询桥接方权限状态

能做什么:核心功能与价值

  1. 发行方主权管理

    • 自主选择桥接方并动态调整限额,降低单点风险。
    • 桥接方被攻击时,损失上限为该桥额度,且可快速下架问题桥接方。
  2. 统一跨链资产表示

    • 不同桥接方铸造的同币种资产完全可互换(1:1),消除 slippage 与流动性分裂。
    • 新链 /rollup 无需重复部署流动性,资产跨域流动更高效。
  3. 兼容与可扩展性

    • 向下兼容 ERC-20,支持通过 Lockbox 平滑迁移存量资产。
    • 多桥并行协作,激励桥接方提升安全性以争取更高额度。

解决了什么问题:行业痛点对比

传统跨链痛点ERC7281 解决方案
控制权旁落桥接方掌控跨链资产,发行方无法干预发行方持有权限,可实时调整桥接策略
流动性分裂不同桥铸造的同币种资产不可互换,形成流动性孤岛统一资产表示,多桥铸造的代币完全 fungible
安全风险集中单一桥被攻击可能导致资产全量损失单桥限额隔离风险,快速下架问题桥接方
用户体验差跨链存在 slippage,资产迁移流程复杂无滑点跨链,发行方治理升级不影响用户持仓
扩展成本高新链需重复部署流动性,资产覆盖效率低统一资产模型降低新链适配成本

行业应用:典型场景

  1. 稳定币跨链部署

    • USDC/USDT 等可通过 ERC7281 在多链发行统一版本,避免多桥并行导致的流动性分散,提升资金利用率与用户体验。
  2. DeFi 跨链协议

    • Aave、Uniswap 等可基于 xERC-20 实现跨链资产无缝接入,降低多链部署复杂度,增强协议间 composability。
  3. RWA(现实世界资产)上链

    • 国债、房产等资产代币化后,通过 ERC7281 跨链流通,保持资产一致性与合规可控性。
  4. NFT 跨链(扩展方向)

    • 社区已提出 ERC7281 扩展方案(如 Sovereign Bridged NFTs),复用授权与限额机制,解决 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";

/**
 * @title ERC7281NFT
 * @dev 基于ERC-7281标准的NFT租赁合约,兼容OpenZeppelin 5.4.0
 * 核心功能:租赁收益分成、动态权限、紧急回收、碎片化租赁
 */
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核心) ==========
    /**
     * @dev 设置NFT租赁条款,仅NFT所有者可调用
     * @param tokenId NFT ID
     * @param user 租赁使用者地址
     * @param expires 租赁到期时间戳(Unix秒)
     * @param rentAmount 总租金(Wei)
     * @param platformFeeRate 平台费率(万分比,如500=5%)
     * @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,完成合约的开发、部署与全链路测试,形成可复用的技术方案。