diff --git a/README.md b/README.md index 7417ad16..6d6f7378 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Rain Orderbook Arbitrage Bot -NodeJS app that clears Rain orderbook orders against major DeFi platforms liquidity by finding arbitrage trades for token pairs of orders details queried from a subgraph or from file containing array of `Order Struct`, bundling them as `takeOrders` and submitting them to [Rain GenericPoolOrderBookFlashBorrower contract](https://github.com/rainprotocol/rain.orderbook.flashborrower.zeroex). +NodeJS app that clears Rain orderbook orders against major DeFi platforms liquidity by finding arbitrage trades for token pairs of orders details queried from a subgraph or from file containing array of `Order Struct`, bundling them as `takeOrders` and submitting them to one of [Rain Arb Contracts](https://github.com/rainprotocol/rain.orderbook/tree/main/src/concrete). This app requires NodeJS v18 or higher to run and is docker ready. This app can also be run in Github Actions with a cron job, please read below for more details. @@ -211,15 +211,14 @@ const configOptions = { monthlyRatelimit : 1000000, // 0x monthly rate limit, only used for 0x mode hideSensitiveData : true, // set to true to hide sensitive data such as wallet private key or rpc url from apearing in logs maxProfit : true, // option to maximize profit for 'srouter' mode - maxRatio : true // option to maximize the maxIORatio in "srouter" mode - usePublicRpcs : false // option to fallback to public rpcs + maxRatio : true, // option to maximize the maxIORatio in "srouter" mode + usePublicRpcs : false, // option to fallback to public rpcs liquidityProviders : [ // list of liquidity providers for "router" mode to get quotes from (optional) "sushiswapv2", "uniswapv2" ] } -const clearOptions = { - prioritization : true, // clear better deals first +const clearOptions = {s gasCoveragePercentage : "500" // percentage of the transaction gas cost denominated in receiving ERC20 to be earned from the transaction in order for it to be successfull, as an example a value of 500 means atleast 5x the amount of transaction gas cost needs to be earned for the transaction to be successfull } diff --git a/arb-bot.js b/arb-bot.js index 89f7580f..6fc7428b 100755 --- a/arb-bot.js +++ b/arb-bot.js @@ -127,7 +127,8 @@ const arbRound = async options => { { orderHash : options.orderHash, orderOwner : options.orderOwner, - orderInterpreter: options.orderInterpreter + orderInterpreter: options.orderInterpreter, + shuffle : options.shuffle } ); await clear( @@ -147,6 +148,7 @@ const main = async argv => { const rpcs = [...options.rpc]; let roundGap = 10000; let rpcTurn = 0; + let shuffle = 0; if (options.repetitions) { if (/^\d+$/.test(options.repetitions)) repetitions = Number(options.repetitions); @@ -167,6 +169,7 @@ const main = async argv => { // eslint-disable-next-line no-constant-condition if (repetitions === -1) while (true) { options.rpc = rpcs[rpcTurn]; + options.shuffle = shuffle; try { await arbRound(options); console.log("\x1b[32m%s\x1b[0m", "Round finished successfully!"); @@ -178,10 +181,13 @@ const main = async argv => { } if (rpcTurn === rpcs.length - 1) rpcTurn = 0; else rpcTurn++; + if (shuffle === 3) shuffle = 0; + else shuffle++; await sleep(roundGap); } else for (let i = 1; i <= repetitions; i++) { options.rpc = rpcs[rpcTurn]; + options.shuffle = shuffle; try { await arbRound(options); console.log("\x1b[32m%s\x1b[0m", `Round ${i} finished successfully!`); @@ -195,6 +201,8 @@ const main = async argv => { } if (rpcTurn === rpcs.length - 1) rpcTurn = 0; else rpcTurn++; + if (shuffle === 3) shuffle = 0; + else shuffle++; await sleep(roundGap); } }; diff --git a/config.json b/config.json index d9bf290e..4720369e 100644 --- a/config.json +++ b/config.json @@ -121,6 +121,16 @@ "address": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", "decimals": 18 }, + "liquidityProviders": [ + "apeswap", + "dfyn", + "elk", + "jetswap", + "quickswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv3" + ], "stableTokens": [ { "symbol": "USDT", @@ -222,6 +232,16 @@ "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "decimals": 18 }, + "liquidityProviders": [ + "apeswap", + "curveswap", + "elk", + "pancakeswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv2", + "uniswapv3" + ], "stableTokens": [ { "symbol": "USDT", @@ -305,28 +325,6 @@ } ] }, - { - "network": "goerli", - "chainId": 5, - "explorer": "https://goerli.etherscan.io/", - "zeroEx": { - "apiUrl": "https://goerli.api.0x.org/", - "proxyAddress": "0xf91bb752490473b8342a3e964e855b9f9a2a668e" - }, - "curve": { - "pools": [] - }, - "nativeToken": { - "symbol": "ETH", - "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "decimals": 18 - }, - "nativeWrappedToken": { - "symbol": "WETH", - "address": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", - "decimals": 18 - } - }, { "network": "avalanche", "chainId": 43114, @@ -350,6 +348,12 @@ "address": "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7", "decimals": 18 }, + "liquidityProviders": [ + "elk", + "traderjoe", + "sushiswapv3", + "sushiswapv2" + ], "stableTokens": [ { "symbol": "USDT.e", @@ -436,6 +440,13 @@ "address": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", "decimals": 18 }, + "liquidityProviders": [ + "dfyn", + "elk", + "sushiswapv3", + "uniswapv3", + "sushiswapv2" + ], "stableTokens": [ { "symbol": "USDT", @@ -507,6 +518,14 @@ "address": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83", "decimals": 18 }, + "liquidityProviders": [ + "dfyn", + "elk", + "jetswap", + "spookyswap", + "sushiswapv3", + "sushiswapv2" + ], "stableTokens": [ { "symbol": "fUSDT", @@ -588,6 +607,16 @@ "address": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", "decimals": 18 }, + "liquidityProviders": [ + "apeswap", + "biswap", + "elk", + "jetswap", + "pancakeswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv3" + ], "stableTokens": [ { "symbol": "USDT", @@ -669,6 +698,10 @@ "address": "0x471EcE3750Da237f93B8E339c536989b8978a438", "decimals": 18 }, + "liquidityProviders": [ + "ubeswap", + "sushiswapv2" + ], "stableTokens": [ { "symbol": "MAI", @@ -700,6 +733,11 @@ "address": "0x4200000000000000000000000000000000000006", "decimals": 18 }, + "liquidityProviders": [ + "elk", + "sushiswapv3", + "uniswapv3" + ], "stableTokens": { } @@ -726,5 +764,27 @@ "address": "0x5B67676a984807a212b1c59eBFc9B3568a474F0a", "decimals": 18 } + }, + { + "network": "goerli", + "chainId": 5, + "explorer": "https://goerli.etherscan.io/", + "zeroEx": { + "apiUrl": "https://goerli.api.0x.org/", + "proxyAddress": "0xf91bb752490473b8342a3e964e855b9f9a2a668e" + }, + "curve": { + "pools": [] + }, + "nativeToken": { + "symbol": "ETH", + "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "decimals": 18 + }, + "nativeWrappedToken": { + "symbol": "WETH", + "address": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", + "decimals": 18 + } } ] \ No newline at end of file diff --git a/docs/html/curve.js.html b/docs/html/curve.js.html index 9f54f2df..218f30e2 100644 --- a/docs/html/curve.js.html +++ b/docs/html/curve.js.html @@ -34,7 +34,6 @@

Source: curve.js

getEthPrice, getDataFetcher, getActualPrice, - // estimateProfit, bundleTakeOrders } = require("./utils"); @@ -155,7 +154,7 @@

Source: curve.js

* @param {ethers.Signer} - The ethersjs signer * @param {boolean} sort - (optional) Sort based on best deals or not */ -const prepare = async(bundledOrders, availableSwaps, config, signer, sort = true) => { +const prepare = (bundledOrders, availableSwaps, config, signer) => { for (let i = 0; i < bundledOrders.length; i++) { let pairFormat; const bOrder = bundledOrders[i]; @@ -187,61 +186,62 @@

Source: curve.js

: pool.underlyingCoinsUnwrapped.findIndex( v => v.symbol === bOrder.sellTokenSymbol ); - try { - let rate; - // let cumulativeAmountFixed = ethers.constants.Zero; - // bOrder.takeOrders.forEach(v => { - // cumulativeAmountFixed = cumulativeAmountFixed.add(v.quoteAmount); - // }); - // const cumulativeAmount = cumulativeAmountFixed.div("1" + "0".repeat(18 - bOrder.sellTokenDecimals)); - if (pairFormat === "c") { - rate = await bOrder.poolContract.get_dy( - bOrder.sellTokenIndex, - bOrder.buyTokenIndex, - // cumulativeAmount - "1" + "0".repeat(bOrder.sellTokenDecimals) - ); - } - else { - rate = await bOrder.poolContract.get_dy_underlying( - bOrder.sellTokenIndex, - bOrder.buyTokenIndex, - // cumulativeAmount - "1" + "0".repeat(bOrder.sellTokenDecimals) - ); - } - // const rateFixed = rate.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - // const price = rateFixed.mul("1" + "0".repeat(18)).div(cumulativeAmountFixed); - const price = rate.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - bOrder.initPrice = price; - - console.log(`Current market price for ${pair}: ${ethers.utils.formatEther(price)}`); - console.log("Current ratio of the orders in this token pair:"); - bOrder.takeOrders.forEach(v => { - console.log(ethers.utils.formatEther(v.ratio)); - }); - bOrder.takeOrders = bOrder.takeOrders.filter( - v => price.gte(v.ratio) - ); - console.log("\n"); - } - catch(error) { - console.log(`>>> could not get price for this ${pair} due to:`); - console.log(error); - } + // try { + // let rate; + // // let cumulativeAmountFixed = ethers.constants.Zero; + // // bOrder.takeOrders.forEach(v => { + // // cumulativeAmountFixed = cumulativeAmountFixed.add(v.quoteAmount); + // // }); + // // const cumulativeAmount = cumulativeAmountFixed.div("1" + "0".repeat(18 - bOrder.sellTokenDecimals)); + // if (pairFormat === "c") { + // rate = await bOrder.poolContract.get_dy( + // bOrder.sellTokenIndex, + // bOrder.buyTokenIndex, + // // cumulativeAmount + // "1" + "0".repeat(bOrder.sellTokenDecimals) + // ); + // } + // else { + // rate = await bOrder.poolContract.get_dy_underlying( + // bOrder.sellTokenIndex, + // bOrder.buyTokenIndex, + // // cumulativeAmount + // "1" + "0".repeat(bOrder.sellTokenDecimals) + // ); + // } + // // const rateFixed = rate.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); + // // const price = rateFixed.mul("1" + "0".repeat(18)).div(cumulativeAmountFixed); + // const price = rate.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); + // bOrder.initPrice = price; + + // console.log(`Current market price for ${pair}: ${ethers.utils.formatEther(price)}`); + // console.log("Current ratio of the orders in this token pair:"); + // bOrder.takeOrders.forEach(v => { + // console.log(ethers.utils.formatEther(v.ratio)); + // }); + // bOrder.takeOrders = bOrder.takeOrders.filter( + // v => price.gte(v.ratio) + // ); + // console.log("\n"); + // } + // catch(error) { + // console.log(`>>> could not get price for this ${pair} due to:`); + // console.log(error); + // } } } - console.log( - ">>> Filtering bundled orders with lower ratio than current market price...", - "\n" - ); - bundledOrders = bundledOrders.filter(v => v.initPrice && v.takeOrders.length > 0); - if (sort) { - console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); - bundledOrders.sort( - (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 - ); - } + // console.log( + // ">>> Filtering bundled orders with lower ratio than current market price...", + // "\n" + // ); + // bundledOrders = bundledOrders.filter(v => v.initPrice && v.takeOrders.length > 0); + // if (sort) { + // console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); + // bundledOrders.sort( + // (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 + // ); + // } + bundledOrders = bundledOrders.filter(v => v.poolIndex !== undefined); return bundledOrders; }; @@ -252,20 +252,17 @@

Source: curve.js

* @param {any[]} ordersDetails - The order details queried from subgraph * @param {string} gasCoveragePercentage - (optional) The percentage of the gas cost to cover on each transaction * for it to be considered profitable and get submitted - * @param {boolean} prioritization - (optional) Prioritize better deals to get cleared first, default is true * @returns The report of details of cleared orders */ const curveClear = async( config, ordersDetails, - gasCoveragePercentage = "100", - prioritization = true + gasCoveragePercentage = "100" ) => { if ( gasCoveragePercentage < 0 || !Number.isInteger(Number(gasCoveragePercentage)) ) throw "invalid gas coverage percentage, must be an integer greater than equal 0"; - if (typeof prioritization !== "boolean") throw "invalid value for 'prioritization'"; const signer = config.signer; const arbAddress = config.arbAddress; @@ -278,10 +275,10 @@

Source: curve.js

// instantiating orderbook contract const orderbook = new ethers.Contract(orderbookAddress, orderbookAbi, signer); - let gasPrice = await signer.provider.getGasPrice(); - console.log( - "------------------------- Starting Clearing Process -------------------------", + "------------------------- Starting The", + "\x1b[32mCURVE.FI\x1b[0m", + "Mode -------------------------", "\n" ); console.log("\x1b[33m%s\x1b[0m", Date()); @@ -294,17 +291,12 @@

Source: curve.js

"------------------------- Bundling Orders -------------------------", "\n" ); bundledOrders = await bundleTakeOrders(ordersDetails, orderbook, arb); - console.log( - "------------------------- Getting Best Deals From Curve -------------------------", - "\n" - ); const availableSwaps = getAvailableSwaps(config); - bundledOrders = await prepare( + bundledOrders = prepare( bundledOrders, availableSwaps, config, - signer, - prioritization + signer ); } else { @@ -317,16 +309,14 @@

Source: curve.js

return; } - console.log( - "------------------------- Trying To Clear Bundled Orders -------------------------", - "\n" - ); - const report = []; - const dataFetcher = getDataFetcher(config, processLps(config.lps), !!config.usePublicRpc); + const dataFetcher = getDataFetcher( + config, + processLps(config.lps, config.chainId), + !!config.usePublicRpc + ); for (let i = 0; i < bundledOrders.length; i++) { try { - gasPrice = await signer.provider.getGasPrice(); console.log( `------------------------- Trying To Clear ${ bundledOrders[i].buyTokenSymbol @@ -476,14 +466,16 @@

Source: curve.js

} if (arbType === "order-taker") takeOrdersConfigStruct.data = exchangeData; - // console.log(">>> Estimating the profit for this token pair...", "\n"); - const ethPrice = await getEthPrice( - config, - bundledOrders[i].buyToken, - bundledOrders[i].buyTokenDecimals, - gasPrice, - dataFetcher - ); + const gasPrice = await signer.provider.getGasPrice(); + const ethPrice = gasCoveragePercentage !== "0" + ? "0" + : await getEthPrice( + config, + bundledOrders[i].buyToken, + bundledOrders[i].buyTokenDecimals, + gasPrice, + dataFetcher + ); if (ethPrice === undefined) console.log("can not get ETH price, skipping...", "\n"); else { const rawtx = { @@ -505,48 +497,9 @@

Source: curve.js

}; console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); let gasLimit = await signer.estimateGas(rawtx); - gasLimit = gasLimit.mul("11").div("10"); + gasLimit = gasLimit.mul("112").div("100"); rawtx.gasLimit = gasLimit; const gasCost = gasLimit.mul(gasPrice); - // let gasLimit; - // console.log("Block Number: " + await signer.provider.getBlockNumber()); - // if (arbType === "order-taker") gasLimit = await arb.estimateGas.arb( - // takeOrdersConfigStruct, - // // set to zero for estimation - // ethers.constants.Zero, - // { gasPrice } - // ); - // else gasLimit = await arb.estimateGas.arb( - // takeOrdersConfigStruct, - // // set to zero for estimation - // ethers.constants.Zero, - // data, - // { gasPrice } - // ); - // gasLimit = gasLimit.mul("11").div("10"); - // const gasCost = gasLimit.mul(gasPrice); - // const maxEstimatedProfit = estimateProfit( - // ethers.utils.formatEther(bundledOrders[i].initPrice), - // ethPrice, - // bundledOrders[i], - // gasCost, - // gasCoveragePercentage - // ).div( - // "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - // ); - // console.log(`Max Estimated Profit: ${ - // ethers.utils.formatUnits( - // maxEstimatedProfit, - // bundledOrders[i].buyTokenDecimals - // ) - // } ${bundledOrders[i].buyTokenSymbol}`, "\n"); - - // if (maxEstimatedProfit.isNegative()) console.log( - // ">>> Skipping because estimated negative profit for this token pair", - // "\n" - // ); - // else { - console.log(">>> Trying to submit the transaction for this token pair...", "\n"); const gasCostInToken = ethers.utils.parseUnits( ethPrice ).mul( @@ -556,43 +509,50 @@

Source: curve.js

36 - bundledOrders[i].buyTokenDecimals ) ); - rawtx.data = arb.interface.encodeFunctionData( - "arb", - arbType === "order-taker" - ? [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100) - ] - : [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100), - exchangeData - ] - ); - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - const tx = await signer.sendTransaction(rawtx); - // if (arbType === "order-taker") tx = await arb.arb( - // takeOrdersConfigStruct, - // // set to zero because only profitable transactions are submitted - // gasCostInToken.mul(gasCoveragePercentage).div(100), - // { gasPrice, gasLimit } - // ); - // else tx = await arb.arb( - // takeOrdersConfigStruct, - // // set to zero because only profitable transactions are submitted - // gasCostInToken.mul(gasCoveragePercentage).div(100), - // data, - // { gasPrice, gasLimit } - // ); - console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); - console.log( - ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", - "\n" - ); + if (gasCoveragePercentage !== "0") { + const headroom = ( + Number(gasCoveragePercentage) * 1.15 + ).toFixed(); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100"), + exchangeData + ] + ); + await signer.estimateGas(rawtx); + } try { + console.log(">>> Trying to submit the transaction for this token pair...", "\n"); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100"), + exchangeData + ] + ); + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + const tx = await signer.sendTransaction(rawtx); + console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); + console.log( + ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", + "\n" + ); + const receipt = await tx.wait(); - // console.log(receipt); const income = getIncome(signer, receipt); const clearActualPrice = getActualPrice( receipt, @@ -621,10 +581,10 @@

Source: curve.js

`${bundledOrders[i].takeOrders.length} orders cleared successfully of this token pair!`, "\n" ); - console.log( - "\x1b[36m%s\x1b[0m", - `Clear Initial Price: ${ethers.utils.formatEther(bundledOrders[i].initPrice)}` - ); + // console.log( + // "\x1b[36m%s\x1b[0m", + // `Clear Initial Price: ${ethers.utils.formatEther(bundledOrders[i].initPrice)}` + // ); console.log("\x1b[36m%s\x1b[0m", `Clear Actual Price: ${clearActualPrice}`); console.log("\x1b[36m%s\x1b[0m", `Clear Amount: ${ ethers.utils.formatUnits( @@ -659,15 +619,10 @@

Source: curve.js

sellToken: bundledOrders[i].sellToken, sellTokenDecimals: bundledOrders[i].sellTokenDecimals, clearedAmount: bundledQuoteAmount.toString(), - clearPrice: ethers.utils.formatEther( - bundledOrders[i].initPrice - ), - // clearGuaranteedPrice: ethers.utils.formatUnits( - // guaranteedAmount, - // bundledOrders[i].buyTokenDecimals + // clearPrice: ethers.utils.formatEther( + // bundledOrders[i].initPrice // ), clearActualPrice, - // maxEstimatedProfit, gasUsed: receipt.gasUsed, gasCost: actualGasCost, income, @@ -679,12 +634,16 @@

Source: curve.js

console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); console.log(error, "\n"); } - // } } } catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); - console.log(error, "\n"); + if (error === "dryrun" || error === "nomatch") { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction dry run failed, skipping..."); + } + else { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); + console.log(error, "\n"); + } } } } @@ -708,13 +667,13 @@

Source: curve.js


diff --git a/docs/html/global.html b/docs/html/global.html index af4b25b2..59e52d05 100644 --- a/docs/html/global.html +++ b/docs/html/global.html @@ -204,7 +204,7 @@

(constant) P
Source:
@@ -266,7 +266,7 @@

(constant) ZAP
Source:
@@ -408,13 +408,13 @@

(constant) (constant) fallbackTransports

+

(constant) fallbacks

- Fallback transports for viem client + Chain specific fallback data
@@ -624,7 +624,7 @@
Parameters:
Source:
@@ -796,7 +796,7 @@
Parameters:
Source:
@@ -842,7 +842,7 @@
Returns:
-

(async) bundleTakeOrders(ordersDetails, orderbook, arb)

+

build0xQueries(api, queries, tokenAddress, tokenDecimals, tokenSymbol)

@@ -850,7 +850,14 @@

(async) - Builds and bundles orders which their details are queried from a orderbook subgraph by checking the vault balances and evaling + Builds initial 0x requests bodies from token addresses that is required +for getting token prices with least amount of hits possible and that is +to pair up tokens in a way that each show up only once in a request body +so that the number of requests will be: "number-of-tokens / 2" at best or +"(number-of-tokens / 2) + 1" at worst if the number of tokens is an odd digit. +This way the responses will include the "rate" for sell/buy tokens to native +network token which will be used to estimate the initial price of all possible +token pair combinations. @@ -886,7 +893,30 @@
Parameters:
- ordersDetails + api + + + + + +string + + + + + + + + + + The 0x API endpoint URL + + + + + + + queries @@ -902,20 +932,20 @@
Parameters:
- Orders details queried from subgraph + The array that keeps the 0x query text - orderbook + tokenAddress -ethers.Contract +string @@ -925,20 +955,20 @@
Parameters:
- The Orderbook EthersJS contract instance with signer + The token address - arb + tokenDecimals -ethers.Contract +number @@ -948,7 +978,30 @@
Parameters:
- The Arb EthersJS contract instance with signer + The token decimals + + + + + + + tokenSymbol + + + + + +string + + + + + + + + + + The token symbol @@ -989,7 +1042,7 @@
Parameters:
Source:
@@ -1014,16 +1067,6 @@
Parameters:
-
Returns:
- - -
- Array of bundled take orders -
- - - - @@ -1035,7 +1078,7 @@
Returns:
-

(async) clear(mode, config, ordersDetails, options)

+

(async) bundleTakeOrders(ordersDetails, orderbook, arb)

@@ -1043,7 +1086,7 @@

(async) clear - Method to find and take arbitrage trades for Rain Orderbook orders against some liquidity providers + Builds and bundles orders which their details are queried from a orderbook subgraph by checking the vault balances and evaling @@ -1079,36 +1122,13 @@
Parameters:
- mode - - - - - -string - - - - - - - - - - The mode for clearing, either "0x" or "curve" or "router" - - - - - - - config + ordersDetails -object +Array.<any> @@ -1118,20 +1138,20 @@
Parameters:
- The configuration object + Orders details queried from subgraph - ordersDetails + orderbook -Array.<any> +ethers.Contract @@ -1141,20 +1161,20 @@
Parameters:
- The order details queried from subgraph + The Orderbook EthersJS contract instance with signer - options + arb -clearOptions +ethers.Contract @@ -1164,7 +1184,7 @@
Parameters:
- The options for clear, 'slippage',' gasCoveragePercentage' and 'prioritization' + The Arb EthersJS contract instance with signer @@ -1205,7 +1225,7 @@
Parameters:
Source:
@@ -1234,7 +1254,7 @@
Returns:
- The report of details of cleared orders + Array of bundled take orders
@@ -1251,7 +1271,7 @@
Returns:
-

(async) curveClear(config, ordersDetails, gasCoveragePercentage, prioritization)

+

(async) clear(mode, config, ordersDetails, options)

@@ -1259,7 +1279,7 @@

(async) cur
- Main function that gets order details from subgraph, bundles the ones that have balance and tries clearing them with curve + Method to find and take arbitrage trades for Rain Orderbook orders against some liquidity providers
@@ -1285,8 +1305,6 @@

Parameters:
- Default - Description @@ -1297,13 +1315,13 @@
Parameters:
- config + mode -object +string @@ -1312,25 +1330,21 @@
Parameters:
- - - - - The configuration object + The mode for clearing, either "0x" or "curve" or "router" - ordersDetails + config -Array.<any> +object @@ -1339,25 +1353,21 @@
Parameters:
- - - - - The order details queried from subgraph + The configuration object - gasCoveragePercentage + ordersDetails -string +Array.<any> @@ -1366,28 +1376,21 @@
Parameters:
- - - 100 - - - - (optional) The percentage of the gas cost to cover on each transaction -for it to be considered profitable and get submitted + The order details queried from subgraph - prioritization + options -boolean +clearOptions @@ -1396,14 +1399,8 @@
Parameters:
- - - true - - - - (optional) Prioritize better deals to get cleared first, default is true + The options for clear, such as 'gasCoveragePercentage'' @@ -1444,7 +1441,7 @@
Parameters:
Source:
@@ -1490,7 +1487,7 @@
Returns:
-

estimateProfit(pairPrice, ethPrice, bundledOrder, gas, gasCoveragePercentage)

+

(async) curveClear(config, ordersDetails, gasCoveragePercentage)

@@ -1498,7 +1495,7 @@

estimat
- Estimates the profit for a single bundled orders struct + Main function that gets order details from subgraph, bundles the ones that have balance and tries clearing them with curve
@@ -1536,61 +1533,7 @@

Parameters:
- pairPrice - - - - - -string - - - - - - - - - - - - - - The price token pair - - - - - - - ethPrice - - - - - -string - - - - - - - - - - - - - - Price of ETH to buy token - - - - - - - bundledOrder + config @@ -1610,20 +1553,20 @@
Parameters:
- The bundled order object + The configuration object - gas + ordersDetails -ethers.BigNumber +Array.<any> @@ -1637,7 +1580,7 @@
Parameters:
- The estimated gas cost in ETH + The order details queried from subgraph @@ -1666,7 +1609,8 @@
Parameters:
- Percentage of gas to cover, default is 100,i.e. full gas coverage + (optional) The percentage of the gas cost to cover on each transaction +for it to be considered profitable and get submitted @@ -1707,7 +1651,7 @@
Parameters:
Source:
@@ -1736,7 +1680,7 @@
Returns:
- The estimated profit + The report of details of cleared orders
@@ -1753,7 +1697,7 @@
Returns:
-

(async) fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken, excludePools)

+

estimateProfit(pairPrice, ethPrice, bundledOrder, gas, gasCoveragePercentage)

@@ -1761,7 +1705,7 @@

(as
- A wrapper for DataFetcher fetchPoolsForToken() to avoid any errors for liquidity providers that are not available for target chain + Estimates the profit for a single bundled orders struct
@@ -1787,6 +1731,8 @@
Parameters:
+ Default + Description @@ -1797,13 +1743,13 @@
Parameters:
- dataFetcher + pairPrice -DataFetcher +string @@ -1812,21 +1758,25 @@
Parameters:
- - DataFetcher instance + + + + + + The price token pair - fromToken + ethPrice -Token +string @@ -1835,21 +1785,25 @@
Parameters:
+ + + + - The from token + Price of ETH to buy token - toToken + bundledOrder -Token +object @@ -1858,21 +1812,52 @@
Parameters:
+ + + + - The to token + The bundled order object - excludePools + gas -Array.<string> +ethers.BigNumber + + + + + + + + + + + + + + The estimated gas cost in ETH + + + + + + + gasCoveragePercentage + + + + + +string @@ -1881,8 +1866,14 @@
Parameters:
+ + + 100 + + + - Set of pools to exclude + Percentage of gas to cover, default is 100,i.e. full gas coverage @@ -1923,7 +1914,7 @@
Parameters:
Source:
@@ -1948,6 +1939,16 @@
Parameters:
+
Returns:
+ + +
+ The estimated profit +
+ + + + @@ -1959,7 +1960,7 @@
Parameters:
-

fromFixed18(bn, decimals)

+

(async) fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken, excludePools)

@@ -1967,7 +1968,7 @@

fromFixed1
- Convert a 18 fixed point BigNumber to a BigNumber with some other decimals point + A wrapper for DataFetcher fetchPoolsForToken() to avoid any errors for liquidity providers that are not available for target chain
@@ -2003,13 +2004,13 @@

Parameters:
- bn + dataFetcher -BigNumber +DataFetcher @@ -2019,20 +2020,20 @@
Parameters:
- The BigNumber to convert + DataFetcher instance - decimals + fromToken -number +Token @@ -2042,7 +2043,53 @@
Parameters:
- The decimals point of convert the given BigNumber + The from token + + + + + + + toToken + + + + + +Token + + + + + + + + + + The to token + + + + + + + excludePools + + + + + +Array.<string> + + + + + + + + + + Set of pools to exclude @@ -2083,7 +2130,7 @@
Parameters:
Source:
@@ -2108,16 +2155,6 @@
Parameters:
-
Returns:
- - -
- A decimals point BigNumber -
- - - - @@ -2129,7 +2166,7 @@
Returns:
-

getActualClearAmount(arbAddress, receipt)

+

fromFixed18(bn, decimals)

@@ -2137,7 +2174,7 @@

g
- Extracts the actual clear amount (received token value) from transaction receipt + Convert a 18 fixed point BigNumber to a BigNumber with some other decimals point
@@ -2173,13 +2210,13 @@

Parameters:
- arbAddress + bn -string +BigNumber @@ -2189,20 +2226,20 @@
Parameters:
- The arb contract address + The BigNumber to convert - receipt + decimals -any +number @@ -2212,7 +2249,7 @@
Parameters:
- The transaction receipt + The decimals point of convert the given BigNumber @@ -2253,7 +2290,7 @@
Parameters:
Source:
@@ -2282,7 +2319,7 @@
Returns:
- The actual clear amount + A decimals point BigNumber
@@ -2299,7 +2336,7 @@
Returns:
-

getActualPrice(receipt, orderbook, arb, amount, buyDecimals)

+

getActualClearAmount(arbAddress, receipt)

@@ -2307,7 +2344,7 @@

getActu
- Calculates the actual clear price from transactioin event + Extracts the actual clear amount (received token value) from transaction receipt
@@ -2343,76 +2380,7 @@

Parameters:
- receipt - - - - - -any - - - - - - - - - - The transaction receipt - - - - - - - orderbook - - - - - -string - - - - - - - - - - The Orderbook contract address - - - - - - - arb - - - - - -string - - - - - - - - - - The Arb contract address - - - - - - - amount + arbAddress @@ -2428,20 +2396,20 @@
Parameters:
- The clear amount + The arb contract address - buyDecimals + receipt -number +any @@ -2451,7 +2419,7 @@
Parameters:
- The buy token decimals + The transaction receipt @@ -2492,7 +2460,7 @@
Parameters:
Source:
@@ -2521,7 +2489,7 @@
Returns:
- The actual clear price or undefined if necessary info not found in transaction events + The actual clear amount
@@ -2538,7 +2506,7 @@
Returns:
-

getAvailableSwaps(config)

+

getActualPrice(receipt, orderbook, arb, amount, buyDecimals)

@@ -2546,7 +2514,7 @@

getA
- Returns array of available swaps pairs from specified curve pools in config file + Calculates the actual clear price from transactioin event
@@ -2582,7 +2550,246 @@

Parameters:
- config + receipt + + + + + +any + + + + + + + + + + The transaction receipt + + + + + + + orderbook + + + + + +string + + + + + + + + + + The Orderbook contract address + + + + + + + arb + + + + + +string + + + + + + + + + + The Arb contract address + + + + + + + amount + + + + + +string + + + + + + + + + + The clear amount + + + + + + + buyDecimals + + + + + +number + + + + + + + + + + The buy token decimals + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + +
+ The actual clear price or undefined if necessary info not found in transaction events +
+ + + + + + + + + + + + + + + +

getAvailableSwaps(config)

+ + + + + + +
+ Returns array of available swaps pairs from specified curve pools in config file +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4202,6 +4411,10 @@
Parameters:
+ + @@ -4225,6 +4438,10 @@
Parameters:
+ + @@ -4248,13 +4465,48 @@
Parameters:
+ + - -
NameTypeDescription
config @@ -2639,7 +2846,7 @@
Parameters:
Source:
@@ -2891,7 +3098,7 @@
Parameters:
Source:
@@ -3061,7 +3268,7 @@
Parameters:
Source:
@@ -3290,7 +3497,7 @@
Parameters:
Source:
@@ -3450,7 +3657,7 @@
Parameters:
Source:
@@ -3813,7 +4020,7 @@
Parameters:
Source:
@@ -3950,7 +4157,7 @@
Parameters:
Source:
@@ -4097,7 +4304,7 @@
Parameters:
Source:
@@ -4143,7 +4350,7 @@
Returns:
-

getQuery(orderHash, owner, interpreter)

+

getQuery(orderHash, owner, interpreter, shuffle)

@@ -4177,6 +4384,8 @@
Parameters:
+
DefaultDescription
+ + The order hash to apply as filter
+ + The order owner to apply as filter
+ + The interpreter to apply as filter
+ + + + shuffle + + + + + +number + + + + + + + + + + + 0 + + + + + (optional) A number in range of 0 - 3 that +will change the order of getting order details from subgraph, mostly +used for continuous bot running + + + + + @@ -4290,7 +4542,7 @@
Parameters:
Source:
@@ -4564,983 +4816,7 @@
Parameters:
- abiencoded - - - - - -boolean - - - - - - - - - - If the result should be abi encoded or not - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

initRequests(api, quotes, tokenAddress, tokenDecimals, tokenSymbol)

- - - - - - -
- Builds initial 0x requests bodies from token addresses that is required -for getting token prices with least amount of hits possible and that is -to pair up tokens in a way that each show up only once in a request body -so that the number of requests will be: "number-of-tokens / 2" at best or -"(number-of-tokens / 2) + 1" at worst if the number of tokens is an odd digit. -This way the responses will include the "rate" for sell/buy tokens to native -network token which will be used to estimate the initial price of all possible -token pair combinations. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
api - - -string - - - - The 0x API endpoint URL
quotes - - -Array.<any> - - - - The array that keeps the quotes
tokenAddress - - -string - - - - The token address
tokenDecimals - - -number - - - - The token decimals
tokenSymbol - - -string - - - - The token symbol
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(async) interpreterEval(interpreter, arbAddress, obAddress, order, inputIndex, outputIndex)

- - - - - - -
- Calls eval for a specific order to get its max output and ratio -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
interpreter - - -ethers.Contract - - - - The interpreter ethersjs contract instance with signer
arbAddress - - -string - - - - Arb contract address
obAddress - - -string - - - - OrderBook contract address
order - - -object - - - - The order details fetched from sg
inputIndex - - -number - - - - The input token index
outputIndex - - -number - - - - The ouput token index
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- The ratio and maxOuput as BigNumber -
- - - - - - - - - - - - - - - -

(async) prepare(bundledOrders, availableSwaps, config, sort)

- - - - - - -
- Prepares the bundled orders by getting the best deals from Curve pools and sorting the -bundled orders based on the best deals -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
bundledOrders - - -Array.<any> - - - - The bundled orders array
availableSwaps - - -Array.<any> - - - - The available swaps from Curve specofied pools
config - - -any - - - - The network config data
- - -ethers.Signer - - - - The ethersjs signer
sort - - -boolean - - - - (optional) Sort based on best deals or not
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

(async) prepare(bundledOrders, dataFetcher, config, gasPrice, sort)

- - - - - - -
- Prepares the bundled orders by getting the best deals from Router and sorting the -bundled orders based on the best deals -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + @@ -5603,7 +4873,7 @@
Parameters:
Source:
@@ -5639,7 +4909,7 @@
Parameters:
-

(async) prepare(bundledOrders, dataFetcher, config, gasPrice, sort)

+

(async) interpreterEval(interpreter, arbAddress, obAddress, order, inputIndex, outputIndex)

@@ -5647,8 +4917,7 @@

(async) prepar
- Prepares the bundled orders by getting the best deals from Router and sorting the -bundled orders based on the best deals + Calls eval for a specific order to get its max output and ratio
@@ -5674,8 +4943,6 @@

Parameters:
- - @@ -5686,13 +4953,13 @@
Parameters:
- + - - + - + - - + - + - - + - + - - + - + + + + + + + + + + + +number + + + + + - + + + @@ -5857,7 +5125,7 @@
Parameters:
Source:
@@ -5882,6 +5150,16 @@
Parameters:
+
Returns:
+ + +
+ The ratio and maxOuput as BigNumber +
+ + + + @@ -5893,7 +5171,7 @@
Parameters:
-

(async) prepare(quotes, bundledOrders, sort)

+

prepare(bundledOrders, availableSwaps, config, sort)

@@ -5901,7 +5179,7 @@

(async) prepar
- Prepares the bundled orders by getting the best deals from 0x and sorting the + Prepares the bundled orders by getting the best deals from Curve pools and sorting the bundled orders based on the best deals
@@ -5928,8 +5206,6 @@

Parameters:
- - @@ -5940,13 +5216,13 @@
Parameters:
- + + + + + + + + + + + +Array.<any> + + + + + + + - + - + + + + + + + + + + + +ethers.Signer + + + - + + + + + @@ -6009,12 +5323,6 @@
Parameters:
- - @@ -6057,7 +5365,7 @@
Parameters:
Source:
@@ -6093,7 +5401,7 @@
Parameters:
-

processLps(liquidityProviders)

+

processLps(liquidityProviders, chainId)

@@ -6157,6 +5465,29 @@
Parameters:
+ + + + + + + + + + + + + + + +
NameTypeDefaultDescription
bundledOrders - - -Array.<any> - - - - - - The bundled orders array
dataFetcher - - -any - - - - - - The DataFetcher instance
config - - -any - - - - - - The network config data
gasPrice - - -ethers.BigNumber - - - - - - The network gas price
sortabiencoded @@ -5555,14 +4831,8 @@
Parameters:
-
- - true - - (optional) Sort based on best deals or notIf the result should be abi encoded or not
DefaultDescription
bundledOrdersinterpreter -Array.<any> +ethers.Contract @@ -5701,25 +4968,21 @@
Parameters:
-
- - The bundled orders arrayThe interpreter ethersjs contract instance with signer
dataFetcherarbAddress -any +string @@ -5728,25 +4991,21 @@
Parameters:
-
- - The DataFetcher instanceArb contract address
configobAddress -any +string @@ -5755,25 +5014,21 @@
Parameters:
-
- - The network config dataOrderBook contract address
gasPriceorder -ethers.BigNumber +object @@ -5782,25 +5037,21 @@
Parameters:
-
- - The network gas priceThe order details fetched from sg
sortinputIndex -boolean +number @@ -5809,14 +5060,31 @@
Parameters:
-
- - true + + The input token index
outputIndex + - (optional) Sort based on best deals or notThe ouput token index
DefaultDescription
quotesbundledOrders -Array.<string> +Array.<any> @@ -5955,25 +5231,44 @@
Parameters:
-
+ + The bundled orders array
availableSwaps + - The 0x request quote bodiesThe available swaps from Curve specofied pools
bundledOrdersconfig -Array.<any> +any @@ -5982,12 +5277,31 @@
Parameters:
-
+ + The network config data
+ - The bundled orders arrayThe ethersjs signer
- - true - - (optional) Sort based on best deals or not
chainId + + +number + + + + The chain id
@@ -6194,7 +5525,7 @@
Parameters:
Source:
@@ -6390,7 +5721,7 @@
Parameters:
Source:
@@ -6437,7 +5768,7 @@
Returns:
-

(async) routerClear(config, ordersDetails, gasCoveragePercentage, prioritization)

+

(async) routerClear(config, ordersDetails, gasCoveragePercentage)

@@ -6564,35 +5895,6 @@
Parameters:
- - - - prioritization - - - - - -boolean - - - - - - - - - - - true - - - - - (optional) Prioritize better deals to get cleared first, default is true - - - @@ -6630,7 +5932,7 @@
Parameters:
Source:
@@ -6777,7 +6079,7 @@
Parameters:
Source:
@@ -6813,7 +6115,7 @@
Parameters:
-

(async) srouterClear(config, ordersDetails, gasCoveragePercentage, prioritization)

+

(async) srouterClear(config, ordersDetails, gasCoveragePercentage)

@@ -6940,35 +6242,6 @@
Parameters:
- - - - prioritization - - - - - -boolean - - - - - - - - - - - true - - - - - (optional) Prioritize better deals to get cleared first, default is true - - - @@ -7006,7 +6279,7 @@
Parameters:
Source:
@@ -7176,7 +6449,7 @@
Parameters:
Source:
@@ -7323,7 +6596,7 @@
Parameters:
Source:
@@ -7506,7 +6779,7 @@
Parameters:
Source:
@@ -7542,7 +6815,7 @@
Parameters:
-

(async) zeroExClear(config, ordersDetails, gasCoveragePercentage, prioritization)

+

(async) zeroExClear(config, ordersDetails, gasCoveragePercentage)

@@ -7668,35 +6941,6 @@
Parameters:
- - - - prioritization - - - - - -boolean - - - - - - - - - - - true - - - - - (optional) Prioritize better deals to get cleared first, default is true - - - @@ -7734,7 +6978,7 @@
Parameters:
Source:
@@ -7790,13 +7034,13 @@
Returns:

- Documentation generated by JSDoc 4.0.2 on Thu Sep 14 2023 13:47:17 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 4.0.2 on Sat Sep 16 2023 00:48:16 GMT+0000 (Coordinated Universal Time)
diff --git a/docs/html/index.html b/docs/html/index.html index b8f69248..8ba6fb79 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -50,13 +50,13 @@


- Documentation generated by JSDoc 4.0.2 on Thu Sep 14 2023 13:47:17 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 4.0.2 on Sat Sep 16 2023 00:48:16 GMT+0000 (Coordinated Universal Time)
diff --git a/docs/html/index.js.html b/docs/html/index.js.html index cc8d71da..18b2a18e 100644 --- a/docs/html/index.js.html +++ b/docs/html/index.js.html @@ -87,10 +87,10 @@

Source: index.js

* for it to be considered profitable and get submitted */ gasCoveragePercentage: "100", - /** - * Prioritize better deals to get cleared first, default is true - */ - prioritization: true + // /** + // * Prioritize better deals to get cleared first, default is true + // */ + // prioritization: true }; /** @@ -129,7 +129,8 @@

Source: index.js

query: getQuery( sgFilters?.orderHash, sgFilters?.orderOwner, - sgFilters?.orderInterpreter + sgFilters?.orderInterpreter, + sgFilters?.shuffle ) }, { headers: { "Content-Type": "application/json" } } @@ -223,7 +224,7 @@

Source: index.js

* @param {string} mode - The mode for clearing, either "0x" or "curve" or "router" * @param {object} config - The configuration object * @param {any[]} ordersDetails - The order details queried from subgraph - * @param {clearOptions} options - The options for clear, 'slippage',' gasCoveragePercentage' and 'prioritization' + * @param {clearOptions} options - The options for clear, such as 'gasCoveragePercentage'' * @returns The report of details of cleared orders */ const clear = async( @@ -235,9 +236,9 @@

Source: index.js

const _mode = mode.toLowerCase(); const version = versions.node; const majorVersion = Number(version.slice(0, version.indexOf("."))); - const prioritization = options.prioritization !== undefined - ? options.prioritization - : clearOptions.prioritization; + // const prioritization = options.prioritization !== undefined + // ? !!options.prioritization + // : clearOptions.prioritization; const gasCoveragePercentage = options.gasCoveragePercentage !== undefined ? options.gasCoveragePercentage : clearOptions.gasCoveragePercentage; @@ -253,14 +254,14 @@

Source: index.js

config, ordersDetails, gasCoveragePercentage, - prioritization + // prioritization ); else if (_mode === "curve") { if (majorVersion >= 18) return await curveClear( config, ordersDetails, gasCoveragePercentage, - prioritization + // prioritization ); else throw `NodeJS v18 or higher is required for running the app in "curve" mode, current version: ${version}`; } @@ -269,7 +270,7 @@

Source: index.js

config, ordersDetails, gasCoveragePercentage, - prioritization + // prioritization ); else throw `NodeJS v18 or higher is required for running the app in "router" mode, current version: ${version}`; } @@ -278,7 +279,7 @@

Source: index.js

config, ordersDetails, gasCoveragePercentage, - prioritization + // prioritization ); else throw `NodeJS v18 or higher is required for running the app in "router" mode, current version: ${version}`; } @@ -299,13 +300,13 @@

Source: index.js


- Documentation generated by JSDoc 4.0.2 on Thu Sep 14 2023 13:47:17 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 4.0.2 on Sat Sep 16 2023 00:48:16 GMT+0000 (Coordinated Universal Time)
diff --git a/docs/html/query.js.html b/docs/html/query.js.html index d357fb02..99624a60 100644 --- a/docs/html/query.js.html +++ b/docs/html/query.js.html @@ -77,15 +77,36 @@

Source: query.js

* @param {string} orderHash - The order hash to apply as filter * @param {string} owner - The order owner to apply as filter * @param {string} interpreter - The interpreter to apply as filter + * @param {number} shuffle - (optional) A number in range of 0 - 3 that + * will change the order of getting order details from subgraph, mostly + * used for continuous bot running * @returns the query string */ -const getQuery = (orderHash, owner, interpreter) => { +const getQuery = (orderHash, owner, interpreter, shuffle = 0) => { const orderHashFilter = orderHash ? `, id :"${orderHash.toLowerCase()}"` : ""; const ownerFilter = owner ? `, owner :"${owner.toLowerCase()}"` : ""; const interpreterFilter = interpreter ? `, interpreter :"${interpreter.toLowerCase()}"` : ""; + let orderingProp, orderingDir; + const _turn = shuffle % 4; + if (_turn === 0) { + orderingProp = "id"; + orderingDir = "asc"; + } + if (_turn === 1) { + orderingProp = "id"; + orderingDir = "desc"; + } + if (_turn === 2) { + orderingProp = "timestamp"; + orderingDir = "asc"; + } + if (_turn === 3) { + orderingProp = "timestamp"; + orderingDir = "desc"; + } return `{ orders( - where: {orderActive: true${orderHashFilter}${ownerFilter}${interpreterFilter}} + orderBy: ${orderingProp}, orderDirection: ${orderingDir}, where: {orderActive: true${orderHashFilter}${ownerFilter}${interpreterFilter}} ) { id handleIO @@ -140,13 +161,13 @@

Source: query.js


- Documentation generated by JSDoc 4.0.2 on Thu Sep 14 2023 13:47:17 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 4.0.2 on Sat Sep 16 2023 00:48:16 GMT+0000 (Coordinated Universal Time)
diff --git a/docs/html/router.js.html b/docs/html/router.js.html index 6b3ee032..2b0f08e0 100644 --- a/docs/html/router.js.html +++ b/docs/html/router.js.html @@ -42,82 +42,6 @@

Source: router.js

} = require("./utils"); -/** - * Prepares the bundled orders by getting the best deals from Router and sorting the - * bundled orders based on the best deals - * - * @param {any[]} bundledOrders - The bundled orders array - * @param {any} dataFetcher - The DataFetcher instance - * @param {any} config - The network config data - * @param {ethers.BigNumber} gasPrice - The network gas price - * @param {boolean} sort - (optional) Sort based on best deals or not - */ -const prepare = async(bundledOrders, dataFetcher, config, gasPrice, sort = true) => { - for (let i = 0; i < bundledOrders.length; i++) { - const bOrder = bundledOrders[i]; - const pair = bOrder.buyTokenSymbol + "/" + bOrder.sellTokenSymbol; - try { - const fromToken = new Token({ - chainId: config.chainId, - decimals: bOrder.sellTokenDecimals, - address: bOrder.sellToken, - symbol: bOrder.sellTokenSymbol - }); - const toToken = new Token({ - chainId: config.chainId, - decimals: bOrder.buyTokenDecimals, - address: bOrder.buyToken, - symbol: bOrder.buyTokenSymbol - }); - await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); - const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken, toToken); - const route = Router.findBestRoute( - pcMap, - config.chainId, - fromToken, - // cumulativeAmount, - "1" + "0".repeat(bOrder.sellTokenDecimals), - toToken, - gasPrice.toNumber(), - // providers, - // poolFilter - ); - if (route.status == "NoWay") throw "could not find any route for this token pair"; - - // const rateFixed = route.amountOutBN.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - // const price = rateFixed.mul("1" + "0".repeat(18)).div(cumulativeAmountFixed); - const price = route.amountOutBN.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - bOrder.initPrice = price; - - console.log(`Current market price for ${pair} for: ${ethers.utils.formatEther(price)}`); - console.log("Current ratio of the orders in this token pair:"); - bOrder.takeOrders.forEach(v => { - console.log(ethers.utils.formatEther(v.ratio)); - }); - bOrder.takeOrders = bOrder.takeOrders.filter( - v => price.gte(v.ratio) - ); - console.log("\n"); - } - catch(error) { - console.log(`>>> could not get price for this ${pair} due to:`); - console.log(error, "\n"); - } - } - console.log( - ">>> Filtering bundled orders with lower ratio than current market price...", - "\n" - ); - bundledOrders = bundledOrders.filter(v => v.initPrice && v.takeOrders.length > 0); - if (sort) { - console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); - bundledOrders.sort( - (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 - ); - } - return bundledOrders; -}; - /** * Main function that gets order details from subgraph, bundles the ones that have balance and tries clearing them with router contract * @@ -125,22 +49,19 @@

Source: router.js

* @param {any[]} ordersDetails - The order details queried from subgraph * @param {string} gasCoveragePercentage - (optional) The percentage of the gas cost to cover on each transaction * for it to be considered profitable and get submitted - * @param {boolean} prioritization - (optional) Prioritize better deals to get cleared first, default is true * @returns The report of details of cleared orders */ const routerClear = async( config, ordersDetails, - gasCoveragePercentage = "100", - prioritization = true + gasCoveragePercentage = "100" ) => { if ( gasCoveragePercentage < 0 || !Number.isInteger(Number(gasCoveragePercentage)) ) throw "invalid gas coverage percentage, must be an integer greater than equal 0"; - if (typeof prioritization !== "boolean") throw "invalid value for 'prioritization'"; - const lps = processLps(config.lps); + const lps = processLps(config.lps, config.chainId); const dataFetcher = getDataFetcher(config, lps, !!config.usePublicRpc); const signer = config.signer; const arbAddress = config.arbAddress; @@ -153,10 +74,10 @@

Source: router.js

// instantiating orderbook contract const orderbook = new ethers.Contract(orderbookAddress, orderbookAbi, signer); - let gasPrice = await signer.provider.getGasPrice(); - console.log( - "------------------------- Starting Clearing Process -------------------------", + "------------------------- Starting The", + "\x1b[32mROUTER\x1b[0m", + "Mode -------------------------", "\n" ); console.log("\x1b[33m%s\x1b[0m", Date()); @@ -169,11 +90,6 @@

Source: router.js

"------------------------- Bundling Orders -------------------------", "\n" ); bundledOrders = await bundleTakeOrders(ordersDetails, orderbook, arb); - console.log( - "------------------------- Getting Best Deals From RouteProcessor3 -------------------------", - "\n" - ); - bundledOrders = await prepare(bundledOrders, dataFetcher, config, gasPrice, prioritization); } else { console.log("No orders found, exiting...", "\n"); @@ -185,15 +101,9 @@

Source: router.js

return; } - console.log( - "------------------------- Trying To Clear Bundled Orders -------------------------", - "\n" - ); - const report = []; for (let i = 0; i < bundledOrders.length; i++) { try { - gasPrice = await signer.provider.getGasPrice(); console.log( `------------------------- Trying To Clear ${ bundledOrders[i].buyTokenSymbol @@ -275,6 +185,7 @@

Source: router.js

await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken,toToken); + const gasPrice = await signer.provider.getGasPrice(); const route = Router.findBestRoute( pcMap, config.chainId, @@ -292,7 +203,11 @@

Source: router.js

"1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) ); const price = rateFixed.mul("1" + "0".repeat(18)).div(cumulativeAmountFixed); - console.log(`Current best route price for this token pair: ${ethers.utils.formatEther(price)}`, "\n"); + console.log( + "Current best route price for this token pair:", + `\x1b[33m${ethers.utils.formatEther(price)}\x1b[0m`, + "\n" + ); // filter take orders based on curent price and calculate final bundle quote amount bundledOrders[i].takeOrders = bundledOrders[i].takeOrders.filter( @@ -329,11 +244,6 @@

Source: router.js

v => console.log("\x1b[36m%s\x1b[0m", v) ); console.log(""); - // console.log( - // "\x1b[36m%s\x1b[0m", - // visualizeRoute(fromToken.address, toToken.address, route.legs), - // "\n" - // ); const rpParams = Router.routeProcessor2Params( pcMap, @@ -345,7 +255,6 @@

Source: router.js

// permits // "0.005" ); - const takeOrdersConfigStruct = { output: bundledOrders[i].buyToken, input: bundledOrders[i].sellToken, @@ -390,14 +299,15 @@

Source: router.js

); if (arbType === "order-taker") takeOrdersConfigStruct.data = exchangeData; - // console.log(">>> Estimating the profit for this token pair...", "\n"); - const ethPrice = await getEthPrice( - config, - bundledOrders[i].buyToken, - bundledOrders[i].buyTokenDecimals, - gasPrice, - dataFetcher - ); + const ethPrice = gasCoveragePercentage !== "0" + ? "0" + : await getEthPrice( + config, + bundledOrders[i].buyToken, + bundledOrders[i].buyTokenDecimals, + gasPrice, + dataFetcher + ); if (ethPrice === undefined) console.log("can not get ETH price, skipping...", "\n"); else { const rawtx = { @@ -419,31 +329,9 @@

Source: router.js

}; console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); let gasLimit = await signer.estimateGas(rawtx); - gasLimit = gasLimit.mul("11").div("10"); + gasLimit = gasLimit.mul("112").div("100"); rawtx.gasLimit = gasLimit; const gasCost = gasLimit.mul(gasPrice); - // const maxEstimatedProfit = estimateProfit( - // ethers.utils.formatEther(bundledOrders[i].initPrice), - // ethPrice, - // bundledOrders[i], - // gasCost, - // gasCoveragePercentage - // ).div( - // "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - // ); - // console.log(`Max Estimated Profit: ${ - // ethers.utils.formatUnits( - // maxEstimatedProfit, - // bundledOrders[i].buyTokenDecimals - // ) - // } ${bundledOrders[i].buyTokenSymbol}`, "\n"); - - // if (maxEstimatedProfit.isNegative()) console.log( - // ">>> Skipping because estimated negative profit for this token pair", - // "\n" - // ); - // else { - console.log(">>> Trying to submit the transaction for this token pair...", "\n"); const gasCostInToken = ethers.utils.parseUnits( ethPrice ).mul( @@ -453,28 +341,48 @@

Source: router.js

36 - bundledOrders[i].buyTokenDecimals ) ); - rawtx.data = arb.interface.encodeFunctionData( - "arb", - arbType === "order-taker" - ? [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100) - ] - : [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100), - exchangeData - ] - ); - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - const tx = await signer.sendTransaction(rawtx); - console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); - console.log( - ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", - "\n" - ); + if (gasCoveragePercentage !== "0") { + const headroom = ( + Number(gasCoveragePercentage) * 1.15 + ).toFixed(); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100"), + exchangeData + ] + ); + await signer.estimateGas(rawtx); + } try { + console.log(">>> Trying to submit the transaction for this token pair...", "\n"); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100"), + exchangeData + ] + ); + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + const tx = await signer.sendTransaction(rawtx); + console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); + console.log( + ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", + "\n" + ); const receipt = await tx.wait(); const income = getIncome(signer, receipt); const clearActualPrice = getActualPrice( @@ -506,7 +414,7 @@

Source: router.js

); console.log( "\x1b[36m%s\x1b[0m", - `Clear Initial Price: ${ethers.utils.formatEther(bundledOrders[i].initPrice)}` + `Clear Initial Price: ${ethers.utils.formatEther(price)}` ); console.log("\x1b[36m%s\x1b[0m", `Clear Actual Price: ${clearActualPrice}`); console.log("\x1b[36m%s\x1b[0m", `Clear Amount: ${ @@ -543,14 +451,9 @@

Source: router.js

sellTokenDecimals: bundledOrders[i].sellTokenDecimals, clearedAmount: bundledQuoteAmount.toString(), clearPrice: ethers.utils.formatEther( - bundledOrders[i].initPrice + price ), - // clearGuaranteedPrice: ethers.utils.formatUnits( - // guaranteedAmount, - // bundledOrders[i].buyTokenDecimals - // ), clearActualPrice, - // maxEstimatedProfit, gasUsed: receipt.gasUsed, gasCost: actualGasCost, income, @@ -562,13 +465,17 @@

Source: router.js

console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); console.log(error, "\n"); } - // } } } catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); - console.log(error, "\n"); - // reason, code, method, transaction, error, stack, message + if (error === "dryrun" || error === "nomatch") { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction dry run failed, skipping..."); + } + else { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); + console.log(error, "\n"); + // reason, code, method, transaction, error, stack, message + } } } } @@ -593,13 +500,13 @@

Source: router.js


- Documentation generated by JSDoc 4.0.2 on Thu Sep 14 2023 13:47:17 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 4.0.2 on Sat Sep 16 2023 00:48:16 GMT+0000 (Coordinated Universal Time)
diff --git a/docs/html/srouter.js.html b/docs/html/srouter.js.html index 2e19ea7c..34638a9b 100644 --- a/docs/html/srouter.js.html +++ b/docs/html/srouter.js.html @@ -43,80 +43,6 @@

Source: srouter.js

} = require("./utils"); -/** - * Prepares the bundled orders by getting the best deals from Router and sorting the - * bundled orders based on the best deals - * - * @param {any[]} bundledOrders - The bundled orders array - * @param {any} dataFetcher - The DataFetcher instance - * @param {any} config - The network config data - * @param {ethers.BigNumber} gasPrice - The network gas price - * @param {boolean} sort - (optional) Sort based on best deals or not - */ -const prepare = async(bundledOrders, dataFetcher, config, gasPrice, sort = true) => { - for (let i = 0; i < bundledOrders.length; i++) { - const bOrder = bundledOrders[i]; - const pair = bOrder.buyTokenSymbol + "/" + bOrder.sellTokenSymbol; - try { - const fromToken = new Token({ - chainId: config.chainId, - decimals: bOrder.sellTokenDecimals, - address: bOrder.sellToken, - symbol: bOrder.sellTokenSymbol - }); - const toToken = new Token({ - chainId: config.chainId, - decimals: bOrder.buyTokenDecimals, - address: bOrder.buyToken, - symbol: bOrder.buyTokenSymbol - }); - await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); - const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken, toToken); - const route = Router.findBestRoute( - pcMap, - config.chainId, - fromToken, - // cumulativeAmount, - "1" + "0".repeat(bOrder.sellTokenDecimals), - toToken, - gasPrice.toNumber(), - // providers, - // poolFilter - ); - if (route.status == "NoWay") throw "could not find any route for this token pair"; - - const price = route.amountOutBN.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - bOrder.initPrice = price; - - console.log(`Current market price for ${pair} for: ${ethers.utils.formatEther(price)}`); - console.log("Current ratio of the orders in this token pair:"); - bOrder.takeOrders.forEach(v => { - if (v.ratio) console.log(ethers.utils.formatEther(v.ratio)); - }); - bOrder.takeOrders = bOrder.takeOrders.filter( - v => v.ratio !== undefined ? price.gte(v.ratio) : true - ); - console.log("\n"); - } - catch(error) { - console.log(`>>> could not get price for this ${pair} due to:`); - console.log(error, "\n"); - } - } - console.log( - ">>> Filtering bundled orders with lower ratio than current market price...", - "\n" - ); - bundledOrders = bundledOrders.filter(v => v.initPrice && v.takeOrders.length > 0); - if (sort) { - console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); - bundledOrders.sort( - (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 - ); - } - return bundledOrders; -}; - /** * Main function that gets order details from subgraph, bundles the ones that have balance and tries clearing them with specialized router contract * @@ -124,22 +50,19 @@

Source: srouter.js

* @param {any[]} ordersDetails - The order details queried from subgraph * @param {string} gasCoveragePercentage - (optional) The percentage of the gas cost to cover on each transaction * for it to be considered profitable and get submitted - * @param {boolean} prioritization - (optional) Prioritize better deals to get cleared first, default is true * @returns The report of details of cleared orders */ const srouterClear = async( config, ordersDetails, - gasCoveragePercentage = "100", - prioritization = true + gasCoveragePercentage = "100" ) => { if ( gasCoveragePercentage < 0 || !Number.isInteger(Number(gasCoveragePercentage)) ) throw "invalid gas coverage percentage, must be an integer greater than equal 0"; - if (typeof prioritization !== "boolean") throw "invalid value for 'prioritization'"; - const lps = processLps(config.lps); + const lps = processLps(config.lps, config.chainId); const dataFetcher = getDataFetcher(config, lps, !!config.usePublicRpc); const signer = config.signer; const arbAddress = config.arbAddress; @@ -153,10 +76,10 @@

Source: srouter.js

// instantiating orderbook contract const orderbook = new ethers.Contract(orderbookAddress, orderbookAbi, signer); - let gasPrice = await signer.provider.getGasPrice(); - console.log( - "------------------------- Starting Clearing Process -------------------------", + "------------------------- Starting The", + "\x1b[32mS-ROUTER\x1b[0m", + "Mode -------------------------", "\n" ); console.log("\x1b[33m%s\x1b[0m", Date()); @@ -169,11 +92,6 @@

Source: srouter.js

"------------------------- Bundling Orders -------------------------", "\n" ); bundledOrders = await bundleTakeOrders(ordersDetails, orderbook, arb, maxProfit); - console.log( - "------------------------- Getting Best Deals From RouteProcessor3 -------------------------", - "\n" - ); - bundledOrders = await prepare(bundledOrders, dataFetcher, config, gasPrice, prioritization); } else { console.log("No orders found, exiting...", "\n"); @@ -185,15 +103,9 @@

Source: srouter.js

return; } - console.log( - "------------------------- Trying To Clear Bundled Orders -------------------------", - "\n" - ); - const report = []; for (let i = 0; i < bundledOrders.length; i++) { try { - gasPrice = await signer.provider.getGasPrice(); console.log( `------------------------- Trying To Clear ${ bundledOrders[i].buyTokenSymbol @@ -205,305 +117,322 @@

Source: srouter.js

console.log(`Buy Token Address: ${bundledOrders[i].buyToken}`); console.log(`Sell Token Address: ${bundledOrders[i].sellToken}`, "\n"); - if (!bundledOrders[i].takeOrders.length) console.log( - "All orders of this token pair have empty vault balance, skipping...", - "\n" - ); - else { - const fromToken = new Token({ - chainId: config.chainId, - decimals: bundledOrders[i].sellTokenDecimals, - address: bundledOrders[i].sellToken, - symbol: bundledOrders[i].sellTokenSymbol - }); - const toToken = new Token({ - chainId: config.chainId, - decimals: bundledOrders[i].buyTokenDecimals, - address: bundledOrders[i].buyToken, - symbol: bundledOrders[i].buyTokenSymbol - }); - - const obSellTokenBalance = ethers.BigNumber.from(await signer.call({ - data: "0x70a08231000000000000000000000000" + orderbookAddress.slice(2), - to: bundledOrders[i].sellToken - })); - const quoteChunks = obSellTokenBalance.div("5"); - let ethPrice; - - for (let j = 5; j > 0; j--) { - const maximumInput = j === 5 ? obSellTokenBalance : quoteChunks.mul(j); - const maximumInputFixed = maximumInput.mul( - "1" + "0".repeat(18 - bundledOrders[i].sellTokenDecimals) - ); + if (!bundledOrders[i].takeOrders.length) throw "All orders of this token pair have empty vault balance, skipping..."; - console.log(`>>> Trying to arb with ${ + const fromToken = new Token({ + chainId: config.chainId, + decimals: bundledOrders[i].sellTokenDecimals, + address: bundledOrders[i].sellToken, + symbol: bundledOrders[i].sellTokenSymbol + }); + const toToken = new Token({ + chainId: config.chainId, + decimals: bundledOrders[i].buyTokenDecimals, + address: bundledOrders[i].buyToken, + symbol: bundledOrders[i].buyTokenSymbol + }); + + const obSellTokenBalance = ethers.BigNumber.from(await signer.call({ + data: "0x70a08231000000000000000000000000" + orderbookAddress.slice(2), + to: bundledOrders[i].sellToken + })); + const quoteChunks = obSellTokenBalance.div("5"); + + if (obSellTokenBalance.isZero()) throw `Orderbook has no ${ + bundledOrders[i].sellTokenSymbol + } balance, skipping...`; + + let ethPrice; + const gasPrice = await signer.provider.getGasPrice(); + try { + if (gasCoveragePercentage !== "0") ethPrice = await getEthPrice( + config, + bundledOrders[i].buyToken, + bundledOrders[i].buyTokenDecimals, + gasPrice, + dataFetcher + ); + else ethPrice = "0"; + if (ethPrice === undefined) throw "could not find a route for ETH price, skipping..."; + } + catch { + throw "could not get ETH price, skipping..."; + } + for (let j = 5; j > 0; j--) { + const maximumInput = j === 5 ? obSellTokenBalance : quoteChunks.mul(j); + const maximumInputFixed = maximumInput.mul( + "1" + "0".repeat(18 - bundledOrders[i].sellTokenDecimals) + ); + + console.log(`>>> Trying to arb with ${ + ethers.utils.formatEther(maximumInputFixed) + } ${ + bundledOrders[i].sellTokenSymbol + } as maximum input`); + console.log(">>> Getting best route", "\n"); + + await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); + const pcMap = dataFetcher.getCurrentPoolCodeMap( + fromToken, + toToken + ); + const route = Router.findBestRoute( + pcMap, + config.chainId, + fromToken, + maximumInput, + toToken, + gasPrice.toNumber(), + // 30e9, + // providers, + // poolFilter + ); + if (route.status == "NoWay") console.log( + "\x1b[31m%s\x1b[0m", + `could not find any route for this token pair for ${ ethers.utils.formatEther(maximumInputFixed) } ${ bundledOrders[i].sellTokenSymbol - } as maximum input`); - console.log(">>> Getting best route", "\n"); - await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); - const pcMap = dataFetcher.getCurrentPoolCodeMap( - fromToken, - toToken + }, trying with a lower amount...` + ); + else { + const rateFixed = route.amountOutBN.mul( + "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) + ); + const price = rateFixed.mul("1" + "0".repeat(18)).div(maximumInputFixed); + if (maxProfit) bundledOrders[i].takeOrders = bundledOrders[i].takeOrders.filter( + v => v.ratio !== undefined ? price.mul("102").div("100").gte(v.ratio) : true + ); + console.log( + "Current best route price for this token pair:", + `\x1b[33m${ethers.utils.formatEther(price)}\x1b[0m`, + "\n" + ); + console.log(">>> Route portions: ", "\n"); + visualizeRoute(fromToken, toToken, route.legs).forEach( + v => console.log("\x1b[36m%s\x1b[0m", v) ); - const route = Router.findBestRoute( + console.log(""); + + const rpParams = Router.routeProcessor2Params( pcMap, - config.chainId, + route, fromToken, - maximumInput, toToken, - gasPrice.toNumber(), - // 30e9, - // providers, - // poolFilter - ); - if (route.status == "NoWay") console.log( - "could not find any route for this token pair with this certain amount" + arb.address, + config.routeProcessor3Address, + // permits + // "0.005" ); - else { - const rateFixed = route.amountOutBN.mul( - "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - ); - const price = rateFixed.mul("1" + "0".repeat(18)).div(maximumInputFixed); - console.log(`Current best route price for this token pair: ${ethers.utils.formatEther(price)}`, "\n"); - console.log(">>> Route portions: ", "\n"); - visualizeRoute(fromToken, toToken, route.legs).forEach( - v => console.log("\x1b[36m%s\x1b[0m", v) - ); - console.log(""); - - const rpParams = Router.routeProcessor2Params( - pcMap, - route, - fromToken, - toToken, - arb.address, - config.routeProcessor3Address, - // permits - // "0.005" - ); - const takeOrdersConfigStruct = { - minimumInput: ethers.constants.One, - maximumInput, - maximumIORatio: maxRatio ? ethers.constants.MaxUint256 : price, - orders: bundledOrders[i].takeOrders.map(v => v.takeOrder), - data: ethers.utils.defaultAbiCoder.encode( - ["bytes"], - [rpParams.routeCode] - ) + const takeOrdersConfigStruct = { + minimumInput: ethers.constants.One, + maximumInput, + maximumIORatio: maxRatio ? ethers.constants.MaxUint256 : price, + orders: bundledOrders[i].takeOrders.map(v => v.takeOrder), + data: ethers.utils.defaultAbiCoder.encode( + ["bytes"], + [rpParams.routeCode] + ) + }; + + // building and submit the transaction + try { + const rawtx = { + data: arb.interface.encodeFunctionData("arb", [takeOrdersConfigStruct, "0"]), + to: arb.address, + gasPrice }; + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + let gasLimit; + try { + gasLimit = await signer.estimateGas(rawtx); + } + catch { + throw "nomatch"; + } + gasLimit = gasLimit.mul("112").div("100"); + rawtx.gasLimit = gasLimit; + const gasCost = gasLimit.mul(gasPrice); + const gasCostInToken = ethers.utils.parseUnits( + ethPrice + ).mul( + gasCost + ).div( + "1" + "0".repeat( + 36 - bundledOrders[i].buyTokenDecimals + ) + ); + if (gasCoveragePercentage !== "0") { + const headroom = ( + Number(gasCoveragePercentage) * 1.2 + ).toFixed(); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100") + ] + ); + try { + await signer.estimateGas(rawtx); + } + catch { + throw "dryrun"; + } + } - // building and submit the transaction + // submit the tx only if dry runs with headroom is passed try { - if (ethPrice === undefined) ethPrice = await getEthPrice( - config, - bundledOrders[i].buyToken, - bundledOrders[i].buyTokenDecimals, - gasPrice, - dataFetcher + console.log(">>> Trying to submit the transaction...", "\n"); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100") + ] ); - if (ethPrice === undefined) console.log("can not get ETH price, skipping...", "\n"); - else { - const rawtx = { - data: arb.interface.encodeFunctionData("arb", [takeOrdersConfigStruct, "0"]), - to: arb.address, - gasPrice - }; - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - let gasLimit; - try { - gasLimit = await signer.estimateGas(rawtx); - } - catch { - // console.log(err); - throw "nomatch"; - } - gasLimit = gasLimit.mul("11").div("10"); - rawtx.gasLimit = gasLimit; - const gasCost = gasLimit.mul(gasPrice); - // const maxEstimatedProfit = estimateProfit( - // ethers.utils.formatEther(bundledOrders[i].initPrice), - // ethPrice, - // bundledOrders[i], - // gasCost, - // gasCoveragePercentage - // ).div( - // "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - // ); - // console.log(`Max Estimated Profit: ${ - // ethers.utils.formatUnits( - // maxEstimatedProfit, - // bundledOrders[i].buyTokenDecimals - // ) - // } ${bundledOrders[i].buyTokenSymbol}`, "\n"); - - // if (maxEstimatedProfit.isNegative()) console.log( - // ">>> Skipping because estimated negative profit for this token pair", - // "\n" - // ); - // else { - console.log(">>> Trying to submit the transaction...", "\n"); - const gasCostInToken = ethers.utils.parseUnits( + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + const tx = await signer.sendTransaction(rawtx); + + console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); + console.log( + ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", + "\n" + ); + const receipt = await tx.wait(); + if (receipt.status === 1) { + const clearActualAmount = getActualClearAmount( + arbAddress, + orderbookAddress, + receipt + ); + const income = getIncome(signer, receipt); + const clearActualPrice = getActualPrice( + receipt, + orderbookAddress, + arbAddress, + clearActualAmount.mul("1" + "0".repeat( + 18 - bundledOrders[i].sellTokenDecimals + )), + bundledOrders[i].buyTokenDecimals + ); + const actualGasCost = ethers.BigNumber.from( + receipt.effectiveGasPrice + ).mul(receipt.gasUsed); + const actualGasCostInToken = ethers.utils.parseUnits( ethPrice ).mul( - gasCost + actualGasCost ).div( "1" + "0".repeat( 36 - bundledOrders[i].buyTokenDecimals ) ); - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - rawtx.data = arb.interface.encodeFunctionData( - "arb", - [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100) - ] - ); - const tx = await signer.sendTransaction(rawtx); + const netProfit = income + ? income.sub(actualGasCostInToken) + : undefined; - console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); console.log( - ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", - "\n" + "\x1b[36m%s\x1b[0m", + `Clear Initial Price: ${ethers.utils.formatEther(price)}` ); - - try { - const receipt = await tx.wait(); - // console.log(receipt); - if (receipt.status === 1) { - const clearActualAmount = getActualClearAmount( - arbAddress, - orderbookAddress, - receipt - ); - const income = getIncome(signer, receipt); - const clearActualPrice = getActualPrice( - receipt, - orderbookAddress, - arbAddress, - clearActualAmount.mul("1" + "0".repeat( - 18 - bundledOrders[i].sellTokenDecimals - )), - bundledOrders[i].buyTokenDecimals - ); - const actualGasCost = ethers.BigNumber.from( - receipt.effectiveGasPrice - ).mul(receipt.gasUsed); - const actualGasCostInToken = ethers.utils.parseUnits( - ethPrice - ).mul( - actualGasCost - ).div( - "1" + "0".repeat( - 36 - bundledOrders[i].buyTokenDecimals - ) - ); - const netProfit = income - ? income.sub(actualGasCostInToken) - : undefined; - - console.log( - "\x1b[36m%s\x1b[0m", - `Clear Initial Price: ${ethers.utils.formatEther(bundledOrders[i].initPrice)}` - ); - console.log("\x1b[36m%s\x1b[0m", `Clear Actual Price: ${clearActualPrice}`); - console.log("\x1b[36m%s\x1b[0m", `Clear Amount: ${ - ethers.utils.formatUnits( - clearActualAmount, - bundledOrders[i].sellTokenDecimals - ) - } ${bundledOrders[i].sellTokenSymbol}`); - console.log("\x1b[36m%s\x1b[0m", `Consumed Gas: ${ - ethers.utils.formatEther(actualGasCost) - } ${ - config.nativeToken.symbol - }`, "\n"); - if (income) { - console.log("\x1b[35m%s\x1b[0m", `Gross Income: ${ethers.utils.formatUnits( - income, - bundledOrders[i].buyTokenDecimals - )} ${bundledOrders[i].buyTokenSymbol}`); - console.log("\x1b[35m%s\x1b[0m", `Net Profit: ${ethers.utils.formatUnits( - netProfit, - bundledOrders[i].buyTokenDecimals - )} ${bundledOrders[i].buyTokenSymbol}`, "\n"); - } - - report.push({ - transactionHash: receipt.transactionHash, - tokenPair: - bundledOrders[i].buyTokenSymbol + - "/" + - bundledOrders[i].sellTokenSymbol, - buyToken: bundledOrders[i].buyToken, - buyTokenDecimals: bundledOrders[i].buyTokenDecimals, - sellToken: bundledOrders[i].sellToken, - sellTokenDecimals: bundledOrders[i].sellTokenDecimals, - clearedAmount: clearActualAmount.toString(), - clearPrice: ethers.utils.formatEther( - bundledOrders[i].initPrice - ), - clearActualPrice, - // maxEstimatedProfit, - gasUsed: receipt.gasUsed, - gasCost: actualGasCost, - income, - netProfit, - clearedOrders: bundledOrders[i].takeOrders.map( - v => v.id - ), - }); - j = 0; - } - else if (j > 1) console.log( - `could not clear with ${ethers.utils.formatEther( - maximumInputFixed - )} ${ - bundledOrders[i].sellTokenSymbol - } as max input, trying with lower amount...` - ); - else console.log("could not arb this pair"); - } - catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); - console.log(error, "\n"); - if (j > 1) console.log( - "\x1b[34m%s\x1b[0m", - `could not clear with ${ethers.utils.formatEther( - maximumInputFixed - )} ${ - bundledOrders[i].sellTokenSymbol - } as max input, trying with lower amount...`, "\n" - ); - else console.log("\x1b[34m%s\x1b[0m", "could not arb this pair", "\n"); + console.log("\x1b[36m%s\x1b[0m", `Clear Actual Price: ${clearActualPrice}`); + console.log("\x1b[36m%s\x1b[0m", `Clear Amount: ${ + ethers.utils.formatUnits( + clearActualAmount, + bundledOrders[i].sellTokenDecimals + ) + } ${bundledOrders[i].sellTokenSymbol}`); + console.log("\x1b[36m%s\x1b[0m", `Consumed Gas: ${ + ethers.utils.formatEther(actualGasCost) + } ${ + config.nativeToken.symbol + }`, "\n"); + if (income) { + console.log("\x1b[35m%s\x1b[0m", `Gross Income: ${ethers.utils.formatUnits( + income, + bundledOrders[i].buyTokenDecimals + )} ${bundledOrders[i].buyTokenSymbol}`); + console.log("\x1b[35m%s\x1b[0m", `Net Profit: ${ethers.utils.formatUnits( + netProfit, + bundledOrders[i].buyTokenDecimals + )} ${bundledOrders[i].buyTokenSymbol}`, "\n"); } + + report.push({ + transactionHash: receipt.transactionHash, + tokenPair: + bundledOrders[i].buyTokenSymbol + + "/" + + bundledOrders[i].sellTokenSymbol, + buyToken: bundledOrders[i].buyToken, + buyTokenDecimals: bundledOrders[i].buyTokenDecimals, + sellToken: bundledOrders[i].sellToken, + sellTokenDecimals: bundledOrders[i].sellTokenDecimals, + clearedAmount: clearActualAmount.toString(), + clearPrice: ethers.utils.formatEther(price), + clearActualPrice, + gasUsed: receipt.gasUsed, + gasCost: actualGasCost, + income, + netProfit, + clearedOrders: bundledOrders[i].takeOrders.map( + v => v.id + ), + }); + j = 0; } - } - catch (error) { - if (error !== "nomatch") { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); - console.log(error, "\n"); - // reason, code, method, transaction, error, stack, message - } - if (j > 1) console.log( - "\x1b[34m%s\x1b[0m", + else if (j > 1) console.log( `could not clear with ${ethers.utils.formatEther( maximumInputFixed )} ${ bundledOrders[i].sellTokenSymbol - } as max input, trying with lower amount...`, "\n" + } as max input, trying with lower amount...` ); - else console.log("\x1b[34m%s\x1b[0m", "could not arb this pair", "\n"); + else console.log("could not arb this pair"); } + catch (error) { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); + console.log(error, "\n"); + throw "failed-exec"; + // if (j > 1) console.log( + // "\x1b[34m%s\x1b[0m", + // `could not clear with ${ethers.utils.formatEther( + // maximumInputFixed + // )} ${ + // bundledOrders[i].sellTokenSymbol + // } as max input, trying with lower amount...`, "\n" + // ); + // else console.log("\x1b[34m%s\x1b[0m", "could not arb this pair", "\n"); + } + + } + catch (error) { + if (error !== "nomatch" && error !== "dryrun" && error !== "failed-exec") { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); + console.log(error, "\n"); + // reason, code, method, transaction, error, stack, message + } + if (error === "failed-exec") throw "Transaction execution failed, skipping this pair..."; + if (j > 1) console.log( + "\x1b[34m%s\x1b[0m", + `could not clear with ${ethers.utils.formatEther( + maximumInputFixed + )} ${ + bundledOrders[i].sellTokenSymbol + } as max input, trying with lower amount...`, "\n" + ); + else console.log("\x1b[34m%s\x1b[0m", "could not arb this pair", "\n"); } } } } catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Something went wrong, reason:", "\n"); - console.log(error); + if (typeof error === "string") console.log("\x1b[31m%s\x1b[0m", error, "\n"); + else { + console.log("\x1b[31m%s\x1b[0m", ">>> Something went wrong, reason:", "\n"); + console.log(error); + } } } return report; @@ -521,13 +450,13 @@

Source: srouter.js


- Documentation generated by JSDoc 4.0.2 on Thu Sep 14 2023 13:47:17 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 4.0.2 on Sat Sep 16 2023 00:48:16 GMT+0000 (Coordinated Universal Time)
diff --git a/docs/html/utils.js.html b/docs/html/utils.js.html index 9f2f4c2d..e7c718a4 100644 --- a/docs/html/utils.js.html +++ b/docs/html/utils.js.html @@ -36,11 +36,15 @@

Source: utils.js

/** - * Fallback transports for viem client + * Chain specific fallback data */ -const fallbackTransports = { +const fallbacks = { [ChainId.ARBITRUM_NOVA]: { transport: http("https://nova.arbitrum.io/rpc"), + liquidityProviders: [ + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.ARBITRUM]: { transport: [ @@ -53,30 +57,53 @@

Source: utils.js

http("https://arbitrum.blockpi.network/v1/rpc/public"), http("https://arb-mainnet-public.unifra.io"), ], + liquidityProviders: [ + "dfyn", + "elk", + "sushiswapv3", + "uniswapv3", + "sushiswapv2" + ] }, [ChainId.AVALANCHE]: { transport: [ http("https://api.avax.network/ext/bc/C/rpc"), http("https://rpc.ankr.com/avalanche") ], + liquidityProviders: [ + "elk", + "traderjoe", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.BOBA]: { transport: [ http("https://mainnet.boba.network"), http("https://lightning-replica.boba.network") ], + liquidityProviders: [ + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.BOBA_AVAX]: { transport: [ http("https://avax.boba.network"), http("https://replica.avax.boba.network") ], + liquidityProviders: [ + "sushiswapv2" + ] }, [ChainId.BOBA_BNB]: { transport: [ http("https://bnb.boba.network"), http("https://replica.bnb.boba.network") ], + liquidityProviders: [ + "sushiswapv2" + ] }, [ChainId.BSC]: { transport: [ @@ -86,12 +113,26 @@

Source: utils.js

http("https://bsc-dataseed1.binance.org"), http("https://bsc-dataseed2.binance.org"), ], + liquidityProviders: [ + "apeswap", + "biswap", + "elk", + "jetswap", + "pancakeswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv3" + ] }, [ChainId.BTTC]: { transport: http("https://rpc.bittorrentchain.io"), }, [ChainId.CELO]: { - transport: http("https://forno.celo.org") + transport: http("https://forno.celo.org"), + liquidityProviders: [ + "ubeswap", + "sushiswapv2" + ] }, [ChainId.ETHEREUM]: { transport: [ @@ -104,6 +145,16 @@

Source: utils.js

http("https://1rpc.io/eth"), http("https://ethereum.publicnode.com"), http("https://cloudflare-eth.com"), + ], + liquidityProviders: [ + "apeswap", + "curveswap", + "elk", + "pancakeswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv2", + "uniswapv3" ] }, [ChainId.FANTOM]: { @@ -112,33 +163,66 @@

Source: utils.js

http("https://rpc.fantom.network"), http("https://rpc2.fantom.network"), ], + liquidityProviders: [ + "dfyn", + "elk", + "jetswap", + "spookyswap", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.FUSE]: { transport: http("https://rpc.fuse.io"), + liquidityProviders: [ + "elk", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.GNOSIS]: { transport: http("https://rpc.ankr.com/gnosis"), + liquidityProviders: [ + "elk", + "honeyswap", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.HARMONY]: { transport: [ http("https://api.harmony.one"), http("https://rpc.ankr.com/harmony") ], + liquidityProviders: [ + "sushiswapv2" + ] }, [ChainId.KAVA]: { transport: [ http("https://evm.kava.io"), - http("https://evm2.kava.io") + http("https://evm2.kava.io"), ], + liquidityProviders: [ + "elk" + ] }, [ChainId.MOONBEAM]: { transport: [ http("https://rpc.api.moonbeam.network"), http("https://rpc.ankr.com/moonbeam") ], + liquidityProviders: [ + "sushiswapv2" + ] }, [ChainId.MOONRIVER]: { transport: http("https://rpc.api.moonriver.moonbeam.network"), + liquidityProviders: [ + "elk", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.OPTIMISM]: { transport: [ @@ -149,6 +233,11 @@

Source: utils.js

http("https://optimism.blockpi.network/v1/rpc/public"), http("https://mainnet.optimism.io"), ], + liquidityProviders: [ + "elk", + "sushiswapv3", + "uniswapv3" + ] }, [ChainId.POLYGON]: { transport: [ @@ -164,6 +253,16 @@

Source: utils.js

http("https://rpc-mainnet.maticvigil.com"), // ...polygon.rpcUrls.default.http.map((url) => http(url)), ], + liquidityProviders: [ + "apeswap", + "dfyn", + "elk", + "jetswap", + "quickswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv3" + ] }, [ChainId.POLYGON_ZKEVM]: { transport: [ @@ -171,6 +270,10 @@

Source: utils.js

http("https://rpc.ankr.com/polygon_zkevm"), http("https://rpc.polygon-zkevm.gateway.fm"), ], + liquidityProviders: [ + "dovishv3", + "sushiswapv3" + ] }, [ChainId.THUNDERCORE]: { transport: [ @@ -178,6 +281,10 @@

Source: utils.js

http("https://mainnet-rpc.thundercore.io"), http("https://mainnet-rpc.thundertoken.net"), ], + liquidityProviders: [ + "laserswap", + "sushiswapv3" + ] }, }; @@ -709,11 +816,11 @@

Source: utils.js

transport: config.rpc && config.rpc !== "test" ? useFallbacks ? fallback( - [config.rpc, ...fallbackTransports[config.chainId].transport], + [config.rpc, ...fallbacks[config.chainId].transport], {rank: true} ) : http(config.rpc) - : fallback(fallbackTransports[config.chainId].transport, {rank: true}), + : fallback(fallbacks[config.chainId].transport, {rank: true}), // batch: { // multicall: { // batchSize: 512 @@ -832,8 +939,9 @@

Source: utils.js

* Resolves an array of case-insensitive names to LiquidityProviders, ignores the ones that are not valid * * @param {string[]} liquidityProviders - List of liquidity providers + * @param {number} chainId - The chain id */ -const processLps = (liquidityProviders) => { +const processLps = (liquidityProviders, chainId) => { if ( !liquidityProviders || !Array.isArray(liquidityProviders) || @@ -843,7 +951,12 @@

Source: utils.js

const _lps = []; const LP = Object.values(LiquidityProviders); for (let i = 0; i < liquidityProviders.length; i++) { - const index = LP.findIndex(v => v.toLowerCase() === liquidityProviders[i].toLowerCase()); + const index = LP.findIndex( + v => v.toLowerCase() === liquidityProviders[i].toLowerCase() + && !!fallbacks[chainId]?.liquidityProviders.includes( + liquidityProviders[i].toLowerCase() + ) + ); if (index > -1 && !_lps.includes(LP[index])) _lps.push(LP[index]); } return _lps.length ? _lps : undefined; @@ -1282,8 +1395,66 @@

Source: utils.js

); }; +/** + * Builds initial 0x requests bodies from token addresses that is required + * for getting token prices with least amount of hits possible and that is + * to pair up tokens in a way that each show up only once in a request body + * so that the number of requests will be: "number-of-tokens / 2" at best or + * "(number-of-tokens / 2) + 1" at worst if the number of tokens is an odd digit. + * This way the responses will include the "rate" for sell/buy tokens to native + * network token which will be used to estimate the initial price of all possible + * token pair combinations. + * + * @param {string} api - The 0x API endpoint URL + * @param {any[]} queries - The array that keeps the 0x query text + * @param {string} tokenAddress - The token address + * @param {number} tokenDecimals - The token decimals + * @param {string} tokenSymbol - The token symbol + */ +const build0xQueries = (api, queries, tokenAddress, tokenDecimals, tokenSymbol) => { + tokenAddress = tokenAddress.toLowerCase(); + if (queries.length === 0) queries.push([ + tokenAddress, + tokenDecimals, + tokenSymbol + ]); + else if (!Array.isArray(queries[queries.length - 1])) { + if(!queries.find(v => v.quote.includes(tokenAddress))) queries.push([ + tokenAddress, + tokenDecimals, + tokenSymbol + ]); + } + else { + if( + queries[queries.length - 1][0] !== tokenAddress && + !queries.slice(0, -1).find(v => v.quote.includes(tokenAddress)) + ) { + queries[queries.length - 1] = { + quote: `${ + api + }swap/v1/price?buyToken=${ + queries[queries.length - 1][0] + }&sellToken=${ + tokenAddress + }&sellAmount=${ + "1" + "0".repeat(tokenDecimals) + }`, + tokens: [ + queries[queries.length - 1][2], + tokenSymbol, + queries[queries.length - 1][0], + tokenAddress, + queries[queries.length - 1][1], + tokenDecimals + ] + }; + } + } +}; + module.exports = { - fallbackTransports, + fallbacks, bnFromFloat, toFixed18, fromFixed18, @@ -1305,7 +1476,8 @@

Source: utils.js

promiseTimeout, getActualClearAmount, getRouteForTokens, - visualizeRoute + visualizeRoute, + build0xQueries }; @@ -1317,13 +1489,13 @@

Source: utils.js


- Documentation generated by JSDoc 4.0.2 on Thu Sep 14 2023 13:47:17 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 4.0.2 on Sat Sep 16 2023 00:48:16 GMT+0000 (Coordinated Universal Time)
diff --git a/docs/html/zeroex.js.html b/docs/html/zeroex.js.html index 0910efba..df012dff 100644 --- a/docs/html/zeroex.js.html +++ b/docs/html/zeroex.js.html @@ -35,146 +35,23 @@

Source: zeroex.js

const HEADERS = { headers: { "accept-encoding": "null" } }; -/** - * Builds initial 0x requests bodies from token addresses that is required - * for getting token prices with least amount of hits possible and that is - * to pair up tokens in a way that each show up only once in a request body - * so that the number of requests will be: "number-of-tokens / 2" at best or - * "(number-of-tokens / 2) + 1" at worst if the number of tokens is an odd digit. - * This way the responses will include the "rate" for sell/buy tokens to native - * network token which will be used to estimate the initial price of all possible - * token pair combinations. - * - * @param {string} api - The 0x API endpoint URL - * @param {any[]} quotes - The array that keeps the quotes - * @param {string} tokenAddress - The token address - * @param {number} tokenDecimals - The token decimals - * @param {string} tokenSymbol - The token symbol - */ -const initRequests = (api, quotes, tokenAddress, tokenDecimals, tokenSymbol) => { - if (quotes.length === 0) quotes.push([ - tokenAddress, - tokenDecimals, - tokenSymbol - ]); - else if (!Array.isArray(quotes[quotes.length - 1])) { - if(!quotes.find(v => v.quote.includes(tokenAddress))) quotes.push([ - tokenAddress, - tokenDecimals, - tokenSymbol - ]); - } - else { - if( - quotes[quotes.length - 1][0] !== tokenAddress && - !quotes.slice(0, -1).find(v => v.quote.includes(tokenAddress)) - ) { - quotes[quotes.length - 1] = { - quote: `${ - api - }swap/v1/price?buyToken=${ - quotes[quotes.length - 1][0] - }&sellToken=${ - tokenAddress - }&sellAmount=${ - "1" + "0".repeat(tokenDecimals) - }`, - tokens: [quotes[quotes.length - 1][2], tokenSymbol] - }; - } - } -}; - -/** - * Prepares the bundled orders by getting the best deals from 0x and sorting the - * bundled orders based on the best deals - * - * @param {string[]} quotes - The 0x request quote bodies - * @param {any[]} bundledOrders - The bundled orders array - * @param {boolean} sort - (optional) Sort based on best deals or not - */ -const prepare = async(quotes, bundledOrders, sort = true) => { - try { - console.log(">>> Getting initial prices from 0x"); - const promises = []; - for (let i = 0; i < quotes.length; i++) { - promises.push(axios.get(quotes[i].quote, HEADERS)); - await sleep(1000); - } - const responses = await Promise.allSettled(promises); - - let prices = []; - responses.forEach((v, i) => { - if (v.status == "fulfilled") prices.push([ - { - token: v.value.data.buyTokenAddress, - rate: v.value.data.buyTokenToEthRate - }, - { - token: v.value.data.sellTokenAddress, - rate: v.value.data.sellTokenToEthRate - } - ]); - else { - console.log(`Could not get prices for ${quotes[i].tokens[0]} and ${quotes[i].tokens[1]}, reason:`); - console.log(v.reason.message); - } - }); - prices = prices.flat(); - - bundledOrders.forEach(v => { - console.log(`\nCalculating initial price for ${v.buyTokenSymbol}/${v.sellTokenSymbol} ...`); - const sellTokenPrice = prices.find( - e => e.token.toLowerCase() === v.sellToken.toLowerCase() - )?.rate; - const buyTokenPrice = prices.find( - e => e.token.toLowerCase() === v.buyToken.toLowerCase() - )?.rate; - if (sellTokenPrice && buyTokenPrice) { - v.initPrice = ethers.utils.parseUnits(buyTokenPrice) - .mul(ethers.utils.parseUnits("1")) - .div(ethers.utils.parseUnits(sellTokenPrice)); - console.log(`result: ${ethers.utils.formatEther(v.initPrice)}`); - } - else console.log("Could not calculate initial price for this token pair due to lack of required data!"); - }); - bundledOrders = bundledOrders.filter(v => v.initPrice !== undefined); - - if (sort) { - console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); - bundledOrders.sort( - (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 - ); - } - return bundledOrders; - } - catch (error) { - console.log("something went wrong during the process of getting initial prices!"); - console.log(error); - return []; - } -}; - /** * Main function that gets order details from subgraph, bundles the ones that have balance and tries clearing them with 0x * * @param {object} config - The configuration object * @param {any[]} ordersDetails - The order details queried from subgraph * @param {string} gasCoveragePercentage - (optional) The percentage of the gas cost to cover on each transaction for it to be considered profitable and get submitted - * @param {boolean} prioritization - (optional) Prioritize better deals to get cleared first, default is true * @returns The report of details of cleared orders */ const zeroExClear = async( config, ordersDetails, - gasCoveragePercentage = "100", - prioritization = true + gasCoveragePercentage = "100" ) => { if ( gasCoveragePercentage < 0 || !Number.isInteger(Number(gasCoveragePercentage)) ) throw "invalid gas coverage percentage, must be an integer greater than equal 0"; - if (typeof prioritization !== "boolean") throw "invalid value for 'prioritization'"; let rateLimit; if (config.monthlyRatelimit !== undefined) { @@ -190,8 +67,8 @@

Source: zeroex.js

const proxyAddress = config.zeroEx.proxyAddress; const arbAddress = config.arbAddress; const orderbookAddress = config.orderbookAddress; - const nativeToken = config.nativeToken; const arbType = config.arbType; + // const nativeToken = config.nativeWrappedToken; // set the api key in headers if (config.apiKey) HEADERS.headers["0x-api-key"] = config.apiKey; @@ -204,14 +81,16 @@

Source: zeroex.js

const orderbook = new ethers.Contract(orderbookAddress, orderbookAbi, signer); console.log( - "------------------------- Starting Clearing Process -------------------------", + "------------------------- Starting The", + "\x1b[32m0x\x1b[0m", + "Mode -------------------------", "\n" ); console.log("\x1b[33m%s\x1b[0m", Date()); console.log("Arb Contract Address: " , arbAddress); console.log("OrderBook Contract Address: " , orderbookAddress, "\n"); - const initQuotes = []; + // const initPriceQueries = []; let bundledOrders = []; if (ordersDetails.length) { @@ -219,22 +98,22 @@

Source: zeroex.js

"------------------------- Bundling Orders -------------------------", "\n" ); bundledOrders = await bundleTakeOrders(ordersDetails, orderbook, arb); - for (let i = 0; i < bundledOrders.length; i++) { - initRequests( - api, - initQuotes, - bundledOrders[i].sellToken, - bundledOrders[i].sellTokenDecimals, - bundledOrders[i].sellTokenSymbol - ); - initRequests( - api, - initQuotes, - bundledOrders[i].buyToken, - bundledOrders[i].buyTokenDecimals, - bundledOrders[i].buyTokenSymbol - ); - } + // for (let i = 0; i < bundledOrders.length; i++) { + // build0xQueries( + // api, + // initPriceQueries, + // bundledOrders[i].sellToken, + // bundledOrders[i].sellTokenDecimals, + // bundledOrders[i].sellTokenSymbol + // ); + // build0xQueries( + // api, + // initPriceQueries, + // bundledOrders[i].buyToken, + // bundledOrders[i].buyTokenDecimals, + // bundledOrders[i].buyTokenSymbol + // ); + // } } else { console.log("No orders found, exiting...", "\n"); @@ -246,36 +125,38 @@

Source: zeroex.js

return; } - console.log( - "------------------------- Getting Best Deals From 0x -------------------------", - "\n" - ); - if (Array.isArray(initQuotes[initQuotes.length - 1])) { - initQuotes[initQuotes.length - 1] = { - quote: `${ - api - }swap/v1/price?buyToken=${ - nativeToken.address.toLowerCase() - }&sellToken=${ - initQuotes[initQuotes.length - 1][0] - }&sellAmount=${ - "1" + "0".repeat(initQuotes[initQuotes.length - 1][1]) - }`, - tokens: ["ETH", initQuotes[initQuotes.length - 1][2]] - }; - } - hits += initQuotes.length; - bundledOrders = await prepare( - initQuotes, - bundledOrders, - prioritization - ); - - if (bundledOrders.length) console.log( - "------------------------- Trying To Clear Bundled Orders -------------------------", - "\n" - ); - else { + // console.log( + // "------------------------- Getting Best Deals From 0x -------------------------", + // "\n" + // ); + // if (Array.isArray(initPriceQueries[initPriceQueries.length - 1])) { + // initPriceQueries[initPriceQueries.length - 1] = { + // quote: `${ + // api + // }swap/v1/price?buyToken=${ + // nativeToken.address.toLowerCase() + // }&sellToken=${ + // initPriceQueries[initPriceQueries.length - 1][0] + // }&sellAmount=${ + // "1" + "0".repeat(initPriceQueries[initPriceQueries.length - 1][1]) + // }`, + // tokens: [ + // nativeToken.symbol, + // initPriceQueries[initPriceQueries.length - 1][2], + // nativeToken.address.toLowerCase(), + // initPriceQueries[initPriceQueries.length - 1][0], + // nativeToken.decimals, + // initPriceQueries[initPriceQueries.length - 1][1], + // ] + // }; + // } + // hits += initPriceQueries.length; + // bundledOrders = await prepare( + // initPriceQueries, + // bundledOrders + // ); + + if (bundledOrders.length === 0) { console.log("Could not find any order to clear for current market price, exiting...", "\n"); return; } @@ -383,7 +264,6 @@

Source: zeroex.js

); if (bundledOrders[i].takeOrders.length) { - cumulativeAmount = ethers.constants.Zero; bundledOrders[i].takeOrders.forEach(v => { cumulativeAmount = cumulativeAmount.add(v.quoteAmount); @@ -422,10 +302,9 @@

Source: zeroex.js

orders: bundledOrders[i].takeOrders.map(v => v.takeOrder), }; if (/^flash-loan-v3$|^order-taker$/.test(arbType)) { - takeOrdersConfigStruct.data = "0x00"; + takeOrdersConfigStruct.data = "0x"; delete takeOrdersConfigStruct.output; delete takeOrdersConfigStruct.input; - if (arbType === "flash-loan-v3") takeOrdersConfigStruct.data = "0x"; } // submit the transaction @@ -435,8 +314,6 @@

Source: zeroex.js

[txQuote.allowanceTarget, proxyAddress, txQuote.data] ); if (arbType === "order-taker") takeOrdersConfigStruct.data = exchangeData; - - // console.log(">>> Estimating the profit for this token pair...", "\n"); const rawtx = { data: arb.interface.encodeFunctionData( "arb", @@ -456,43 +333,9 @@

Source: zeroex.js

}; console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); let gasLimit = await signer.estimateGas(rawtx); - gasLimit = gasLimit.mul("11").div("10"); + gasLimit = gasLimit.mul("112").div("100"); rawtx.gasLimit = gasLimit; - const gasCost = gasLimit.mul(gasPrice); - - // let gasLimit; - // console.log("Block Number: " + await signer.provider.getBlockNumber()); - // if (arbType === "order-taker") gasLimit = await arb.estimateGas.arb( - // takeOrdersConfigStruct, - // ethers.constants.Zero, - // { gasPrice: txQuote.gasPrice } - // ); - // else gasLimit = await arb.estimateGas.arb( - // takeOrdersConfigStruct, - // ethers.constants.Zero, - // exchangeData, - // { gasPrice: txQuote.gasPrice } - // ); - - // gasLimit = gasLimit.mul("11").div("10"); - // const gasCost = gasLimit.mul(txQuote.gasPrice); - // const maxEstimatedProfit = estimateProfit( - // txQuote.price, - // txQuote.buyTokenToEthRate, - // bundledOrders[i], - // gasCost, - // gasCoveragePercentage - // ).div( - // "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - // ); - // console.log(`Max Estimated Profit: ${ - // ethers.utils.formatUnits( - // maxEstimatedProfit, - // bundledOrders[i].buyTokenDecimals - // ) - // } ${bundledOrders[i].buyTokenSymbol}`, "\n"); - // if (!maxEstimatedProfit.isNegative()) { - console.log(">>> Trying to submit the transaction for this token pair...", "\n"); + const gasCost = gasLimit.mul(txQuote.gasPrice); const gasCostInToken = ethers.utils.parseUnits( txQuote.buyTokenToEthRate ).mul( @@ -502,31 +345,50 @@

Source: zeroex.js

36 - bundledOrders[i].buyTokenDecimals ) ); - rawtx.data = arb.interface.encodeFunctionData( - "arb", - arbType === "order-taker" - ? [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100) - ] - : [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100), - exchangeData - ] - ); - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - const tx = await signer.sendTransaction(rawtx); - - console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); - console.log( - ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", - "\n" - ); + if (gasCoveragePercentage !== "0") { + const headroom = ( + Number(gasCoveragePercentage) * 1.2 + ).toFixed(); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100"), + exchangeData + ] + ); + await signer.estimateGas(rawtx); + } try { + console.log(">>> Trying to submit the transaction for this token pair...", "\n"); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100"), + exchangeData + ] + ); + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + const tx = await signer.sendTransaction(rawtx); + + console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); + console.log( + ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", + "\n" + ); const receipt = await tx.wait(); - // console.log(receipt); const income = getIncome(signer, receipt); const clearActualPrice = getActualPrice( receipt, @@ -593,7 +455,6 @@

Source: zeroex.js

clearPrice: txQuote.price, clearGuaranteedPrice: txQuote.guaranteedPrice, clearActualPrice, - // maxEstimatedProfit, gasUsed: receipt.gasUsed, gasCost: actualGasCost, income, @@ -607,12 +468,15 @@

Source: zeroex.js

console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); console.log(error, "\n"); } - // } - // else console.log(">>> Skipping because estimated negative profit for this token pair", "\n"); } catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); - console.log(error, "\n"); + if (error === "dryrun" || error === "nomatch") { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction dry run failed, skipping..."); + } + else { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); + console.log(error, "\n"); + } } } else console.log("\x1b[31m%s\x1b[0m", "Failed to get quote from 0x", "\n"); @@ -661,13 +525,13 @@

Source: zeroex.js


- Documentation generated by JSDoc 4.0.2 on Thu Sep 14 2023 13:47:17 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 4.0.2 on Sat Sep 16 2023 00:48:16 GMT+0000 (Coordinated Universal Time)
diff --git a/src/curve.js b/src/curve.js index f96e49ab..95408dbb 100644 --- a/src/curve.js +++ b/src/curve.js @@ -6,7 +6,6 @@ const { getEthPrice, getDataFetcher, getActualPrice, - // estimateProfit, bundleTakeOrders } = require("./utils"); @@ -127,7 +126,7 @@ const getAvailableSwaps = (config) => { * @param {ethers.Signer} - The ethersjs signer * @param {boolean} sort - (optional) Sort based on best deals or not */ -const prepare = async(bundledOrders, availableSwaps, config, signer, sort = true) => { +const prepare = (bundledOrders, availableSwaps, config, signer) => { for (let i = 0; i < bundledOrders.length; i++) { let pairFormat; const bOrder = bundledOrders[i]; @@ -159,61 +158,62 @@ const prepare = async(bundledOrders, availableSwaps, config, signer, sort = true : pool.underlyingCoinsUnwrapped.findIndex( v => v.symbol === bOrder.sellTokenSymbol ); - try { - let rate; - // let cumulativeAmountFixed = ethers.constants.Zero; - // bOrder.takeOrders.forEach(v => { - // cumulativeAmountFixed = cumulativeAmountFixed.add(v.quoteAmount); - // }); - // const cumulativeAmount = cumulativeAmountFixed.div("1" + "0".repeat(18 - bOrder.sellTokenDecimals)); - if (pairFormat === "c") { - rate = await bOrder.poolContract.get_dy( - bOrder.sellTokenIndex, - bOrder.buyTokenIndex, - // cumulativeAmount - "1" + "0".repeat(bOrder.sellTokenDecimals) - ); - } - else { - rate = await bOrder.poolContract.get_dy_underlying( - bOrder.sellTokenIndex, - bOrder.buyTokenIndex, - // cumulativeAmount - "1" + "0".repeat(bOrder.sellTokenDecimals) - ); - } - // const rateFixed = rate.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - // const price = rateFixed.mul("1" + "0".repeat(18)).div(cumulativeAmountFixed); - const price = rate.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - bOrder.initPrice = price; + // try { + // let rate; + // // let cumulativeAmountFixed = ethers.constants.Zero; + // // bOrder.takeOrders.forEach(v => { + // // cumulativeAmountFixed = cumulativeAmountFixed.add(v.quoteAmount); + // // }); + // // const cumulativeAmount = cumulativeAmountFixed.div("1" + "0".repeat(18 - bOrder.sellTokenDecimals)); + // if (pairFormat === "c") { + // rate = await bOrder.poolContract.get_dy( + // bOrder.sellTokenIndex, + // bOrder.buyTokenIndex, + // // cumulativeAmount + // "1" + "0".repeat(bOrder.sellTokenDecimals) + // ); + // } + // else { + // rate = await bOrder.poolContract.get_dy_underlying( + // bOrder.sellTokenIndex, + // bOrder.buyTokenIndex, + // // cumulativeAmount + // "1" + "0".repeat(bOrder.sellTokenDecimals) + // ); + // } + // // const rateFixed = rate.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); + // // const price = rateFixed.mul("1" + "0".repeat(18)).div(cumulativeAmountFixed); + // const price = rate.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); + // bOrder.initPrice = price; - console.log(`Current market price for ${pair}: ${ethers.utils.formatEther(price)}`); - console.log("Current ratio of the orders in this token pair:"); - bOrder.takeOrders.forEach(v => { - console.log(ethers.utils.formatEther(v.ratio)); - }); - bOrder.takeOrders = bOrder.takeOrders.filter( - v => price.gte(v.ratio) - ); - console.log("\n"); - } - catch(error) { - console.log(`>>> could not get price for this ${pair} due to:`); - console.log(error); - } + // console.log(`Current market price for ${pair}: ${ethers.utils.formatEther(price)}`); + // console.log("Current ratio of the orders in this token pair:"); + // bOrder.takeOrders.forEach(v => { + // console.log(ethers.utils.formatEther(v.ratio)); + // }); + // bOrder.takeOrders = bOrder.takeOrders.filter( + // v => price.gte(v.ratio) + // ); + // console.log("\n"); + // } + // catch(error) { + // console.log(`>>> could not get price for this ${pair} due to:`); + // console.log(error); + // } } } - console.log( - ">>> Filtering bundled orders with lower ratio than current market price...", - "\n" - ); - bundledOrders = bundledOrders.filter(v => v.initPrice && v.takeOrders.length > 0); - if (sort) { - console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); - bundledOrders.sort( - (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 - ); - } + // console.log( + // ">>> Filtering bundled orders with lower ratio than current market price...", + // "\n" + // ); + // bundledOrders = bundledOrders.filter(v => v.initPrice && v.takeOrders.length > 0); + // if (sort) { + // console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); + // bundledOrders.sort( + // (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 + // ); + // } + bundledOrders = bundledOrders.filter(v => v.poolIndex !== undefined); return bundledOrders; }; @@ -224,20 +224,17 @@ const prepare = async(bundledOrders, availableSwaps, config, signer, sort = true * @param {any[]} ordersDetails - The order details queried from subgraph * @param {string} gasCoveragePercentage - (optional) The percentage of the gas cost to cover on each transaction * for it to be considered profitable and get submitted - * @param {boolean} prioritization - (optional) Prioritize better deals to get cleared first, default is true * @returns The report of details of cleared orders */ const curveClear = async( config, ordersDetails, - gasCoveragePercentage = "100", - prioritization = true + gasCoveragePercentage = "100" ) => { if ( gasCoveragePercentage < 0 || !Number.isInteger(Number(gasCoveragePercentage)) ) throw "invalid gas coverage percentage, must be an integer greater than equal 0"; - if (typeof prioritization !== "boolean") throw "invalid value for 'prioritization'"; const signer = config.signer; const arbAddress = config.arbAddress; @@ -250,10 +247,10 @@ const curveClear = async( // instantiating orderbook contract const orderbook = new ethers.Contract(orderbookAddress, orderbookAbi, signer); - let gasPrice = await signer.provider.getGasPrice(); - console.log( - "------------------------- Starting Clearing Process -------------------------", + "------------------------- Starting The", + "\x1b[32mCURVE.FI\x1b[0m", + "Mode -------------------------", "\n" ); console.log("\x1b[33m%s\x1b[0m", Date()); @@ -266,17 +263,12 @@ const curveClear = async( "------------------------- Bundling Orders -------------------------", "\n" ); bundledOrders = await bundleTakeOrders(ordersDetails, orderbook, arb); - console.log( - "------------------------- Getting Best Deals From Curve -------------------------", - "\n" - ); const availableSwaps = getAvailableSwaps(config); - bundledOrders = await prepare( + bundledOrders = prepare( bundledOrders, availableSwaps, config, - signer, - prioritization + signer ); } else { @@ -289,16 +281,14 @@ const curveClear = async( return; } - console.log( - "------------------------- Trying To Clear Bundled Orders -------------------------", - "\n" - ); - const report = []; - const dataFetcher = getDataFetcher(config, processLps(config.lps), !!config.usePublicRpc); + const dataFetcher = getDataFetcher( + config, + processLps(config.lps, config.chainId), + !!config.usePublicRpc + ); for (let i = 0; i < bundledOrders.length; i++) { try { - gasPrice = await signer.provider.getGasPrice(); console.log( `------------------------- Trying To Clear ${ bundledOrders[i].buyTokenSymbol @@ -448,14 +438,16 @@ const curveClear = async( } if (arbType === "order-taker") takeOrdersConfigStruct.data = exchangeData; - // console.log(">>> Estimating the profit for this token pair...", "\n"); - const ethPrice = await getEthPrice( - config, - bundledOrders[i].buyToken, - bundledOrders[i].buyTokenDecimals, - gasPrice, - dataFetcher - ); + const gasPrice = await signer.provider.getGasPrice(); + const ethPrice = gasCoveragePercentage !== "0" + ? "0" + : await getEthPrice( + config, + bundledOrders[i].buyToken, + bundledOrders[i].buyTokenDecimals, + gasPrice, + dataFetcher + ); if (ethPrice === undefined) console.log("can not get ETH price, skipping...", "\n"); else { const rawtx = { @@ -477,48 +469,9 @@ const curveClear = async( }; console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); let gasLimit = await signer.estimateGas(rawtx); - gasLimit = gasLimit.mul("11").div("10"); + gasLimit = gasLimit.mul("112").div("100"); rawtx.gasLimit = gasLimit; const gasCost = gasLimit.mul(gasPrice); - // let gasLimit; - // console.log("Block Number: " + await signer.provider.getBlockNumber()); - // if (arbType === "order-taker") gasLimit = await arb.estimateGas.arb( - // takeOrdersConfigStruct, - // // set to zero for estimation - // ethers.constants.Zero, - // { gasPrice } - // ); - // else gasLimit = await arb.estimateGas.arb( - // takeOrdersConfigStruct, - // // set to zero for estimation - // ethers.constants.Zero, - // data, - // { gasPrice } - // ); - // gasLimit = gasLimit.mul("11").div("10"); - // const gasCost = gasLimit.mul(gasPrice); - // const maxEstimatedProfit = estimateProfit( - // ethers.utils.formatEther(bundledOrders[i].initPrice), - // ethPrice, - // bundledOrders[i], - // gasCost, - // gasCoveragePercentage - // ).div( - // "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - // ); - // console.log(`Max Estimated Profit: ${ - // ethers.utils.formatUnits( - // maxEstimatedProfit, - // bundledOrders[i].buyTokenDecimals - // ) - // } ${bundledOrders[i].buyTokenSymbol}`, "\n"); - - // if (maxEstimatedProfit.isNegative()) console.log( - // ">>> Skipping because estimated negative profit for this token pair", - // "\n" - // ); - // else { - console.log(">>> Trying to submit the transaction for this token pair...", "\n"); const gasCostInToken = ethers.utils.parseUnits( ethPrice ).mul( @@ -528,43 +481,50 @@ const curveClear = async( 36 - bundledOrders[i].buyTokenDecimals ) ); - rawtx.data = arb.interface.encodeFunctionData( - "arb", - arbType === "order-taker" - ? [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100) - ] - : [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100), - exchangeData - ] - ); - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - const tx = await signer.sendTransaction(rawtx); - // if (arbType === "order-taker") tx = await arb.arb( - // takeOrdersConfigStruct, - // // set to zero because only profitable transactions are submitted - // gasCostInToken.mul(gasCoveragePercentage).div(100), - // { gasPrice, gasLimit } - // ); - // else tx = await arb.arb( - // takeOrdersConfigStruct, - // // set to zero because only profitable transactions are submitted - // gasCostInToken.mul(gasCoveragePercentage).div(100), - // data, - // { gasPrice, gasLimit } - // ); - console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); - console.log( - ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", - "\n" - ); + if (gasCoveragePercentage !== "0") { + const headroom = ( + Number(gasCoveragePercentage) * 1.15 + ).toFixed(); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100"), + exchangeData + ] + ); + await signer.estimateGas(rawtx); + } try { + console.log(">>> Trying to submit the transaction for this token pair...", "\n"); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100"), + exchangeData + ] + ); + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + const tx = await signer.sendTransaction(rawtx); + console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); + console.log( + ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", + "\n" + ); + const receipt = await tx.wait(); - // console.log(receipt); const income = getIncome(signer, receipt); const clearActualPrice = getActualPrice( receipt, @@ -593,10 +553,10 @@ const curveClear = async( `${bundledOrders[i].takeOrders.length} orders cleared successfully of this token pair!`, "\n" ); - console.log( - "\x1b[36m%s\x1b[0m", - `Clear Initial Price: ${ethers.utils.formatEther(bundledOrders[i].initPrice)}` - ); + // console.log( + // "\x1b[36m%s\x1b[0m", + // `Clear Initial Price: ${ethers.utils.formatEther(bundledOrders[i].initPrice)}` + // ); console.log("\x1b[36m%s\x1b[0m", `Clear Actual Price: ${clearActualPrice}`); console.log("\x1b[36m%s\x1b[0m", `Clear Amount: ${ ethers.utils.formatUnits( @@ -631,15 +591,10 @@ const curveClear = async( sellToken: bundledOrders[i].sellToken, sellTokenDecimals: bundledOrders[i].sellTokenDecimals, clearedAmount: bundledQuoteAmount.toString(), - clearPrice: ethers.utils.formatEther( - bundledOrders[i].initPrice - ), - // clearGuaranteedPrice: ethers.utils.formatUnits( - // guaranteedAmount, - // bundledOrders[i].buyTokenDecimals + // clearPrice: ethers.utils.formatEther( + // bundledOrders[i].initPrice // ), clearActualPrice, - // maxEstimatedProfit, gasUsed: receipt.gasUsed, gasCost: actualGasCost, income, @@ -651,12 +606,16 @@ const curveClear = async( console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); console.log(error, "\n"); } - // } } } catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); - console.log(error, "\n"); + if (error === "dryrun" || error === "nomatch") { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction dry run failed, skipping..."); + } + else { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); + console.log(error, "\n"); + } } } } diff --git a/src/index.js b/src/index.js index c7e2ee01..a26af1d9 100644 --- a/src/index.js +++ b/src/index.js @@ -59,10 +59,10 @@ const clearOptions = { * for it to be considered profitable and get submitted */ gasCoveragePercentage: "100", - /** - * Prioritize better deals to get cleared first, default is true - */ - prioritization: true + // /** + // * Prioritize better deals to get cleared first, default is true + // */ + // prioritization: true }; /** @@ -101,7 +101,8 @@ const getOrderDetails = async(sgs, json, signer, sgFilters) => { query: getQuery( sgFilters?.orderHash, sgFilters?.orderOwner, - sgFilters?.orderInterpreter + sgFilters?.orderInterpreter, + sgFilters?.shuffle ) }, { headers: { "Content-Type": "application/json" } } @@ -195,7 +196,7 @@ const getConfig = async( * @param {string} mode - The mode for clearing, either "0x" or "curve" or "router" * @param {object} config - The configuration object * @param {any[]} ordersDetails - The order details queried from subgraph - * @param {clearOptions} options - The options for clear, 'slippage',' gasCoveragePercentage' and 'prioritization' + * @param {clearOptions} options - The options for clear, such as 'gasCoveragePercentage'' * @returns The report of details of cleared orders */ const clear = async( @@ -207,9 +208,9 @@ const clear = async( const _mode = mode.toLowerCase(); const version = versions.node; const majorVersion = Number(version.slice(0, version.indexOf("."))); - const prioritization = options.prioritization !== undefined - ? options.prioritization - : clearOptions.prioritization; + // const prioritization = options.prioritization !== undefined + // ? !!options.prioritization + // : clearOptions.prioritization; const gasCoveragePercentage = options.gasCoveragePercentage !== undefined ? options.gasCoveragePercentage : clearOptions.gasCoveragePercentage; @@ -225,14 +226,14 @@ const clear = async( config, ordersDetails, gasCoveragePercentage, - prioritization + // prioritization ); else if (_mode === "curve") { if (majorVersion >= 18) return await curveClear( config, ordersDetails, gasCoveragePercentage, - prioritization + // prioritization ); else throw `NodeJS v18 or higher is required for running the app in "curve" mode, current version: ${version}`; } @@ -241,7 +242,7 @@ const clear = async( config, ordersDetails, gasCoveragePercentage, - prioritization + // prioritization ); else throw `NodeJS v18 or higher is required for running the app in "router" mode, current version: ${version}`; } @@ -250,7 +251,7 @@ const clear = async( config, ordersDetails, gasCoveragePercentage, - prioritization + // prioritization ); else throw `NodeJS v18 or higher is required for running the app in "router" mode, current version: ${version}`; } diff --git a/src/query.js b/src/query.js index b39e28aa..e35d3170 100644 --- a/src/query.js +++ b/src/query.js @@ -49,15 +49,36 @@ const DefaultQuery = `{ * @param {string} orderHash - The order hash to apply as filter * @param {string} owner - The order owner to apply as filter * @param {string} interpreter - The interpreter to apply as filter + * @param {number} shuffle - (optional) A number in range of 0 - 3 that + * will change the order of getting order details from subgraph, mostly + * used for continuous bot running * @returns the query string */ -const getQuery = (orderHash, owner, interpreter) => { +const getQuery = (orderHash, owner, interpreter, shuffle = 0) => { const orderHashFilter = orderHash ? `, id :"${orderHash.toLowerCase()}"` : ""; const ownerFilter = owner ? `, owner :"${owner.toLowerCase()}"` : ""; const interpreterFilter = interpreter ? `, interpreter :"${interpreter.toLowerCase()}"` : ""; + let orderingProp, orderingDir; + const _turn = shuffle % 4; + if (_turn === 0) { + orderingProp = "id"; + orderingDir = "asc"; + } + if (_turn === 1) { + orderingProp = "id"; + orderingDir = "desc"; + } + if (_turn === 2) { + orderingProp = "timestamp"; + orderingDir = "asc"; + } + if (_turn === 3) { + orderingProp = "timestamp"; + orderingDir = "desc"; + } return `{ orders( - where: {orderActive: true${orderHashFilter}${ownerFilter}${interpreterFilter}} + orderBy: ${orderingProp}, orderDirection: ${orderingDir}, where: {orderActive: true${orderHashFilter}${ownerFilter}${interpreterFilter}} ) { id handleIO diff --git a/src/router.js b/src/router.js index c68e21ef..6c9b3dcb 100644 --- a/src/router.js +++ b/src/router.js @@ -14,82 +14,6 @@ const { } = require("./utils"); -/** - * Prepares the bundled orders by getting the best deals from Router and sorting the - * bundled orders based on the best deals - * - * @param {any[]} bundledOrders - The bundled orders array - * @param {any} dataFetcher - The DataFetcher instance - * @param {any} config - The network config data - * @param {ethers.BigNumber} gasPrice - The network gas price - * @param {boolean} sort - (optional) Sort based on best deals or not - */ -const prepare = async(bundledOrders, dataFetcher, config, gasPrice, sort = true) => { - for (let i = 0; i < bundledOrders.length; i++) { - const bOrder = bundledOrders[i]; - const pair = bOrder.buyTokenSymbol + "/" + bOrder.sellTokenSymbol; - try { - const fromToken = new Token({ - chainId: config.chainId, - decimals: bOrder.sellTokenDecimals, - address: bOrder.sellToken, - symbol: bOrder.sellTokenSymbol - }); - const toToken = new Token({ - chainId: config.chainId, - decimals: bOrder.buyTokenDecimals, - address: bOrder.buyToken, - symbol: bOrder.buyTokenSymbol - }); - await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); - const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken, toToken); - const route = Router.findBestRoute( - pcMap, - config.chainId, - fromToken, - // cumulativeAmount, - "1" + "0".repeat(bOrder.sellTokenDecimals), - toToken, - gasPrice.toNumber(), - // providers, - // poolFilter - ); - if (route.status == "NoWay") throw "could not find any route for this token pair"; - - // const rateFixed = route.amountOutBN.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - // const price = rateFixed.mul("1" + "0".repeat(18)).div(cumulativeAmountFixed); - const price = route.amountOutBN.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - bOrder.initPrice = price; - - console.log(`Current market price for ${pair} for: ${ethers.utils.formatEther(price)}`); - console.log("Current ratio of the orders in this token pair:"); - bOrder.takeOrders.forEach(v => { - console.log(ethers.utils.formatEther(v.ratio)); - }); - bOrder.takeOrders = bOrder.takeOrders.filter( - v => price.gte(v.ratio) - ); - console.log("\n"); - } - catch(error) { - console.log(`>>> could not get price for this ${pair} due to:`); - console.log(error, "\n"); - } - } - console.log( - ">>> Filtering bundled orders with lower ratio than current market price...", - "\n" - ); - bundledOrders = bundledOrders.filter(v => v.initPrice && v.takeOrders.length > 0); - if (sort) { - console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); - bundledOrders.sort( - (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 - ); - } - return bundledOrders; -}; - /** * Main function that gets order details from subgraph, bundles the ones that have balance and tries clearing them with router contract * @@ -97,22 +21,19 @@ const prepare = async(bundledOrders, dataFetcher, config, gasPrice, sort = true) * @param {any[]} ordersDetails - The order details queried from subgraph * @param {string} gasCoveragePercentage - (optional) The percentage of the gas cost to cover on each transaction * for it to be considered profitable and get submitted - * @param {boolean} prioritization - (optional) Prioritize better deals to get cleared first, default is true * @returns The report of details of cleared orders */ const routerClear = async( config, ordersDetails, - gasCoveragePercentage = "100", - prioritization = true + gasCoveragePercentage = "100" ) => { if ( gasCoveragePercentage < 0 || !Number.isInteger(Number(gasCoveragePercentage)) ) throw "invalid gas coverage percentage, must be an integer greater than equal 0"; - if (typeof prioritization !== "boolean") throw "invalid value for 'prioritization'"; - const lps = processLps(config.lps); + const lps = processLps(config.lps, config.chainId); const dataFetcher = getDataFetcher(config, lps, !!config.usePublicRpc); const signer = config.signer; const arbAddress = config.arbAddress; @@ -125,10 +46,10 @@ const routerClear = async( // instantiating orderbook contract const orderbook = new ethers.Contract(orderbookAddress, orderbookAbi, signer); - let gasPrice = await signer.provider.getGasPrice(); - console.log( - "------------------------- Starting Clearing Process -------------------------", + "------------------------- Starting The", + "\x1b[32mROUTER\x1b[0m", + "Mode -------------------------", "\n" ); console.log("\x1b[33m%s\x1b[0m", Date()); @@ -141,11 +62,6 @@ const routerClear = async( "------------------------- Bundling Orders -------------------------", "\n" ); bundledOrders = await bundleTakeOrders(ordersDetails, orderbook, arb); - console.log( - "------------------------- Getting Best Deals From RouteProcessor3 -------------------------", - "\n" - ); - bundledOrders = await prepare(bundledOrders, dataFetcher, config, gasPrice, prioritization); } else { console.log("No orders found, exiting...", "\n"); @@ -157,15 +73,9 @@ const routerClear = async( return; } - console.log( - "------------------------- Trying To Clear Bundled Orders -------------------------", - "\n" - ); - const report = []; for (let i = 0; i < bundledOrders.length; i++) { try { - gasPrice = await signer.provider.getGasPrice(); console.log( `------------------------- Trying To Clear ${ bundledOrders[i].buyTokenSymbol @@ -247,6 +157,7 @@ const routerClear = async( await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken,toToken); + const gasPrice = await signer.provider.getGasPrice(); const route = Router.findBestRoute( pcMap, config.chainId, @@ -264,7 +175,11 @@ const routerClear = async( "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) ); const price = rateFixed.mul("1" + "0".repeat(18)).div(cumulativeAmountFixed); - console.log(`Current best route price for this token pair: ${ethers.utils.formatEther(price)}`, "\n"); + console.log( + "Current best route price for this token pair:", + `\x1b[33m${ethers.utils.formatEther(price)}\x1b[0m`, + "\n" + ); // filter take orders based on curent price and calculate final bundle quote amount bundledOrders[i].takeOrders = bundledOrders[i].takeOrders.filter( @@ -301,11 +216,6 @@ const routerClear = async( v => console.log("\x1b[36m%s\x1b[0m", v) ); console.log(""); - // console.log( - // "\x1b[36m%s\x1b[0m", - // visualizeRoute(fromToken.address, toToken.address, route.legs), - // "\n" - // ); const rpParams = Router.routeProcessor2Params( pcMap, @@ -317,7 +227,6 @@ const routerClear = async( // permits // "0.005" ); - const takeOrdersConfigStruct = { output: bundledOrders[i].buyToken, input: bundledOrders[i].sellToken, @@ -362,14 +271,15 @@ const routerClear = async( ); if (arbType === "order-taker") takeOrdersConfigStruct.data = exchangeData; - // console.log(">>> Estimating the profit for this token pair...", "\n"); - const ethPrice = await getEthPrice( - config, - bundledOrders[i].buyToken, - bundledOrders[i].buyTokenDecimals, - gasPrice, - dataFetcher - ); + const ethPrice = gasCoveragePercentage !== "0" + ? "0" + : await getEthPrice( + config, + bundledOrders[i].buyToken, + bundledOrders[i].buyTokenDecimals, + gasPrice, + dataFetcher + ); if (ethPrice === undefined) console.log("can not get ETH price, skipping...", "\n"); else { const rawtx = { @@ -391,31 +301,9 @@ const routerClear = async( }; console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); let gasLimit = await signer.estimateGas(rawtx); - gasLimit = gasLimit.mul("11").div("10"); + gasLimit = gasLimit.mul("112").div("100"); rawtx.gasLimit = gasLimit; const gasCost = gasLimit.mul(gasPrice); - // const maxEstimatedProfit = estimateProfit( - // ethers.utils.formatEther(bundledOrders[i].initPrice), - // ethPrice, - // bundledOrders[i], - // gasCost, - // gasCoveragePercentage - // ).div( - // "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - // ); - // console.log(`Max Estimated Profit: ${ - // ethers.utils.formatUnits( - // maxEstimatedProfit, - // bundledOrders[i].buyTokenDecimals - // ) - // } ${bundledOrders[i].buyTokenSymbol}`, "\n"); - - // if (maxEstimatedProfit.isNegative()) console.log( - // ">>> Skipping because estimated negative profit for this token pair", - // "\n" - // ); - // else { - console.log(">>> Trying to submit the transaction for this token pair...", "\n"); const gasCostInToken = ethers.utils.parseUnits( ethPrice ).mul( @@ -425,28 +313,48 @@ const routerClear = async( 36 - bundledOrders[i].buyTokenDecimals ) ); - rawtx.data = arb.interface.encodeFunctionData( - "arb", - arbType === "order-taker" - ? [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100) - ] - : [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100), - exchangeData - ] - ); - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - const tx = await signer.sendTransaction(rawtx); - console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); - console.log( - ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", - "\n" - ); + if (gasCoveragePercentage !== "0") { + const headroom = ( + Number(gasCoveragePercentage) * 1.15 + ).toFixed(); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100"), + exchangeData + ] + ); + await signer.estimateGas(rawtx); + } try { + console.log(">>> Trying to submit the transaction for this token pair...", "\n"); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100"), + exchangeData + ] + ); + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + const tx = await signer.sendTransaction(rawtx); + console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); + console.log( + ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", + "\n" + ); const receipt = await tx.wait(); const income = getIncome(signer, receipt); const clearActualPrice = getActualPrice( @@ -478,7 +386,7 @@ const routerClear = async( ); console.log( "\x1b[36m%s\x1b[0m", - `Clear Initial Price: ${ethers.utils.formatEther(bundledOrders[i].initPrice)}` + `Clear Initial Price: ${ethers.utils.formatEther(price)}` ); console.log("\x1b[36m%s\x1b[0m", `Clear Actual Price: ${clearActualPrice}`); console.log("\x1b[36m%s\x1b[0m", `Clear Amount: ${ @@ -515,14 +423,9 @@ const routerClear = async( sellTokenDecimals: bundledOrders[i].sellTokenDecimals, clearedAmount: bundledQuoteAmount.toString(), clearPrice: ethers.utils.formatEther( - bundledOrders[i].initPrice + price ), - // clearGuaranteedPrice: ethers.utils.formatUnits( - // guaranteedAmount, - // bundledOrders[i].buyTokenDecimals - // ), clearActualPrice, - // maxEstimatedProfit, gasUsed: receipt.gasUsed, gasCost: actualGasCost, income, @@ -534,13 +437,17 @@ const routerClear = async( console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); console.log(error, "\n"); } - // } } } catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); - console.log(error, "\n"); - // reason, code, method, transaction, error, stack, message + if (error === "dryrun" || error === "nomatch") { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction dry run failed, skipping..."); + } + else { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); + console.log(error, "\n"); + // reason, code, method, transaction, error, stack, message + } } } } diff --git a/src/srouter.js b/src/srouter.js index f046ae8d..6b45dbc8 100644 --- a/src/srouter.js +++ b/src/srouter.js @@ -15,80 +15,6 @@ const { } = require("./utils"); -/** - * Prepares the bundled orders by getting the best deals from Router and sorting the - * bundled orders based on the best deals - * - * @param {any[]} bundledOrders - The bundled orders array - * @param {any} dataFetcher - The DataFetcher instance - * @param {any} config - The network config data - * @param {ethers.BigNumber} gasPrice - The network gas price - * @param {boolean} sort - (optional) Sort based on best deals or not - */ -const prepare = async(bundledOrders, dataFetcher, config, gasPrice, sort = true) => { - for (let i = 0; i < bundledOrders.length; i++) { - const bOrder = bundledOrders[i]; - const pair = bOrder.buyTokenSymbol + "/" + bOrder.sellTokenSymbol; - try { - const fromToken = new Token({ - chainId: config.chainId, - decimals: bOrder.sellTokenDecimals, - address: bOrder.sellToken, - symbol: bOrder.sellTokenSymbol - }); - const toToken = new Token({ - chainId: config.chainId, - decimals: bOrder.buyTokenDecimals, - address: bOrder.buyToken, - symbol: bOrder.buyTokenSymbol - }); - await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); - const pcMap = dataFetcher.getCurrentPoolCodeMap(fromToken, toToken); - const route = Router.findBestRoute( - pcMap, - config.chainId, - fromToken, - // cumulativeAmount, - "1" + "0".repeat(bOrder.sellTokenDecimals), - toToken, - gasPrice.toNumber(), - // providers, - // poolFilter - ); - if (route.status == "NoWay") throw "could not find any route for this token pair"; - - const price = route.amountOutBN.mul("1" + "0".repeat(18 - bOrder.buyTokenDecimals)); - bOrder.initPrice = price; - - console.log(`Current market price for ${pair} for: ${ethers.utils.formatEther(price)}`); - console.log("Current ratio of the orders in this token pair:"); - bOrder.takeOrders.forEach(v => { - if (v.ratio) console.log(ethers.utils.formatEther(v.ratio)); - }); - bOrder.takeOrders = bOrder.takeOrders.filter( - v => v.ratio !== undefined ? price.gte(v.ratio) : true - ); - console.log("\n"); - } - catch(error) { - console.log(`>>> could not get price for this ${pair} due to:`); - console.log(error, "\n"); - } - } - console.log( - ">>> Filtering bundled orders with lower ratio than current market price...", - "\n" - ); - bundledOrders = bundledOrders.filter(v => v.initPrice && v.takeOrders.length > 0); - if (sort) { - console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); - bundledOrders.sort( - (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 - ); - } - return bundledOrders; -}; - /** * Main function that gets order details from subgraph, bundles the ones that have balance and tries clearing them with specialized router contract * @@ -96,22 +22,19 @@ const prepare = async(bundledOrders, dataFetcher, config, gasPrice, sort = true) * @param {any[]} ordersDetails - The order details queried from subgraph * @param {string} gasCoveragePercentage - (optional) The percentage of the gas cost to cover on each transaction * for it to be considered profitable and get submitted - * @param {boolean} prioritization - (optional) Prioritize better deals to get cleared first, default is true * @returns The report of details of cleared orders */ const srouterClear = async( config, ordersDetails, - gasCoveragePercentage = "100", - prioritization = true + gasCoveragePercentage = "100" ) => { if ( gasCoveragePercentage < 0 || !Number.isInteger(Number(gasCoveragePercentage)) ) throw "invalid gas coverage percentage, must be an integer greater than equal 0"; - if (typeof prioritization !== "boolean") throw "invalid value for 'prioritization'"; - const lps = processLps(config.lps); + const lps = processLps(config.lps, config.chainId); const dataFetcher = getDataFetcher(config, lps, !!config.usePublicRpc); const signer = config.signer; const arbAddress = config.arbAddress; @@ -125,10 +48,10 @@ const srouterClear = async( // instantiating orderbook contract const orderbook = new ethers.Contract(orderbookAddress, orderbookAbi, signer); - let gasPrice = await signer.provider.getGasPrice(); - console.log( - "------------------------- Starting Clearing Process -------------------------", + "------------------------- Starting The", + "\x1b[32mS-ROUTER\x1b[0m", + "Mode -------------------------", "\n" ); console.log("\x1b[33m%s\x1b[0m", Date()); @@ -141,11 +64,6 @@ const srouterClear = async( "------------------------- Bundling Orders -------------------------", "\n" ); bundledOrders = await bundleTakeOrders(ordersDetails, orderbook, arb, maxProfit); - console.log( - "------------------------- Getting Best Deals From RouteProcessor3 -------------------------", - "\n" - ); - bundledOrders = await prepare(bundledOrders, dataFetcher, config, gasPrice, prioritization); } else { console.log("No orders found, exiting...", "\n"); @@ -157,15 +75,9 @@ const srouterClear = async( return; } - console.log( - "------------------------- Trying To Clear Bundled Orders -------------------------", - "\n" - ); - const report = []; for (let i = 0; i < bundledOrders.length; i++) { try { - gasPrice = await signer.provider.getGasPrice(); console.log( `------------------------- Trying To Clear ${ bundledOrders[i].buyTokenSymbol @@ -177,305 +89,322 @@ const srouterClear = async( console.log(`Buy Token Address: ${bundledOrders[i].buyToken}`); console.log(`Sell Token Address: ${bundledOrders[i].sellToken}`, "\n"); - if (!bundledOrders[i].takeOrders.length) console.log( - "All orders of this token pair have empty vault balance, skipping...", - "\n" - ); - else { - const fromToken = new Token({ - chainId: config.chainId, - decimals: bundledOrders[i].sellTokenDecimals, - address: bundledOrders[i].sellToken, - symbol: bundledOrders[i].sellTokenSymbol - }); - const toToken = new Token({ - chainId: config.chainId, - decimals: bundledOrders[i].buyTokenDecimals, - address: bundledOrders[i].buyToken, - symbol: bundledOrders[i].buyTokenSymbol - }); + if (!bundledOrders[i].takeOrders.length) throw "All orders of this token pair have empty vault balance, skipping..."; - const obSellTokenBalance = ethers.BigNumber.from(await signer.call({ - data: "0x70a08231000000000000000000000000" + orderbookAddress.slice(2), - to: bundledOrders[i].sellToken - })); - const quoteChunks = obSellTokenBalance.div("5"); - let ethPrice; + const fromToken = new Token({ + chainId: config.chainId, + decimals: bundledOrders[i].sellTokenDecimals, + address: bundledOrders[i].sellToken, + symbol: bundledOrders[i].sellTokenSymbol + }); + const toToken = new Token({ + chainId: config.chainId, + decimals: bundledOrders[i].buyTokenDecimals, + address: bundledOrders[i].buyToken, + symbol: bundledOrders[i].buyTokenSymbol + }); - for (let j = 5; j > 0; j--) { - const maximumInput = j === 5 ? obSellTokenBalance : quoteChunks.mul(j); - const maximumInputFixed = maximumInput.mul( - "1" + "0".repeat(18 - bundledOrders[i].sellTokenDecimals) - ); + const obSellTokenBalance = ethers.BigNumber.from(await signer.call({ + data: "0x70a08231000000000000000000000000" + orderbookAddress.slice(2), + to: bundledOrders[i].sellToken + })); + const quoteChunks = obSellTokenBalance.div("5"); + + if (obSellTokenBalance.isZero()) throw `Orderbook has no ${ + bundledOrders[i].sellTokenSymbol + } balance, skipping...`; + + let ethPrice; + const gasPrice = await signer.provider.getGasPrice(); + try { + if (gasCoveragePercentage !== "0") ethPrice = await getEthPrice( + config, + bundledOrders[i].buyToken, + bundledOrders[i].buyTokenDecimals, + gasPrice, + dataFetcher + ); + else ethPrice = "0"; + if (ethPrice === undefined) throw "could not find a route for ETH price, skipping..."; + } + catch { + throw "could not get ETH price, skipping..."; + } + for (let j = 5; j > 0; j--) { + const maximumInput = j === 5 ? obSellTokenBalance : quoteChunks.mul(j); + const maximumInputFixed = maximumInput.mul( + "1" + "0".repeat(18 - bundledOrders[i].sellTokenDecimals) + ); - console.log(`>>> Trying to arb with ${ + console.log(`>>> Trying to arb with ${ + ethers.utils.formatEther(maximumInputFixed) + } ${ + bundledOrders[i].sellTokenSymbol + } as maximum input`); + console.log(">>> Getting best route", "\n"); + + await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); + const pcMap = dataFetcher.getCurrentPoolCodeMap( + fromToken, + toToken + ); + const route = Router.findBestRoute( + pcMap, + config.chainId, + fromToken, + maximumInput, + toToken, + gasPrice.toNumber(), + // 30e9, + // providers, + // poolFilter + ); + if (route.status == "NoWay") console.log( + "\x1b[31m%s\x1b[0m", + `could not find any route for this token pair for ${ ethers.utils.formatEther(maximumInputFixed) } ${ bundledOrders[i].sellTokenSymbol - } as maximum input`); - console.log(">>> Getting best route", "\n"); - await fetchPoolsForTokenWrapper(dataFetcher, fromToken, toToken); - const pcMap = dataFetcher.getCurrentPoolCodeMap( - fromToken, - toToken + }, trying with a lower amount...` + ); + else { + const rateFixed = route.amountOutBN.mul( + "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) + ); + const price = rateFixed.mul("1" + "0".repeat(18)).div(maximumInputFixed); + if (maxProfit) bundledOrders[i].takeOrders = bundledOrders[i].takeOrders.filter( + v => v.ratio !== undefined ? price.mul("102").div("100").gte(v.ratio) : true + ); + console.log( + "Current best route price for this token pair:", + `\x1b[33m${ethers.utils.formatEther(price)}\x1b[0m`, + "\n" + ); + console.log(">>> Route portions: ", "\n"); + visualizeRoute(fromToken, toToken, route.legs).forEach( + v => console.log("\x1b[36m%s\x1b[0m", v) ); - const route = Router.findBestRoute( + console.log(""); + + const rpParams = Router.routeProcessor2Params( pcMap, - config.chainId, + route, fromToken, - maximumInput, toToken, - gasPrice.toNumber(), - // 30e9, - // providers, - // poolFilter - ); - if (route.status == "NoWay") console.log( - "could not find any route for this token pair with this certain amount" + arb.address, + config.routeProcessor3Address, + // permits + // "0.005" ); - else { - const rateFixed = route.amountOutBN.mul( - "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - ); - const price = rateFixed.mul("1" + "0".repeat(18)).div(maximumInputFixed); - console.log(`Current best route price for this token pair: ${ethers.utils.formatEther(price)}`, "\n"); - console.log(">>> Route portions: ", "\n"); - visualizeRoute(fromToken, toToken, route.legs).forEach( - v => console.log("\x1b[36m%s\x1b[0m", v) - ); - console.log(""); + const takeOrdersConfigStruct = { + minimumInput: ethers.constants.One, + maximumInput, + maximumIORatio: maxRatio ? ethers.constants.MaxUint256 : price, + orders: bundledOrders[i].takeOrders.map(v => v.takeOrder), + data: ethers.utils.defaultAbiCoder.encode( + ["bytes"], + [rpParams.routeCode] + ) + }; - const rpParams = Router.routeProcessor2Params( - pcMap, - route, - fromToken, - toToken, - arb.address, - config.routeProcessor3Address, - // permits - // "0.005" - ); - const takeOrdersConfigStruct = { - minimumInput: ethers.constants.One, - maximumInput, - maximumIORatio: maxRatio ? ethers.constants.MaxUint256 : price, - orders: bundledOrders[i].takeOrders.map(v => v.takeOrder), - data: ethers.utils.defaultAbiCoder.encode( - ["bytes"], - [rpParams.routeCode] - ) + // building and submit the transaction + try { + const rawtx = { + data: arb.interface.encodeFunctionData("arb", [takeOrdersConfigStruct, "0"]), + to: arb.address, + gasPrice }; + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + let gasLimit; + try { + gasLimit = await signer.estimateGas(rawtx); + } + catch { + throw "nomatch"; + } + gasLimit = gasLimit.mul("112").div("100"); + rawtx.gasLimit = gasLimit; + const gasCost = gasLimit.mul(gasPrice); + const gasCostInToken = ethers.utils.parseUnits( + ethPrice + ).mul( + gasCost + ).div( + "1" + "0".repeat( + 36 - bundledOrders[i].buyTokenDecimals + ) + ); + if (gasCoveragePercentage !== "0") { + const headroom = ( + Number(gasCoveragePercentage) * 1.2 + ).toFixed(); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100") + ] + ); + try { + await signer.estimateGas(rawtx); + } + catch { + throw "dryrun"; + } + } - // building and submit the transaction + // submit the tx only if dry runs with headroom is passed try { - if (ethPrice === undefined) ethPrice = await getEthPrice( - config, - bundledOrders[i].buyToken, - bundledOrders[i].buyTokenDecimals, - gasPrice, - dataFetcher + console.log(">>> Trying to submit the transaction...", "\n"); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100") + ] ); - if (ethPrice === undefined) console.log("can not get ETH price, skipping...", "\n"); - else { - const rawtx = { - data: arb.interface.encodeFunctionData("arb", [takeOrdersConfigStruct, "0"]), - to: arb.address, - gasPrice - }; - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - let gasLimit; - try { - gasLimit = await signer.estimateGas(rawtx); - } - catch { - // console.log(err); - throw "nomatch"; - } - gasLimit = gasLimit.mul("11").div("10"); - rawtx.gasLimit = gasLimit; - const gasCost = gasLimit.mul(gasPrice); - // const maxEstimatedProfit = estimateProfit( - // ethers.utils.formatEther(bundledOrders[i].initPrice), - // ethPrice, - // bundledOrders[i], - // gasCost, - // gasCoveragePercentage - // ).div( - // "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - // ); - // console.log(`Max Estimated Profit: ${ - // ethers.utils.formatUnits( - // maxEstimatedProfit, - // bundledOrders[i].buyTokenDecimals - // ) - // } ${bundledOrders[i].buyTokenSymbol}`, "\n"); + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + const tx = await signer.sendTransaction(rawtx); - // if (maxEstimatedProfit.isNegative()) console.log( - // ">>> Skipping because estimated negative profit for this token pair", - // "\n" - // ); - // else { - console.log(">>> Trying to submit the transaction...", "\n"); - const gasCostInToken = ethers.utils.parseUnits( + console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); + console.log( + ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", + "\n" + ); + const receipt = await tx.wait(); + if (receipt.status === 1) { + const clearActualAmount = getActualClearAmount( + arbAddress, + orderbookAddress, + receipt + ); + const income = getIncome(signer, receipt); + const clearActualPrice = getActualPrice( + receipt, + orderbookAddress, + arbAddress, + clearActualAmount.mul("1" + "0".repeat( + 18 - bundledOrders[i].sellTokenDecimals + )), + bundledOrders[i].buyTokenDecimals + ); + const actualGasCost = ethers.BigNumber.from( + receipt.effectiveGasPrice + ).mul(receipt.gasUsed); + const actualGasCostInToken = ethers.utils.parseUnits( ethPrice ).mul( - gasCost + actualGasCost ).div( "1" + "0".repeat( 36 - bundledOrders[i].buyTokenDecimals ) ); - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - rawtx.data = arb.interface.encodeFunctionData( - "arb", - [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100) - ] - ); - const tx = await signer.sendTransaction(rawtx); + const netProfit = income + ? income.sub(actualGasCostInToken) + : undefined; - console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); console.log( - ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", - "\n" + "\x1b[36m%s\x1b[0m", + `Clear Initial Price: ${ethers.utils.formatEther(price)}` ); - - try { - const receipt = await tx.wait(); - // console.log(receipt); - if (receipt.status === 1) { - const clearActualAmount = getActualClearAmount( - arbAddress, - orderbookAddress, - receipt - ); - const income = getIncome(signer, receipt); - const clearActualPrice = getActualPrice( - receipt, - orderbookAddress, - arbAddress, - clearActualAmount.mul("1" + "0".repeat( - 18 - bundledOrders[i].sellTokenDecimals - )), - bundledOrders[i].buyTokenDecimals - ); - const actualGasCost = ethers.BigNumber.from( - receipt.effectiveGasPrice - ).mul(receipt.gasUsed); - const actualGasCostInToken = ethers.utils.parseUnits( - ethPrice - ).mul( - actualGasCost - ).div( - "1" + "0".repeat( - 36 - bundledOrders[i].buyTokenDecimals - ) - ); - const netProfit = income - ? income.sub(actualGasCostInToken) - : undefined; - - console.log( - "\x1b[36m%s\x1b[0m", - `Clear Initial Price: ${ethers.utils.formatEther(bundledOrders[i].initPrice)}` - ); - console.log("\x1b[36m%s\x1b[0m", `Clear Actual Price: ${clearActualPrice}`); - console.log("\x1b[36m%s\x1b[0m", `Clear Amount: ${ - ethers.utils.formatUnits( - clearActualAmount, - bundledOrders[i].sellTokenDecimals - ) - } ${bundledOrders[i].sellTokenSymbol}`); - console.log("\x1b[36m%s\x1b[0m", `Consumed Gas: ${ - ethers.utils.formatEther(actualGasCost) - } ${ - config.nativeToken.symbol - }`, "\n"); - if (income) { - console.log("\x1b[35m%s\x1b[0m", `Gross Income: ${ethers.utils.formatUnits( - income, - bundledOrders[i].buyTokenDecimals - )} ${bundledOrders[i].buyTokenSymbol}`); - console.log("\x1b[35m%s\x1b[0m", `Net Profit: ${ethers.utils.formatUnits( - netProfit, - bundledOrders[i].buyTokenDecimals - )} ${bundledOrders[i].buyTokenSymbol}`, "\n"); - } - - report.push({ - transactionHash: receipt.transactionHash, - tokenPair: - bundledOrders[i].buyTokenSymbol + - "/" + - bundledOrders[i].sellTokenSymbol, - buyToken: bundledOrders[i].buyToken, - buyTokenDecimals: bundledOrders[i].buyTokenDecimals, - sellToken: bundledOrders[i].sellToken, - sellTokenDecimals: bundledOrders[i].sellTokenDecimals, - clearedAmount: clearActualAmount.toString(), - clearPrice: ethers.utils.formatEther( - bundledOrders[i].initPrice - ), - clearActualPrice, - // maxEstimatedProfit, - gasUsed: receipt.gasUsed, - gasCost: actualGasCost, - income, - netProfit, - clearedOrders: bundledOrders[i].takeOrders.map( - v => v.id - ), - }); - j = 0; - } - else if (j > 1) console.log( - `could not clear with ${ethers.utils.formatEther( - maximumInputFixed - )} ${ - bundledOrders[i].sellTokenSymbol - } as max input, trying with lower amount...` - ); - else console.log("could not arb this pair"); - } - catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); - console.log(error, "\n"); - if (j > 1) console.log( - "\x1b[34m%s\x1b[0m", - `could not clear with ${ethers.utils.formatEther( - maximumInputFixed - )} ${ - bundledOrders[i].sellTokenSymbol - } as max input, trying with lower amount...`, "\n" - ); - else console.log("\x1b[34m%s\x1b[0m", "could not arb this pair", "\n"); + console.log("\x1b[36m%s\x1b[0m", `Clear Actual Price: ${clearActualPrice}`); + console.log("\x1b[36m%s\x1b[0m", `Clear Amount: ${ + ethers.utils.formatUnits( + clearActualAmount, + bundledOrders[i].sellTokenDecimals + ) + } ${bundledOrders[i].sellTokenSymbol}`); + console.log("\x1b[36m%s\x1b[0m", `Consumed Gas: ${ + ethers.utils.formatEther(actualGasCost) + } ${ + config.nativeToken.symbol + }`, "\n"); + if (income) { + console.log("\x1b[35m%s\x1b[0m", `Gross Income: ${ethers.utils.formatUnits( + income, + bundledOrders[i].buyTokenDecimals + )} ${bundledOrders[i].buyTokenSymbol}`); + console.log("\x1b[35m%s\x1b[0m", `Net Profit: ${ethers.utils.formatUnits( + netProfit, + bundledOrders[i].buyTokenDecimals + )} ${bundledOrders[i].buyTokenSymbol}`, "\n"); } + + report.push({ + transactionHash: receipt.transactionHash, + tokenPair: + bundledOrders[i].buyTokenSymbol + + "/" + + bundledOrders[i].sellTokenSymbol, + buyToken: bundledOrders[i].buyToken, + buyTokenDecimals: bundledOrders[i].buyTokenDecimals, + sellToken: bundledOrders[i].sellToken, + sellTokenDecimals: bundledOrders[i].sellTokenDecimals, + clearedAmount: clearActualAmount.toString(), + clearPrice: ethers.utils.formatEther(price), + clearActualPrice, + gasUsed: receipt.gasUsed, + gasCost: actualGasCost, + income, + netProfit, + clearedOrders: bundledOrders[i].takeOrders.map( + v => v.id + ), + }); + j = 0; } - } - catch (error) { - if (error !== "nomatch") { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); - console.log(error, "\n"); - // reason, code, method, transaction, error, stack, message - } - if (j > 1) console.log( - "\x1b[34m%s\x1b[0m", + else if (j > 1) console.log( `could not clear with ${ethers.utils.formatEther( maximumInputFixed )} ${ bundledOrders[i].sellTokenSymbol - } as max input, trying with lower amount...`, "\n" + } as max input, trying with lower amount...` ); - else console.log("\x1b[34m%s\x1b[0m", "could not arb this pair", "\n"); + else console.log("could not arb this pair"); } + catch (error) { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); + console.log(error, "\n"); + throw "failed-exec"; + // if (j > 1) console.log( + // "\x1b[34m%s\x1b[0m", + // `could not clear with ${ethers.utils.formatEther( + // maximumInputFixed + // )} ${ + // bundledOrders[i].sellTokenSymbol + // } as max input, trying with lower amount...`, "\n" + // ); + // else console.log("\x1b[34m%s\x1b[0m", "could not arb this pair", "\n"); + } + + } + catch (error) { + if (error !== "nomatch" && error !== "dryrun" && error !== "failed-exec") { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); + console.log(error, "\n"); + // reason, code, method, transaction, error, stack, message + } + if (error === "failed-exec") throw "Transaction execution failed, skipping this pair..."; + if (j > 1) console.log( + "\x1b[34m%s\x1b[0m", + `could not clear with ${ethers.utils.formatEther( + maximumInputFixed + )} ${ + bundledOrders[i].sellTokenSymbol + } as max input, trying with lower amount...`, "\n" + ); + else console.log("\x1b[34m%s\x1b[0m", "could not arb this pair", "\n"); } } } } catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Something went wrong, reason:", "\n"); - console.log(error); + if (typeof error === "string") console.log("\x1b[31m%s\x1b[0m", error, "\n"); + else { + console.log("\x1b[31m%s\x1b[0m", ">>> Something went wrong, reason:", "\n"); + console.log(error); + } } } return report; diff --git a/src/utils.js b/src/utils.js index 40375779..90dca772 100644 --- a/src/utils.js +++ b/src/utils.js @@ -8,11 +8,15 @@ const { DataFetcher, Router, LiquidityProviders } = require("@sushiswap/router") /** - * Fallback transports for viem client + * Chain specific fallback data */ -const fallbackTransports = { +const fallbacks = { [ChainId.ARBITRUM_NOVA]: { transport: http("https://nova.arbitrum.io/rpc"), + liquidityProviders: [ + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.ARBITRUM]: { transport: [ @@ -25,30 +29,53 @@ const fallbackTransports = { http("https://arbitrum.blockpi.network/v1/rpc/public"), http("https://arb-mainnet-public.unifra.io"), ], + liquidityProviders: [ + "dfyn", + "elk", + "sushiswapv3", + "uniswapv3", + "sushiswapv2" + ] }, [ChainId.AVALANCHE]: { transport: [ http("https://api.avax.network/ext/bc/C/rpc"), http("https://rpc.ankr.com/avalanche") ], + liquidityProviders: [ + "elk", + "traderjoe", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.BOBA]: { transport: [ http("https://mainnet.boba.network"), http("https://lightning-replica.boba.network") ], + liquidityProviders: [ + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.BOBA_AVAX]: { transport: [ http("https://avax.boba.network"), http("https://replica.avax.boba.network") ], + liquidityProviders: [ + "sushiswapv2" + ] }, [ChainId.BOBA_BNB]: { transport: [ http("https://bnb.boba.network"), http("https://replica.bnb.boba.network") ], + liquidityProviders: [ + "sushiswapv2" + ] }, [ChainId.BSC]: { transport: [ @@ -58,12 +85,26 @@ const fallbackTransports = { http("https://bsc-dataseed1.binance.org"), http("https://bsc-dataseed2.binance.org"), ], + liquidityProviders: [ + "apeswap", + "biswap", + "elk", + "jetswap", + "pancakeswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv3" + ] }, [ChainId.BTTC]: { transport: http("https://rpc.bittorrentchain.io"), }, [ChainId.CELO]: { - transport: http("https://forno.celo.org") + transport: http("https://forno.celo.org"), + liquidityProviders: [ + "ubeswap", + "sushiswapv2" + ] }, [ChainId.ETHEREUM]: { transport: [ @@ -76,6 +117,16 @@ const fallbackTransports = { http("https://1rpc.io/eth"), http("https://ethereum.publicnode.com"), http("https://cloudflare-eth.com"), + ], + liquidityProviders: [ + "apeswap", + "curveswap", + "elk", + "pancakeswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv2", + "uniswapv3" ] }, [ChainId.FANTOM]: { @@ -84,33 +135,66 @@ const fallbackTransports = { http("https://rpc.fantom.network"), http("https://rpc2.fantom.network"), ], + liquidityProviders: [ + "dfyn", + "elk", + "jetswap", + "spookyswap", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.FUSE]: { transport: http("https://rpc.fuse.io"), + liquidityProviders: [ + "elk", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.GNOSIS]: { transport: http("https://rpc.ankr.com/gnosis"), + liquidityProviders: [ + "elk", + "honeyswap", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.HARMONY]: { transport: [ http("https://api.harmony.one"), http("https://rpc.ankr.com/harmony") ], + liquidityProviders: [ + "sushiswapv2" + ] }, [ChainId.KAVA]: { transport: [ http("https://evm.kava.io"), - http("https://evm2.kava.io") + http("https://evm2.kava.io"), ], + liquidityProviders: [ + "elk" + ] }, [ChainId.MOONBEAM]: { transport: [ http("https://rpc.api.moonbeam.network"), http("https://rpc.ankr.com/moonbeam") ], + liquidityProviders: [ + "sushiswapv2" + ] }, [ChainId.MOONRIVER]: { transport: http("https://rpc.api.moonriver.moonbeam.network"), + liquidityProviders: [ + "elk", + "sushiswapv3", + "sushiswapv2" + ] }, [ChainId.OPTIMISM]: { transport: [ @@ -121,6 +205,11 @@ const fallbackTransports = { http("https://optimism.blockpi.network/v1/rpc/public"), http("https://mainnet.optimism.io"), ], + liquidityProviders: [ + "elk", + "sushiswapv3", + "uniswapv3" + ] }, [ChainId.POLYGON]: { transport: [ @@ -136,6 +225,16 @@ const fallbackTransports = { http("https://rpc-mainnet.maticvigil.com"), // ...polygon.rpcUrls.default.http.map((url) => http(url)), ], + liquidityProviders: [ + "apeswap", + "dfyn", + "elk", + "jetswap", + "quickswap", + "sushiswapv3", + "sushiswapv2", + "uniswapv3" + ] }, [ChainId.POLYGON_ZKEVM]: { transport: [ @@ -143,6 +242,10 @@ const fallbackTransports = { http("https://rpc.ankr.com/polygon_zkevm"), http("https://rpc.polygon-zkevm.gateway.fm"), ], + liquidityProviders: [ + "dovishv3", + "sushiswapv3" + ] }, [ChainId.THUNDERCORE]: { transport: [ @@ -150,6 +253,10 @@ const fallbackTransports = { http("https://mainnet-rpc.thundercore.io"), http("https://mainnet-rpc.thundertoken.net"), ], + liquidityProviders: [ + "laserswap", + "sushiswapv3" + ] }, }; @@ -681,11 +788,11 @@ const getDataFetcher = (config, liquidityProviders = [], useFallbacks = false) = transport: config.rpc && config.rpc !== "test" ? useFallbacks ? fallback( - [config.rpc, ...fallbackTransports[config.chainId].transport], + [config.rpc, ...fallbacks[config.chainId].transport], {rank: true} ) : http(config.rpc) - : fallback(fallbackTransports[config.chainId].transport, {rank: true}), + : fallback(fallbacks[config.chainId].transport, {rank: true}), // batch: { // multicall: { // batchSize: 512 @@ -804,8 +911,9 @@ const fetchPoolsForTokenWrapper = async(dataFetcher, fromToken, toToken, exclude * Resolves an array of case-insensitive names to LiquidityProviders, ignores the ones that are not valid * * @param {string[]} liquidityProviders - List of liquidity providers + * @param {number} chainId - The chain id */ -const processLps = (liquidityProviders) => { +const processLps = (liquidityProviders, chainId) => { if ( !liquidityProviders || !Array.isArray(liquidityProviders) || @@ -815,7 +923,12 @@ const processLps = (liquidityProviders) => { const _lps = []; const LP = Object.values(LiquidityProviders); for (let i = 0; i < liquidityProviders.length; i++) { - const index = LP.findIndex(v => v.toLowerCase() === liquidityProviders[i].toLowerCase()); + const index = LP.findIndex( + v => v.toLowerCase() === liquidityProviders[i].toLowerCase() + && !!fallbacks[chainId]?.liquidityProviders.includes( + liquidityProviders[i].toLowerCase() + ) + ); if (index > -1 && !_lps.includes(LP[index])) _lps.push(LP[index]); } return _lps.length ? _lps : undefined; @@ -1254,8 +1367,66 @@ const visualizeRoute = (fromToken, toToken, legs) => { ); }; +/** + * Builds initial 0x requests bodies from token addresses that is required + * for getting token prices with least amount of hits possible and that is + * to pair up tokens in a way that each show up only once in a request body + * so that the number of requests will be: "number-of-tokens / 2" at best or + * "(number-of-tokens / 2) + 1" at worst if the number of tokens is an odd digit. + * This way the responses will include the "rate" for sell/buy tokens to native + * network token which will be used to estimate the initial price of all possible + * token pair combinations. + * + * @param {string} api - The 0x API endpoint URL + * @param {any[]} queries - The array that keeps the 0x query text + * @param {string} tokenAddress - The token address + * @param {number} tokenDecimals - The token decimals + * @param {string} tokenSymbol - The token symbol + */ +const build0xQueries = (api, queries, tokenAddress, tokenDecimals, tokenSymbol) => { + tokenAddress = tokenAddress.toLowerCase(); + if (queries.length === 0) queries.push([ + tokenAddress, + tokenDecimals, + tokenSymbol + ]); + else if (!Array.isArray(queries[queries.length - 1])) { + if(!queries.find(v => v.quote.includes(tokenAddress))) queries.push([ + tokenAddress, + tokenDecimals, + tokenSymbol + ]); + } + else { + if( + queries[queries.length - 1][0] !== tokenAddress && + !queries.slice(0, -1).find(v => v.quote.includes(tokenAddress)) + ) { + queries[queries.length - 1] = { + quote: `${ + api + }swap/v1/price?buyToken=${ + queries[queries.length - 1][0] + }&sellToken=${ + tokenAddress + }&sellAmount=${ + "1" + "0".repeat(tokenDecimals) + }`, + tokens: [ + queries[queries.length - 1][2], + tokenSymbol, + queries[queries.length - 1][0], + tokenAddress, + queries[queries.length - 1][1], + tokenDecimals + ] + }; + } + } +}; + module.exports = { - fallbackTransports, + fallbacks, bnFromFloat, toFixed18, fromFixed18, @@ -1277,5 +1448,6 @@ module.exports = { promiseTimeout, getActualClearAmount, getRouteForTokens, - visualizeRoute + visualizeRoute, + build0xQueries }; diff --git a/src/zeroex.js b/src/zeroex.js index 215b0f04..6938043d 100644 --- a/src/zeroex.js +++ b/src/zeroex.js @@ -7,146 +7,23 @@ const { sleep, getIncome, getActualPrice } = require("./utils"); const HEADERS = { headers: { "accept-encoding": "null" } }; -/** - * Builds initial 0x requests bodies from token addresses that is required - * for getting token prices with least amount of hits possible and that is - * to pair up tokens in a way that each show up only once in a request body - * so that the number of requests will be: "number-of-tokens / 2" at best or - * "(number-of-tokens / 2) + 1" at worst if the number of tokens is an odd digit. - * This way the responses will include the "rate" for sell/buy tokens to native - * network token which will be used to estimate the initial price of all possible - * token pair combinations. - * - * @param {string} api - The 0x API endpoint URL - * @param {any[]} quotes - The array that keeps the quotes - * @param {string} tokenAddress - The token address - * @param {number} tokenDecimals - The token decimals - * @param {string} tokenSymbol - The token symbol - */ -const initRequests = (api, quotes, tokenAddress, tokenDecimals, tokenSymbol) => { - if (quotes.length === 0) quotes.push([ - tokenAddress, - tokenDecimals, - tokenSymbol - ]); - else if (!Array.isArray(quotes[quotes.length - 1])) { - if(!quotes.find(v => v.quote.includes(tokenAddress))) quotes.push([ - tokenAddress, - tokenDecimals, - tokenSymbol - ]); - } - else { - if( - quotes[quotes.length - 1][0] !== tokenAddress && - !quotes.slice(0, -1).find(v => v.quote.includes(tokenAddress)) - ) { - quotes[quotes.length - 1] = { - quote: `${ - api - }swap/v1/price?buyToken=${ - quotes[quotes.length - 1][0] - }&sellToken=${ - tokenAddress - }&sellAmount=${ - "1" + "0".repeat(tokenDecimals) - }`, - tokens: [quotes[quotes.length - 1][2], tokenSymbol] - }; - } - } -}; - -/** - * Prepares the bundled orders by getting the best deals from 0x and sorting the - * bundled orders based on the best deals - * - * @param {string[]} quotes - The 0x request quote bodies - * @param {any[]} bundledOrders - The bundled orders array - * @param {boolean} sort - (optional) Sort based on best deals or not - */ -const prepare = async(quotes, bundledOrders, sort = true) => { - try { - console.log(">>> Getting initial prices from 0x"); - const promises = []; - for (let i = 0; i < quotes.length; i++) { - promises.push(axios.get(quotes[i].quote, HEADERS)); - await sleep(1000); - } - const responses = await Promise.allSettled(promises); - - let prices = []; - responses.forEach((v, i) => { - if (v.status == "fulfilled") prices.push([ - { - token: v.value.data.buyTokenAddress, - rate: v.value.data.buyTokenToEthRate - }, - { - token: v.value.data.sellTokenAddress, - rate: v.value.data.sellTokenToEthRate - } - ]); - else { - console.log(`Could not get prices for ${quotes[i].tokens[0]} and ${quotes[i].tokens[1]}, reason:`); - console.log(v.reason.message); - } - }); - prices = prices.flat(); - - bundledOrders.forEach(v => { - console.log(`\nCalculating initial price for ${v.buyTokenSymbol}/${v.sellTokenSymbol} ...`); - const sellTokenPrice = prices.find( - e => e.token.toLowerCase() === v.sellToken.toLowerCase() - )?.rate; - const buyTokenPrice = prices.find( - e => e.token.toLowerCase() === v.buyToken.toLowerCase() - )?.rate; - if (sellTokenPrice && buyTokenPrice) { - v.initPrice = ethers.utils.parseUnits(buyTokenPrice) - .mul(ethers.utils.parseUnits("1")) - .div(ethers.utils.parseUnits(sellTokenPrice)); - console.log(`result: ${ethers.utils.formatEther(v.initPrice)}`); - } - else console.log("Could not calculate initial price for this token pair due to lack of required data!"); - }); - bundledOrders = bundledOrders.filter(v => v.initPrice !== undefined); - - if (sort) { - console.log("\n", ">>> Sorting the bundled orders based on initial prices..."); - bundledOrders.sort( - (a, b) => a.initPrice.gt(b.initPrice) ? -1 : a.initPrice.lt(b.initPrice) ? 1 : 0 - ); - } - return bundledOrders; - } - catch (error) { - console.log("something went wrong during the process of getting initial prices!"); - console.log(error); - return []; - } -}; - /** * Main function that gets order details from subgraph, bundles the ones that have balance and tries clearing them with 0x * * @param {object} config - The configuration object * @param {any[]} ordersDetails - The order details queried from subgraph * @param {string} gasCoveragePercentage - (optional) The percentage of the gas cost to cover on each transaction for it to be considered profitable and get submitted - * @param {boolean} prioritization - (optional) Prioritize better deals to get cleared first, default is true * @returns The report of details of cleared orders */ const zeroExClear = async( config, ordersDetails, - gasCoveragePercentage = "100", - prioritization = true + gasCoveragePercentage = "100" ) => { if ( gasCoveragePercentage < 0 || !Number.isInteger(Number(gasCoveragePercentage)) ) throw "invalid gas coverage percentage, must be an integer greater than equal 0"; - if (typeof prioritization !== "boolean") throw "invalid value for 'prioritization'"; let rateLimit; if (config.monthlyRatelimit !== undefined) { @@ -162,8 +39,8 @@ const zeroExClear = async( const proxyAddress = config.zeroEx.proxyAddress; const arbAddress = config.arbAddress; const orderbookAddress = config.orderbookAddress; - const nativeToken = config.nativeToken; const arbType = config.arbType; + // const nativeToken = config.nativeWrappedToken; // set the api key in headers if (config.apiKey) HEADERS.headers["0x-api-key"] = config.apiKey; @@ -176,14 +53,16 @@ const zeroExClear = async( const orderbook = new ethers.Contract(orderbookAddress, orderbookAbi, signer); console.log( - "------------------------- Starting Clearing Process -------------------------", + "------------------------- Starting The", + "\x1b[32m0x\x1b[0m", + "Mode -------------------------", "\n" ); console.log("\x1b[33m%s\x1b[0m", Date()); console.log("Arb Contract Address: " , arbAddress); console.log("OrderBook Contract Address: " , orderbookAddress, "\n"); - const initQuotes = []; + // const initPriceQueries = []; let bundledOrders = []; if (ordersDetails.length) { @@ -191,22 +70,22 @@ const zeroExClear = async( "------------------------- Bundling Orders -------------------------", "\n" ); bundledOrders = await bundleTakeOrders(ordersDetails, orderbook, arb); - for (let i = 0; i < bundledOrders.length; i++) { - initRequests( - api, - initQuotes, - bundledOrders[i].sellToken, - bundledOrders[i].sellTokenDecimals, - bundledOrders[i].sellTokenSymbol - ); - initRequests( - api, - initQuotes, - bundledOrders[i].buyToken, - bundledOrders[i].buyTokenDecimals, - bundledOrders[i].buyTokenSymbol - ); - } + // for (let i = 0; i < bundledOrders.length; i++) { + // build0xQueries( + // api, + // initPriceQueries, + // bundledOrders[i].sellToken, + // bundledOrders[i].sellTokenDecimals, + // bundledOrders[i].sellTokenSymbol + // ); + // build0xQueries( + // api, + // initPriceQueries, + // bundledOrders[i].buyToken, + // bundledOrders[i].buyTokenDecimals, + // bundledOrders[i].buyTokenSymbol + // ); + // } } else { console.log("No orders found, exiting...", "\n"); @@ -218,36 +97,38 @@ const zeroExClear = async( return; } - console.log( - "------------------------- Getting Best Deals From 0x -------------------------", - "\n" - ); - if (Array.isArray(initQuotes[initQuotes.length - 1])) { - initQuotes[initQuotes.length - 1] = { - quote: `${ - api - }swap/v1/price?buyToken=${ - nativeToken.address.toLowerCase() - }&sellToken=${ - initQuotes[initQuotes.length - 1][0] - }&sellAmount=${ - "1" + "0".repeat(initQuotes[initQuotes.length - 1][1]) - }`, - tokens: ["ETH", initQuotes[initQuotes.length - 1][2]] - }; - } - hits += initQuotes.length; - bundledOrders = await prepare( - initQuotes, - bundledOrders, - prioritization - ); - - if (bundledOrders.length) console.log( - "------------------------- Trying To Clear Bundled Orders -------------------------", - "\n" - ); - else { + // console.log( + // "------------------------- Getting Best Deals From 0x -------------------------", + // "\n" + // ); + // if (Array.isArray(initPriceQueries[initPriceQueries.length - 1])) { + // initPriceQueries[initPriceQueries.length - 1] = { + // quote: `${ + // api + // }swap/v1/price?buyToken=${ + // nativeToken.address.toLowerCase() + // }&sellToken=${ + // initPriceQueries[initPriceQueries.length - 1][0] + // }&sellAmount=${ + // "1" + "0".repeat(initPriceQueries[initPriceQueries.length - 1][1]) + // }`, + // tokens: [ + // nativeToken.symbol, + // initPriceQueries[initPriceQueries.length - 1][2], + // nativeToken.address.toLowerCase(), + // initPriceQueries[initPriceQueries.length - 1][0], + // nativeToken.decimals, + // initPriceQueries[initPriceQueries.length - 1][1], + // ] + // }; + // } + // hits += initPriceQueries.length; + // bundledOrders = await prepare( + // initPriceQueries, + // bundledOrders + // ); + + if (bundledOrders.length === 0) { console.log("Could not find any order to clear for current market price, exiting...", "\n"); return; } @@ -355,7 +236,6 @@ const zeroExClear = async( ); if (bundledOrders[i].takeOrders.length) { - cumulativeAmount = ethers.constants.Zero; bundledOrders[i].takeOrders.forEach(v => { cumulativeAmount = cumulativeAmount.add(v.quoteAmount); @@ -394,10 +274,9 @@ const zeroExClear = async( orders: bundledOrders[i].takeOrders.map(v => v.takeOrder), }; if (/^flash-loan-v3$|^order-taker$/.test(arbType)) { - takeOrdersConfigStruct.data = "0x00"; + takeOrdersConfigStruct.data = "0x"; delete takeOrdersConfigStruct.output; delete takeOrdersConfigStruct.input; - if (arbType === "flash-loan-v3") takeOrdersConfigStruct.data = "0x"; } // submit the transaction @@ -407,8 +286,6 @@ const zeroExClear = async( [txQuote.allowanceTarget, proxyAddress, txQuote.data] ); if (arbType === "order-taker") takeOrdersConfigStruct.data = exchangeData; - - // console.log(">>> Estimating the profit for this token pair...", "\n"); const rawtx = { data: arb.interface.encodeFunctionData( "arb", @@ -428,43 +305,9 @@ const zeroExClear = async( }; console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); let gasLimit = await signer.estimateGas(rawtx); - gasLimit = gasLimit.mul("11").div("10"); + gasLimit = gasLimit.mul("112").div("100"); rawtx.gasLimit = gasLimit; - const gasCost = gasLimit.mul(gasPrice); - - // let gasLimit; - // console.log("Block Number: " + await signer.provider.getBlockNumber()); - // if (arbType === "order-taker") gasLimit = await arb.estimateGas.arb( - // takeOrdersConfigStruct, - // ethers.constants.Zero, - // { gasPrice: txQuote.gasPrice } - // ); - // else gasLimit = await arb.estimateGas.arb( - // takeOrdersConfigStruct, - // ethers.constants.Zero, - // exchangeData, - // { gasPrice: txQuote.gasPrice } - // ); - - // gasLimit = gasLimit.mul("11").div("10"); - // const gasCost = gasLimit.mul(txQuote.gasPrice); - // const maxEstimatedProfit = estimateProfit( - // txQuote.price, - // txQuote.buyTokenToEthRate, - // bundledOrders[i], - // gasCost, - // gasCoveragePercentage - // ).div( - // "1" + "0".repeat(18 - bundledOrders[i].buyTokenDecimals) - // ); - // console.log(`Max Estimated Profit: ${ - // ethers.utils.formatUnits( - // maxEstimatedProfit, - // bundledOrders[i].buyTokenDecimals - // ) - // } ${bundledOrders[i].buyTokenSymbol}`, "\n"); - // if (!maxEstimatedProfit.isNegative()) { - console.log(">>> Trying to submit the transaction for this token pair...", "\n"); + const gasCost = gasLimit.mul(txQuote.gasPrice); const gasCostInToken = ethers.utils.parseUnits( txQuote.buyTokenToEthRate ).mul( @@ -474,31 +317,50 @@ const zeroExClear = async( 36 - bundledOrders[i].buyTokenDecimals ) ); - rawtx.data = arb.interface.encodeFunctionData( - "arb", - arbType === "order-taker" - ? [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100) - ] - : [ - takeOrdersConfigStruct, - gasCostInToken.mul(gasCoveragePercentage).div(100), - exchangeData - ] - ); - console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); - const tx = await signer.sendTransaction(rawtx); - - console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); - console.log( - ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", - "\n" - ); + if (gasCoveragePercentage !== "0") { + const headroom = ( + Number(gasCoveragePercentage) * 1.2 + ).toFixed(); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(headroom).div("100"), + exchangeData + ] + ); + await signer.estimateGas(rawtx); + } try { + console.log(">>> Trying to submit the transaction for this token pair...", "\n"); + rawtx.data = arb.interface.encodeFunctionData( + "arb", + arbType === "order-taker" + ? [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100") + ] + : [ + takeOrdersConfigStruct, + gasCostInToken.mul(gasCoveragePercentage).div("100"), + exchangeData + ] + ); + console.log("Block Number: " + await signer.provider.getBlockNumber(), "\n"); + const tx = await signer.sendTransaction(rawtx); + + console.log("\x1b[33m%s\x1b[0m", config.explorer + "tx/" + tx.hash, "\n"); + console.log( + ">>> Transaction submitted successfully to the network, waiting for transaction to mine...", + "\n" + ); const receipt = await tx.wait(); - // console.log(receipt); const income = getIncome(signer, receipt); const clearActualPrice = getActualPrice( receipt, @@ -565,7 +427,6 @@ const zeroExClear = async( clearPrice: txQuote.price, clearGuaranteedPrice: txQuote.guaranteedPrice, clearActualPrice, - // maxEstimatedProfit, gasUsed: receipt.gasUsed, gasCost: actualGasCost, income, @@ -579,12 +440,15 @@ const zeroExClear = async( console.log("\x1b[31m%s\x1b[0m", ">>> Transaction execution failed due to:"); console.log(error, "\n"); } - // } - // else console.log(">>> Skipping because estimated negative profit for this token pair", "\n"); } catch (error) { - console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); - console.log(error, "\n"); + if (error === "dryrun" || error === "nomatch") { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction dry run failed, skipping..."); + } + else { + console.log("\x1b[31m%s\x1b[0m", ">>> Transaction failed due to:"); + console.log(error, "\n"); + } } } else console.log("\x1b[31m%s\x1b[0m", "Failed to get quote from 0x", "\n"); diff --git a/test/0x.test.js b/test/0x.test.js index da4b022e..32711866 100644 --- a/test/0x.test.js +++ b/test/0x.test.js @@ -178,7 +178,7 @@ // config.apiKey = process?.env?.API_KEY; // config.signer = bot; // config.arbType = "flash-loan-v2"; -// const reports = await clear("0x", config, sgOrders, {prioritization: false}); +// const reports = await clear("0x", config, sgOrders); // // should have cleared 2 toke pairs bundled orders // assert.ok(reports.length == 2); @@ -298,7 +298,7 @@ // config.apiKey = process?.env?.API_KEY; // config.signer = bot; // config.arbType = "flash-loan-v3"; -// const reports = await clear("0x", config, sgOrders, {prioritization: false}); +// const reports = await clear("0x", config, sgOrders); // // should have cleared 2 toke pairs bundled orders // assert.ok(reports.length == 2); @@ -418,7 +418,7 @@ // config.apiKey = process?.env?.API_KEY; // config.signer = bot; // config.arbType = "order-taker"; -// const reports = await clear("0x", config, sgOrders, {prioritization: false}); +// const reports = await clear("0x", config, sgOrders); // // should have cleared 2 toke pairs bundled orders // assert.ok(reports.length == 2); diff --git a/test/curve.test.js b/test/curve.test.js index 73b29e5e..98e1fc48 100644 --- a/test/curve.test.js +++ b/test/curve.test.js @@ -175,7 +175,7 @@ describe("Rain Arb Bot 'curve' Mode Tests", async function () { config.signer = bot; config.lps = ["SushiSwapV2"]; config.arbType = "flash-loan-v2"; - const reports = await clear("curve", config, sgOrders, {prioritization: false}); + const reports = await clear("curve", config, sgOrders); // should have cleared 2 toke pairs bundled orders assert.ok(reports.length == 2); @@ -296,7 +296,7 @@ describe("Rain Arb Bot 'curve' Mode Tests", async function () { config.signer = bot; config.lps = ["SushiSwapV2"]; config.arbType = "flash-loan-v3"; - const reports = await clear("curve", config, sgOrders, {prioritization: false}); + const reports = await clear("curve", config, sgOrders); // should have cleared 2 toke pairs bundled orders assert.ok(reports.length == 2); @@ -416,7 +416,7 @@ describe("Rain Arb Bot 'curve' Mode Tests", async function () { config.signer = bot; config.lps = ["SushiSwapV2"]; config.arbType = "order-taker"; - const reports = await clear("curve", config, sgOrders, {prioritization: false}); + const reports = await clear("curve", config, sgOrders); // should have cleared 2 toke pairs bundled orders assert.ok(reports.length == 2); diff --git a/test/router.test.js b/test/router.test.js index a216debc..c16502ca 100644 --- a/test/router.test.js +++ b/test/router.test.js @@ -175,7 +175,8 @@ describe("Rain Arb Bot 'router' Mode Tests", async function () { config.signer = bot; config.lps = ["SushiSwapV2"]; config.arbType = "flash-loan-v2"; - const reports = await clear("router", config, sgOrders, {prioritization: false}); + config.apiKey = process?.env?.API_KEY; + const reports = await clear("router", config, sgOrders); // should have cleared 2 toke pairs bundled orders assert.ok(reports.length == 2); @@ -296,7 +297,8 @@ describe("Rain Arb Bot 'router' Mode Tests", async function () { config.signer = bot; config.lps = ["SushiSwapV2"]; config.arbType = "flash-loan-v3"; - const reports = await clear("router", config, sgOrders, {prioritization: false}); + config.apiKey = process?.env?.API_KEY; + const reports = await clear("router", config, sgOrders); // should have cleared 2 toke pairs bundled orders assert.ok(reports.length == 2); @@ -416,7 +418,8 @@ describe("Rain Arb Bot 'router' Mode Tests", async function () { config.signer = bot; config.lps = ["SushiSwapV2"]; config.arbType = "order-taker"; - const reports = await clear("router", config, sgOrders, {prioritization: false}); + config.apiKey = process?.env?.API_KEY; + const reports = await clear("router", config, sgOrders); // should have cleared 2 toke pairs bundled orders assert.ok(reports.length == 2); diff --git a/test/srouter.test.js b/test/srouter.test.js index 688eb371..85f88f6b 100644 --- a/test/srouter.test.js +++ b/test/srouter.test.js @@ -167,7 +167,7 @@ describe("Rain Arb Bot 'srouter' Mode Tests", async function () { config.rpc = "test"; config.signer = bot; config.lps = ["SushiSwapV2"]; - const reports = await clear("srouter", config, sgOrders, {prioritization: false}); + const reports = await clear("srouter", config, sgOrders); // should have cleared 2 toke pairs bundled orders assert.ok(reports.length == 2);