diff --git a/src/chains/defaults/index.ts b/src/chains/defaults/index.ts index bf776f0..59b0444 100644 --- a/src/chains/defaults/index.ts +++ b/src/chains/defaults/index.ts @@ -1,3 +1,3 @@ -export { getFlashArbMessages } from "./messages/getFlashArbMessages"; +export { messageFactory } from "./messages/messageFactory"; export { getPoolStates } from "./queries/getPoolState"; export { initPools } from "./queries/getPoolState"; diff --git a/src/chains/defaults/messages/getSwapMessage.ts b/src/chains/defaults/messages/getSwapMessage.ts new file mode 100644 index 0000000..8807ced --- /dev/null +++ b/src/chains/defaults/messages/getSwapMessage.ts @@ -0,0 +1,42 @@ +import { toUtf8 } from "@cosmjs/encoding"; +import { EncodeObject } from "@cosmjs/proto-signing"; +import { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx"; + +import { Asset, isNativeAsset } from "../../../core/types/base/asset"; +import { Pool } from "../../../core/types/base/pool"; + +/** + * + */ +export function getSwapMessage( + pool: Pool, + offerAsset: Asset, + walletAddress: string, + beliefPrice: string, + maxSpread = 0.005, +) { + const msg = { + swap: { + max_spread: String(maxSpread), + offer_asset: offerAsset, + // belief_price: beliefPrice, + }, + }; + const encodedMsgObject: EncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromJSON({ + funds: [ + { + denom: isNativeAsset(offerAsset.info) + ? offerAsset.info.native_token.denom + : offerAsset.info.token.contract_addr, + amount: offerAsset.amount, + }, + ], + sender: walletAddress, + contract: pool.address, + msg: toUtf8(JSON.stringify(msg)), + }), + }; + return encodedMsgObject; +} diff --git a/src/chains/defaults/messages/messageFactory.ts b/src/chains/defaults/messages/messageFactory.ts new file mode 100644 index 0000000..6e1b35a --- /dev/null +++ b/src/chains/defaults/messages/messageFactory.ts @@ -0,0 +1,22 @@ +import { EncodeObject } from "@cosmjs/proto-signing"; + +import { OptimalTrade } from "../../../core/arbitrage/arbitrage"; +import { OptimalOrderbookTrade } from "../../../core/arbitrage/optimizers/orderbookOptimizer"; +import { getOrderbookArbMessages } from "../../inj/messages/getOrderbookArbMessage"; +import { getFlashArbMessages } from "./getFlashArbMessages"; +/** + * + */ +export function messageFactory( + arbTrade: OptimalTrade | OptimalOrderbookTrade, + publicAddress: string, + flashloancontract?: string, +): [Array, number] | undefined { + if (arbTrade.path["orderbook" as keyof typeof arbTrade.path] !== undefined) { + return getOrderbookArbMessages(arbTrade, publicAddress); + } else if (flashloancontract !== undefined) { + return getFlashArbMessages(arbTrade, publicAddress, flashloancontract); + } else { + return undefined; + } +} diff --git a/src/chains/index.ts b/src/chains/index.ts index 70bf972..10ee0b3 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -1,4 +1,5 @@ //export all known setups for chains export * as defaults from "./defaults"; +export * as injective from "./inj"; export * as juno from "./juno"; export * as terra from "./terra"; diff --git a/src/chains/inj/index.ts b/src/chains/inj/index.ts index c2adcc0..72801f2 100644 --- a/src/chains/inj/index.ts +++ b/src/chains/inj/index.ts @@ -1 +1,3 @@ export * from "../defaults"; +export { getOrderbookState } from "./queries/getOrderbookState"; +export { initOrderbooks } from "./queries/initOrderbook"; diff --git a/src/chains/inj/messages/getOrderbookArbMessage.ts b/src/chains/inj/messages/getOrderbookArbMessage.ts new file mode 100644 index 0000000..449f50e --- /dev/null +++ b/src/chains/inj/messages/getOrderbookArbMessage.ts @@ -0,0 +1,61 @@ +import { EncodeObject } from "@cosmjs/proto-signing"; + +import { OptimalOrderbookTrade } from "../../../core/arbitrage/optimizers/orderbookOptimizer"; +import { toChainAsset, toChainPrice } from "../../../core/types/base/asset"; +import { OrderSequence } from "../../../core/types/base/path"; +import { outGivenIn } from "../../../core/types/base/pool"; +import { getSwapMessage } from "../../defaults/messages/getSwapMessage"; +import { getMarketSpotOrderMessage } from "./getSpotOrderMessage"; + +/** + * + */ +export function getOrderbookArbMessages( + arbTrade: OptimalOrderbookTrade, + publicAddress: string, +): [Array, number] { + if (arbTrade.path.orderSequence === OrderSequence.AmmFirst) { + //buy on the amm, transfer to trading account, sell the inj there, withdraw the usdt to injective account + const [outGivenIn0, outInfo0] = outGivenIn(arbTrade.path.pool, arbTrade.offerAsset); + + const price = toChainPrice(arbTrade.offerAsset, { amount: String(outGivenIn0), info: outInfo0 }); + const offerAsset = toChainAsset(arbTrade.offerAsset); + const msg0 = getSwapMessage(arbTrade.path.pool, offerAsset, publicAddress, price); + + const offerAsset1 = { + amount: String(outGivenIn0), + info: outInfo0, + }; + + const msg1 = getMarketSpotOrderMessage(arbTrade, publicAddress, offerAsset1, 2); + + return [[msg0, msg1], 2]; + } else { + const offerAsset1 = { + amount: String(arbTrade.outGivenIn), + info: arbTrade.path.orderbook.baseAssetInfo, + }; + const msg0 = getMarketSpotOrderMessage(arbTrade, publicAddress, offerAsset1, 1); + + const [outGivenIn1, outInfo1] = outGivenIn(arbTrade.path.pool, { + amount: String(arbTrade.outGivenIn), + info: offerAsset1.info, + }); + + const belief_price = toChainPrice( + { + amount: String(arbTrade.outGivenIn), + info: offerAsset1.info, + }, + { amount: String(outGivenIn1), info: outInfo1 }, + ); + const offerAsset = toChainAsset({ + amount: String(arbTrade.outGivenIn), + info: offerAsset1.info, + }); + + const msg1 = getSwapMessage(arbTrade.path.pool, offerAsset, publicAddress, belief_price); + + return [[msg0, msg1], 2]; + } +} diff --git a/src/chains/inj/messages/getSpotOrderMessage.ts b/src/chains/inj/messages/getSpotOrderMessage.ts new file mode 100644 index 0000000..c499008 --- /dev/null +++ b/src/chains/inj/messages/getSpotOrderMessage.ts @@ -0,0 +1,48 @@ +import { MsgCreateSpotMarketOrder, spotPriceToChainPriceToFixed } from "@injectivelabs/sdk-ts"; +import { BigNumberInBase } from "@injectivelabs/utils/dist/cjs/classes"; + +import { OptimalOrderbookTrade } from "../../../core/arbitrage/optimizers/orderbookOptimizer"; +import { Asset, isMatchingAssetInfos } from "../../../core/types/base/asset"; +import { SpotMarketOrderMessage } from "../../../core/types/messages/spotorders"; + +/** + *'/injective.exchange.v1beta1.MsgCreateSpotMarketOrder'. + */ +export function getMarketSpotOrderMessage( + arbTrade: OptimalOrderbookTrade, + injectiveAddress: string, + offerAsset: Asset, + orderType: 1 | 2, +) { + let decimals = 6; + if (isMatchingAssetInfos(offerAsset.info, arbTrade.path.orderbook.baseAssetInfo)) { + decimals = arbTrade.path.orderbook.baseAssetDecimals - arbTrade.path.orderbook.quoteAssetDecimals; + } else { + decimals = arbTrade.path.orderbook.quoteAssetDecimals - arbTrade.path.orderbook.quoteAssetDecimals; + } + + const beliefPriceOrderbook = spotPriceToChainPriceToFixed({ + value: Math.round(arbTrade.worstPrice * 1000) / 1000, + baseDecimals: arbTrade.path.orderbook.baseAssetDecimals, + quoteDecimals: arbTrade.path.orderbook.quoteAssetDecimals, + }); + + let orderSize = +new BigNumberInBase(offerAsset.amount).toWei(decimals).toFixed(); + orderSize = + Math.floor(+orderSize / arbTrade.path.orderbook.minQuantityIncrement) * + arbTrade.path.orderbook.minQuantityIncrement; + + const marketSpotOrderMsg: SpotMarketOrderMessage = { + marketId: arbTrade.path.orderbook.marketId, + subaccountId: "", + injectiveAddress: injectiveAddress, + orderType: orderType, + feeRecipient: injectiveAddress, + price: beliefPriceOrderbook, + quantity: String(orderSize), + }; + return { + typeUrl: "/injective.exchange.v1beta1.MsgCreateSpotMarketOrder", + value: MsgCreateSpotMarketOrder.fromJSON(marketSpotOrderMsg), + }; +} diff --git a/src/chains/inj/queries/getOrderbookState.ts b/src/chains/inj/queries/getOrderbookState.ts new file mode 100644 index 0000000..bc2c5c2 --- /dev/null +++ b/src/chains/inj/queries/getOrderbookState.ts @@ -0,0 +1,51 @@ +import { BigNumberInBase, BigNumberInWei } from "@injectivelabs/utils"; + +import { ChainOperator } from "../../../core/chainOperator/chainoperator"; +import { isNativeAsset } from "../../../core/types/base/asset"; +import { Order, Orderbook } from "../../../core/types/base/orderbook"; + +/** + * + */ +export async function getOrderbookState(chainOperator: ChainOperator, orderbooks: Array) { + await Promise.all( + orderbooks.map(async (orderbook) => { + const ob = await chainOperator.queryOrderbook(orderbook.marketId); + if (!ob) { + console.log("cannot fetch orderbook: ", orderbook.marketId); + return; + } + orderbook.sells = []; + orderbook.buys = []; + let quantityDecimals: number; + let priceDecimals: number; + if (isNativeAsset(orderbook.baseAssetInfo) && orderbook.baseAssetInfo.native_token.denom === "inj") { + quantityDecimals = 12; + priceDecimals = 12; + } else { + quantityDecimals = 0; + priceDecimals = 0; + } + ob.buys.map((buy) => { + const quantity = new BigNumberInWei(buy.quantity).toBase(quantityDecimals); + const price = new BigNumberInBase(buy.price).toWei(priceDecimals); + const buyOrder: Order = { + quantity: +quantity.toFixed(), + price: +price.toFixed(), + type: "buy", + }; + orderbook.buys.push(buyOrder); + ob.sells.map((sell) => { + const quantity = new BigNumberInWei(sell.quantity).toBase(quantityDecimals); + const price = new BigNumberInBase(sell.price).toWei(priceDecimals); + const sellOrder: Order = { + quantity: +quantity.toFixed(), + price: +price.toFixed(), + type: "sell", + }; + orderbook.sells.push(sellOrder); + }); + }); + }), + ); +} diff --git a/src/chains/inj/queries/initOrderbook.ts b/src/chains/inj/queries/initOrderbook.ts new file mode 100644 index 0000000..79b9158 --- /dev/null +++ b/src/chains/inj/queries/initOrderbook.ts @@ -0,0 +1,37 @@ +import { ChainOperator } from "../../../core/chainOperator/chainoperator"; +import { AssetInfo } from "../../../core/types/base/asset"; +import { DexConfig } from "../../../core/types/base/configs"; +import { Orderbook } from "../../../core/types/base/orderbook"; +import { identity } from "../../../core/types/identity"; +import { getOrderbookState } from "./getOrderbookState"; +/** + * + */ +export async function initOrderbooks( + chainoperator: ChainOperator, + botConfig: DexConfig, +): Promise | undefined> { + const orderbooks: Array = []; + for (const orderbookAddress of botConfig.orderbooks) { + const marketInfo = await chainoperator.queryMarket(orderbookAddress); + if (!marketInfo) { + console.log("cannot fetch market: ", orderbookAddress); + return; + } + const baseAssetInfo: AssetInfo = { native_token: { denom: marketInfo.baseDenom } }; + const quoteAssetInfo: AssetInfo = { native_token: { denom: marketInfo.quoteDenom } }; + const ob = identity({ + baseAssetInfo: baseAssetInfo, + quoteAssetInfo: quoteAssetInfo, + baseAssetDecimals: marketInfo.baseToken?.decimals ?? 6, + quoteAssetDecimals: marketInfo.quoteToken?.decimals ?? 6, + minQuantityIncrement: marketInfo.minQuantityTickSize ?? 10e3, + buys: [], + sells: [], + marketId: orderbookAddress, + }); + orderbooks.push(ob); + } + await getOrderbookState(chainoperator, orderbooks); + return orderbooks; +} diff --git a/src/core/arbitrage/arbitrage.ts b/src/core/arbitrage/arbitrage.ts index 67b9bcb..b781ba0 100644 --- a/src/core/arbitrage/arbitrage.ts +++ b/src/core/arbitrage/arbitrage.ts @@ -1,8 +1,9 @@ import { Asset } from "../types/base/asset"; import { DexConfig, LiquidationConfig } from "../types/base/configs"; import { AnchorOverseer } from "../types/base/overseer"; -import { Path } from "../types/base/path"; -import { getOptimalTrade } from "./optimizers/analyticalOptimizer"; +import { isOrderbookPath, OrderbookPath, Path } from "../types/base/path"; +import { getOptimalTrade as getOptimalAmmTrade } from "./optimizers/analyticalOptimizer"; +import { getOptimalTrade as getOptimalOrderbookTrade, OptimalOrderbookTrade } from "./optimizers/orderbookOptimizer"; export interface OptimalTrade { offerAsset: Asset; @@ -12,8 +13,25 @@ export interface OptimalTrade { /** * */ -export function trySomeArb(paths: Array, botConfig: DexConfig): OptimalTrade | undefined { - const optimalTrade: OptimalTrade | undefined = getOptimalTrade(paths, botConfig.offerAssetInfo); +export function tryAmmArb(paths: Array, botConfig: DexConfig): OptimalTrade | undefined { + const optimalTrade: OptimalTrade | undefined = getOptimalAmmTrade(paths, botConfig.offerAssetInfo); + + if (!optimalTrade) { + return undefined; + } else { + if (!isAboveThreshold(botConfig, optimalTrade)) { + return undefined; + } else { + return optimalTrade; + } + } +} + +/** + * + */ +export function tryOrderbookArb(paths: Array, botConfig: DexConfig): OptimalOrderbookTrade | undefined { + const optimalTrade: OptimalOrderbookTrade | undefined = getOptimalOrderbookTrade(paths, botConfig.offerAssetInfo); if (!optimalTrade) { return undefined; @@ -45,17 +63,24 @@ export function tryLiquidationArb( /** * */ -function isAboveThreshold(botConfig: DexConfig, optimalTrade: OptimalTrade): boolean { - // We dont know the number of message required to execute the trade, so the profit threshold will be set to the most conservative value: nr_of_pools*2-1 - const profitThreshold = - botConfig.profitThresholds.get((optimalTrade.path.pools.length - 1) * 2 + 1) ?? - Array.from(botConfig.profitThresholds.values())[botConfig.profitThresholds.size - 1]; - if (botConfig.skipConfig) { - const skipBidRate = botConfig.skipConfig.skipBidRate; - return ( - (1 - skipBidRate) * optimalTrade.profit - (botConfig.flashloanFee / 100) * +optimalTrade.offerAsset.amount > - profitThreshold - ); //profit - skipbid*profit - flashloanfee*tradesize must be bigger than the set PROFIT_THRESHOLD + TX_FEE. The TX fees dont depend on tradesize nor profit so are set in config - } else - return optimalTrade.profit - (botConfig.flashloanFee / 100) * +optimalTrade.offerAsset.amount > profitThreshold; +function isAboveThreshold(botConfig: DexConfig, optimalTrade: OptimalTrade | OptimalOrderbookTrade): boolean { + if (isOrderbookPath(optimalTrade.path)) { + return optimalTrade.profit >= Array.from(botConfig.profitThresholds.values())[0]; + } else { + // We dont know the number of message required to execute the trade, so the profit threshold will be set to the most conservative value: nr_of_pools*2-1 + const profitThreshold = + botConfig.profitThresholds.get((optimalTrade.path.pools.length - 1) * 2 + 1) ?? + Array.from(botConfig.profitThresholds.values())[botConfig.profitThresholds.size - 1]; + if (botConfig.skipConfig) { + const skipBidRate = botConfig.skipConfig.skipBidRate; + return ( + (1 - skipBidRate) * optimalTrade.profit - + (botConfig.flashloanFee / 100) * +optimalTrade.offerAsset.amount > + profitThreshold + ); //profit - skipbid*profit - flashloanfee*tradesize must be bigger than the set PROFIT_THRESHOLD + TX_FEE. The TX fees dont depend on tradesize nor profit so are set in config + } else + return ( + optimalTrade.profit - (botConfig.flashloanFee / 100) * +optimalTrade.offerAsset.amount > profitThreshold + ); + } } diff --git a/src/core/arbitrage/optimizers/analyticalOptimizer.ts b/src/core/arbitrage/optimizers/analyticalOptimizer.ts index 2db1255..e281fa8 100644 --- a/src/core/arbitrage/optimizers/analyticalOptimizer.ts +++ b/src/core/arbitrage/optimizers/analyticalOptimizer.ts @@ -3,114 +3,6 @@ import { Path } from "../../types/base/path"; import { getAssetsOrder, outGivenIn } from "../../types/base/pool"; import { OptimalTrade } from "../arbitrage"; -// function to get the optimal tradsize and profit for a single path. -// it assumes the token1 from pool1 is the same asset as token1 from pool2 and -// token2 from pool1 equals the asset from token2 from pool2. e.g. A->B (pool1) then B->A (pool2). -/** - *@deprecated Prefer cyclical method in getTradeForPath. This function is used for debugging and comparing both. - */ -function getTradesizeAndProfitForPath(path: Path, offerAssetInfo: AssetInfo): [number, number] { - // input token from the first pool equals out token from second pool - - let in0: number; - let out0: number; - - let in1: number; - let out1: number; - if (path.pools.length == 2) { - const [inAsset0, outAsset0] = getAssetsOrder(path.pools[0], offerAssetInfo) ?? []; - const [inAsset1, outAsset1] = getAssetsOrder(path.pools[1], outAsset0.info) ?? []; - - const in0 = +inAsset0.amount; - const out0 = +outAsset0.amount; - const in1 = +inAsset1.amount; - const out1 = +outAsset1.amount; - - const pool0fee = Math.max(path.pools[0].outputfee, path.pools[0].inputfee) / 100; - const pool1fee = Math.max(path.pools[1].outputfee, path.pools[1].inputfee) / 100; - const x1 = - (in0 * in1 - Math.sqrt(((pool0fee - 1) * pool1fee - pool0fee + 1) * in0 * in1 * out1 * out0)) / - ((pool0fee - 1) * out0 - in1); - const x2 = - (in0 * in1 + Math.sqrt(((pool0fee - 1) * pool1fee - pool0fee + 1) * in0 * in1 * out1 * out0)) / - ((pool0fee - 1) * out0 - in1); - const x = Math.min(Math.floor(Math.max(x1, x2)), 1000000000); - let currentOfferAsset = { amount: String(x), info: offerAssetInfo }; - for (let i = 0; i < path.pools.length; i++) { - const [outAmount, outInfo] = outGivenIn(path.pools[i], currentOfferAsset); - currentOfferAsset = { amount: String(outAmount), info: outInfo }; - } - const profit = +currentOfferAsset.amount - x; - - return [x, Math.round(profit)]; - } else if (path.pools.length == 3) { - const [inAsset0, outAsset0] = getAssetsOrder(path.pools[0], offerAssetInfo) ?? []; - const [inAsset1, outAsset1] = getAssetsOrder(path.pools[1], outAsset0.info) ?? []; - const [inAsset2, outAsset2] = getAssetsOrder(path.pools[2], outAsset1.info) ?? []; - - const in0 = +inAsset0.amount; - const out0 = +outAsset0.amount; - const in1 = +inAsset1.amount; - const out1 = +outAsset1.amount; - const in2 = +inAsset2.amount; - const out2 = +outAsset2.amount; - - const pool0fee = Math.max(path.pools[0].outputfee, path.pools[0].inputfee) / 100; - const pool1fee = Math.max(path.pools[1].outputfee, path.pools[1].inputfee) / 100; - const pool2fee = Math.max(path.pools[2].outputfee, path.pools[2].inputfee) / 100; - const x1 = - -( - in0 * in1 * in2 + - Math.sqrt( - -in0 * in1 * in2 * out0 * out1 * out2 * pool0fee + - in0 * in1 * in2 * out0 * out1 * out2 + - (in0 * in1 * in2 * out0 * out1 * out2 * pool0fee - in0 * in1 * in2 * out0 * out1 * out2) * - pool1fee + - (in0 * in1 * in2 * out0 * out1 * out2 * pool0fee - - in0 * in1 * in2 * out0 * out1 * out2 - - (in0 * in1 * in2 * out0 * out1 * out2 * pool0fee - in0 * in1 * in2 * out0 * out1 * out2) * - pool1fee) * - pool2fee, - ) - ) / - (in1 * in2 + - in2 * out0 + - out0 * out1 - - (in2 * out0 + out0 * out1) * pool0fee + - (out0 * out1 * pool0fee - out0 * out1) * pool1fee); - const x2 = - -( - in0 * in1 * in2 - - Math.sqrt( - -in0 * in1 * in2 * out0 * out1 * out2 * pool0fee + - in0 * in1 * in2 * out0 * out1 * out2 + - (in0 * in1 * in2 * out0 * out1 * out2 * pool0fee - in0 * in1 * in2 * out0 * out1 * out2) * - pool1fee + - (in0 * in1 * in2 * out0 * out1 * out2 * pool0fee - - in0 * in1 * in2 * out0 * out1 * out2 - - (in0 * in1 * in2 * out0 * out1 * out2 * pool0fee - in0 * in1 * in2 * out0 * out1 * out2) * - pool1fee) * - pool2fee, - ) - ) / - (in1 * in2 + - in2 * out0 + - out0 * out1 - - (in2 * out0 + out0 * out1) * pool0fee + - (out0 * out1 * pool0fee - out0 * out1) * pool1fee); - const x = Math.min(Math.floor(Math.max(x1, x2)), 1000000000); - let currentOfferAsset = { amount: String(x), info: offerAssetInfo }; - for (let i = 0; i < path.pools.length; i++) { - const [outAmount, outInfo] = outGivenIn(path.pools[i], currentOfferAsset); - currentOfferAsset = { amount: String(outAmount), info: outInfo }; - } - const profit = +currentOfferAsset.amount - x; - return [x, Math.round(profit)]; - } else { - return [-1, -1]; - } -} - /** Function to calculate the optimal path, tradesize and profit given an Array of paths and a starting asset. * @param paths Type `Array` to check for arbitrage. * @param offerAssetInfo Type `AssetInfo` to start the arbitrage from. diff --git a/src/core/arbitrage/optimizers/orderbookOptimizer.ts b/src/core/arbitrage/optimizers/orderbookOptimizer.ts new file mode 100644 index 0000000..7cca323 --- /dev/null +++ b/src/core/arbitrage/optimizers/orderbookOptimizer.ts @@ -0,0 +1,111 @@ +import { Asset, AssetInfo } from "../../types/base/asset"; +import { OrderbookMarketBuy, OrderbookMarketSell } from "../../types/base/orderbook"; +import { OrderbookPath, OrderSequence } from "../../types/base/path"; +import { outGivenIn } from "../../types/base/pool"; +import { OptimalTrade } from "../arbitrage"; + +export interface OptimalOrderbookTrade extends Omit { + worstPrice: number; //worst price for the market order to accept to fill the order + averagePrice: number; //average price obtained by the order + path: OrderbookPath; + outGivenIn: number; +} +/** + *Calculates the optimal tradesize given a CLOB and a AMM xy=k pool. + *@param orderbook Orderbook type to arb against. + *@param pool Pool type to arb against. + *@param offerAsset AssetInfo type to start and end the arbitrage trade with. + */ +export function getOptimalTrade( + paths: Array, + offerAssetInfo: AssetInfo, +): OptimalOrderbookTrade | undefined { + let optimalOrderbookTrade: OptimalOrderbookTrade = { + path: paths[0], + offerAsset: { amount: "0", info: offerAssetInfo }, + profit: 0, + worstPrice: 0, + averagePrice: 0, + outGivenIn: 0, + }; + let optimalProfit = 0; + for (const path of paths) { + const [ + optimalProfitPath, + optimalOfferAssetPath, + optimalWorstPricePath, + optimalAveragePricePath, + optimalOutGivenInPath, + ] = getOptimalTradeForPath(path, offerAssetInfo); + if (optimalProfitPath > optimalProfit) { + optimalOrderbookTrade = { + path: path, + offerAsset: optimalOfferAssetPath, + profit: optimalProfitPath, + worstPrice: optimalWorstPricePath, + averagePrice: optimalAveragePricePath, + outGivenIn: optimalOutGivenInPath, + }; + optimalProfit = optimalProfitPath; + } + } + return optimalProfit > 0 ? optimalOrderbookTrade : undefined; +} + +/** + * + */ +function getOptimalTradeForPath( + path: OrderbookPath, + offerAssetInfo: AssetInfo, +): [number, Asset, number, number, number] { + let tradesizes = [...Array(900).keys()]; + tradesizes = tradesizes.map((x) => x * 1e6); + + let leftIndex = 0; + let rightIndex = tradesizes.length - 1; + let guessIndex = 0; + while (leftIndex < rightIndex) { + guessIndex = Math.floor((leftIndex + rightIndex) / 2); + if ( + getProfitForTradesize(path, tradesizes[guessIndex], offerAssetInfo)[0] < + getProfitForTradesize(path, tradesizes[guessIndex + 1], offerAssetInfo)[0] + ) { + leftIndex = guessIndex + 1; + } else if ( + getProfitForTradesize(path, tradesizes[guessIndex], offerAssetInfo)[0] > + getProfitForTradesize(path, tradesizes[guessIndex + 1], offerAssetInfo)[0] + ) { + rightIndex = guessIndex; + } + } + return getProfitForTradesize(path, tradesizes[guessIndex], offerAssetInfo); +} + +/** + * Calculates profit for a given Path and Tradesize. + * @param tradesize Tradesize to check profit for. + * @param path OrderbookPath to check the profit for. + * @returns Array containing `[profit, received assets, worst price orderbook, average price of the trade]`. + */ +function getProfitForTradesize( + path: OrderbookPath, + tradesize: number, + offerAssetInfo: AssetInfo, +): [number, Asset, number, number, number] { + if (path.orderSequence === OrderSequence.AmmFirst) { + const offerAsset: Asset = { amount: String(tradesize), info: offerAssetInfo }; + const [outGivenIn0, outInfo0] = outGivenIn(path.pool, offerAsset); + // console.log("amm price received: ", ts / outGivenIn0, "tradesize: ", ts, "assets received: ", outGivenIn0); + const offerAsset1: Asset = { amount: String(outGivenIn0), info: outInfo0 }; + const [outGivenIn1, worstPrice, averagePrice] = OrderbookMarketSell(path.orderbook, offerAsset1); + return [outGivenIn1 - +offerAsset.amount, offerAsset, worstPrice, averagePrice, outGivenIn1]; + } else { + const offerAsset: Asset = { amount: String(tradesize), info: path.orderbook.quoteAssetInfo }; + const [outGivenIn0, worstPrice, averagePrice] = OrderbookMarketBuy(path.orderbook, offerAsset); + const outInfo0 = path.orderbook.baseAssetInfo; + const offerAsset1 = { amount: String(outGivenIn0), info: outInfo0 }; + const [outGivenIn1, outInfo1] = outGivenIn(path.pool, offerAsset1); + return [outGivenIn1 - +offerAsset.amount, offerAsset, worstPrice, averagePrice, outGivenIn0]; + } +} diff --git a/src/core/chainOperator/chainAdapters/cosmjs.ts b/src/core/chainOperator/chainAdapters/cosmjs.ts index bd60865..4c3bb8d 100644 --- a/src/core/chainOperator/chainAdapters/cosmjs.ts +++ b/src/core/chainOperator/chainAdapters/cosmjs.ts @@ -221,7 +221,18 @@ class CosmjsAdapter implements ChainOperatorInterface { console.log("Continue..."); this._currRpcUrl = out; } - + /** + * + */ + public async queryOrderbook() { + console.log("orderbook query not yet implemented for cosmjs"); + } + /** + * + */ + async queryMarket(marketId: string) { + console.log("market query not yet implemented for cosmjs"); + } /** * */ diff --git a/src/core/chainOperator/chainAdapters/injective.ts b/src/core/chainOperator/chainAdapters/injective.ts index 193793d..02fcf18 100644 --- a/src/core/chainOperator/chainAdapters/injective.ts +++ b/src/core/chainOperator/chainAdapters/injective.ts @@ -1,6 +1,6 @@ import { JsonObject } from "@cosmjs/cosmwasm-stargate"; import { stringToPath } from "@cosmjs/crypto/build/slip10"; -import { fromBase64, fromUtf8 } from "@cosmjs/encoding"; +import { fromUtf8 } from "@cosmjs/encoding"; import { EncodeObject } from "@cosmjs/proto-signing"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { StdFee } from "@cosmjs/stargate"; @@ -14,10 +14,13 @@ import { createTransaction, IndexerGrpcSpotApi, MsgBroadcasterWithPk, + MsgCreateSpotMarketOrder, MsgExecuteContract, MsgSend, + OrderbookWithSequence, PrivateKey, PublicKey, + SpotMarket, } from "@injectivelabs/sdk-ts"; import { ChainId } from "@injectivelabs/ts-types"; import { SkipBundleClient } from "@skip-mev/skipjs"; @@ -135,7 +138,8 @@ class InjectiveAdapter implements ChainOperatorInterface { address, Buffer.from(JSON.stringify(queryMsg)).toString("base64"), ); - const jsonResult = JSON.parse(fromUtf8(fromBase64(String(queryResult.data)))); + + const jsonResult = JSON.parse(fromUtf8(queryResult.data)); return jsonResult; } /** @@ -145,6 +149,18 @@ class InjectiveAdapter implements ChainOperatorInterface { const mempoolResult = await this._httpClient.execute(createJsonRpcRequest("unconfirmed_txs")); return mempoolResult.result; } + /** + * + */ + async queryOrderbook(marketId: string): Promise { + return await this._spotQueryClient.fetchOrderbookV2(marketId); + } + /** + * + */ + async queryMarket(marketId: string): Promise { + return await this._spotQueryClient.fetchMarket(marketId); + } /** * @@ -172,7 +188,6 @@ class InjectiveAdapter implements ChainOperatorInterface { injectiveAddress: this._publicAddress, }; const simRes = await this._signAndBroadcastClient.simulate(broadcasterOptions); - console.log("simulation succesful: \n", simRes); const res = await this._signAndBroadcastClient.broadcast(broadcasterOptions); return { height: res.height, @@ -259,7 +274,7 @@ class InjectiveAdapter implements ChainOperatorInterface { */ private prepair(messages: Array) { try { - const encodedExecuteMsgs: Array = []; + const encodedExecuteMsgs: Array = []; messages.map((msg, idx) => { if (msg.typeUrl === "/cosmwasm.wasm.v1.MsgExecuteContract") { const msgExecuteContract = msg.value; @@ -309,6 +324,9 @@ class InjectiveAdapter implements ChainOperatorInterface { encodedExecuteMsgs.push(msgSendInjective); } + if (msg.typeUrl === "/injective.exchange.v1beta1.MsgCreateSpotMarketOrder") { + encodedExecuteMsgs.push(msg.value); + } }); return encodedExecuteMsgs; } catch (error) { diff --git a/src/core/chainOperator/chainoperator.ts b/src/core/chainOperator/chainoperator.ts index 87062dd..dfec803 100644 --- a/src/core/chainOperator/chainoperator.ts +++ b/src/core/chainOperator/chainoperator.ts @@ -48,7 +48,7 @@ export class ChainOperator { return await this.client.queryContractSmart(address, queryMsg); } catch (e: any) { //custom error for initPools JunoSwapPoolState - if (e.message.includes("Query failed with (18):")) { + if (e.message.includes("unknown variant `pool`,")) { throw new Error(e.message); } console.log(e); @@ -69,6 +69,24 @@ export class ChainOperator { return await this.client.queryMempool(); } } + /** + * + */ + // async queryOrderbooks(marketids: Array) { + // return this.client.queryOrderbook(marketids); + // } + /** + * + */ + async queryOrderbook(marketId: string) { + return this.client.queryOrderbook(marketId); + } + /** + * + */ + async queryMarket(marketId: string) { + return this.client.queryMarket(marketId); + } /** * */ diff --git a/src/core/logging/logger.ts b/src/core/logging/logger.ts index 9b5bed6..033d101 100644 --- a/src/core/logging/logger.ts +++ b/src/core/logging/logger.ts @@ -1,8 +1,15 @@ +import { inspect } from "util"; + +import { OptimalTrade } from "../arbitrage/arbitrage"; +import { OptimalOrderbookTrade } from "../arbitrage/optimizers/orderbookOptimizer"; +import { TxResponse } from "../chainOperator/chainOperatorInterface"; import { DexLoopInterface } from "../types/arbitrageloops/interfaces/dexloopInterface"; import { LiquidationLoop } from "../types/arbitrageloops/loops/liqMempoolLoop"; +import { isNativeAsset, NativeAssetInfo } from "../types/base/asset"; import { BotConfig } from "../types/base/configs"; import { LogType } from "../types/base/logging"; import { Loan } from "../types/base/overseer"; +import { OrderSequence } from "../types/base/path"; import { DiscordLogger } from "./discordLogger"; import { SlackLogger } from "./slackLogger"; import { TelegramLogger } from "./telegramLogger"; @@ -45,7 +52,8 @@ export class Logger { ); } } - public defaults = { + + public loopLogging = { /** * */ @@ -85,6 +93,10 @@ export class Logger { const nrOfPaths = loop.paths.filter((path) => path.pools.length === pathlength).length; setupMessage += `**${pathlength} HOP Paths:** \t${nrOfPaths}\n`; } + setupMessage += `**\nOrderbooks: ${loop.orderbooks.map( + (ob) => `\n[${ob.quoteAssetInfo.native_token.denom} - ${ob.baseAssetInfo.native_token.denom}]`, + )}`; + setupMessage += `**\nOrderbook paths: ${loop.orderbookPaths.length}\n`; setupMessage += "---".repeat(30); await this._sendMessage(setupMessage, LogType.All); }, @@ -111,6 +123,70 @@ Min Risk ratio: ${maxRisk[maxRisk.length - 1].riskRatio.toPrecision(3)}`; await this._sendMessage(setupMessage, LogType.All); }, }; + + public tradeLogging = { + /** + * + */ + _sendMessage: async (message: string, type: LogType = LogType.All, code = -1) => + this.sendMessage(message, type, code), + + /** + * + */ + async logOrderbookTrade(arbtradeOB: OptimalOrderbookTrade, txResponses?: Array) { + let tradeMsg = "-".repeat(39) + "Orderbook Arb" + "-".repeat(38); + if (arbtradeOB.path.orderSequence === OrderSequence.AmmFirst) { + tradeMsg += `**\nPool: ${arbtradeOB.path.pool.address}: ${ + (arbtradeOB.path.pool.assets[0].info).native_token.denom + }/${(arbtradeOB.path.pool.assets[1].info).native_token.denom}`; + tradeMsg += `**\nOrderbook: ${ + (arbtradeOB.path.orderbook.baseAssetInfo).native_token.denom + } / USDT`; + } else { + tradeMsg += `**\nOrderbook: ${ + (arbtradeOB.path.orderbook.baseAssetInfo).native_token.denom + } / USDT`; + tradeMsg += `**\nPool: ${arbtradeOB.path.pool.address}: ${ + (arbtradeOB.path.pool.assets[0].info).native_token.denom + }/${(arbtradeOB.path.pool.assets[1].info).native_token.denom}`; + } + tradeMsg += `**\nOffering: ${inspect(arbtradeOB.offerAsset, true, null, true)}`; + tradeMsg += `**\nExpected profit: ${arbtradeOB.profit}`; + if (txResponses) { + txResponses.forEach((txResponse: TxResponse) => { + tradeMsg += `\nHash: ${txResponse.transactionHash} Code: ${txResponse.code}`; + }); + } + await this._sendMessage(tradeMsg, LogType.All); + }, + + /** + * + */ + async logAmmTrade(arbTrade: OptimalTrade, txResponses?: Array) { + let tradeMsg = "-".repeat(42) + "AMM Arb" + "-".repeat(41); + arbTrade.path.pools.forEach((pool) => { + tradeMsg += `\n**Pool: ${pool.address} with Assets: ${pool.assets[0].amount} ${ + isNativeAsset(pool.assets[0].info) + ? pool.assets[0].info.native_token.denom + : pool.assets[0].info.token.contract_addr + } / ${pool.assets[1].amount} ${ + isNativeAsset(pool.assets[1].info) + ? pool.assets[1].info.native_token.denom + : pool.assets[1].info.token.contract_addr + }`; + }); + tradeMsg += `**\nOffering: ${inspect(arbTrade.offerAsset, { showHidden: true, depth: 3, colors: true })}`; + tradeMsg += `**\nExpected profit: ${arbTrade.profit}`; + if (txResponses) { + txResponses.forEach((txResponse: TxResponse) => { + tradeMsg += `\nHash: ${txResponse.transactionHash} Code: ${txResponse.code}`; + }); + } + await this._sendMessage(tradeMsg, LogType.All); + }, + }; /** * Sends the `message` to the console and other external messaging systems if defined. * @param message The message to log. diff --git a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts index 8b8040c..f0d7211 100644 --- a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts +++ b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts @@ -1,17 +1,22 @@ -import { EncodeObject } from "@cosmjs/proto-signing"; - -import { OptimalTrade } from "../../../arbitrage/arbitrage"; +import { getPoolStates } from "../../../../chains/defaults"; +import { messageFactory } from "../../../../chains/defaults/messages/messageFactory"; +import { getOrderbookState } from "../../../../chains/inj"; +import { OptimalTrade, tryAmmArb, tryOrderbookArb } from "../../../arbitrage/arbitrage"; +import { OptimalOrderbookTrade } from "../../../arbitrage/optimizers/orderbookOptimizer"; import { ChainOperator } from "../../../chainOperator/chainoperator"; import { Logger } from "../../../logging"; import { DexConfig } from "../../base/configs"; -import { Path } from "../../base/path"; +import { Orderbook } from "../../base/orderbook"; +import { OrderbookPath, Path } from "../../base/path"; import { Pool } from "../../base/pool"; /** * */ export interface DexLoopInterface { pools: Array; + orderbooks: Array; paths: Array; //holds all known paths minus cooldowned paths + orderbookPaths: Array; pathlib: Array; //holds all known paths CDpaths: Map; //holds all cooldowned paths' identifiers chainOperator: ChainOperator; @@ -21,17 +26,32 @@ export interface DexLoopInterface { logger: Logger | undefined; iterations: number; - /** - * + /* + * Optimizers to use during loop runtime + */ + ammArb: typeof tryAmmArb | undefined; //Optimizer to calculate AMM arbitrage opportunities + orderbookArb: typeof tryOrderbookArb | undefined; //Optimizer to calculate Orderbook <> AMM arbitrage opportunities + + /* + * State updaters to use during loop runtime + */ + updatePoolStates: typeof getPoolStates; //state updater for AMM pools + updateOrderbookStates?: typeof getOrderbookState; //state updated for Orderbooks + + /* + * Message factories to translate `OptimalTrades` to Cosmos SDK messages + */ + messageFactory: typeof messageFactory; //factory to transform `OptimalTrades` into messages + + /* + * Additional required functions for DEX loops + */ + clearIgnoreAddresses: () => void; //delete timedout wallet addresses + reset: () => Promise; //reset all loop states + step: () => Promise; //iteration step to run continuously + + /* + * Trade Functions */ - arbitrageFunction: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; - updateStateFunction: (chainOperator: ChainOperator, pools: Array) => Promise; - messageFunction: ( - arbTrade: OptimalTrade, - walletAddress: string, - flashloancontract: string, - ) => [Array, number]; - step: () => Promise; - reset: () => Promise; - clearIgnoreAddresses: () => void; + trade: (arbTrade: OptimalTrade | undefined, arbTradeOB: OptimalOrderbookTrade | undefined) => void; //function to execute messages in a transaction on-chain } diff --git a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts index 7c81dbf..e240eee 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts @@ -1,6 +1,5 @@ import { sha256 } from "@cosmjs/crypto"; import { toHex } from "@cosmjs/encoding"; -import { EncodeObject } from "@cosmjs/proto-signing"; import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { inspect } from "util"; @@ -12,6 +11,7 @@ import { Logger } from "../../../logging"; import { DexConfig } from "../../base/configs"; import { LogType } from "../../base/logging"; import { decodeMempool, MempoolTx } from "../../base/mempool"; +import { Orderbook } from "../../base/orderbook"; import { applyMempoolMessagesOnPools, Pool } from "../../base/pool"; import { DexMempoolLoop } from "./dexMempoolloop"; /** @@ -26,14 +26,22 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { botConfig: DexConfig, logger: Logger | undefined, allPools: Array, + orderbooks: Array, updateState: (chainOperator: ChainOperator, pools: Array) => Promise, - messageFunction: ( - arbTrade: OptimalTrade, - walletAddress: string, - flashloancontract: string, - ) => [Array, number], + + messageFactory: DexMempoolLoop["messageFactory"], + updateOrderbookStates?: (chainOperator: ChainOperator, orderbooks: Array) => Promise, ) { - super(chainOperator, botConfig, logger, allPools, updateState, messageFunction); + super( + chainOperator, + botConfig, + logger, + allPools, + orderbooks, + updateState, + messageFactory, + updateOrderbookStates, + ); } /** @@ -41,10 +49,10 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { */ public async step(): Promise { this.iterations++; - const arbTrade: OptimalTrade | undefined = this.arbitrageFunction(this.paths, this.botConfig); + const arbTrade: OptimalTrade | undefined = this.ammArb(this.paths, this.botConfig); if (arbTrade) { - await this.trade(arbTrade); + await this.trade(arbTrade, undefined); this.cdPaths(arbTrade.path); return; } @@ -71,9 +79,9 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { } else { for (const mempoolTx of mempoolTxs) { applyMempoolMessagesOnPools(this.pools, [mempoolTx]); - const arbTrade: OptimalTrade | undefined = this.arbitrageFunction(this.paths, this.botConfig); + const arbTrade: OptimalTrade | undefined = this.ammArb(this.paths, this.botConfig); if (arbTrade) { - await this.trade(arbTrade, mempoolTx); + await this.skipTrade(arbTrade, mempoolTx); this.cdPaths(arbTrade.path); await this.chainOperator.reset(); return; @@ -86,7 +94,7 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { /** * */ - public async trade(arbTrade: OptimalTrade, toArbTrade?: MempoolTx) { + public async skipTrade(arbTrade: OptimalTrade, toArbTrade?: MempoolTx) { if ( !this.botConfig.skipConfig?.useSkip || this.botConfig.skipConfig?.skipRpcUrl === undefined || @@ -108,27 +116,35 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { this.chainOperator.client.publicAddress, this.botConfig.skipConfig.skipBidWallet, ); - const [msgs, nrOfWasms] = this.messageFunction( + const messages = this.messageFactory( arbTrade, this.chainOperator.client.publicAddress, this.botConfig.flashloanRouterAddress, ); - msgs.push(bidMsgEncoded); + if (!messages) { + console.error("error in creating messages", 1); + process.exit(1); + } + messages[0].push(bidMsgEncoded); //if gas fee cannot be found in the botconfig based on pathlengths, pick highest available const TX_FEE = - this.botConfig.txFees.get(nrOfWasms) ?? + this.botConfig.txFees.get(messages[1]) ?? Array.from(this.botConfig.txFees.values())[this.botConfig.txFees.size - 1]; console.log(inspect(TX_FEE, { depth: null })); let res: SkipResult; if (toArbTrade) { const txToArbRaw: TxRaw = TxRaw.decode(toArbTrade.txBytes); - res = await this.chainOperator.signAndBroadcastSkipBundle(msgs, TX_FEE, undefined, txToArbRaw); + res = ( + await this.chainOperator.signAndBroadcastSkipBundle(messages[0], TX_FEE, undefined, txToArbRaw) + ); console.log("mempool transaction to backrun: "); console.log(toHex(sha256(toArbTrade.txBytes))); } else { - res = await this.chainOperator.signAndBroadcastSkipBundle(msgs, TX_FEE, undefined, undefined); + res = ( + await this.chainOperator.signAndBroadcastSkipBundle(messages[0], TX_FEE, undefined, undefined) + ); } console.log(inspect(res, { depth: null })); @@ -142,7 +158,7 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { } if (this.botConfig.skipConfig.tryWithoutSkip && res.result.code === 4) { await this.logger?.sendMessage("no skip validator up, trying default broadcast", LogType.Console); - await this.trade(arbTrade); + await this.trade(arbTrade, undefined); } if (res.result.result_check_txs != undefined) { diff --git a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts index fa1444b..42f09c0 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts @@ -2,25 +2,26 @@ import { sha256 } from "@cosmjs/crypto"; import { toHex } from "@cosmjs/encoding"; -import { EncodeObject } from "@cosmjs/proto-signing"; import { getPaths, newGraph } from "../../../arbitrage/graph"; -import { inspect } from "util"; -import { OptimalTrade, trySomeArb } from "../../../arbitrage/arbitrage"; +import { OptimalTrade, tryAmmArb, tryOrderbookArb } from "../../../arbitrage/arbitrage"; import { ChainOperator } from "../../../chainOperator/chainoperator"; import { Logger } from "../../../logging/logger"; import { DexConfig } from "../../base/configs"; -import { LogType } from "../../base/logging"; import { Mempool, IgnoredAddresses, MempoolTx, decodeMempool, flushTxMemory } from "../../base/mempool"; -import { Path } from "../../base/path"; +import { getOrderbookAmmPaths, OrderbookPath, OrderSequence, Path } from "../../base/path"; import { removedUnusedPools, applyMempoolMessagesOnPools, Pool } from "../../base/pool"; import { DexLoopInterface } from "../interfaces/dexloopInterface"; +import { Orderbook } from "../../base/orderbook"; +import { OptimalOrderbookTrade } from "../../../arbitrage/optimizers/orderbookOptimizer"; /** * */ export class DexMempoolLoop implements DexLoopInterface { pools: Array; + orderbooks: Array; paths: Array; //holds all known paths minus cooldowned paths + orderbookPaths: Array; pathlib: Array; //holds all known paths CDpaths: Map; //holds all cooldowned paths' identifiers chainOperator: ChainOperator; @@ -29,9 +30,11 @@ export class DexMempoolLoop implements DexLoopInterface { botConfig: DexConfig; logger: Logger | undefined; iterations = 0; - updateStateFunction: DexLoopInterface["updateStateFunction"]; - messageFunction: DexLoopInterface["messageFunction"]; - arbitrageFunction: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + updatePoolStates: DexLoopInterface["updatePoolStates"]; + updateOrderbookStates?: (chainOperator: ChainOperator, orderbooks: Array) => Promise; + messageFactory: DexLoopInterface["messageFactory"]; + ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; // CACHE VALUES totalBytes = 0; mempool!: Mempool; @@ -45,23 +48,26 @@ export class DexMempoolLoop implements DexLoopInterface { botConfig: DexConfig, logger: Logger | undefined, allPools: Array, + orderbooks: Array, updateState: (chainOperator: ChainOperator, pools: Array) => Promise, - messageFunction: ( - arbTrade: OptimalTrade, - walletAddress: string, - flashloancontract: string, - ) => [Array, number], + messageFactory: DexLoopInterface["messageFactory"], + updateOrderbookStates?: DexLoopInterface["updateOrderbookStates"], ) { const graph = newGraph(allPools); const paths = getPaths(graph, botConfig.offerAssetInfo, botConfig.maxPathPools) ?? []; const filteredPools = removedUnusedPools(allPools, paths); + const orderbookPaths = getOrderbookAmmPaths(allPools, orderbooks); + this.orderbookPaths = orderbookPaths; this.pools = filteredPools; + this.orderbooks = orderbooks; this.CDpaths = new Map(); this.paths = paths; this.pathlib = paths; - this.arbitrageFunction = trySomeArb; - this.updateStateFunction = updateState; - this.messageFunction = messageFunction; + this.ammArb = tryAmmArb; + this.orderbookArb = tryOrderbookArb; + this.updateOrderbookStates = updateOrderbookStates; + this.updatePoolStates = updateState; + this.messageFactory = messageFactory; this.chainOperator = chainOperator; this.botConfig = botConfig; this.logger = logger; @@ -72,13 +78,16 @@ export class DexMempoolLoop implements DexLoopInterface { */ public async step() { this.iterations++; - await this.updateStateFunction(this.chainOperator, this.pools); + await this.updatePoolStates(this.chainOperator, this.pools); + if (this.updateOrderbookStates) { + await this.updateOrderbookStates(this.chainOperator, this.orderbooks); + } - const arbTrade: OptimalTrade | undefined = this.arbitrageFunction(this.paths, this.botConfig); + const arbTrade: OptimalTrade | undefined = this.ammArb(this.paths, this.botConfig); + const arbtradeOB = this.orderbookArb(this.orderbookPaths, this.botConfig); - if (arbTrade) { - await this.trade(arbTrade); - this.cdPaths(arbTrade.path); + if (arbTrade || arbtradeOB) { + await this.trade(arbTrade, arbtradeOB); return; } @@ -107,16 +116,15 @@ export class DexMempoolLoop implements DexLoopInterface { applyMempoolMessagesOnPools(this.pools, mempoolTxs); } - const arbTrade = this.arbitrageFunction(this.paths, this.botConfig); + const arbTrade = this.ammArb(this.paths, this.botConfig); + const arbtradeOB = this.orderbookArb(this.orderbookPaths, this.botConfig); - if (arbTrade) { - await this.trade(arbTrade); + if (arbTrade || arbtradeOB) { + await this.trade(arbTrade, arbtradeOB); console.log("mempool transactions to backrun:"); mempoolTxs.map((mpt) => { console.log(toHex(sha256(mpt.txBytes))); }); - this.cdPaths(arbTrade.path); - await this.chainOperator.reset(); break; } } @@ -127,6 +135,7 @@ export class DexMempoolLoop implements DexLoopInterface { * */ async reset() { + await this.chainOperator.reset(); this.unCDPaths(); this.totalBytes = 0; flushTxMemory(); @@ -135,41 +144,87 @@ export class DexMempoolLoop implements DexLoopInterface { /** * */ - public async trade(arbTrade: OptimalTrade) { - const [msgs, nrOfMessages] = this.messageFunction( - arbTrade, - this.chainOperator.client.publicAddress, - this.botConfig.flashloanRouterAddress, - ); + public async trade(arbTrade: OptimalTrade | undefined, arbTradeOB: OptimalOrderbookTrade | undefined) { + if (arbTrade && arbTradeOB) { + if (arbTrade.profit > arbTradeOB.profit) { + await this.tradeAmm(arbTrade); + this.cdPaths(arbTrade.path); + } else if (arbTrade.profit <= arbTradeOB.profit) { + await this.tradeOrderbook(arbTradeOB); + this.cdPaths(arbTradeOB.path); + } + } else if (arbTrade) { + await this.tradeAmm(arbTrade); + this.cdPaths(arbTrade.path); + } else if (arbTradeOB) { + await this.tradeOrderbook(arbTradeOB); + this.cdPaths(arbTradeOB.path); + } + await delay(6000); // await this.logger?.sendMessage(JSON.stringify(msgs), LogType.Console); + } + /** + * + */ + private async tradeOrderbook(arbTradeOB: OptimalOrderbookTrade) { + const messages = this.messageFactory(arbTradeOB, this.chainOperator.client.publicAddress, undefined); + if (!messages) { + console.error("error in creating messages", 1); + process.exit(1); + } const TX_FEE = - this.botConfig.txFees.get(nrOfMessages) ?? + this.botConfig.txFees.get(messages[1]) ?? Array.from(this.botConfig.txFees.values())[this.botConfig.txFees.size - 1]; - console.log(inspect(TX_FEE)); - const txResponse = await this.chainOperator.signAndBroadcast(msgs, TX_FEE); - await this.logger?.sendMessage(JSON.stringify(txResponse), LogType.Console); - - if (txResponse.code === 0) { - this.chainOperator.client.sequence = this.chainOperator.client.sequence + 1; + if (arbTradeOB.path.orderSequence === OrderSequence.AmmFirst) { + const txResponse = await this.chainOperator.signAndBroadcast(messages[0], TX_FEE); + await this.logger?.tradeLogging.logOrderbookTrade(arbTradeOB, [txResponse]); + } else { + const txResponse = await this.chainOperator.signAndBroadcast([messages[0][0]], TX_FEE); + await delay(3000); + const txResponse2 = await this.chainOperator.signAndBroadcast([messages[0][1]], TX_FEE); + await this.logger?.tradeLogging.logOrderbookTrade(arbTradeOB, [ + txResponse, + txResponse2, + ]); } - await delay(5000); } + /** + * + */ + private async tradeAmm(arbTrade: OptimalTrade) { + const messages = this.messageFactory( + arbTrade, + this.chainOperator.client.publicAddress, + this.botConfig.flashloanRouterAddress, + ); + if (!messages) { + console.error("error in creating messages", 1); + process.exit(1); + } + const TX_FEE = + this.botConfig.txFees.get(messages[1]) ?? + Array.from(this.botConfig.txFees.values())[this.botConfig.txFees.size - 1]; + + const txResponse = await this.chainOperator.signAndBroadcast(messages[0], TX_FEE); + + await this.logger?.tradeLogging.logAmmTrade(arbTrade, [txResponse]); + } /** * Put path on Cooldown, add to CDPaths with iteration number as block. * Updates the iteration count of elements in CDpaths if its in equalpath of param: path * Updates this.Path. */ - public cdPaths(path: Path) { + public cdPaths(path: Path | OrderbookPath) { //add equalpaths to the CDPath array for (const equalpath of path.equalpaths) { - this.CDpaths.set(equalpath[0], [this.iterations, 5, equalpath[1]]); + this.CDpaths.set(equalpath[0], [this.iterations, 30, equalpath[1]]); } //add self to the CDPath array - this.CDpaths.set(path.identifier[0], [this.iterations, 10, path.identifier[1]]); + this.CDpaths.set(path.identifier[0], [this.iterations, 60, path.identifier[1]]); const out = new Array(); //remove all equal paths from this.paths if this.paths'identifier overlaps with one in equalpaths @@ -180,6 +235,16 @@ export class DexMempoolLoop implements DexLoopInterface { } }); this.paths = out; + + const outOB = new Array(); + //remove all equal paths from this.paths if this.paths'identifier overlaps with one in equalpaths + this.orderbookPaths.forEach((activePath) => { + //if our updated cdpaths contains the path still active, make sure to remove it from the active paths + if (!this.CDpaths.get(activePath.identifier[0])) { + outOB.push(activePath); + } + }); + this.orderbookPaths = outOB; } /**. diff --git a/src/core/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index 1506941..73f8e16 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -1,13 +1,14 @@ -import { inspect } from "util"; - import * as chains from "../../../../chains"; -import { OptimalTrade, trySomeArb } from "../../../arbitrage/arbitrage"; +import { messageFactory } from "../../../../chains/defaults/messages/messageFactory"; +import { OptimalTrade, tryAmmArb, tryOrderbookArb } from "../../../arbitrage/arbitrage"; import { getPaths, newGraph } from "../../../arbitrage/graph"; +import { OptimalOrderbookTrade } from "../../../arbitrage/optimizers/orderbookOptimizer"; import { ChainOperator } from "../../../chainOperator/chainoperator"; import { Logger } from "../../../logging"; import { DexConfig } from "../../base/configs"; import { LogType } from "../../base/logging"; -import { Path } from "../../base/path"; +import { Orderbook } from "../../base/orderbook"; +import { getOrderbookAmmPaths, OrderbookPath, OrderSequence, Path } from "../../base/path"; import { Pool, removedUnusedPools } from "../../base/pool"; import { DexLoopInterface } from "../interfaces/dexloopInterface"; import { DexMempoolLoop } from "./dexMempoolloop"; @@ -18,7 +19,9 @@ import { DexMempoolSkipLoop } from "./dexMempoolSkiploop"; */ export class DexLoop implements DexLoopInterface { pools: Array; + orderbooks: Array; paths: Array; //holds all known paths minus cooldowned paths + orderbookPaths: Array; pathlib: Array; //holds all known paths CDpaths: Map; //holds all cooldowned paths' identifiers chainOperator: ChainOperator; @@ -27,9 +30,11 @@ export class DexLoop implements DexLoopInterface { botConfig: DexConfig; logger: Logger | undefined; iterations = 0; - updateStateFunction: DexLoopInterface["updateStateFunction"]; - messageFunction: DexLoopInterface["messageFunction"]; - arbitrageFunction: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + updatePoolStates: DexLoopInterface["updatePoolStates"]; + updateOrderbookStates?: DexLoopInterface["updateOrderbookStates"]; + messageFactory: DexLoopInterface["messageFactory"]; + ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; /** * @@ -39,19 +44,27 @@ export class DexLoop implements DexLoopInterface { botConfig: DexConfig, logger: Logger | undefined, allPools: Array, - updateState: DexLoopInterface["updateStateFunction"], - messageFunction: DexLoopInterface["messageFunction"], + orderbooks: Array, + updatePoolStates: DexLoopInterface["updatePoolStates"], + messageFactory: DexLoopInterface["messageFactory"], + updateOrderbookStates?: DexLoopInterface["updateOrderbookStates"], ) { const graph = newGraph(allPools); const paths = getPaths(graph, botConfig.offerAssetInfo, botConfig.maxPathPools) ?? []; const filteredPools = removedUnusedPools(allPools, paths); + const orderbookPaths = getOrderbookAmmPaths(allPools, orderbooks); + + this.orderbookPaths = orderbookPaths; + this.orderbooks = orderbooks; this.pools = filteredPools; this.CDpaths = new Map(); this.paths = paths; this.pathlib = paths; - this.arbitrageFunction = trySomeArb; - this.updateStateFunction = updateState; - this.messageFunction = messageFunction; + this.ammArb = tryAmmArb; + this.orderbookArb = tryOrderbookArb; + this.updatePoolStates = updatePoolStates; + this.updateOrderbookStates = updateOrderbookStates; + this.messageFactory = messageFactory; this.chainOperator = chainOperator; this.botConfig = botConfig; this.logger = logger; @@ -64,33 +77,64 @@ export class DexLoop implements DexLoopInterface { botConfig: DexConfig, logger: Logger, ): Promise { - let getFlashArbMessages = chains.defaults.getFlashArbMessages; + const msgFactory = chains.defaults.messageFactory; let getPoolStates = chains.defaults.getPoolStates; let initPools = chains.defaults.initPools; - + const initOrderbook = chains.injective.initOrderbooks; + const getOrderbookState = chains.injective.getOrderbookState; await import("../../../../chains/" + botConfig.chainPrefix).then(async (chainSetups) => { if (chainSetups === undefined) { await logger.sendMessage("Unable to resolve specific chain imports, using defaults", LogType.Console); } - getFlashArbMessages = chainSetups.getFlashArbMessages; + // msgFactory = chainSetups.getFlashArbMessages; getPoolStates = chainSetups.getPoolStates; initPools = chainSetups.initPools; return; }); + const orderbooks: Array = []; + if (botConfig.chainPrefix === "inj" && botConfig.orderbooks.length > 0) { + const obs = await initOrderbook(chainOperator, botConfig); + if (obs) { + orderbooks.push(...obs); + } + } const allPools = await initPools(chainOperator, botConfig.poolEnvs, botConfig.mappingFactoryRouter); if (botConfig.useMempool && !botConfig.skipConfig?.useSkip) { - return new DexMempoolLoop(chainOperator, botConfig, logger, allPools, getPoolStates, getFlashArbMessages); + console.log("spinning up mempool loop"); + return new DexMempoolLoop( + chainOperator, + botConfig, + logger, + allPools, + orderbooks, + getPoolStates, + msgFactory, + getOrderbookState, + ); } else if (botConfig.useMempool && botConfig.skipConfig?.useSkip) { + console.log("spinning up skip mempool loop"); return new DexMempoolSkipLoop( chainOperator, botConfig, logger, allPools, + orderbooks, getPoolStates, - getFlashArbMessages, + msgFactory, + getOrderbookState, ); } - return new DexLoop(chainOperator, botConfig, logger, allPools, getPoolStates, getFlashArbMessages); + console.log("spinning up no-mempool loop"); + return new DexLoop( + chainOperator, + botConfig, + logger, + allPools, + orderbooks, + getPoolStates, + messageFactory, + getOrderbookState, + ); } /** * @@ -98,18 +142,13 @@ export class DexLoop implements DexLoopInterface { public async step() { this.iterations++; - const arbTrade: OptimalTrade | undefined = this.arbitrageFunction(this.paths, this.botConfig); + const arbTrade: OptimalTrade | undefined = this.ammArb(this.paths, this.botConfig); + const arbtradeOB = this.orderbookArb(this.orderbookPaths, this.botConfig); - if (arbTrade) { - console.log(inspect(arbTrade.path.pools, { showHidden: true, depth: 4, colors: true })); - console.log(inspect(arbTrade.offerAsset, { showHidden: true, depth: 3, colors: true })); - console.log("expected profit: ", arbTrade.profit); - await this.trade(arbTrade); - this.cdPaths(arbTrade.path); + if (arbTrade || arbtradeOB) { + await this.trade(arbTrade, arbtradeOB); await this.chainOperator.reset(); } - - await delay(1500); } /** @@ -117,30 +156,82 @@ export class DexLoop implements DexLoopInterface { */ async reset() { this.unCDPaths(); - await this.updateStateFunction(this.chainOperator, this.pools); + await this.updatePoolStates(this.chainOperator, this.pools); + if (this.updateOrderbookStates) { + await this.updateOrderbookStates(this.chainOperator, this.orderbooks); + } + } + + /** + * + */ + public async trade(arbTrade: OptimalTrade | undefined, arbTradeOB: OptimalOrderbookTrade | undefined) { + if (arbTrade && arbTradeOB) { + if (arbTrade.profit > arbTradeOB.profit) { + await this.tradeAmm(arbTrade); + this.cdPaths(arbTrade.path); + } else if (arbTrade.profit <= arbTradeOB.profit) { + await this.tradeOrderbook(arbTradeOB); + this.cdPaths(arbTradeOB.path); + } + } else if (arbTrade) { + await this.tradeAmm(arbTrade); + this.cdPaths(arbTrade.path); + } else if (arbTradeOB) { + await this.tradeOrderbook(arbTradeOB); + this.cdPaths(arbTradeOB.path); + } + + await delay(6000); + // await this.logger?.sendMessage(JSON.stringify(msgs), LogType.Console); + } + /** + * + */ + private async tradeOrderbook(arbTradeOB: OptimalOrderbookTrade) { + const messages = this.messageFactory(arbTradeOB, this.chainOperator.client.publicAddress, undefined); + if (!messages) { + console.error("error in creating messages", 1); + process.exit(1); + } + if (arbTradeOB.path.orderSequence === OrderSequence.AmmFirst) { + const txResponse = await this.chainOperator.signAndBroadcast(messages[0]); + await this.logger?.tradeLogging.logOrderbookTrade(arbTradeOB, [txResponse]); + } else { + const txResponse = await this.chainOperator.signAndBroadcast([messages[0][0]]); + await delay(2000); + const txResponse2 = await this.chainOperator.signAndBroadcast([messages[0][1]]); + await this.logger?.tradeLogging.logOrderbookTrade(arbTradeOB, [ + txResponse, + txResponse2, + ]); + } } /** * */ - public async trade(arbTrade: OptimalTrade) { - const publicAddress = this.chainOperator.client.publicAddress; - const [msgs, nrOfMessages] = this.messageFunction( + private async tradeAmm(arbTrade: OptimalTrade) { + const messages = this.messageFactory( arbTrade, - publicAddress, + this.chainOperator.client.publicAddress, this.botConfig.flashloanRouterAddress, ); + if (!messages) { + console.error("error in creating messages", 1); + process.exit(1); + } + const txResponse = await this.chainOperator.signAndBroadcast(messages[0]); - const txResponse = await this.chainOperator.signAndBroadcast(msgs); - console.log(txResponse); - await delay(10000); + await this.logger?.tradeLogging.logAmmTrade(arbTrade, [txResponse]); } + /** * Put path on Cooldown, add to CDPaths with iteration number as block. * Updates the iteration count of elements in CDpaths if its in equalpath of param: path * Updates this.Path. */ - public cdPaths(path: Path) { + public cdPaths(path: Path | OrderbookPath) { //add equalpaths to the CDPath array for (const equalpath of path.equalpaths) { this.CDpaths.set(equalpath[0], [this.iterations, 5, equalpath[1]]); @@ -157,6 +248,16 @@ export class DexLoop implements DexLoopInterface { } }); this.paths = out; + + const outOB = new Array(); + //remove all equal paths from this.paths if this.paths'identifier overlaps with one in equalpaths + this.orderbookPaths.forEach((activePath) => { + //if our updated cdpaths contains the path still active, make sure to remove it from the active paths + if (!this.CDpaths.get(activePath.identifier[0])) { + outOB.push(activePath); + } + }); + this.orderbookPaths = outOB; } /** Removes the CD Paths if CD iteration number of path + Cooldownblocks <= this.iterations diff --git a/src/core/types/base/asset.ts b/src/core/types/base/asset.ts index 93b7035..a02dbec 100644 --- a/src/core/types/base/asset.ts +++ b/src/core/types/base/asset.ts @@ -85,7 +85,10 @@ export function isMatchingAssetInfos(a: AssetInfo, b: AssetInfo) { * */ export function toChainAsset(input: Asset): Asset { - if (isNativeAsset(input.info) && input.info.native_token.denom === "inj") { + if ( + isNativeAsset(input.info) && + ["inj", "peggy0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"].includes(input.info.native_token.denom) + ) { return { amount: new BigNumber(+input.amount).multipliedBy(new BigNumber(10).pow(12)).toFixed(), info: input.info, @@ -101,7 +104,10 @@ export function toChainAsset(input: Asset): Asset { * */ export function fromChainAsset(input: Asset): Asset { - if (isNativeAsset(input.info) && input.info.native_token.denom === "inj") { + if ( + isNativeAsset(input.info) && + ["inj", "peggy0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"].includes(input.info.native_token.denom) + ) { return { amount: new BigNumber(+input.amount).dividedBy(new BigNumber(10).pow(12)).toFixed(6), info: input.info, @@ -129,7 +135,10 @@ export function toChainPrice(input: Asset, output: Asset): string { const outputChain = toChainAsset(output); if (isMatchingAssetInfos(inputChain.info, outputChain.info)) { return new BigNumber(inputChain.amount).dividedBy(outputChain.amount).toFixed(6); - } else if (isNativeAsset(outputChain.info) && outputChain.info.native_token.denom === "inj") { + } else if ( + isNativeAsset(outputChain.info) && + ["inj", "peggy0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"].includes(outputChain.info.native_token.denom) + ) { return new BigNumber(inputChain.amount).dividedBy(outputChain.amount).toFixed(18); } else { return new BigNumber(inputChain.amount).dividedBy(outputChain.amount).toFixed(6); diff --git a/src/core/types/base/configs.ts b/src/core/types/base/configs.ts index 8e0849f..1e72deb 100644 --- a/src/core/types/base/configs.ts +++ b/src/core/types/base/configs.ts @@ -55,7 +55,7 @@ export interface DexConfig extends BaseConfig { mappingFactoryRouter: Array<{ factory: string; router: string }>; offerAssetInfo: NativeAssetInfo; poolEnvs: Array<{ pool: string; inputfee: number; outputfee: number; LPratio: number }>; - + orderbooks: Array; timeoutDuration: number; useRpcUrlScraper?: boolean; } @@ -222,6 +222,10 @@ function getDexConfig(envs: NodeJS.ProcessEnv, baseConfig: BaseConfig): DexConfi const addrs = JSON.parse(envs.IGNORE_ADDRESSES); addrs.forEach((element: string) => (IGNORE_ADDRS[element] = { timeoutAt: 0, duration: timeoutDuration })); } + let orderbooks; + if (envs.ORDERBOOKS) { + orderbooks = JSON.parse(envs.ORDERBOOKS); + } return { ...baseConfig, @@ -232,6 +236,7 @@ function getDexConfig(envs: NodeJS.ProcessEnv, baseConfig: BaseConfig): DexConfi mappingFactoryRouter: FACTORIES_TO_ROUTERS_MAPPING, offerAssetInfo: OFFER_ASSET_INFO, poolEnvs: POOLS_ENVS, + orderbooks: orderbooks ?? [], timeoutDuration: timeoutDuration, useRpcUrlScraper: envs.USE_RPC_URL_SCRAPER == "1" ? true : false, }; diff --git a/src/core/types/base/orderbook.ts b/src/core/types/base/orderbook.ts new file mode 100644 index 0000000..b9970bb --- /dev/null +++ b/src/core/types/base/orderbook.ts @@ -0,0 +1,58 @@ +import { Asset, NativeAssetInfo } from "./asset"; + +export interface Order { + price: number; + quantity: number; + type: "sell" | "buy"; +} +export interface Orderbook { + marketId: string; //contractaddress or marketid + baseAssetInfo: NativeAssetInfo; + quoteAssetInfo: NativeAssetInfo; + baseAssetDecimals: number; + quoteAssetDecimals: number; + minQuantityIncrement: number; + buys: Array; + sells: Array; +} + +/** + * Market sell the offered asset, meaning it should be matched to the buy side of the orderbook. + * @param orderbook Orderbook type to sell on. + * @param offerAsset Asset type to sell. + * @return [number, number] the received asset amount and average price. + */ +export function OrderbookMarketSell(orderbook: Orderbook, offerAsset: Asset) { + //we are selling the base asset + let rest = Math.floor(+offerAsset.amount); + let result = 0; + let buyIndex = 0; + while (rest > 0) { + const currentBuy = orderbook.buys[buyIndex]; + const currentOrderSize = currentBuy.quantity > rest ? rest : currentBuy.quantity; + rest = rest - Math.floor(currentOrderSize); + result = result + Math.floor(currentOrderSize * currentBuy.price); + buyIndex = buyIndex + 1; + } + return [Math.floor(0.999 * result), orderbook.buys[buyIndex].price, (0.999 * result) / +offerAsset.amount]; +} + +/** + * + */ +export function OrderbookMarketBuy(orderbook: Orderbook, offerAsset: Asset) { + //we are buying the base asset + let rest = +offerAsset.amount; + let result = 0; + let sellIndex = 0; + while (rest > 0) { + const currentSell = orderbook.sells[sellIndex]; + + const currentOrderSize = + currentSell.quantity * currentSell.price > rest ? rest : currentSell.quantity * currentSell.price; + rest = rest - Math.floor(currentOrderSize); + result = result + Math.floor(currentOrderSize / currentSell.price); + sellIndex = sellIndex + 1; + } + return [Math.floor(0.999 * result), orderbook.sells[sellIndex].price, +offerAsset.amount / (0.999 * result)]; +} diff --git a/src/core/types/base/path.ts b/src/core/types/base/path.ts index 5488a34..2578d47 100644 --- a/src/core/types/base/path.ts +++ b/src/core/types/base/path.ts @@ -1,3 +1,6 @@ +import { identity } from "../identity"; +import { isMatchingAssetInfos } from "./asset"; +import { Orderbook } from "./orderbook"; import { Pool } from "./pool"; export interface Path { @@ -5,3 +8,57 @@ export interface Path { equalpaths: Array<[string, number]>; identifier: [string, number]; } + +export enum OrderSequence { + AmmFirst, + OrderbookFirst, +} + +export interface OrderbookPath { + pool: Pool; + orderbook: Orderbook; + orderSequence: OrderSequence; + equalpaths: Array<[string, number]>; + identifier: [string, number]; +} +/** + * + */ +export function isOrderbookPath(x: any): x is OrderbookPath { + return x["orderbook" as keyof typeof x] !== undefined; +} +/** + * + */ +export function getOrderbookAmmPaths(pools: Array, orderbooks: Array): Array { + const paths: Array = []; + let idx = 0; + for (const orderbook of orderbooks) { + for (const pool of pools) { + if ( + (isMatchingAssetInfos(pool.assets[0].info, orderbook.baseAssetInfo) && + isMatchingAssetInfos(pool.assets[1].info, orderbook.quoteAssetInfo)) || + (isMatchingAssetInfos(pool.assets[1].info, orderbook.baseAssetInfo) && + isMatchingAssetInfos(pool.assets[0].info, orderbook.quoteAssetInfo)) + ) { + const path = identity({ + pool: pool, + orderbook: orderbook, + orderSequence: OrderSequence.AmmFirst, + equalpaths: [], + identifier: [pool.LPratio + orderbook.marketId, idx], + }); + const reversedpath = identity({ + pool: pool, + orderbook: orderbook, + orderSequence: OrderSequence.OrderbookFirst, + equalpaths: [], + identifier: [orderbook.marketId + pool.LPratio, idx + 1], + }); + paths.push(path, reversedpath); + idx += 2; + } + } + } + return paths; +} diff --git a/src/core/types/messages/spotorders.ts b/src/core/types/messages/spotorders.ts new file mode 100644 index 0000000..f75d3d8 --- /dev/null +++ b/src/core/types/messages/spotorders.ts @@ -0,0 +1,25 @@ +import { OrderType } from "@injectivelabs/sdk-ts/"; + +/* order types: + UNSPECIFIED: 0; + BUY: 1; + SELL: 2; + STOP_BUY: 3; + STOP_SELL: 4; + TAKE_BUY: 5; + TAKE_SELL: 6; + BUY_PO: 7; + SELL_PO: 8; + BUY_ATOMIC: 9; + SELL_ATOMIC: 10; +*/ +export declare type SpotMarketOrderMessage = { + marketId: string; + subaccountId: string; + injectiveAddress: string; + orderType: OrderType; + triggerPrice?: string; + feeRecipient: string; + price: string; + quantity: string; +}; diff --git a/src/core/types/modules.d.ts b/src/core/types/modules.d.ts index 5e2b86e..a8b53c6 100644 --- a/src/core/types/modules.d.ts +++ b/src/core/types/modules.d.ts @@ -64,11 +64,16 @@ declare namespace NodeJS { * The "fee" value should be a numeric specifying how much ofo the BASE_DENOM to pay. */ POOLS: string; + /** + * A list of all known orderbook pairs (marketids), only relevant when SETUP_TYPE = dex. + */ + ORDERBOOKS: string; /** * Set to "1" if the code should use mempool analysis to send transactions. * * This will decrease success rate but increase throughput. */ + USE_MEMPOOL: string; /** * The price of a GAS_UNIT on the specific chain, denominated by BASE_DENOM. diff --git a/src/index.ts b/src/index.ts index d71c263..8b17453 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,7 @@ async function main() { const logger = new Logger(botConfig); // print the config - await logger.defaults.logConfig(botConfig); + await logger.loopLogging.logConfig(botConfig); //spawn chainOperator for interaction with blockchains const chainOperator = await ChainOperator.connectWithSigner(botConfig); //create the arbitrage loop based on input config @@ -28,12 +28,12 @@ async function main() { case SetupType.DEX: loop = await DexLoop.createLoop(chainOperator, botConfig, logger); //print the created arbitrage loop - await logger.defaults.logDexLoop(loop); + await logger.loopLogging.logDexLoop(loop); break; case SetupType.LIQUIDATION: loop = await LiquidationLoop.createLoop(chainOperator, botConfig, logger); //print the created arbitrage loop - await logger.defaults.logLiqLoop(loop); + await logger.loopLogging.logLiqLoop(loop); break; } diff --git a/src/node/injectiveclient.ts b/src/node/injectiveclient.ts deleted file mode 100644 index eeba6f1..0000000 --- a/src/node/injectiveclient.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getNetworkEndpoints, Network } from "@injectivelabs/networks"; -import { - ChainGrpcBankApi, - ChainGrpcExchangeApi, - ChainGrpcWasmApi, - IndexerGrpcAccountApi, - IndexerGrpcSpotApi, - MsgBroadcasterWithPk, - PrivateKey, -} from "@injectivelabs/sdk-ts"; -import { inspect } from "util"; - -/** - * - */ -export function getInjectiveClient(mnemonic: string) { - const network = Network.Mainnet; - const endpoints = getNetworkEndpoints(network); - console.log(inspect(endpoints, { depth: null })); - const privateKey = PrivateKey.fromMnemonic(mnemonic); - - const broadcasterOptions = { - network: network, - privateKey: privateKey, - }; - const spotMarketClient = new ChainGrpcExchangeApi(endpoints.grpc); - const spotClient = new IndexerGrpcSpotApi(endpoints.indexer); - const broadcastClient = new MsgBroadcasterWithPk(broadcasterOptions); - const wasmClient = new ChainGrpcWasmApi(endpoints.grpc); - const bankclient = new ChainGrpcBankApi(endpoints.grpc); - - const accountClient = new IndexerGrpcAccountApi(endpoints.indexer); - - return { broadcastClient, spotClient, wasmClient, accountClient, bankclient }; -}