diff --git a/.env.example b/.env.example index 07f4d81..259452b 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,6 @@ -# Configuration file - APPNAME=Hummingbot Gateway API NODE_ENV=dev PORT=5000 -PROTOCOLS=["celo", "terra", "balancer", "eth"] # use only if ip whitelist is required for local or docker instance # note that docker instance does not use 127.0.0.1 address @@ -16,13 +13,14 @@ IP_WHITELIST= TERRA_LCD_URL=https://tequila-lcd.terra.dev TERRA_CHAIN=tequila-0004 -# Balancer +# Ethereum # - network: mainnet, kovan, etc # - rpc url: infura or other rpc url -BALANCER_NETWORK={network} +ETHEREUM CHAIN={network} ETHEREUM_RPC_URL=https://{network}.infura.io/v3/{api_key} -# subgraph_network: +# Balancer +# subgraph_network # Reference: https://docs.balancer.finance/sor/development#subgraph # - mainnet: balancer # - kovan: balancer-kovan @@ -31,10 +29,14 @@ REACT_APP_SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/balancer-labs/{su # exchange_proxy: # Reference: https://docs.balancer.finance/smart-contracts/addresses -# - kovan: 0x4e67bf5bD28Dd4b570FBAFe11D0633eCbA2754Ec # - mainnet: 0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21 +# - kovan: 0x4e67bf5bD28Dd4b570FBAFe11D0633eCbA2754Ec EXCHANGE_PROXY={exchange_proxy} +# Uniswap +# Reference: https://uniswap.org/docs/v2/smart-contracts/router02/ +UNISWAP_ROUTER=0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D + # cert CERT_PATH={full_path_to_certs_folder} CERT_PASSPHRASE={passphrase} diff --git a/package.json b/package.json index a592d2b..e5b8380 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { + "@uniswap/sdk": "^3.0.3", "@balancer-labs/sor": "^0.3.3", "@terra-money/terra.js": "^0.5.8", "bignumber.js": "^9.0.0", diff --git a/src/app.js b/src/app.js index c65bc44..a98af7b 100644 --- a/src/app.js +++ b/src/app.js @@ -11,6 +11,7 @@ import balancerRoutes from './routes/balancer.route' // import celoRoutes from './routes/celo.route' import ethRoutes from './routes/eth.route' import terraRoutes from './routes/terra.route' +import uniswapRoutes from './routes/uniswap.route' // terminate if environment not found const result = dotenv.config(); @@ -40,6 +41,7 @@ app.use('/eth', ethRoutes); // app.use('/celo', celoRoutes); app.use('/terra', terraRoutes); app.use('/balancer', balancerRoutes); +app.use('/uniswap', uniswapRoutes); app.get('/', (req, res, next) => { const cert = req.connection.getPeerCertificate() diff --git a/src/routes/balancer.route.js b/src/routes/balancer.route.js index 593d7ee..cd213e8 100644 --- a/src/routes/balancer.route.js +++ b/src/routes/balancer.route.js @@ -4,18 +4,16 @@ import express from 'express'; import { getParamData, latency, reportConnectionError, statusMessages } from '../services/utils'; import Balancer from '../services/balancer'; -import Ethereum from '../services/eth'; require('dotenv').config() const debug = require('debug')('router') const router = express.Router() -const balancer = new Balancer(process.env.BALANCER_NETWORK) -const eth = new Ethereum(process.env.BALANCER_NETWORK) +const balancer = new Balancer(process.env.ETHEREUM_CHAIN) const denomMultiplier = 1e18 -const swapMoreThanMaxPriceError = 'Swap price exceeds maxPrice' -const swapLessThanMaxPriceError = 'Swap price lower than maxPrice' +const swapMoreThanMaxPriceError = 'Price too high' +const swapLessThanMaxPriceError = 'Price too low' router.use((req, res, next) => { const cert = req.connection.getPeerCertificate() @@ -49,6 +47,7 @@ router.post('/sell-price', async (req, res) => { "quote":"0x....." "base":"0x....." "amount":0.1 + "swaps": 4 (optional) } */ const initTime = Date.now() @@ -57,6 +56,10 @@ router.post('/sell-price', async (req, res) => { const baseTokenAddress = paramData.base const quoteTokenAddress = paramData.quote const amount = new BigNumber(parseInt(paramData.amount * denomMultiplier)) + let maxSwaps + if (paramData.maxSwaps) { + maxSwaps = parseInt(paramData.maxSwaps) + } try { // fetch the optimal pool mix from balancer-sor @@ -64,6 +67,7 @@ router.post('/sell-price', async (req, res) => { baseTokenAddress, // tokenIn is base asset quoteTokenAddress, // tokenOut is quote asset amount, + maxSwaps, ) if (swaps != null && expectedOut != null) { @@ -109,6 +113,10 @@ router.post('/buy-price', async (req, res) => { const baseTokenAddress = paramData.base const quoteTokenAddress = paramData.quote const amount = new BigNumber(parseInt(paramData.amount * denomMultiplier)) + let maxSwaps + if (paramData.maxSwaps) { + maxSwaps = parseInt(paramData.maxSwaps) + } try { // fetch the optimal pool mix from balancer-sor @@ -116,6 +124,7 @@ router.post('/buy-price', async (req, res) => { quoteTokenAddress, // tokenIn is quote asset baseTokenAddress, // tokenOut is base asset amount, + maxSwaps, ) if (swaps != null && expectedIn != null) { res.status(200).json({ @@ -174,6 +183,10 @@ router.post('/sell', async (req, res) => { if (paramData.gasPrice) { gasPrice = parseFloat(paramData.gasPrice) } + let maxSwaps + if (paramData.maxSwaps) { + maxSwaps = parseInt(paramData.maxSwaps) + } const minAmountOut = maxPrice / amount * denomMultiplier debug('minAmountOut', minAmountOut) @@ -184,6 +197,7 @@ router.post('/sell', async (req, res) => { baseTokenAddress, // tokenIn is base asset quoteTokenAddress, // tokenOut is quote asset amount, + maxSwaps, ) const price = expectedOut / amount @@ -200,6 +214,8 @@ router.post('/sell', async (req, res) => { gasPrice, ) + debug(txObj) + // submit response res.status(200).json({ network: balancer.network, @@ -260,6 +276,10 @@ router.post('/buy', async (req, res) => { if (paramData.gasPrice) { gasPrice = parseFloat(paramData.gasPrice) } + let maxSwaps + if (paramData.maxSwaps) { + maxSwaps = parseInt(paramData.maxSwaps) + } try { // fetch the optimal pool mix from balancer-sor @@ -267,6 +287,7 @@ router.post('/buy', async (req, res) => { quoteTokenAddress, // tokenIn is quote asset baseTokenAddress, // tokenOut is base asset amount, + maxSwaps, ) const price = expectedIn / amount diff --git a/src/routes/eth.route.js b/src/routes/eth.route.js index ba72c37..77619ef 100644 --- a/src/routes/eth.route.js +++ b/src/routes/eth.route.js @@ -7,8 +7,10 @@ import Ethereum from '../services/eth'; const router = express.Router() const eth = new Ethereum(process.env.ETHEREUM_CHAIN) const separator = ',' -const spenders = { balancer: process.env.EXCHANGE_PROXY, - uniswap: process.env.UNISWAP_ROUTER } +const spenders = { + balancer: process.env.EXCHANGE_PROXY, + uniswap: process.env.UNISWAP_ROUTER +} const debug = require('debug')('router') diff --git a/src/routes/uniswap.route.js b/src/routes/uniswap.route.js new file mode 100644 index 0000000..992fb6c --- /dev/null +++ b/src/routes/uniswap.route.js @@ -0,0 +1,305 @@ +import BigNumber from 'bignumber.js'; +import { ethers } from 'ethers'; +import express from 'express'; + +import { getParamData, latency, statusMessages } from '../services/utils'; +import Uniswap from '../services/uniswap'; + +require('dotenv').config() +const debug = require('debug')('router') + +const router = express.Router() +const uniswap = new Uniswap(process.env.ETHEREUM_CHAIN) + +const denomMultiplier = 1e18 +const swapMoreThanMaxPriceError = 'Price too high' +const swapLessThanMaxPriceError = 'Price too low' + +router.use((req, res, next) => { + const cert = req.connection.getPeerCertificate() + if (req.client.authorized) { + next() + } else if (cert.subject) { + res.status(403).send({ error: statusMessages.ssl_cert_invalid }) + } else { + res.status(401).send({ error: statusMessages.ssl_cert_required }) + } +}) + +router.post('/', async (req, res) => { + /* + POST / + */ + res.status(200).json({ + network: uniswap.network, + provider: uniswap.provider.connection.url, + uniswap_router: uniswap.router, + connection: true, + timestamp: Date.now(), + }) +}) + +router.post('/sell-price', async (req, res) => { + /* + POST: /sell-price + x-www-form-urlencoded: { + "quote":"0x....." + "base":"0x....." + "amount":0.1 + } + */ + const initTime = Date.now() + // params: base (required), quote (required), amount (required) + const paramData = getParamData(req.body) + const baseTokenAddress = paramData.base + const quoteTokenAddress = paramData.quote + + try { + // fetch the optimal pool mix from uniswap + const route = await uniswap.fetch_route( + baseTokenAddress, // tokenIn is base asset + quoteTokenAddress, // tokenOut is quote asset + ) + + if (route != null) { + res.status(200).json({ + network: uniswap.network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + base: baseTokenAddress, + quote: quoteTokenAddress, + amount: parseFloat(paramData.amount), + expectedOut: parseFloat(paramData.amount * route.midPrice.toSignificant(8)), + price: route.midPrice.toSignificant(8), + swaps: route.path, + }) + } else { // no pool available + res.status(200).json({ + error: statusMessages.no_pool_available, + message: '' + }) + } + } catch (err) { + let reason + err.reason ? reason = err.reason : reason = statusMessages.operation_error + res.status(500).json({ + error: reason, + message: err + }) + } +}) + +router.post('/buy-price', async (req, res) => { + /* + POST: /buy-price + x-www-form-urlencoded: { + "quote":"0x....." + "base":"0x....." + "amount":0.1 + } + */ + const initTime = Date.now() + // params: base (required), quote (required), amount (required) + const paramData = getParamData(req.body) + const baseTokenAddress = paramData.base + const quoteTokenAddress = paramData.quote + + try { + // fetch the optimal pool mix from uniswap + const route = await uniswap.fetch_route( + quoteTokenAddress, // tokenIn is quote asset + baseTokenAddress, // tokenOut is base asset + ) + if (route != null) { + res.status(200).json({ + network: uniswap.network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + base: baseTokenAddress, + quote: quoteTokenAddress, + amount: parseFloat(paramData.amount), + expectedIn: parseFloat(paramData.amount * route.midPrice.invert().toSignificant(8)), + price: route.midPrice.invert().toSignificant(8), + swaps: route.path, + }) + } else { // no pool available + res.status(200).json({ + error: statusMessages.no_pool_available, + message: '' + }) + } + } catch (err) { + let reason + err.reason ? reason = err.reason : reason = statusMessages.operation_error + res.status(500).json({ + error: reason, + message: err + }) + } +}) + +router.post('/sell', async (req, res) => { + /* + POST: /sell + x-www-form-urlencoded: { + "quote":"0x....." + "base":"0x....." + "amount":0.1 + "minPrice":1 + "gasPrice":10 + "privateKey":{{privateKey}} + } + */ + const initTime = Date.now() + // params: privateKey (required), base (required), quote (required), amount (required), maxPrice (required), gasPrice (required) + const paramData = getParamData(req.body) + const privateKey = paramData.privateKey + const wallet = new ethers.Wallet(privateKey, uniswap.provider) + const baseTokenAddress = paramData.base + const quoteTokenAddress = paramData.quote + const amount = new BigNumber(parseInt(paramData.amount * denomMultiplier)) + + let maxPrice + if (paramData.maxPrice) { + maxPrice = parseFloat(paramData.maxPrice) + } + let gasPrice + if (paramData.gasPrice) { + gasPrice = parseFloat(paramData.gasPrice) + } + + const minAmountOut = maxPrice / amount + debug('minAmountOut', minAmountOut) + + try { + // fetch the optimal pool mix from uniswap + const route = await uniswap.fetch_route( + baseTokenAddress, // tokenIn is base asset + quoteTokenAddress, // tokenOut is quote asset + ) + + const price = route.midPrice.toSignificant(8) + debug(`Price: ${price.toString()}`) + if (!maxPrice || price >= maxPrice) { + // pass swaps to exchange-proxy to complete trade + const txObj = await uniswap.swapExactIn( + wallet, + route, + baseTokenAddress, + amount, + gasPrice, + ) + + // submit response + res.status(200).json({ + network: uniswap.network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + base: baseTokenAddress, + quote: quoteTokenAddress, + amount: parseFloat(paramData.amount), + expectedOut: parseFloat(paramData.amount * route.midPrice.toSignificant(8)), + price: price, + gasUsed: parseInt(txObj.gasUsed), + txHash: txObj.transactionHash, + status: txObj.status, + }) + } else { + res.status(200).json({ + error: swapLessThanMaxPriceError, + message: `Swap price ${price} lower than maxPrice ${maxPrice}` + }) + debug(`Swap price ${price} lower than maxPrice ${maxPrice}`) + } + } catch (err) { + let reason + err.reason ? reason = err.reason : reason = statusMessages.operation_error + res.status(500).json({ + error: reason, + message: err + }) + } +}) + +router.post('/buy', async (req, res) => { + /* + POST: /buy + x-www-form-urlencoded: { + "quote":"0x....." + "base":"0x....." + "amount":0.1 + "maxPrice":1 + "gasPrice":10 + "privateKey":{{privateKey}} + } + */ + const initTime = Date.now() + // params: privateKey (required), base (required), quote (required), amount (required), maxPrice (required), gasPrice (required) + const paramData = getParamData(req.body) + const privateKey = paramData.privateKey + const wallet = new ethers.Wallet(privateKey, uniswap.provider) + const baseTokenAddress = paramData.base + const quoteTokenAddress = paramData.quote + const amount = new BigNumber(parseInt(paramData.amount * denomMultiplier)) + + let maxPrice + if (paramData.maxPrice) { + maxPrice = parseFloat(paramData.maxPrice) + } + let gasPrice + if (paramData.gasPrice) { + gasPrice = parseFloat(paramData.gasPrice) + } + + try { + // fetch the optimal pool mix from uniswap + const route = await uniswap.fetch_route( + quoteTokenAddress, // tokenIn is quote asset + baseTokenAddress, // tokenOut is base asset + // amount, + ) + + const price = route.midPrice.invert().toSignificant(8) + debug(`Price: ${price.toString()}`) + if (!maxPrice || price <= maxPrice) { + // pass swaps to exchange-proxy to complete trade + const txObj = await uniswap.swapExactOut( + wallet, + route, + baseTokenAddress, + amount, + gasPrice, + ) + + // submit response + res.status(200).json({ + network: uniswap.network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + base: baseTokenAddress, + quote: quoteTokenAddress, + amount: parseFloat(paramData.amount), + expectedIn: parseFloat(paramData.amount * route.midPrice.invert().toSignificant(8)), + price: price, + gasUsed: parseInt(txObj.gasUsed), + txHash: txObj.transactionHash, + status: txObj.status, + }) + } else { + res.status(200).json({ + error: swapMoreThanMaxPriceError, + message: `Swap price ${price} exceeds maxPrice ${maxPrice}` + }) + debug(`Swap price ${price} exceeds maxPrice ${maxPrice}`) + } + } catch (err) { + let reason + err.reason ? reason = err.reason : reason = statusMessages.operation_error + res.status(500).json({ + error: reason, + message: err + }) + } +}) + +export default router; diff --git a/src/services/balancer.js b/src/services/balancer.js index 0698e3a..9aa3593 100644 --- a/src/services/balancer.js +++ b/src/services/balancer.js @@ -1,5 +1,4 @@ require('dotenv').config() // DO NOT REMOVE. needed to configure REACT_APP_SUBGRAPH_URL used by @balancer-labs/sor -const fs = require('fs'); const sor = require('@balancer-labs/sor') const BigNumber = require('bignumber.js') const ethers = require('ethers') @@ -7,30 +6,33 @@ const proxyArtifact = require('../static/ExchangeProxy.json') const debug = require('debug')('router') // constants -const MAX_UINT = ethers.constants.MaxUint256; const MULTI = '0xeefba1e63905ef1d7acba5a8513c70307c1ce441'; -const GAS_LIMIT = 1200000 +const EXCHANGE_PROXY = '0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21'; +const EXCHANGE_PROXY_KOVAN = '0x4e67bf5bD28Dd4b570FBAFe11D0633eCbA2754Ec'; +const MAX_UINT = ethers.constants.MaxUint256; +const MAX_SWAPS = 4; +const GAS_BASE = 200000; +const GAS_PER_SWAP = 100000; export default class Balancer { - constructor (network = 'kovan') { - // network defaults to kovan + constructor (network = 'mainnet') { const providerUrl = process.env.ETHEREUM_RPC_URL - this.network = process.env.BALANCER_NETWORK + this.network = process.env.ETHEREUM_CHAIN this.provider = new ethers.providers.JsonRpcProvider(providerUrl) - this.exchangeProxy = process.env.EXCHANGE_PROXY - if (network === 'kovan') { - // this.erc20Tokens = JSON.parse(fs.readFileSync('src/static/erc20_tokens_kovan.json')) - // this.exchangeProxy = '0x4e67bf5bD28Dd4b570FBAFe11D0633eCbA2754Ec' - } else if (network === 'mainnet') { - // this.erc20Tokens = JSON.parse(fs.readFileSync('src/static/erc20_tokens.json')) - // this.exchangeProxy = '0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21' - } else { - throw Error(`Invalid network ${network}`) + switch (network) { + case 'mainnet': + this.exchangeProxy = EXCHANGE_PROXY; + break; + case 'kovan': + this.exchangeProxy = EXCHANGE_PROXY_KOVAN; + break; + default: + throw Error(`Invalid network ${network}`) } } - async priceSwapIn (tokenIn, tokenOut, tokenInAmount) { + async priceSwapIn (tokenIn, tokenOut, tokenInAmount, maxSwaps = MAX_SWAPS) { // Fetch all the pools that contain the tokens provided const pools = await sor.getPoolsWithTokens(tokenIn, tokenOut) if (pools.pools.length === 0) { @@ -48,11 +50,11 @@ export default class Balancer { // Parse the pools and pass them to smart order outer to get the swaps needed const sorSwaps = sor.smartOrderRouter( - poolData, // balancers: Pool[] - 'swapExactIn', // swapType: string - tokenInAmount, // targetInputAmount: BigNumber - new BigNumber('4'), // maxBalancers: number - 0 // costOutputToken: BigNumber + poolData, // balancers: Pool[] + 'swapExactIn', // swapType: string + tokenInAmount, // targetInputAmount: BigNumber + new BigNumber(maxSwaps.toString()), // maxBalancers: number + 0 // costOutputToken: BigNumber ) const swapsFormatted = sor.formatSwapsExactAmountIn(sorSwaps, MAX_UINT, 0) @@ -75,7 +77,7 @@ export default class Balancer { return { swaps, expectedOut } } - async priceSwapOut (tokenIn, tokenOut, tokenOutAmount) { + async priceSwapOut (tokenIn, tokenOut, tokenOutAmount, maxSwaps = MAX_SWAPS) { // Fetch all the pools that contain the tokens provided const pools = await sor.getPoolsWithTokens(tokenIn, tokenOut) if (pools.pools.length === 0) { @@ -93,11 +95,11 @@ export default class Balancer { // Parse the pools and pass them to smart order outer to get the swaps needed const sorSwaps = sor.smartOrderRouter( - poolData, // balancers: Pool[] - 'swapExactOut', // swapType: string - tokenOutAmount, // targetInputAmount: BigNumber - new BigNumber('4'), // maxBalancers: number - 0 // costOutputToken: BigNumber + poolData, // balancers: Pool[] + 'swapExactOut', // swapType: string + tokenOutAmount, // targetInputAmount: BigNumber + new BigNumber(maxSwaps.toString()), // maxBalancers: number + 0 // costOutputToken: BigNumber ) const swapsFormatted = sor.formatSwapsExactAmountOut(sorSwaps, MAX_UINT, MAX_UINT) const expectedIn = sor.calcTotalInput(swapsFormatted, poolData) @@ -120,6 +122,7 @@ export default class Balancer { } async swapExactIn (wallet, swaps, tokenIn, tokenOut, amountIn, minAmountOut, gasPrice) { + debug(`Number of swaps: ${swaps.length}`); const contract = new ethers.Contract(this.exchangeProxy, proxyArtifact.abi, wallet) const tx = await contract.batchSwapExactIn( swaps, @@ -129,7 +132,7 @@ export default class Balancer { 0, { gasPrice: gasPrice * 1e9, - gasLimit: GAS_LIMIT + gasLimit: GAS_BASE + swaps.length * GAS_PER_SWAP, } ) debug(`Tx Hash: ${tx.hash}`); @@ -138,6 +141,7 @@ export default class Balancer { } async swapExactOut (wallet, swaps, tokenIn, tokenOut, expectedIn, gasPrice) { + debug(`Number of swaps: ${swaps.length}`); const contract = new ethers.Contract(this.exchangeProxy, proxyArtifact.abi, wallet) const tx = await contract.batchSwapExactOut( swaps, @@ -146,7 +150,7 @@ export default class Balancer { expectedIn, { gasPrice: gasPrice * 1e9, - gasLimit: GAS_LIMIT + gasLimit: GAS_BASE + swaps.length * GAS_PER_SWAP, } ) debug(`Tx Hash: ${tx.hash}`) diff --git a/src/services/uniswap.js b/src/services/uniswap.js new file mode 100644 index 0000000..11ebde3 --- /dev/null +++ b/src/services/uniswap.js @@ -0,0 +1,102 @@ +const uni = require('@uniswap/sdk') +const ethers = require('ethers') +const proxyArtifact = require('../static/uniswap_v2_router_abi.json') +const debug = require('debug')('router') + +// constants +const ROUTER = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; +const GAS_LIMIT = 1200000; +const TTL = 60; + +export default class Uniswap { + constructor (network = 'mainnet') { + const providerUrl = process.env.ETHEREUM_RPC_URL + this.network = process.env.ETHEREUM_CHAIN + this.provider = new ethers.providers.JsonRpcProvider(providerUrl) + this.router = ROUTER; + this.allowedSlippage = new uni.Percent('0', '100') + + switch (network) { + case 'mainnet': + this.chainID = uni.ChainId.MAINNET; + break; + case 'kovan': + this.chainID = uni.ChainId.KOVAN; + break; + default: + throw Error(`Invalid network ${network}`) + } + } + + async fetch_route(tokenIn, tokenOut){ + var route, pair, pairOne, pairTwo + var tIn = await uni.Fetcher.fetchTokenData(this.chainID, tokenIn) + var tOut = await uni.Fetcher.fetchTokenData(this.chainID, tokenOut) + + try { + pair = await uni.Fetcher.fetchPairData(tIn, tOut) + route = new uni.Route([pair], tIn, tOut) + } + catch(err) { + console.log('Trying alternative/indirect route.') + pairOne = await uni.Fetcher.fetchPairData(tIn, uni.WETH[this.chainID]) + pairTwo = await uni.Fetcher.fetchPairData(tOut, uni.WETH[this.chainID]) + route = new uni.Route([pairOne, pairTwo], tIn, tOut) + } + return route + } + + async swapExactIn (wallet, route, tokenAddress, amountIn, gasPrice) { + const tIn = await uni.Fetcher.fetchTokenData(this.chainID, tokenAddress) + const tokenAmountIn = new uni.TokenAmount(tIn, amountIn) + const result = uni.Router.swapCallParameters( + uni.Trade.exactIn(route, tokenAmountIn), + { + ttl: TTL, + recipient: wallet.address, + allowedSlippage: this.allowedSlippage + } + ) + + const contract = new ethers.Contract(this.router, proxyArtifact.abi, wallet) + const tx = await contract.[result.methodName]( + ...result.args, + { + gasPrice: gasPrice * 1e9, + gasLimit: GAS_LIMIT, + value: result.value + } + ) + + debug(`Tx Hash: ${tx.hash}`); + const txObj = await tx.wait() + return txObj + } + + async swapExactOut (wallet, route, tokenAddress, amountOut, gasPrice) { + const tOut = await uni.Fetcher.fetchTokenData(this.chainID, tokenAddress) + const tokenAmountOut = new uni.TokenAmount(tOut, amountOut) + const result = uni.Router.swapCallParameters( + uni.Trade.exactOut(route, tokenAmountOut), + { + ttl: TTL, + recipient: wallet.address, + allowedSlippage: this.allowedSlippage + } + ) + + const contract = new ethers.Contract(this.router, proxyArtifact.abi, wallet) + const tx = await contract.[result.methodName]( + ...result.args, + { + gasPrice: gasPrice * 1e9, + gasLimit: GAS_LIMIT, + value: result.value + } + ) + + debug(`Tx Hash: ${tx.hash}`); + const txObj = await tx.wait() + return txObj + } +} diff --git a/src/static/uniswap_v2_router_abi.json b/src/static/uniswap_v2_router_abi.json new file mode 100644 index 0000000..e2eac42 --- /dev/null +++ b/src/static/uniswap_v2_router_abi.json @@ -0,0 +1,1924 @@ +{ + "abi": [ + { + "inputs": [], + "name": "WETH", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "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": "pure", + "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" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "deployedBytecode": { + "immutableReferences": {}, + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + } + }, + "interface": [ + { + "inputs": [], + "name": "WETH", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "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": "pure", + "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" + } + ], + "bytecode": "" +} diff --git a/yarn.lock b/yarn.lock index e1a4de4..f007dde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1256,6 +1256,24 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ== +"@uniswap/sdk@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-3.0.3.tgz#8201c7c72215d0030cb99acc7e661eff895c18a9" + integrity sha512-t4s8bvzaCFSiqD2qfXIm3rWhbdnXp+QjD3/mRaeVDHK7zWevs6RGEb1ohMiNgOCTZANvBayb4j8p+XFdnMBadQ== + dependencies: + "@uniswap/v2-core" "^1.0.0" + big.js "^5.2.2" + decimal.js-light "^2.5.0" + jsbi "^3.1.1" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + toformat "^2.0.0" + +"@uniswap/v2-core@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" + integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1429,6 +1447,11 @@ bech32@1.1.4, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + bignumber.js@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" @@ -1828,6 +1851,11 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: dependencies: ms "2.1.2" +decimal.js-light@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decimal.js@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" @@ -2881,6 +2909,11 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsbi@^3.1.1: + version "3.1.4" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.4.tgz#9654dd02207a66a4911b4e4bb74265bc2cbc9dd0" + integrity sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -3979,6 +4012,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +tiny-invariant@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + tiny-secp256k1@^1.1.3: version "1.1.5" resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.5.tgz#3dc37b9bf0fa5b4390b9fa29e953228810cebc18" @@ -3990,6 +4028,11 @@ tiny-secp256k1@^1.1.3: elliptic "^6.4.0" nan "^2.13.2" +tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -4007,6 +4050,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toformat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8" + integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ== + toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"