NFTのSCAM防止機能、ContractAllowListの導入手順
前回、下記の記事でContractAllowListの機能について紹介しました。次に、実際の導入手順を説明したいと思います。
導入する機能や方法を決める
「NFTを転送するコントラクトを制限する機能」と「NFTをロックする機能」の片方、もしくは両方を導入するかどうかを決めます。
「NFTをロックする機能」を導入する場合、ロックする仕組みは何にするのか、ロックはどの単位でのロックを有効にするのか、ロックと解除は誰が出来るのかを決めます。
今回の例では一通りの導入手順を説明するために、「NFTを転送するコントラクトを制限する機能」と「NFTをロックする機能」の両方を導入する想定で進めます。ロックできるのは、コレクション=コレクションオーナー、ウォレット=ウォレット、トークン=ホルダーウォレットとします。
NFTコントラクトに導入する
それでは実際にコードを触ります。今回は、NFTBoilをベースに話を進めます。元々の、はやっちさんのリポジトリは最新版だと私の環境でうまく動かないので、私がforkしたものをベースにします。ODENPETSやAstarPrinceはこれをベースにしています。
gitやnpmなどは導入されている前提で話を進めます。IDEはVisualStudioCodeを使用してます。必要に応じて導入してください。
NFTBoilの取得
GitHubからcloneします
git clone git@github.com:syunduel/NFTBoil.git
持ってこれました。
パッケージを取得する
ここからしばらくは、NFTBoilのREADMEのとおり実行していきます。
まずはNFTBoilディレクトリに移動し、npmコマンドで必要なパッケージを持ってきます
$ cd NFTBoil
$ npm i
npm WARN deprecated ganache-core@2.13.2: ganache-core is now ganache; visit https://trfl.io/g7 for details
npm WARN deprecated ganache-core@2.13.2: ganache-core is now ganache; visit https://trfl.io/g7 for details
〜中略〜
ngine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
> nftboil@1.0.0 prepare
> husky install
husky - Git hooks installed
added 3946 packages, and audited 3953 packages in 31s
298 packages are looking for funding
run `npm fund` for details
93 vulnerabilities (8 low, 16 moderate, 35 high, 34 critical)
To address issues that do not require attention, run:
npm audit fix
To address all issues possible (including breaking changes), run:
npm audit fix --force
Some issues need review, and may require choosing
a different dependency.
Run `npm audit` for details.
Warningが出つつも、取ってこれました。
env ファイルの設定
設定ファイルを作ります。とりあえずサンプルをコピーします。
$ cp ./contract/.env.example ./contract/.env
ACCOUNT_PRIVATE_KEYに値を設定します。設定するのは、デプロイする際のアカウントの秘密鍵です。
この.envファイルを他人に見せないように気をつけてください。このディレクトリの.envファイルはGitHubにアップされないように設定されていますが、別の名前でコピーしたりするとアップされてしまうので気をつけてください。
コントラクトのテスト
まずlocalのノードを起動します
$ npx hardhat typechain
Generating typings for: 26 artifacts in dir: typechain-types for target: ethers-v5
Successfully generated 82 typings!
Compiled 25 Solidity files successfully
$ npx hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
Accounts
========
WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.
Account #0 : 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
〜中略〜
WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.
ターミナルをもう一つ起動してcontractディレクトリまで移動し、テストを実行します。全てパスするはずです。
$ cd Documents/repos/CALSample/NFTBoil/contract
$ npx hardhat test
No need to generate any newer typings.
NFTBoilMerkle contract
Basic checks
✓ check the owner
✓ check default is PreSale
✓ Confirm pre price
✓ Confirm public price (24054 gas)
Public Minting checks
✓ PublicMint fail if presale is active (45966 gas)
✓ Non-owner cannot mint without enough balance
〜中略〜
···················|·····························|·············|·············|·············|··············|··············
| NFTBoilMerkleA · unpause · - · - · 27753 · 2 · - │
···················|·····························|·············|·············|·············|··············|··············
| Deployments · · % of limit · │
·················································|·············|·············|·············|··············|··············
| NFTBoilMerkle · - · - · 2739631 · 40.8 % · - │
·················································|·············|·············|·············|··············|··············
| NFTBoilMerkleA · - · - · 2748210 · 40.9 % · - │
·------------------------------------------------|-------------|-------------|-------------|--------------|-------------·
78 passing (21s)
CALパッケージのインストール
次はCALのREADMEに従ってCALをインストールします。
NFTBoilを使用している場合は、contractディレクトリで実行してください。
$ npm i contract-allow-list
added 1 package, and audited 3954 packages in 5s
298 packages are looking for funding
run `npm fund` for details
1 high severity vulnerability
To address all issues, run:
npm audit fix
Run `npm audit` for details.
CALはERC721Psiを使用しているため、ERC721Psiをインストールします。
$ npm i erc721psi
added 48 packages, and audited 4002 packages in 11s
321 packages are looking for funding
run `npm fund` for details
1 high severity vulnerability
To address all issues, run:
npm audit fix
Run `npm audit` for details.
自分のコントラクトの作成
contract/contracts/NFTBoilMerkleA.sol をコピーして、自分のコントラクトを作成します。
ファイル名とコントラクト名を自分のプロジェクトのNFTの名前に変更します。ここでは「MyAwesomeNFT」としました。サンプルっぽい良い名前です。
あわせて、envファイルの方も変えておきます。
次に、テストをコピーします。NFTBoilMerkleA.test.tsをコピーして、ファイル名を変更します。ファイルの中に書いてあるコントラクト名も、一括で置換します。
typechainがエラーになるので、typechainコマンドを再実行します
$ npx hardhat typechain
Generating typings for: 27 artifacts in dir: typechain-types for target: ethers-v5
Successfully generated 84 typings!
Compiled 26 Solidity files successfully
エラーが消えました
テストを再実施します
$ npx hardhat test
No need to generate any newer typings.
NFTBoilMerkleA contract
Basic checks
✓ check the owner
✓ check default is PreSale
✓ check default is Mintable
✓ check default is NOT publicSaleWithoutProof
✓ Confirm pre price
〜中略〜
MyAwesomeNFT contract
Basic checks
✓ check the owner
✓ check default is PreSale
✓ check default is Mintable
✓ check default is NOT publicSaleWithoutProof
〜中略〜
| NFTBoilMerkleA · unpause · - · - · 27753 · 2 · - │
···················|·····························|·············|·············|·············|··············|··············
| Deployments · · % of limit · │
·················································|·············|·············|·············|··············|··············
| MyAwesomeNFT · - · - · 2748186 · 40.9 % · - │
·················································|·············|·············|·············|··············|··············
| NFTBoilMerkle · - · - · 2739631 · 40.8 % · - │
·················································|·············|·············|·············|··············|··············
| NFTBoilMerkleA · - · - · 2748210 · 40.9 % · - │
·------------------------------------------------|-------------|-------------|-------------|--------------|-------------·
124 passing (30s)
先ほど作成したコントラクトのテストが、追加で実行されていると思います。これがCAL導入前のベースになります。
CALのコードの説明
CALは役割によってクラスが分けられています。それぞれの役割は下記のようになります。
contracts/ERC721AntiScam/
ERC721を継承したコントラクトで、CALの各機能を取り込んだコントラクトです。各NFTプロジェクトのコントラクトが継承するコントラクトになります。
contracts/core/
CALの本体が入っています。前回の機能説明の図の中の「CAL」です。CAL運営チームによって既にデプロイされています。
各プロジェクトのエンジニアがこれを触る場面はありません。
contracts/governor/
CALのアドレスの追加/削除を、投票によって決定するためのコードです。現在まだ本番では利用していません。
contracts/mock/
CALを導入する際のサンプルコードが入っています。CALを導入する際には、ここのコードを参考にします。
contracts/proxy/
contracts/core/と同様、CALProxyの本体です。各プロジェクトのエンジニアがこれを触る場面はありません。
contracts/votes/
governorと同じく投票用…だと思います。現在まだ本番では利用していません。
さらに「contracts/ERC721AntiScam/」は、利用方法によってどれを継承するかが変わります。
文字数省略のために下記として説明します。
「NFTを転送するコントラクトを制限する機能」→「sAFA抑止機能」
「NFTをロックする機能」→「ロック機能」
「sAFA抑止機能」と「ロック機能」の両方を使う場合で、ロックするのが運営が指定したウォレットの場合
contracts/ERC721AntiScam/extensions/ERC721AntiScamControl.sol
「sAFA抑止機能」と「ロック機能」の両方を使う場合で、ロックするアドレスをコントラクトオーナーやトークンオーナーなどに独自に設定したい場合
contracts/ERC721AntiScam/ERC721AntiScam.sol
※_setContractLock、_setWalletLock、_setTokenLockを呼び出す関数を独自に実装する
「sAFA抑止機能」のみ使用する場合
contracts/ERC721AntiScam/restrictApprove/ERC721RestrictApprove.sol
「ロック機能」のみ使用する場合(あまり無い気がしますが)
contracts/ERC721AntiScam/lockable/ERC721Lockable.sol
CAL関連のコードを追加
上記を参考に、自分のプロジェクトにあったコントラクトを継承し、必要な変数やメソッドを追加します。今回は「sAFA抑止機能」と「ロック機能」の両方を使う想定でした。ERC721AntiScamかERC721AntiScamControlのどちらかですが、今回はERC721AntiScamを採用して進めます。
「contracts/mock/TestNFTcollection.sol」を開きます
TestNFTcollectionをそのままベースにしても良いのですが、今回はMerkleTreeのアローリスト機能があるNFTBoilのコードにコピペで導入していきます。
下記をざざっと行います。最後にコードまるごと貼ってありますので、そちらもどうぞ。
継承するERC721AntiScamとAccessControlをインポート
import "@openzeppelin/contracts/access/AccessControl.sol";
import "contract-allow-list/contracts/ERC721AntiScam/ERC721AntiScam.sol";
継承するコントラクトをERC721AからERC721AntiScamに変更
AccessControlの継承を追加
contract MyAwesomeNFT is ERC721AntiScam, AccessControl, ERC2981, Pausable, CantBeEvil(LicenseVersion.CBE_NECR_HS) {
ADMIN変数を追加
bytes32 public ADMIN = "ADMIN";
ERC721Aを使用していた部分をERC721Psiに変更
constructor(
string memory _name,
string memory _symbol
) ERC721Psi(_name, _symbol) {
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
return string(abi.encodePacked(ERC721Psi.tokenURI(tokenId), BASE_EXTENSION));
コンストラクターで、コントラクトの作成者を最初のADMINに追加
_setupRole(ADMIN, msg.sender);
_startTokenIdの削除(ERC721Psiは_startTokenIdがオーバーライド出来ないようになっているため。NFTの実際のtoken_idを1から始めるには、1版最初にmintしたtokenをburnする必要があるようです。
// function _startTokenId() internal view virtual override returns (uint256) {
// return 1;
// }
ERC721Lockable、ERC721RestrictApprove、ERC721AntiScamをオーバーライドしている関数をコピペ
/*///////////////////////////////////////////////////////////////
OVERRIDES ERC721Lockable
//////////////////////////////////////////////////////////////*/
function setTokenLock(uint256[] calldata tokenIds, LockStatus lockStatus)
external
override
〜中略〜
return
AccessControl.supportsInterface(interfaceId) ||
ERC721AntiScam.supportsInterface(interfaceId);
}
最後のsupportsInterfaceがメソッドが元から存在しているため、重複してしまいます。メソッドの中身をマージして、下記のようにします。
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721AntiScam, AccessControl, CantBeEvil, ERC2981)
returns (bool)
{
return
AccessControl.supportsInterface(interfaceId) ||
ERC721AntiScam.supportsInterface(interfaceId) ||
ERC2981.supportsInterface(interfaceId) ||
CantBeEvil.supportsInterface(interfaceId);
}
「sAFA抑止機能」と「ロック機能」のオン/オフ
function setEnableRestrict(bool value) external onlyOwner {
enableRestrict = value;
}
function setEnableLock(bool value) external onlyOwner {
enableLock = value;
}
完成したコード
バグってても責任は持てませんので、よろしくおねがいします。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
/// @title: MyAwesomeNFT
/// @author: Shunichiro
/// @dev: This contract using NFTBoil (https://github.com/syunduel/NFTBoil)
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "erc721a/contracts/ERC721A.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "contract-allow-list/contracts/ERC721AntiScam/ERC721AntiScam.sol";
// This NFT License is a16z Can't be Evil Lisence
import {LicenseVersion, CantBeEvil} from "@a16z/contracts/licenses/CantBeEvil.sol";
contract MyAwesomeNFT is ERC721AntiScam, AccessControl, ERC2981, Pausable, CantBeEvil(LicenseVersion.CBE_NECR_HS) {
using Strings for uint256;
bytes32 public ADMIN = "ADMIN";
string private baseURI = "";
uint256 public preCost = 0.001 ether;
uint256 public publicCost = 0.001 ether;
bool public presale = true;
bool public mintable = false;
bool public publicSaleWithoutProof = false;
uint256 public maxPerWallet = 300;
uint256 public publicMaxPerTx = 5;
address public royaltyAddress;
uint96 public royaltyFee = 500;
uint256 constant public MAX_SUPPLY = 10000;
string constant private BASE_EXTENSION = ".json";
address constant private DEFAULT_ROYALITY_ADDRESS = 0xA9028b1EA3A8485130eB86Dc1F26654c823D9849;
bytes32 public merkleRootPreMint;
bytes32 public merkleRootPublicMint;
mapping(address => uint256) private claimed;
constructor(
string memory _name,
string memory _symbol
) ERC721Psi(_name, _symbol) {
_setDefaultRoyalty(DEFAULT_ROYALITY_ADDRESS, royaltyFee);
_setupRole(ADMIN, msg.sender);
}
modifier whenMintable() {
require(mintable == true, "Mintable: paused");
_;
}
/**
* @dev The modifier allowing the function access only for real humans.
*/
modifier callerIsUser() {
require(tx.origin == msg.sender, "The caller is another contract");
_;
}
// internal
function _baseURI() internal view override returns (string memory) {
return baseURI;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
return string(abi.encodePacked(ERC721Psi.tokenURI(tokenId), BASE_EXTENSION));
}
/**
* @notice Set the merkle root for the allow list mint
*/
function setMerkleRootPreMint(bytes32 _merkleRoot) external onlyOwner {
merkleRootPreMint = _merkleRoot;
}
/**
* @notice Set the merkle root for the public mint
*/
function setMerkleRootPublicMint(bytes32 _merkleRoot) external onlyOwner {
merkleRootPublicMint = _merkleRoot;
}
function publicMint(uint256 _mintAmount, uint256 _publicMintMax, bytes32[] calldata _merkleProof) public
payable
whenNotPaused
whenMintable
callerIsUser
{
uint256 cost = publicCost * _mintAmount;
mintCheck(_mintAmount, cost);
require(!presale, "Presale is active.");
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, _publicMintMax));
require(
MerkleProof.verify(_merkleProof, merkleRootPublicMint, leaf),
"Invalid Merkle Proof"
);
require(
claimed[msg.sender] + _mintAmount <= _publicMintMax,
"Already claimed max"
);
require(
_mintAmount <= publicMaxPerTx,
"Mint amount over"
);
_mint(msg.sender, _mintAmount);
claimed[msg.sender] += _mintAmount;
}
function preMint(uint256 _mintAmount, uint256 _preMintMax, bytes32[] calldata _merkleProof)
public
payable
whenMintable
whenNotPaused
{
uint256 cost = preCost * _mintAmount;
mintCheck(_mintAmount, cost);
require(presale, "Presale is not active.");
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, _preMintMax));
require(
MerkleProof.verify(_merkleProof, merkleRootPreMint, leaf),
"Invalid Merkle Proof"
);
require(
claimed[msg.sender] + _mintAmount <= _preMintMax,
"Already claimed max"
);
_mint(msg.sender, _mintAmount);
claimed[msg.sender] += _mintAmount;
}
function publicMintWithoutProof(uint256 _mintAmount) public
payable
whenNotPaused
whenMintable
callerIsUser
{
uint256 cost = publicCost * _mintAmount;
mintCheck(_mintAmount, cost);
require(!presale, "Presale is active.");
require(publicSaleWithoutProof, "publicSaleWithoutProof is not open.");
require(
_mintAmount <= publicMaxPerTx,
"Mint amount over"
);
require(
claimed[msg.sender] + _mintAmount <= maxPerWallet,
"Already claimed max"
);
_mint(msg.sender, _mintAmount);
claimed[msg.sender] += _mintAmount;
}
function mintCheck(
uint256 _mintAmount,
uint256 cost
) private view {
require(_mintAmount > 0, "Mint amount cannot be zero");
require(
totalSupply() + _mintAmount <= MAX_SUPPLY,
"MAXSUPPLY over"
);
require(msg.value >= cost, "Not enough funds");
}
function ownerMint(address _address, uint256 count) public onlyOwner {
_mint(_address, count);
}
function setPresale(bool _state) public onlyOwner {
presale = _state;
}
function setPublicSaleWithoutProof(bool _state) public onlyOwner {
publicSaleWithoutProof = _state;
}
function setPreCost(uint256 _preCost) public onlyOwner {
preCost = _preCost;
}
function setPublicCost(uint256 _publicCost) public onlyOwner {
publicCost = _publicCost;
}
function setMintable(bool _state) public onlyOwner {
mintable = _state;
}
function setMaxPerWallet(uint256 _maxPerWallet) external onlyOwner {
maxPerWallet = _maxPerWallet;
}
function setPublicMaxPerTx(uint256 _publicMaxPerTx) external onlyOwner {
publicMaxPerTx = _publicMaxPerTx;
}
function getCurrentCost() public view returns (uint256) {
if (presale) {
return preCost;
} else{
return publicCost;
}
}
function setBaseURI(string memory _newBaseURI) public onlyOwner {
baseURI = _newBaseURI;
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function withdraw() external onlyOwner {
Address.sendValue(payable(owner()), address(this).balance);
}
/**
* @notice Change the royalty fee for the collection
*/
function setRoyaltyFee(uint96 _feeNumerator) external onlyOwner {
royaltyFee = _feeNumerator;
_setDefaultRoyalty(royaltyAddress, royaltyFee);
}
/**
* @notice Change the royalty address where royalty payouts are sent
*/
function setRoyaltyAddress(address _royaltyAddress) external onlyOwner {
royaltyAddress = _royaltyAddress;
_setDefaultRoyalty(royaltyAddress, royaltyFee);
}
function setEnableRestrict(bool value) external onlyOwner {
enableRestrict = value;
}
function setEnableLock(bool value) external onlyOwner {
enableLock = value;
}
/*///////////////////////////////////////////////////////////////
OVERRIDES ERC721Lockable
//////////////////////////////////////////////////////////////*/
function setTokenLock(uint256[] calldata tokenIds, LockStatus lockStatus)
external
override
{
for (uint256 i = 0; i < tokenIds.length; i++) {
require(msg.sender == ownerOf(tokenIds[i]), "not owner.");
}
_setTokenLock(tokenIds, lockStatus);
}
function setWalletLock(address to, LockStatus lockStatus)
external
override
{
require(to == msg.sender, "not yourself.");
_setWalletLock(to, lockStatus);
}
function setContractLock(LockStatus lockStatus)
external
override
onlyOwner
{
_setContractLock(lockStatus);
}
/*///////////////////////////////////////////////////////////////
OVERRIDES ERC721RestrictApprove
//////////////////////////////////////////////////////////////*/
function addLocalContractAllowList(address transferer)
external
override
onlyRole(ADMIN)
{
_addLocalContractAllowList(transferer);
}
function removeLocalContractAllowList(address transferer)
external
override
onlyRole(ADMIN)
{
_removeLocalContractAllowList(transferer);
}
function getLocalContractAllowList()
external
override
view
returns(address[] memory)
{
return _getLocalContractAllowList();
}
function setCALLevel(uint256 level) external override onlyRole(ADMIN) {
CALLevel = level;
}
function setCAL(address calAddress) external onlyRole(ADMIN) {
_setCAL(calAddress);
}
/*///////////////////////////////////////////////////////////////
OVERRIDES ERC721AntiScam
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721AntiScam, AccessControl, ERC2981, CantBeEvil)
returns (bool)
{
return
AccessControl.supportsInterface(interfaceId) ||
ERC721AntiScam.supportsInterface(interfaceId) ||
ERC2981.supportsInterface(interfaceId) ||
CantBeEvil.supportsInterface(interfaceId);
}
}
テストの実行
テストを実行します。token_idが1から始まるようになったため、テストが2つほど失敗しますが、それ以外は成功すると思います。
$ npx hardhat test
Generating typings for: 42 artifacts in dir: typechain-types for target: ethers-v5
Successfully generated 126 typings!
Compiled 41 Solidity files successfully
NFTBoilMerkleA contract
Basic checks
✓ check the owner
✓ check default is PreSale
✓ check default is Mintable
〜中略〜
···················|·····························|·············|·············|·············|··············|··············
| Deployments · · % of limit · │
·················································|·············|·············|·············|··············|··············
| MyAwesomeNFT · - · - · 4851403 · 72.2 % · - │
·················································|·············|·············|·············|··············|··············
| NFTBoilMerkle · - · - · 2739631 · 40.8 % · - │
·················································|·············|·············|·············|··············|··············
| NFTBoilMerkleA · - · - · 2748210 · 40.9 % · - │
·------------------------------------------------|-------------|-------------|-------------|--------------|-------------·
122 passing (33s)
2 failing
1) MyAwesomeNFT contract
Public Minting checks
Owner and Bob mint:
AssertionError: Expected "0" to be equal 1
+ expected - actual
{
- "_hex": "0x01"
+ "_hex": "0x00"
"_isBigNumber": true
}
at assertArgsArraysEqual (/Users/shunichiro/Documents/repos/CALSample/NFTBoil/node_modules/@ethereum-waffle/chai/dist/cjs/matchers/emit.js:58:54)
at tryAssertArgsArraysEqual (/Users/shunichiro/Documents/repos/CALSample/NFTBoil/node_modules/@ethereum-waffle/chai/dist/cjs/matchers/emit.js:65:20)
at /Users/shunichiro/Documents/repos/CALSample/NFTBoil/node_modules/@ethereum-waffle/chai/dist/cjs/matchers/emit.js:77:13
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Context.<anonymous> (test/NFTBoilMerkleA.test.ts:168:7)
2) MyAwesomeNFT contract
Public Minting checks
Bob mints 1 plus 4:
AssertionError: Expected "0" to be equal 1
+ expected - actual
{
- "_hex": "0x01"
+ "_hex": "0x00"
"_isBigNumber": true
}
at assertArgsArraysEqual (/Users/shunichiro/Documents/repos/CALSample/NFTBoil/node_modules/@ethereum-waffle/chai/dist/cjs/matchers/emit.js:58:54)
at tryAssertArgsArraysEqual (/Users/shunichiro/Documents/repos/CALSample/NFTBoil/node_modules/@ethereum-waffle/chai/dist/cjs/matchers/emit.js:65:20)
at /Users/shunichiro/Documents/repos/CALSample/NFTBoil/node_modules/@ethereum-waffle/chai/dist/cjs/matchers/emit.js:77:13
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Context.<anonymous> (test/NFTBoilMerkleA.test.ts:250:7)
自分のNFTコントラクトをデプロイする
ここからは、Goerliテストネットで行います。
.envファイルの設定
Goerliテストネットにデプロイしてコードをverifyするために、 .envファイルに設定を追加します。
RPCはinfuraやalchemyで取得してください。ETH_APIはetherscanで取得してください。
GOERLI_RPC="https://goerli.infura.io/v3/xxxxxxxxxx"
ETH_API="xxxxxxxx"
デプロイ
hardhatコマンドでデプロイします
$ npx hardhat run scripts/deploy.ts --network goerli
No need to generate any newer typings.
Deploying ERC721 token...
Contract deployed to: 0x9224Cf3A746471D49b71292F124c839Bae52D2fa
デプロイ出来ました。今回の場合、作成されたコントラクトはこちらです。
https://goerli.etherscan.io/address/0x9224Cf3A746471D49b71292F124c839Bae52D2fa
コードのverify
同じくhardhatコマンドでverifyします
$ npx hardhat verify --network goerli 0x9224Cf3A746471D49b71292F124c839Bae52D2fa MyAwesomeNFT AWESOME
Nothing to compile
No need to generate any newer typings.
Successfully submitted source code for contract
contracts/MyAwesomeNFT.sol:MyAwesomeNFT at 0x9224Cf3A746471D49b71292F124c839Bae52D2fa
for verification on the block explorer. Waiting for verification result...
Successfully verified contract MyAwesomeNFT on Etherscan.
https://goerli.etherscan.io/address/0x9224Cf3A746471D49b71292F124c839Bae52D2fa#code
verify出来ました。
CALProxyのアドレスを設定する
CALのホワイトリストを使うためには、CALProxyのアドレスをセットする必要があります。
etherscanのwrite画面で「setCAL」を開き、CALProxyのアドレスをセットします。各ネットワークでのCALProxyとCALのアドレスは下記のとおりです。
※各NFTから設定するアドレスは、CALではなくCALProxyです。
Goerli
ContractAllowList
ContractAllowListProxy
Ethereum Mainnet
ContractAllowList
ContractAllowListProxy
Shibuya
ContractAllowList
ContractAllowListProxy
Astar
ContractAllowList
ContractAllowListProxy
ケースに応じて必要になること
「sAFA抑止機能」をオフにする
setEnableRestrictにfalseを設定するとオフになります。
「ロック機能」をオフにする
setEnableLockにfalseを設定するとオフになります。
独自の許可アドレスを設定する
独自に許可したいマーケットやツールがある場合、そのアドレスをセットします。
addLocalContractAllowListで追加してください。不要になった場合は、removeLocalContractAllowListで削除してください。
NFTコレクションをロックする
コレクション全体のロックを設定したい場合、setContractLockでロックしてください。設定する値は下記の通り。
ロックを外したい場合、1 = UnLock
ロックしたい場合、2 = Lock
ウォレット単位で設定する場合はsetWalletLock、トークン単位で設定する場合はsetTokenLockを使用してください。
この記事が気に入ったらサポートをしてみませんか?