赞
踩
本文我们将简要介绍什么是以太坊改进提案(EIP)和以太坊意见征求稿(ERC),以及它们是如何使用的,实现一个 ERC-2981标准的 NFT 版税标准的合约案例。
官网:https://eips.ethereum.org/
EIP全称Ethereum Improvement Proposal(以太坊改进提案),是以太坊特定新功能和流程的技术设计文档。
EIP 作为主要机制
EIP根据所涉及的领域不同大体可以分为三大类Standards Track EIP、Meta EIP和Informational EIP:
Standards Track EIP
它描述的是任何改变以太坊所有或大多数实现细节的EIP。进一步分为四个子类:Core、Networking、Interface、ERC。
Meta:描述了以太坊周边相关事物的改变过程,例如对决策过程的改变。
Informational:提供一般信息或描述以太坊设计问题,但不提出新功能。用户可以自由地忽略Informational EIP,因为信息性 EIP 不一定代表以太坊社区的推荐。
ERC的全称是Ethereum Request For Comment ,即以太坊意见征求稿。主要是用来记录以太坊上应用级的开发标准和约定。例如Token标准、名称注册表、URI 方案、库/包格式和钱包格式(EIP75、EIP85)。ERC 指定了合约需要实现的一组必需功能,以便应用程序和其他合约可以理解如何与它们交互。 例如,最流行的标准之一是ERC-721标准,它定义了 NFT 是什么。因为应用程序知道 ERC-721 是什么样子,所以它知道它可以在合约上与哪些函数和属性交互。
请注意,ERC 不被视为core EIP,因此是否采用该标准取决于开发人员。因此,提高对 ERC 的认识对其作用至关重要。
EIP 是以太坊治理的核心方式。任何人都可以提出它们,社区成员可以评论、辩论和协作来决定它是否应该被采纳!您可以在此处找到提交指南。
ERC 是智能合约的可组合性的动力!可组合性定义了 dapp 和合约相互交互的能力。例如,ERC-2981 NFT 版税标准定义了如何在合约上存储版税信息,以便在市场等 dapp 进行销售时,他们知道如何获得补偿艺术家所需的版税信息!
如上所述,ERC-2981 是版税标准。为了符合 ERC-2981 标准,智能合约必须具有以下功能:
pragma solidity ^0.6.0; import "./IERC165.sol"; /// /// @dev Interface for the NFT Royalty Standard /// interface IERC2981 is IERC165 { /// ERC165 bytes to add to interface array - set in parent contract /// implementing this standard /// /// bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a /// bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a; /// _registerInterface(_INTERFACE_ID_ERC2981); /// @notice Called with the sale price to determine how much royalty // is owed and to whom. /// @param _tokenId - the NFT asset queried for royalty information /// @param _salePrice - the sale price of the NFT asset specified by _tokenId /// @return receiver - address of who should be sent the royalty payment /// @return royaltyAmount - the royalty payment amount for _salePrice function royaltyInfo( uint256 _tokenId, uint256 _salePrice ) external view returns ( address receiver, uint256 royaltyAmount ); } interface IERC165 { /// @notice Query if a contract implements an interface /// @param interfaceID The interface identifier, as specified in ERC-165 /// @dev Interface identification is specified in ERC-165. This function /// uses less than 30,000 gas. /// @return `true` if the contract implements `interfaceID` and /// `interfaceID` is not 0xffffffff, `false` otherwise function supportsInterface(bytes4 interfaceID) external view returns (bool); }
ps:ERC-165是一种允许合约声明其对接口的支持的标准。这将使市场能够检查 NFT 是否支持版税标准!在市场合约中可能看起来像这样:
bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;
function checkRoyalties(address _contract) internal returns (bool) {
(bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_ERC2981);
return success;
}
在我们开始编写代码之前,我们先了解一些关于 NFT 版税标准的重要警告。
版权支付规范没有强制。根据该信息采取行动取决于市场。目前,Coinbase NFT、Rarible、SuperRare 和 Zora 为 ERC-2981 支付版税。如果你的 NFT 在 OpenSea 上出售,你将必须通过他们的网站单独设置你的 NFT 的版税。
我们已经介绍了什么是 EIP、什么是 ERC 以及它们如何表示 ERC-2981,我们实际编写一个实现 ERC-2981 版税标准的 ERC-721 NFT 智能合约。您可以在此处找到完整的代码。我们将导入 Open Zeppelin 的合约,它提供安全的、预写的 ERC 实现,我们的合约可以继承这些实现!
要将您的 DApp 连接到以太坊主网和测试网,您需要一个 Infura 帐户。在这里注册一个帐户。
登录后,创建一个项目!我们称之为nft-royalty,然后从下拉列表中选择 Web3 API
要与浏览器中的dapp互动,您需要一个MetaMask钱包。在此处注册一个帐户。
为了部署到公共测试网络,您需要一些测试ETH来支付您的gas费用!Paradigm 是一个很棒的多链水龙头,它一次将资金存放在8个不同的网络中。
Truffle 用来构建您的 truffle 项目并添加示例合约和测试。我们创建一个名为nft-royalty的项目。
truffle init nft-royalty
cd nft-royalty
truffle create contract RoyalPets
truffle create test TestRoyalties
执行完,项目目录结构如下:
nft-royalty
├── contracts
│ └── RoyalPets.sol
├── migrations
│ └── 1_deploy_contracts.js
├── test
│ └── test_royalties.js
└── truffle-config.js
Open Zeppelin 已经提供了安全的、写好的 ERC-2981 和 ERC-721 合约实现,我们可以继承!要下载它们,只需调用npm i "@openzeppelin/contracts"
。
借助 OpenZeppelin,我们有几种方法可以识别 NFT 合约是否符合版税标准。由于我们的基础合约是 ERC-721,我们可以选择继承 OpenZeppelin 的版税合约ERC721Royalty。该合约覆盖了该_burn功能,以清除NFT的版税信息。
重要的提示!此函数和来自OpenZeppelin的
_burn
函数都不检查tokenId
所有权。这意味着任何人都可以销毁这个 NFT。如果您想避免这种情况,请添加require
检查该条件的检查。
function _burn(uint256 tokenId) internal virtual override {
super._burn(tokenId);
_resetTokenRoyalty(tokenId);
}
创建合约后,我们要设置默认的版税接收人和百分比。请注意,OpenZeppelin 使用基点计算特许权使用费。为了将默认接收人设置为合约所有者并将费用设置为 1%,可以在构造函数中进行设置:
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol";
contract RoyalPets is ERC721Royalty {
constructor() ERC721("RoyalPets", "RP") {
_setDefaultRoyalty(msg.sender, 100);
}
}
在本教程中,我们使用 OpenZeppelin 的ERC721URIStorage
扩展。在这种情况下,我们希望它也继承OpenZeppelin ERC2981合约的属性,如下所示:
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract RoyalPets is ERC721URIStorage, ERC2981 {
constructor() ERC721("RoyalPets", "RP") {
_setDefaultRoyalty(msg.sender, 100);
}
}
此时,我们可以看到vscode报错,ERC721URIStorage
和ERC2981
都实现了supportsInterface
方法!为了解决这个问题,我们也需要在RoyalPets 中自己实现。在这个函数中添加:
function supportsInterface(bytes4 interfaceId)
public view virtual override(ERC721, ERC2981)
returns (bool) {
return super.supportsInterface(interfaceId);
}
此外,因为我们不再继承ERC721Royalty,所以我们不再拥有它的_burn
方法。需要我们自己添加:
function _burn(uint256 tokenId)
internal virtual override {
super._burn(tokenId);
_resetTokenRoyalty(tokenId);
}
为了让外部账户可以销毁他们的 NFT,开放了一个公开销毁方法:
function burnNFT(uint256 tokenId)
public {
_burn(tokenId);
}
最后,我们将添加 NFT 铸币功能。案例将创建两种类型的铸造函数:一种使用默认版税信息铸造代币,另一种在每个代币的基础上指定版税信息。
如前所述,Infura 博客涵盖了这些基础知识。一个细微的差别是我们不会使用静态元数据文件来填充tokenURI. 两个铸币函数如下所示:
function mintNFT(address recipient, string memory tokenURI) public onlyOwner returns (uint256) { _tokenIds.increment(); uint256 newItemId = _tokenIds.current(); _safeMint(recipient, newItemId); _setTokenURI(newItemId, tokenURI); return newItemId; } function mintNFTWithRoyalty(address recipient, string memory tokenURI, address royaltyReceiver, uint96 feeNumerator) public onlyOwner returns (uint256) { uint256 tokenId = mintNFT(recipient, tokenURI); _setTokenRoyalty(tokenId, royaltyReceiver, feeNumerator); return tokenId; }
最终的智能合约,如下:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; import "@openzeppelin/contracts/token/common/ERC2981.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; contract RoyalPets is ERC721URIStorage, ERC2981, Ownable { using Counters for Counters.Counter; Counters.Counter private _tokenIds; constructor() ERC721("RoyalPets", "RP") { _setDefaultRoyalty(msg.sender, 100); } function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC2981) returns (bool) { return super.supportsInterface(interfaceId); } function _burn(uint256 tokenId) internal virtual override { super._burn(tokenId); _resetTokenRoyalty(tokenId); } function burnNFT(uint256 tokenId) public onlyOwner { _burn(tokenId); } function mintNFT(address recipient, string memory tokenURI) public onlyOwner returns (uint256) { _tokenIds.increment(); uint256 newItemId = _tokenIds.current(); _safeMint(recipient, newItemId); _setTokenURI(newItemId, tokenURI); return newItemId; } function mintNFTWithRoyalty(address recipient, string memory tokenURI, address royaltyReceiver, uint96 feeNumerator) public onlyOwner returns (uint256) { uint256 tokenId = mintNFT(recipient, tokenURI); _setTokenRoyalty(tokenId, royaltyReceiver, feeNumerator); return tokenId; } }
为了部署我们的智能合约,我们需要对migrations/1_deploy_contracts.js
修改:
const RoyalPets = artifacts.require("Royalpets");
module.exports = function (deployer) {
deployer.deploy(RoyalPets);
};
接下来,我们启动一个本地 Ganache 实例。
有多种方法可以做到这一点:通过 VS Code 扩展、Ganache CLI 和 Ganche 图形用户界面。2种方式都有自己的优势,您可以在此处查看Ganache v7 版本的功能。
在本教程中,我们将使用 GUI。运行Ganche 图形用户界面,创建一个工作区,然后点击保存。它会在 HTTP://127.0.0.1:7545 上创建一个正在运行的 Ganache 实例。
接下来,在truffle-config.js
中取消下面代码注释,将 development
中的端口号修改为 7545 。
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
}
然后运行truffle migrate
,默认为development
网络,即可部署!
也可以从 VS Code 扩展进行部署。build/contracts
然后,您可以在 VS Code 扩展中或在 Ganache UI中查看您构建的合约!
如果您想在不编写完整测试的情况下即时测试您的智能合约命令,您可以使用truffle develop
或truffle console
。在这里阅读更多相关信息。
出于本教程的目的,我们将继续编写 Javascript 测试。
请注意,使用 Truffle,您可以选择使用 Javascript、Typescript 或 Solidity 编写测试。
const RoyalPets = artifacts.require("RoyalPets"); contract("RoyalPets", function (accounts) { it("should support the ERC721 and ERC2198 standards", async () => { const royalPetsInstance = await RoyalPets.deployed(); const ERC721InterfaceId = "0x80ac58cd"; const ERC2981InterfaceId = "0x2a55205a"; var isERC721 = await royalPetsInstance.supportsInterface(ERC721InterfaceId); var isER2981 = await royalPetsInstance.supportsInterface(ERC2981InterfaceId); assert.equal(isERC721, true, "RoyalPets is not an ERC721"); assert.equal(isER2981, true, "RoyalPets is not an ERC2981"); }); it("should return the correct royalty info when specified and burned", async () => { const royalPetsInstance = await RoyalPets.deployed(); await royalPetsInstance.mintNFT(accounts[0], "fakeURI"); // Override royalty for this token to be 10% and paid to a different account await royalPetsInstance.mintNFTWithRoyalty(accounts[0], "fakeURI", accounts[1], 1000); const defaultRoyaltyInfo = await royalPetsInstance.royaltyInfo.call(1, 1000); var tokenRoyaltyInfo = await royalPetsInstance.royaltyInfo.call(2, 1000); const owner = await royalPetsInstance.owner.call(); assert.equal(defaultRoyaltyInfo[0], owner, "Default receiver is not the owner"); // Default royalty percentage taken should be 1%. assert.equal(defaultRoyaltyInfo[1].toNumber(), 10, "Royalty fee is not 10"); assert.equal(tokenRoyaltyInfo[0], accounts[1], "Royalty receiver is not a different account"); // Default royalty percentage taken should be 1%. assert.equal(tokenRoyaltyInfo[1].toNumber(), 100, "Royalty fee is not 100"); // Royalty info should be set back to default when NFT is burned await royalPetsInstance.burnNFT(2); tokenRoyaltyInfo = await royalPetsInstance.royaltyInfo.call(2, 1000); assert.equal(tokenRoyaltyInfo[0], owner, "Royalty receiver has not been set back to default"); assert.equal(tokenRoyaltyInfo[1].toNumber(), 10, "Royalty has not been set back to default"); }); });
执行命令:
truffle test
执行结果:
Contract: RoyalPets
✔ should support the ERC721 and ERC2198 standards (67ms)
✔ should return the correct royalty info when specified and burned (1077ms)
2 passing (1s)
如果您想铸造NFT并在MetaMask 钱包中查看它,则需要将合约部署到公共测试网或主网上。为此,您需要从Infura项目和MetAmask Wallet Secret Key中获取Infura Project API。在您的文件夹的根目录,添加一个.env
文件,输入该信息。
警告:不要泄漏或提交此文件。我们建议将
.env
添加到.gitignore
文件中。
MNEMONIC="YOUR SECRET KEY"
INFURA_API_KEY="YOUR INFURA_API_KEY"
在ruffle-config.js
文件的顶部,添加如下代码以获取这个信息:
require('dotenv').config();
const mnemonic = process.env["MNEMONIC"];
const infuraApiKey = process.env["INFURA_API_KEY"];
const HDWalletProvider = require('@truffle/hdwallet-provider');
最后,将 Goerli 网络添加到以下networks
列表中:
goerli: {
provider: () => new HDWalletProvider(mnemonic, `https://goerli.infura.io/v3/${infuraApiKey}`),
network_id: 5, // Goerli's network id
chain_id: 5, // Goerli's chain id
gas: 5500000, // Gas limit used for deploys.
confirmations: 2, // # of confirmations to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets)
}
最终的truffle-config.js
,如下
require('dotenv').config(); const mnemonic = process.env["MNEMONIC"]; const infuraApiKey = process.env["INFURA_API_KEY"]; const HDWalletProvider = require('@truffle/hdwallet-provider'); module.exports = { networks: { development: { host: "127.0.0.1", // Localhost (default: none) port: 7545, // Standard Ethereum port (default: none) network_id: "*", // Any network (default: none) }, goerli: { provider: () => new HDWalletProvider(mnemonic, `https://goerli.infura.io/v3/${infuraApiKey}`), network_id: 5, // Goerli's network id chain_id: 5, // Goerli's chain id gas: 5500000, // Gas limit used for deploys. confirmations: 2, // # of confirmations to wait between deployments. (default: 0) timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) skipDryRun: true // Skip dry run before migrations? (default: false for public nets) } }, // Set default mocha options here, use special reporters, etc. mocha: { // timeout: 100000 }, // Configure your compilers compilers: { solc: { version: "0.8.15", // Fetch exact version from solc-bin (default: truffle's version) } }, };
需要为 dotenv
和 @truffle/hdwallet-provider
dev依赖项。
npm i --save-dev dotenv
npm i --save-dev @truffle/hdwallet-provider
truffle migrate --network goerli
最后,运行 truffle migrate --network goerli
进行部署!
然后,要快速与Goerli网络进行交互,我们可以使用truffle console --network goerli
,并调用合适的合约函数。我们已经将一些元数据固定在IPFS上,供您使用,tokenURI: ipfs://bafybeiffapvkruv2vwtomswqzxiaxdgm2dflet2cxmh6t4ixrgaezumbw4
。
命令执行如下:
truffle migrate --network goerli
truffle(goerli)> const contract = await RoyalPets.deployed()
undefined
truffle(goerli)> await contract.mintNFT("YOUR ADDRESS", "ipfs://bafybeiffapvkruv2vwtomswqzxiaxdgm2dflet2cxmh6t4ixrgaezumbw4")
如果您想使用自己的元数据,可以使用Truffle或Infura。在此处查看指南:
要在您的手机钱包上查看您的 NFT,请手机打开 MetaMask ,切换到 Goerli 网络,然后打开 NFTs 选项卡!要在 OpenSea 上查看,您必须部署到主网或Polygon。否则,如果您将合约部署到 rinkeby,您可以在 https://testnets.opensea.io/
上查看它。请注意,合并后 rinkeby 将被弃用。
如果您不想在Infura项目中监视您的交易,也可以通过Truffle Dashboard设置,这使您可以通过Metamask部署和签署交易 - 并且永远不会泄漏您的私钥!为此,只需运行:
truffle dashboard
truffle migrate --network dashboard
truffle console --network dashboard
至此,您已经写了一份NFT智能合约,可以查询版税信息。请注意将元数据上传到IPFS的更深入的指南!有关代码的详细演练,可以在YouTube上观看直播。在Web3的未来版本中,通过实现ERC-4907以及创建存在各种NFT标准的NFT租赁市场,可以看到我们如何通过实现ERC-4907来生成基本的ERC-721s!
您可能会考虑的其他一些扩展实现oyaltyInfo的方式。Gemini是一个很酷的博客,详细介绍了一些decaying royalties, multisig royalties, and stepped royalties在这里。如果您尝试任何一个,请告诉我们!
如果您想关于内容进行谈论,建议您在此处展开讨论。如果您想展示自己的建造或与Unleashed社区一起交流,请加入我们的 Discord!最后,不要忘记在Twitter上关注我们,以获取所有Truffle的最新更新。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。