做个像Pump.fun一样的Memecoin启动台
2025-05-25 23:53:21 # Web3

安装初始环境

安装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在本地部署。

image-20250207190149258

部署成功。

Snipaste_2025-02-07_19-03-39

创建并测试Factory

整个Meme Coin启动台的逻辑如下图:

image-20250216005120396

全局变量

全局变量如下:

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是否公开售卖

同时还创建了两个事件,分别为CreatedBuy事件。

1
2
3
4
//当创建一个token时,发生了一个Created事件
event Created(address indexed token);
//当购买一个token时,发生一个Buy事件
event Buy(address indexed token, uint256 amount);

Solidity中,event(事件)用于记录和通知区块链上发生的特定操作。事件不会改变合约的状态,且可以被外部系统廉价地存储和检索,比直接返回值更节省gas。在代码中,indexedSolidity中的一个特殊关键字,主要用于优化事件的检索和过滤。被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));
}

首先,传入的参数_nametoken的名称,_symboltoken的符号。通过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");
//部署合约
//默认是第一个用户部署合约即deployer
//await Factory.connect(deployer).deploy(FEE);
const factory = await Factory.deploy(FEE);
//创建token
const transaction = await factory.connect(creator).create("Pump", "PUM", {value: FEE});
//确保交易完成,再执行后续代码
await transaction.wait();

//获取第一个token,tokens为token数组
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);
//console.log(sale);

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的地址。

部署部分测试:

测试部署的合约的feedeployer是否正确。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,即调用该合约的地址,其被赋值给了ownerFactory合约调用了Token合约,所以二者是相等。

(2)测试creatortotalSupply是否和部署时一致。

(3)创建token时需要支付一定的fee,这个fee是存储在factory合约中的,创建token后判断合约中的余额和fee是否相等。

(4)测试token的售卖信息。

测试完成。

image-20250216175651190

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);

//买token
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);
//buyer买token,将ETH转入到合约中,检查合约收到的ETH
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);
})
//buyer买token,检查buyer收到的token
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);
})
//更新token sale
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);
//sold为10000,带入getCost计算 0.0001 + 0.0001 = 0.0002
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)核对buyertoken数量是否正确,见代码。

(3)核对token的售卖信息,见代码。

(4)核对销售的价格是否符合逻辑。基本单位为ether1 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);

//Buy tokens again to reach the target
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);
//console.log(balance);
expect(balance).to.equal(ethers.parseUnits("980000",18));
})
})

对于存款逻辑测试:

(1)调用loadFixture(buyTokenFixture);函数获取相关用户和token。注意这里是调用了buyTokenFixture,在buyTokenFixture函数中buyer已经购买了10000 ethertoken,并向factory合约中转入了1 ether

image-20250217130455947

(2)在此Depositing测试中buyer又再次购买了10000 ethertoken,并向合约中转入了2 ether

(3)检查token的销售信息。因为先后向factory合约中一共转入了3 ether,达到了目标,sale.isOpen被赋值了false。所以测试中,sale.isOpen是为false的。

image-20250217131220117

(4)达到token的售卖目标后,将factory剩余的token全部转移为creator。因为一共购买了20000 ether,一共铸造了1000000 ethertoken,故转移给creatortoken的数量应该为980000 ether。所以测试中creatortoken数量为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 (){
//部署合约,提交了FEE
//然后再将FEE进行撤回
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合约的ownerdeployerfactory合约的资金数为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 node

然后,进行合约部署

1
npx hardhat ignition deploy ignition/modules/Factory.js --network localhost

image-20250217145430869

成功部署。

image-20250217145456776

如果已经部署了合约,需要重置合约

1
npx hardhat ignition deploy ignition/modules/Factory.js --network localhost --reset

前端

本地测试,使用next.js渲染前端的界面,前端使用React库用作前端界面展示。前端执行javascript和部署在区块链上的factory合约进行交互,返回结果,前端进行展示。

启动next.js,如下:

1
npm run dev

image-20250217150831503

Metamask导入本地用户

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

image-20250217154415563

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

image-20250217154636893

连接钱包并加载链上内容

首先将部署合约后的合约地址添加到配置环境中,地址为0x5FbDB2315678afecb367f032d93F642f64180aa3

image-20250217151249055

连接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);
//address,abi,signerOrProvider
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);

image-20250217151435573

image-20250217151912945

在页面第一次加载时,自动执行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>

image-20250217160659358

image-20250217160630715

列出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
//获取token的总数量
const totalToken = await factory.totalTokens();
const tokens = []
for(let i = 0; i < totalToken; i++){
const tokenToSale = await factory.getTokenSale(i);
//输出创建的token的信息
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;

image-20250217153634506

界面展示

image-20250217154119339

购买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 >

界面展示:

image-20250217161106789

参考

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/