2
头图

1. 准备工作

Uniswap 是以太坊最为流行的去中心化交易所,它的代码完全开源,本文将以 uniswap v2 版本为例,讲解如何将 uniswap v2 智能合约部署到以太坊 goerli 测试网络,并且搭建前端进行操作。uniswap-v2 版本智能合约部分的代码存放在 uniswap-v2-coreuniswap-v2-periphery 两个仓库,编译智能合约需要 node@>=10 版本。

首先安装 nodejs,并指定版本 node@>=10

sudo apt update -y
sudo apt install curl git npm -y
sudo npm install -g n yarn -y
sudo n 10 && PATH="$PATH"

然后将 clone 两个智能合约的代码仓库到本地:

git clone https://github.com/Uniswap/uniswap-v2-core.git
git clone https://github.com/Uniswap/uniswap-v2-periphery.git

以及 clone uniswap-interface 前端仓库,把 tag 切换到 v2.6.5,因为后续的版本推出了 UNI 代币和治理功能,这里不进行部署。

git clone https://github.com/Uniswap/uniswap-interface.git
cd uniswap-interface && git checkout v2.6.5

我们还需要准备一个开放了 JSON RPC API 的以太坊节点,嫌麻烦可以去 https://infura.io 申请一个免费的 API Key。以及一个拥有足够 ETH 余额的以太坊地址,goerli 测试网络可以打开 https://faucet.goerli.mudit.blog 水龙头为你的地址获取测试的 ETH 代币。

现在准备工作完成了,下面开始编译并且部署智能合约。

2. 部署合约

由于智能合约代码存放在两个仓库,不便统一部署,我们先创建一个文件夹 uniswap-contracts 保存后续编译的智能合约代码。

mkdir uniswap-contracts

当前的目录结构如下:

root@54ab88b47042:~# ls
uniswap-contracts  uniswap-interface  uniswap-v2-core  uniswap-v2-periphery

接下来我们分别编译两个项目的智能合约代码,然后拷贝到 uniswap-contracts 目录。

2.1 编译合约

首先是 uniswap-v2-core 项目,进入目录后拉取依赖然后编译:

cd uniswap-v2-core
yarn && yarn compile

编译后的代码存放在 build 目录,我们需要把它拷贝至之前创建的 uniswap-contracts 目录。

cp -r build ../uniswap-contracts
cd ..

接下来编译 uniswap-v2-periphery 项目,也是相同的步骤,最后将编译后的代码拷贝到 uniswap-contracts 目录:

cd uniswap-v2-periphery
yarn && yarn compile
cp -r build ../uniswap-contracts
cd ..

2.2 部署合约

编译好的合约代码我们已经全部拷贝到 uniswap-contracts 目录。接下来就是部署合约了,这一步稍微麻烦一些,需要我们编写一个脚本。

首先在 uniswap-contracts 目录下创建一个文本文件 deploy.js,并将下面的代码拷贝进去。

注意:常量 endpointhexPrivateKey 请自行修改,并保证地址里面有足够的 ETH 用于支付 GAS 费用。
const Web3 = require('web3')
const WETH9 = require('./build/WETH9.json')
const UniswapV2Pair = require('./build/UniswapV2Pair.json')
const UniswapV2Factory = require('./build/UniswapV2Factory.json')
const UniswapV2Router01 = require('./build/UniswapV2Router01.json')
const UniswapV2Router02 = require('./build/UniswapV2Router02.json')

const endpoint = 'https://goerli.infura.io/v3/5c5a4a14c82f4d6e852b7cc29b2cbb6e';
const hexPrivateKey = '0xfad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19';

async function sendTransaction(web3, chainId, account, data, nonce, gasPrice) {
    const message = {
        from: account.address,
        gas: 5000000,
        gasPrice: gasPrice,
        data: data.startsWith('0x') ? data : '0x' + data,
        nonce: nonce,
        chainId: chainId
    }
    const transaction = await account.signTransaction(message)
    return web3.eth.sendSignedTransaction(transaction.rawTransaction)
}

(async () => {
    const options = { timeout: 1000 * 30 }
    const web3 = new Web3(new Web3.providers.HttpProvider(endpoint, options))
    const account = web3.eth.accounts.privateKeyToAccount(hexPrivateKey)

    const chainId = await web3.eth.getChainId()
    const gasPrice = await web3.eth.getGasPrice()
    let nonce = await web3.eth.getTransactionCount(account.address)

    // deploy WETH contract
    let weth = null
    {
        const contract = new web3.eth.Contract(WETH9.abi)
        const data = contract.deploy({ data: WETH9.bytecode }).encodeABI()
        const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
        console.info('WETH:', weth = receipt.contractAddress)
        nonce = nonce + 1
    }

    // deploy UniswapV2Factory contract
    let factory = null
    {
        const contract = new web3.eth.Contract(UniswapV2Factory.abi)
        const options = { data: UniswapV2Factory.bytecode, arguments: [account.address] }
        const data = contract.deploy(options).encodeABI()
        const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
        console.info('UniswapV2Factory:', factory = receipt.contractAddress)
        nonce = nonce + 1
    }

    // deploy UniswapV2Router01 contract
    {
        const contract = new web3.eth.Contract(UniswapV2Router01.abi)
        const options = { data: UniswapV2Router01.bytecode, arguments: [factory, weth] }
        const data = contract.deploy(options).encodeABI()
        const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
        console.info('UniswapV2Router01:', receipt.contractAddress)
        nonce = nonce + 1
    }

    // deploy UniswapV2Router02 contract
    {
        const contract = new web3.eth.Contract(UniswapV2Router02.abi)
        const options = { data: UniswapV2Router02.bytecode, arguments: [factory, weth] }
        const data = contract.deploy(options).encodeABI()
        const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
        console.info('UniswapV2Router02:', receipt.contractAddress)
        nonce = nonce + 1
    }

    let data = UniswapV2Pair.bytecode
    if (!data.startsWith('0x')) data = '0x' + data
    console.info('INIT_CODE_HASH:', web3.utils.keccak256(data))
})()

然后再拉取依赖:

npm init -f && npm install web3

最后执行部署合约脚本:

node deploy.js 

稍等片刻终端就会输入部署后的合约地址了,如下所示:

root@7f704ee78d9d:~/uniswap-contracts# node deploy.js 
WETH: 0x3bBb875C856b5607DF7740fBd3a40B2B443D6597
UniswapV2Factory: 0xf94B5efD90972e5a5e77b5E3dE5236333CedCe6F
UniswapV2Router01: 0x41Db8E670e03d864A449ef1106537E6ca0C18dEC
UniswapV2Router02: 0x81A14364BF285aeA0BAFf5925670a4bDBD575E99
INIT_CODE_HASH: 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f

到这里合约就部署完成了,把终端输出的合约地址记录下来,部署前端的时候需要进行配置。

3. 部署前端

智能合约已经部署到测试网络,最后我们运行前端看看能否正常工作了吧。首先进入 uniswap-interface 目录然后拉取依赖:

cd uniswap-interface
yarn

由于我们部署了新的合约,而前端配置里面还是 uniswap 官方的合约地址,所以需要进行如下修改:

  1. 修改 uniswap-interface/src/constants/index.ts 文件中 ROUTER_ADDRESS 的值为 ${UniswapV2Router02}
  2. 修改 uniswap-interface/src/state/swap/hooks.ts文件中 BAD_RECIPIENT_ADDRESSES 数组的值为 [${UniswapV2Factory}, ${UniswapV2Router01}, ${UniswapV2Router02}]。
  3. 修改 uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.cjs.development.jsuniswap-interface/node_modules/@uniswap/sdk/dist/sdk.esm.js 文件中 FACTORY_ADDRESS${UniswapV2Factory}
  4. 修改 uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.cjs.development.jsuniswap-interface/node_modules/@uniswap/sdk/dist/sdk.esm.js 文件中 INIT_CODE_HASH${INIT_CODE_HASH}
  5. 修改 uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.cjs.development.jsuniswap-interface/node_modules/@uniswap/sdk/dist/sdk.esm.js 文件中全局变量 WETH,将其中 keyGÖRLIToken 类型的地址修改为 ${WETH}

修改完成之后运行前端程序:

yarn start
Starting the development server...

Browserslist: caniuse-lite is outdated. Please run:
npx browserslist@latest --update-db

Files successfully emitted, waiting for typecheck results...

Compiled successfully!

You can now view @uniswap/interface in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://172.23.227.86:3000

Note that the development build is not optimized.
To create a production build, use yarn build.

最后打开浏览器访问地址 http://localhost:3000 查看效果。

  1. 点击 Connect to a wallet,选择 MetaMask 钱包(需要预先安装浏览器插件)。
    1.png
  2. 切换钱包网络至 Goerli 测试网络,因为我们的智能合约部署在上面。
    2.png
  3. 现在可以开始添加流动性或者交易了。
    3.png

4. 添加代币列表

现在 uniswap v2 已经成功部署了,如果我们想要在交易所添加自己的代币该怎么办呢?下面我就来一步一步讲解如何添加自定义代币。

uniswap v2 前端显示的代币列表配置在 uniswap-interface/src/constants/lists.ts 文件中的 DEFAULT_LIST_OF_LISTS 常量数组,数组元素的值可以是一个 http地址ifps地址ENS name。地址返回结果必须是指定结构的 json 文件,我们可以通过向 DEFAULT_LIST_OF_LISTS 常量数组添加新的地址达到添加自定义代币的目的。

下面就来通过添加一个代币详细描述这个过程。

1. 创建 tokens.json 文件

文件格式如下:

{
    "name": "Test Tokens List",
    "version": {
        "major": 1,
        "minor": 0,
        "patch": 0
    },
    "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png",
    "timestamp": "2021-07-25 00:00:00.000+00:00",
    "tokens": [
        {
            "chainId": 5,
            "address": "0x499d11E0b6eAC7c0593d8Fb292DCBbF815Fb29Ae",
            "name": "Matic Token",
            "symbol": "MATIC",
            "decimals": 18,
            "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0/logo.png"
        }
    ]
}

tokens 字段是一个数组类型,它负责描述代币列表包含的所有代币。我们在里面添加了一个名为 Matic Token 的代币(当然也可以添加多个代币),符号是 MATIC,合约地址是 0x499d11E0b6eAC7c0593d8Fb292DCBbF815Fb29Ae。请注意里面有一个 chainId 字段,值为 5,这是因为 goerli 测试网络的 chainId 是 5,前端只有在钱包连接网络的 chainId 为 5 时才会显示这个代币。其它以太坊网络 chainId 的值请参考:https://besu.hyperledger.org/en/stable/Concepts/NetworkID-And-ChainID

2. 上传 tokens.json 文件

tokens.json 文件完成编辑后就可以上传至服务器了。随便上传到哪里都可以,比如你自己的 HTTP 文件服务器,只要能够公网访问就行。我将它上传到了 gist.github.com (需要翻墙),访问地址是:https://gist.githubusercontent.com/pygdev/ec497ed8bba8008c2512b3f241bfb5ef/raw/ac6d0286e3e5a39499a71575a065f16787782a70/tokens.json

3. 在前端添加 Tokens List

首先打开 uniswap v2 前端页面,连接钱包,并切换至 goerli 网络,然后点击 选择通证 按钮。

11.png

然后在输入框输入 tokens.json 的地址,点击 Add 按钮。

12.png

添加 tokens.json 成功后 Tokens List 就会出现在列表里面了,点击 Select 按钮添加代币列表。

13.png

添加成功,现在可以在交易所里面为 MATIC 代币添加流动性或者进行兑换了。

14.png

注意:添加流动性需要获取帐户里有一些 MATIC 代币,可以到 MATIC Faucet 进行领取。

anchor
5 声望4 粉丝