使用Hardhat在zkEVM Testnet上部署一个Onchain的SVG动画NFT
什么是zkEVM
简而言之,Polygon zkEVM是一个二级扩容解决方案,旨在实现EVM-equivalence(等效),并允许你使用现有的Solidity代码更便宜、更快、更安全地部署合约到它。
想进一步深入了解它吗?请看我的另一篇文章:如何将ERC20代币合约部署到zkEVM Testnet。
准备网络与Token
建议使用Polygon zkEVM Bridge 站点,将网络添加到我们的钱包,并将 Goerli Token 桥接到 zkEVM Testnet:
环境要求
在我们开始之前,请确保你的电脑上已经安装了以下依赖:NVM或Node v18.15.0,并且已经配置了钱包,并且goerli 测试网代币被桥接到了zkEVM Testnet。如果你想了解如何做到这一点,请查看我的另一篇文章如何将ERC20合约部署到zkEVM Testnet。
用Hardhat部署合约
让我们建立一个ERC721 NFT,并通过Hardhat将其部署到zkEVM。
为了确保我们从新开始,我们将利用Hardhat模板生成一个基本的TypeScript项目。
# Create our project folder
mkdir zkevm-erc721-hardhat;
cd zkevm-erc721-hardhat;
npx hardhat;
# Expected Output:
# Ok to proceed? (y) y
# 888 888 888 888 888
# 888 888 888 888 888
# 888 888 888 888 888
# 8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
# 888 888 "88b 888P" d88" 888 888 "88b "88b 888
# 888 888 .d888888 888 888 888 888 888 .d888888 888
# 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
# 888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
#
# Welcome to Hardhat v2.13.0
#
# ? What do you want to do? …
# Create a JavaScript project
# ❯ Create a TypeScript project
# Create an empty hardhat.config.js
# Quit
#
# ✔ What do you want to do? · Create a TypeScript project
# ✔ Hardhat project root: · /path/to/zkevm-erc721-hardhat
# ✔ Do you want to add a .gitignore? (Y/n) · y
# ✔ Do you want to install this sample project's dependencies with npm (hardhat @nomicfoundation/hardhat-toolbox)? (Y/n) · y
接下来,让我们安装依赖项:
npme install;
npm install @openzeppelin/contracts dotenv;
创建我们的NFT
我们这里的NFT,将通过引入一个动画的SVG来使它突出一点。
首先删除默认生成的模板化合约,并在其目录上创建一个新的合约。
rm contracts/Lock.sol;
touch contracts/zkEVMNFT.sol;
在该文件中,复制并粘贴以下NFT solidity代码。
文件: ./contracts/zkEVMNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
// Imports
// ========================================================
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
// Contract
// ========================================================
contract ZkEVMNFT is ERC721 {
// Extending functionality
using Strings for uint256;
/**
* Main constructor
*/
constructor() ERC721("zkEVMNFT", "zkNFT") {}
/**
* Main minting function
*/
function safeMint(address to, uint256 tokenId) public {
_safeMint(to, tokenId);
}
// The following functions are overrides required by Solidity.
/**
* @dev See {ERC721}
*/
function _burn(uint256 tokenId) internal override(ERC721) {
super._burn(tokenId);
}
/**
* Public function or burning
*/
function burn (uint256 tokenId) public {
_burn(tokenId);
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId)
public
view
override(ERC721)
returns (string memory)
{
// Validation
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
// SVG Image
bytes memory imageSVG = abi.encodePacked(
"<svg width=\"256\" height=\"256\" viewBox=\"0 0 256 256\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">",
"<style xmlns=\"http://www.w3.org/2000/svg\">@keyframes rainbow-background { 0% { fill: #ff0000; } 8.333% { fill: #ff8000; } 16.667% { fill: #ffff00; } 25.000% { fill: #80ff00; } 33.333% { fill: #00ff00; } 41.667% { fill: #00ff80; } 50.000% { fill: #00ffff; } 58.333% { fill: #0080ff; } 66.667% { fill: #0000ff; } 75.000% { fill: #8000ff; } 83.333% { fill: #ff00ff; } 91.667% { fill: #ff0080; } 100.00% { fill: #ff0000; }} #background { animation: rainbow-background 5s infinite; } #text { font-family: \"Helvetica\", \"Arial\", sans-serif; font-weight: bold; font-size: 72px; }</style>",
"<g clip-path=\"url(#clip0_108_2)\">",
"<rect id=\"background\" width=\"256\" height=\"256\" fill=\"#ff0000\"/>",
"<rect x=\"28\" y=\"28\" width=\"200\" height=\"200\" fill=\"white\"/>",
"</g>",
"<defs>",
"<clipPath id=\"clip0_108_2\">",
"<rect width=\"256\" height=\"256\" fill=\"white\"/>",
"</clipPath>",
"</defs>",
"<text xmlns=\"http://www.w3.org/2000/svg\" id=\"text\" x=\"128\" y=\"150\" fill=\"black\" style=\"width: 256px; display: block; text-align: center;\" text-anchor=\"middle\">", tokenId.toString(), "</text>",
"</svg>"
);
// JSON
bytes memory dataURI = abi.encodePacked(
"{",
"\"name\": \"NUMSVG #", tokenId.toString(), "\",",
"\"image\": \"data:image/svg+xml;base64,", Base64.encode(bytes(imageSVG)), "\"",
"}"
);
// Returned JSON
return string(
abi.encodePacked(
"data:application/json;base64,",
Base64.encode(dataURI)
)
);
}
}
配置 Hardhat
接下来我们将配置Hardhat配置文件,使环境变量能够被加载,配置对网络的支持,并调整优化。
文件: ./hardhat.config.ts
// Imports
// ========================================================
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import dotenv from "dotenv";
// Config
// ========================================================
dotenv.config();
const config: HardhatUserConfig = {
solidity: {
version: "0.8.18",
settings: {
optimizer: {
enabled: true,
runs: 200,
}
}
},
networks: {
mumbai: {
url: `${process.env.RPC_MUMBAI_URL || ''}`,
accounts: process.env.WALLET_PRIVATE_KEY
? [`0x${process.env.WALLET_PRIVATE_KEY}`]
: [],
},
zkevmTestnet: {
url: `${process.env.RPC_ZKEVM_URL || ''}`,
accounts: process.env.WALLET_PRIVATE_KEY
? [`0x${process.env.WALLET_PRIVATE_KEY}`]
: [],
}
},
};
// Exports
// ========================================================
export default config;
为Hardhat添加环境变量
一旦我们完成了Hardhat的配置,我们将创建一个环境变量文件来存储我们的关键值。
touch .env;
注意:记得保护你的私钥不被人窥视。
文件: ./env
RPC_MUMBAI_URL=https://rpc.ankr.com/polygon_mumbai
RPC_ZKEVM_URL=https://rpc.public.zkevm-test.net
WALLET_PRIVATE_KEY=<YOUR-WALLET-PRIVATE-KEY>
最后,让我们修改我们的部署脚本以指向正确的合约。
文件: ./scripts/deploy.ts
// Imports
// ========================================================
import { ethers } from "hardhat";
// Main Deployment Script
// ========================================================
async function main() {
// Make sure in the contract factory that it mateches the contract name in the solidity file
// Ex: contract zkEVMNFT
const zkERC721Contract = await ethers.getContractFactory("zkEVMNFT");
const contract = await zkERC721Contract.deploy();
await contract.deployed();
console.log(`zkEVMNFT deployed to ${contract.address}`);
};
// Init
// ========================================================
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
部署出NFT合约
一旦一切就绪,我们现在可以将我们的NFT合约部署到zkEVM Testnet
# FROM: ./zkevm-erc721-hardhat
npx hardhat run scripts/deploy.ts --network zkevmTestnet;
# Expected Output:
# zkEVMNFT deployed to 0x7F77cF06C84A7bae6D710eBfc78a48214E4937a7
如果我们在资源管理器上进入合约,我们可以看到以下结果:
https://explorer.public.zkevm-test.net/address/0x7F77cF06C84A7bae6D710eBfc78a48214E4937a7
在zkEVM区块浏览器上验证我们的NFT合约
我们可以直接通过Hardhat与合约交互,进行铸币和读取,但我们将使用区块链浏览器来连接和铸币一个新的NFT。
在我们进入验证过程之前,我们将通过标准输入JSON进行验证,我们需要一个文件来上传。
如果我们看一下我们的build-info文件夹,我们可以找到一个JSON文件,我们只需要得到input部分。复制整个部分并创建一个名为verify.json的新文件,并将JSON数据粘贴在那里。
记住,你需要事先编译合约,用npx hardhat compile
生成这些文件。
文件: ./artifacts/build-info/your-build-file.json
创建 JSON 文件
我们新的标准输入JSON文件为verify.json
接下来在区块资源管理器上访问该合约,并开始验证过程
zkEVM Testnet 区块浏览器验证和发布合约
zkEVM Testnet Block Explorer 通过标准输入 JSON 验证
zkEVM Testnet Block Explorer验证Conifgure verify.json
一旦验证通过,我们就可以在zkEVM Testnet block explorer上看到我们合约的完整代码。
https://explorer.public.zkevm-test.net/address/0x7F77cF06C84A7bae6D710eBfc78a48214E4937a7/contracts#address-tabs
在 zkEVM 测试完上铸造 NFT
现在我们的合约已经被验证了,我们可以通过区块浏览器和我们的钱包与它交互。
为了本教程的目的,我们将铸造一个NFT tokenID为4,以纪念距离zkEVM主网测试版发布的天数--2023年3月27日
https://explorer.public.zkevm-test.net/address/0x7F77cF06C84A7bae6D710eBfc78a48214E4937a7/write-contract#address-tabs
在zkEVM Testnet上铸造NFT
确认在zkEVM Testnet上成功铸造了一个NFT
查看我们的zkEVM NFT
我们在zkEVM Testnet上铸造了NFT,现在我们希望能够直观地看到这个链上SVG NFT
zkEVM Testnet区块浏览器读取NFT
复制tokenURI查询的数据部分,在Chrome浏览器中,将其粘贴到地址栏。
zkEVM Testnet NFT tokenURI JSON数据
从JSON中复制image值,在Chrome中,将其粘贴在地址栏中。
zkEVM Testnet NFT SVG
我们成功地部署了合约,验证了合约,并且我们能够看到铸造的NFT。
完整的zkEVM NFT代码库
点击这里查看本教程的完整代码库.
下一步是什么?
请关注更多关于zkEVM的教程。
如果你想更深入地了解zkEVM,请查看Polygon University。