資安

Uniswap協議對接教程【Ether | ERC20】

在這個教程中,我們將簡要地談論Uniswap的工作原理,並利用Uniswap協議構建一個簡單的Ether/ERC20兌換程序,你可以在此基礎上繼續擴展,從而讓用戶可以在你的Dapp或錢包中輕鬆的實現Ether/ERC20的兌換。

用自己熟悉的語言學習 以太坊DApp開發Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart

1、Uniswap概述

Uniswap是用於Ether和ERC20代幣的去中心化交換協議。整個過程在鏈上進行,沒有網守。每個ERC20代幣都有一個單獨的交換智能合約。這些交換合約持有以太幣及其相關的ERC20的儲備金。交易者可以隨時根據儲備進行交易。他們將以太幣發送給合同,並獲得ERC20代幣,反之亦然。這些準備金由流動性提供者存放,他們在每次交易中收取費用。也可以在ERC20令牌之間進行交換。

交易價格由定價公式自動確定:常量產品做市商模型。計算得出它總是恆定的。此公式也適用於始終恆定的ERC20代幣交換。

假設你有10ETH與1,000 DAI交換的合約。那麼常數將是10 1000 = 10,000(x y = k)。如果你發送1ETH給合約,那麼現在就11ETH在合約中。智能合約規定,以太幣乘以合同中的Dai必須保持不變。所以10,000 / 11 ≒ 909DAI應該在合約中。這也意味著合約不必具有1000DAI,因此它將返回1000 – 909 = 91DAI給買方,這是合約中現有金額與應包含的金額之間的差。

該公式通常也稱為x乘以y等於k公式。下圖顯示了價格如何根據x和y的值而變化:

在這裡插入圖片描述

Uniswap通過其儲備系統和自動定價公式降低了協調買賣雙方的成本。結果,交易者可以更容易,更快地交換代幣。

好吧,讓我們開始使用我們的應用程序。我們將構建一個交換Ether和Dai的簡單nodejs應用程序。你可以在github下載最終完成的代碼。不過在著手具體的代碼開發之前,我們需要先準備好開發環境。

2、Ether/ERC20 DEX應用開發環境準備

2.1 生成測試私鑰和地址

首先,從vanity-eth獲取測試用的私鑰和地址。點擊“生成”按鈕即可。注意,除測試目的外,請勿使用網頁生成你的私鑰和地址。

2.2 獲取測試用的以太幣

如果您在Rinkeby中沒有任何以太幣,可以利用以太幣水龍頭獲得一些。你可以通過搜索地址在Rinkeby Etherscan中檢查餘額。0.1ETH就足夠了。

2.3 創建一個新的Npm項目

mkdir eht-uniswap-demo
cd eht-uniswap-demo/
npm init --yes

如果沒有npm和nodejs,可以從這裡下載。

2.4 初始化git倉庫

很簡單,就一句話:

git init

2.5 安裝web3軟件包

npm i --save [email protected]

web3是以太坊JavaScript API。它是以太坊網絡(包括測試網)的接口。它與以太坊節點通信或通過JavaScript應用程序與部署在區塊鏈上的智能合約進行交易。

2.6 獲取Infura端點

簡單起見,我們將使用Infura作為與以太坊網絡進行通信的服務提供商,而不是自己從頭搭建一個以太坊節點。創建一個Infura帳戶,並創建一個新項目就可以獲得免費的Rinkeby的API端點。它看起來應該像這樣:

https://rinkeby.infura.io/v3/YOUR_PROJECT_ID

2.7 安裝ethereumjs-tx軟件包

npm i --save [email protected]

我們使用ethereumjs-tx創建並簽署交易。

3、用Ether兌換DAI

現在我們將開始編寫代碼實現用Ether兌換DAI。你可以點擊這裡查看完成的代碼。

3.1 創建新文件

創建一個新文件EthToDaiRinkeby.mjs。mjs是文件擴展名,允許你使用ES6語法編寫代碼。

3.2 導入包

import Web3 from "web3";
import EthTx from "ethereumjs-tx";

3.3 聲明合約地址和Abi

下面是Rinkeby測試網中的交換合約的地址和Dai合約的abi,你可以使用它們與交換智能合約進行交互。

Uniswap DAI交易所合約地址:

const daiExchangeAddress = "0x77dB9C915809e7BE439D2AB21032B1b8B58F6891";

你可以在Etherscan查看合約的詳細信息。

交換合約abi:

const daiExchangeAbi = '[{“name”: “TokenPurchase”, “inputs”: [{“type”: “address”, “name”: “buyer”, “indexed”: true}, {“type”: “uint256”, “name”: “eth_sold”, “indexed”: true}, {“type”: “uint256”, “name”: “tokens_bought”, “indexed”: true}], “anonymous”: false, “type”: “event”}, {“name”: “EthPurchase”, “inputs”: [{“type”: “address”, “name”: “buyer”, “indexed”: true}, {“type”: “uint256”, “name”: “tokens_sold”, “indexed”: true}, {“type”: “uint256”, “name”: “eth_bought”, “indexed”: true}], “anonymous”: false, “type”: “event”}, {“name”: “AddLiquidity”, “inputs”: [{“type”: “address”, “name”: “provider”, “indexed”: true}, {“type”: “uint256”, “name”: “eth_amount”, “indexed”: true}, {“type”: “uint256”, “name”: “token_amount”, “indexed”: true}], “anonymous”: false, “type”: “event”}, {“name”: “RemoveLiquidity”, “inputs”: [{“type”: “address”, “name”: “provider”, “indexed”: true}, {“type”: “uint256”, “name”: “eth_amount”, “indexed”: true}, {“type”: “uint256”, “name”: “token_amount”, “indexed”: true}], “anonymous”: false, “type”: “event”}, {“name”: “Transfer”, “inputs”: [{“type”: “address”, “name”: “_from”, “indexed”: true}, {“type”: “address”, “name”: “_to”, “indexed”: true}, {“type”: “uint256”, “name”: “_value”, “indexed”: false}], “anonymous”: false, “type”: “event”}, {“name”: “Approval”, “inputs”: [{“type”: “address”, “name”: “_owner”, “indexed”: true}, {“type”: “address”, “name”: “_spender”, “indexed”: true}, {“type”: “uint256”, “name”: “_value”, “indexed”: false}], “anonymous”: false, “type”: “event”}, {“name”: “setup”, “outputs”: [], “inputs”: [{“type”: “address”, “name”: “token_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 175875}, {“name”: “addLiquidity”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “min_liquidity”}, {“type”: “uint256”, “name”: “max_tokens”}, {“type”: “uint256”, “name”: “deadline”}], “constant”: false, “payable”: true, “type”: “function”, “gas”: 82605}, {“name”: “removeLiquidity”, “outputs”: [{“type”: “uint256”, “name”: “out”}, {“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “amount”}, {“type”: “uint256”, “name”: “min_eth”}, {“type”: “uint256”, “name”: “min_tokens”}, {“type”: “uint256”, “name”: “deadline”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 116814}, {“name”: “__default__”, “outputs”: [], “inputs”: [], “constant”: false, “payable”: true, “type”: “function”}, {“name”: “ethToTokenSwapInput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “min_tokens”}, {“type”: “uint256”, “name”: “deadline”}], “constant”: false, “payable”: true, “type”: “function”, “gas”: 12757}, {“name”: “ethToTokenTransferInput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “min_tokens”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “recipient”}], “constant”: false, “payable”: true, “type”: “function”, “gas”: 12965}, {“name”: “ethToTokenSwapOutput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_bought”}, {“type”: “uint256”, “name”: “deadline”}], “constant”: false, “payable”: true, “type”: “function”, “gas”: 50463}, {“name”: “ethToTokenTransferOutput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_bought”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “recipient”}], “constant”: false, “payable”: true, “type”: “function”, “gas”: 50671}, {“name”: “tokenToEthSwapInput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_sold”}, {“type”: “uint256”, “name”: “min_eth”}, {“type”: “uint256”, “name”: “deadline”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 47503}, {“name”: “tokenToEthTransferInput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_sold”}, {“type”: “uint256”, “name”: “min_eth”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “recipient”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 47712}, {“name”: “tokenToEthSwapOutput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “eth_bought”}, {“type”: “uint256”, “name”: “max_tokens”}, {“type”: “uint256”, “name”: “deadline”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 50175}, {“name”: “tokenToEthTransferOutput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “eth_bought”}, {“type”: “uint256”, “name”: “max_tokens”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “recipient”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 50384}, {“name”: “tokenToTokenSwapInput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_sold”}, {“type”: “uint256”, “name”: “min_tokens_bought”}, {“type”: “uint256”, “name”: “min_eth_bought”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “token_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 51007}, {“name”: “tokenToTokenTransferInput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_sold”}, {“type”: “uint256”, “name”: “min_tokens_bought”}, {“type”: “uint256”, “name”: “min_eth_bought”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “recipient”}, {“type”: “address”, “name”: “token_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 51098}, {“name”: “tokenToTokenSwapOutput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_bought”}, {“type”: “uint256”, “name”: “max_tokens_sold”}, {“type”: “uint256”, “name”: “max_eth_sold”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “token_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 54928}, {“name”: “tokenToTokenTransferOutput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_bought”}, {“type”: “uint256”, “name”: “max_tokens_sold”}, {“type”: “uint256”, “name”: “max_eth_sold”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “recipient”}, {“type”: “address”, “name”: “token_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 55019}, {“name”: “tokenToExchangeSwapInput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_sold”}, {“type”: “uint256”, “name”: “min_tokens_bought”}, {“type”: “uint256”, “name”: “min_eth_bought”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “exchange_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 49342}, {“name”: “tokenToExchangeTransferInput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_sold”}, {“type”: “uint256”, “name”: “min_tokens_bought”}, {“type”: “uint256”, “name”: “min_eth_bought”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “recipient”}, {“type”: “address”, “name”: “exchange_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 49532}, {“name”: “tokenToExchangeSwapOutput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_bought”}, {“type”: “uint256”, “name”: “max_tokens_sold”}, {“type”: “uint256”, “name”: “max_eth_sold”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “exchange_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 53233}, {“name”: “tokenToExchangeTransferOutput”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_bought”}, {“type”: “uint256”, “name”: “max_tokens_sold”}, {“type”: “uint256”, “name”: “max_eth_sold”}, {“type”: “uint256”, “name”: “deadline”}, {“type”: “address”, “name”: “recipient”}, {“type”: “address”, “name”: “exchange_addr”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 53423}, {“name”: “getEthToTokenInputPrice”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “eth_sold”}], “constant”: true, “payable”: false, “type”: “function”, “gas”: 5542}, {“name”: “getEthToTokenOutputPrice”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_bought”}], “constant”: true, “payable”: false, “type”: “function”, “gas”: 6872}, {“name”: “getTokenToEthInputPrice”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “tokens_sold”}], “constant”: true, “payable”: false, “type”: “function”, “gas”: 5637}, {“name”: “getTokenToEthOutputPrice”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “uint256”, “name”: “eth_bought”}], “constant”: true, “payable”: false, “type”: “function”, “gas”: 6897}, {“name”: “tokenAddress”, “outputs”: [{“type”: “address”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1413}, {“name”: “factoryAddress”, “outputs”: [{“type”: “address”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1443}, {“name”: “balanceOf”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “_owner”}], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1645}, {“name”: “transfer”, “outputs”: [{“type”: “bool”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “_to”}, {“type”: “uint256”, “name”: “_value”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 75034}, {“name”: “transferFrom”, “outputs”: [{“type”: “bool”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “_from”}, {“type”: “address”, “name”: “_to”}, {“type”: “uint256”, “name”: “_value”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 110907}, {“name”: “approve”, “outputs”: [{“type”: “bool”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “_spender”}, {“type”: “uint256”, “name”: “_value”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 38769}, {“name”: “allowance”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “_owner”}, {“type”: “address”, “name”: “_spender”}], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1925}, {“name”: “name”, “outputs”: [{“type”: “bytes32”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1623}, {“name”: “symbol”, “outputs”: [{“type”: “bytes32”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1653}, {“name”: “decimals”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1683}, {“name”: “totalSupply”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1713}]';

聲明地址和私鑰變量

const addressFrom = "[YOUR_ADDRESS]";
const privKey = "[YOUR_PRIVATE_KEY]";

3.4 設置Web3

設置Web3以將Infura用作Web3的提供器:

const web3 = new Web3(
  new Web3.providers.HttpProvider("https://rinkeby.infura.io/v3/[YOUR_PROJECT_ID]"
  )
);

3.5 實例化合約對象

可以使用Contract帶有地址和abi的web3方法來實例化合同。將這些值作為參數傳遞給web3.eth.Contract:

const daiExchangeContract = new web3.eth.Contract(JSON.parse(daiExchangeAbi), daiExchangeAddress);

現在我們準備調用交換合約功能來與Dai交換以太幣。

3.6 聲明ETH_SOLDConst變量

首先聲明一個const變量以指定要與Dai交換的以太幣的數量:

const ETH_SOLD = web3.utils.toHex(50000000000000000); // 0.05ETH

3.7 為ethToTokenSwapInput功能編碼Abi

我們使用合約的ethToTokenSwapInput方法與Dai交換以太幣。

function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought);

該方法需要兩個參數:min_tokens和deadline。min_tokens參數指定購買的Dai的最小數量。deadline聲明瞭交易的最後期限,意思是此交易只能在此時間之前執行。

這些選項可以減輕礦工搶佔先機的風險。假設你賣出1ETH,當前價格為200 DAI。你可以說:如果我得到的金額少於199 DAI,那麼我就希望交易失敗。 惡意的領跑者可能會將價格推高,但由於你已經制定了交易的執行價位,因此可以不受影響。截止日期可以防止礦工在對其更有利的時候執行交易,從而幫助你減輕風險。可以訪問這裡進一步瞭解有關交易搶跑的信息。

我們聲明一個傳遞給min_tokens參數的const變量:

const MIN_TOKENS = web3.utils.toHex(0.2 * 10 ** 18); // 0.2 DAI

你應該選擇一個不太低的金額,因為太低了就容易被搶跑者利用。但是也不能太高,因為太高的話,你的交易就很容易失敗。

我們聲明另一個傳遞給deadline參數的const變量。下面的數字是的Unix時間戳10/01/2019 @ 12:00am (UTC):

const DEADLINE = 1569888000; // 10/01/2019 @ 12:00am (UTC)

你可以使用這個在線工具將時間轉換為unix時間戳。

現在,將兩個const變量作為函數的參數傳入ethToTokenSwapInput併為其編碼abi:

const exchangeEncodedABI = daiExchangeContract.methods.ethToTokenSwapInput(MIN_TOKENS, DEADLINE).encodeABI();

3.8 聲明sendSignedTx函數

接下來,創建一個函數來簽名交易對象,然後將其廣播到以太坊網絡:

function sendSignedTx(transactionObject, cb) {
  let transaction = new EthTx(transactionObject);
  const privateKey = new Buffer.from(privKey, "hex");
  transaction.sign(privateKey);
  const serializedEthTx = transaction.serialize().toString("hex");
  web3.eth.sendSignedTransaction(`0x${serializedEthTx}`, cb);
}

構造一個事務對象然後執行 sendSignedTx

web3.eth.getTransactionCount(addressFrom).then(transactionNonce => {
  const transactionObject = {
    chainId: 4,
    nonce: web3.utils.toHex(transactionNonce),
    gasLimit: web3.utils.toHex(6000000),
    gasPrice: web3.utils.toHex(10000000000),
    to: daiExchangeAddress,
    from: addressFrom,
    data: exchangeEncodedABI,
    value: ETH_SOLD
  };

sendSignedTx(transactionObject, function(error, result){
    if(error) return console.log("error ===> ", error);
    console.log("sent ===> ", result);
  })
}
);

3.9 運行代碼

npm run eth-to-dai-rinkeby

一旦獲得了這樣的交易哈希:0xbbc617c9.......92bec4839e10,就可以轉到Etherscan查看交易的詳細信息。下面以我的交易為例:

在這裡插入圖片描述

如果交易成功確認,你就可以從Erc20代幣的Txns選項卡中看到在你的地址中收到了一些DAI。

在這裡插入圖片描述

4、用dai兌換ether

接下來讓我們用DAI兌換ether。這會略微複雜一些。我們將進行兩次交易。首先,我們需要授權Uniswap Dai交易合約可以代表我們的帳戶消費Dai。第二,我們調用合約的tokenToEthSwapInput函數將DAI兌換為Ether。

4.1 授權Uniswap DAI交換合約

我們為此製作另一個文件:approveDaiExchangeRinkeby.mjs。由於大多數代碼都是相同的,因此我將跳過細節。你可以在此處查看完成的代碼。

4.2 聲明代幣合約地址和Abi變量

這是Rinkeby測試網中的Dai代幣合約的地址和abi:

代幣合約地址:

const daiTokenAddress = “0x2448eE2641d78CC42D7AD76498917359D961A783”;

代幣合約abi:

const daiTokenAbi = '[{“name”: “Transfer”, “inputs”: [{“type”: “address”, “name”: “_from”, “indexed”: true}, {“type”: “address”, “name”: “_to”, “indexed”: true}, {“type”: “uint256”, “name”: “_value”, “indexed”: false}], “anonymous”: false, “type”: “event”}, {“name”: “Approval”, “inputs”: [{“type”: “address”, “name”: “_owner”, “indexed”: true}, {“type”: “address”, “name”: “_spender”, “indexed”: true}, {“type”: “uint256”, “name”: “_value”, “indexed”: false}], “anonymous”: false, “type”: “event”}, {“outputs”: [], “inputs”: [{“type”: “string”, “name”: “_name”}, {“type”: “string”, “name”: “_symbol”}, {“type”: “uint256”, “name”: “_decimals”}, {“type”: “uint256”, “name”: “_supply”}], “constant”: false, “payable”: false, “type”: “constructor”}, {“name”: “transfer”, “outputs”: [{“type”: “bool”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “_to”}, {“type”: “uint256”, “name”: “_value”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 74020}, {“name”: “transferFrom”, “outputs”: [{“type”: “bool”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “_from”}, {“type”: “address”, “name”: “_to”}, {“type”: “uint256”, “name”: “_value”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 110371}, {“name”: “approve”, “outputs”: [{“type”: “bool”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “_spender”}, {“type”: “uint256”, “name”: “_value”}], “constant”: false, “payable”: false, “type”: “function”, “gas”: 37755}, {“name”: “name”, “outputs”: [{“type”: “string”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 6402}, {“name”: “symbol”, “outputs”: [{“type”: “string”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 6432}, {“name”: “decimals”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 663}, {“name”: “totalSupply”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [], “constant”: true, “payable”: false, “type”: “function”, “gas”: 693}, {“name”: “balanceOf”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “arg0”}], “constant”: true, “payable”: false, “type”: “function”, “gas”: 877}, {“name”: “allowance”, “outputs”: [{“type”: “uint256”, “name”: “out”}], “inputs”: [{“type”: “address”, “name”: “arg0”}, {“type”: “address”, “name”: “arg1”}], “constant”: true, “payable”: false, “type”: “function”, “gas”: 1061}]';

注意:ERC20代幣的ABI都是相同的。

4.3 聲明daiExchangeAddress變量

const daiExchangeAddress = "0x77dB9C915809e7BE439D2AB21032B1b8B58F6891";

我們將授權此地址代表我們的帳戶進行Dai的轉賬。

4.4 實例化Dai代幣合約

const daiTokenContract = new web3.eth.Contract(
  JSON.parse(daiTokenAbi),
  daiTokenAddress
);

聲明常量以傳遞給approve函數

const ADDRESS_SPENDER = daiExchangeAddress;
const TOKENS = web3.utils.toHex(1 * 10 ** 18); // 1 DAI

我們授權daiExchangeAddress可以操作我們賬戶的1 DAI。

4.5 Approve函數調用的ABI編碼

const approveEncodedABI = daiTokenContract.methods
  .approve(ADDRESS_SPENDER, TOKENS)
  .encodeABI();

4.6 聲明sendSignedTx函數

下面代碼創建一個函數來簽名交易對象,然後將其廣播到以太坊網絡:

function sendSignedTx(transactionObject, cb) {
  let transaction = new EthTx(transactionObject);
  const privateKey = new Buffer.from(privKey, "hex");
  transaction.sign(privateKey);
  const serializedEthTx = transaction.serialize().toString("hex");
  web3.eth.sendSignedTransaction(`0x${serializedEthTx}`, cb);
}

下面代碼構造一個事務對象然後執行 sendSignedTx:

web3.eth.getTransactionCount(addressFrom).then(transactionNonce => {
  const transactionObject = {
    chainId: 4,
    nonce: web3.utils.toHex(transactionNonce),
    gasLimit: web3.utils.toHex(42000),
    gasPrice: web3.utils.toHex(5000000),
    to: daiTokenAddress,
    from: addressFrom,
    data: approveEncodedABI
  };

sendSignedTx(transactionObject, function(error, result){
    if(error) return console.log("error ===>", error);
    console.log("sent ===>", result);
  })
}
);

4.7 運行代碼文件

npm run approve-dai-exchange-rinkeby

一旦獲得了這樣的交易哈希:0x1d16e0......1f1d0f44e5262d0e8f018a,就可以轉到Etherscan查看交易的詳細信息。下面以我的交易為例:

在這裡插入圖片描述

4.8 用DAI兌換以太幣

最後,讓我們完成用DAI兌換以太幣的代碼。我們將跳過細節,因為大多數代碼將是相同的。創建另一個文件DaiToEthRinkeby.mjs,然後調用合約的tokenToEthSwapInput函數。

function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256 eth_bought);

該函數需要三個參數。tokens_sold是要售出的ERC20代幣的數量。min_eth是至少應該得到以太幣數量。deadline是交易截止時間戳。

const TOKENS_SOLD = web3.utils.toHex(0.4 * 10 ** 18); // 0.4DAI
const MIN_ETH = web3.utils.toHex(5000000000000000); // 0.005ETH
const DEADLINE = 1569888000; // 10/01/2019 @ 12:00am (UTC)

現在,將三個const變量作為函數的參數傳入tokenToEthSwapInput並進行ABI編碼:

const tokenToEthEncodedABI = daiExchangeContract.methods
  .tokenToEthSwapInput(TOKENS_SOLD, MIN_ETH, DEADLINE)
  .encodeABI();

4.9 運行代碼文件

運行文件:

npm run dai-to-eth-rinkeby

獲得交易哈希後,你就可以轉到Etherscan查看交易的詳細信息。下面以我的交易為例:

在這裡插入圖片描述

現在,你可以看到你的以太坊地址中DAI減少了:

在這裡插入圖片描述

並且以太幣被添加到你的地址:

在這裡插入圖片描述

5、集成其他ERC20代幣

如果要集成其他ERC20代幣以進行交換,只需要更改代碼中的代幣地址就可以實現了。


原文鏈接:用Uniswap打造自己的ETH&ERC20交易所 — 匯智網

Leave a Reply

Your email address will not be published. Required fields are marked *