在社区中我们会经常见到为奖励早期支持者,其提供白名单,供获取 NFT。至于说白名单实现的方式有多种多样,今天主要介绍一种,将白名单写入合约,并且通过外部调用的方式来支持此项功能。
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.4;
import "hardhat/console.sol";
contract UseWeb3Whitelist {
uint8 max;
uint8 whitelistedNumber;
mapping (address => bool) public whiteMap;
constructor(uint8 _max) {
max = _max;
whitelistedNumber = 0;
}
function addToWhitelist() public{
require(!whiteMap[msg.sender], "already been whitelisted");
require(whitelistedNumber < max, "limit reached");
whiteMap[msg.sender] = true;
whitelistedNumber += 1;
console.log("%s added", msg.sender);
}
function verifyWhite(address whiteAddress) public view returns(bool){
return whiteMap[whiteAddress];
}
}
上述代码其实有一个个人习惯问题,我喜欢在构造函数中显式的将状态进行初始化。
whiteMap 维护了白名单列表的状态
whitelistedNumber 设置一个上限
我并不喜欢让 owner 来手动的添加,但这一点在代码中是可以做的,包括修改状态
接下来我们需要为 NFT 的发布定制一些功能:
只提供 10 NFT,且每一个都是唯一的
用户通过一笔交易只能铸造 1 个 NFT
白名单 mint 的价格
普通 mint 的价格
等待白名单用户铸造完成
可以提取销售获得的以太坊
在开始编码前,需先了解一下 openzeppelin 提供的几个合约:
Access Control 合约提供了一些控制权限,默认情况下 Ownable 的所有者就是部署合约的地址,此外 Ownable 还可以将所有权转移或放弃管理权限
Counters 合约是一种获取只能递增、递减或重置的计数器的简单方法
ERC721URIStorage 一种更灵活的元数据存储方式
此外,我们还需要去准备一些图片并熟悉 TokenURI 的 JSON 格式
{
"name": "Example UseWeb3NFT",
"description": "A silent hero. A watchful protector. by wen",
"image": "ipfs://Qma7CPJT5WQerSRpnhPsqCb7oBZEU25myBVhkq52YvAdPd",
}
正常情况下我们会提前将图片存储到 IPFS 上,然后编写一个 tokenIds 关联列表,在这个列表中存储了 tokenIds 对应的 CID,最终一个动态的值仅仅是 image 字段。
说明:由于是案例代码,图片使用我个人推特上的头像代替,并未实现 tokenIds 的关联列表
我将 whiteMint 和 mint 分别用开关控制起来,这个开关只有 owner 才能设置
简单来说,我们应该有一个页面让用户提交白名单,再有一个页面来提供销售
说明:由于是案例,我将两个页面合二唯一,做了五个按钮,分别是添加白名单,白名单mint,普通 mint,mint 打开权限和提取销售金额
为了让合约可以接收 eth,你需要 payable 关键字
whiteMint 和 mint 都做了类似的事情,不同的地方在于 whiteMint 会去外部调用,检查地址是否在白名单中。
同时检查是否已经被 mint 过
我们用 current 来获取当前的 ids,在调用完 _safeMint 和 设置 _setTokenURI 后,我们需要调用 increment 将 ids 自增
现在我们需要编写一些前端代码:
import React, { useEffect, useState } from 'react';
import { ethers } from 'ethers';
import UseWeb3NFTJSON from './abi/UseWeb3NFT.json';
import UseWeb3WhitelistJSON from './abi/UseWeb3Whitelist.json';
import './UseWeb3NFT.css';
const USEWEB3_WHITELIST_CONTRACT_ADDRESS = "0x987D7633510071fF9a8E2aC7EdD566D227A9352C";
const USEWEB3_WHITELIST_ABI = UseWeb3WhitelistJSON.abi;
const USEWEB3_NFT_CONTRACT_ADDRESS = "0x9809d9D94b0B3380db38b1e1a06047a2964e0041";
const USEWEB3_NFT_ABI = UseWeb3NFTJSON.abi;
const getWeb3Provider = () => {
let web3Provider = window.web3Provider;
if (!web3Provider){
web3Provider = new ethers.providers.Web3Provider(window.ethereum);
window.web3Provider = web3Provider;
}
return web3Provider;
}
export default function UseWeb3NFT(){
const [address, setAddress] = useState('');
useEffect(() => {
if (!address){
const web3Provider = getWeb3Provider();
const selectedAddress = web3Provider.provider.selectedAddress;
if (!selectedAddress){
web3Provider.provider.request({ method: 'eth_requestAccounts' }).then((accounts) => {
const account = accounts[0];
setAddress(account);
});
} else {
setAddress(selectedAddress);
}
}
}, [address]);
const addWhileAddress = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const whitelistContract = new ethers.Contract(USEWEB3_WHITELIST_CONTRACT_ADDRESS, USEWEB3_WHITELIST_ABI, signer);
await whitelistContract.addToWhitelist();
alert('添加成功');
}
const whiteMint = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const NFTContract = new ethers.Contract(USEWEB3_NFT_CONTRACT_ADDRESS, USEWEB3_NFT_ABI, signer);
await NFTContract.whiteMint({value: ethers.utils.parseEther("0.001")});
}
const mint = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const NFTContract = new ethers.Contract(USEWEB3_NFT_CONTRACT_ADDRESS, USEWEB3_NFT_ABI, signer);
await NFTContract.mint({value: ethers.utils.parseEther("0.01")});
}
const openedMint = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const NFTContract = new ethers.Contract(USEWEB3_NFT_CONTRACT_ADDRESS, USEWEB3_NFT_ABI, signer);
await NFTContract.mintOpened();
}
const withdraw = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const NFTContract = new ethers.Contract(USEWEB3_NFT_CONTRACT_ADDRESS, USEWEB3_NFT_ABI, signer);
await NFTContract.withdraw();
}
return (
<div className="useweb3NFTContainer">
<div onClick={addWhileAddress}>Add While Address</div>
<div onClick={whiteMint}>White Mint</div>
<div onClick={mint}>Mint</div>
<div onClick={openedMint}>opened Mint</div>
<div onClick={withdraw}>withdraw</div>
</div>
)
}
现在,我们分别进行测试,第一个测试如果在没有添加白名单时,去点击 whiteMint 我们会得到一个错误:

设置完白名单后,点击 whiteMint 来获取我们的第一个 NFT 吧
如果已经 mint 过,再次点击 whiteMint 依然会得到另外一个错误:

当我们未开启 mint 权限时,如果点击 mint ,我们会得到一个错误:

如果你输入错误的价格或者不是 owner 地址提取销售金额,依然是会得到一个错误
现在让我们将合约部署到 rinkeby 网络中
$ npm run rinkeby
可以在 https://rinkeby.etherscan.io/ 上查看合约。
让我们先 whileMint 一个,打开 https://testnets.opensea.io/ 将合约贴过去,此时:

同时打开 mint 的开关,使用 mint 函数多 mint 几个,再次观察合约地址:

是的,我们的 NFT 销售了几个,一旦销售完成,我们可以使用取款函数,将销售金额提取到 owner 地址。
为了将合约看起来更正式,我们需要对合约进行验证,在 https://etherscan.io/ 申请一个 API-KEY,然后粘贴到 .env 文件的 ETHERSCAN_KEY 字段
安装 @nomiclabs/hardhat-etherscan 后打开 hardhat.config.js 文件,在文件顶部导入 @nomiclabs/hardhat-etherscan 并在配置中增加如下配置:
etherscan: {
apiKey: {
rinkeby: ETHERSCAN_KEY
}
}
修改 package.json 文件的 scripts 增加一行:
"verify": "hardhat run scripts/verify.js --network rinkeby"
最后运行:
$ npm run verify
重新在 https://rinkeby.etherscan.io/ 检查合约,如图:

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "hardhat/console.sol";
interface IUseWebWhitelist {
function verifyWhite(address whiteAddress) external view returns(bool);
}
contract UseWeb3NFT is ERC721URIStorage, Ownable{
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
string baseTokenURI;
uint256 public price = 0.01 ether;
uint256 public whitePrice = 0.001 ether;
uint256 public maxIds = 10000;
bool public whiteMinted;
bool public minted;
mapping (address => bool) whiteMints;
IUseWebWhitelist whitelist;
modifier onlyWhiteMinted {
require(whiteMinted, "Contract currently paused");
_;
}
modifier onlyMint {
require(minted, "mint currently paused");
_;
}
constructor(address whitelistContract) ERC721("Example UseWeb3NFT", "EUWNFT"){
whitelist = IUseWebWhitelist(whitelistContract);
whiteMinted = true;
minted = false;
baseTokenURI = "ipfs://Qma7CPJT5WQerSRpnhPsqCb7oBZEU25myBVhkq52YvAdPd";
}
function mintOpened() public onlyOwner{
minted = true;
}
function mintClosed() public onlyOwner{
minted = false;
}
function whiteOpened() public onlyOwner{
whiteMinted = true;
}
function whiteClosed() public onlyOwner{
whiteMinted = false;
}
function whiteMint() public payable onlyWhiteMinted{
// 检查是否是白名单地址
require(whitelist.verifyWhite(msg.sender), "You are not whitelisted");
// 检查是否已经 mint
require(!whiteMints[msg.sender], "you are minted");
uint256 currentTokenIds = _tokenIds.current();
// 白名单价格
require(msg.value >= whitePrice, "eth sent is not correct");
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Example UseWeb3NFT", "description": "A silent hero. A watchful protector. by wen", "image":"',baseTokenURI,'"}'))));
string memory finalTokenUri = string(
abi.encodePacked("data:application/json;base64,", json)
);
_safeMint(msg.sender, currentTokenIds);
_setTokenURI(currentTokenIds, finalTokenUri);
_tokenIds.increment();
whiteMints[msg.sender] = true;
}
function mint() public payable onlyMint {
uint256 currentTokenIds = _tokenIds.current();
// 检查是否超过最大数
require(currentTokenIds < maxIds, "Exceeded maximum");
// mint 价格
require(msg.value >= price, "eth sent is not correct");
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Example UseWeb3NFT", "description": "A silent hero. A watchful protector. by wen", "image":"',baseTokenURI,'"}'))));
string memory finalTokenUri = string(
abi.encodePacked("data:application/json;base64,", json)
);
_safeMint(msg.sender, currentTokenIds);
_setTokenURI(currentTokenIds, finalTokenUri);
_tokenIds.increment();
}
/**
将合约中收到的 eth 发送给 owner 地址
*/
function withdraw() public onlyOwner {
address _owner = owner();
uint256 amount = address(this).balance;
(bool sendStatus, ) = _owner.call{value: amount}("");
require(sendStatus, "Failed send");
}
receive() external payable {}
fallback() external payable {}
}
经济下行阶段,一个 37 岁失业程序员的独白(经历/经验分享)
警告:区块链投资高风险,需要谨慎,谨慎,再谨慎!
实战案例四:DeFi 去中心化交易所
现实情况是期望代币可以在去中心化的交易场所中交换,这篇文章就是从一个简单案例来说明交换,流动性该如何实现。 我们需要先梳理一下,期望这个应用具备哪些功能:只用一个代币对建立交易场所交易收取 1% 的费用用户可以为 UseWeb3Token 添加或删除流动性为用户提供 LP 代币说明:实现会比这个例子复杂的多// SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity ^0.8.4; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract UseWeb3Exchange is ERC20 { address public useweb3TokenAddress; constructor(address useweb3TokenContract) ERC20("LP Token", "LP") { useweb3TokenAddress = useweb3TokenContract; } function getReserve() publ...
初识 Solidity 和 OpenZeppelin
Solidity 是一种面向对象的高级静态语言,用于实现智能合约,运行于 以太坊虚拟机,它支持继承,库和自定义类型等。pragma solidity ^0.8.0; contract HelloWorld { } Solidity 有三种类型的变量,熟悉它是因为变量的范围是由它们声明的位置所决定的:Local在函数内部声明且不存储在区块链上State存储在区块链上Global提供区块链相关的信息,它在运行时由以太坊虚拟机注入包括交易发送者,区块时间戳,区块哈希等全局变量语法知识,请阅读:https://docs.soliditylang.org/en/v0.8.9/index.html初识 OpenZeppelin说明:OpenZeppelin 是一家以太坊安全公司,其为流行的智能合约标准开发了一组合约,这些合约经过了大量的测试和安全审查,所以如果我们需要实现这些标准合约时,应该尝试找到 OpenZeppelin 提供的合约,而不是重头开始重写整个标准。https://github.com/OpenZeppelin/openzeppelin-contracts在 useweb3 ...
Dev
在社区中我们会经常见到为奖励早期支持者,其提供白名单,供获取 NFT。至于说白名单实现的方式有多种多样,今天主要介绍一种,将白名单写入合约,并且通过外部调用的方式来支持此项功能。
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.4;
import "hardhat/console.sol";
contract UseWeb3Whitelist {
uint8 max;
uint8 whitelistedNumber;
mapping (address => bool) public whiteMap;
constructor(uint8 _max) {
max = _max;
whitelistedNumber = 0;
}
function addToWhitelist() public{
require(!whiteMap[msg.sender], "already been whitelisted");
require(whitelistedNumber < max, "limit reached");
whiteMap[msg.sender] = true;
whitelistedNumber += 1;
console.log("%s added", msg.sender);
}
function verifyWhite(address whiteAddress) public view returns(bool){
return whiteMap[whiteAddress];
}
}
上述代码其实有一个个人习惯问题,我喜欢在构造函数中显式的将状态进行初始化。
whiteMap 维护了白名单列表的状态
whitelistedNumber 设置一个上限
我并不喜欢让 owner 来手动的添加,但这一点在代码中是可以做的,包括修改状态
接下来我们需要为 NFT 的发布定制一些功能:
只提供 10 NFT,且每一个都是唯一的
用户通过一笔交易只能铸造 1 个 NFT
白名单 mint 的价格
普通 mint 的价格
等待白名单用户铸造完成
可以提取销售获得的以太坊
在开始编码前,需先了解一下 openzeppelin 提供的几个合约:
Access Control 合约提供了一些控制权限,默认情况下 Ownable 的所有者就是部署合约的地址,此外 Ownable 还可以将所有权转移或放弃管理权限
Counters 合约是一种获取只能递增、递减或重置的计数器的简单方法
ERC721URIStorage 一种更灵活的元数据存储方式
此外,我们还需要去准备一些图片并熟悉 TokenURI 的 JSON 格式
{
"name": "Example UseWeb3NFT",
"description": "A silent hero. A watchful protector. by wen",
"image": "ipfs://Qma7CPJT5WQerSRpnhPsqCb7oBZEU25myBVhkq52YvAdPd",
}
正常情况下我们会提前将图片存储到 IPFS 上,然后编写一个 tokenIds 关联列表,在这个列表中存储了 tokenIds 对应的 CID,最终一个动态的值仅仅是 image 字段。
说明:由于是案例代码,图片使用我个人推特上的头像代替,并未实现 tokenIds 的关联列表
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "hardhat/console.sol";
interface IUseWebWhitelist {
function verifyWhite(address whiteAddress) external view returns(bool);
}
contract UseWeb3NFT is ERC721URIStorage, Ownable{
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
string baseTokenURI;
uint256 public price = 0.01 ether;
uint256 public whitePrice = 0.001 ether;
uint256 public maxIds = 10000;
bool public whiteMinted;
bool public minted;
mapping (address => bool) whiteMints;
IUseWebWhitelist whitelist;
modifier onlyWhiteMinted {
require(whiteMinted, "Contract currently paused");
_;
}
modifier onlyMint {
require(minted, "mint currently paused");
_;
}
constructor(address whitelistContract) ERC721("Example UseWeb3NFT", "EUWNFT"){
whitelist = IUseWebWhitelist(whitelistContract);
whiteMinted = true;
minted = false;
baseTokenURI = "ipfs://Qma7CPJT5WQerSRpnhPsqCb7oBZEU25myBVhkq52YvAdPd";
}
function mintOpened() public onlyOwner{
minted = true;
}
function mintClosed() public onlyOwner{
minted = false;
}
function whiteOpened() public onlyOwner{
whiteMinted = true;
}
function whiteClosed() public onlyOwner{
whiteMinted = false;
}
function whiteMint() public payable onlyWhiteMinted{
// 检查是否是白名单地址
require(whitelist.verifyWhite(msg.sender), "You are not whitelisted");
// 检查是否已经 mint
require(!whiteMints[msg.sender], "you are minted");
uint256 currentTokenIds = _tokenIds.current();
// 白名单价格
require(msg.value >= whitePrice, "eth sent is not correct");
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Example UseWeb3NFT", "description": "A silent hero. A watchful protector. by wen", "image":"',baseTokenURI,'"}'))));
string memory finalTokenUri = string(
abi.encodePacked("data:application/json;base64,", json)
);
_safeMint(msg.sender, currentTokenIds);
_setTokenURI(currentTokenIds, finalTokenUri);
_tokenIds.increment();
whiteMints[msg.sender] = true;
}
function mint() public payable onlyMint {
uint256 currentTokenIds = _tokenIds.current();
// 检查是否超过最大数
require(currentTokenIds < maxIds, "Exceeded maximum");
// mint 价格
require(msg.value >= price, "eth sent is not correct");
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Example UseWeb3NFT", "description": "A silent hero. A watchful protector. by wen", "image":"',baseTokenURI,'"}'))));
string memory finalTokenUri = string(
abi.encodePacked("data:application/json;base64,", json)
);
_safeMint(msg.sender, currentTokenIds);
_setTokenURI(currentTokenIds, finalTokenUri);
_tokenIds.increment();
}
/**
将合约中收到的 eth 发送给 owner 地址
*/
function withdraw() public onlyOwner {
address _owner = owner();
uint256 amount = address(this).balance;
(bool sendStatus, ) = _owner.call{value: amount}("");
require(sendStatus, "Failed send");
}
receive() external payable {}
fallback() external payable {}
}
我将 whiteMint 和 mint 分别用开关控制起来,这个开关只有 owner 才能设置
简单来说,我们应该有一个页面让用户提交白名单,再有一个页面来提供销售
说明:由于是案例,我将两个页面合二唯一,做了五个按钮,分别是添加白名单,白名单mint,普通 mint,mint 打开权限和提取销售金额
为了让合约可以接收 eth,你需要 payable 关键字
whiteMint 和 mint 都做了类似的事情,不同的地方在于 whiteMint 会去外部调用,检查地址是否在白名单中。
同时检查是否已经被 mint 过
我们用 current 来获取当前的 ids,在调用完 _safeMint 和 设置 _setTokenURI 后,我们需要调用 increment 将 ids 自增
现在我们需要编写一些前端代码:
import React, { useEffect, useState } from 'react';
import { ethers } from 'ethers';
import UseWeb3NFTJSON from './abi/UseWeb3NFT.json';
import UseWeb3WhitelistJSON from './abi/UseWeb3Whitelist.json';
import './UseWeb3NFT.css';
const USEWEB3_WHITELIST_CONTRACT_ADDRESS = "0x987D7633510071fF9a8E2aC7EdD566D227A9352C";
const USEWEB3_WHITELIST_ABI = UseWeb3WhitelistJSON.abi;
const USEWEB3_NFT_CONTRACT_ADDRESS = "0x9809d9D94b0B3380db38b1e1a06047a2964e0041";
const USEWEB3_NFT_ABI = UseWeb3NFTJSON.abi;
const getWeb3Provider = () => {
let web3Provider = window.web3Provider;
if (!web3Provider){
web3Provider = new ethers.providers.Web3Provider(window.ethereum);
window.web3Provider = web3Provider;
}
return web3Provider;
}
export default function UseWeb3NFT(){
const [address, setAddress] = useState('');
useEffect(() => {
if (!address){
const web3Provider = getWeb3Provider();
const selectedAddress = web3Provider.provider.selectedAddress;
if (!selectedAddress){
web3Provider.provider.request({ method: 'eth_requestAccounts' }).then((accounts) => {
const account = accounts[0];
setAddress(account);
});
} else {
setAddress(selectedAddress);
}
}
}, [address]);
const addWhileAddress = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const whitelistContract = new ethers.Contract(USEWEB3_WHITELIST_CONTRACT_ADDRESS, USEWEB3_WHITELIST_ABI, signer);
await whitelistContract.addToWhitelist();
alert('添加成功');
}
const whiteMint = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const NFTContract = new ethers.Contract(USEWEB3_NFT_CONTRACT_ADDRESS, USEWEB3_NFT_ABI, signer);
await NFTContract.whiteMint({value: ethers.utils.parseEther("0.001")});
}
const mint = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const NFTContract = new ethers.Contract(USEWEB3_NFT_CONTRACT_ADDRESS, USEWEB3_NFT_ABI, signer);
await NFTContract.mint({value: ethers.utils.parseEther("0.01")});
}
const openedMint = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const NFTContract = new ethers.Contract(USEWEB3_NFT_CONTRACT_ADDRESS, USEWEB3_NFT_ABI, signer);
await NFTContract.mintOpened();
}
const withdraw = async () => {
const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const NFTContract = new ethers.Contract(USEWEB3_NFT_CONTRACT_ADDRESS, USEWEB3_NFT_ABI, signer);
await NFTContract.withdraw();
}
return (
<div className="useweb3NFTContainer">
<div onClick={addWhileAddress}>Add While Address</div>
<div onClick={whiteMint}>White Mint</div>
<div onClick={mint}>Mint</div>
<div onClick={openedMint}>opened Mint</div>
<div onClick={withdraw}>withdraw</div>
</div>
)
}
现在,我们分别进行测试,第一个测试如果在没有添加白名单时,去点击 whiteMint 我们会得到一个错误:

设置完白名单后,点击 whiteMint 来获取我们的第一个 NFT 吧
如果已经 mint 过,再次点击 whiteMint 依然会得到另外一个错误:

当我们未开启 mint 权限时,如果点击 mint ,我们会得到一个错误:

如果你输入错误的价格或者不是 owner 地址提取销售金额,依然是会得到一个错误
现在让我们将合约部署到 rinkeby 网络中
$ npm run rinkeby
可以在 https://rinkeby.etherscan.io/ 上查看合约。
让我们先 whileMint 一个,打开 https://testnets.opensea.io/ 将合约贴过去,此时:

同时打开 mint 的开关,使用 mint 函数多 mint 几个,再次观察合约地址:

是的,我们的 NFT 销售了几个,一旦销售完成,我们可以使用取款函数,将销售金额提取到 owner 地址。
为了将合约看起来更正式,我们需要对合约进行验证,在 https://etherscan.io/ 申请一个 API-KEY,然后粘贴到 .env 文件的 ETHERSCAN_KEY 字段
安装 @nomiclabs/hardhat-etherscan 后打开 hardhat.config.js 文件,在文件顶部导入 @nomiclabs/hardhat-etherscan 并在配置中增加如下配置:
etherscan: {
apiKey: {
rinkeby: ETHERSCAN_KEY
}
}
修改 package.json 文件的 scripts 增加一行:
"verify": "hardhat run scripts/verify.js --network rinkeby"
最后运行:
$ npm run verify
重新在 https://rinkeby.etherscan.io/ 检查合约,如图:

经济下行阶段,一个 37 岁失业程序员的独白(经历/经验分享)
警告:区块链投资高风险,需要谨慎,谨慎,再谨慎!
实战案例四:DeFi 去中心化交易所
现实情况是期望代币可以在去中心化的交易场所中交换,这篇文章就是从一个简单案例来说明交换,流动性该如何实现。 我们需要先梳理一下,期望这个应用具备哪些功能:只用一个代币对建立交易场所交易收取 1% 的费用用户可以为 UseWeb3Token 添加或删除流动性为用户提供 LP 代币说明:实现会比这个例子复杂的多// SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity ^0.8.4; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract UseWeb3Exchange is ERC20 { address public useweb3TokenAddress; constructor(address useweb3TokenContract) ERC20("LP Token", "LP") { useweb3TokenAddress = useweb3TokenContract; } function getReserve() publ...
初识 Solidity 和 OpenZeppelin
Solidity 是一种面向对象的高级静态语言,用于实现智能合约,运行于 以太坊虚拟机,它支持继承,库和自定义类型等。pragma solidity ^0.8.0; contract HelloWorld { } Solidity 有三种类型的变量,熟悉它是因为变量的范围是由它们声明的位置所决定的:Local在函数内部声明且不存储在区块链上State存储在区块链上Global提供区块链相关的信息,它在运行时由以太坊虚拟机注入包括交易发送者,区块时间戳,区块哈希等全局变量语法知识,请阅读:https://docs.soliditylang.org/en/v0.8.9/index.html初识 OpenZeppelin说明:OpenZeppelin 是一家以太坊安全公司,其为流行的智能合约标准开发了一组合约,这些合约经过了大量的测试和安全审查,所以如果我们需要实现这些标准合约时,应该尝试找到 OpenZeppelin 提供的合约,而不是重头开始重写整个标准。https://github.com/OpenZeppelin/openzeppelin-contracts在 useweb3 ...
Share Dialog
Share Dialog

Subscribe to icepy

Subscribe to icepy
<100 subscribers
<100 subscribers
No activity yet