UNISWAP(V2) テストネットRopstenでETHからDAIの交換をしてみる!
こんにちはかずみょんです。今回はUniswapのコントラクトを叩いてETHをDAIに交換するコードを書いてみようと思います。まあ最初は何も分からんのでサンプルコードを探すところから始めようかな。(環境設定とかは前回の記事見てください。)
探したらあった~↓↓
このサイトの”3.Uniswap v2 Trading Bot Code”を参考にしながら書いてみます。その前に動作環境としていくつか事前準備が必要そう。
1.ETHのホスティングノード設定(infra.io)
Web3を動かすためにETHのホスティングノードの設定が必要そうです。下記サイトからサインアップしよう。
”CREATE PROJECT”をクリックし名前を入力すると下記画面になるので"ENDPOINT"を"ROPSTEN"にします。アクセスポイントが表示されました。(IDとかアドレスとかはまあバレてもいいんですがとりあえず黒塗りしときます)
"SAVE CHANGE"ボタンを押したらプロジェクトがアップデートされます。
2.テスト用Metamask作成
テスト用のMetamaskを新規に作成します。プライベートキーとか扱うためメインネットで使っているものをは使用しない方がよいと思います。私はメインはBraveブラウザーでMetamask使ってるんですが、テスト用にChromeブラウザーにMetamaskを作りました。
下記サイトで作ったETHアドレスを入力しテスト用ETHをもらいます。
MetamaskのネットワークをRopstenに設定し見てみると1ETH受領しています。全部で3ETHもありますね。
3.設定ファイルの作成
作業ディレクトリ下に"credentials.json"というファイルを新規作成します。このファイルに先ほど設定したETHホスティングノードのprojectID部分とMetamaskから取り出した秘密鍵を設定します。(アカウントの詳細から秘密鍵のエクスポートで取り出せます。
*絶対メインネットで使っているMetamaskからは取り出さないで下さいね)
{
"infuraKey": "ETHホスティングノードのprojectIDを設定",
"privateKey": "テスト用Metamaskの秘密鍵を設定"
}
次にRopstenにデプロイされているuniswapのコントラクトのABI(ABIとはコントラクトに実装された関数を呼び出すための仕様が定義されたもの)を設定するファイルを作ります。作業ディレクトリ下に "uniswapRouterABI.json"というファイルを新規作成します。
Ropstenのetherscanで実際に使用するuniswapのコントラクトアドレスを確認します。
https://ropsten.etherscan.io/address/0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D#code
このサイトの下の方に”Contract ABI"なるものがあります。これを全部コピーし、"uniswapRouterABI.json"にペーストします。
いちおうコードを置いときます。
[{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETHSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
4.ETHをWETHに換えてApproveしとく
よくよくコードを見るとWETHをDAIに換えるコードなので、予めETHをWETHに換えます。どうやって換えようか??
面倒くさいのでethscanにMetamask繋いで直接コントラクト叩きます。
https://ropsten.etherscan.io/address/0xc778417e063141139fce010982780140aa0cd5ab#writeContract
WETHのコントラクトアドレスを検索し、①Contract → ②Metamaskをコネクト → ③Deposit欄に換えたい数量を入力し”Write”ボタンをポチっとする → Metamaskの承認する
1WETHが入手できた。
後、WETHを交換するための自分の持っているWETHを転送する許可をuniswapのコントラクト側に与えておかなければなりません。WETHのコントラクト画面で先ほどは5番のdeposit欄でWETHを入手しましたが、次に1番のapproveを選択し最初にuniswapコントラクトアドレスを入力し2番目の欄に許可数量を入力し”Write”ボタンをポチっとしてMetamaskの承認します。
なんか結構、大変だね。ふぅ~。。
5.web3をインストールする
前回使った環境でやっちゃいます。作業ディレクトリ下で
npm install web3
"package.json”の中身をみるとインストールできました。
6.InfraのETHホスティングノードに秘密鍵をインポートする
やっとコード書いて順番に実行していくことができるようになった~。
まず,Web3のセッティングして秘密鍵のインポートしたら、インポートしたアドレスとETH残高を表示させてみよう。
//uniswap v2
//const UNISWAP = require('@uniswap/sdk')
const Web3 = require('web3')
const uniswapRouterABI = require('./uniswapRouterABI.json')
console.log('Network = Ropsten test net')
console.log('Web3 version = v' + Web3.version)
//const routerABI = require('./uniswapABI.json')
const credentials = require('./credentials.json')
const privateKey = credentials.privateKey
const routerAddress = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; // UniswapV2Router02
const wethAdd = '0xc778417e063141139fce010982780140aa0cd5ab' // Ropsten WETH
const daiAdd = '0xad6d458402f60fd3bd25163575031acdce07538d' // Ropsten DAI
//console.log('https://ropsten.infura.io/v3/' + credentials.infuraKey)
const web3 = new Web3('https://ropsten.infura.io/v3/' + credentials.infuraKey)
async function main () {
const activeAccount = await web3.eth.accounts.privateKeyToAccount(privateKey)
const account = activeAccount.address
console.log('account = ' + account)
let balance = await web3.eth.getBalance(account)
balance = web3.utils.fromWei(balance, 'ether')
console.log('balance(eth) = ' + balance)
}
main()
実行したら、テスト用で作ったMetamaskのアドレスと残高が表示されました(^^)v。
7.WETHをDAIに交換する
面倒くさいので、とりあえずコピペしちゃいます。動くかな??
//uniswap v2
//const UNISWAP = require('@uniswap/sdk')
const Web3 = require('web3')
const uniswapRouterABI = require('./uniswapRouterABI.json')
console.log('Network = Ropsten test net')
console.log('Web3 version = v' + Web3.version)
//const routerABI = require('./uniswapABI.json')
const credentials = require('./credentials.json')
const privateKey = credentials.privateKey
const routerAddress = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; // UniswapV2Router02
const wethAdd = '0xc778417e063141139fce010982780140aa0cd5ab' // Ropsten WETH
const daiAdd = '0xad6d458402f60fd3bd25163575031acdce07538d' // Ropsten DAI
//console.log('https://ropsten.infura.io/v3/' + credentials.infuraKey)
const web3 = new Web3('https://ropsten.infura.io/v3/' + credentials.infuraKey)
async function main () {
const activeAccount = await web3.eth.accounts.privateKeyToAccount(privateKey)
const account = activeAccount.address
console.log('account = ' + account)
let balance = await web3.eth.getBalance(account)
balance = web3.utils.fromWei(balance, 'ether')
console.log('balance(eth) = ' + balance)
const qty = new web3.utils.toBN(web3.utils.toWei('1'))
console.log('qty = ',qty.toString())
const routerContract = new web3.eth.Contract(uniswapRouterABI, routerAddress)
const expiryDate = Math.floor(Date.now() / 1000) + 900
let tx_builder = routerContract.methods.swapExactTokensForTokens(qty,0,[wethAdd, daiAdd],activeAccount.address,expiryDate)
let encoded_tx = tx_builder.encodeABI()
let transactionObject = {
gas: 238989,
data: encoded_tx,
from: account,
to: routerAddress
}
web3.eth.accounts.signTransaction(transactionObject, activeAccount.privateKey, (error, signedTx) => {
if (error) {
console.log(error)
} else {
web3.eth.sendSignedTransaction(signedTx.rawTransaction).on('receipt', (receipt) => {
console.log(receipt);
})
}
})
}
main()
動いた!!RopstenのEtherscanで確認すると
1ETHが328DAIに変換できました。1ETHが安い!!実際にはガスPriceとか設定せんといかんですね。他にもパラメーターがいくつかありそうなので実際に使うときはきちんとドキュメントに目を通した方がよいです。
まあとりあえず、やりたいことはできたかな!
8.まとめ
Node.jsを使ってコードからコントラクトを叩くことができました。DEXやCEX混合したアビトラもコーディングができそうな感じになってきました。やりだしたら何でもできてキリがないけど、だいたいみんなのやりたいことはアグリゲーターが実装してくれるはずですし。。だけど自分でやるのも結構おもしろいですよね。最後にUniswap smartcontractとWeb3の資料リンクしときます。
uniswap V2 smartcontract: https://uniswap.org/docs/v2/smart-contracts/factory
uniswap V3 smartcontract: https://docs.uniswap.org/reference/smart-contracts
wev3 document: https://web3js.readthedocs.io/en/v1.3.4/index.html
ではまた。