本文为开发者提供了一个详细的指南,介绍了如何创建一个ERC-20 Token Factory dApp。指南覆盖了从Factory设计模式的理论到具体的智能合约代码实现和部署步骤,使用了OpenZeppelin和Foundry等工具,并提供了前端的开发指导。适合需要创建自定义ERC-20代币的开发者,以实现数字资产的生成和管理。
info
本指南基于我们在 QuickNode Guide Examples GitHub 仓库中的代码: EVM Token Factory。
在 web3 中,代币化正在增长,我们看到越来越多的企业和零售用户想要创建自己的数字资产。无论是稳定币、治理(DAO)代币还是恶搞币,它们都通常使用 ERC-20 标准。
在本指南中,我们将向你展示如何创建你的 Token Factory dApp,并允许用户通过几次点击创建和自定义他们的 ERC-20 代币。我们将使用 OpenZeppelin 的智能合约,Foundry 作为我们的智能合约开发工具,使用 React、Next.js 和 WalletConnect 来构建前端界面。
让我们开始吧!
依赖 | 版本 |
---|---|
node | 18.13.0 |
Factory 设计模式允许你在另一合约内以编程方式创建新的智能合约。该模式在用户或其他合约需要部署多个合约实例而无需手动部署每个合约的情况下非常有用。
此 Factory 模式可用于不同的用例,例如代币和智能合约账户生成。在本指南中,我们将演示如何在 ERC-20 代币中使用 Factory 模式。
此 dApp 采用的 Factory 模式设计如下:
createToken
并包含有效的输入参数(即代币名称、符号和供应量)现在我们已经了解了 Factory 模式及其在我们上下文中的使用方式,让我们讨论用户希望创建代币时使用的 ERC-20 代币实现。
在这个 EVM Token Factory dApp 中实现的 ERC-20 代币利用了 OpenZeppelin 库,你可以在 这里 找到。如果你想了解更多关于 ERC-20 代币及其规范的信息,请查看此指南:如何创建和部署 ERC20 代币
要与以太坊区块链通信,你需要访问一个节点。虽然我们可以自己运行一个节点,但在 QuickNode,我们使其快速且容易启动区块链节点。你可以在 这里注册一个账户。启动节点后,检索 HTTP URL。它应该如下所示:
为了部署此 dApp 所需的智能合约,你需要 Sepolia 测试网的 ETH 来支付 gas 费。Multi-Chain QuickNode Faucet 使获取测试 ETH 变得简单!
导航到 Multi-Chain QuickNode Faucet 并连接你的钱包(例如 MetaMask、Coinbase Wallet)或粘贴你的钱包地址以获取测试 ETH。请注意,使用 EVM 水龙头需要在以太坊主网的主网余额要求为 0.001 ETH。你还可以通过推特或使用你的 QuickNode 账户登录以获取额外奖励!
创建了节点端点并为钱包添加了资金后,让我们进入本指南的技术编码部分。
首先,我们需要克隆 GitHub 仓库并导航到正确的目录:
git clone git@github.com:quiknode-labs/qn-guide-examples.git
cd qn-guide-examples/sample-dapps/evm-token-factory
接下来,让我们配置我们将用于智能合约部署的 RPC URL 和私钥。一种方法是将这些细节包含在 .env
文件中;但是,如果你计划将此代码开源,建议将其保留在命令行指令中,而不做包含。让我们创建环境变量以包含我们的 RPC 端点和私钥:
export RPC_URL=<你的 RPC 端点>
export PRIVATE_KEY=<你的钱包私钥>
最后,我们将安装智能合约依赖。在 evm-token-factory/contracts 文件夹中,运行以下命令以安装两个库,forge-std
用于一组有用的合约和库,OpenZeppelin/openzeppelin-contracts
用于我们将使用的 ERC-20 实现。
forge install foundry-rs/forge-std --no-commit
forge install OpenZeppelin/openzeppelin-contracts --no-commit
项目目录设置以及依赖安装完毕后,让我们继续创建智能合约。
接下来,让我们进入智能合约开发。由于智能合约尚未部署,你将无法创建任何 ERC-20 代币,但这是我们的下一步。
导航进入 evm-token-factory/contracts
目录,让我们回顾一下结构:
├── lib // 依赖
├── remappings.txt // 推断的库映射
├── script // 调用/与合约交互的脚本
│ ├── Counter.s.sol
│ └── CreateToken.s.sol
├── src // 智能合约源文件夹
│ ├── Counter.sol
│ ├── Factory.sol
│ └── Token.sol
└── test // 测试
│ ├── Counter.t.sol
│ ├── Factory.t.sol
│ └── Token.t.sol
├── foundry.toml // Forge 配置文件
请注意,在编译和部署智能合约后,将创建新的文件夹,如 build 和 cache。现在我们对文件夹结构熟悉后,让我们继续合约开发。
导航到 src/Token.sol
文件,你将看到 ERC-20 代币逻辑。此智能合约利用了 OpenZeppelin 的库。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import "openzeppelin-contracts/contracts/access/Ownable.sol";
contract Token is ERC20, Ownable {
constructor(address initialOwner, uint256 initialSupply, string memory name, string memory symbol)
ERC20(name, symbol)
Ownable(initialOwner)
{
_transferOwnership(initialOwner);
_mint(initialOwner, initialSupply * 10 ** 18);
}
}
合约还通过 OpenZeppelin 的 Ownable
库实现了访问控制。这有效地允许“所有者”(在大多数情况下是部署者)管理管理员任务。
接下来,让我们讨论 Factory 合约。
导航到 src/Factory.sol
文件,你将看到 Factory 合约逻辑。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./Token.sol";
contract TokenFactory {
event TokenCreated(address indexed tokenAddress, address indexed owner, uint256 initialSupply);
function createToken(address initialOwner, uint256 initialSupply, string memory name, string memory symbol) public returns (address) {
Token newToken = new Token(initialOwner, initialSupply, name, symbol);
emit TokenCreated(address(newToken), initialOwner, initialSupply);
return address(newToken);
}
}
上面的 Factory 合约(即 TokenFactory)没有包含构造函数,仅包含一个自定义函数 createToken
,该函数接受用于创建 ERC-20 代币的 initialOwner
、initialSupply
、name
和 symbol
。它还会发出 TokenCreated
事件,以记录每个新代币的地址及其初始所有者和供应量。
在构建智能合约后,下一步是测试它们。让我们继续。
为了确保智能合约按预期工作,让我们创建测试案例。Foundry 使得使用 forge test
命令变得简单。
让我们导航到 test/Token.t.sol
文件并查看测试代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract TokenTest is Test {
Token token;
address initialOwner;
uint initialSupply = 1000;
string name = "TestToken";
string symbol = "TT";
function setUp() public {
initialOwner = address(this);
token = new Token(initialOwner, initialSupply, name, symbol);
}
function testInitialOwner() public view {
assertEq(token.owner(), initialOwner);
}
function testInitialSupply() public view {
uint expectedSupply = initialSupply * 10 ** token.decimals();
assertEq(token.totalSupply(), expectedSupply);
assertEq(token.balanceOf(initialOwner), expectedSupply);
}
function testNameAndSymbol() public view {
assertEq(token.name(), name);
assertEq(token.symbol(), symbol);
}
}
接下来,导航到 test/Factory.t.sol
文件并查看测试代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Factory.sol";
import "../src/Token.sol";
contract TokenFactoryTest is Test {
TokenFactory factory;
address initialOwner;
uint256 initialSupply = 1000;
string name = "MyToken";
string symbol = "MTK";
event TokenCreated(address indexed tokenAddress, address indexed owner, uint256 initialSupply);
function setUp() public {
factory = new TokenFactory();
initialOwner = address(this);
}
function testCreateToken() public {
vm.expectEmit(false, false, false, false);
emit TokenCreated(address(0), initialOwner, initialSupply);
address tokenAddr = factory.createToken(initialOwner, initialSupply, name, symbol);
assertTrue(tokenAddr != address(0), "Token creation failed");
Token token = Token(tokenAddr);
assertEq(token.owner(), initialOwner, "Owner is not set correctly");
assertEq(token.totalSupply(), initialSupply * 10 ** 18, "Initial supply is incorrect");
assertEq(token.name(), name, "Token name is incorrect");
assertEq(token.symbol(), symbol, "Token symbol is incorrect");
}
}
要执行这些测试,运行以下命令:
forge test
你将看到如下输出:
Compiler run successful!
Ran 3 tests for test/Token.t.sol:TokenTest
[PASS] testInitialOwner() (gas: 12698)
[PASS] testInitialSupply() (gas: 19499)
[PASS] testNameAndSymbol() (gas: 23001)
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.44ms (207.54µs CPU time)
Ran 1 test for test/Factory.t.sol:TokenFactoryTest
[PASS] testCreateToken() (gas: 611315)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.91ms (738.63µs CPU time)
Ran 2 tests for test/Counter.t.sol:CounterTest
[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 30765, ~: 31310)
[PASS] test_Increment() (gas: 31325)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 10.85ms (9.42ms CPU time)
Ran 3 test suites in 502.03ms (14.20ms CPU time): 6 tests passed, 0 failed, 0 skipped (6 total tests)
当我们的测试如预期工作后,让我们继续合约部署。
在创建、编译和测试了我们的智能合约后,让我们将它们部署到像 Sepolia 这样的测试网上。我们将使用在 项目设置 中设置的环境变量,因此请确保这些设置已完成。
然后,在 evm-token-factory/contracts 目录中运行以下 forge create 命令以部署 Factory 合约:
forge create --rpc-url=$RPC_URL --private-key=$PRIVATE_KEY src/Factory.sol:TokenFactory
你应该会看到如下输出:
[⠊] Compiling...
No files changed, compilation skipped
Deployer: 0xb84c1F9663b59723f3E24FD79242206a97640069
Deployed to: 0xfD5061780F6393e6677C15C6f6a27E843A360Bf7
Transaction hash: 0x46e171d9a1b985e1b0f566695277f517956b62835f41071684d5fcdfc06d0090
你可以通过在区块浏览器上查看确认合约已经部署,例如 Etherscan。
尽管我们稍后将向你展示如何构建 Token Factory dApp 的前端接口,但让我们创建一个 Foundry 脚本来测试我们的智能合约,现在它们已在 Sepolia 测试网上部署。
导航到 script/CreateToken.s.sol
文件,你将看到以下脚本代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/Factory.sol";
contract CreateToken is Script {
function run() public {
vm.startBroadcast();
address initialOwner = msg.sender;
uint256 initialSupply = 1000;
string memory name = "MyToken";
string memory symbol = "MTK";
TokenFactory factory = new TokenFactory();
address tokenAddress = factory.createToken(initialOwner, initialSupply, name, symbol);
console.log("Token created at address:", tokenAddress);
vm.stopBroadcast();
}
}
上面的脚本创建了已部署的 TokenFactory
合约的实例,并调用 createToken
函数,并传递函数参数(例如 initialOwner,initialSupply,name,symbol)。此外,使用 Foundry 的 vm.startBroadcast
和 vm.stopBroadcast
函数广播我们正在执行的合约调用。
要执行脚本,请运行以下代码:
forge script script/CreateToken.s.sol --rpc-url=$RPC_URL --private-key=$PRIVATE_KEY --broadcast
你将看到如下输出:
[⠒] Compiling...
No files changed, compilation skipped
Script ran successfully.
== Logs ==
Token created at address: 0x73fB510d5aF9CA850b18E2bfAB455c5DC4D3053A
## 设置 1 EVM。
==========================
Chain 11155111
估算 gas 价格:47.739060376 gwei
估算脚本总 gas 用量:2110503
估算所需金额:0.100753430140729128 ETH
==========================
发送交易 [0 - 1]。
⠉ [00:00:00] [######################################################################################################################################] 2/2 txes (0.0s)##
等待收据。
⠙ [00:00:07] [##################################################################################################################################] 2/2 receipts (0.0s)
##### sepolia
✅ [成功]Hash: 0xfd9c8d6e27e0b86fbc214b72ad67290869e177c23fee66ff2c2e6be4b94e03d1
合约地址:0x964fC448374f6F4614A9d9a0fC2A34288916f8AE
区块:6041473
支付:0.024943164730376728 ETH (983708 gas * 25.356269066 gwei)
##### sepolia
✅ [成功]Hash: 0x7be1de4915a5320d5cd3c7db705618244ded400ab02ee9dbfe6f32674eb57c05
区块:6041473
支付:0.015280220220821986 ETH (602621 gas * 25.356269066 gwei)
==========================
链上执行完成且成功。
总支付:0.040223384951198714 ETH (1586329 gas * 平均 25.356269066 gwei)
完成所有合约创建后,让我们接下来配置前端。
接下来也是最终一步是构建前端接口。为了创建此前端,我们将使用 React、Next.js 和 WalletConnect 的 Web3Modal/Ethers SDK。
由于所有代码均已在目录中,让我们配置环境变量、所用链和合约地址。
evm-token-factory/
.env.example
重命名为 .env.local
并使用每个区块链的 RPC URL 更新它。如果你只计划使用 Sepolia,则仅保留 NEXT_PUBLIC_SEPOLIA_RPC_URL
变量并删除其他变量。还请在 NEXT_PUBLIC_PROJECT_ID
变量中包含你的 WalletConnect 项目 ID(你可以将其留空,但某些功能将不受支持)。evm-token-factory/app/utils/ethereum.ts
文件中更新 factoryAddress
值,使用你的已部署的 Factory 合约地址。这是在 部署 部分的输出中收到的地址。src/context/web3modal.tsx
文件中未使用的链(例如,主网)。首先,让我们安装前端依赖:
npm install
接下来,运行应用程序:
npm run dev
你可以连接你的钱包,然后输入代币详细信息,单击“创建代币”,签署交易,并等待其被挖矿。
这就是你!你刚刚创建了自己的 EVM 兼容的工厂代币生成器!有关更多信息,请参考 README.md。
订阅我们的 新闻通讯,获取更多关于以太坊的文章和指南。如果你有任何反馈,请随时通过 Twitter 联系我们。你还可以随时与我们在 Discord 社区服务器上交流,那里有一些你所能遇见的最酷的开发者 :)
让我们知道 如果你有任何反馈或新主题请求。我们很想听到你的声音。
- 原文链接: quicknode.com/guides/eth...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!