1. 准备工作
Uniswap 是以太坊最为流行的去中心化交易所,它的代码完全开源,本文将以 uniswap v2 版本为例,讲解如何将 uniswap v2 智能合约部署到以太坊 goerli 测试网络,并且搭建前端进行操作。uniswap-v2 版本智能合约部分的代码存放在 uniswap-v2-core 和 uniswap-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
,并将下面的代码拷贝进去。
注意:常量 endpoint 和 hexPrivateKey 请自行修改,并保证地址里面有足够的 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 官方的合约地址,所以需要进行如下修改:
- 修改
uniswap-interface/src/constants/index.ts
文件中 ROUTER_ADDRESS 的值为${UniswapV2Router02}
。 - 修改
uniswap-interface/src/state/swap/hooks.ts
文件中 BAD_RECIPIENT_ADDRESSES 数组的值为 [${UniswapV2Factory}
,${UniswapV2Router01}
,${UniswapV2Router02}
]。 - 修改
uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.cjs.development.js
和uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.esm.js
文件中 FACTORY_ADDRESS 为${UniswapV2Factory}
。 - 修改
uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.cjs.development.js
和uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.esm.js
文件中 INIT_CODE_HASH 为${INIT_CODE_HASH}
。 - 修改
uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.cjs.development.js
和uniswap-interface/node_modules/@uniswap/sdk/dist/sdk.esm.js
文件中全局变量 WETH,将其中key
为 GÖRLI 的Token
类型的地址修改为${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 查看效果。
- 点击 Connect to a wallet,选择 MetaMask 钱包(需要预先安装浏览器插件)。
- 切换钱包网络至 Goerli 测试网络,因为我们的智能合约部署在上面。
- 现在可以开始添加流动性或者交易了。
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 网络,然后点击 选择通证 按钮。
然后在输入框输入 tokens.json
的地址,点击 Add 按钮。
添加 tokens.json
成功后 Tokens List 就会出现在列表里面了,点击 Select 按钮添加代币列表。
添加成功,现在可以在交易所里面为 MATIC 代币添加流动性或者进行兑换了。
注意:添加流动性需要获取帐户里有一些 MATIC 代币,可以到 MATIC Faucet 进行领取。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。