在這個教程中,我們將簡要地談論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代幣以進行交換,只需要更改代碼中的代幣地址就可以實現了。