Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何创建一个代币承销商 dApp #155

Open
qiwihui opened this issue Feb 28, 2022 · 0 comments
Open

如何创建一个代币承销商 dApp #155

qiwihui opened this issue Feb 28, 2022 · 0 comments
Labels
区块链 区块链相关
Milestone

Comments

@qiwihui
Copy link
Owner

qiwihui commented Feb 28, 2022

这篇教程我们来完成 scaffold-eth 项目的第二个挑战:代币承销商,我们可以在网站 speedrunethereum.com 中查看或者直接查看对应的 Github 连接:scaffold-eth/scaffold-eth-typescript-challenges

这个挑战的目的是创建一个自己的ERC20代币,并编写承销商合约,实现用户对代币的购买和卖出。下面,我们一步步完成这个过程。

一、安装并设置环境

首先,我们下载项目,并初始化环境。

git clone https://github.com/scaffold-eth/scaffold-eth-typescript-challenges.git challenge-2-token-vendor
cd challenge-2-token-vendor
git checkout challenge-2-token-vendor
yarn install

安装好依赖包之后,我们可以看到项目的主要目录为 packages,包含一下子目录

packages/
├── hardhat-ts
├── services
├── subgraph
└── vite-app-ts

其中:

  • hardhat-ts 是项目合约代码,包含合约文件以及合约的部署等;
  • services The Graph 协议的 graph-node 配置;
  • subgraph The Graph 协议相应的处理设置,包括 mappings,数据结构等;
  • vite-app-ts 前端项目,主要负责用户与合约交互。

The Graph 协议是去中心化的区块链数据索引协议,本片教程中暂时不涉及。我们需要启动三个命令终端,分别用于运行以下命令:

  • yarn chain 使用 hardhat 运行本地区块链,作为合约部署的本地测试链;
  • yarn deploy 编译、部署和发布合约;
  • yarn start 启动 react 应用的前端;

按顺序分别运行上述命令之后,此时我们就可以在 http://localhost:3000中访问我们的应用。如果需要重新部署合约,运行 yarn deploy --reset 即可。

yarn-start

二、编写 ERC20 代币合约

现在我们进入合约编写部分。我们的目标是编写一个 ERC20 代币合约,并为创建者铸造 1000 个代币。

什么是 ERC20 合约标准

代币可以在以太坊中表示任何东西,比如信誉积分,黄金等,而 ERC-20 提供了一个同质化代币的标准,每个代币与另一个代币(在类型和价值上)完全相同。

ERC20是各个代币的标准接口,包含以下方法:

// 名称
function name() public view returns (string)
// 符号
function symbol() public view returns (string)
// 合约使用的小数位,常见为 18
function decimals() public view returns (uint8)
// 代币总供应量
function totalSupply() public view returns (uint256)
// 地址的代币持有量
function balanceOf(address _owner) public view returns (uint256 balance)
// 代币划转
function transfer(address _to, uint256 _value) public returns (bool success)
// 用于划转代币,但这些代币不一定属于调用合约的用户
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
// 合约授予用户代币管理权限,调用者设置 spender 消费自己 amount 数量的代币
function approve(address _spender, uint256 _value) public returns (bool success)
// 检查代币的可消费余额
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

// 事件
// 代币转移事件
event Transfer(address indexed from, address indexed to, uint256 value);
// 当调用 approve 时,触发 Approval 事件
event Approval(
    address indexed owner,
    address indexed spender,
    uint256 value
);

其中,合约必需设置 totalSupplybalanceOftransfertransferFromapprove 以及 allowance 这六个函数,其他如 namesymboldecimalsze 则是可选实现。

使用 OpenZeppelin 库

如果从上述的合约标准开始,我们需要实现这六个函数的方法,幸运的是,OpenZeppelin 库是一个成熟的合约开发库,为我们实现了 ERC20 代币基本功能,我们可以基于这个库开发我们的 ERC20 代币,这将大大减少我们的工作量。我们可以在 ERC20 标准 页面查到相关的使用方法。

除了 ERC20,OpenZeppelin 库还提供了其他合约标准的实现,比如 ERC721,ERC777等,以及大量的经过安全审计的库,这些对于我们快速开发和实现安全的合约代码提供了支持。

编写代码

我们使用 ERC20.sol 来实现我们的合约,创见一个名为 GOLD 的代币,代币符号为 GLD,并为创建者铸造 1000 个代币:

pragma solidity >=0.8.0 <0.9.0;
// SPDX-License-Identifier: MIT

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

// learn more: https://docs.openzeppelin.com/contracts/3.x/erc20

  constructor() public ERC20('Gold', 'GLD') {
    // 铸造 1000 * 10 ** 18 给 msg.sender
    _mint(msg.sender, 1000 * 10 ** 18);
  }
}

其中, _mint 方法是 ERC20 提供的方法,该方法创建相应数量的代币,并将代币发送给账户:

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

代码地址:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol#L248

部署脚本

接着我们使用脚本进行部署,并向地址发送 1000 代币,地址可以在 http://localhost:3000 中连接我们的 Metamask 得到。部署脚本地址:packages/hardhat-ts/deploy/00_deploy_your_token.ts

...

  const yourToken = await ethers.getContract('YourToken', deployer);

  // 发送代币
  const result = await yourToken.transfer('0x169841AA3024cfa570024Eb7Dd6Bf5f774092088', ethers.utils.parseEther('1000'));

...

然后我们运行 yarn deploy --reset 部署合约。

验证

  1. 使用 Debug 页面功能进行检查,查看用户账户中的代币余额,可以看到账户中有 1000 个代币;

    balance

  2. 使用 transfer() 将代币转给另一个账户;

    在 Debug 中,使用 transfer 功能,输入目标钱包地址 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33,以及发送的数量 1000000000000000000000(1000*1E18,1后边有21个0),点击发送。等交易完成之后,可以分别查看原来账户和目标账户的代币数量,可以看到原来的变成了 0,目标账户是 1000。

    transfer

注意:

  • 如果发送时出现余额不足的提示,可以使用页面左下角的 Faucet 为账户充值。
  • 验证完成之后,需要将 00_deploy_your_token.ts 中的 transfer 代码注释了,不然会影响之后的步骤。

三、承销商合约 — 购买

接下来,我们创建一个承销商合约,这个合约允许用户通过以太购买代币。

为了完成这个功能,我们需要:

  1. 设置兑换比例,教程中为 tokensPerEth=100 ,也就是 1个以太可以兑换 100 GLD;
  2. 实现 buyTokens 函数,这个函数必须是 payable,可以接受发送的以太,计算对应的 GLD 数量,然后使用 transfer 将相应的 GLD 代币发送给购买者 msg.sender
  3. 触发一个 BuyTokens 事件,记录购买者,使用的 ETH 数量以及购买的 GLD 数量;
  4. 实现第二个函数 withdraw,用来将合约中的 ETH 全部提取到合约的所有者(owner)地址。我们可以使用两种方式设置合约的所有者:
    1. 部署时,使用我们能控制的钱包地址进行部署,并设置所有者;
    2. 使用任意地址部署,部署结束之后进行合约所有权转移;

在这个教程中,我们使用第二个方式,这样我们可以不用将我们控制的地址的私钥添加到项目配置中,降低暴露。

pragma solidity >=0.8.0 <0.9.0;
// SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/access/Ownable.sol";
import './YourToken.sol';

contract Vendor is Ownable {
  YourToken yourToken;
  uint256 public tokensPerEth = 100;

  // 购买代币事件
  event BuyTokens(address buyer, uint256 amountOfEth, uint256 amountOfTokens);

  constructor(address tokenAddress) public {
    yourToken = YourToken(tokenAddress);
  }

  // 允许用户使用 EHT 购买代币
  function buyTokens() payable public {
    // 检查是否有足够的 ETH
    require(msg.value > 0, "Not enought ether");

    uint256 amountOfTokens = msg.value * tokensPerEth;

    // 检查承销商是否有足够的代币
    uint256 tokenBalance = yourToken.balanceOf(address(this));
    require(tokenBalance > amountOfTokens, "Not enought tokens");
    
    // 发送代币
    bool sent =  yourToken.transfer(msg.sender, amountOfTokens);
    require(sent, "Failed to transfer token to the buyer");

    emit BuyTokens(msg.sender, msg.value, amountOfTokens);
  }

  // 允许所有者取出所有代币
  function withdraw() public onlyOwner {

    uint256 balance = address(this).balance;
    require(balance > 0, "No ether to withdraw");
    
    // 发送代币给所有者
    (bool sent, ) = msg.sender.call{value: balance}("");
    require(sent, "Failed to withdraw balance");
  }
    

  // ToDo: create a sellTokens() function:
}

其中, Ownable 可以进行权限控制,合约提供的onlyOwner修改器可以用来限制某些特定合约函数的访问权限。在这里,我们的 withdraw 函数必需限制合约的所有这才能提取所有的资金。同时,这个合约提供了 transferOwnership 函数,可以用来转移合约的所有者,这个将在我们的脚本部分中使用。

对于部署脚本,我们需要完成以下功能:

  1. 在部署的时候将所有的代币发送到承销商的合约地址 vendor.address ,而不是我们之前的地址;
  2. 为了能将承销商合约中的所有 ETH 提取出来,需要将合约的所有权 ownership 转移到我们能控制的地址,比如我们在前端使用的地址。

脚本位置: packages/hardhat-ts/deploy/01_deploy_vendor.ts

  // You might need the previously deployed yourToken:
  const yourToken = await ethers.getContract('YourToken', deployer);

  // 部署承销商合约
  await deploy('Vendor', {
    // Learn more about args here: https://www.npmjs.com/package/hardhat-deploy#deploymentsdeploy
    from: deployer,
    args: [yourToken.address],
    log: true,
  });
	// 获取部署的合约
  const vendor = await ethers.getContract('Vendor', deployer);

  // 发送 1000 个代币给承销商
  console.log('\n 🏵  Sending all 1000 tokens to the vendor...\n');
  await yourToken.transfer(vendor.address, ethers.utils.parseEther('1000'));

  // 转移所有权
  await vendor.transferOwnership('0x169841AA3024cfa570024Eb7Dd6Bf5f774092088');

部署合约

完成上述代码之后,我们重新部署我们的合约:

yarn deploy --reset

对应的输出结果为:

$ yarn deploy --reset

Compiling 7 files with 0.8.6
Generating typings for: 7 artifacts in dir: ../vite-app-ts/src/generated/contract-types for target: ethers-v5
Successfully generated 15 typings!
Compilation finished successfully
deploying "YourToken" (tx: 0x758e492bc71e9de37cf109aa6aa966fc6c042d086babce32ddd76af02ec22acb)...: deployed at 0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82 with 639137 gas
deploying "Vendor" (tx: 0x7b0402937081b72f59abb9994e3773b0283116e1106665766af31bf246b466cc)...: deployed at 0x9A676e781A523b5d0C0e43731313A708CB607508 with 482680 gas

 🏵  Sending all 1000 tokens to the vendor...

可以从命令行输出中看到合约部署的地址为:

  • 承销商合约地址: 0x9A676e781A523b5d0C0e43731313A708CB607508
  • 代币地址: 0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82

验证

我们通过以下步骤进行验证:

  1. 通过 Debug 页面查看承销商 (Vendor)合约地址初始时是否有 1000 个代币;

  2. 使用 0.1 ETH 购买 10 个 GLD:我们使用 Buy Tokens 功能购买 10 个代币,可以看到此时的价格约为 0.1 ETH(ETH 价格为 2766.7 美元)。

    buyTokens

  3. 将购买的代币发送给另一个账户:同样使用页面 Transfer Tokens 功能完成;

    tokenBalance

  4. 使用所有者账户,查看是否能全部取出合约中的 ETH:在 Debug 页面,我们使用 withdraw 功能,尝试将承销商合约中的 ETH 全部取出,可以看到,当交易完成以后,合约的余额变为了0:

    vendorBalanceBefore

    变为:

    vendorBalanceAfter

四、承销商合约 — 回购

接下来我们添加承销商合约的回购代币功能,也就是允许用户通过发送代币给承销商合约,承销商合约将对应的ETH发给用户账户。但是在以太坊中,合约只能通过 payable 接受 ETH,无法接受直接发送代币,如果直接向合约发送代币,代币将会永久消失。所以在 ERC20 标准中,我们需要使用 approvetranferFrom 者两个函数来完成这个过程。

approve(address spender, uint256 amount) -> bool
transferFrom(address from, address to, uint256 amount) -> bool

首先,用户通过调用 approve 函数授权承销商合约( spender )处理 amount 数量的代币,然后,调用 transferFrom 函数将代币从用户账户( from )转移 amount 数量的代币给承销商合约( to )。这其中的难点在于 approvetransferFrom 函数。我们来看一下这两个函数在 OpenZeppelin 中具体实现,首先是 approve

    mapping(address => mapping(address => uint256)) private _allowances;

		/**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

从上面可以看出, approve 函数调用了 _approve_approve 中用 _allowances 这个哈希记录了 ownerspender 之间的授权数量 amount。因此可以推断, transferFrom 函数以及其他需要授权情况的函数都使用了 _allowances 这个变量,比如 allowance 函数。

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Moves `amount` of tokens from `sender` to `recipient`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        _balances[to] += amount;

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

transferFrom 函数中,先使用 _spendAllowance 进行授权数量检查并更新授权数量,然后再使用 _transfer 进行代币划转,而 _spendAllowance 中正是调用了 allowance 这个函数。

合约实现

合约的函数实现如下:

...
  event SellTokens(address seller, uint256 amountOfTokens, uint256 amountOfETH);

...

  // 允许用户使用代币换回 ETH
  function sellTokens(uint256 amountToSell) public {
    // 价差是否合理
    require(amountToSell > 0, "Amount to sell must be greater than 0");
    
    // 检查用户是否有足够的代币
    uint256 userBalance = yourToken.balanceOf(msg.sender));
    require(userBalance >= amountToSell, "Not enought tokens");

    // 检查承销商是否有足够的 ETH
    uint256 amountOfEthNeeded = amountToSell / tokensPerEth;
    uint256 venderBalance = address(this).balance;
    require(amountOfEthNeeded <= venderBalance, "Not enought ether");

    // 用户发送代币给承销商
    bool sent =  yourToken.transferFrom(msg.sender, address(this), amountToSell);
    require(sent, "Failed to transfer tokens from seller to vender");

    // 承销商发送 ETH 给用户
    (bool sent, ) = msg.sender.call{value: amountOfEthNeeded}("");
    require(sent, "Failed to send ether from vender to seller");

    emit SellTokens(msg.sender, amountToSell, amountOfEthNeeded);
  }

部署合约

我们再次部署新的合约:

$ yarn deploy --reset
Compiling 7 files with 0.8.6

Generating typings for: 7 artifacts in dir: ../vite-app-ts/src/generated/contract-types for target: ethers-v5
Successfully generated 15 typings!
Compilation finished successfully
deploying "YourToken" (tx: 0xd087814faeb6a8f1a7205d443550419b68d252bcd071e30c7965844105b761ac)...: deployed at 0x68B1D87F95878fE05B998F19b66F4baba5De1aed with 639137 gas
deploying "Vendor" (tx: 0xafaf257948f8c87e0a836eac6e2bbc1ec38026a5c2a0dfc0f71823a4ace635fd)...: deployed at 0x3Aa5ebB10DC797CAC828524e59A333d0A371443c with 694098 gas

 🏵  Sending all 1000 tokens to the vendor...

此时,合约地址变为:

  • 承销商合约地址: 0x3Aa5ebB10DC797CAC828524e59A333d0A371443c
  • 代币地址: 0x68B1D87F95878fE05B998F19b66F4baba5De1aed

验证

验证过程需要包含两步:

  1. 先在 Debug 页面使用代币的 approve 允许承销商合约处理 10 个代币:

    approve

    编辑权限 中,我们可以查看到授权的代币数量:

    approveAmount

  2. 使用承销商的 sellTokens 将 10 个代币换成 ETH。如果上一步没有使用 approve 的话,程序会报错。

    sellTokens

到这一步,我们就完成了合约的编写。

五、部署到测试网络

我们将部署合约到测试网络中,使用的测试网络是 rinkeby

  1. 修改以下变量为 rinkeby
    1. packages/hardhat-ts/hardhat.config.tsdefaultNetwork 变量,
    2. packages/vite-app-ts/src/config/providersConfig.ts 中的 targetNetworkInfo 变量
  2. 查看可用账户: yarn account ,如果没有找到可用账户,则使用 yarn generate 生成;
  3. 使用 faucet.paradigm.xyz 获取一些测试用的的 ETH,可以使用对应的区块浏览器查看账户情况,比如 https://rinkeby.etherscan.io/,当我们完成测试用币的申请之后,我们可以看到账户余额为 0.1ETH;
  4. 再次使用 yarn deploy 进行合约部署:
$ yarn deploy
Nothing to compile
No need to generate any newer typings.
deploying "YourToken" (tx: 0xa7a89a2917cfa355d1305643dc89f54d776186c0059977b0a237737fa37dff62)...: deployed at 0x0F0D10eF3589cE896E9E54E09568cB7a5371e398 with 639137 gas
deploying "Vendor" (tx: 0x3a1f02b77de29704a16599067c8e10abb0da78e547ea0eea8200761da5d45715)...: deployed at 0xb335Fc61D759C041503dC17266575229E593DE17 with 694098 gas

 🏵  Sending all 1000 tokens to the vendor...

可以看到,合约部署成功,此时我们可以在线上测试网络查看到具体的合约部署情况:

并且部署完成了初始化代币分发和所有权转换。详情可以查看部署账户信息: https://rinkeby.etherscan.io/address/0xccb20d43f62f31dd94436f04a1e90d7d08569e57

六、发布

接下来,我们将发布我们的前端项目到 Surge (或者使用 s3, ipfs 上)。Surge.sh 提供了免费的网站的部署,对于我们的测试网站来时再合适不过。

  1. 编译前端项目: yarn build
  2. 将项目发布到 surge 上: yarn surge
$ yarn surge

   Welcome to surge! (surge.sh)
   Login (or create surge account) by entering email & password.

          email: qwh005007@gmail.com
       password: 

   Running as qwh005007@gmail.com (Student)

        project: ./dist
         domain: qiwihui-scaffold-2.surge.sh
         upload: [====================] 100% eta: 0.0s (83 files, 16080214 bytes)
            CDN: [====================] 100%
     encryption: *.surge.sh, surge.sh (57 days)
             IP: 138.197.235.123

   Success! - Published to qiwihui-scaffold-2.surge.sh

Surge 在运行命令的过程中就设置了账户名称,以及可以自定义域名:qiwihui-scaffold-2.surge.sh,当完成部署之后,我们就可以在浏览器中访问这个页面,和我们本地运行的结果是一致的。

七、合约验证

当我们向测试网络部署合约时,部署的是合约编译之后的字节码,合约源码不会发布。实际生产中,有时我们需要发布我们的源代码,以保证我们的代码真实可信。此时,我们就可以借助 etherscan 提供的功能进行验证。

  1. 首先,我们获取 etherscan 的 API key,地址为 https://etherscan.io/myapikey,比如 PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8

  2. 更新 packages/hardhat-ts/package.json 中对应的 api-key 参数:

    ...
        "send": "hardhat send",
        "generate": "hardhat generate",
        "account": "hardhat account",
        "etherscan-verify": "hardhat etherscan-verify --api-key PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8"
      },
    ...
  3. 由于项目中的一个 bug,需要在根目录下的 packages.json 中添加以下命令才能直接使用之后的命令:

    "verify": "yarn workspace @scaffold-eth/hardhat etherscan-verify",
  4. 运行 yarn verify --network rinkeby ,这个命令将通过 etherscan 接口进行合约验证,输出结果为:

    $ yarn verify --network rinkeby
    verifying Vendor (0xb335Fc61D759C041503dC17266575229E593DE17) ...
    waiting for result...
     => contract Vendor is now verified
    verifying YourToken (0x0F0D10eF3589cE896E9E54E09568cB7a5371e398) ...
    waiting for result...
     => contract YourToken is now verified
  5. 验证完成后,我们可以看到 etherscan 中的合约页面已经加上了一个蓝色小钩,在合约中,也可以看到我们合约的源代码:

    contractVerified

至此,我们就完成了合约的验证。

八、提交结果

最后,当我们完成上述的所有步骤之后,我们可以将我们的结果提交到 speedrunethereum.com 上,选择对应的挑战,并提交部署的前端地址和承销商合约的链接即可:

submitChallenge

Congratulations! 你已经完成了这个教程

总结

通过篇教程,我们可以学习到如下内容:

  1. 合约 approvetransferFrom 的使用;
  2. 如何使用 OpenZeppelin 创建 ERC20 代币;
  3. 创建承销商合约实现用户对代币的买卖;
  4. 在测试网路 Rinkeby 上部署合约;
  5. Surge.sh 上部署前端项目;
  6. 在 etherscan 上查看合约以及验证合约;
  7. 以及关于 web3 开发的知识,包括 hardhat,react 等。
@qiwihui qiwihui added the 区块链 区块链相关 label Feb 28, 2022
@qiwihui qiwihui added this to the 技术 milestone Feb 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
区块链 区块链相关
Projects
None yet
Development

No branches or pull requests

1 participant