NFT市场虽然很火,但高昂的gas费用和繁琐的操作步骤一直让用户头疼。每次交易都得授权,gas费蹭蹭往上涨,体验不太好。为了解决这些问题,我写了一个NFT市场应用:“NFTMarketPlus”,使用了ERC20Permit和EIP-712来优化gas消耗,还让操作更简单
NFT 市场虽然很火,但高昂的 gas 费用和繁琐的操作步骤一直让用户头疼。每次交易都得授权,gas 费蹭蹭往上涨,体验不太好。为了解决这些问题,我写了一个 NFT 市场应用:“NFT Market Plus”,使用了 ERC20Permit
和 EIP-712
来优化 gas 消耗,还让操作更简单。
ERC20Permit
省去了手动授权的步骤,直接用 token 交易,省时省钱;EIP-712
通过结构化签名,让授权 NFT 转移变得更高效、更安全。我还用 Next.js
写了个前端,用户可以在浏览器上直接与智能合约交互,体验更加顺畅。
接下来我会介绍整个项目的技术细节,包括智能合约的设计、前端交互的实现、以及如何优化 gas 和提升安全性,最后给出完整的项目源码和线上预览链接。希望能给想要开发 NFT 市场的朋友们一些参考。
智能合约的开发,我选用了 foundry
作为开发框架。 ERC20
代币作为 NFT 市场的基础支付货币,交易对象为 ERC721
标准的 NFT。相关功能依赖了 openzeppelin
库,如 ERC20Permit
、EIP712
、ECDSA
等。智能合约部署在 Sepolia
测试网。
前端开发使用了基于 React
框架的 Next.js
。使用了 RainbowKit
、viem
和 wagmi
来实现 Web3 相关的交互。TailwindCSS
和 NextUI
负责样式和 UI 组件。。同时,项目还引入了 TypeScript
进行类型检查,并使用了 Lucide
图标库和 React Hot Toast
来显示通知。前端页面托管和部署在 Vercel
。
智能合约包括三个文件:
YMToken.sol
: ERC20
代币作为 NFT Market Plus 的基础支付货币YMNFT.sol
:示例 NFT,作为 NFT Market Plus 的交易对象NFTMarketPlus.sol
:NFT Market Plus 主体// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract YMToken is ERC20, ERC20Burnable, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("YMToken", "YMT")
Ownable(initialOwner)
ERC20Permit("YMToken")
{}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
使用 openzeppelin
的模板生成器生成,相较普通的 ERC20
代币,多了线下 Permit
授权的功能。
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
同样使用 openzeppelin
的模板生成器生成,相较普通的 ERC721
,额外支持了 EIP712
。
function permit(
address _to,
uint256 _tokenId,
uint256 _deadline,
bytes calldata _signature
) public returns (address) {
// verify the signature
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
LIST_TYPEHASH,
_to,
_tokenId,
_deadline
)));
address signer = ECDSA.recover(digest, _signature);
require(ownerOf(_tokenId) == signer, "Signer is not the owner of the NFT");
_approve(_to, _tokenId, address(0));
return signer;
}
添加了 permit
方法来支持链上验证签名。
function permitBuy(address _nftAddr, uint256 _tokenId, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
require(block.timestamp <= deadline, "Signature expired");
token.permit(
msg.sender,
address(this),
nftList[_nftAddr][_tokenId].price,
deadline,
v, r, s
);
buy(_nftAddr, _tokenId);
}
permitBuy
方法在进行 buy 操作之前,先使用 ERC20Permit 里的 permit
方法验证传进来的签名数据是否正确。正确则直接进行 buy 操作;否则交易回退。相比于传统交易方式,少了手动 approve
的操作,既方便了交互,又节约了 gas。
function listWithSignature(
address _nftAddr,
uint256 _tokenId,
uint256 _price,
uint256 _deadline,
bytes calldata _signature
) public {
require(block.timestamp <= _deadline, "Signature expired");
// approve
address signer = INFT(_nftAddr).permit(address(this), _tokenId, _deadline, _signature);
// list the NFT
_list(signer, _nftAddr, _tokenId, _price);
}
listWithSignature
在 NFT 挂单前,调用了 YMNFT
的 permit
方法,使用 EIP712
结构化签名来验证交易信息。只有当签名者是 NFT 的所有者,挂单才能成功,否则将失败回退。
前端整体分为三个页面:
Market
页面用于展示已上架的 NFT。用户可以在该页面购买 NFT,NFT 所有者也可以在这里取消已上架的 NFT。
Profile
页面用于管理用户资产。用户可以在此查询 YMToken 代币数量、通过水龙头免费领取 1000 代币,以及查看自己拥有的 NFT 并进行挂单上架操作。
Admin
页面只对管理员开发。管理员可以在此向任意地址铸造任意代币和 NFT。
当我们需要将 NFT 上架时,在 Profile
页面点击 List 按钮,输入想要挂单的价格,点击 Submit,会在钱包插件中弹出签名确认信息,确认后生成签名,然后进入到上架操作,在钱包中点击确认,验证签名正确,就能上架成功,一会儿后即可在Market
页面看到刚刚上架的 NFT。
当我们需要购买 NFT 时,在 Market
页面点击 Buy 按钮,也会在钱包插件中弹出签名确认信息,生成签名,然后在购买操作中验证签名。如果签名正确且你有足够的 token,就能购买成功,该 NFT 会转移到你的地址,你可在 Profile
中查看确认。
我们可以注意到,使用 EIP-712
和 ERC20Permit
进行上架和购买操作,全程无需额外的 approve
,授权、验证、买/卖只需要一步搞定,非常顺滑流畅,极大提高了用户的操作体验,也节约了宝贵的 gas 手续费。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!