From 2eddd48be5f163b77c1eeb5602945ea4324d1fc4 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Mon, 10 Jul 2023 16:42:57 +0200 Subject: [PATCH 01/17] feat: first piece for orderbook support --- src/core/types/base/orderbook.ts | 58 ++++++++++++++++++++++++++++++++ src/core/types/base/path.ts | 41 ++++++++++++++++++++++ src/core/types/modules.d.ts | 5 +++ 3 files changed, 104 insertions(+) create mode 100644 src/core/types/base/orderbook.ts diff --git a/src/core/types/base/orderbook.ts b/src/core/types/base/orderbook.ts new file mode 100644 index 00000000..fbd34c29 --- /dev/null +++ b/src/core/types/base/orderbook.ts @@ -0,0 +1,58 @@ +import { Asset, AssetInfo } from "./asset"; + +export interface Order { + price: number; + quantity: number; + type: "sell" | "buy"; +} +export interface Orderbook { + marketId: string; //contractaddress or marketid + baseAssetInfo: AssetInfo; + quoteAssetInfo: AssetInfo; + 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 = +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 5488a34f..36bed2bb 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,41 @@ export interface Path { equalpaths: Array<[string, number]>; identifier: [string, number]; } + +export interface OrderbookPath { + pools: [Orderbook, Pool] | [Pool, Orderbook]; + equalpaths: Array<[string, number]>; + identifier: [string, number]; +} + +/** + * + */ +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({ + pools: [pool, orderbook], + equalpaths: [], + identifier: [pool.LPratio + orderbook.marketId, idx], + }); + const reversedpath = identity({ + pools: [orderbook, pool], + equalpaths: [], + identifier: [orderbook.marketId + pool.LPratio, idx + 1], + }); + paths.push(path, reversedpath); + idx += 2; + } + } + } + return paths; +} diff --git a/src/core/types/modules.d.ts b/src/core/types/modules.d.ts index 5e2b86ef..a8b53c6b 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. From 39253969fdad171e8f4f65e265a8a4ae3d6b3806 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Tue, 11 Jul 2023 16:35:18 +0200 Subject: [PATCH 02/17] feat: orderbook implementation v2 --- src/chains/index.ts | 1 + src/chains/inj/index.ts | 2 + src/chains/inj/queries/getOrderbookState.ts | 51 +++++++++++++++++++ src/chains/inj/queries/initOrderbook.ts | 37 ++++++++++++++ .../chainOperator/chainAdapters/cosmjs.ts | 13 ++++- .../chainOperator/chainAdapters/injective.ts | 19 ++++++- src/core/chainOperator/chainoperator.ts | 18 +++++++ src/core/logging/logger.ts | 2 + .../interfaces/dexloopInterface.ts | 5 +- .../loops/dexMempoolSkiploop.ts | 4 +- .../arbitrageloops/loops/dexMempoolloop.ts | 9 +++- .../types/arbitrageloops/loops/dexloop.ts | 32 ++++++++++-- src/core/types/base/configs.ts | 7 ++- src/index.ts | 2 +- 14 files changed, 190 insertions(+), 12 deletions(-) create mode 100644 src/chains/inj/queries/getOrderbookState.ts create mode 100644 src/chains/inj/queries/initOrderbook.ts diff --git a/src/chains/index.ts b/src/chains/index.ts index 70bf972b..10ee0b36 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 c2adcc01..72801f23 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/queries/getOrderbookState.ts b/src/chains/inj/queries/getOrderbookState.ts new file mode 100644 index 00000000..bc2c5c23 --- /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 00000000..79b9158d --- /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/chainOperator/chainAdapters/cosmjs.ts b/src/core/chainOperator/chainAdapters/cosmjs.ts index bd60865d..4c3bb8d8 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 193793d2..974f8a4d 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"; @@ -16,8 +16,10 @@ import { MsgBroadcasterWithPk, MsgExecuteContract, MsgSend, + OrderbookWithSequence, PrivateKey, PublicKey, + SpotMarket, } from "@injectivelabs/sdk-ts"; import { ChainId } from "@injectivelabs/ts-types"; import { SkipBundleClient } from "@skip-mev/skipjs"; @@ -135,7 +137,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 +148,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); + } /** * diff --git a/src/core/chainOperator/chainoperator.ts b/src/core/chainOperator/chainoperator.ts index 87062dd9..abcbff71 100644 --- a/src/core/chainOperator/chainoperator.ts +++ b/src/core/chainOperator/chainoperator.ts @@ -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 9b5bed61..9790a6f7 100644 --- a/src/core/logging/logger.ts +++ b/src/core/logging/logger.ts @@ -85,6 +85,8 @@ 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}`; + setupMessage += `**\nOrderbook paths: ${loop.orderbookPaths.length}\n`; setupMessage += "---".repeat(30); await this._sendMessage(setupMessage, LogType.All); }, diff --git a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts index 8b8040c6..7a726e29 100644 --- a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts +++ b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts @@ -4,14 +4,17 @@ import { OptimalTrade } from "../../../arbitrage/arbitrage"; 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; diff --git a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts index 7c81dbf2..d43e4873 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts @@ -12,6 +12,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,6 +27,7 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { botConfig: DexConfig, logger: Logger | undefined, allPools: Array, + orderbooks: Array, updateState: (chainOperator: ChainOperator, pools: Array) => Promise, messageFunction: ( arbTrade: OptimalTrade, @@ -33,7 +35,7 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { flashloancontract: string, ) => [Array, number], ) { - super(chainOperator, botConfig, logger, allPools, updateState, messageFunction); + super(chainOperator, botConfig, logger, allPools, orderbooks, updateState, messageFunction); } /** diff --git a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts index fa1444ba..d41d834e 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts @@ -11,16 +11,19 @@ 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, Path } from "../../base/path"; import { removedUnusedPools, applyMempoolMessagesOnPools, Pool } from "../../base/pool"; import { DexLoopInterface } from "../interfaces/dexloopInterface"; +import { Orderbook } from "../../base/orderbook"; /** * */ 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; @@ -45,6 +48,7 @@ export class DexMempoolLoop implements DexLoopInterface { botConfig: DexConfig, logger: Logger | undefined, allPools: Array, + orderbooks: Array, updateState: (chainOperator: ChainOperator, pools: Array) => Promise, messageFunction: ( arbTrade: OptimalTrade, @@ -55,7 +59,10 @@ export class DexMempoolLoop implements DexLoopInterface { 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; diff --git a/src/core/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index 1506941b..db1b12f8 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -7,7 +7,8 @@ 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, 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; @@ -39,12 +42,17 @@ export class DexLoop implements DexLoopInterface { botConfig: DexConfig, logger: Logger | undefined, allPools: Array, + orderbooks: Array, updateState: DexLoopInterface["updateStateFunction"], messageFunction: DexLoopInterface["messageFunction"], ) { 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; @@ -67,7 +75,7 @@ export class DexLoop implements DexLoopInterface { let getFlashArbMessages = chains.defaults.getFlashArbMessages; let getPoolStates = chains.defaults.getPoolStates; let initPools = chains.defaults.initPools; - + const initOrderbook = chains.injective.initOrderbooks; await import("../../../../chains/" + botConfig.chainPrefix).then(async (chainSetups) => { if (chainSetups === undefined) { await logger.sendMessage("Unable to resolve specific chain imports, using defaults", LogType.Console); @@ -77,20 +85,36 @@ export class DexLoop implements DexLoopInterface { initPools = chainSetups.initPools; return; }); + const orderbooks: Array = []; + if (botConfig.chainPrefix === "inj") { + 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); + return new DexMempoolLoop( + chainOperator, + botConfig, + logger, + allPools, + orderbooks, + getPoolStates, + getFlashArbMessages, + ); } else if (botConfig.useMempool && botConfig.skipConfig?.useSkip) { return new DexMempoolSkipLoop( chainOperator, botConfig, logger, allPools, + orderbooks, getPoolStates, getFlashArbMessages, ); } - return new DexLoop(chainOperator, botConfig, logger, allPools, getPoolStates, getFlashArbMessages); + return new DexLoop(chainOperator, botConfig, logger, allPools, orderbooks, getPoolStates, getFlashArbMessages); } /** * diff --git a/src/core/types/base/configs.ts b/src/core/types/base/configs.ts index 8e0849f6..1e72deb7 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/index.ts b/src/index.ts index d71c2630..037e3437 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { DexLoop } from "./core/types/arbitrageloops/loops/dexloop"; import { LiquidationLoop } from "./core/types/arbitrageloops/loops/liqMempoolLoop"; import { DexConfig, LiquidationConfig, setBotConfig, SetupType } from "./core/types/base/configs"; // load env files -dotenv.config({ path: "./src/envs/terra.env" }); +dotenv.config({ path: "./src/envs/injective.env" }); /** * Runs the main program. From 3e9c0ef8e7c0113115f142be0b3333a64c545ab7 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Tue, 15 Aug 2023 13:10:29 +0200 Subject: [PATCH 03/17] feat: implement orderbook in existing loops --- src/core/arbitrage/arbitrage.ts | 59 ++++++--- .../optimizers/analyticalOptimizer.ts | 108 ---------------- .../optimizers/orderbookOptimizer.ts | 120 ++++++++++++++++++ src/core/logging/logger.ts | 4 +- .../interfaces/dexloopInterface.ts | 4 +- .../loops/dexMempoolSkiploop.ts | 4 +- .../arbitrageloops/loops/dexMempoolloop.ts | 13 +- .../types/arbitrageloops/loops/dexloop.ts | 13 +- src/core/types/base/orderbook.ts | 8 +- src/core/types/base/path.ts | 24 +++- src/node/injectiveclient.ts | 35 ----- 11 files changed, 210 insertions(+), 182 deletions(-) create mode 100644 src/core/arbitrage/optimizers/orderbookOptimizer.ts delete mode 100644 src/node/injectiveclient.ts diff --git a/src/core/arbitrage/arbitrage.ts b/src/core/arbitrage/arbitrage.ts index 67b9bcb1..d104ac3b 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())[1]; + } 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 2db12550..e281fa87 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 00000000..664784b0 --- /dev/null +++ b/src/core/arbitrage/optimizers/orderbookOptimizer.ts @@ -0,0 +1,120 @@ +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 optimalOrderbookTrade; +} + +/** + * + */ +function getOptimalTradeForPath( + path: OrderbookPath, + offerAssetInfo: AssetInfo, +): [number, Asset, number, number, number] { + let tradesizes = [...Array(800).keys()]; + tradesizes = tradesizes.map((x) => x * 1e6); + if (path.orderSequence === OrderSequence.AmmFirst) { + let optimalTradesize = 0; + let optimalProfit = 0; + let optimalOfferAsset = { amount: "0", info: offerAssetInfo }; + let optimalWorstPrice = 0; + let optimalAveragePrice = 0; + let optimalOutGivenIn = 0; + for (const ts of tradesizes) { + if (ts === 0) { + continue; + } + const offerAsset: Asset = { amount: String(ts), 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); + // console.log("ob price received: ", price, "usdt received: ", outGivenIn1); + // console.log("profit: ", outGivenIn1 - ts); + if (outGivenIn1 - ts > optimalProfit) { + (optimalTradesize = ts), + (optimalProfit = outGivenIn1 - ts), + (optimalOfferAsset = offerAsset), + (optimalWorstPrice = worstPrice), + (optimalAveragePrice = averagePrice); + optimalOutGivenIn = outGivenIn1; + } + } + return [optimalProfit, optimalOfferAsset, optimalWorstPrice, optimalAveragePrice, optimalOutGivenIn]; + } else { + let optimalTradesize = 0; + let optimalProfit = 0; + let optimalOfferAsset = { amount: "0", info: offerAssetInfo }; + let optimalWorstPrice = 0; + let optimalAveragePrice = 0; + let optimalOutGivenIn = 0; + for (const ts of tradesizes) { + if (ts === 0) { + continue; + } + const offerAsset: Asset = { amount: String(ts), 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); + if (outGivenIn1 - ts > optimalProfit) { + (optimalTradesize = ts), + (optimalProfit = outGivenIn1 - ts), + (optimalOfferAsset = offerAsset), + (optimalWorstPrice = worstPrice), + (optimalAveragePrice = averagePrice), + (optimalOutGivenIn = outGivenIn0); + } + } + return [optimalProfit, optimalOfferAsset, optimalWorstPrice, optimalAveragePrice, optimalOutGivenIn]; + } +} diff --git a/src/core/logging/logger.ts b/src/core/logging/logger.ts index 9790a6f7..8ca98b78 100644 --- a/src/core/logging/logger.ts +++ b/src/core/logging/logger.ts @@ -85,7 +85,9 @@ 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}`; + 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); diff --git a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts index 7a726e29..dd8e7650 100644 --- a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts +++ b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts @@ -1,6 +1,7 @@ import { EncodeObject } from "@cosmjs/proto-signing"; import { OptimalTrade } from "../../../arbitrage/arbitrage"; +import { OptimalOrderbookTrade } from "../../../arbitrage/optimizers/orderbookOptimizer"; import { ChainOperator } from "../../../chainOperator/chainoperator"; import { Logger } from "../../../logging"; import { DexConfig } from "../../base/configs"; @@ -27,7 +28,8 @@ export interface DexLoopInterface { /** * */ - arbitrageFunction: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; updateStateFunction: (chainOperator: ChainOperator, pools: Array) => Promise; messageFunction: ( arbTrade: OptimalTrade, diff --git a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts index d43e4873..6a6b89ea 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts @@ -43,7 +43,7 @@ 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); @@ -73,7 +73,7 @@ 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); this.cdPaths(arbTrade.path); diff --git a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts index d41d834e..71da92ee 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts @@ -5,7 +5,7 @@ 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"; @@ -15,6 +15,7 @@ import { getOrderbookAmmPaths, OrderbookPath, 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"; /** * @@ -34,7 +35,8 @@ export class DexMempoolLoop implements DexLoopInterface { iterations = 0; updateStateFunction: DexLoopInterface["updateStateFunction"]; messageFunction: DexLoopInterface["messageFunction"]; - arbitrageFunction: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; // CACHE VALUES totalBytes = 0; mempool!: Mempool; @@ -66,7 +68,8 @@ export class DexMempoolLoop implements DexLoopInterface { this.CDpaths = new Map(); this.paths = paths; this.pathlib = paths; - this.arbitrageFunction = trySomeArb; + this.ammArb = tryAmmArb; + this.orderbookArb = tryOrderbookArb; this.updateStateFunction = updateState; this.messageFunction = messageFunction; this.chainOperator = chainOperator; @@ -81,7 +84,7 @@ export class DexMempoolLoop implements DexLoopInterface { this.iterations++; await this.updateStateFunction(this.chainOperator, this.pools); - 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); @@ -114,7 +117,7 @@ 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); if (arbTrade) { await this.trade(arbTrade); diff --git a/src/core/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index db1b12f8..3f4283a4 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -1,8 +1,9 @@ import { inspect } from "util"; import * as chains from "../../../../chains"; -import { OptimalTrade, trySomeArb } from "../../../arbitrage/arbitrage"; +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"; @@ -32,7 +33,8 @@ export class DexLoop implements DexLoopInterface { iterations = 0; updateStateFunction: DexLoopInterface["updateStateFunction"]; messageFunction: DexLoopInterface["messageFunction"]; - arbitrageFunction: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; + orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; /** * @@ -57,7 +59,8 @@ export class DexLoop implements DexLoopInterface { this.CDpaths = new Map(); this.paths = paths; this.pathlib = paths; - this.arbitrageFunction = trySomeArb; + this.ammArb = tryAmmArb; + this.orderbookArb = tryOrderbookArb; this.updateStateFunction = updateState; this.messageFunction = messageFunction; this.chainOperator = chainOperator; @@ -122,8 +125,8 @@ 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 })); diff --git a/src/core/types/base/orderbook.ts b/src/core/types/base/orderbook.ts index fbd34c29..b9970bbf 100644 --- a/src/core/types/base/orderbook.ts +++ b/src/core/types/base/orderbook.ts @@ -1,4 +1,4 @@ -import { Asset, AssetInfo } from "./asset"; +import { Asset, NativeAssetInfo } from "./asset"; export interface Order { price: number; @@ -7,8 +7,8 @@ export interface Order { } export interface Orderbook { marketId: string; //contractaddress or marketid - baseAssetInfo: AssetInfo; - quoteAssetInfo: AssetInfo; + baseAssetInfo: NativeAssetInfo; + quoteAssetInfo: NativeAssetInfo; baseAssetDecimals: number; quoteAssetDecimals: number; minQuantityIncrement: number; @@ -24,7 +24,7 @@ export interface Orderbook { */ export function OrderbookMarketSell(orderbook: Orderbook, offerAsset: Asset) { //we are selling the base asset - let rest = +offerAsset.amount; + let rest = Math.floor(+offerAsset.amount); let result = 0; let buyIndex = 0; while (rest > 0) { diff --git a/src/core/types/base/path.ts b/src/core/types/base/path.ts index 36bed2bb..2578d474 100644 --- a/src/core/types/base/path.ts +++ b/src/core/types/base/path.ts @@ -9,12 +9,24 @@ export interface Path { identifier: [string, number]; } +export enum OrderSequence { + AmmFirst, + OrderbookFirst, +} + export interface OrderbookPath { - pools: [Orderbook, Pool] | [Pool, Orderbook]; + 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; +} /** * */ @@ -30,12 +42,16 @@ export function getOrderbookAmmPaths(pools: Array, orderbooks: Array({ - pools: [pool, orderbook], + pool: pool, + orderbook: orderbook, + orderSequence: OrderSequence.AmmFirst, equalpaths: [], identifier: [pool.LPratio + orderbook.marketId, idx], }); const reversedpath = identity({ - pools: [orderbook, pool], + pool: pool, + orderbook: orderbook, + orderSequence: OrderSequence.OrderbookFirst, equalpaths: [], identifier: [orderbook.marketId + pool.LPratio, idx + 1], }); diff --git a/src/node/injectiveclient.ts b/src/node/injectiveclient.ts deleted file mode 100644 index eeba6f19..00000000 --- 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 }; -} From b3028dd6956f11587bde461bf7473f4969977f53 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Tue, 15 Aug 2023 13:40:50 +0200 Subject: [PATCH 04/17] feat: add orderbook state update to loop --- .../interfaces/dexloopInterface.ts | 3 +- .../loops/dexMempoolSkiploop.ts | 13 +++++- .../arbitrageloops/loops/dexMempoolloop.ts | 12 ++++-- .../types/arbitrageloops/loops/dexloop.ts | 41 +++++++++++++++---- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts index dd8e7650..516640b1 100644 --- a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts +++ b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts @@ -30,7 +30,8 @@ export interface DexLoopInterface { */ ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; - updateStateFunction: (chainOperator: ChainOperator, pools: Array) => Promise; + updatePoolStates: (chainOperator: ChainOperator, pools: Array) => Promise; + updateOrderbookStates?: (chainOperator: ChainOperator, orderbooks: Array) => Promise; messageFunction: ( arbTrade: OptimalTrade, walletAddress: string, diff --git a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts index 6a6b89ea..1bb3dcc9 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts @@ -29,13 +29,24 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { allPools: Array, orderbooks: Array, updateState: (chainOperator: ChainOperator, pools: Array) => Promise, + messageFunction: ( arbTrade: OptimalTrade, walletAddress: string, flashloancontract: string, ) => [Array, number], + updateOrderbookStates?: (chainOperator: ChainOperator, orderbooks: Array) => Promise, ) { - super(chainOperator, botConfig, logger, allPools, orderbooks, updateState, messageFunction); + super( + chainOperator, + botConfig, + logger, + allPools, + orderbooks, + updateState, + messageFunction, + updateOrderbookStates, + ); } /** diff --git a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts index 71da92ee..d75a8e2a 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts @@ -33,7 +33,8 @@ export class DexMempoolLoop implements DexLoopInterface { botConfig: DexConfig; logger: Logger | undefined; iterations = 0; - updateStateFunction: DexLoopInterface["updateStateFunction"]; + updatePoolStates: DexLoopInterface["updatePoolStates"]; + updateOrderbookStates?: (chainOperator: ChainOperator, orderbooks: Array) => Promise; messageFunction: DexLoopInterface["messageFunction"]; ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; @@ -57,6 +58,7 @@ export class DexMempoolLoop implements DexLoopInterface { walletAddress: string, flashloancontract: string, ) => [Array, number], + updateOrderbookStates?: DexLoopInterface["updateOrderbookStates"], ) { const graph = newGraph(allPools); const paths = getPaths(graph, botConfig.offerAssetInfo, botConfig.maxPathPools) ?? []; @@ -70,7 +72,8 @@ export class DexMempoolLoop implements DexLoopInterface { this.pathlib = paths; this.ammArb = tryAmmArb; this.orderbookArb = tryOrderbookArb; - this.updateStateFunction = updateState; + this.updateOrderbookStates = updateOrderbookStates; + this.updatePoolStates = updateState; this.messageFunction = messageFunction; this.chainOperator = chainOperator; this.botConfig = botConfig; @@ -82,7 +85,10 @@ 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.ammArb(this.paths, this.botConfig); diff --git a/src/core/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index 3f4283a4..859ed577 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -31,7 +31,8 @@ export class DexLoop implements DexLoopInterface { botConfig: DexConfig; logger: Logger | undefined; iterations = 0; - updateStateFunction: DexLoopInterface["updateStateFunction"]; + updatePoolStates: DexLoopInterface["updatePoolStates"]; + updateOrderbookStates?: DexLoopInterface["updateOrderbookStates"]; messageFunction: DexLoopInterface["messageFunction"]; ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; @@ -45,8 +46,9 @@ export class DexLoop implements DexLoopInterface { logger: Logger | undefined, allPools: Array, orderbooks: Array, - updateState: DexLoopInterface["updateStateFunction"], + updatePoolStates: DexLoopInterface["updatePoolStates"], messageFunction: DexLoopInterface["messageFunction"], + updateOrderbookStates?: DexLoopInterface["updateOrderbookStates"], ) { const graph = newGraph(allPools); const paths = getPaths(graph, botConfig.offerAssetInfo, botConfig.maxPathPools) ?? []; @@ -61,7 +63,8 @@ export class DexLoop implements DexLoopInterface { this.pathlib = paths; this.ammArb = tryAmmArb; this.orderbookArb = tryOrderbookArb; - this.updateStateFunction = updateState; + this.updatePoolStates = updatePoolStates; + this.updateOrderbookStates = updateOrderbookStates; this.messageFunction = messageFunction; this.chainOperator = chainOperator; this.botConfig = botConfig; @@ -79,6 +82,7 @@ export class DexLoop implements DexLoopInterface { 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); @@ -89,7 +93,7 @@ export class DexLoop implements DexLoopInterface { return; }); const orderbooks: Array = []; - if (botConfig.chainPrefix === "inj") { + if (botConfig.chainPrefix === "inj" && botConfig.orderbooks.length > 0) { const obs = await initOrderbook(chainOperator, botConfig); if (obs) { orderbooks.push(...obs); @@ -105,6 +109,7 @@ export class DexLoop implements DexLoopInterface { orderbooks, getPoolStates, getFlashArbMessages, + getOrderbookState, ); } else if (botConfig.useMempool && botConfig.skipConfig?.useSkip) { return new DexMempoolSkipLoop( @@ -115,9 +120,19 @@ export class DexLoop implements DexLoopInterface { orderbooks, getPoolStates, getFlashArbMessages, + getOrderbookState, ); } - return new DexLoop(chainOperator, botConfig, logger, allPools, orderbooks, getPoolStates, getFlashArbMessages); + return new DexLoop( + chainOperator, + botConfig, + logger, + allPools, + orderbooks, + getPoolStates, + getFlashArbMessages, + getOrderbookState, + ); } /** * @@ -135,6 +150,15 @@ export class DexLoop implements DexLoopInterface { this.cdPaths(arbTrade.path); await this.chainOperator.reset(); } + if (arbtradeOB) { + console.log(inspect(arbtradeOB.path.pool, { showHidden: true, depth: 2, colors: true })); + console.log(inspect(arbtradeOB.path.orderbook, { showHidden: true, depth: 2, colors: true })); + console.log(inspect(arbtradeOB.offerAsset, { showHidden: true, depth: 3, colors: true })); + console.log("expected profit: ", arbtradeOB.profit); + // await this.trade(arbtradeOB); + this.cdPaths(arbtradeOB.path); + await this.chainOperator.reset(); + } await delay(1500); } @@ -144,7 +168,10 @@ 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); + } } /** @@ -167,7 +194,7 @@ export class DexLoop implements DexLoopInterface { * 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]]); From 7cc99cdda7082c7669162845aaf59ad0f8ae8cb4 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Wed, 16 Aug 2023 14:31:07 +0200 Subject: [PATCH 05/17] feat: add swap message to defaults --- .../defaults/messages/getSwapMessage.ts | 40 +++++++++++++++++++ src/core/arbitrage/arbitrage.ts | 2 +- .../optimizers/orderbookOptimizer.ts | 2 +- 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 src/chains/defaults/messages/getSwapMessage.ts diff --git a/src/chains/defaults/messages/getSwapMessage.ts b/src/chains/defaults/messages/getSwapMessage.ts new file mode 100644 index 00000000..9394f7af --- /dev/null +++ b/src/chains/defaults/messages/getSwapMessage.ts @@ -0,0 +1,40 @@ +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"; +import { DefaultSwapMessage } from "../../../core/types/messages/swapmessages"; + +/** + * + */ +export function getSwapMessage( + pool: Pool, + offerAsset: Asset, + walletAddress: string, + beliefPrice: string, + maxSpread = 0.05, +) { + const msg: DefaultSwapMessage = { + 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, + contractAddress: pool.address, + msg: msg, + }), + }; + return encodedMsgObject; +} diff --git a/src/core/arbitrage/arbitrage.ts b/src/core/arbitrage/arbitrage.ts index d104ac3b..b781ba0a 100644 --- a/src/core/arbitrage/arbitrage.ts +++ b/src/core/arbitrage/arbitrage.ts @@ -65,7 +65,7 @@ export function tryLiquidationArb( */ function isAboveThreshold(botConfig: DexConfig, optimalTrade: OptimalTrade | OptimalOrderbookTrade): boolean { if (isOrderbookPath(optimalTrade.path)) { - return optimalTrade.profit >= Array.from(botConfig.profitThresholds.values())[1]; + 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 = diff --git a/src/core/arbitrage/optimizers/orderbookOptimizer.ts b/src/core/arbitrage/optimizers/orderbookOptimizer.ts index 664784b0..e6cdfeef 100644 --- a/src/core/arbitrage/optimizers/orderbookOptimizer.ts +++ b/src/core/arbitrage/optimizers/orderbookOptimizer.ts @@ -50,7 +50,7 @@ export function getOptimalTrade( } } - return optimalOrderbookTrade; + return optimalProfit > 0 ? optimalOrderbookTrade : undefined; } /** From e14062426002e01c2f569b1251f21258eb396152 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Thu, 17 Aug 2023 16:47:05 +0200 Subject: [PATCH 06/17] feat: add spotorder messages --- .../defaults/messages/getSwapMessage.ts | 5 +- .../inj/messages/getOrderbookArbMessage.ts | 57 +++++++++++++++++++ .../inj/messages/getSpotOrderMessage.ts | 48 ++++++++++++++++ .../chainOperator/chainAdapters/injective.ts | 6 +- .../types/arbitrageloops/loops/dexloop.ts | 18 +++++- src/core/types/messages/spotorders.ts | 25 ++++++++ 6 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 src/chains/inj/messages/getOrderbookArbMessage.ts create mode 100644 src/chains/inj/messages/getSpotOrderMessage.ts create mode 100644 src/core/types/messages/spotorders.ts diff --git a/src/chains/defaults/messages/getSwapMessage.ts b/src/chains/defaults/messages/getSwapMessage.ts index 9394f7af..7bf38a15 100644 --- a/src/chains/defaults/messages/getSwapMessage.ts +++ b/src/chains/defaults/messages/getSwapMessage.ts @@ -1,3 +1,4 @@ +import { toUtf8 } from "@cosmjs/encoding"; import { EncodeObject } from "@cosmjs/proto-signing"; import { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx"; @@ -32,8 +33,8 @@ export function getSwapMessage( amount: offerAsset.amount, }, sender: walletAddress, - contractAddress: pool.address, - msg: msg, + contract: pool.address, + msg: toUtf8(JSON.stringify(msg)), }), }; return encodedMsgObject; diff --git a/src/chains/inj/messages/getOrderbookArbMessage.ts b/src/chains/inj/messages/getOrderbookArbMessage.ts new file mode 100644 index 00000000..2db2317c --- /dev/null +++ b/src/chains/inj/messages/getOrderbookArbMessage.ts @@ -0,0 +1,57 @@ +import { BigNumberInBase } from "@injectivelabs/utils/dist/cjs/classes"; + +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) { + 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]; + } else { + const offerAsset1 = { + amount: String(arbTrade.outGivenIn), + info: arbTrade.path.orderbook.baseAssetInfo, + }; + const msg0 = getMarketSpotOrderMessage(arbTrade, publicAddress, offerAsset1, 1); + + const decimals = arbTrade.path.orderbook.baseAssetDecimals - arbTrade.path.orderbook.quoteAssetDecimals; + + let orderSize = +new BigNumberInBase(arbTrade.outGivenIn).toWei(decimals).toFixed(); + + const belief_price = String( + Math.round((orderSize / arbTrade.outGivenIn) * 100000 * (10 ^ decimals)) / 100000 / (10 ^ decimals), + ); + orderSize = + Math.floor(orderSize / arbTrade.path.orderbook.minQuantityIncrement) * + arbTrade.path.orderbook.minQuantityIncrement; + console.log(orderSize); + + const offerAsset = { + amount: String(orderSize), + info: offerAsset1.info, + }; + const msg1 = getSwapMessage(arbTrade.path.pool, offerAsset, publicAddress, belief_price); + + return [msg0, msg1]; + } +} diff --git a/src/chains/inj/messages/getSpotOrderMessage.ts b/src/chains/inj/messages/getSpotOrderMessage.ts new file mode 100644 index 00000000..c499008a --- /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/core/chainOperator/chainAdapters/injective.ts b/src/core/chainOperator/chainAdapters/injective.ts index 974f8a4d..67463906 100644 --- a/src/core/chainOperator/chainAdapters/injective.ts +++ b/src/core/chainOperator/chainAdapters/injective.ts @@ -14,6 +14,7 @@ import { createTransaction, IndexerGrpcSpotApi, MsgBroadcasterWithPk, + MsgCreateSpotMarketOrder, MsgExecuteContract, MsgSend, OrderbookWithSequence, @@ -274,7 +275,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; @@ -324,6 +325,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/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index 859ed577..ea921c7a 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -1,6 +1,7 @@ import { inspect } from "util"; import * as chains from "../../../../chains"; +import { getOrderbookArbMessages } from "../../../../chains/inj/messages/getOrderbookArbMessage"; import { OptimalTrade, tryAmmArb, tryOrderbookArb } from "../../../arbitrage/arbitrage"; import { getPaths, newGraph } from "../../../arbitrage/graph"; import { OptimalOrderbookTrade } from "../../../arbitrage/optimizers/orderbookOptimizer"; @@ -9,7 +10,7 @@ import { Logger } from "../../../logging"; import { DexConfig } from "../../base/configs"; import { LogType } from "../../base/logging"; import { Orderbook } from "../../base/orderbook"; -import { getOrderbookAmmPaths, OrderbookPath, Path } from "../../base/path"; +import { getOrderbookAmmPaths, OrderbookPath, OrderSequence, Path } from "../../base/path"; import { Pool, removedUnusedPools } from "../../base/pool"; import { DexLoopInterface } from "../interfaces/dexloopInterface"; import { DexMempoolLoop } from "./dexMempoolloop"; @@ -152,10 +153,21 @@ export class DexLoop implements DexLoopInterface { } if (arbtradeOB) { console.log(inspect(arbtradeOB.path.pool, { showHidden: true, depth: 2, colors: true })); - console.log(inspect(arbtradeOB.path.orderbook, { showHidden: true, depth: 2, colors: true })); + console.log(inspect(arbtradeOB.path.orderbook, { showHidden: true, depth: 1, colors: true })); console.log(inspect(arbtradeOB.offerAsset, { showHidden: true, depth: 3, colors: true })); console.log("expected profit: ", arbtradeOB.profit); - // await this.trade(arbtradeOB); + + const msgs = getOrderbookArbMessages(arbtradeOB, this.chainOperator.client.publicAddress); + console.log(msgs[0], msgs[1]); + if (arbtradeOB.path.orderSequence === OrderSequence.AmmFirst) { + const txResponse = await this.chainOperator.signAndBroadcast(msgs); + console.log(txResponse); + } else { + const txResponse = await this.chainOperator.signAndBroadcast([msgs[0]]); + console.log(txResponse); + const txResponse2 = await this.chainOperator.signAndBroadcast([msgs[1]]); + console.log(txResponse2); + } this.cdPaths(arbtradeOB.path); await this.chainOperator.reset(); } diff --git a/src/core/types/messages/spotorders.ts b/src/core/types/messages/spotorders.ts new file mode 100644 index 00000000..f75d3d89 --- /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; +}; From 643d16288b26a80efb9ffafb9c873eb1d51c5850 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Thu, 17 Aug 2023 16:59:05 +0200 Subject: [PATCH 07/17] bug: funds field must be array --- src/chains/defaults/messages/getSwapMessage.ts | 14 ++++++++------ src/core/chainOperator/chainAdapters/injective.ts | 1 - 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/chains/defaults/messages/getSwapMessage.ts b/src/chains/defaults/messages/getSwapMessage.ts index 7bf38a15..fecea9b7 100644 --- a/src/chains/defaults/messages/getSwapMessage.ts +++ b/src/chains/defaults/messages/getSwapMessage.ts @@ -26,12 +26,14 @@ export function getSwapMessage( 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, - }, + 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)), diff --git a/src/core/chainOperator/chainAdapters/injective.ts b/src/core/chainOperator/chainAdapters/injective.ts index 67463906..02fcf181 100644 --- a/src/core/chainOperator/chainAdapters/injective.ts +++ b/src/core/chainOperator/chainAdapters/injective.ts @@ -188,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, From 474c4edaff5604b1682df03871cdd0fa28c08e63 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Mon, 21 Aug 2023 15:30:37 +0200 Subject: [PATCH 08/17] bug: add small delay when sending two transactions --- .../inj/messages/getOrderbookArbMessage.ts | 1 - .../types/arbitrageloops/loops/dexloop.ts | 33 ++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/chains/inj/messages/getOrderbookArbMessage.ts b/src/chains/inj/messages/getOrderbookArbMessage.ts index 2db2317c..958ac2cb 100644 --- a/src/chains/inj/messages/getOrderbookArbMessage.ts +++ b/src/chains/inj/messages/getOrderbookArbMessage.ts @@ -44,7 +44,6 @@ export function getOrderbookArbMessages(arbTrade: OptimalOrderbookTrade, publicA orderSize = Math.floor(orderSize / arbTrade.path.orderbook.minQuantityIncrement) * arbTrade.path.orderbook.minQuantityIncrement; - console.log(orderSize); const offerAsset = { amount: String(orderSize), diff --git a/src/core/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index ea921c7a..f43805df 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -7,6 +7,7 @@ import { getPaths, newGraph } from "../../../arbitrage/graph"; import { OptimalOrderbookTrade } from "../../../arbitrage/optimizers/orderbookOptimizer"; import { ChainOperator } from "../../../chainOperator/chainoperator"; import { Logger } from "../../../logging"; +import { NativeAssetInfo } from "../../base/asset"; import { DexConfig } from "../../base/configs"; import { LogType } from "../../base/logging"; import { Orderbook } from "../../base/orderbook"; @@ -152,19 +153,41 @@ export class DexLoop implements DexLoopInterface { await this.chainOperator.reset(); } if (arbtradeOB) { - console.log(inspect(arbtradeOB.path.pool, { showHidden: true, depth: 2, colors: true })); - console.log(inspect(arbtradeOB.path.orderbook, { showHidden: true, depth: 1, colors: true })); - console.log(inspect(arbtradeOB.offerAsset, { showHidden: true, depth: 3, colors: true })); - console.log("expected profit: ", arbtradeOB.profit); + console.log("-".repeat(39), "Orderbook Arb", "-".repeat(38)); + if (arbtradeOB.path.orderSequence === OrderSequence.AmmFirst) { + console.log( + `Pool: ${arbtradeOB.path.pool.address}: ${ + (arbtradeOB.path.pool.assets[0].info).native_token.denom + }/${(arbtradeOB.path.pool.assets[1].info).native_token.denom}`, + ); + console.log( + `Orderbook: ${ + (arbtradeOB.path.orderbook.baseAssetInfo).native_token.denom + } / USDT`, + ); + } else { + console.log( + `Orderbook: ${ + (arbtradeOB.path.orderbook.baseAssetInfo).native_token.denom + } / USDT`, + ); + console.log( + `Pool: ${arbtradeOB.path.pool.address}: ${ + (arbtradeOB.path.pool.assets[0].info).native_token.denom + }/${(arbtradeOB.path.pool.assets[1].info).native_token.denom}`, + ); + } + console.log(`Offering: ${inspect(arbtradeOB.offerAsset, true, null, true)}`); + console.log("Expected profit: ", arbtradeOB.profit); const msgs = getOrderbookArbMessages(arbtradeOB, this.chainOperator.client.publicAddress); - console.log(msgs[0], msgs[1]); if (arbtradeOB.path.orderSequence === OrderSequence.AmmFirst) { const txResponse = await this.chainOperator.signAndBroadcast(msgs); console.log(txResponse); } else { const txResponse = await this.chainOperator.signAndBroadcast([msgs[0]]); console.log(txResponse); + await delay(2000); const txResponse2 = await this.chainOperator.signAndBroadcast([msgs[1]]); console.log(txResponse2); } From 996f75337354833046e8bcc90bbfbb668008356d Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Tue, 22 Aug 2023 13:08:25 +0200 Subject: [PATCH 09/17] chore: put logging of orderbook trade in logger --- src/core/logging/logger.ts | 47 ++++++++++++++++++- .../types/arbitrageloops/loops/dexloop.ts | 33 +------------ src/index.ts | 6 +-- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/core/logging/logger.ts b/src/core/logging/logger.ts index 8ca98b78..463297d1 100644 --- a/src/core/logging/logger.ts +++ b/src/core/logging/logger.ts @@ -1,8 +1,14 @@ +import { inspect } from "util"; + +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 { 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 +51,8 @@ export class Logger { ); } } - public defaults = { + + public loopLogging = { /** * */ @@ -115,6 +122,44 @@ 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); + }, + }; /** * 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/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index f43805df..a43fb77a 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -7,7 +7,6 @@ import { getPaths, newGraph } from "../../../arbitrage/graph"; import { OptimalOrderbookTrade } from "../../../arbitrage/optimizers/orderbookOptimizer"; import { ChainOperator } from "../../../chainOperator/chainoperator"; import { Logger } from "../../../logging"; -import { NativeAssetInfo } from "../../base/asset"; import { DexConfig } from "../../base/configs"; import { LogType } from "../../base/logging"; import { Orderbook } from "../../base/orderbook"; @@ -153,43 +152,15 @@ export class DexLoop implements DexLoopInterface { await this.chainOperator.reset(); } if (arbtradeOB) { - console.log("-".repeat(39), "Orderbook Arb", "-".repeat(38)); - if (arbtradeOB.path.orderSequence === OrderSequence.AmmFirst) { - console.log( - `Pool: ${arbtradeOB.path.pool.address}: ${ - (arbtradeOB.path.pool.assets[0].info).native_token.denom - }/${(arbtradeOB.path.pool.assets[1].info).native_token.denom}`, - ); - console.log( - `Orderbook: ${ - (arbtradeOB.path.orderbook.baseAssetInfo).native_token.denom - } / USDT`, - ); - } else { - console.log( - `Orderbook: ${ - (arbtradeOB.path.orderbook.baseAssetInfo).native_token.denom - } / USDT`, - ); - console.log( - `Pool: ${arbtradeOB.path.pool.address}: ${ - (arbtradeOB.path.pool.assets[0].info).native_token.denom - }/${(arbtradeOB.path.pool.assets[1].info).native_token.denom}`, - ); - } - console.log(`Offering: ${inspect(arbtradeOB.offerAsset, true, null, true)}`); - console.log("Expected profit: ", arbtradeOB.profit); - const msgs = getOrderbookArbMessages(arbtradeOB, this.chainOperator.client.publicAddress); if (arbtradeOB.path.orderSequence === OrderSequence.AmmFirst) { const txResponse = await this.chainOperator.signAndBroadcast(msgs); - console.log(txResponse); + await this.logger?.tradeLogging.logOrderbookTrade(arbtradeOB, [txResponse]); } else { const txResponse = await this.chainOperator.signAndBroadcast([msgs[0]]); - console.log(txResponse); await delay(2000); const txResponse2 = await this.chainOperator.signAndBroadcast([msgs[1]]); - console.log(txResponse2); + await this.logger?.tradeLogging.logOrderbookTrade(arbtradeOB, [txResponse, txResponse2]); } this.cdPaths(arbtradeOB.path); await this.chainOperator.reset(); diff --git a/src/index.ts b/src/index.ts index 037e3437..b1711293 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; } From 44aefaf421e8a9cce11f12cff0ea350cf1026fa7 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Tue, 22 Aug 2023 15:48:51 +0200 Subject: [PATCH 10/17] feat: introduce message factory --- src/chains/defaults/index.ts | 2 +- .../defaults/messages/messageFactory.ts | 22 +++++ .../inj/messages/getOrderbookArbMessage.ts | 10 ++- src/core/logging/logger.ts | 29 +++++- .../interfaces/dexloopInterface.ts | 9 +- .../loops/dexMempoolSkiploop.ts | 27 +++--- .../arbitrageloops/loops/dexMempoolloop.ts | 27 +++--- .../types/arbitrageloops/loops/dexloop.ts | 89 ++++++++++--------- 8 files changed, 136 insertions(+), 79 deletions(-) create mode 100644 src/chains/defaults/messages/messageFactory.ts diff --git a/src/chains/defaults/index.ts b/src/chains/defaults/index.ts index bf776f0d..59b04448 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/messageFactory.ts b/src/chains/defaults/messages/messageFactory.ts new file mode 100644 index 00000000..6e1b35a1 --- /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/inj/messages/getOrderbookArbMessage.ts b/src/chains/inj/messages/getOrderbookArbMessage.ts index 958ac2cb..0ac39479 100644 --- a/src/chains/inj/messages/getOrderbookArbMessage.ts +++ b/src/chains/inj/messages/getOrderbookArbMessage.ts @@ -1,3 +1,4 @@ +import { EncodeObject } from "@cosmjs/proto-signing"; import { BigNumberInBase } from "@injectivelabs/utils/dist/cjs/classes"; import { OptimalOrderbookTrade } from "../../../core/arbitrage/optimizers/orderbookOptimizer"; @@ -10,7 +11,10 @@ import { getMarketSpotOrderMessage } from "./getSpotOrderMessage"; /** * */ -export function getOrderbookArbMessages(arbTrade: OptimalOrderbookTrade, publicAddress: string) { +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); @@ -26,7 +30,7 @@ export function getOrderbookArbMessages(arbTrade: OptimalOrderbookTrade, publicA const msg1 = getMarketSpotOrderMessage(arbTrade, publicAddress, offerAsset1, 2); - return [msg0, msg1]; + return [[msg0, msg1], 2]; } else { const offerAsset1 = { amount: String(arbTrade.outGivenIn), @@ -51,6 +55,6 @@ export function getOrderbookArbMessages(arbTrade: OptimalOrderbookTrade, publicA }; const msg1 = getSwapMessage(arbTrade.path.pool, offerAsset, publicAddress, belief_price); - return [msg0, msg1]; + return [[msg0, msg1], 2]; } } diff --git a/src/core/logging/logger.ts b/src/core/logging/logger.ts index 463297d1..033d1019 100644 --- a/src/core/logging/logger.ts +++ b/src/core/logging/logger.ts @@ -1,10 +1,11 @@ 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 { NativeAssetInfo } from "../types/base/asset"; +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"; @@ -159,6 +160,32 @@ Min Risk ratio: ${maxRisk[maxRisk.length - 1].riskRatio.toPrecision(3)}`; } 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. diff --git a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts index 516640b1..e939ba9b 100644 --- a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts +++ b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts @@ -1,5 +1,4 @@ -import { EncodeObject } from "@cosmjs/proto-signing"; - +import { messageFactory } from "../../../../chains/defaults/messages/messageFactory"; import { OptimalTrade } from "../../../arbitrage/arbitrage"; import { OptimalOrderbookTrade } from "../../../arbitrage/optimizers/orderbookOptimizer"; import { ChainOperator } from "../../../chainOperator/chainoperator"; @@ -32,11 +31,7 @@ export interface DexLoopInterface { orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; updatePoolStates: (chainOperator: ChainOperator, pools: Array) => Promise; updateOrderbookStates?: (chainOperator: ChainOperator, orderbooks: Array) => Promise; - messageFunction: ( - arbTrade: OptimalTrade, - walletAddress: string, - flashloancontract: string, - ) => [Array, number]; + messageFactory: typeof messageFactory; step: () => Promise; reset: () => Promise; clearIgnoreAddresses: () => void; diff --git a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts index 1bb3dcc9..f47b103e 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"; @@ -30,11 +29,7 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { 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( @@ -44,7 +39,7 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { allPools, orderbooks, updateState, - messageFunction, + messageFactory, updateOrderbookStates, ); } @@ -121,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 })); diff --git a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts index d75a8e2a..15b397af 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts @@ -2,14 +2,11 @@ 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, 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 { getOrderbookAmmPaths, OrderbookPath, Path } from "../../base/path"; import { removedUnusedPools, applyMempoolMessagesOnPools, Pool } from "../../base/pool"; @@ -35,7 +32,7 @@ export class DexMempoolLoop implements DexLoopInterface { iterations = 0; updatePoolStates: DexLoopInterface["updatePoolStates"]; updateOrderbookStates?: (chainOperator: ChainOperator, orderbooks: Array) => Promise; - messageFunction: DexLoopInterface["messageFunction"]; + messageFactory: DexLoopInterface["messageFactory"]; ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; // CACHE VALUES @@ -53,11 +50,7 @@ export class DexMempoolLoop implements DexLoopInterface { 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); @@ -74,7 +67,7 @@ export class DexMempoolLoop implements DexLoopInterface { this.orderbookArb = tryOrderbookArb; this.updateOrderbookStates = updateOrderbookStates; this.updatePoolStates = updateState; - this.messageFunction = messageFunction; + this.messageFactory = messageFactory; this.chainOperator = chainOperator; this.botConfig = botConfig; this.logger = logger; @@ -152,21 +145,25 @@ export class DexMempoolLoop implements DexLoopInterface { * */ public async trade(arbTrade: OptimalTrade) { - const [msgs, nrOfMessages] = this.messageFunction( + const messages = this.messageFactory( arbTrade, this.chainOperator.client.publicAddress, this.botConfig.flashloanRouterAddress, ); + if (!messages) { + console.error("error in creating messages", 1); + process.exit(1); + } // await this.logger?.sendMessage(JSON.stringify(msgs), LogType.Console); 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); + const txResponse = await this.chainOperator.signAndBroadcast(messages[0], TX_FEE); + + await this.logger?.tradeLogging.logAmmTrade(arbTrade, [txResponse]); if (txResponse.code === 0) { this.chainOperator.client.sequence = this.chainOperator.client.sequence + 1; diff --git a/src/core/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index a43fb77a..51bc7393 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -1,7 +1,5 @@ -import { inspect } from "util"; - import * as chains from "../../../../chains"; -import { getOrderbookArbMessages } from "../../../../chains/inj/messages/getOrderbookArbMessage"; +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"; @@ -10,7 +8,7 @@ import { Logger } from "../../../logging"; import { DexConfig } from "../../base/configs"; import { LogType } from "../../base/logging"; import { Orderbook } from "../../base/orderbook"; -import { getOrderbookAmmPaths, OrderbookPath, OrderSequence, Path } from "../../base/path"; +import { getOrderbookAmmPaths, isOrderbookPath, OrderbookPath, OrderSequence, Path } from "../../base/path"; import { Pool, removedUnusedPools } from "../../base/pool"; import { DexLoopInterface } from "../interfaces/dexloopInterface"; import { DexMempoolLoop } from "./dexMempoolloop"; @@ -34,7 +32,7 @@ export class DexLoop implements DexLoopInterface { iterations = 0; updatePoolStates: DexLoopInterface["updatePoolStates"]; updateOrderbookStates?: DexLoopInterface["updateOrderbookStates"]; - messageFunction: DexLoopInterface["messageFunction"]; + messageFactory: DexLoopInterface["messageFactory"]; ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; @@ -48,7 +46,7 @@ export class DexLoop implements DexLoopInterface { allPools: Array, orderbooks: Array, updatePoolStates: DexLoopInterface["updatePoolStates"], - messageFunction: DexLoopInterface["messageFunction"], + messageFactory: DexLoopInterface["messageFactory"], updateOrderbookStates?: DexLoopInterface["updateOrderbookStates"], ) { const graph = newGraph(allPools); @@ -66,7 +64,7 @@ export class DexLoop implements DexLoopInterface { this.orderbookArb = tryOrderbookArb; this.updatePoolStates = updatePoolStates; this.updateOrderbookStates = updateOrderbookStates; - this.messageFunction = messageFunction; + this.messageFactory = messageFactory; this.chainOperator = chainOperator; this.botConfig = botConfig; this.logger = logger; @@ -79,7 +77,7 @@ export class DexLoop implements DexLoopInterface { botConfig: DexConfig, logger: Logger, ): Promise { - let getFlashArbMessages = chains.defaults.getFlashArbMessages; + let msgFactory = chains.defaults.messageFactory; let getPoolStates = chains.defaults.getPoolStates; let initPools = chains.defaults.initPools; const initOrderbook = chains.injective.initOrderbooks; @@ -88,7 +86,7 @@ export class DexLoop implements DexLoopInterface { 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; @@ -109,7 +107,7 @@ export class DexLoop implements DexLoopInterface { allPools, orderbooks, getPoolStates, - getFlashArbMessages, + msgFactory, getOrderbookState, ); } else if (botConfig.useMempool && botConfig.skipConfig?.useSkip) { @@ -120,7 +118,7 @@ export class DexLoop implements DexLoopInterface { allPools, orderbooks, getPoolStates, - getFlashArbMessages, + msgFactory, getOrderbookState, ); } @@ -131,7 +129,7 @@ export class DexLoop implements DexLoopInterface { allPools, orderbooks, getPoolStates, - getFlashArbMessages, + messageFactory, getOrderbookState, ); } @@ -143,26 +141,23 @@ export class DexLoop implements DexLoopInterface { 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); - await this.chainOperator.reset(); - } - if (arbtradeOB) { - const msgs = getOrderbookArbMessages(arbtradeOB, this.chainOperator.client.publicAddress); - if (arbtradeOB.path.orderSequence === OrderSequence.AmmFirst) { - const txResponse = await this.chainOperator.signAndBroadcast(msgs); - await this.logger?.tradeLogging.logOrderbookTrade(arbtradeOB, [txResponse]); - } else { - const txResponse = await this.chainOperator.signAndBroadcast([msgs[0]]); - await delay(2000); - const txResponse2 = await this.chainOperator.signAndBroadcast([msgs[1]]); - await this.logger?.tradeLogging.logOrderbookTrade(arbtradeOB, [txResponse, txResponse2]); + if (arbTrade || arbtradeOB) { + if (arbTrade && arbtradeOB) { + if (arbTrade.profit > arbtradeOB.profit) { + await this.trade(arbTrade); + } else if (arbtradeOB.profit >= arbTrade.profit) { + await this.trade(arbtradeOB); + } + this.cdPaths(arbTrade.path); + this.cdPaths(arbtradeOB.path); + } else if (arbTrade) { + await this.trade(arbTrade); + this.cdPaths(arbTrade.path); + } else if (arbtradeOB) { + await this.trade(arbtradeOB); + this.cdPaths(arbtradeOB.path); } - this.cdPaths(arbtradeOB.path); + await this.chainOperator.reset(); } @@ -183,16 +178,30 @@ export class DexLoop implements DexLoopInterface { /** * */ - public async trade(arbTrade: OptimalTrade) { + public async trade(arbTrade: OptimalTrade | OptimalOrderbookTrade) { const publicAddress = this.chainOperator.client.publicAddress; - const [msgs, nrOfMessages] = this.messageFunction( - arbTrade, - publicAddress, - this.botConfig.flashloanRouterAddress, - ); - - const txResponse = await this.chainOperator.signAndBroadcast(msgs); - console.log(txResponse); + const messages = this.messageFactory(arbTrade, publicAddress, this.botConfig.flashloanRouterAddress); + if (!messages) { + console.error("error in creating messages", 1); + process.exit(1); + } + if (isOrderbookPath(arbTrade.path)) { + if (arbTrade.path.orderSequence === OrderSequence.AmmFirst) { + const txResponse = await this.chainOperator.signAndBroadcast(messages[0]); + await this.logger?.tradeLogging.logOrderbookTrade(arbTrade, [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(arbTrade, [ + txResponse, + txResponse2, + ]); + } + } else { + const txResponse = await this.chainOperator.signAndBroadcast(messages[0]); + await this.logger?.tradeLogging.logAmmTrade(arbTrade, [txResponse]); + } await delay(10000); } /** From 692aa28cd2b934f88e0393d6f07ec2887f155aca Mon Sep 17 00:00:00 2001 From: =0xBossanova <0xBossanova@proton.me> Date: Wed, 23 Aug 2023 14:23:43 +0200 Subject: [PATCH 11/17] feat: implement binary search in orderbook optimizer --- .../optimizers/orderbookOptimizer.ts | 99 +++++++++---------- 1 file changed, 45 insertions(+), 54 deletions(-) diff --git a/src/core/arbitrage/optimizers/orderbookOptimizer.ts b/src/core/arbitrage/optimizers/orderbookOptimizer.ts index e6cdfeef..7cca323b 100644 --- a/src/core/arbitrage/optimizers/orderbookOptimizer.ts +++ b/src/core/arbitrage/optimizers/orderbookOptimizer.ts @@ -49,7 +49,6 @@ export function getOptimalTrade( optimalProfit = optimalProfitPath; } } - return optimalProfit > 0 ? optimalOrderbookTrade : undefined; } @@ -60,61 +59,53 @@ function getOptimalTradeForPath( path: OrderbookPath, offerAssetInfo: AssetInfo, ): [number, Asset, number, number, number] { - let tradesizes = [...Array(800).keys()]; + let tradesizes = [...Array(900).keys()]; tradesizes = tradesizes.map((x) => x * 1e6); - if (path.orderSequence === OrderSequence.AmmFirst) { - let optimalTradesize = 0; - let optimalProfit = 0; - let optimalOfferAsset = { amount: "0", info: offerAssetInfo }; - let optimalWorstPrice = 0; - let optimalAveragePrice = 0; - let optimalOutGivenIn = 0; - for (const ts of tradesizes) { - if (ts === 0) { - continue; - } - const offerAsset: Asset = { amount: String(ts), 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); - // console.log("ob price received: ", price, "usdt received: ", outGivenIn1); - // console.log("profit: ", outGivenIn1 - ts); - if (outGivenIn1 - ts > optimalProfit) { - (optimalTradesize = ts), - (optimalProfit = outGivenIn1 - ts), - (optimalOfferAsset = offerAsset), - (optimalWorstPrice = worstPrice), - (optimalAveragePrice = averagePrice); - optimalOutGivenIn = outGivenIn1; - } + + 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 [optimalProfit, optimalOfferAsset, optimalWorstPrice, optimalAveragePrice, optimalOutGivenIn]; + } + 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 { - let optimalTradesize = 0; - let optimalProfit = 0; - let optimalOfferAsset = { amount: "0", info: offerAssetInfo }; - let optimalWorstPrice = 0; - let optimalAveragePrice = 0; - let optimalOutGivenIn = 0; - for (const ts of tradesizes) { - if (ts === 0) { - continue; - } - const offerAsset: Asset = { amount: String(ts), 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); - if (outGivenIn1 - ts > optimalProfit) { - (optimalTradesize = ts), - (optimalProfit = outGivenIn1 - ts), - (optimalOfferAsset = offerAsset), - (optimalWorstPrice = worstPrice), - (optimalAveragePrice = averagePrice), - (optimalOutGivenIn = outGivenIn0); - } - } - return [optimalProfit, optimalOfferAsset, optimalWorstPrice, optimalAveragePrice, optimalOutGivenIn]; + 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]; } } From f4905d2e258ac8d1158c1624b912975dc6107c4c Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Thu, 24 Aug 2023 17:08:03 +0200 Subject: [PATCH 12/17] refactor: trades in loops separated, orderbook to mempoolloop --- src/chains/defaults/queries/getPoolState.ts | 4 ++ .../loops/dexMempoolSkiploop.ts | 4 +- .../arbitrageloops/loops/dexMempoolloop.ts | 67 ++++++++++++++----- .../types/arbitrageloops/loops/dexloop.ts | 25 +++---- src/index.ts | 2 +- 5 files changed, 68 insertions(+), 34 deletions(-) diff --git a/src/chains/defaults/queries/getPoolState.ts b/src/chains/defaults/queries/getPoolState.ts index f4974e67..34c13605 100644 --- a/src/chains/defaults/queries/getPoolState.ts +++ b/src/chains/defaults/queries/getPoolState.ts @@ -1,3 +1,5 @@ +import { inspect } from "util"; + import { ChainOperator } from "../../../core/chainOperator/chainoperator"; import { Asset, @@ -33,6 +35,7 @@ interface PoolState { export async function getPoolStates(chainOperator: ChainOperator, pools: Array) { await Promise.all( pools.map(async (pool) => { + console.log(pool); if (pool.dexname === AmmDexName.junoswap) { const poolState = await chainOperator.queryContractSmart(pool.address, { info: {} }); @@ -69,6 +72,7 @@ export async function initPools( let dexname: AmmDexName; let totalShare: string; try { + console.log(inspect(poolAddress, true, null, true)); const poolState = await chainOperator.queryContractSmart(poolAddress.pool, { pool: {} }); [assets, dexname, totalShare] = processPoolStateAssets(poolState); } catch (error) { diff --git a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts index f47b103e..2db70590 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts @@ -81,7 +81,7 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { applyMempoolMessagesOnPools(this.pools, [mempoolTx]); 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; @@ -94,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 || diff --git a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts index 15b397af..10417868 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts @@ -84,10 +84,10 @@ export class DexMempoolLoop implements DexLoopInterface { } 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; } @@ -117,15 +117,14 @@ export class DexMempoolLoop implements DexLoopInterface { } const arbTrade = this.ammArb(this.paths, this.botConfig); + const arbtradeOB = this.orderbookArb(this.orderbookPaths, this.botConfig); - if (arbTrade) { + if (arbTrade || arbtradeOB) { await this.trade(arbTrade); console.log("mempool transactions to backrun:"); mempoolTxs.map((mpt) => { console.log(toHex(sha256(mpt.txBytes))); }); - this.cdPaths(arbTrade.path); - await this.chainOperator.reset(); break; } } @@ -136,6 +135,7 @@ export class DexMempoolLoop implements DexLoopInterface { * */ async reset() { + await this.chainOperator.reset(); this.unCDPaths(); this.totalBytes = 0; flushTxMemory(); @@ -144,7 +144,49 @@ export class DexMempoolLoop implements DexLoopInterface { /** * */ - public async trade(arbTrade: OptimalTrade) { + public async trade(arbTrade?: OptimalTrade, arbTradeOB?: OptimalOrderbookTrade) { + 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(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.logOrderbookTrade(arbTradeOB, [txResponse]); + } + + /** + * + */ + private async tradeAmm(arbTrade: OptimalTrade) { const messages = this.messageFactory( arbTrade, this.chainOperator.client.publicAddress, @@ -154,9 +196,6 @@ export class DexMempoolLoop implements DexLoopInterface { console.error("error in creating messages", 1); process.exit(1); } - - // await this.logger?.sendMessage(JSON.stringify(msgs), LogType.Console); - const TX_FEE = this.botConfig.txFees.get(messages[1]) ?? Array.from(this.botConfig.txFees.values())[this.botConfig.txFees.size - 1]; @@ -164,19 +203,13 @@ export class DexMempoolLoop implements DexLoopInterface { const txResponse = await this.chainOperator.signAndBroadcast(messages[0], TX_FEE); await this.logger?.tradeLogging.logAmmTrade(arbTrade, [txResponse]); - - if (txResponse.code === 0) { - this.chainOperator.client.sequence = this.chainOperator.client.sequence + 1; - } - await delay(5000); } - /** * 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]]); diff --git a/src/core/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index 51bc7393..96e38e0f 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -141,27 +141,24 @@ export class DexLoop implements DexLoopInterface { const arbTrade: OptimalTrade | undefined = this.ammArb(this.paths, this.botConfig); const arbtradeOB = this.orderbookArb(this.orderbookPaths, this.botConfig); - if (arbTrade || arbtradeOB) { - if (arbTrade && arbtradeOB) { - if (arbTrade.profit > arbtradeOB.profit) { - await this.trade(arbTrade); - } else if (arbtradeOB.profit >= arbTrade.profit) { - await this.trade(arbtradeOB); - } - this.cdPaths(arbTrade.path); - this.cdPaths(arbtradeOB.path); - } else if (arbTrade) { + + if (arbTrade && arbtradeOB) { + if (arbTrade.profit > arbtradeOB.profit) { await this.trade(arbTrade); this.cdPaths(arbTrade.path); - } else if (arbtradeOB) { + } else if (arbtradeOB.profit >= arbTrade.profit) { await this.trade(arbtradeOB); this.cdPaths(arbtradeOB.path); } - - await this.chainOperator.reset(); + } else if (arbTrade) { + await this.trade(arbTrade); + this.cdPaths(arbTrade.path); + } else if (arbtradeOB) { + await this.trade(arbtradeOB); + this.cdPaths(arbtradeOB.path); } - await delay(1500); + await this.chainOperator.reset(); } /** diff --git a/src/index.ts b/src/index.ts index b1711293..4d25b091 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { DexLoop } from "./core/types/arbitrageloops/loops/dexloop"; import { LiquidationLoop } from "./core/types/arbitrageloops/loops/liqMempoolLoop"; import { DexConfig, LiquidationConfig, setBotConfig, SetupType } from "./core/types/base/configs"; // load env files -dotenv.config({ path: "./src/envs/injective.env" }); +dotenv.config({ path: "./src/envs/juno.env" }); /** * Runs the main program. From 5b5c6ceb22f6ca2f913a8326a09ab2a9f4b9b27e Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Mon, 28 Aug 2023 11:46:16 +0200 Subject: [PATCH 13/17] refactor: extend loopInterface and trade functions --- src/chains/defaults/queries/getPoolState.ts | 4 - .../interfaces/dexloopInterface.ts | 41 +++++-- .../loops/dexMempoolSkiploop.ts | 4 +- .../arbitrageloops/loops/dexMempoolloop.ts | 21 ++-- .../types/arbitrageloops/loops/dexloop.ts | 101 +++++++++++------- src/index.ts | 2 +- 6 files changed, 111 insertions(+), 62 deletions(-) diff --git a/src/chains/defaults/queries/getPoolState.ts b/src/chains/defaults/queries/getPoolState.ts index 34c13605..f4974e67 100644 --- a/src/chains/defaults/queries/getPoolState.ts +++ b/src/chains/defaults/queries/getPoolState.ts @@ -1,5 +1,3 @@ -import { inspect } from "util"; - import { ChainOperator } from "../../../core/chainOperator/chainoperator"; import { Asset, @@ -35,7 +33,6 @@ interface PoolState { export async function getPoolStates(chainOperator: ChainOperator, pools: Array) { await Promise.all( pools.map(async (pool) => { - console.log(pool); if (pool.dexname === AmmDexName.junoswap) { const poolState = await chainOperator.queryContractSmart(pool.address, { info: {} }); @@ -72,7 +69,6 @@ export async function initPools( let dexname: AmmDexName; let totalShare: string; try { - console.log(inspect(poolAddress, true, null, true)); const poolState = await chainOperator.queryContractSmart(poolAddress.pool, { pool: {} }); [assets, dexname, totalShare] = processPoolStateAssets(poolState); } catch (error) { diff --git a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts index e939ba9b..f0d7211f 100644 --- a/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts +++ b/src/core/types/arbitrageloops/interfaces/dexloopInterface.ts @@ -1,5 +1,7 @@ +import { getPoolStates } from "../../../../chains/defaults"; import { messageFactory } from "../../../../chains/defaults/messages/messageFactory"; -import { OptimalTrade } from "../../../arbitrage/arbitrage"; +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"; @@ -24,15 +26,32 @@ export interface DexLoopInterface { logger: Logger | undefined; iterations: number; - /** - * + /* + * Optimizers to use during loop runtime */ - ammArb: (paths: Array, botConfig: DexConfig) => OptimalTrade | undefined; - orderbookArb: (paths: Array, botConfig: DexConfig) => OptimalOrderbookTrade | undefined; - updatePoolStates: (chainOperator: ChainOperator, pools: Array) => Promise; - updateOrderbookStates?: (chainOperator: ChainOperator, orderbooks: Array) => Promise; - messageFactory: typeof messageFactory; - step: () => Promise; - reset: () => Promise; - clearIgnoreAddresses: () => void; + 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 + */ + 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 2db70590..e240eeed 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolSkiploop.ts @@ -52,7 +52,7 @@ export class DexMempoolSkipLoop extends DexMempoolLoop { 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; } @@ -158,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 10417868..16034175 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts @@ -8,7 +8,7 @@ import { ChainOperator } from "../../../chainOperator/chainoperator"; import { Logger } from "../../../logging/logger"; import { DexConfig } from "../../base/configs"; import { Mempool, IgnoredAddresses, MempoolTx, decodeMempool, flushTxMemory } from "../../base/mempool"; -import { getOrderbookAmmPaths, OrderbookPath, 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"; @@ -120,7 +120,7 @@ export class DexMempoolLoop implements DexLoopInterface { const arbtradeOB = this.orderbookArb(this.orderbookPaths, this.botConfig); if (arbTrade || arbtradeOB) { - await this.trade(arbTrade); + await this.trade(arbTrade, arbtradeOB); console.log("mempool transactions to backrun:"); mempoolTxs.map((mpt) => { console.log(toHex(sha256(mpt.txBytes))); @@ -144,7 +144,7 @@ export class DexMempoolLoop implements DexLoopInterface { /** * */ - public async trade(arbTrade?: OptimalTrade, arbTradeOB?: OptimalOrderbookTrade) { + public async trade(arbTrade: OptimalTrade | undefined, arbTradeOB: OptimalOrderbookTrade | undefined) { if (arbTrade && arbTradeOB) { if (arbTrade.profit > arbTradeOB.profit) { await this.tradeAmm(arbTrade); @@ -178,9 +178,18 @@ export class DexMempoolLoop implements DexLoopInterface { 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.logOrderbookTrade(arbTradeOB, [txResponse]); + 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(2000); + const txResponse2 = await this.chainOperator.signAndBroadcast([messages[0][1]], TX_FEE); + await this.logger?.tradeLogging.logOrderbookTrade(arbTradeOB, [ + txResponse, + txResponse2, + ]); + } } /** diff --git a/src/core/types/arbitrageloops/loops/dexloop.ts b/src/core/types/arbitrageloops/loops/dexloop.ts index 96e38e0f..90f113ae 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -8,7 +8,7 @@ import { Logger } from "../../../logging"; import { DexConfig } from "../../base/configs"; import { LogType } from "../../base/logging"; import { Orderbook } from "../../base/orderbook"; -import { getOrderbookAmmPaths, isOrderbookPath, OrderbookPath, OrderSequence, Path } from "../../base/path"; +import { getOrderbookAmmPaths, OrderbookPath, OrderSequence, Path } from "../../base/path"; import { Pool, removedUnusedPools } from "../../base/pool"; import { DexLoopInterface } from "../interfaces/dexloopInterface"; import { DexMempoolLoop } from "./dexMempoolloop"; @@ -77,7 +77,7 @@ export class DexLoop implements DexLoopInterface { botConfig: DexConfig, logger: Logger, ): Promise { - let msgFactory = chains.defaults.messageFactory; + const msgFactory = chains.defaults.messageFactory; let getPoolStates = chains.defaults.getPoolStates; let initPools = chains.defaults.initPools; const initOrderbook = chains.injective.initOrderbooks; @@ -86,7 +86,7 @@ export class DexLoop implements DexLoopInterface { if (chainSetups === undefined) { await logger.sendMessage("Unable to resolve specific chain imports, using defaults", LogType.Console); } - msgFactory = chainSetups.getFlashArbMessages; + // msgFactory = chainSetups.getFlashArbMessages; getPoolStates = chainSetups.getPoolStates; initPools = chainSetups.initPools; return; @@ -100,6 +100,7 @@ export class DexLoop implements DexLoopInterface { } const allPools = await initPools(chainOperator, botConfig.poolEnvs, botConfig.mappingFactoryRouter); if (botConfig.useMempool && !botConfig.skipConfig?.useSkip) { + console.log("spinning up mempool loop"); return new DexMempoolLoop( chainOperator, botConfig, @@ -111,6 +112,7 @@ export class DexLoop implements DexLoopInterface { getOrderbookState, ); } else if (botConfig.useMempool && botConfig.skipConfig?.useSkip) { + console.log("spinning up skip mempool loop"); return new DexMempoolSkipLoop( chainOperator, botConfig, @@ -122,6 +124,7 @@ export class DexLoop implements DexLoopInterface { getOrderbookState, ); } + console.log("spinning up no-mempool loop"); return new DexLoop( chainOperator, botConfig, @@ -142,23 +145,10 @@ export class DexLoop implements DexLoopInterface { const arbTrade: OptimalTrade | undefined = this.ammArb(this.paths, this.botConfig); const arbtradeOB = this.orderbookArb(this.orderbookPaths, this.botConfig); - if (arbTrade && arbtradeOB) { - if (arbTrade.profit > arbtradeOB.profit) { - await this.trade(arbTrade); - this.cdPaths(arbTrade.path); - } else if (arbtradeOB.profit >= arbTrade.profit) { - await this.trade(arbtradeOB); - this.cdPaths(arbtradeOB.path); - } - } else if (arbTrade) { - await this.trade(arbTrade); - this.cdPaths(arbTrade.path); - } else if (arbtradeOB) { - await this.trade(arbtradeOB); - this.cdPaths(arbtradeOB.path); + if (arbTrade || arbtradeOB) { + await this.trade(arbTrade, arbtradeOB); + await this.chainOperator.reset(); } - - await this.chainOperator.reset(); } /** @@ -175,32 +165,67 @@ export class DexLoop implements DexLoopInterface { /** * */ - public async trade(arbTrade: OptimalTrade | OptimalOrderbookTrade) { - const publicAddress = this.chainOperator.client.publicAddress; - const messages = this.messageFactory(arbTrade, 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); } - if (isOrderbookPath(arbTrade.path)) { - if (arbTrade.path.orderSequence === OrderSequence.AmmFirst) { - const txResponse = await this.chainOperator.signAndBroadcast(messages[0]); - await this.logger?.tradeLogging.logOrderbookTrade(arbTrade, [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(arbTrade, [ - txResponse, - txResponse2, - ]); - } - } else { + if (arbTradeOB.path.orderSequence === OrderSequence.AmmFirst) { const txResponse = await this.chainOperator.signAndBroadcast(messages[0]); - await this.logger?.tradeLogging.logAmmTrade(arbTrade, [txResponse]); + 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, + ]); } - await delay(10000); } + + /** + * + */ + 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 txResponse = await this.chainOperator.signAndBroadcast(messages[0]); + + 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 diff --git a/src/index.ts b/src/index.ts index 4d25b091..b1711293 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { DexLoop } from "./core/types/arbitrageloops/loops/dexloop"; import { LiquidationLoop } from "./core/types/arbitrageloops/loops/liqMempoolLoop"; import { DexConfig, LiquidationConfig, setBotConfig, SetupType } from "./core/types/base/configs"; // load env files -dotenv.config({ path: "./src/envs/juno.env" }); +dotenv.config({ path: "./src/envs/injective.env" }); /** * Runs the main program. From a6ec01ee7c950af7850452c10d8fbee51c708408 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Tue, 29 Aug 2023 13:13:06 +0200 Subject: [PATCH 14/17] feat: add orderbookpaths to cooldowns --- .../types/arbitrageloops/loops/dexMempoolloop.ts | 16 +++++++++++++--- src/core/types/arbitrageloops/loops/dexloop.ts | 10 ++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts index 16034175..42f09c0c 100644 --- a/src/core/types/arbitrageloops/loops/dexMempoolloop.ts +++ b/src/core/types/arbitrageloops/loops/dexMempoolloop.ts @@ -183,7 +183,7 @@ export class DexMempoolLoop implements DexLoopInterface { await this.logger?.tradeLogging.logOrderbookTrade(arbTradeOB, [txResponse]); } else { const txResponse = await this.chainOperator.signAndBroadcast([messages[0][0]], TX_FEE); - await delay(2000); + await delay(3000); const txResponse2 = await this.chainOperator.signAndBroadcast([messages[0][1]], TX_FEE); await this.logger?.tradeLogging.logOrderbookTrade(arbTradeOB, [ txResponse, @@ -221,10 +221,10 @@ export class DexMempoolLoop implements DexLoopInterface { 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 @@ -235,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 90f113ae..73f8e169 100644 --- a/src/core/types/arbitrageloops/loops/dexloop.ts +++ b/src/core/types/arbitrageloops/loops/dexloop.ts @@ -248,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 From 822a9a43ae9035715e7a3a1cac0151d0dede6007 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Tue, 29 Aug 2023 14:15:09 +0200 Subject: [PATCH 15/17] bug: extra catch for juno pool query error --- src/core/chainOperator/chainoperator.ts | 2 +- src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/chainOperator/chainoperator.ts b/src/core/chainOperator/chainoperator.ts index abcbff71..dfec8036 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); diff --git a/src/index.ts b/src/index.ts index b1711293..8b17453a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { DexLoop } from "./core/types/arbitrageloops/loops/dexloop"; import { LiquidationLoop } from "./core/types/arbitrageloops/loops/liqMempoolLoop"; import { DexConfig, LiquidationConfig, setBotConfig, SetupType } from "./core/types/base/configs"; // load env files -dotenv.config({ path: "./src/envs/injective.env" }); +dotenv.config({ path: "./src/envs/terra.env" }); /** * Runs the main program. From 53fd6a4561de8b8a9eacd588da52bb6452688a32 Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Mon, 4 Sep 2023 11:11:34 +0200 Subject: [PATCH 16/17] feat: add ETH denom to 18 decimal default --- src/core/types/base/asset.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/core/types/base/asset.ts b/src/core/types/base/asset.ts index 93b70352..a02dbecc 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); From 52d8988cf4e43df0164f8bf4a9c233a49a3c47fa Mon Sep 17 00:00:00 2001 From: 0xBossanova <0xBossanova@proton.me> Date: Mon, 4 Sep 2023 11:49:53 +0200 Subject: [PATCH 17/17] bug: remove belief_price for now --- .../defaults/messages/getSwapMessage.ts | 7 ++--- .../inj/messages/getOrderbookArbMessage.ts | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/chains/defaults/messages/getSwapMessage.ts b/src/chains/defaults/messages/getSwapMessage.ts index fecea9b7..8807cedd 100644 --- a/src/chains/defaults/messages/getSwapMessage.ts +++ b/src/chains/defaults/messages/getSwapMessage.ts @@ -4,7 +4,6 @@ import { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx"; import { Asset, isNativeAsset } from "../../../core/types/base/asset"; import { Pool } from "../../../core/types/base/pool"; -import { DefaultSwapMessage } from "../../../core/types/messages/swapmessages"; /** * @@ -14,13 +13,13 @@ export function getSwapMessage( offerAsset: Asset, walletAddress: string, beliefPrice: string, - maxSpread = 0.05, + maxSpread = 0.005, ) { - const msg: DefaultSwapMessage = { + const msg = { swap: { max_spread: String(maxSpread), offer_asset: offerAsset, - belief_price: beliefPrice, + // belief_price: beliefPrice, }, }; const encodedMsgObject: EncodeObject = { diff --git a/src/chains/inj/messages/getOrderbookArbMessage.ts b/src/chains/inj/messages/getOrderbookArbMessage.ts index 0ac39479..449f50e6 100644 --- a/src/chains/inj/messages/getOrderbookArbMessage.ts +++ b/src/chains/inj/messages/getOrderbookArbMessage.ts @@ -1,5 +1,4 @@ import { EncodeObject } from "@cosmjs/proto-signing"; -import { BigNumberInBase } from "@injectivelabs/utils/dist/cjs/classes"; import { OptimalOrderbookTrade } from "../../../core/arbitrage/optimizers/orderbookOptimizer"; import { toChainAsset, toChainPrice } from "../../../core/types/base/asset"; @@ -38,21 +37,23 @@ export function getOrderbookArbMessages( }; const msg0 = getMarketSpotOrderMessage(arbTrade, publicAddress, offerAsset1, 1); - const decimals = arbTrade.path.orderbook.baseAssetDecimals - arbTrade.path.orderbook.quoteAssetDecimals; - - let orderSize = +new BigNumberInBase(arbTrade.outGivenIn).toWei(decimals).toFixed(); - - const belief_price = String( - Math.round((orderSize / arbTrade.outGivenIn) * 100000 * (10 ^ decimals)) / 100000 / (10 ^ decimals), + 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 }, ); - orderSize = - Math.floor(orderSize / arbTrade.path.orderbook.minQuantityIncrement) * - arbTrade.path.orderbook.minQuantityIncrement; - - const offerAsset = { - amount: String(orderSize), + const offerAsset = toChainAsset({ + amount: String(arbTrade.outGivenIn), info: offerAsset1.info, - }; + }); + const msg1 = getSwapMessage(arbTrade.path.pool, offerAsset, publicAddress, belief_price); return [[msg0, msg1], 2];