@1001-digital/erc721-extensions 中文文档教程
ERC721 Contract Extensions
OpenZeppelin ERC721 基础合约的一组可组合扩展。
Available Extensions
WithContractMetaData.sol
直接从您的智能合约中链接到您收藏的合约元数据。
建立在 Ownable
之上,并允许合同所有者即使在部署后也可以更改合同元数据。
确保 URL 链接到适当的 JSON 文件。
contract Token is ERC721, WithContractMetadata {
constructor()
ERC721("Token", "LT")
WithContractMetadata("ipfs://0123456789123456789123456789123456789123456789/metadata.json")
{}
}
要更改合约元数据 URI,请以合约所有者身份调用 setContractURI(string uri)
。
WithIPFSMetaData.sol
处理指向托管在 IPFS 上的元数据文件的链接,并遵循这样做的最佳实践:
- Projects have to embed the Content ID hash in the contract right from the start
- Project owners can never change the CID
- Tokens link to an
ipfs://
-URL to be independent of particular IPFS Gateways. - Tokens wrap a folder with a
metadata.json
file (and pot. all the assets of the token).
注意:在公开销售完成之前,您不应该发布元数据。
这是为了防止人们试图狙击稀有标记(稀有性可以通过遍历所有元数据文件并查看特征和属性分布得出)。 尽管这样做非常昂贵(以汽油费计),但它有可能成为不良做法。
contract CleanToken is ERC721, WithIPFSMetaData {
constructor()
ERC721("CleanToken", "CT")
WithIPFSMetaData("0123456789123456789123456789123456789123456789")
{}
}
WithIPFSMetadataAndPreviewMetadata.sol
TODO
WithTransferTimeLock.sol
为了防止在公开销售代币后立即出现攻击向量,此扩展添加了一个时间锁在首次销售后转移 12 小时。 这让所有买家有足够的时间来熟悉他们的代币和整个收藏,以及物品的稀有性。
或者:从一开始就发布没有 tokenID 的整个收藏/或至少发布所有稀有度统计信息。
TODO
WithLimitedSupply.sol
限制代币供应的简单代币跟踪器。
要跟踪代币供应并获取下一个可用的代币 ID,请在创建新代币时调用 nextToken()
。
contract RareToken is ERC721, WithLimitedSupply {
constructor()
ERC721("RareToken", "RT")
WithLimitedSupply(1000) // Max. 1k NFTs available
{}
function mint () external ensureAvailability() {
uint256 newTokenId = nextToken(); // Create a new token ID
// ...
}
}
内部的 nextToken()
方法不会自动检查令牌是否可用,但 WithLimitedSupply
提供了 ensureAvailability
修饰符,您可以将其附加到您的铸造函数。 如果您在同一笔交易中实施铸造多个令牌,您应该使用 ensureAvailabilityFor(amount)
修饰符检查可用性。
有两个合约以此为基础:LinearlyAssigned
,它添加了从特定数字启动令牌跟踪器的选项;RandomlyAssigned
,它支持半随机令牌 ID 分配。
LinearlyAssigned.sol
用最大供应量和起始索引实例化它:(
contract RareToken is ERC721, LinarlyAssigned {
constructor()
ERC721("RareToken", "RT")
LinarlyAssigned(1000, 1) // Max. 1k NFTs available; Start counting from 1 (instead of 0)
{}
function mint () external ensureAvailability() {
uint256 newTokenId = nextToken(); // Create a new token ID
// ...
}
}
RandomlyAssigned.sol
半)随机*从薄荷上的固定集合大小分配代币 ID。
contract RandomToken is ERC721, RandomlyAssigned {
constructor()
ERC721("RandomToken", "RT")
RandomlyAssigned(1000, 1) // Max. 1k NFTs available; Start counting from 1 (instead of 0)
{}
function mint () external ensureAvailability() {
uint256 newTokenId = nextToken(); // Create a new random token ID
// ...
}
}
*) 我们无法在链上创建适当的随机数。 但这已经足够好了,如果你在公开销售期间隐藏你的元数据并且对 NFT 项目来说不是太有价值(pot.exploit 会花费大量的 gas,因此在经济上不可行为“正常”NFT 收藏做盈利). 如果您想要真正的随机分配,请查看 Chainlink。
WithSaleStart.sol
使合同所有者能够设置和更新公开销售日期的扩展。
这建立在 Ownable
扩展之上,因此只有合同部署者可以通过 setSaleStart(uint256 time)
更改销售开始时间。 此外,为了忠于 NFT 的无信任精神,在初始销售开始后无法更改销售开始。
要在您的项目中使用它,请调用 afterSaleStart
/ beforeSaleStart
修饰符。
contract MyToken is ERC721, WithSaleStart {
constructor()
ERC721("MyToken", "MT")
WithSaleStart(1735686000)
{}
function claim () external afterSaleStart {
// ...
}
}
LimitedTokensPerWallet.sol
限制外部钱包可以持有的代币数量。
contract LimitedToken is ERC721, LimitedTokensPerWallet {
constructor()
ERC721("LimitedToken", "LT")
LimitedTokensPerWallet(3) // Only allow three tokens per wallet
{}
// Mint & Transfer limits are taken care of by the library.
}
OnePerWallet.sol
LimitedTokensPerWallet
的更极端版本,它只允许在外部钱包地址中持有一个令牌。
要在您的项目中使用它,只需扩展合同即可。 如果您需要更多控制权,请调用 onePerWallet
修饰符。
contract OneForAllToken is ERC721, OnePerWallet {
constructor()
ERC721("OneForAllToken", "OFA")
{}
// Mints and Transfer limites are taken care of by the library.
}
上面的代码不会阻止人们通过单独的智能合约铸造多个代币——它只检查外部拥有的账户(带有私钥的钱包)。 如果您想考虑到这一点,请在您的 mint 函数上另外激活
onePerAccount
修饰符。
WithMarketOffers.sol
实施一个简单的基于报价的市场。 代币所有者可以选择通过内置市场出售它们。
只需扩展 WithMarketOffers.sol
合同即可完成这项工作。
WithFees.sol
旨在抽象出当前费用标准的复杂性。
contract SharedUpsideToken is ERC721, WithFees {
constructor()
ERC721("SharedUpsideToken", "SUP")
// 500 Basis Points (5%) of secondary sales
// should go to the given beneficiary address
WithFees(0xe11Da9560b51f8918295edC5ab9c0a90E9ADa20B, 500)
{}
function supportsInterface(bytes4 interfaceId) public view override(WithFees, ERC721) returns (bool) {
return WithFees.supportsInterface(interfaceId);
}
}
WithWithdrawals.sol
一个实现取款功能的简单助手。
只需以合约所有者的身份调用withdraw
。
Installation
- In your project run
npm install @1001-digital/erc721-extensions
- Within your project, import the extensions you want to use like
import "@1001-digital/erc721-extensions/contracts/WithIPFSMetaData.sol";
Local Development
该项目使用 Hardhat 开发环境。 要在本地进行设置,请克隆此存储库并运行 npm install
。
注意:如果你的系统全局安装了 hh
,你可以将 npm run
换成 hh
。
- Run the test suite:
npm run test
- Spin up a local development blockchain:
npm run node
Thank You
如果您有任何改进建议、反馈或错误报告,请随时添加问题,或通过 Twitter @jwahdatehagh 或电子邮件 jalil@1001.digital。
ERC721 Contract Extensions
A set of composable extensions for the OpenZeppelin ERC721 base contracts.
Available Extensions
WithContractMetaData.sol
Link to your collection's contract meta data right from within your smart contract.
Builds on Ownable
and allows the contract owner to change contract metadata even after deployment.
Make sure the URL links to an appropriate JSON file.
contract Token is ERC721, WithContractMetadata {
constructor()
ERC721("Token", "LT")
WithContractMetadata("ipfs://0123456789123456789123456789123456789123456789/metadata.json")
{}
}
To change the contract metadat URI, call setContractURI(string uri)
as the contract owner.
WithIPFSMetaData.sol
Handles linking to metadata files hosted on IPFS and follows best practices doing so:
- Projects have to embed the Content ID hash in the contract right from the start
- Project owners can never change the CID
- Tokens link to an
ipfs://
-URL to be independent of particular IPFS Gateways. - Tokens wrap a folder with a
metadata.json
file (and pot. all the assets of the token).
Note: You should never publish metadata before public sale is complete.
This is to prevent people from trying to snipe rare tokens (rarity can be derived from going through all metadata files and looking at the trait and attribute distributions). Although very expensive (in gas fees) to do so, it is potentially possible and thus bad practice.
contract CleanToken is ERC721, WithIPFSMetaData {
constructor()
ERC721("CleanToken", "CT")
WithIPFSMetaData("0123456789123456789123456789123456789123456789")
{}
}
WithIPFSMetadataAndPreviewMetadata.sol
TODO
WithTransferTimeLock.sol
To prevent an attack vector right after public sale of tokens, this extension adds a timelock to transfers for 12 hours after initial sale. This gives all buyers enough time to familiarise themselves with their tokens and the entire collection, as well as the rarities of items.
Alternatively: Publish the entire collection without tokenIDs from the get go / or at least all rarity statistics.
TODO
WithLimitedSupply.sol
A simple token tracker that limits the token supply.
To keep track of the token supply and to get the next available tokenID, call nextToken()
when creating new tokens.
contract RareToken is ERC721, WithLimitedSupply {
constructor()
ERC721("RareToken", "RT")
WithLimitedSupply(1000) // Max. 1k NFTs available
{}
function mint () external ensureAvailability() {
uint256 newTokenId = nextToken(); // Create a new token ID
// ...
}
}
The internal nextToken()
method does not automatically check whether tokens are available but WithLimitedSupply
provides the ensureAvailability
modifier that you can attach to your minting function. If you implement minting multiple tokens within the same transaction, you should check availability with the ensureAvailabilityFor(amount)
modifier.
There are two Contracts that build on this: LinearlyAssigned
, which adds the option of starting the token tracker from a specific number and RandomlyAssigned
, wich enables semi random token ID assignments.
LinearlyAssigned.sol
Instanciate it with the max supply as well as the starting index:
contract RareToken is ERC721, LinarlyAssigned {
constructor()
ERC721("RareToken", "RT")
LinarlyAssigned(1000, 1) // Max. 1k NFTs available; Start counting from 1 (instead of 0)
{}
function mint () external ensureAvailability() {
uint256 newTokenId = nextToken(); // Create a new token ID
// ...
}
}
RandomlyAssigned.sol
(Semi-)randomly* assign token IDs from a fixed collection size on mint.
contract RandomToken is ERC721, RandomlyAssigned {
constructor()
ERC721("RandomToken", "RT")
RandomlyAssigned(1000, 1) // Max. 1k NFTs available; Start counting from 1 (instead of 0)
{}
function mint () external ensureAvailability() {
uint256 newTokenId = nextToken(); // Create a new random token ID
// ...
}
}
*) We can't create proper random numbers on chain. But this does the job well enough, if you hide your metadata during public sale and are not too valuable of an NFT project (pot. exploit costs a lot of gas, thus making it economically unfeasible to do for profit for 'normal' NFT collections). If you want true random assignment, check out Chainlink.
WithSaleStart.sol
An extension that enables the contract owner to set and update the date of a public sale.
This builds upon the Ownable
extension, so only contract deployers can change the sale start via setSaleStart(uint256 time)
. Also, to stay true to the trustless spirit of NFTs, it is not possible to change the sale start after the initial sale started.
To use this in your project, call the afterSaleStart
/ beforeSaleStart
modifiers.
contract MyToken is ERC721, WithSaleStart {
constructor()
ERC721("MyToken", "MT")
WithSaleStart(1735686000)
{}
function claim () external afterSaleStart {
// ...
}
}
LimitedTokensPerWallet.sol
Limits the amount of tokens an external wallet can hold.
contract LimitedToken is ERC721, LimitedTokensPerWallet {
constructor()
ERC721("LimitedToken", "LT")
LimitedTokensPerWallet(3) // Only allow three tokens per wallet
{}
// Mint & Transfer limits are taken care of by the library.
}
OnePerWallet.sol
A more extreme version of LimitedTokensPerWallet
, which only allows holding one token in an external wallet address.
To use this in your project just extend the Contract. If you need more control, call the onePerWallet
modifier.
contract OneForAllToken is ERC721, OnePerWallet {
constructor()
ERC721("OneForAllToken", "OFA")
{}
// Mints and Transfer limites are taken care of by the library.
}
The above code does not prevent people from minting multiple tokens via a separate smart contract - it only checks against externally owned accounts (wallets with private keys). If you want to account for that, additionally activate the
onePerAccount
modifier on your mint function.
WithMarketOffers.sol
Implements a simple offer based marketplace. Owners of tokens can choose to sell them via the in-built market.
Just extend the WithMarketOffers.sol
contract to make this work.
WithFees.sol
Aims to abstracts out the complexity of current fee standards.
contract SharedUpsideToken is ERC721, WithFees {
constructor()
ERC721("SharedUpsideToken", "SUP")
// 500 Basis Points (5%) of secondary sales
// should go to the given beneficiary address
WithFees(0xe11Da9560b51f8918295edC5ab9c0a90E9ADa20B, 500)
{}
function supportsInterface(bytes4 interfaceId) public view override(WithFees, ERC721) returns (bool) {
return WithFees.supportsInterface(interfaceId);
}
}
WithWithdrawals.sol
A simple helper that implements a withdrawal function.
Just call withdraw
as the contract owner.
Installation
- In your project run
npm install @1001-digital/erc721-extensions
- Within your project, import the extensions you want to use like
import "@1001-digital/erc721-extensions/contracts/WithIPFSMetaData.sol";
Local Development
This project uses the Hardhat development environment. To set it up locally, clone this repository and run npm install
.
Note: You can exchange npm run
for hh
if you have hh
installed globally on your system.
- Run the test suite:
npm run test
- Spin up a local development blockchain:
npm run node
Thank You
If you have any improvement suggestions, feedback or bug reports please feel free add an issue, or reach out via Twitter @jwahdatehagh or Email jalil@1001.digital.