今天將會透過 Mint ERC721 NFTs on Avalanche 來研究如何在 Avalanche 上實作 NFT
研究主題如下
Avalanche 是一個由多個區塊鏈所組成的網路,其中一個組成元素是 EVM 的分支並且相容於 Ethereum
Avalanche 包含3個子網路:
架構如下圖:
值得注意的是,大部份 Dapp 都是透過 C-chain 來做互動。
為了在本機電腦可以執行與區塊鏈互動的部份,所以需要在本機執行節點程式
打開 avalancego 專案
其結構如下
avalancego/
├── ...
└──scripts
├── ansible
├── aws
├── build.sh
├── ...
└── versions.sh
這邊需要透過 build.sh 來建制可執行的 binary file
而 avm-sim 也需要做同樣的事情
備註: 需要把 execution 的權限給 build.sh
chmod +x scripts/build.sh
執行 ava-sim
透過以下指令做執行
./scripts/run.sh
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"keystore.createUser",
"params" :{
"username":"MYUSERNAME",
"password":"MYPASSWORD"
}
}' -H 'Content-Type: application/json' 127.0.0.1:9650/ext/keystore
這裡的 MYUSERNAME, MYPASSWORD 是建立在 AVAX 的帳戶
這邊透過一個已經具有資金的 private key 來鏈結到剛剛建立 AVAX 帳戶
PrivateKey-ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN
使用以下指令
curl --location --request POST '127.0.0.1:9650/ext/bc/C/avax' \
--header 'Content-Type: application/json' \
--data-raw '{
"method": "avax.importKey",
"params": {
"username":"MYUSERNAME",
"password":"MYPASSWORD",
"privateKey":"PrivateKey-ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN"
},
"jsonrpc": "2.0",
"id": 1
}'
新增 Avalanche Local 測試鏈資訊如下
Network Name: Avalanche Local
New RPC URL: http://localhost:9650/ext/bc/C/rpc (for C-chain)
ChainID: 43112
Symbol: AVAX
Explorer: N/A
0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027
mkdir hello-avax && cd hello-avax
yarn init -y
安裝 hardhat
yarn add hardhat -D
使用 hardhat 初始化專案
npx hardhat
更新 hardhat.config.js 如下
const { task } = require("hardhat/config")
const { BigNumber } = require("ethers")
require("@nomiclabs/hardhat-waffle")
const FORK_FUJI = false
const FORK_MAINNET = false
const forkingData = FORK_FUJI ? {
url: 'https://api.avax-test.network/ext/bc/C/rpc',
} : FORK_MAINNET ? {
url: 'https://api.avax.network/ext/bc/C/rpc'
} : undefined
task("accounts", "Prints the list of accounts", async (args, hre) => {
const accounts = await hre.ethers.getSigners()
accounts.forEach((account) => {
console.log(account.address)
})
})
task("balances", "Prints the list of AVAX account balances", async (args, hre) => {
const accounts = await hre.ethers.getSigners()
for (const account of accounts){
const balance = await hre.ethers.provider.getBalance(
account.address
);
console.log(`${account.address} has balance ${balance.toString()}`);
}
})
module.exports = {
solidity: {
compilers: [
{
version: "0.5.16"
},
{
version: "0.6.2"
},
{
version: "0.6.4"
},
{
version: "0.7.0"
},
{
version: "0.8.0"
},
{
version: "0.8.1"
}
]
},
networks: {
hardhat: {
gasPrice: 225000000000,
chainId: !forkingData ? 43112 : undefined,
},
local: {
url: 'http://localhost:9650/ext/bc/C/rpc',
gasPrice: 225000000000,
chainId: 43112,
accounts: [
"0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
"0x7b4198529994b0dc604278c99d153cfd069d594753d471171a1d102a10438e07",
"0x15614556be13730e9e8d6eacc1603143e7b96987429df8726384c2ec4502ef6e",
"0x31b571bf6894a248831ff937bb49f7754509fe93bbd2517c9c73c4144c0e97dc",
"0x6934bef917e01692b789da754a0eae31a8536eb465e7bff752ea291dad88c675",
"0xe700bdbdbc279b808b1ec45f8c2370e4616d3a02c336e68d85d4668e08f53cff",
"0xbbc2865b76ba28016bc2255c7504d000e046ae01934b04c694592a6276988630",
"0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60",
"0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c",
"0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a"
]
}
}
}
執行 察看 local account
npx hardhat accounts --network local
執行 察看 local account balance
npx hardhat balances --network local
為了使用 ERC721 標準 Contract 需要使用以下指令安裝套件
yarn add @openzeppelin/contracts
實作 Smart Contract File.sol 如下
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract Filet is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() ERC721("Filet", "FILET") {}
function mintTo(address player, string memory tokenURI)
public
returns (uint256)
{
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(player, newItemId);
_setTokenURI(newItemId, tokenURI);
return newItemId;
}
}
這個 Smart Contract 是用來設定 NFT 對應的 storage
裡面的 mintTo 是用來鑄造 NFT 給對應的 player
使用以下指令做 compile
npx hardhat compile
修改 scripts/deploy.js 如下
const {
Contract,
ContractFactory
} = require("ethers")
const { ethers } = require("hardhat")
const deploy = async (contractName) => {
const Contract = await ethers.getContractFactory(contractName)
const contract = await Contract.deploy()
await contract.deployed()
console.log(`${contractName} deployed to: ${contract.address}`)
}
const main = async () => {
await deploy("Filet")
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error)
process.exit(1)
})
執行以下指令 deploy smart contract 到 local network
npx hardhat run scripts/deploy.js --network local
npx hardhat console --network local
透過以下指令初始化 Smart Contract 物件
>>> const Filet = await ethers.getContractFactory("Filet")
>>> const filet = await Filet.attach("0x52C84043CD9c865236f11d9Fc9F56aa003c1f922")
透過以下指令察看 accouts
>>> const accounts = await ethers.provider.listAccounts()
>>> accounts
安裝 nft.storage 與 mime 套件
yarn add nft.storage mime
實作 scripts/upload.mjs
如下
import { NFTStorage, File } from 'nft.storage'
import mime from 'mime'
import fs from 'fs'
import path from 'path'
import dotenv from 'dotenv'
dotenv.config()
const NFT_STORAGE_KEY = process.env.NFT_STORAGE_API_KEY
async function storeNFT(imagePath, name, description) {
const image = await fileFromPath(imagePath)
const nftstorage = new NFTStorage({ token: NFT_STORAGE_KEY })
return nftstorage.store({
image,
name,
description,
})
}
async function fileFromPath(filePath) {
const content = await fs.promises.readFile(filePath)
const type = mime.getType(filePath)
return new File([content], path.basename(filePath), { type })
}
async function upload(imagePath, name, description) {
const result = await storeNFT(imagePath, name, description)
return result
}
export { upload }
執行 mintTo
>> const _tx = await filet.mintTo(accounts[1], result1.url)
執行完後 發現 accounts[1] 的 balance 更新為 1
確認 owner 為 accounts[1]