0x1 概述
我在入门以太坊智能合约开发时,首先我就面临了一个选择,Hardhat Vs Truffle Vs Remix,我应该选择哪个开发工具。我就在谷歌上搜索很多对比,其中霍利维尔·瓦尔迪兹 的 《Hardhat Vs Truffle Vs Remix - Which Is The Best For Blockchain Development?》
这篇文章很及时的帮助了我,并且OpenZeppelin、Aave、BitGo、1inch等都在使用该开发工具,而且我在深入了解官方文档后发现文档整体很清晰并且有条理,而我又有一定的前端开发经验,所以在开发工具的选型上我选择了 Hardhat 。如果你没有前端经验,希望在前期学习的时候更关注合约本身,那么我会推荐 Remix 作为前期学习的开发工具。
这是我基于 Hardhat 官方教程写的第一篇以太坊合约和 dApp 开发指南,未来我也会持续分享所学。
0x2 搭建环境
首先 Hardhat 是基于 JavaScript 编写的,所以我们首先需要安装 Node.js 环境。
安装 Node.js
如果你已经安装了 Node.js 你可以跳过这个部分。如果没有,接下来会介绍如何在 Ubuntu、MacOS和 Windows 上安装它。
Linux
Ubuntu
将以下命令复制粘贴到终端:
sudo apt update
sudo apt install curl git
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt install nodejs
MacOS
确保你已经安装了 git ,如果没有安装可以参考 atlassian 写的 《Install Git》。
在 MacOS 上安装 Node.js 有多种方法。我选择了使用 Node 版本管理器(nvm)。将以下命令复制粘贴到终端:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
nvm install --lts
nvm use stable
nvm alias default stable
npm install npm --global # 升级 npm 到最新版本
Windows
Windows 可以直接到 Node.js 官网,下载对应的安装包即可完成安装。
0x3 创建一个新的项目
我们将使用 npm CLI 来安装 Hardhat。
打开终端并运行以下命令:
mkdir hardhat-tutorial
cd hardhat-tutorial
npm init --yes
npm install --save-dev hardhat
安装 Hardhat 的过程中会安装 Ethereum JavaScript 依赖项,所以请耐心等待。
在安装 Hardhat 的同一目录下运行:
npx hardhat
使用键盘选择 Create an empty hardhat.config.js 并按下回车键。
$ npx hardhat
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.9.1
? What do you want to do? …
Create a basic sample project
Create an advanced sample project
Create an advanced sample project that uses TypeScript
❯ Create an empty hardhat.config.js
Quit
当 Hardhat 运行时,它会从当前工作目录开始搜索最近的 hardhat.config.js
文件。这个文件通常在你的项目根目录,一个空的 hardhat.config.js
就足以让 Hardhat 工作。未来我们对 Hardhat 的全部设置都会在这个文件中。
Hardhat 架构
Hardhat 基于任务和插件的理念设计。而 Hardhat 的大部分功能都来自插件,作为开发人员,你可以自由选择要使用的插件。
任务
每次从 CLI 运行 Hardhat 时,你都在运行一项任务。例如 npx hardhat compile
命令就是运行 compile
任务。要查看项目当前可用的任务,只执行 npx hardhat
。你也可以通过帮助命令了解其他任务 npx hardhat help [task]
。
当然我们也可以创建自定义的任务,参考创建任务。
插件
在你最终使用什么工具方面,Hardhat 并不会限制你,但它确实带有一些内置的默认设置。所有这些也都可以被重写。
在本文中,我们将使用 Ethers.js 和 Waffle 插件。它们将允许你与以太坊交互并测试你的合约。稍后我们将解释它们是如何使用的。要安装它们,你只需在项目目录中运行以下命令:
npm install --save-dev @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-waffle ethereum-waffle chai
我们需要添加以下代码
require("@nomiclabs/hardhat-waffle");
到如下位置,看起来会是这样:
require("@nomiclabs/hardhat-waffle");
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.7.3",
};
我们只需要引用 hardhat-waffle
。
0x4 编写与编译合约
我们将创建一个简单的智能合约来实现代币转移。代币合约最常用于交换和存储价值。我们不会在本文中展开详细谈考合约中的 Solidity 代码,但是我们实现了一些你应该之大的逻辑:
- 代币的总供应量时固定的,无法被改变
- 整个供应量分配到部署合约的地址
- 任何人都可以收到代币
- 任何拥有至少一个代币的人都可以转移代币
代币是不可分割的。你可以转移1、2、3或者37个代币,但不能转移2.5个
你可能听说过 ERC20,它是以太坊中的代币标准。DAI、USDC、MKR和ZRX等代币遵循 ERC20 标准,这使得它们都可以与任何可以处理 ERC20 代币的软件兼容。为简单起见,我们要构建的代币没有遵循 ERC20。
编写智能合约
首先创建一个名为新目录
contracts
,并在该目录中创建一个名为Token.sol
。
将下面的代码粘贴到该文件中,花一点时间阅读代码。它很简单,并且充满了解释 Solidity 基础知识的备注。要获得语法高亮,你应该在文本编辑器中添加 Solidity 语言的支持。只需寻找 Solidity 或 Ethereum 插件。我们建议使用 Visual Studio Code 或 Sublime Text 3。
// Solidity 文件 第一行代码都会是 pragma // Solidity 编译器将使用它来验证对应的版本 pragma solidity ^0.7.0; // 这是智能合约的主要组成部分 contract Token { // 一些字符串类型变量来标识代币 // `public` 修饰符使变量在合约外部可读 string public name = "My Hardhat Token"; string public symbol = "MHT"; // 存储在无符号整型变量中的固定数量代币 uint256 public totalSupply = 1000000; // 地址类型变量用于存储以太坊账户 address public owner; // `mapping` 是一个键/值映射。我们在这里存储每个帐户余额 mapping(address => uint256) balances; /** * 合约初始化 * * `constructor` 只在合约创建时执行 */ constructor() { // totalSupply 被分配给交易发送方,即部署合约的帐户 balances[msg.sender] = totalSupply; owner = msg.sender; } /** * 传递代币的函数 * * `external` 修饰符使函数只能从合约外部调用 */ function transfer(address to, uint256 amount) external { // 检查交易发送方是否有足够的代币 // 如果 `require` 的第一个参数计算结果为 `false``,则整个交易会恢复 require(balances[msg.sender] >= amount, "Not enough tokens"); // 转移金额 balances[msg.sender] -= amount; balances[to] += amount; } /** * 读取给定帐户的代币余额 * * `view` 修饰符表示它不修改合约的状态,这允许我们在不执行交易的情况下调用它 */ function balanceOf(address account) external view returns (uint256) { return balances[account]; } }
*.sol
是 Solidity 的文件类型。我们建议将文件名与其包含的合约相匹配,这是一个常见的做法。编译合同
编译合约,在你的终端中执行
npx hardhat compile
。该compile
任务是内置任务之一。$ npx hardhat compile Compiling 1 file with 0.7.3 Compilation finished successfully
合约已经编译成功,现在你就可以使用了。
0x5 测试合约
在构建智能合约时编写自动化测试是至关重要的,因为我们的代码将直接操作用户的钱!为此,我们将使用 Hardhat 网络,这是一个为开发而设计的本地以太坊网络,它内置在 Hardhat 工具中,并且是默认网络。你无需设置任何内容即可使用它。在我们的测试中,我们将使用 ethers.js 与我们在上节中构建的以太坊合约进行交互,并且使用 Mocha 作为我们的测试运行时。
编写测试
在我们的项目根目录中创建名为test
的目录,并在里面创建一个文件名为Token.js
的文件。
我们从下面的代码开始。我们将在接下来解释其中的含义,但现在你只需将其粘贴到 Token.js
中:
const { expect } = require("chai");
describe("Token contract", function () {
it("Deployment should assign the total supply of tokens to the owner", async function () {
const [owner] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const hardhatToken = await Token.deploy();
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
在你的终端上运行 npx hardhat test
,你应该看到以下输出:
npx hardhat test
Token contract
✓ Deployment should assign the total supply of tokens to the owner (654ms)
1 passing (663ms)
这意味着测试通过了。现在让我们解释每一行:
const [owner] = await ethers.getSigners();
Signer
代表 ethers.js 中以太坊的对象。 它用于向合约和其他账户发送交易。在这里,我们得到了所连接节点中的账户列表,在本例中时Hardhat 网络,并且只保留第一个。
该ethers
变量在全局范围内可用。你可以在顶部添加这么一行:
const { ethers } = require("hardhat");
要了解更多关于Singer
的信息,你可以查看 Signers 文档。
const Token = await ethers.getContractFactory("Token");
在 ethers.js 中,ContractFactory
是一个用于部署新智能合约的抽象,所以这里的 Token
是我们的代币合约工厂的实例。
const hardhatToken = await Token.deploy();
在 ContractFactory 上调用deploy()
将开始部署,并返回Contract
解析后的 Promise
。这是为你的每个智能合约功能提供方法的对象。
const ownerBalance = await hardhatToken.balanceOf(owner.address);
一旦部署了合约,我们就可以在hardhatToken
上调用我们的合约方法,并通过调用balanceOf()
来获取相应所有者帐户的余额。
请记住,获得整个供应量的代币所有者是进行部署的账户,当使用hardhat-ethers
插件时,ContractFactory
和Contract
实例默认连接到第一个签名者。这意味着owner
变量中的账户执行了部署,而balanceOf()
应该返回整个供应量。
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
在这里,我们再次使用我们的Contract
实例来调用 Solidity 代码中的智能合约函数。totalSupply()
返回代币的供应量,我们检查它是否等于ownerBalance
,正如它应该做的那样。
为了做到这一点,我们使用了 Chai,这是一个断言库。这些断言函数被称为 "匹配器",而我们在这里使用的函数实际上来自 Waffle。这就是为什么我们要使用hardhat-waffle
插件,它使我们更容易从Ethereum 断言值。请查看 Waffle 文档中的这一节,了解整个以太坊特定匹配器的列表。
使用其他帐户
如果你需要从默认账户(或ethers.js中的Signer
)以外的账户发送交易来测试你的代码,你可以使用ethers.js Contract
中的connect()
方法将其连接到一个不同的账户。像这样:
const { expect } = require("chai");
describe("Transactions", function() {
it("Should transfer tokens between accounts", async function() {
const [owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const hardhatToken = await Token.deploy();
// 从所有者转移50个代币到addr1
await hardhatToken.transfer(addr1.address, 50);
expect(await hardhatToken.balanceOf(addr1.address)).to.equal(50);
// 从addr1转移50个代币到addr2
await hardhatToken.connect(addr1).transfer(addr2.address, 50);
expect(await hardhatToken.balanceOf(addr2.address)).to.equal(50);
});
});
完全覆盖
现在我们已经介绍了测试合约所需的基础知识,这里有一个完整的代币测试套件,其中包含有关 Mocha 以及如何构建测试的大量附加信息。我们建议通读。
// 我们在这里导入Chai以使用它的断言函数
const { expect } = require("chai");
// `describe` 是一个Mocha函数,它允许您组织测试
// 实际上并不需要这样做,但是组织测试会使调试变得更容易。所有的Mocha函数都可以在全局作用域中使用
// `describe` 参数为一个测试名称和一个回调函数
// 回调必须定义该部分的测试。这个回调函数不能是一个异步函数
describe("Token contract", function () {
// Mocha 有四个函数可以让你能在整个测试生命周期中进行不同的操作,
// 它们是: `before`, `beforeEach`, `after`, `afterEach`
// 它们在设置测试环境和运行后清理环境方面非常有用
// 一个常见的模式是声明一些变量,并在 `before` 和 `beforeEach` 回调中分配它们
let Token;
let hardhatToken;
let owner;
let addr1;
let addr2;
let addrs;
// `beforeEach` 将在每次测试之前运行,每次重新部署合约。它接收回调,可以是异步的
beforeEach(async function () {
// 在这里获取 ContractFactory 和签名者
Token = await ethers.getContractFactory("Token");
[owner, addr1, addr2, ...addrs] = await ethers.getSigners();
// 要部署我们的合约,只需调用 Token.deploy() 并等待它被 deployed(),一旦它的交易被挖掘,就会发生部署
hardhatToken = await Token.deploy();
});
// 你可以嵌套 describe 调用来创建小结
describe("Deployment", function () {
// `it` 是 MoCha 函数,这是用来定义测试的工具。它接收测试名和回调函数
// 如果回调函数是一步的,Mocha 将 `await`
it("Should set the right owner", async function () {
// Expect 接收一个值,并将其包装在Assertion对象中。这些对象有很多实用方法来断言值
// 这个测试期望存储在合约中的所有者变量等于我们部署合约时候的所有者
expect(await hardhatToken.owner()).to.equal(owner.address);
});
it("Should assign the total supply of tokens to the owner", async function () {
const ownerBalance = await hardhatToken.balanceOf(owner.address);
expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
});
});
describe("Transactions", function () {
it("Should transfer tokens between accounts", async function () {
// 将50个代币从所有者转移到 addr1
await hardhatToken.transfer(addr1.address, 50);
const addr1Balance = await hardhatToken.balanceOf(addr1.address);
expect(addr1Balance).to.equal(50);
// 将50个代币从 addr1 转移到 addr2
// 我们使用.connect(签名者)从另一个帐户发送交易
await hardhatToken.connect(addr1).transfer(addr2.address, 50);
const addr2Balance = await hardhatToken.balanceOf(addr2.address);
expect(addr2Balance).to.equal(50);
});
it("Should fail if sender doesn’t have enough tokens", async function () {
const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);
// 尝试从 addr1 (0 代币)发送1个代表给所有者(1000000 代币)。
// `require`将计算为 false 并恢复交易
await expect(
hardhatToken.connect(addr1).transfer(owner.address, 1)
).to.be.revertedWith("Not enough tokens");
// 所有者余额不应该改变
expect(await hardhatToken.balanceOf(owner.address)).to.equal(
initialOwnerBalance
);
});
it("Should update balances after transfers", async function () {
const initialOwnerBalance = await hardhatToken.balanceOf(owner.address);
// 将100个代币从所有者转移到 addr1
await hardhatToken.transfer(addr1.address, 100);
// 将另外50个代币从所有者转移到addr2
await hardhatToken.transfer(addr2.address, 50);
// 检查余额
const finalOwnerBalance = await hardhatToken.balanceOf(owner.address);
expect(finalOwnerBalance).to.equal(initialOwnerBalance.sub(150));
const addr1Balance = await hardhatToken.balanceOf(addr1.address);
expect(addr1Balance).to.equal(100);
const addr2Balance = await hardhatToken.balanceOf(addr2.address);
expect(addr2Balance).to.equal(50);
});
});
});
这就是npx hardhat test
完整测试套件的输出,是这样的:
$ npx hardhat test
Token contract
Deployment
✓ Should set the right owner
✓ Should assign the total supply of tokens to the owner
Transactions
✓ Should transfer tokens between accounts (199ms)
✓ Should fail if sender doesn’t have enough tokens
✓ Should update balances after transfers (111ms)
5 passing (1s)
请记住,当你运行npx hardhat test
时,如果你的合约自上次运行测试以来发生更改,则将编译它们。
0x6 使用 Hardhat 网络进行调试
Hardhat 内置了Hardhat 网络,这是一个专为开发而设计的本地以太坊网络。它允许你部署合约、运行测试和调试代码。这是 Hardhat 连接的默认网络,因此你无需进行任何设置即可使其正常工作。只需运行你的测试。
Solidity console.log
在 Hardhat 网络上运行合约和测试时,你可以在 Solidity 代码中使用console.log()
来打印日志消息与合约变量。要使用它,你必须在你的合约代码中导入 hardhat/console.sol
。
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Token {
//...
}
像在 JavaScript 中使用console.log
那样在transfer()
函数添加一些内容:
function transfer(address to, uint256 amount) external {
console.log("Sender balance is %s tokens", balances[msg.sender]);
console.log("Trying to send %s tokens to %s", amount, to);
require(balances[msg.sender] >= amount, "Not enough tokens");
balances[msg.sender] -= amount;
balances[to] += amount;
}
运行测试时将显示日志输出:
$ npx hardhat test
Token contract
Deployment
✓ Should set the right owner
✓ Should assign the total supply of tokens to the owner
Transactions
Sender balance is 1000 tokens
Trying to send 50 tokens to 0xead9c93b79ae7c1591b1fb5323bd777e86e150d4
Sender balance is 50 tokens
Trying to send 50 tokens to 0xe5904695748fe4a84b40b3fc79de2277660bd1d3
✓ Should transfer tokens between accounts (373ms)
✓ Should fail if sender doesn’t have enough tokens
Sender balance is 1000 tokens
Trying to send 100 tokens to 0xead9c93b79ae7c1591b1fb5323bd777e86e150d4
Sender balance is 900 tokens
Trying to send 100 tokens to 0xe5904695748fe4a84b40b3fc79de2277660bd1d3
✓ Should update balances after transfers (187ms)
5 passing (2s)
查看文档以了解有关此功能的更多信息。
0x7 部署到真实网络
一旦你准备好与其他人分享你的 dApp,你可能想要做的就是部署到真实的网络。这样,其他人就可以访问你的 dApp了。
涉及真钱交易的以太坊网络,被称为“mainnet”,然而还有其他不与真钱交易的网络,但却能很好的模拟现实世界的场景,也可以被其他人访问的网络称为“testnets”。以太坊有多个测试网:Ropsten、Kovan、Rinkeby和Goerli。我们建议将合约部署到 Ropsten 测试网。
在软件层面,部署到测试网与部署到主网是一样的。唯一的区别是你连接到哪个网络。让我们看看使用ether.js部署合约的代码是什么样子的。
使用的主要概念是Signer
、ContractFactory
和Contract
,我们在测试部分中解释过。与测试相比,没有什么新事情需要做,因为当在测试合约时,实际上是在向你的开发网络进行部署。这使得代码非常相似。
让我们在项目根目录中创建一个新的目录 scripts
,并将以下内容粘贴到deploy.js
文件中:
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
console.log("Account balance:", (await deployer.getBalance()).toString());
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy();
console.log("Token address:", token.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
要在运行任何任务时指定 Hardhat 连接到特定的以太坊网络,可以使用--network
参数。像这样:
npx hardhat run scripts/deploy.js --network
在这种情况下,不带--network
参数运行它会使代码针在Hardhat 网络的嵌入式实例运行,因此当 Hardhat 完成运行时,部署实际上会丢失,但测试我们的部署代码是否有效仍然很有用:
$ npx hardhat run scripts/deploy.js
Deploying contracts with the account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Account balance: 10000000000000000000000
Token address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
部署到远程网络
要部署到远程网络(例如主网或任何测试网),你需要在hardhat.config.js
文件中添加一个network
条目。我们将在此示例中使用 Ropsten,但你也可以以类似地方式添加任何网络:
require("@nomiclabs/hardhat-waffle");
// 登录https://www.alchemyapi.io,注册,
// 在其仪表板中创建一个新的应用程序,并将“KEY”替换为其密钥
const ALCHEMY_API_KEY = "KEY";
// 从 Metamask 导出你的私钥,打开Metamask,并进入帐户详细信息>导出私钥
// 千万注意,永远不要把真实的Ether转到测试帐户
const ROPSTEN_PRIVATE_KEY = "YOUR ROPSTEN PRIVATE KEY";
module.exports = {
solidity: "0.7.3",
networks: {
ropsten: {
url: `https://eth-ropsten.alchemyapi.io/v2/${ALCHEMY_API_KEY}`,
accounts: [`${ROPSTEN_PRIVATE_KEY}`]
}
}
};
我们正在使用Alchemy,但指向任何以太坊节点或网关的url
都可以工作。
要部署在Ropsten网络上,你需要将Ropsten-ETH发送到将要进行部署的地址。你可以从测试网的水龙头上得到一些ETH,这是一种免费分发测试ETH的服务。这是Ropsten的一个水龙头,你必须在交易之前将Metamask的网络更改为Ropsten。
最后,运行:
npx hardhat run scripts/deploy.js --network ropsten
如果一切顺利,你应该会看到已部署的合约地址。
0x8 模版项目
如果你想快速开始使用你的dApp,亦或者想通过前端查看整个项目,你可以使用以下demo项目库。
https://github.com/nomiclabs/...
包括些什么
- 我们在本教程中使用的 Solidity 合约
- 使用 ethers.js 和 Waffle 的测试套件
使用 ethers.js 与合约交互的最小前端
Solidity 合约和测试
在 repo 的根目录中,您会找到我们通过本教程与合约一起构建的Hardhat项目。
Token
刷新你对它实现的记忆:- 代币的总供应量是固定的,无法更改
- 整个供应分配到部署合约的地址
- 任何人都可以收到代币
- 任何拥有至少一个代币的人都可以转移代币
代币是不可分割的。你可以转移 1、2、3 或 37 个代币,但不能转移 2.5 个
前端应用
你会发现一个简单的应用程序,
frontend
它允许用户做两件事:- 检查已连接钱包的余额
将代币发送到地址
这是一个单独的 npm 项目,它是使用create-react-app
创建的,所以这意味着它使用了webpack
和babel
。前端文件架构
src/
`包含所有代码src/components
包含反应组件Dapp.js
是唯一具有业务逻辑的文件。如果你将其用作模版,你可以在此处用你自己的代码替换- 其他所有组件都只呈现 HTML,没有逻辑。
src/contracts
有合约的 ABI 和地址,这些是由部署脚本自动生成的
如何使用它
首先克隆该库,然后部署合约:
cd hardhat-hackathon-boilerplate
npm install
npx hardhat node
在这里,我们只安装 npm 项目的依赖项,并通过运行npx hardhat node
启动一个Hardhat 网络实例,你可以使用 MetaMask 连接到该实例。在同一目录中的不同终端中,运行:
npx hardhat --network localhost run scripts/deploy.js
这会将合约部署到Hardhat网络。完成后运行:
cd frontend
npm install
npm run start
启动react web app。打开http://localhost:3000/ ,你应该会看到:
在 MetaMask 中将你的网络设置为localhost:8545
。 然后可能还需要配置 MetaMask 以与 Hardhat 配合使用。为此,请转到Settings -> Networks -> Localhost 8545
并将链 ID 更改为 31337。
这里发生的情况是显示当前钱包余额的前端代码检测到余额为0,因此你将无法尝试转账功能。通过运行:
npx hardhat --network localhost faucet
这将运行我们包含在Hardhat中的自定义任务,该任务使用部署帐户的余额向你的地址发送 100 MBT 和 1 ETH。这将允许你将代币发送到另一个地址。
你可以查看/tasks/faucet.js
中的任务代码。
$ npx hardhat --network localhost faucet 0x0987a41e73e69f60c5071ce3c8f7e730f9a60f90
Transferred 1 ETH and 100 tokens to 0x0987a41e73e69f60c5071ce3c8f7e730f9a60f90
在你运行npx hardhat node
的终端中,你也应该看到:
eth_sendTransaction
Contract call: Token#transfer
Transaction: 0x460526d98b86f7886cd0f218d6618c96d27de7c745462ff8141973253e89b7d4
From: 0xc783df8a850f42e7f7e57013759c285caa701eb6
To: 0x7c2c195cd6d34b8f845992d380aadb2730bb9c6f
Value: 0 ETH
Gas used: 37098 of 185490
Block #8: 0x6b6cd29029b31f30158bfbd12faf2c4ac4263068fd12b6130f5655e70d1bc257
console.log:
Transferring from 0xc783df8a850f42e7f7e57013759c285caa701eb6 to 0x0987a41e73e69f60c5071ce3c8f7e730f9a60f90 100 tokens
在我们的合约中显示transfer()
函数的console.log
输出,这是运行水龙头任务后web应用程序的样子:
试着调试它并阅读代码。它充满了注释,解释正在发生的事情,并清楚地表明哪些代码是以太坊模版代码,哪些实际上是dApp逻辑。这将使该项目易于为你的项目重用。
0x9 结束语
恭喜你完成了本教程!
以下是一些补充链接,让你更深入了解: