在以太坊测试网络部署 uniswap v2 去中心化交易所

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 钱包(需要预先安装浏览器插件)。
    在以太坊测试网络部署 uniswap v2 去中心化交易所_第1张图片
  2. 切换钱包网络至 Goerli 测试网络,因为我们的智能合约部署在上面。
    在以太坊测试网络部署 uniswap v2 去中心化交易所_第2张图片
  3. 现在可以开始添加流动性或者交易了。
    在以太坊测试网络部署 uniswap v2 去中心化交易所_第3张图片

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 网络,然后点击 选择通证 按钮。

在以太坊测试网络部署 uniswap v2 去中心化交易所_第4张图片

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

在以太坊测试网络部署 uniswap v2 去中心化交易所_第5张图片

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

在以太坊测试网络部署 uniswap v2 去中心化交易所_第6张图片

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

在以太坊测试网络部署 uniswap v2 去中心化交易所_第7张图片

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

你可能感兴趣的:(区块链以太坊智能合约)