安装初始环境
安装hardhat
,并配置node环境,我使用的node
环境为v22.13.0
。
下载github项目
1
| git clone -b starter_code https://github.com/dappuniversity/fun-pump
|
进入到目录下/FUN-PUMP
,使用npm install
进行安装
创建Token
使用ERC20.sol
创建token
,参考openzeppelin
。
使用到Remix
去编写token
的solidity,Token.sol
的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.27;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Token is ERC20 { address payable public owner; address public creator;
constructor( address _creator, string memory _name, string memory _symbol, uint256 _totalSupply ) ERC20(_name, _symbol) { owner = payable(msg.sender); creator = _creator;
_mint(msg.sender, _totalSupply); } }
|
上述代码中变量:
_creator
:代币创建者的地址。
_name
:代币名称(例如 “My Token”)。
_symbol
:代币符号(例如 “MTK”)。
_totalSupply
:要铸造的代币总量。
owner = payable(msg.sender);
的含义:
msg.sender
是调用当前函数的地址。
payable(msg.sender)
将地址转换为可支付类型,意味着这个地址可以接收ETH。
- 在这里,
owner
被设置为部署 Token
合约的地址(Factory
合约),并且可以接收ETH。
- 使用
_mint()
将全部 _totalSupply
铸造给合约部署者。
先使用Remix
去部署测试,再使用hardhat
在本地部署。

部署成功。

创建并测试Factory
整个Meme Coin
启动台的逻辑如下图:

全局变量
全局变量如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| uint256 public constant TARGET = 3 ether; uint256 public constant TOKEN_LIMIT = 500_000 ether; //交易费费用 uint256 public immutable fee; //合约的部署者 address public owner; //token数组 address[] public tokens; //token总共的数量 uint256 public totalTokens;
//将token的地址映射到token售卖信息的结构体上 mapping(address => TokenSale) public tokenToSale; //创建一个售卖token的结构体 struct TokenSale{ address token; string name; address creator; uint256 sold; uint256 raised; bool isOpen; }
|
其中创建了一个token
的结构体,其中
token
表示创建的token地址
name
代表token的名称
creator
表示token的创造者
sold
表示售卖的token数量
raised
表示token已经筹资了多少
isOpen
表示token是否公开售卖
同时还创建了两个事件,分别为Created
和Buy
事件。
1 2 3 4
| //当创建一个token时,发生了一个Created事件 event Created(address indexed token); //当购买一个token时,发生一个Buy事件 event Buy(address indexed token, uint256 amount);
|
在Solidity
中,event
(事件)用于记录和通知区块链上发生的特定操作。事件不会改变合约的状态,且可以被外部系统廉价地存储和检索,比直接返回值更节省gas
。在代码中,indexed
是Solidity
中的一个特殊关键字,主要用于优化事件的检索和过滤。被indexed
标记的参数会创建topics
(主题),便于快速查询。
Create函数(代币创建)
函数代码
Solidity
代码详细如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function create(string memory _name, string memory _symbol) external payable { //确保创建者的余额大于fee,否则报错不向下继续执行。 require(msg.value >= fee, "Factory: Creator fee not met");
//创建一个token Token token = new Token(msg.sender, _name, _symbol, 1000000 ether); //tokens数组存储token tokens.push(address(token)); //token计数 totalTokens ++; //列出用于售卖的token // address token; // string name; // address creator; // uint256 sold; // uint256 raised; // bool isOpen; TokenSale memory sale = TokenSale(address(token),_name,msg.sender,0,0,true);
tokenToSale[address(token)] = sale; //tell people it's a live emit Created(address(token)); }
|
首先,传入的参数_name
为token
的名称,_symbol
为token
的符号。通过require
判断创建者的资金是否大于fee
,满足条件后才能创建token
。
其次,创建token
,给Token必要的参数,msg.sender
为调用create
函数的地址,这里为factory
合约的地址,其为Token的创建者,_name
为Token的名称,_symbol
为Token的符号,1000000 ether
为token的总供应量。
然后,将创建的token
保存到已创建的token
数组中,并计数创建的token
数量。
再者,创建一个TokenSale
结构体,用于记录token
的售卖情况,初始化存入token
的地址、名称和创建者地址,初始token
的售卖量和筹资量均为0,售卖状态是打开的。
最后,使用到mapping
,使得token
的地址直接映射到token
的售卖信息(tokenSale
)上。同时,每创建一个新的token
都会创建一个Created
事件。
合约测试
js代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers") const { expect } = require("chai") const { ethers } = require("hardhat")
describe("Factory", function () { const FEE = ethers.parseUnits("0.01", 18); async function deployFactoryFixture() { const [deployer, creator, buyer] = await ethers.getSigners(); const Factory = await ethers.getContractFactory("Factory"); const factory = await Factory.deploy(FEE); const transaction = await factory.connect(creator).create("Pump", "PUM", {value: FEE}); await transaction.wait();
const tokenAddress = await factory.tokens(0); const token = await ethers.getContractAt("Token", tokenAddress);
return { factory, token, deployer, creator ,buyer}; } describe("Deployment", function () { it("Should set the fee", async function () { const { factory } = await loadFixture(deployFactoryFixture); expect(await factory.fee()).to.equal(FEE); }) it("Should set the owner", async function () { const { factory,deployer } = await loadFixture(deployFactoryFixture); expect(await factory.owner()).to.equal(deployer.address); }) }) describe("Creating", function () { it("Should set the owner", async function () { const { factory,token } = await loadFixture(deployFactoryFixture); expect(await token.owner()).to.equal(await factory.getAddress()); }) it("Should set the creator", async function () { const { token,creator } = await loadFixture(deployFactoryFixture); expect(await token.creator()).to.equal(creator.address); }) it("Should set the supply", async function () { const { factory,token } = await loadFixture(deployFactoryFixture); const totalSupply = ethers.parseUnits("1000000", 18); expect(await token.balanceOf(await factory.getAddress())).to.equal(totalSupply); }) it("Should update ETH balance", async function () { const { factory } = await loadFixture(deployFactoryFixture); const balance = await ethers.provider.getBalance(await factory.getAddress()); expect(balance).to.equal(FEE); }) it("Should create the sale", async function () { const { factory,token,creator} = await loadFixture(deployFactoryFixture); const count = await factory.totalTokens(); expect(count).to.equal(1);
const sale = await factory.getTokenSale(0);
expect(sale.token).to.equal(await token.getAddress()); expect(sale.creator).to.equal(creator.address); expect(sale.sold).to.equal(0); expect(sale.raised).to.equal(0); expect(sale.isOpen).to.equal(true); }) }) })
|
测试合约的部署,对于deployFactoryFixture
函数:
使用ethers.getSigners()
去获取用户,deployer、creator
分别为合约的部署者和创建者,buyer
为token的买家。编译并部署合约,用creator
用户去交易,创建一个名称为Pump
,符号为PUM
的代币(token
)。然后,获取创建的token
的地址。
部署部分测试:
测试部署的合约的fee
和deployer
是否正确。Factory.deploy(FEE);
默认是使用第一个用户去部署合约,正常应该是Factory.connect(deployer).deploy(FEE);
去部署合约。
创建token
部分测试:
(1)测试token.owner()
和factory.getAddress()
是否相等。token.owner()
是token
的创建者,factory.getAddress()
是factory
合约部署的地址,因为在Token.sol
中,创建token
时,使用的是msg.sender
,即调用该合约的地址,其被赋值给了owner
,Factory
合约调用了Token
合约,所以二者是相等。
(2)测试creator
和totalSupply
是否和部署时一致。
(3)创建token
时需要支付一定的fee
,这个fee
是存储在factory
合约中的,创建token
后判断合约中的余额和fee
是否相等。
(4)测试token
的售卖信息。
测试完成。

Buy函数(代币销售)
函数代码
代码详细如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function buy(address _token, uint256 _amount) external payable{ TokenSale storage sale = tokenToSale[_token]; //检查条件 require(sale.isOpen == true, "Factory: Buying closed"); require(_amount >= 1 ether, "Factory: Amount too low"); require(_amount <= 10000 ether, "Factory: Amount exceeded");
//计算一个token的价格基于购买的总量 uint256 cost = getCost(sale.sold); uint256 price = cost * (_amount / 10 ** 18); //确保足够的ETH发送 require(msg.value >= price, "Factory: Insufficient ETH received"); //更新sale sale.sold += _amount; sale.raised += price; //判断筹集目标是否达成 if(sale.sold >= TOKEN_LIMIT || sale.raised >= TARGET){ //关闭销售 sale.isOpen = false; }
Token(_token).transfer(msg.sender, _amount); //触发Buy事件 emit Buy(_token, _amount); }
|
该函数为external payable
的,说明只能外部调用且是可以在调用时发送ETH
的。函数传入的参数为_token
和_amount
,即token
的地址和购买的数量。
首先,使用 storage
关键字,允许直接修改存储状态,并通过token
的地址获取对应的售卖信息。然后检查token
的售卖信息是否符合情况。
然后,根据token
的售卖情况计算token
的价格。使用到了getCost
函数,自己定义的函数,具体如下:
1 2 3 4 5 6 7
| function getCost(uint256 _sold) public pure returns (uint256){ uint256 floor = 0.0001 ether; uint256 step = 0.0001 ether; uint256 increment = 10000 ether; uint256 cost = (step * (_sold / increment)) + floor; return cost; }
|
其次,判断购买者的资金是否大于价格,若满足,则可购买,然后更新token
的售卖信息,购买量和筹集的资金均增加。更新后,判断token
的售卖信息是否达到上限的要求,若满足,则关闭售卖状态。
最后,向购买者buyer
转账购买的token
。转账完成后触发Buy
事件。
合约测试
js代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| async function buyTokenFixture() { const { factory, token, creator , buyer } = await deployFactoryFixture(); const AMOUNT = ethers.parseUnits("10000", 18); const COST = ethers.parseUnits("1", 18);
const transaction = await factory.connect(buyer).buy(await token.getAddress(), AMOUNT, {value: COST}); await transaction.wait(); return { factory, token, creator, buyer}; } describe("Buying", function () { const AMOUNT = ethers.parseUnits("10000", 18); const COST = ethers.parseUnits("1", 18); it("Should update ETH balance", async function () { const { factory } = await loadFixture(buyTokenFixture); const balance = await ethers.provider.getBalance(await factory.getAddress()); expect(balance).to.equal(FEE + COST); }) it("Should update token balance", async function () { const {token, buyer} = await loadFixture(buyTokenFixture); const balance = await token.balanceOf(buyer.address); expect(balance).to.equal(AMOUNT); }) it("Should update token sale", async function () { const { factory, token } = await loadFixture(buyTokenFixture); const sale = await factory.tokenToSale(await token.getAddress()); expect(sale.sold).to.equal(AMOUNT); expect(sale.raised).to.equal(COST); expect(sale.isOpen).to.equal(true); }) it("Should increase base cost", async function () { const { factory, token } = await loadFixture(buyTokenFixture); const sale = await factory.tokenToSale(await token.getAddress()); const cost = await factory.getCost(sale.sold); expect(cost).to.be.equal(ethers.parseUnits("0.0002"));
}) })
|
对于buyTokenFixture
函数:
该函数部署一个合约,并使用buyer
用户购买token
,输入购买的数量AMOUNT
和转入的费用COST
,完成token
的购买。
购买逻辑部分测试:
(1)核对购买token
后的合约资金
1 2 3
| const { factory } = await loadFixture(buyTokenFixture); const balance = await ethers.provider.getBalance(await factory.getAddress()); expect(balance).to.equal(FEE + COST);
|
ethers.provider.getBalance
获取合约的资金数量,判断是否等于FEE + COST
。创建token
转入factory
合约FEE
,购买token
又花费了COST
,最后合约的资金为FEE + COST
。
(2)核对buyer
的token
数量是否正确,见代码。
(3)核对token
的售卖信息,见代码。
(4)核对销售的价格是否符合逻辑。基本单位为ether
,1 ether
等于1后面18个0
。销售的数量为10000 ether
,带入到getCost
函数可得,购买的价格应该为0.0002
ETH。
Deposit函数(资金管理)
函数代码
代码详细如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function deposit(address _token) external{ //代币销售结束后资金结算的逻辑 //剩余的tokens 和 ETH转给creator Token token = Token(_token); TokenSale memory sale = tokenToSale[_token];
require(sale.isOpen == false,"Factory: Target not reached");
//将Factory合约持有的代币转移给creator token.transfer(sale.creator, token.balanceOf(address(this)));
//将筹集的ETH全部转移creator (bool success,) = payable(sale.creator).call{value: sale.raised}(""); require(success,"Factory: ETH transfer failed");
}
|
该函数是external
的,只能从合约的外部调用,其主要功能为token
销售结束后的资金结算。
函数传入的参数为_token
,即token
的地址。利用token
的地址获取到token
的销售信息,只有当token
达到销售的预期后才进行资金的清算,即代码中的sale.isOpen == false
。然后将factory
合约持有的token
全部转给creator
。最后再把factory
筹集的ETH
也全部转给creator
。
合约测试
js代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| describe("Depositing", function () { const AMOUNT = ethers.parseUnits("10000", 18); const COST = ethers.parseUnits("2", 18); it("Sale should be closed and successful deposits", async function (){ const { factory, token, creator, buyer} = await loadFixture(buyTokenFixture);
const buyTx = await factory.connect(buyer).buy(await token.getAddress(), AMOUNT, {value: COST}); await buyTx.wait();
const sale = await factory.tokenToSale(await token.getAddress()); expect(sale.isOpen).to.equal(false);
const depositTx = await factory.connect(creator).deposit(await token.getAddress()); await depositTx.wait();
const balance = await token.balanceOf(creator.address); expect(balance).to.equal(ethers.parseUnits("980000",18)); }) })
|
对于存款逻辑测试:
(1)调用loadFixture(buyTokenFixture);
函数获取相关用户和token
。注意这里是调用了buyTokenFixture
,在buyTokenFixture
函数中buyer
已经购买了10000 ether
的token
,并向factory
合约中转入了1 ether
。

(2)在此Depositing
测试中buyer
又再次购买了10000 ether
的token
,并向合约中转入了2 ether
。
(3)检查token
的销售信息。因为先后向factory
合约中一共转入了3 ether
,达到了目标,sale.isOpen
被赋值了false
。所以测试中,sale.isOpen
是为false
的。

(4)达到token
的售卖目标后,将factory
剩余的token
全部转移为creator
。因为一共购买了20000 ether
,一共铸造了1000000 ether
的token
,故转移给creator
的token
的数量应该为980000 ether
。所以测试中creator
的token
数量为980000 ether
。
Withdraw函数(合约资金回撤)
函数代码
代码详细如下:
1 2 3 4 5 6
| function withdraw(uint256 _amount) external{ require(msg.sender == owner, "Factory: Not Owner");
(bool success, ) = payable(owner).call{value: _amount}(""); require(success, "Factory: ETH transfer failed"); }
|
该合约函数传入的参数为_amount
,即要转移的资金数量,其功能是将一定数量的合约资金转给合约的拥有者owner
。
首先,判断msg.sender
是否为owner
,即调用withdraw
合约函数的地址是否为owner
。只有合约的owner
才能管理合约中的资金。该函数的第一步即验证权限,确定要为owner
。确认owner
的身份后,调用转账函数,将合约的_amount
数量的资金转移给owner
。
合约测试
js代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| describe("Withdrawing Fee", function (){ it("Should update ETH balances", async function(){ const { factory, deployer} = await loadFixture(deployFactoryFixture); const transaction = await factory.connect(deployer).withdraw(FEE); await transaction.wait();
const balance = await ethers.provider.getBalance(await factory.getAddress());
expect(balance).to.equal(0); }) })
|
合约资金回撤测试:
(1)首先,deployer
调用loadFixture(deployFactoryFixture)
函数进行factory
合约的部署,合约部署时,会创建第一个token
,并转入创建的FEE
。此时factory
合约的owner
为deployer
,factory
合约的资金数为FEE
。
(2)deployer
调用factory
合约的withdraw
函数进行合约资金的回撤。
(3)最后测试,回撤资金成功后,factory
合约的资金变为0。
完整合约代码
完整的Factory.sol
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.27;
import {Token} from "./Token.sol";
contract Factory { uint256 public constant TARGET = 3 ether; uint256 public constant TOKEN_LIMIT = 500_000 ether; //交易费费用 uint256 public immutable fee; //合约的部署者 address public owner; //token数组 address[] public tokens; //token总共的数量 uint256 public totalTokens;
//将token的地址映射到token售卖信息的结构体上 mapping(address => TokenSale) public tokenToSale; //创建一个售卖token的结构体 struct TokenSale{ address token; string name; address creator; uint256 sold; uint256 raised; bool isOpen; } //当创建一个token时,发生了一个Created事件 event Created(address indexed token); //当购买一个token时,发生一个Buy事件 event Buy(address indexed token, uint256 amount);
constructor(uint256 _fee) { fee = _fee; owner = msg.sender; }
//通过token在token数组中的下标_index,来获取token的售卖信息 function getTokenSale(uint256 _index) public view returns (TokenSale memory) { return tokenToSale[tokens[_index]]; }
function getCost(uint256 _sold) public pure returns (uint256){ uint256 floor = 0.0001 ether; uint256 step = 0.0001 ether; uint256 increment = 10000 ether; uint256 cost = (step * (_sold / increment)) + floor; return cost; }
function create(string memory _name, string memory _symbol) external payable { //确保创建者的余额大于fee,否则报错不向下继续执行。 require(msg.value >= fee, "Factory: Creator fee not met"); //创建一个token Token token = new Token(msg.sender, _name, _symbol, 1000000 ether); //tokens数组存储token tokens.push(address(token)); //token计数 totalTokens ++; //列出用于售卖的token // address token; // string name; // address creator; // uint256 sold; // uint256 raised; // bool isOpen; TokenSale memory sale = TokenSale(address(token),_name,msg.sender,0,0,true);
tokenToSale[address(token)] = sale; //触发Created事件,通知token被创建了。 emit Created(address(token)); }
function buy(address _token, uint256 _amount) external payable{ TokenSale storage sale = tokenToSale[_token]; //检查条件 require(sale.isOpen == true, "Factory: Buying closed"); require(_amount >= 1 ether, "Factory: Amount too low"); require(_amount <= 10000 ether, "Factory: Amount exceeded");
//计算一个token的价格基于购买的总量 uint256 cost = getCost(sale.sold); uint256 price = cost * (_amount / 10 ** 18); //确保足够的ETH发送 require(msg.value >= price, "Factory: Insufficient ETH received"); //更新sale sale.sold += _amount; sale.raised += price; //判断筹集目标是否达成 if(sale.sold >= TOKEN_LIMIT || sale.raised >= TARGET){ //关闭销售 sale.isOpen = false; }
Token(_token).transfer(msg.sender, _amount); //触发Buy事件 emit Buy(_token, _amount); }
function deposit(address _token) external{ //代币销售结束后资金结算的逻辑
//剩下的token余额和增加的ETH //会进入像uniswap v3那样的流动性池 //为了简单起见,我们只转移剩余的部分 //剩余的tokens 和 ETH转给creator Token token = Token(_token); TokenSale memory sale = tokenToSale[_token];
require(sale.isOpen == false,"Factory: Target not reached");
//将Factory合约持有的代币转移给creator token.transfer(sale.creator, token.balanceOf(address(this)));
//将筹集的ETH全部转移creator (bool success,) = payable(sale.creator).call{value: sale.raised}(""); require(success,"Factory: ETH transfer failed");
}
function withdraw(uint256 _amount) external{ require(msg.sender == owner, "Factory: Not Owner"); (bool success, ) = payable(owner).call{value: _amount}(""); require(success, "Factory: ETH transfer failed");
}
}
|
本地部署合约
在/FUN-PUMP/ignition/modules/
目录下,创建Factory.js
,进行合约的部署,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
| const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const { ethers } = require("hardhat");
const FEE = ethers.parseUnits("0.01", 18);
module.exports = buildModule("FactoryModule", (m) => { const fee = m.getParameter("fee", FEE); const factory = m.contract("Factory", [fee]);
return { factory }; })
|
首先启动本地的区块链。
然后,进行合约部署
1
| npx hardhat ignition deploy ignition/modules/Factory.js --network localhost
|

成功部署。

如果已经部署了合约,需要重置合约
1
| npx hardhat ignition deploy ignition/modules/Factory.js --network localhost --reset
|
前端
本地测试,使用next.js
渲染前端的界面,前端使用React
库用作前端界面展示。前端执行javascript
和部署在区块链上的factory
合约进行交互,返回结果,前端进行展示。
启动next.js
,如下:

点击添加账户,导入账户。

本地区块链启动时,会有几个测试账户,导入私钥即可。

连接钱包并加载链上内容
首先将部署合约后的合约地址添加到配置环境中,地址为0x5FbDB2315678afecb367f032d93F642f64180aa3
。

连接metamask钱包
1 2
| const provider = new ethers.BrowserProvider(window.ethereum); setProvider(provider);
|
获取网络和合约的地址
1 2 3 4 5 6 7 8 9 10 11
| const network = await provider.getNetwork();
console.log("chainId:",network.chainId); console.log("address:", config[network.chainId].factory.address);
const factory = new ethers.Contract(config[network.chainId].factory.address,Factory,provider); setFactory(factory); console.log("factory:",factory); const fee = await factory.fee(); console.log("fee:",fee); setFee(fee);
|


在页面第一次加载时,自动执行loadBlockchainData
函数,加载钱包。
1 2 3
| useEffect(() => { loadBlockchainData(); },[])
|
创建Token
在List.js
中,编写一个表单提交,输入token
的名称和符号。
1 2 3 4 5 6 7 8 9 10 11 12
| async function listHandler(form){ const name = form.get("name"); const ticker = form.get("ticker");
const signer = await provider.getSigner();
const transaction = await factory.connect(signer).create(name, ticker, {value: fee}); await transaction.wait();
console.log("submitted...",name ,ticker); toggleCreate(); }
|
表单的html代码,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <div className="list"> <h2>list new token</h2> <div className="list_description"> <p>fee: {ethers.formatEther(fee, 18)} ETH</p> </div>
<form action={listHandler}> <input type="text" name="name" placeholder="name"/> <input type="text" name="ticker" placeholder="ticker"/> <input type="submit" value="[ list ]"/> </form>
<button onClick={toggleCreate} className="btn--fancy"> [ cancel ] </button> </div>
|


列出Token
使用factory
对象获取已经创建的token
总数totalToken
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const totalToken = await factory.totalTokens(); const tokens = [] for(let i = 0; i < totalToken; i++){ const tokenToSale = await factory.getTokenSale(i); console.log(tokenToSale);
const token = { token: tokenToSale.token, name: tokenToSale.name, creator: tokenToSale.creator, sold: tokenToSale.sold, raised: tokenToSale.raised, isOpen: tokenToSale.isOpen, image: images[i] } tokens.push(token); } setTokens(tokens.reverse()); console.log("tokens:",tokens);
|
在Token.js
中进行前端展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { ethers } from "ethers"
function Token({ toggleTrade, token }) { return ( <button onClick={() => toggleTrade(token)} className="token"> <div className="token_details"> <img src={token.image} alt="token image" width={256} height={256} /> <p>creator by {token.creator.slice(0,6) + '...' + token.creator.slice(38,42)}</p> <p>market Cap: {ethers.formatEther(token.raised, 18)} ETH</p> <p className="name">{token.name}</p> </div> </button> ); }
export default Token;
|

界面展示

购买Token
在Trade.js
中,购买token
的js代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const [target, setTarget] = useState(0); const [limit, setLimit] = useState(0); const [cost, setCost] = useState(0);
async function buyHandler(form){ const amount = form.get("amount"); const cost = await factory.getCost(token.sold); const totalCost = cost * BigInt(amount); const signer = await provider.getSigner(); const transaction = await factory.connect(signer).buy( token.token, ethers.parseUnits(amount, 18), {value: totalCost} ); await transaction.wait();
toggleTrade(); }
async function getSaleDetails(){ const target = await factory.TARGET(); setTarget(target); const limit = await factory.TOKEN_LIMIT(); setLimit(limit); const cost = await factory.getCost(token.sold); setCost(cost);
}
useEffect(() => { getSaleDetails(); }, [token]);
|
购买token
的html代码,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <div className="trade"> <h2>trade</h2> <div className="trade_details"> <p className="name">{token.name}</p> <p>creator: {token.creator.slice(0,6) + '...' + token.creator.slice(38,42)}</p> <img src={token.image} alt="token image" width={256} height={256} /> <p>marketcap: {ethers.formatEther(token.raised, 18)} ETH</p> <p>base cost: {ethers.formatEther(cost, 18)} ETH</p> </div>
{token.sold >= limit || token.raised >= target ? ( <p className="disclaimer">target reached!!!</p> ) : ( <form action={buyHandler}> <input type="number" name="amount" min={1} max={10000} placeholder="amount"/> <input type="submit" value="[ buy ]"/> </form> ) }
<button onClick={toggleTrade} className="btn--fancy"> [ cancel ] </button> </div >
|
界面展示:

参考
https://www.youtube.com/watch?v=z7Vz8ZKylc4
https://www.openzeppelin.com/
https://github.com/dappuniversity/fun-pump
https://docs.ethers.org/v5/api/contract/contract/