diff --git a/.env.injective.example b/.env.injective.example index 8c88a70..841e591 100644 --- a/.env.injective.example +++ b/.env.injective.example @@ -23,7 +23,8 @@ SKIP_URL="https://injective-1-api.skip.money" SKIP_BID_WALLET="inj17yqtnk08ly94lgz3fzagfu2twsws33z7sfsv46" SKIP_BID_RATE="0.1" #e.g. 20% of the profit is used as a bid to win the auction CHAIN_PREFIX="inj" -RPC_URL="https://ww-juno-rpc.polkachu.com" #irrelevant overwritten by k8s frominjective +USE_RPC_URL_SCRAPER="0" +RPC_URL="" #change this GAS_UNIT_PRICE="0.0004" FLASHLOAN_ROUTER_ADDRESS="inj1hyja4uyjktpeh0fxzuw2fmjudr85rk2qxgqkvu" FLASHLOAN_FEE="0.15" \ No newline at end of file diff --git a/.env.juno.example b/.env.juno.example index 098cf84..36ccda5 100644 --- a/.env.juno.example +++ b/.env.juno.example @@ -20,11 +20,16 @@ SKIP_URL= "http://juno-1-api.skip.money" SKIP_BID_WALLET= "juno10g0l3hd9sau3vnjrayjhergcpxemucxcspgnn4" SKIP_BID_RATE="0.31" #e.g. 31% of the profit is used as a bid to win the auction +# Addresses to Blacklist. Needed against Spam Txs. +# For more Info Discord Channel Developers/Bot-Support +IGNORE_ADDRESSES='[""]' + ##JUNO SETTINGS BASE_DENOM="ujuno" # The asset denom to be used as base asset. This should be the denom of a Native Token only. GAS_DENOM="ujuno" CHAIN_PREFIX="juno" -RPC_URL="https://juno-rpc.reece.sh" #change this +USE_RPC_URL_SCRAPER="0" +RPC_URL=["https://juno-rpc.reece.sh"] #change this GAS_UNIT_PRICE="0.0025" FLASHLOAN_ROUTER_ADDRESS="juno1qa7vdlm6zgq3radal5sltyl4t4qd32feug9qs50kcxda46q230pqzny48s" FACTORIES_TO_ROUTERS_MAPPING ='{"factory":"juno14m9rd2trjytvxvu4ldmqvru50ffxsafs8kequmfky7jh97uyqrxqs5xrnx","router":"juno128lewlw6kv223uw4yzdffl8rnh3k9qs8vrf6kef28579w8ygccyq7m90n2"}, diff --git a/.env.terra.example b/.env.terra.example index c56c742..6f90407 100644 --- a/.env.terra.example +++ b/.env.terra.example @@ -24,7 +24,8 @@ SKIP_BID_RATE="0.1" #e.g. 10% of the profit is used as a bid to win the auction BASE_DENOM="uluna" GAS_DENOM="uluna" CHAIN_PREFIX="terra" -RPC_URL="" ##change this +USE_RPC_URL_SCRAPER="0" +RPC_URL=[""] ##change this GAS_UNIT_PRICE="0.015" FLASHLOAN_ROUTER_ADDRESS="terra1c8tpvta3umr4mpufvxmq39gyuw2knpyxyfgq8thpaeyw2r6a80qsg5wa00" FLASHLOAN_FEE="0.15" #in % diff --git a/src/chains/defaults/queries/getPoolsFromFactory.ts b/src/chains/defaults/queries/getPoolsFromFactory.ts index 11fdc3c..4f1eca6 100644 --- a/src/chains/defaults/queries/getPoolsFromFactory.ts +++ b/src/chains/defaults/queries/getPoolsFromFactory.ts @@ -20,9 +20,17 @@ export async function getPoolsFromFactory( const factorypairs: Array<{ pool: string; factory: string; router: string }> = []; await Promise.all( factoryMapping.map(async (factorymap) => { - let res: FactoryState = await chainOperator.queryContractSmart(factorymap.factory, { - pairs: { limit: 30 }, - }); + let res: FactoryState = { pairs: [] }; + try { + res = await chainOperator.queryContractSmart(factorymap.factory, { + pairs: { limit: 30 }, + }); + } catch { + console.log("RPC err on getPoolsfromFactory ... switching RPC"); + await chainOperator.client.getNewClients(); + await getPoolsFromFactory(chainOperator, factoryMapping); + return; + } res.pairs.map((factorypair) => { factorypairs.push({ diff --git a/src/core/chainOperator/chainAdapters/cosmjs.ts b/src/core/chainOperator/chainAdapters/cosmjs.ts index c1b8369..11c72cc 100644 --- a/src/core/chainOperator/chainAdapters/cosmjs.ts +++ b/src/core/chainOperator/chainAdapters/cosmjs.ts @@ -18,26 +18,36 @@ import { ChainOperatorInterface, TxResponse } from "../chainOperatorInterface"; class CosmjsAdapter implements ChainOperatorInterface { private _signingCWClient!: SigningCosmWasmClient; //used to sign transactions private _tmClient!: Tendermint34Client; //used to broadcast transactions - private _httpClient: HttpBatchClient | HttpClient; //used to query rpc methods (unconfirmed_txs, account) + private _httpClient!: HttpBatchClient | HttpClient; //used to query rpc methods (unconfirmed_txs, account) private _wasmQueryClient!: QueryClient & WasmExtension; //used to query wasm methods (contract states) private _account!: AccountData; private _publicAddress!: string; private _accountNumber = 0; private _sequence = 0; - + private _chainPrefix: string; private _chainId!: string; - private _signer!: DirectSecp256k1HdWallet; private _skipBundleClient?: SkipBundleClient; + private _timeoutRPCs: Map; + + private _rpcUrls!: Array; + private _denom!: string; + private _gasPrice!: string; + private _currRpcUrl: string; /** * */ constructor(botConfig: BotConfig) { - this._httpClient = new HttpBatchClient(botConfig.rpcUrl); + this._chainPrefix = botConfig.chainPrefix; + this._timeoutRPCs = new Map(); + this._currRpcUrl = botConfig.rpcUrls[0]; if (botConfig.skipConfig) { this._skipBundleClient = new SkipBundleClient(botConfig.skipConfig.skipRpcUrl); } + this._rpcUrls = botConfig.rpcUrls; + this._denom = botConfig.baseDenom; + this._gasPrice = botConfig.gasPrice; } /** * @@ -57,6 +67,12 @@ class CosmjsAdapter implements ChainOperatorInterface { public get publicAddress(): string { return this._publicAddress; } + /** + * + */ + public set publicAddress(value) { + this._publicAddress = value; + } /** * */ @@ -68,24 +84,30 @@ class CosmjsAdapter implements ChainOperatorInterface { */ async init(botConfig: BotConfig) { // derive signing wallet - const signer = await DirectSecp256k1HdWallet.fromMnemonic(botConfig.mnemonic, { + this._signer = await DirectSecp256k1HdWallet.fromMnemonic(botConfig.mnemonic, { prefix: botConfig.chainPrefix, }); - this._signer = signer; - // connect to client and querier - this._signingCWClient = await SigningCosmWasmClient.connectWithSigner(botConfig.rpcUrl, signer, { - prefix: botConfig.chainPrefix, - gasPrice: GasPrice.fromString(botConfig.gasPrice + botConfig.baseDenom), - }); - this._tmClient = await Tendermint34Client.create(this._httpClient); - this._wasmQueryClient = QueryClient.withExtensions(this._tmClient, setupWasmExtension, setupAuthExtension); - this._account = (await signer.getAccounts())[0]; + await this.setClients(botConfig.rpcUrls[0]); + this._account = (await this._signer.getAccounts())[0]; const { accountNumber, sequence } = await this._signingCWClient.getSequence(this._account.address); this._chainId = await this._signingCWClient.getChainId(); this._accountNumber = accountNumber; - this._sequence = sequence; - this._publicAddress = this._account.address; + this.sequence = sequence; + this.publicAddress = this._account.address; + } + + /** + * + */ + async setClients(rpcUrl: string) { + this._httpClient = new HttpBatchClient(rpcUrl); + this._tmClient = await Tendermint34Client.create(this._httpClient); + this._wasmQueryClient = QueryClient.withExtensions(this._tmClient, setupWasmExtension, setupAuthExtension); + this._signingCWClient = await SigningCosmWasmClient.connectWithSigner(rpcUrl, this._signer, { + prefix: this._chainPrefix, + gasPrice: GasPrice.fromString(this._gasPrice + this._denom), + }); } /** * @@ -155,6 +177,52 @@ class CosmjsAdapter implements ChainOperatorInterface { return mempoolResult.result; } + /** + * Sets new Clients for Mempoolloop. + * + */ + public async getNewClients(): Promise { + let out: string; + const TIMEOUTDUR = 60000; // 10 Min timeout if error + let n = 0; + let urlString: string | undefined; + this._timeoutRPCs.set(this._currRpcUrl, Date.now()); + while (!urlString && n < this._rpcUrls.length) { + const currTime: number = Date.now(); + + if (!this._timeoutRPCs.has(this._rpcUrls[n])) { + urlString = this._rpcUrls[n]; + } else { + const errTime = this._timeoutRPCs.get(this._rpcUrls[n]); + if (errTime && errTime + TIMEOUTDUR <= currTime) { + this._timeoutRPCs.delete(this._rpcUrls[n]); + urlString = this._rpcUrls[n]; + } + } + n++; + } + if (!urlString) { + console.log("All RPC's Timeouted"); + let n: number = Date.now(); + let nextUrl: string = this._currRpcUrl; + for (const [url, timeouted] of this._timeoutRPCs.entries()) { + if (timeouted < n) { + n = timeouted; + nextUrl = url; + } + } + await delay(TIMEOUTDUR + n - Date.now()); + await this.setClients(nextUrl); + out = nextUrl; + } else { + console.log("Updating Clients to: " + urlString); + await this.setClients(urlString); + out = urlString; + } + console.log("Continue..."); + this._currRpcUrl = out; + } + /** * */ @@ -164,5 +232,11 @@ class CosmjsAdapter implements ChainOperatorInterface { this._sequence = sequence; } } +/** + * + */ +function delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} export default CosmjsAdapter; diff --git a/src/core/chainOperator/chainAdapters/injective.ts b/src/core/chainOperator/chainAdapters/injective.ts index 37841d9..52592a8 100644 --- a/src/core/chainOperator/chainAdapters/injective.ts +++ b/src/core/chainOperator/chainAdapters/injective.ts @@ -24,7 +24,6 @@ import { SkipBundleClient } from "@skip-mev/skipjs"; import { MsgSend as CosmJSMsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx"; import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { MsgExecuteContract as CosmJSMsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx"; -import { inspect } from "util"; import { BotConfig } from "../../types/base/botConfig"; import { Mempool } from "../../types/base/mempool"; @@ -42,7 +41,7 @@ class InjectiveAdapter implements ChainOperatorInterface { private _network: Network; private _publicKey: PublicKey; - private _publicAddress: string; + private _publicAddress!: string; private _signer!: DirectSecp256k1HdWallet; private _accountNumber = 0; @@ -63,16 +62,16 @@ class InjectiveAdapter implements ChainOperatorInterface { }); this._spotQueryClient = new IndexerGrpcSpotApi(endpoints.indexer); this._wasmQueryClient = new ChainGrpcWasmApi(endpoints.grpc); - this._httpClient = new HttpBatchClient(botConfig.rpcUrl); + this._httpClient = new HttpBatchClient(botConfig.rpcUrls[0]); this._chainId = network === Network.TestnetK8s ? ChainId.Testnet : ChainId.Mainnet; this._network = network; this._publicKey = privateKey.toPublicKey(); - this._publicAddress = privateKey.toPublicKey().toAddress().address; + this.publicAddress = privateKey.toPublicKey().toAddress().address; } /** * */ - public get sequence() { + public get sequence(): number { return this._sequence; } /** @@ -87,6 +86,12 @@ class InjectiveAdapter implements ChainOperatorInterface { public get publicAddress(): string { return this._publicAddress; } + /** + * + */ + public set publicAddress(value) { + this._publicAddress = value; + } /** * */ @@ -104,7 +109,7 @@ class InjectiveAdapter implements ChainOperatorInterface { const baseAccount = BaseAccount.fromRestApi(accountDetailsResponse); const accountDetails = baseAccount.toAccountDetails(); this._accountNumber = accountDetails.accountNumber; - this._sequence = accountDetails.sequence; + this.sequence = accountDetails.sequence; const hdPath = stringToPath("m/44'/60'/0'/0/0"); this._signer = await DirectSecp256k1HdWallet.fromMnemonic(botConfig.mnemonic, { @@ -241,7 +246,6 @@ class InjectiveAdapter implements ChainOperatorInterface { } else { signed = await this._skipBundleClient.signBundle([cosmTxRaw], this._signer, this._skipSigningAddress); } - console.log(inspect(signed, { depth: null })); const res = await this._skipBundleClient.sendBundle(signed, 0, true); return res; } @@ -306,6 +310,13 @@ class InjectiveAdapter implements ChainOperatorInterface { console.log(error); } } + /** + * Sets new Clients for Mempoolloop. + * TODO!!! + */ + public async getNewClients() { + throw new Error("Change Clients not implemented for Injective yet"); + } } export default InjectiveAdapter; diff --git a/src/core/chainOperator/chainoperator.ts b/src/core/chainOperator/chainoperator.ts index b7a9100..9cb227b 100644 --- a/src/core/chainOperator/chainoperator.ts +++ b/src/core/chainOperator/chainoperator.ts @@ -37,13 +37,14 @@ export class ChainOperator { const cosmjsClient = new CosmjsAdapter(botConfig); await cosmjsClient.init(botConfig); return new Promise((resolve, reject) => { - resolve(new ChainOperator(cosmjsClient, botConfig.rpcUrl)); + resolve(new ChainOperator(cosmjsClient, botConfig.rpcUrls[0])); }); } /** * */ async queryContractSmart(address: string, queryMsg: Record): Promise { + //Error handled in getPoolState. return await this.client.queryContractSmart(address, queryMsg); } @@ -51,7 +52,13 @@ export class ChainOperator { * */ async queryMempool() { - return await this.client.queryMempool(); + try { + return await this.client.queryMempool(); + } catch (e) { + console.log(e); + await this.client.getNewClients(); + return await this.client.queryMempool(); + } } /** * @@ -61,7 +68,13 @@ export class ChainOperator { fee?: StdFee | "auto", memo?: string | undefined, ): Promise { - return await this.client.signAndBroadcast(msgs, fee, memo); + try { + return await this.client.signAndBroadcast(msgs, fee, memo); + } catch (e) { + console.log(e); + await this.client.getNewClients(); + return await this.client.signAndBroadcast(msgs, fee, memo); + } } /** @@ -74,6 +87,12 @@ export class ChainOperator { * */ async signAndBroadcastSkipBundle(messages: Array, fee: StdFee, memo?: string, otherTx?: TxRaw) { - return await this.client.signAndBroadcastSkipBundle(messages, fee, memo, otherTx); + try { + return await this.client.signAndBroadcastSkipBundle(messages, fee, memo, otherTx); + } catch (e) { + console.log(e); + await this.client.getNewClients(); + return await this.client.signAndBroadcastSkipBundle(messages, fee, memo, otherTx); + } } } diff --git a/src/core/types/arbitrageloops/mempoolLoop.ts b/src/core/types/arbitrageloops/mempoolLoop.ts index e27cb2e..2dad240 100644 --- a/src/core/types/arbitrageloops/mempoolLoop.ts +++ b/src/core/types/arbitrageloops/mempoolLoop.ts @@ -17,6 +17,8 @@ export class MempoolLoop { pathlib: Array; //holds all known paths CDpaths: Map; //holds all cooldowned paths' identifiers chainOperator: ChainOperator; + // TODO: Change ignore Addresses to opject + ignoreAddresses: Set; botConfig: BotConfig; logger: Logger | undefined; // CACHE VALUES @@ -52,6 +54,7 @@ export class MempoolLoop { botConfig: BotConfig, logger: Logger | undefined, pathlib: Array, + ignoreAddresses: Set, ) { this.pools = pools; this.CDpaths = new Map(); @@ -64,6 +67,7 @@ export class MempoolLoop { this.botConfig = botConfig; this.logger = logger; this.pathlib = pathlib; + this.ignoreAddresses = ignoreAddresses; } /** @@ -92,11 +96,25 @@ export class MempoolLoop { this.totalBytes = +this.mempool.total_bytes; } - const mempoolTrades: Array = processMempool(this.mempool); + const mempooltxs: [Array, Array<{ sender: string; reciever: string }>] = processMempool( + this.mempool, + this.ignoreAddresses, + ); + // Checks if there is a SendMsg from a blacklisted Address, if so add the reciever to the timeouted addresses + mempooltxs[1].forEach((Element) => { + if (this.ignoreAddresses.has(Element.sender)) { + this.ignoreAddresses.add(Element.reciever); + } + }); + const mempoolTrades: Array = mempooltxs[0]; if (mempoolTrades.length === 0) { continue; } else { - applyMempoolTradesOnPools(this.pools, mempoolTrades); + for (const trade of mempoolTrades) { + if (trade.sender && !this.ignoreAddresses.has(trade.sender)) { + applyMempoolTradesOnPools(this.pools, [trade]); + } + } } const arbTrade = this.arbitrageFunction(this.paths, this.botConfig); diff --git a/src/core/types/arbitrageloops/skipLoop.ts b/src/core/types/arbitrageloops/skipLoop.ts index d4ee9cf..bb1b41b 100644 --- a/src/core/types/arbitrageloops/skipLoop.ts +++ b/src/core/types/arbitrageloops/skipLoop.ts @@ -45,8 +45,20 @@ export class SkipLoop extends MempoolLoop { logger: Logger | undefined, pathlib: Array, + ignoreAddresses: Set, ) { - super(pools, paths, arbitrage, updateState, messageFunction, chainOperator, botConfig, logger, pathlib); + super( + pools, + paths, + arbitrage, + updateState, + messageFunction, + chainOperator, + botConfig, + logger, + pathlib, + ignoreAddresses, + ); (this.skipClient = skipClient), (this.skipSigner = skipSigner), (this.logger = logger); } @@ -75,27 +87,34 @@ export class SkipLoop extends MempoolLoop { this.totalBytes = +this.mempool.total_bytes; } - const mempoolTrades: Array = processMempool(this.mempool); + const mempooltxs: [Array, Array<{ sender: string; reciever: string }>] = processMempool( + this.mempool, + this.ignoreAddresses, + ); + const mempoolTrades = mempooltxs[0]; + mempooltxs[1].forEach((Element) => { + if (this.ignoreAddresses.has(Element.sender)) { + this.ignoreAddresses.add(Element.reciever); + } + }); + if (mempoolTrades.length === 0) { continue; } else { for (const trade of mempoolTrades) { - applyMempoolTradesOnPools(this.pools, [trade]); - - const arbTrade: OptimalTrade | undefined = this.arbitrageFunction(this.paths, this.botConfig); - - if (arbTrade) { - await this.skipTrade(arbTrade, trade); - - this.cdPaths(arbTrade.path); - - break; + if (trade.sender && !this.ignoreAddresses.has(trade.sender)) { + applyMempoolTradesOnPools(this.pools, [trade]); + const arbTrade: OptimalTrade | undefined = this.arbitrageFunction(this.paths, this.botConfig); + if (arbTrade) { + await this.skipTrade(arbTrade, trade); + //this.cdPaths(arbTrade.path); + break; + } } } } } } - /** * */ @@ -154,6 +173,13 @@ export class SkipLoop extends MempoolLoop { const logMessageCheckTx = `**CheckTx Error:** index: ${idx}\t ${String(item.log)}\n`; logMessage = logMessage.concat(logMessageCheckTx); + if (toArbTrade?.sender && idx == 0 && item["code"] == "5") { + this.ignoreAddresses.add(toArbTrade.sender); + await this.logger?.sendMessage( + "Error on Trade from Address: " + toArbTrade.sender, + LogType.Console, + ); + } } }); } @@ -164,6 +190,15 @@ export class SkipLoop extends MempoolLoop { const logMessageDeliverTx = `**DeliverTx Error:** index: ${idx}\t ${String(item.log)}\n`; logMessage = logMessage.concat(logMessageDeliverTx); + if (idx == 0 && (item["code"] == 10 || item["code"] == 5)) { + if (toArbTrade?.sender) { + this.ignoreAddresses.add(toArbTrade.sender); + await this.logger?.sendMessage( + "Error on Trade from Address: " + toArbTrade.sender, + LogType.Console, + ); + } + } } }); } diff --git a/src/core/types/base/botConfig.ts b/src/core/types/base/botConfig.ts index cf5cf48..7f1a5b3 100644 --- a/src/core/types/base/botConfig.ts +++ b/src/core/types/base/botConfig.ts @@ -1,5 +1,6 @@ import { StdFee } from "@cosmjs/stargate"; import { getStdFee } from "@injectivelabs/utils"; +import axios from "axios"; import { assert } from "console"; import { NativeAssetInfo } from "./asset"; @@ -22,7 +23,9 @@ interface LoggerConfig { export interface BotConfig { chainPrefix: string; - rpcUrl: string; + rpcUrls: Array; + useRpcUrlScraper: boolean; + ignoreAddresses: Set; poolEnvs: Array<{ pool: string; inputfee: number; outputfee: number; LPratio: number }>; maxPathPools: number; mappingFactoryRouter: Array<{ factory: string; router: string }>; @@ -49,9 +52,20 @@ export interface BotConfig { /** * */ -export function setBotConfig(envs: NodeJS.ProcessEnv): BotConfig { +export async function setBotConfig(envs: NodeJS.ProcessEnv): Promise { validateEnvs(envs); - + let RPCURLS: Array; + if (envs.RPC_URL && envs.USE_RPC_URL_SCRAPER) { + const RPCURLS_PROVIDED = envs.RPC_URL.startsWith("[") ? JSON.parse(envs.RPC_URL) : [envs.RPC_URL]; + RPCURLS = await getRPCfromRegistry(envs.CHAIN_PREFIX, RPCURLS_PROVIDED); + } else if (!envs.RPC_URL && envs.USE_RPC_URL_SCRAPER) { + RPCURLS = await getRPCfromRegistry(envs.CHAIN_PREFIX); + } else if (envs.RPC_URL) { + RPCURLS = envs.RPC_URL.startsWith("[") ? JSON.parse(envs.RPC_URL) : [envs.RPC_URL]; + } else { + console.log("no RPC URL provided or USE_RPC_URL_SCRAPER not set correctly"); + process.exit(1); + } let pools = envs.POOLS.trim() .replace(/\n|\r|\t/g, "") .replace(/,\s*$/, ""); @@ -71,6 +85,12 @@ export function setBotConfig(envs: NodeJS.ProcessEnv): BotConfig { const GAS_USAGE_PER_HOP = +envs.GAS_USAGE_PER_HOP; const MAX_PATH_HOPS = +envs.MAX_PATH_HOPS; //required gas units per trade (hop) + const IGNORE_ADDRS = new Set(); + // set ignored Addresses + if (envs.IGNORE_ADDRESSES) { + const addrs = JSON.parse(envs.IGNORE_ADDRESSES); + addrs.forEach((element: string) => IGNORE_ADDRS.add(element)); + } // setup skipconfig if present let skipConfig; if (envs.USE_SKIP == "1") { @@ -115,7 +135,8 @@ export function setBotConfig(envs: NodeJS.ProcessEnv): BotConfig { } const botConfig: BotConfig = { chainPrefix: envs.CHAIN_PREFIX, - rpcUrl: envs.RPC_URL, + rpcUrls: RPCURLS, + useRpcUrlScraper: envs.USE_RPC_URL_SCRAPER == "1" ? true : false, poolEnvs: POOLS_ENVS, maxPathPools: MAX_PATH_HOPS, mappingFactoryRouter: FACTORIES_TO_ROUTERS_MAPPING, @@ -132,6 +153,7 @@ export function setBotConfig(envs: NodeJS.ProcessEnv): BotConfig { skipConfig: skipConfig, loggerConfig: loggerConfig, signOfLife: SIGN_OF_LIFE, + ignoreAddresses: IGNORE_ADDRS, }; return botConfig; } @@ -144,10 +166,10 @@ function validateEnvs(envs: NodeJS.ProcessEnv) { assert(envs.WALLET_MNEMONIC, `Please set "WALLET_MNEMONIC" in env, or ".env" file`); assert(envs.BASE_DENOM, `Please set "BASE_DENOM" in env or ".env" file`); assert(envs.CHAIN_PREFIX, `Please set "CHAIN_PREFIX" in env or ".env" file`); - assert(envs.RPC_URL && envs.RPC_URL.includes("http"), `Please set "RPC_URL" in env or ".env" file`); assert(envs.FACTORIES_TO_ROUTERS_MAPPING, `Please set "FACTORIES_TO_ROUTERS_MAPPING" in env or ".env" file`); assert(envs.POOLS, `Please set "POOLS" in env or ".env" file`); assert(envs.FLASHLOAN_ROUTER_ADDRESS, `Please set "FLASHLOAN_ROUTER_ADDRESS" in env, or ".env" file`); + assert(envs.GAS_DENOM, `Please set "GAS_DENOM" in env or ".env" file`); } /** @@ -158,3 +180,33 @@ function validateSkipEnvs(envs: NodeJS.ProcessEnv) { assert(envs.SKIP_BID_WALLET, `Please set SKIP_BID_WALLET in env or ".env" file`); assert(envs.SKIP_BID_RATE, `Please set SKIP_BID_RATE in env or ".env" file`); } + +/** + * + */ +async function getRPCfromRegistry(prefix: string, inputurls?: Array) { + const registry = await axios.get(`https://api.github.com/repos/cosmos/chain-registry/contents/`); + let path = ""; + registry.data.forEach((elem: any) => { + if (elem.name.includes(prefix)) { + path = elem.path; + } + }); + const chaindata = await axios.get( + `https://raw.githubusercontent.com/cosmos/chain-registry/master/${path}/chain.json`, + ); + const rpcs = chaindata.data.apis.rpc; + let out: Array; + if (!inputurls) { + out = new Array(); + } else { + out = inputurls; + } + + rpcs.forEach((element: any) => { + if (!out.includes(element.address)) { + out.push(element.address); + } + }); + return out; +} diff --git a/src/core/types/base/mempool.ts b/src/core/types/base/mempool.ts index f14b391..852ca4e 100644 --- a/src/core/types/base/mempool.ts +++ b/src/core/types/base/mempool.ts @@ -1,5 +1,6 @@ import { fromAscii, fromBase64, fromUtf8 } from "@cosmjs/encoding"; import { decodeTxRaw } from "@cosmjs/proto-signing"; +import { MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx"; import { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx"; import { isSendMessage, SendMessage } from "../messages/sendmessages"; @@ -38,6 +39,7 @@ export interface MempoolTrade { | JunoSwapOperationsMessage; offer_asset: Asset | undefined; txBytes: Uint8Array; + sender: string | undefined; } let txMemory: { [key: string]: boolean } = {}; @@ -60,8 +62,11 @@ export function showTxMemory() { *@param mempool The mempool(state) to process. *@return An array of swap, send and swap-operation messages that exist in the `mempool`. */ -export function processMempool(mempool: Mempool): Array { - const mempoolTrades = []; +export function processMempool( + mempool: Mempool, + ignoreAddresses: Set, +): [Array, Array<{ sender: string; reciever: string }>] { + const mempoolTrades: [Array, Array<{ sender: string; reciever: string }>] = [[], []]; for (const tx of mempool.txs) { if (txMemory[tx] == true) { // the transaction is already processed and stored in the txMemory @@ -80,6 +85,7 @@ export function processMempool(mempool: Mempool): Array { ) { const msgExecuteContract: MsgExecuteContract = MsgExecuteContract.decode(message.value); const containedMsg = JSON.parse(fromUtf8(msgExecuteContract.msg)); + const sender = msgExecuteContract.sender; // check if the message is a swap message we want to add to the relevant trades if (isDefaultSwapMessage(containedMsg)) { @@ -90,22 +96,24 @@ export function processMempool(mempool: Mempool): Array { if (isWyndDaoTokenAsset(offerAsset.info)) { offerAsset.info = { token: { contract_addr: offerAsset.info.token } }; } - mempoolTrades.push({ + mempoolTrades[0].push({ contract: msgExecuteContract.contract, message: containedMsg, offer_asset: offerAsset, txBytes: txBytes, + sender: sender, }); continue; } // check if the message is a junoswap message we want to add to the relevant trades else if (isJunoSwapMessage(containedMsg)) { - mempoolTrades.push({ + mempoolTrades[0].push({ contract: msgExecuteContract.contract, message: containedMsg, offer_asset: undefined, txBytes: txBytes, + sender: sender, }); continue; } @@ -123,7 +131,8 @@ export function processMempool(mempool: Mempool): Array { containedMsg.send.contract, ); if (mempoolTrade) { - mempoolTrades.push(mempoolTrade); + mempoolTrade.sender = sender; + mempoolTrades[0].push(mempoolTrade); } continue; } else if (isSwapMessage(msgJson)) { @@ -134,11 +143,12 @@ export function processMempool(mempool: Mempool): Array { amount: containedMsg.send.amount, info: { token: { contract_addr: token_addr } }, }; - mempoolTrades.push({ + mempoolTrades[0].push({ contract: contract, message: containedMsg, offer_asset: offer_asset, txBytes: txBytes, + sender: sender, }); continue; } else { @@ -153,29 +163,37 @@ export function processMempool(mempool: Mempool): Array { amount: containedMsg.execute_swap_operations.routes[0].offer_amount, info: containedMsg.execute_swap_operations.routes[0].operations[0].t_f_m_swap.offer_asset_info, }; - mempoolTrades.push({ + mempoolTrades[0].push({ contract: containedMsg.execute_swap_operations.routes[0].operations[0].t_f_m_swap.pair_contract, message: containedMsg, offer_asset: offerAsset, txBytes: txBytes, + sender: sender, }); } else if (isJunoSwapOperationsMessage(containedMsg)) { - mempoolTrades.push({ + mempoolTrades[0].push({ contract: msgExecuteContract.contract, message: containedMsg, offer_asset: undefined, txBytes: txBytes, + sender: sender, }); } // check if the message is a swap-operations router message we want to add to the relevant trades else if (isSwapOperationsMessage(containedMsg)) { const mempoolTrade = processSwapOperations(containedMsg, txBytes, msgExecuteContract); if (mempoolTrade) { - mempoolTrades.push(mempoolTrade); + mempoolTrades[0].push(mempoolTrade); } + } else if (ignoreAddresses.has(msgExecuteContract.contract)) { + const gets = fromAscii(fromBase64(containedMsg.delegate.msg)); + mempoolTrades[1].push({ sender: msgExecuteContract.contract, reciever: gets }); } else { continue; } + } else if (message.typeUrl == "/cosmos.bank.v1beta1.MsgSend") { + const msgSend: MsgSend = MsgSend.decode(message.value); + mempoolTrades[1].push({ sender: msgSend.fromAddress, reciever: msgSend.toAddress }); } } } @@ -212,6 +230,7 @@ function processSwapOperations( message: containedMsg, offer_asset: offerAsset, txBytes: txBytes, + sender: msgExecuteContract?.sender, }; } if (isAstroSwapOperationsMessages(operationsMessage)) { @@ -221,6 +240,7 @@ function processSwapOperations( message: containedMsg, offer_asset: offerAsset, txBytes: txBytes, + sender: msgExecuteContract?.sender, }; } if (isWyndDaoSwapOperationsMessages(operationsMessage)) { @@ -244,6 +264,7 @@ function processSwapOperations( message: containedMsg, offer_asset: offerAsset, txBytes: txBytes, + sender: msgExecuteContract?.sender, }; } } diff --git a/src/core/types/modules.d.ts b/src/core/types/modules.d.ts index 41845c6..85ac055 100644 --- a/src/core/types/modules.d.ts +++ b/src/core/types/modules.d.ts @@ -24,8 +24,12 @@ declare namespace NodeJS { /** * The http endpoint to the RPC. */ - RPC_URL: string; + /** + * Whether or not to use cosmos chainregistry for finding public endpoints and using them + * if necessary. + */ + USE_RPC_URL_SCRAPER: string; /** * A list of all the factories to map, separated by ", \n". * diff --git a/src/index.ts b/src/index.ts index d6d8e56..f3b22da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,23 +14,6 @@ import { LogType } from "./core/types/base/logging"; import { removedUnusedPools } from "./core/types/base/pool"; // load env files dotenv.config(); -const botConfig = setBotConfig(process.env); - -let startupMessage = "===".repeat(30); -startupMessage += "\n**White Whale Bot**\n"; -startupMessage += "===".repeat(30); -startupMessage += `\nEnvironment Variables:\n -**RPC ENPDOINT:** \t${botConfig.rpcUrl} -**OFFER DENOM:** \t${JSON.stringify(botConfig.offerAssetInfo)} -**FACTORIES_TO_ROUTERS_MAPPING:** \t${JSON.stringify(botConfig.mappingFactoryRouter)} -**USE MEMPOOL:** \t${botConfig.useMempool} -**USE SKIP:** \t${botConfig.skipConfig?.useSkip} -`; -if (botConfig.skipConfig) { - startupMessage += `**SKIP URL:** \t${botConfig.skipConfig.skipRpcUrl}\n`; - startupMessage += `**SKIP BID RATE:** \t${botConfig.skipConfig.skipBidRate}\n`; -} -startupMessage += "---".repeat(30); /** * Runs the main program. @@ -41,6 +24,23 @@ startupMessage += "---".repeat(30); * */ async function main() { + const botConfig = await setBotConfig(process.env); + let startupMessage = "===".repeat(30); + startupMessage += "\n**White Whale Bot**\n"; + startupMessage += "===".repeat(30); + startupMessage += `\nEnvironment Variables:\n +**RPC ENDPOINT SCRAPER: ** \t${botConfig.useRpcUrlScraper} +**RPC ENPDOINTS:** \t${botConfig.rpcUrls} +**OFFER DENOM:** \t${JSON.stringify(botConfig.offerAssetInfo)} +**FACTORIES_TO_ROUTERS_MAPPING:** \t${JSON.stringify(botConfig.mappingFactoryRouter)} +**USE MEMPOOL:** \t${botConfig.useMempool} +**USE SKIP:** \t${botConfig.skipConfig?.useSkip} +`; + if (botConfig.skipConfig) { + startupMessage += `**SKIP URL:** \t${botConfig.skipConfig.skipRpcUrl}\n`; + startupMessage += `**SKIP BID RATE:** \t${botConfig.skipConfig.skipBidRate}\n`; + } + startupMessage += "---".repeat(30); const logger = new Logger(botConfig); let getFlashArbMessages = chains.defaults.getFlashArbMessages; let getPoolStates = chains.defaults.getPoolStates; @@ -59,7 +59,6 @@ async function main() { }); const chainOperator = await ChainOperator.connectWithSigner(botConfig); let setupMessage = "---".repeat(30); - const allPools = await initPools(chainOperator, botConfig.poolEnvs, botConfig.mappingFactoryRouter); const graph = newGraph(allPools); const paths = getPaths(graph, botConfig.offerAssetInfo, botConfig.maxPathPools) ?? []; @@ -79,6 +78,7 @@ Total Paths:** \t${paths.length}\n`; await logger.sendMessage(startupMessage, LogType.Console); let loop; + if (botConfig.skipConfig) { await logger.sendMessage("Initializing skip loop...", LogType.Console); const [skipClient, skipSigner] = await getSkipClient( @@ -98,6 +98,7 @@ Total Paths:** \t${paths.length}\n`; skipSigner, logger, [...paths], + botConfig.ignoreAddresses, ); } else if (botConfig.useMempool === true) { await logger.sendMessage("Initializing mempool loop...", LogType.Console); @@ -112,6 +113,7 @@ Total Paths:** \t${paths.length}\n`; botConfig, logger, [...paths], + botConfig.ignoreAddresses, ); } else { await logger.sendMessage("Initializing non-mempool loop...", LogType.Console); @@ -143,6 +145,10 @@ Total Paths:** \t${paths.length}\n`; const message = `**chain:** ${chainOperator.client.chainId} **wallet:** **status:** running for ${ loop.iterations } blocks or ${hours === 0 ? "" : hours + " Hour(s) and "}${mins} Minutes`; + //switching RPCS every 6 Hrs + if (mins == 0 && hours === 6) { + await chainOperator.client.getNewClients(); + } await logger.sendMessage(message); } }