互联网的技术日新月异,互联网不断深入人们的生活;
web3.0将是彻底改变人们生活的互联网形式;
web3.0使所有网上公民不再受到现有资源积累的限制;
具有更加平等地获得财富和声誉的机会。
web3.0会从哪里开始呢?
本合集文章,授权转载,侵权必究。
Web 3.0世界系列文章
来源 : 代码与野兽
【 Web3 系列文章 】
NO.1 遇见Web3: 在 Web3 的世界中写下第一行 HelloWorld
NO.2 全面系统的 Web3 学习路线,助你成为 Web3 开发专家
NO.3 Web3世界:区块链、比特币、以太坊和智能合约
NO.4 Remix IDE 使用与 VSCode 搭建 Solidity 开发环境
NO.5 深入聊聊 Web3 世界中的协议和硬盘:IPFS
NO.6 一文聊透 Solidity 语法:助你成为智能合约专家
NO.7 Web3世界:使用 React+ethers.js 开发简单加密钱包
NO.8 Web3世界:Web3为什么能赚钱?为什么不要All in Web3?
本文会讲解代币、ERC20 的基本概念,并完成一个包含智能合约、前端、测试、部署的完整 DApp 设计与实现。
非常适合 Web3 初学者朋友进行学习。
文中用到的一些主要的工具、框架及技术:
注意:文中不会涉及这些工具、框架、技术的基本安装及使用。
完成效果如下:
这里指的代币是以太坊平台的代币,其他平台的代币在概念上可能略有出入。
除了以太坊平台有代币的概念外,很多模仿以太坊的平台也都有代币的概念,比如 BSC 和 polygon 等。
代币,英文是 Token,顾名思义就是代表某种东西的货币。它可以表示任何东西:
像人民币一样的法币。
代币唯一不能代表的东西就是 gas 费。
代币除了代币以外,还有另一个名字,叫做通证。所谓通证,也就是通用的证明。你持有这个通证,就可以证明你拥有某件东西或者某项权益。这和中国成立初期的粮票、布票是一个道理。
现在我们明白了,虽然它有三种称呼,但其实是一个意思,文中通称为代币。
在代码的角度上,代币就是一份智能合约,它负责提供查账、转账、记账等功能,没有什么特殊。
最后扩展一点儿小知识。
比特币和以太币是代币吗?可以说是,但和通常意义上的代币还是有些区别。我们一般会称呼为比特币和以太坊这种自己拥有区块链的代币为「币」。而除了比特币以外的币,我们还会称为「替代币」。没有自己的区块链,依赖其他区块链的币,我们称为「代币」。这些名词是每一个玩币的人都应该清楚的。
ERC 是 Ethereum Request for Comment 的缩写,也就是以太坊改进建议。提交 ERC 后,以太坊社区会对这个草案进行评估,最终会接受或者拒绝该建议。
如果接受的话,ERC 会被确认为 EIP。
EIP 是 Ethereum Improvement Proposals 的缩写,也就是被接纳的以太坊改进建议。
ERC 是按照时间顺序从 1 开始递增的,ERC 20 就是第 20 个建议。
在讲 ERC20 之前,我们先来看下发行代币过程中存在的问题。
我们上面讲过,代币就是智能合约,智能合约就是代码。虽然代币合约都是做查账、转账、记账这几件主要的事情的,但在没有规范约束的情况下,每种代币的实现可能都是不同的。
比如 Pig 币的转账函数是 t,参数顺序是余额、收款人;Cat 币的转账函数是 tr,参数顺序是收款人、余额。虽然各自都是没问题的,但那这样很多应用来集成他们就变得非常麻烦了,这会导致有多少种代币就要集成多少次。特别是交易所和钱包这类应用。
ERC20 是关于代币的建议,由以太坊联合创始人 Vitalik 在 2015 年 6 月提出。它是一个简单的接口,允许开发者在以太坊区块链上发行自己的代币,并可以与第三方应用集成。
EIP 20 的地址:eips.ethereum.org/EIPS/eip-20
既然是接口,那就是一种规范约束。所有人都应该按照这个接口去实现自己的代币合约。
如果你不按照这个规范实现你的代币合约,那么你的代币在集成到第三方应用时就会无法识别,比如在 MetaMask 中不能正常显示代币名称、余额等。
代币的价值很依赖流通性,如果你的代币不能通用、不能流通的话,那么基本上就失去了代币的价值。
所以发行代币,要按照 ERC20 接口去实现合约。
截至本文写作时间(2023/1/4),以太坊上的 ERC20 代币有将近 74 万种,BSC 上的 ERC20 代币有将近 300 万种。从这些数字上足以看出 ERC20 对促进代币发展的过程中提供的重要作用。
插一句,BSC 就是币安智能链,因为以太坊交易的 gas 费太贵,BSC 就模仿以太坊做了它们自己的平台,但 gas 相比以太坊少很多,所以吸引了很多用户,后面顺理成章地发展起来了。
ERC20 接口规定了 9 个方法和 2 个事件。
方法:
function name() public view returns (string)
function symbol() public view returns (string)
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)
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)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
我们详细讲解一下它们的作用。
我们可以将它们分为三类,查询、转账、授权:
查询类方法有:name、symbol、decimals、totalSupply、balanceOf。
其中 name、symbol 和 decimals 都是可选的,因为它们没有具体的功能。但建议是要全部实现。
totalSupply 和 balanceOf 分别是发行总量和查余额,很容易理解。
转账方法有:transfer。
它可以将你账户上的代币转给另一个账户,也很容易理解。
授权方法有:transferFrom、approve 和 allowance。
这三个函数可能比较难理解。
我再举个例子来详细讲一下授权这块内容。
假设我是一家游戏平台(游戏平台也是一个地址,和用户没区别),玩家张三完成了我的任务,我奖励他 50 个代币。但我并不会直接转账到他的账户,而是在授权账本上记下来,玩家张三可以使用我的 50 个代币。
李四是个游戏商人,贩卖游戏道具。张三要在李四手里买一个价值 30 个代币的道具,就可以使用平台的代币支付给李四。而这个代币可能也不会直接打到李四的账户上,和上面的玩法一样,我也在授权账本上记下,允许李四使用我的 30 个代币,同时把张三原来那 50 个代币的账户改为 20 个代币。
这就是授权的玩法,对应的实现就是:
游戏平台给玩家授权代币:approve。
查询玩家在游戏平台的授权代币余额:allowance。
玩家使用平台授权的代币进行交易:transferFrom。
当然这只是我举例的一个场景,授权的玩法可以应用在更多的场景中。
实现 ERC20 是很多刚接触智能合约的小伙伴都需要学习的内容。
你可能需要使用 VSCode 或者 Remix 作为编辑器来写 Solidity 代码。这部分内容就不多讲了。
因为一些特殊的原因,这里我选择 VSCode。
我会使用 truffle 来创建项目。
它可以帮我们对合约进行编译和部署。
运行命令创建项目:
mkdir noth-token-contract
cd noth-token-contract
truffle init
创建 contracts/IERC20.sol 文件,定义 IERC20 接口。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address _owner) external view returns (uint256 balance);
function transfer(address _to, uint256 _value)
external
returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function approve(address _spender, uint256 _value)
external
returns (bool success);
function allowance(address _owner, address _spender)
external
view
returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
}
再创建 contracts/NoahToken.sol 文件,实现 IERC20 接口。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC20.sol";
contract NoahToken is IERC20 {
string private _name; // 代币名称
string private _symbol; // 代币代号
uint8 private _decimals; // 代币精度
uint256 private _totalSupply; // 代币发行总量
mapping(address => uint256) private _balances; // 账本
mapping(address => mapping(address => uint256)) private _allowance; // 授权记录
address public owner; // 合约发布者
constructor(
string memory _initName,
string memory _initSymbol,
uint8 _initDecimals,
uint256 _initTotalSupply
) {
// 发布合约时设置代币名称、代号、精度和发行总量
_name = _initName;
_symbol = _initSymbol;
_decimals = _initDecimals;
_totalSupply = _initTotalSupply;
owner = msg.sender;
// 在合约部署时把所有的代币发行给合约发布者
_balances[owner] = _initTotalSupply;
}
function name() external view override returns (string memory) {
return _name;
}
function symbol() external view override returns (string memory) {
return _symbol;
}
function decimals() external view override returns (uint8) {
return _decimals;
}
function totalSupply() external view override returns (uint256) {
return _totalSupply;
}
function balanceOf(address _owner)
external
view
override
returns (uint256 balance)
{
return _balances[_owner];
}
function transfer(address _to, uint256 _value)
external
override
returns (bool success)
{
// 检查发送者余额是否足够
require(_balances[msg.sender] >= _value, "Insufficient balance");
// 扣除发送者余额
_balances[msg.sender] -= _value;
// 增加接收者余额
_balances[_to] += _value;
// 触发转账事件
emit Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(
address _from,
address _to,
uint256 _value
) external override returns (bool success) {
// 检查发送者余额是否足够
require(_balances[_from] >= _value, "Insufficient balance");
// 检查授权额度是否足够
require(
_allowance[_from][msg.sender] >= _value,
"Insufficient allowance"
);
// 扣除发送者余额
_balances[_from] -= _value;
// 增加接收者余额
_balances[_to] += _value;
// 扣除授权额度
_allowance[_from][msg.sender] -= _value;
// 触发转账事件
emit Transfer(_from, _to, _value);
return true;
}
function approve(address _spender, uint256 _value)
external
override
returns (bool success)
{
// 设置授权额度
_allowance[msg.sender][_spender] = _value;
// 触发授权事件
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender)
external
view
override
returns (uint256 remaining)
{
return _allowance[_owner][_spender];
}
}
具体代码的作用我都加到注释中了,就不再多赘述。
在合约编写完成之后,我们需要在本地进行测试、编译、部署。
truffle 支持通过代码对智能合约进行测试。目前支持 JavaScript 和 Solidity 两种语言,但 JavaScript 更灵活,也更流行。这里选择 JavaScript 进行测试。
创建 test/token.js 文件,该文件是测试文件。
truffle 使用 Mocha 和 Chai 这两个库作为断言库,但略有不同。
首先应该使用 contract 函数而不是 describe 函数。
contract 函数会传递一个默认参数,它会提供一组可用的账户。
const NoahToken = artifacts.require("NoahToken");
contract("Token", (accounts) => {
const [alice, bob] = accounts;
it("balanceOf", async () => {
// 发 Noah 币,发行 1024 个
const noahTokenInstance = await NoahToken.new('noah', 'NOAH', 0, '1024', { from: alice });
// 查看 alice 的余额是否是 1024
const result = await noahTokenInstance.balanceOf(alice);
assert.equal(result.valueOf().words[0], 1024, "1024 wasn't in alice");
});
it("transfer", async () => {
// 发 Noah 币,发行 1024 个
const noahTokenInstance = await NoahToken.new('noah', 'NOAH', 0, '1024', { from: alice });
// alice 将 1 个 Noah 币转给 bob
await noahTokenInstance.transfer(bob, 1, { from: alice });
// 查看 alice 的余额是否是 1023
let aliceBalanceResult = await noahTokenInstance.balanceOf(alice);
assert.equal(aliceBalanceResult.valueOf().words[0], 1023, "1023 wasn't in alice");
// 查看 bob 的余额是否是 1
let bobBalanceResult = await noahTokenInstance.balanceOf(bob);
assert.equal(bobBalanceResult.valueOf().words[0], 1, "1 wasn't in bob");
// bob 将 1 个 Noah 币转给 alice
await noahTokenInstance.transfer(alice, 1, { from: bob });
// 查看 alice 的余额是否是 1024
aliceBalanceResult = await noahTokenInstance.balanceOf(alice);
assert.equal(aliceBalanceResult.valueOf().words[0], 1024, "1024 wasn't in alice");
// 查看 bob 的余额是否是 0
bobBalanceResult = await noahTokenInstance.balanceOf(bob);
assert.equal(bobBalanceResult.valueOf().words[0], 0, "0 wasn't in bob");
});
});
代码中有详尽的注释,就不多赘述了。其他 function 也可以用这种方式进行测试。
编写完成后运行命令:
truffle test ./test/token.js
全部 pass 即可通过。
除了代码测试外,我们通常还需要将合约部署到开发环境,和前端代码进行集成联调。
我使用 ganache 部署智能合约,它会在本地运行一个区块链。
首先在项目中对 truffle-config.js 文件进行修改,添加 development 环境的相关配置。
{
"network": {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
}
}
同时创建一个 migrations/1_NoahToken_migration.js 文件,用于部署。
内容如下:
const NoahToken = artifacts.require("NoahToken");
module.exports = function (deployer) {
deployer.deploy(NoahToken, 'noah', 'NOAH', 18, '1024000000000000000000');
}
deployer.deploy 的第一个参数是合约,其余的参数是部署合约传递的参数。
最后运行 truffle 的编译部署脚本:
truffle migrate --network development --f 1
network 参数是指定的网络环境,truffle 会将合约部署到指定的网络。
f 参数是指定的部署文件名前缀,truffle 会从这个文件开始迁移。
稍等片刻就可以在 ganache 的 contracts 中就可以看到这个合约的地址了。
点进去就可以看到合约的详细信息。
不过需要注意,ganache 中数字是以十六进制形式进行展示,所以 decimals 和 totalSupply 和我们传入的十进制数字不匹配。
另一个注意事项是:ganache 中的 mapping 一直存在显示问题,永远都显示 0 items。这个 Bug 存在时间超过了一年。记得我刚用 Ganache 的时候,一度怀疑是我的合约写得有问题,在这个问题上折腾了一天,记忆犹新。遗憾的是,一年多过去了,ganache 还没有修复这个 Bug。
在测试之前,我们先要配置网络。
配置信息如下图所示:
接下来我们要把代币添加到 MetaMask 中。
在这一步就可以看到代币余额了。
不过本地的链在 MetaMask 中不能正常显示,部署到链上时是正常的。
前端使用了很多库,我们先来安装这些库。
运行命令创建项目:
npx create-next-app
项目名看你的喜好;编程语言选择 TypeScript。
与智能合约交互的 SDK 使用 wagmi 和 ethersjs,安装依赖:
npm i wagmi ethers
UI 组件库选择 chakra,安装依赖:
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
CSS 框架选择 tailwindcss,安装依赖:
npm install -D tailwindcss postcss autoprefixer
初始化配置。
npx tailwindcss init -p
修改 tailwind.config.js 的内容。
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
修改 styles/globals.css 的内容。
@tailwind base;
@tailwind components;
@tailwind utilities;
这些配置比较烦琐,更多内容可以参考官方文档。
nextjs 会默认打开 React 的严格模式,但我们用不到,需要关闭。
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
}
module.exports = nextConfig
这是我们代币的操作页面。
export default function NoahToken() {
return hello, noah token!
}
接下来我们需要配置 wagmi 的相关配置。但在那之前我们需要关闭 SSR。
nextjs 的页面默认都是开启 SSR 的,这会导致下面这个 Error。
触发这个 Error 的原因是服务端的 UI 和客户端不一致。
关闭服务端渲染很简单。
import dynamic from "next/dynamic";
export default dynamic(() => Promise.resolve(NoahToken), { ssr: false });
代码分为几个组件:
代码量较多,不在这里做更多分析。后续考虑单独写一篇专门介绍 wagmi 的文章。
Goeril 是目前最流行的测试网络之一,接下来我们会把 Noah 币部署到这个网络上。
要部署到 Goeril 首先需要在app.infura.io/ 上面创建一个项目。
然后可以获取到 API Key。
回到合约项目中。
安装两个包:
npm i @truffle/hdwallet-provider dotenv
dotenv 用于读取环境变量。
创建 .env 文件,写入以下内容:
PRIVATE_KEY="xxx"
PROJECT_ID="xxx"
回到 truffle-config.js 文件,添加 goerli 相关配置:
require('dotenv').config();
const { PRIVATE_KEY, PROJECT_ID } = process.env;
const HDWalletProvider = require('@truffle/hdwallet-provider');
module.exports = {
// ...
networks: {
// ...
goerli: {
provider: () => new HDWalletProvider(PRIVATE_KEY, `https://goerli.infura.io/v3/${PROJECT_ID}`),
network_id: 5, // Goerli's id
confirmations: 2, // # of confirmations to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
}
}
}
最后运行命令:
truffle migrate --network goerli --f 1
稍等片刻,部署成功。
将 DApp 部署到 Vercel
部署之前需要将合约地址配置到 vercel 的环境变量中。
由于之前我就配置好了 Vercel,而且这部分不是重点,就不展开讲了。
当然,在实际工作中,并不会真正从零实现一个 ERC20 的代币合约,通常会使用 OpenZepplin 这种库来一键发币。本文教学目的是以学习为主。
只懂得如何发行代币还不够,我们还需要知道如何推广币。推广币最简单的方式就是发布一个免费领币的网站,因为互联网上最不缺的就是羊毛党。
后面我也会写一篇文章介绍如何开发免费领币网站,也就是水龙头网站。
Web3 是未来世界一大变数,我们想帮助更多人了解并加入 Web3,如果你对 Web3 感兴趣,记得关注我~
一起沉淀、一起成长、一起拥抱未来。
#头条创作挑战赛# #web3#
页面更新:2024-04-25
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号