ERC721智能合约中每次NFT铸造的审批系统实现可行性问询
ERC721带审批机制的铸造方案
完全可以实现你要的「用户发起请求→所有者审批→完成铸造」流程,同时能优化Gas成本。以下是具体实现思路和代码示例:
核心设计思路
- 链上存储铸造请求:把用户的铸造请求(地址、NFT元数据、费用状态等)直接存在智能合约里,替代链下数据库的部分功能,确保流程可信。
- 拆分操作步骤:将「发起请求→审批→铸造」拆分为三个链上函数,既保留审批权限,又减少不必要的Gas支出。
- 费用托管到合约:用户发起请求时直接把铸造费转到合约,审批完成后再转给所有者,避免链下收费的信任问题。
智能合约实现示例
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract ApprovedNFT is ERC721, Ownable { // 存储铸造请求的结构体 struct MintRequest { address requester; // 请求用户地址 string tokenURI; // NFT元数据地址(如IPFS链接) bool isApproved; // 是否已审批 bool isMinted; // 是否已铸造 } uint256 public nextTokenId; mapping(uint256 => MintRequest) public mintRequests; uint256 public totalRequests; uint256 public mintFee; // 铸造费用 // 事件:方便链下系统跟踪状态 event MintRequestCreated(uint256 requestId, address requester); event MintRequestApproved(uint256 requestId); event NFTMinted(uint256 requestId, uint256 tokenId, address to); constructor(string memory name, string memory symbol, uint256 _mintFee) ERC721(name, symbol) Ownable(msg.sender) { mintFee = _mintFee; } // 1. 用户发起铸造请求,支付费用到合约 function requestMint(string calldata _tokenURI) external payable { require(msg.value == mintFee, "费用金额不符"); totalRequests++; mintRequests[totalRequests] = MintRequest({ requester: msg.sender, tokenURI: _tokenURI, isApproved: false, isMinted: false }); emit MintRequestCreated(totalRequests, msg.sender); } // 2. 所有者审批特定请求 function approveMint(uint256 _requestId) external onlyOwner { MintRequest storage req = mintRequests[_requestId]; require(!req.isMinted, "该请求已完成铸造"); req.isApproved = true; emit MintRequestApproved(_requestId); } // 3. 用户领取已审批的NFT(用户支付Gas) function claimNFT(uint256 _requestId) external { MintRequest storage req = mintRequests[_requestId]; require(req.requester == msg.sender, "非请求发起者"); require(req.isApproved, "请求未通过审批"); require(!req.isMinted, "该请求已完成铸造"); req.isMinted = true; _safeMint(msg.sender, nextTokenId); emit NFTMinted(_requestId, nextTokenId, msg.sender); // 将合约中的费用转给所有者 payable(owner()).transfer(mintFee); nextTokenId++; } // 可选:所有者批量铸造已审批请求(所有者支付Gas,适合批量处理) function batchMintApproved(uint256[] calldata _requestIds) external onlyOwner { uint256 feeToTransfer = mintFee * _requestIds.length; require(address(this).balance >= feeToTransfer, "合约余额不足"); for(uint256 i = 0; i < _requestIds.length; i++){ uint256 reqId = _requestIds[i]; MintRequest storage req = mintRequests[reqId]; require(req.isApproved && !req.isMinted, "请求不符合铸造条件"); req.isMinted = true; _safeMint(req.requester, nextTokenId); emit NFTMinted(reqId, nextTokenId, req.requester); nextTokenId++; } payable(owner()).transfer(feeToTransfer); } // 所有者提取合约余额(防止费用卡住) function withdrawBalance() external onlyOwner { payable(owner()).transfer(address(this).balance); } // 所有者修改铸造费用(可选) function setMintFee(uint256 _newFee) external onlyOwner { mintFee = _newFee; } }
业务流程说明
- 用户发起请求:用户调用
requestMint,支付铸造费并提交NFT的元数据链接(如IPFS地址),生成唯一请求ID,仅需支付一次Gas。 - 所有者审批:你可以通过链下系统监听
MintRequestCreated事件,获取所有待审批请求;确认信息无误后,调用approveMint标记请求为已审批,若批量审批可大幅降低Gas成本。 - 完成铸造:
- 方式一:用户收到审批通知后,调用
claimNFT领取NFT,同时合约自动将费用转给你。 - 方式二:你调用
batchMintApproved批量处理所有已审批请求,一次Gas完成多份NFT铸造,用户无需额外操作。
- 方式一:用户收到审批通知后,调用
优化点说明
- 相比你之前的方案,新流程最多只需要两次Gas(用户发起+用户领取/所有者批量铸造),且批量操作能进一步降低Gas成本。
- 所有状态都在链上存储,避免链下数据库和链上状态不一致的问题。
- 可通过添加事件,轻松对接你的现有数据库,实现请求状态的实时同步。
内容的提问来源于stack exchange,提问作者Muhammad Minhaj
相关产品推荐
相关产品推荐

