完整部署uniswap 合约、前端教程

10/19 09:54
阅读数 145


参考链接 崔棉大师的教程

手把手教你部署自己的uniswap交易所

之前部署是跟着崔棉大师的教程走的,但是部署完了,没法实际使用,添加流动性还是交易会报错
这里主要是做补充;

前提条件

  • 自己有账号,且申请测试以太坊 (ropsten直接小狐狸 buy 打开链接领,rinkeby需要推特发链接再去领取)
  • 会使用 remix 部署合约
  • 部署前端需会使用 npm / yarn

部署合约

合约源代码
此处只部署routerV2

注意事项
在这里插入图片描述

部署工厂合约和路由合约时,EVM VERSION 选择 istanbul, COMPILER CONFIGURATION 中勾选 Enable optimization ;
WETH部署时 EVM VERSION 选择 default;
工厂合约和WETH合约可以直接部署,路由合约需要修改一个Code

部署工厂和WETH合约

该步骤略, 这两个直接部署即可
weth代码中添加以下代码,便于直接获取任意数量WETH,方便测试大额交易

    //直接获取WETH
    function mint(uint _value)public payable{
        balanceOf[msg.sender] += _value;
        Deposit(msg.sender, _value);
    }

部署路由合约(重要环节!!)

initCode
重要环节,我刚开始部署的时候就是这一步不清楚导致的部署的合约,无法使用, 至于为啥会不一样,不太清楚!

步骤1 获取字节码

编译工厂合约,获取pair的字节码, 看下图
在这里插入图片描述

获得类似这样的

得到以下结构的内容, 只需要object字段的内容; 复制
{
	"linkReferences": {},
	"object": "取这里的内容",
	"opcodes": "-",
	"sourceMap": "-"
}
步骤2 获得initCode

打开网址 http://emn178.github.io/online-tools/keccak_256.html

将刚才得到的object字段内容粘贴,选择input type HEX
如下图
在这里插入图片描述

步骤3 替换路由中的initCode

将 96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f
替换成
de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17 (步骤2中获取的)

// 路由中该代码
// calculates the CREATE2 address for a pair without making any external calls
    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(uint(keccak256(abi.encodePacked(
                hex'ff',
                factory,
                keccak256(abi.encodePacked(token0, token1)),
                hex'de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17' //init code hash
                //hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' //init code hash
            ))));
    }

编译部署即可

当前部署结果

添加流动性
ropsten
https://ropsten.etherscan.io/tx/0x90a860e95f2796b08985b02a1163eccb58efefd053e0a80ebf75cca8f7f5b8fa

rinkeby
https://rinkeby.etherscan.io/tx/0x4c82a23ec995bf404a98e39b490c5e7893945c4341495c7fe6de43b90e646aeb

新账号,所以rinkeby和ropsten两个测试网都是部署的以下地址

工厂
0x2CD020750216583CCF657a0949F0843ec1f73EFE
WETH
0x57E25a96A6dBA2cA02e9C96d08f672574c6E6B13
路由
0x9A36D38C6De905f969C172a85dD362E3Bc36B936
initCode
de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17
测试Token
0xa5BA457F1DfdCC3E65515E69E545292D203b0E76

另外吐槽下…
ropsten 获取测试币最简便,但是打包忒慢了
rinkeby 打包很快,不过要发推后再领, 有梯子的建议使用这个

部署前端

前端代码
可以clone最好, 太慢的话就直接下载zip解压
1、自行下载好源码
2、安装好yarn
3、修改代码



修改文件: 项目目录/uniswap-interface/src/constants/index.ts 第 6 行

export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' //修改成你的路由合约地址


前端代码可以打开IDE, 然后全局搜索替换成自己部署的信息,之后编译代码就行了!!!

//uniswap官方部署的信息
工厂
0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
WETH
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
路由
0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
initCode
96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f



将以上4个信息都替换成自己部署的合约地址

工厂
0x2CD020750216583CCF657a0949F0843ec1f73EFE
WETH
0x57E25a96A6dBA2cA02e9C96d08f672574c6E6B13
路由
0x9A36D38C6De905f969C172a85dD362E3Bc36B936
initCode
de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17

如要替换weth,需要注意环境替换

{
    mainnet:'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
    ropsten:'0xc778417E063141139Fce010982780140Aa0cD5Ab', ( "chainId": 3,)
    rinkeby:'0xc778417E063141139Fce010982780140Aa0cD5Ab', ("chainId": 4)
    goerli:'0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
    kovan:'0xd0A1E359811322d97991E03f863a0C30C2cF029C'
}

替换完后,编译

$ cd uniswap-interface
$ yarn
$ yarn start

//执行 yarn start 后跳出个网页 http://localhost:3000/#/swap

其他补充(懂solidity的可以看看)

如果会solidity,且看uniswap源码的,可以往下看看

崔棉大师有个Uniswap源码中文注解的文档,有需要的可以去购买

添加流动性

添加流动性需要输入两个token, 带ETH的方法,router会帮你转成WETH,最终实际就是该方法

 function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    )

交换方法

这里主要说明交易的方法
这里主要归纳为3个方法(带ETH的方法,router会帮你转成WETH,最终都是两个ERC20交换)

说明:

  • 交换方法中,不存在买卖的说法,只有in/out; 我下面的方法注释写买/卖是为了便于理解
  • 交换方法中所有参数in/out 都是相对于路由自己
//方法1 需要获取精确的输出,输入不确定金额(也可以理解买,如购买100个UNI TOKEN,需要未知weth)
function swapTokensForExactTokens(
        uint amountOut,//期望输出金额
        uint amountInMax,//最大输入  (如果到你打包的交易时,如果实际需要输入的金额大于该金额,交易失败!)
        address[] calldata path,
        address to,
        uint deadline
    )
    
//方法2 通过输入金额,输出不确定金额 (也可以理解为卖, 如卖掉100个UNI TOKEN,可以获得未知WETH)
function swapExactTokensForTokens(
        uint amountIn,//实际输入金额
        uint amountOutMin,//最小输出 (如果到打包你的交易时,实际输出小于该金额,交易失败!)
        address[] calldata path,
        address to,
        uint deadline
    )
//方法3 通过输入金额,输出不确定金额
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    )

此处说明上面方法2/3 的区别

方法3是以交易对实际获取到了多少代币,去调用交易对的交换
方法2是以用户调用转账输入的金额(该金额可能是错的,比如没有实际输入,或者扣了手续费),去调用交易对交换

比如黑币,在你转账的时候,扣除你20% 30%等
如果使用方法2 是无法成功 会提示UniswapV2: K
如果使用方法3,把amountOutMin填0,那么这个交易一定可以成功,哪怕交易对只给返回0.00000000001个以太坊

工具

 // returns sorted token addresses, used to handle return values from pairs sorted in this order
 //两个地址排序
    function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
        require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
    }

    // calculates the CREATE2 address for a pair without making any external calls
    // 计算交易对地址, 注意这个init code hash... 这是个坑
    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(uint(keccak256(abi.encodePacked(
                hex'ff',
                factory,
                keccak256(abi.encodePacked(token0, token1)),
                hex'de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17' // init code hash
            ))));
    }

    // fetches and sorts the reserves for a pair
    //获取当前储备量,返回值会根据你输入的token排序
    function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
        (address token0,) = sortTokens(tokenA, tokenB);
        (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
    }

    // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
    //添加流动性时,通过tokenA输入额,计算tokenB需要输入多少
    function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
        require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
        require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        amountB = amountA.mul(reserveB) / reserveA;
    }

    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
    //通过in计算out (后面详细说明)
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
        require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }
    
     // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
     //通过out 计算in (后面详细说明)
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
        require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint numerator = reserveIn.mul(amountOut).mul(1000);
        uint denominator = reserveOut.sub(amountOut).mul(997);
        amountIn = (numerator / denominator).add(1);
    }

in/out计算公式推导

此处主要说getAmountOut 和 getAmountIn 两个方法
上面工具中的两个方法内部算法是简化过的。

 /**
 *
 *
 * 推导公式
 * in 输入金额, out 输出金额
 * rIn tokenIn的流动性, rOut,tokenOut的流动性
 * fee 手续费,注:当前带入0.997   也就是997/1000
 *
 * 两个计算公式实际是一样的, 只是一个求in,一个求out
 * (rIn + in * f) * (rOut - out) = rIn * rOut
 *
 *
 * 由out计算in
 * (rIn + in * f) * (rOut - out) = rIn * rOut
 * rIn * rOut + in * f * rOut  - rIn * out - in * f * out = rIn * rOut
 * rIn * out = in * f * rOut - in * f * out
 * in = rIn * out / (f * (rOut - out)) + 1  (尾部的 +1应该是避免精度计算,最后一位小了,会成交不了)
 *
 *
 * 由in计算out
 * (rIn + in * f) * (rOut - out) = rIn * rOut
 * rIn * rOut + in * f * rOut  - rIn * out - in * f * out = rIn * rOut
 * in * f * rOut = rIn * out + in * f * out
 * out = in * f * rOut / rIn + in *f
 * 
 */

待补充

另外,交易对中,还有个错误 UniswapV2: K 提示

该提示主要是校验交换的金额是否正确,如果黑币进出账少了,或者外部修改手续费也无法交易
暂时没弄清楚简化公式怎么来的


展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部