现实情况是期望代币可以在去中心化的交易场所中交换,这篇文章就是从一个简单案例来说明交换,流动性该如何实现。
我们需要先梳理一下,期望这个应用具备哪些功能:
只用一个代币对建立交易场所
交易收取 1% 的费用
用户可以为 UseWeb3Token 添加或删除流动性
为用户提供 LP 代币
说明:实现会比这个例子复杂的多
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract UseWeb3Exchange is ERC20 {
address public useweb3TokenAddress;
constructor(address useweb3TokenContract) ERC20("LP Token", "LP") {
useweb3TokenAddress = useweb3TokenContract;
}
function getReserve() public view returns(uint256){
return ERC20(useweb3TokenAddress).balanceOf(address(this));
}
}
因为要提供 LP 所以直接继承 ERC20
同时定义 getReserve 获取当前合约持有的 UseWeb3Token 数量
function getReserve() public view returns(uint256){
return ERC20(useweb3TokenAddress).balanceOf(address(this));
}
接下来,我们开始编写添加流动性方法
function addLiquidity(uint256 _amount) public payable returns(uint256){
uint256 liquidity;
// 用当前合约的 eth 做初始值
uint256 ethBalance = address(this).balance;
// 查询当前合约持有 useweb3Token 的数量
uint256 currentContractHoldUseweb3TokenReserve = getReserve();
ERC20 useweb3Token = ERC20(useweb3TokenAddress);
// currentContractHoldUseweb3TokenReserve = 0 表示第一次添加流动性
if (currentContractHoldUseweb3TokenReserve == 0){
// 将 _amount 数量 从发起者地址转移到当前合约地址
useweb3Token.transferFrom(msg.sender, address(this), _amount);
liquidity = ethBalance;
// 给发起者地址 LP 代币
_mint(msg.sender, liquidity);
} else {
// 已经存在流动性时的处理逻辑
// 当前合约的 eth 数量 - 用户发送来的 eth 数量
uint256 ethReserve = ethBalance - msg.value;
// (用用户发送来的 eth 数量乘以当前合约持有的 useweb3Token的数量)除以(已经减过的eth储备) ,获取一定比率的 useweb3Token
uint256 useweb3TokenAmount = (msg.value * currentContractHoldUseweb3TokenReserve)/(ethReserve);
// 检查 _amount 参数必须大于 计算出来的 useweb3Token 的数量
require(_amount > useweb3TokenAmount, "Amount of tokens sent is less than the minimum tokens required");
useweb3Token.transferFrom(msg.sender, address(this), useweb3TokenAmount);
// (LP 代币的总量 * 用户发送来的 eth 数量)除以(已经减过的eth储备)
liquidity = (totalSupply() * msg.value)/ethReserve;
// 给发送者地址 LP 代币
_mint(msg.sender, liquidity);
}
return liquidity;
}
删除流动性方法
function removeLiquidity(uint256 _amount) public returns(uint256, uint256){
require(_amount > 0, "amount > 0");
// 查询当前合约持有的 eth 数量
uint256 ethReserve = address(this).balance;
uint256 lpTotal = totalSupply();
uint256 ethAmount = (ethReserve * _amount)/lpTotal;
uint256 useweb3TokenAmount = (getReserve() * _amount)/lpTotal;
// 烧毁用户的 lp 代币
_burn(msg.sender, _amount);
// 将 eth 和 useweb3Token 代币还给用户
payable(msg.sender).transfer(ethAmount);
ERC20(useweb3TokenAddress).transfer(msg.sender, useweb3TokenAmount);
return (ethAmount, useweb3TokenAmount);
}
交易收取费用的方法
function getAmountOfTokens(uint256 inputAmount, uint256 inputReserve, uint256 outputReserve) public pure returns(uint256){
require(inputReserve > 0 && outputReserve > 0, "invalid reserves");
uint256 inputAmountWithFee = inputAmount * 99;
uint256 numerator = inputAmountWithFee * outputReserve;
uint256 denominator = (inputReserve * 100) + inputAmountWithFee;
return numerator / denominator;
}
交换的方法
function ethToUseWeb3Token(uint256 minTokens) public payable{
uint256 currentContractHoldUseweb3TokenReserve = getReserve();
uint256 tokensBought = getAmountOfTokens(msg.value, address(this).balance - msg.value, currentContractHoldUseweb3TokenReserve);
require(tokensBought >= minTokens, "insufficient output amount");
ERC20(useweb3TokenAddress).transfer(msg.sender, tokensBought);
}
function useWeb3TokenToEth(uint256 tokenSold, uint256 minEth) public{
uint256 currentContractHoldUseweb3TokenReserve = getReserve();
uint256 ethBought = getAmountOfTokens(tokenSold, currentContractHoldUseweb3TokenReserve, address(this).balance);
require(ethBought >= minEth, "insufficient output amount");
ERC20(useweb3TokenAddress).transferFrom(msg.sender, address(this), tokenSold);
payable(msg.sender).transfer(ethBought);
}
我们编写一个简单的前端代码来看一看合约运行的结果,需要注意的是,我们需要为交易合约授权。

const web3Provider = getWeb3Provider();
const signer = web3Provider.getSigner();
const exchangeContract = new ethers.Contract(USEWEB3_EXCHANGE_ADDRESS, USEWEB3_EXCHANGE_ABI, signer);
const tokenContract = new ethers.Contract(USEWEB3_TOKEN_ADDRESS, USEWEB3_TOKEN_ABI, signer);
const tokenTx = await tokenContract.approve(USEWEB3_EXCHANGE_ADDRESS, ethers.utils.parseEther("10"));
await tokenTx.wait();
然后初始添加流动性
const exchangeTx = await exchangeContract.addLiquidity(ethers.utils.parseEther("10"), {value: ethers.utils.parseEther("0.01")});
await exchangeTx.wait();

通过 https://rinkeby.etherscan.io/ 来观察最后的结果


