diff --git a/packages/cli/README.md b/packages/cli/README.md index b497998c..79fce90f 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -49,7 +49,6 @@ Usage: venus release-funds [options] Options: --simulate Simulate transactions (default: false) - --verbose Verbose logging (default: false) --accrue-interest Accrue Interest (default: false) --no-reduce-reserves Reduce BNB Reserves -d, --debug Add debug logging (default: false) diff --git a/packages/cli/package.json b/packages/cli/package.json index 02f48492..404473c2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@venusprotocol/cli", - "version": "1.5.0-dev.1", + "version": "1.5.0-dev.2", "license": "MIT", "bin": { "venus": "dist/venus.js" diff --git a/packages/cli/source/commands/convert.tsx b/packages/cli/source/commands/convert.tsx index 5aba717d..11ce82b6 100644 --- a/packages/cli/source/commands/convert.tsx +++ b/packages/cli/source/commands/convert.tsx @@ -3,7 +3,7 @@ import { option } from "pastel"; import { Box, Spacer, Text, useApp, useStderr } from "ink"; import zod from "zod"; import { parseUnits } from "viem"; -import { TokenConverter, PancakeSwapProvider, UniswapProvider, BalanceResult } from "@venusprotocol/keeper-bots"; +import { TokenConverter, PancakeSwapProvider, UniswapProvider } from "@venusprotocol/keeper-bots"; import { stringifyBigInt, getConverterConfigId } from "../utils/index.js"; import { Options, Title, BorderBox } from "../components/index.js"; import { reducer, defaultState } from "../state/convert.js"; @@ -152,10 +152,7 @@ export default function Convert({ options }: Props) { const tokenConverter = new TokenConverter({ subscriber: dispatch, simulate: !!simulate, - verbose: debug, - swapProvider: network?.includes("bsc") - ? new PancakeSwapProvider({ subscriber: dispatch }) - : new UniswapProvider({ subscriber: dispatch }), + swapProvider: network?.includes("bsc") ? PancakeSwapProvider : UniswapProvider, }); do { @@ -173,84 +170,69 @@ export default function Convert({ options }: Props) { // @todo check if we need to release funds or if there are already enough funds to make our trade await tokenConverter.releaseFundsForConversions(potentialConversions); } - await Promise.allSettled( - potentialConversions.map(async (t: BalanceResult) => { - let amountOut = t.assetOut.balance; - const vTokenAddress = t.assetOutVTokens.core || t.assetOutVTokens.isolated![0]![1]; - const { underlyingPriceUsd, underlyingUsdValue, underlyingDecimals } = await tokenConverter.getUsdValue( - t.assetOut.address, - vTokenAddress, - amountOut, - ); + for (const t of potentialConversions) { + let amountOut = t.assetOut.balance; - if (+underlyingUsdValue > minTradeUsd) { - if (+underlyingUsdValue > maxTradeUsd) { - amountOut = parseUnits((maxTradeUsd / +underlyingPriceUsd.toString()).toString(), underlyingDecimals); - } + const vTokenAddress = t.assetOutVTokens.core || t.assetOutVTokens.isolated![0]![1]; + const { underlyingPriceUsd, underlyingUsdValue, underlyingDecimals } = await tokenConverter.getUsdValue( + t.assetOut.address, + vTokenAddress, + amountOut, + ); - const arbitrageArgs = await tokenConverter.prepareConversion( - t.tokenConverter, - t.assetOut.address, - t.assetIn.address, - amountOut, - ); + if (+underlyingUsdValue > minTradeUsd) { + if (+underlyingUsdValue > maxTradeUsd) { + amountOut = parseUnits((maxTradeUsd / +underlyingPriceUsd.toString()).toString(), underlyingDecimals); + } - const { trade, amount, minIncome } = arbitrageArgs || { - trade: undefined, - amount: 0n, - minIncome: 0n, - }; + const arbitrageArgs = await tokenConverter.prepareConversion( + t.tokenConverter, + t.assetOut.address, + t.assetIn.address, + amountOut, + ); - const maxMinIncome = ((amount * BigInt(10000 + minIncomeBp)) / 10000n - amount) * -1n; + const { trade, amount, minIncome } = arbitrageArgs || { + trade: undefined, + amount: 0n, + minIncome: 0n, + }; - if (trade && ((profitable && minIncome > 0n) || !profitable)) { - dispatch({ - type: "ExecuteTrade", - context: { - converter: t.tokenConverter, - tokenToReceiveFromConverter: t.assetOut.address, - tokenToSendToConverter: t.assetIn.address, - amount, - minIncome, - percentage: Number((minIncome * 10000000n) / amount) / 10000000, - maxMinIncome, - }, - }); + const maxMinIncome = ((amount * BigInt(10000 + minIncomeBp)) / 10000n - amount) * -1n; - await tokenConverter.arbitrage(t.tokenConverter, trade, amount, minIncome); - } else if (t.accountBalanceAssetOut < minIncome * -1n) { - dispatch({ - type: "ExecuteTrade", - error: "Insufficient wallet balance to pay min income", - context: { - converter: t.tokenConverter, - tokenToReceiveFromConverter: t.assetOut.address, - tokenToSendToConverter: t.assetIn.address, - amount, - minIncome, - percentage: Number((minIncome * 10000000n) / amount) / 10000000, - maxMinIncome, - }, - }); - } else if (minIncome < 1 && minIncome * -1n > maxMinIncome * -1n) { - dispatch({ - type: "ExecuteTrade", - error: "Min income too high", - context: { - converter: t.tokenConverter, - tokenToReceiveFromConverter: t.assetOut.address, - tokenToSendToConverter: t.assetIn.address, - amount, - minIncome, - percentage: Number((minIncome * 10000000n) / amount) / 10000000, - maxMinIncome, - }, - }); - } + const context = { + converter: t.tokenConverter, + tokenToReceiveFromConverter: t.assetOut.address, + tokenToSendToConverter: t.assetIn.address, + amount, + minIncome, + percentage: Number(minIncome) && Number(amount) && Number((minIncome * 10000000n) / amount) / 10000000, + maxMinIncome, + }; + if (profitable && minIncome < 0) { + dispatch({ + error: "Conversion is not profitable", + type: "ExecuteTrade", + context, + }); + } else if (minIncome < 1 && minIncome * -1n > maxMinIncome * -1n) { + dispatch({ + type: "ExecuteTrade", + error: "Min income too high", + context, + }); + } else if (t.accountBalanceAssetOut < minIncome * -1n && !profitable) { + dispatch({ + error: "Insufficient wallet balance to pay min income", + type: "ExecuteTrade", + context, + }); + } else if (trade) { + await tokenConverter.arbitrage(t.tokenConverter, trade, amount, minIncome); } - }), - ); + } + } } while (loop); }; if (converter || assetIn || assetOut) { diff --git a/packages/cli/source/commands/releaseFunds.tsx b/packages/cli/source/commands/releaseFunds.tsx index 2b051ef2..e57be350 100644 --- a/packages/cli/source/commands/releaseFunds.tsx +++ b/packages/cli/source/commands/releaseFunds.tsx @@ -29,16 +29,6 @@ export const options = zod.object({ }), ) .optional(), - verbose: zod - .boolean() - .default(false) - .describe( - option({ - description: "Verbose logging", - alias: "v", - }), - ) - .optional(), accrueInterest: zod .boolean() .default(false) @@ -136,10 +126,7 @@ function ReleaseFunds({ options = {} }: Props) { const tokenConverter = new TokenConverter({ subscriber: dispatch, simulate: !!simulate, - verbose: false, - swapProvider: network?.includes("bsc") - ? new PancakeSwapProvider({ subscriber: dispatch, verbose: false }) - : new UniswapProvider({ subscriber: dispatch, verbose: false }), + swapProvider: network?.includes("bsc") ? PancakeSwapProvider : UniswapProvider, }); const corePoolMarkets = await getCoreMarkets(); const isolatedPoolsMarkets = await getIsolatedMarkets(); diff --git a/packages/cli/source/state/convert.ts b/packages/cli/source/state/convert.ts index 319f8044..334a3c35 100644 --- a/packages/cli/source/state/convert.ts +++ b/packages/cli/source/state/convert.ts @@ -1,6 +1,6 @@ import { Address } from "viem"; import { - Message, + ConverterBotMessage, GetBestTradeMessage, ArbitrageMessage, PotentialConversionsMessage, @@ -43,7 +43,7 @@ export const defaultState = { messages: [], }; -export const reducer = (state: State, action: Message | ExecuteTradeMessage): State => { +export const reducer = (state: State, action: ConverterBotMessage | ExecuteTradeMessage): State => { switch (action.type) { case "PotentialConversions": case "GetBestTrade": diff --git a/packages/cli/source/state/releaseFunds.ts b/packages/cli/source/state/releaseFunds.ts index fcbf6d8c..4792bcd0 100644 --- a/packages/cli/source/state/releaseFunds.ts +++ b/packages/cli/source/state/releaseFunds.ts @@ -1,9 +1,9 @@ -import { Message } from "@venusprotocol/keeper-bots"; +import { ConverterBotMessage } from "@venusprotocol/keeper-bots"; interface State { releasedFunds: { trx: string | undefined; - error: string | undefined; + error: string | string[] | undefined; context: [`0x${string}`, readonly `0x${string}`[]]; }[]; } @@ -12,7 +12,7 @@ export const defaultState = { releasedFunds: [], }; -export const reducer = (state: State, action: Message): State => { +export const reducer = (state: State, action: ConverterBotMessage): State => { switch (action.type) { case "ReleaseFunds": { const releasedFunds = [...state.releasedFunds]; diff --git a/packages/keeper-bots/.env.test b/packages/keeper-bots/.env.test index 2d98392b..62a87d25 100644 --- a/packages/keeper-bots/.env.test +++ b/packages/keeper-bots/.env.test @@ -7,3 +7,4 @@ PRIVATE_KEY_bscmainnet=0x0000000000000000000000000000000000000000000000000000000 RPC_bsctestnet=https://bsc-testnet.nodereal.io/v1/apiKey RPC_bscmainnet=https://bsc-mainnet.nodereal.io/v1/apiKey THE_GRAPH_STUDIO_API_KEY=1234567890 +TESTNET_GRAPH_ID= \ No newline at end of file diff --git a/packages/keeper-bots/package.json b/packages/keeper-bots/package.json index 092d201a..254f6e01 100644 --- a/packages/keeper-bots/package.json +++ b/packages/keeper-bots/package.json @@ -1,6 +1,6 @@ { "name": "@venusprotocol/keeper-bots", - "version": "1.0.0-dev.1", + "version": "1.0.0-dev.2", "description": "", "scripts": { "dev": "tsc --watch", @@ -42,8 +42,7 @@ "hardhat": "^2.19.5", "jsbi": "^3.2.5", "urql": "^3.0.3", - "viem": "^2.7.1", - "winston": "^3.11.0" + "viem": "^2.7.1" }, "devDependencies": { "@typechain/hardhat": "^6.1.2", diff --git a/packages/keeper-bots/src/bot-base.ts b/packages/keeper-bots/src/bot-base.ts new file mode 100644 index 00000000..6c3bb340 --- /dev/null +++ b/packages/keeper-bots/src/bot-base.ts @@ -0,0 +1,47 @@ +import getConfig from "./config"; +import type { SUPPORTED_CHAINS } from "./config/chains"; +import publicClient from "./config/clients/publicClient"; +import walletClient from "./config/clients/walletClient"; +import { ConverterBotMessage } from "./converter-bot/types"; +import { SwapProvider } from "./providers"; + +const config = getConfig(); + +class BotBase { + protected chainName: SUPPORTED_CHAINS; + protected subscriber: undefined | ((msg: ConverterBotMessage) => void); + protected simulate: boolean; + protected swapProvider: SwapProvider; + public publicClient: typeof publicClient; + public walletClient: typeof walletClient; + + constructor({ + subscriber, + swapProvider, + simulate, + }: { + subscriber?: (msg: ConverterBotMessage) => void; + swapProvider: typeof SwapProvider; + simulate: boolean; + }) { + this.subscriber = subscriber; + this.swapProvider = new swapProvider({ subscriber }); + this.publicClient = publicClient; + this.walletClient = walletClient; + this.simulate = simulate; + this.chainName = config.network.name; + } + protected sendMessage({ + type, + trx = undefined, + error = undefined, + context = undefined, + blockNumber = undefined, + }: Partial) { + if (this.subscriber) { + this.subscriber({ type, trx, error, context, blockNumber } as ConverterBotMessage); + } + } +} + +export default BotBase; diff --git a/packages/keeper-bots/src/config/clients/__mocks__/publicClient.ts b/packages/keeper-bots/src/config/clients/__mocks__/publicClient.ts index 5d30bc55..03563e97 100644 --- a/packages/keeper-bots/src/config/clients/__mocks__/publicClient.ts +++ b/packages/keeper-bots/src/config/clients/__mocks__/publicClient.ts @@ -1,4 +1,4 @@ -const getPublicClientMock = () => ({ +const publicClientMock = { simulateContract: jest.fn(), readContract: jest.fn(), multicall: jest.fn(), @@ -6,6 +6,6 @@ const getPublicClientMock = () => ({ getBlockNumber: jest.fn(), waitForTransactionReceipt: jest.fn(), estimateContractGas: jest.fn(), -}); +}; -export default getPublicClientMock; +export default publicClientMock; diff --git a/packages/keeper-bots/src/config/clients/__mocks__/walletClient.ts b/packages/keeper-bots/src/config/clients/__mocks__/walletClient.ts index ad04a4dd..c5750723 100644 --- a/packages/keeper-bots/src/config/clients/__mocks__/walletClient.ts +++ b/packages/keeper-bots/src/config/clients/__mocks__/walletClient.ts @@ -1,8 +1,8 @@ -const getWalletClientMock = () => ({ +const walletClientMock = { account: { address: "0x4CCeBa2d7D2B4fdcE4304d3e09a1fea9fbEb1528", }, writeContract: jest.fn(() => "0xtransactionHash"), -}); +}; -export default getWalletClientMock; +export default walletClientMock; diff --git a/packages/keeper-bots/src/config/clients/publicClient.ts b/packages/keeper-bots/src/config/clients/publicClient.ts index d04b134f..6f55e8c9 100644 --- a/packages/keeper-bots/src/config/clients/publicClient.ts +++ b/packages/keeper-bots/src/config/clients/publicClient.ts @@ -12,4 +12,4 @@ const getPublicClient = () => { }); }; -export default getPublicClient; +export default getPublicClient(); diff --git a/packages/keeper-bots/src/config/clients/walletClient.ts b/packages/keeper-bots/src/config/clients/walletClient.ts index a648c54c..c20deebb 100644 --- a/packages/keeper-bots/src/config/clients/walletClient.ts +++ b/packages/keeper-bots/src/config/clients/walletClient.ts @@ -22,4 +22,4 @@ const getWalletClient = () => { }); }; -export default getWalletClient; +export default getWalletClient(); diff --git a/packages/keeper-bots/src/converter-bot/TokenConverter.test.ts b/packages/keeper-bots/src/converter-bot/TokenConverter.test.ts index e98e24f7..7c3e3c97 100644 --- a/packages/keeper-bots/src/converter-bot/TokenConverter.test.ts +++ b/packages/keeper-bots/src/converter-bot/TokenConverter.test.ts @@ -9,8 +9,10 @@ import { tokenConverterOperatorAbi, vBnbAdminAbi, } from "../config/abis/generated"; +import publicClient from "../config/clients/publicClient"; +import walletClient from "../config/clients/walletClient"; +import { PancakeSwapProvider, UniswapProvider } from "../providers"; import TokenConverter from "./TokenConverter"; -import { PancakeSwapProvider, UniswapProvider } from "./providers"; import readTokenConvertersTokenBalances, { BalanceResult } from "./queries/getTokenConvertersTokenBalances"; jest.mock("@pancakeswap/smart-router/evm"); @@ -41,19 +43,27 @@ const addresses = { const createTokenConverterInstance = ({ simulate = false }: { simulate: boolean } = { simulate: false }) => { const subscriberMock = jest.fn(); - const pancakeSwapProvider = new PancakeSwapProvider({ subscriber: subscriberMock, verbose: false }); - (pancakeSwapProvider.publicClient.multicall as jest.Mock).mockImplementation(() => [ - { result: 18 }, - { result: "USDT" }, - ]); - (pancakeSwapProvider.publicClient.simulateContract as jest.Mock).mockImplementation(() => ({ - result: [1000000000000000000n, 1000000000000000000n], - })); + const pancakeSwapProvider = new PancakeSwapProvider({ subscriber: subscriberMock }); + + (publicClient.multicall as unknown as jest.Mock).mockImplementation( + jest.fn(() => [{ result: 18 }, { result: "USDT" }]), + ); + + (publicClient.simulateContract as unknown as jest.Mock).mockImplementation( + jest.fn(() => ({ + result: [1000000000000000000n, 1000000000000000000n], + })), + ); + (publicClient.getBlock as unknown as jest.Mock).mockImplementation(jest.fn(() => ({ timestamp: 1713214109n }))); + (publicClient.waitForTransactionReceipt as unknown as jest.Mock).mockImplementation( + jest.fn(() => ({ blockNumber: 23486902n })), + ); + (publicClient.estimateContractGas as unknown as jest.Mock).mockImplementation(jest.fn(() => {})); + const tokenConverter = new TokenConverter({ subscriber: subscriberMock, - verbose: false, simulate, - swapProvider: pancakeSwapProvider, + swapProvider: PancakeSwapProvider, }); return { tokenConverter, pancakeSwapProvider, subscriberMock }; }; @@ -64,16 +74,14 @@ describe("Token Converter", () => { (SmartRouter.getBestTrade as jest.Mock).mockImplementation(() => mockRoute); }); + afterEach(() => { + (walletClient.writeContract as jest.Mock).mockClear(); + (publicClient.simulateContract as jest.Mock).mockClear(); + }); + describe("getBestTrade", () => { test("should throw error if not trade found", async () => { const { tokenConverter } = createTokenConverterInstance(); - (tokenConverter.publicClient.multicall as jest.Mock).mockImplementation(() => [ - { result: 18 }, - { result: "USDT" }, - ]); - (tokenConverter.publicClient.simulateContract as jest.Mock).mockImplementation(() => ({ - result: [1000000000000000000n, 1000000000000000000n], - })); (SmartRouter.getBestTrade as jest.Mock).mockImplementationOnce(() => null); @@ -90,13 +98,6 @@ describe("Token Converter", () => { test("should handle thrown error", async () => { const { tokenConverter } = createTokenConverterInstance(); - (tokenConverter.publicClient.multicall as jest.Mock).mockImplementation(() => [ - { result: 18 }, - { result: "USDT" }, - ]); - (tokenConverter.publicClient.simulateContract as jest.Mock).mockImplementation(() => ({ - result: [1000000000000000000n, 1000000000000000000n], - })); (SmartRouter.getBestTrade as jest.Mock).mockImplementationOnce(() => { throw new Error("Cannot find a valid swap route"); @@ -114,14 +115,8 @@ describe("Token Converter", () => { test("should return trade with low price impact", async () => { const { tokenConverter } = createTokenConverterInstance(); - (tokenConverter.publicClient.multicall as jest.Mock).mockImplementation(() => [ - { result: 18 }, - { result: "USDT" }, - ]); - (tokenConverter.publicClient.simulateContract as jest.Mock).mockImplementation(() => ({ - result: [1000000000000000000n, 1000000000000000000n], - })); - const [trade, updatedAmountIn] = await tokenConverter.getBestTrade( + + const [trade] = await tokenConverter.getBestTrade( addresses.USDCPrimeConverter, addresses.WBNB, addresses.USDC, @@ -167,18 +162,10 @@ describe("Token Converter", () => { expect(trade.path).toEqual( "0xcf6bb5389c92bdda8a3747ddb454cb7a64626c630009c4bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", ); - expect(updatedAmountIn).toEqual([1000000000000000000n, 1000000000000000000n]); }); test("should call getBestTrade again if price impact is high with lower amount", async () => { - const { tokenConverter, subscriberMock, pancakeSwapProvider } = createTokenConverterInstance(); - (tokenConverter.publicClient.multicall as jest.Mock).mockImplementation(() => [ - { result: 18 }, - { result: "USDT" }, - ]); - (tokenConverter.publicClient.simulateContract as jest.Mock).mockImplementation(() => ({ - result: [1000000000000000000n, 1000000000000000000n], - })); + const { subscriberMock, pancakeSwapProvider } = createTokenConverterInstance(); const pancakeSwapProviderMock = jest .spyOn(PancakeSwapProvider.prototype, "getBestTrade") @@ -193,7 +180,7 @@ describe("Token Converter", () => { return new Percent(9n, 1000n); }); - const [trade, updatedAmountIn] = await pancakeSwapProvider.getBestTrade( + const [trade] = await pancakeSwapProvider.getBestTrade( addresses.USDCPrimeConverter, addresses.WBNB, addresses.USDC, @@ -239,7 +226,6 @@ describe("Token Converter", () => { expect(trade.path).toEqual( "0xcf6bb5389c92bdda8a3747ddb454cb7a64626c630009c4bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", ); - expect(updatedAmountIn).toEqual([1000000000000000000n, 1000000000000000000n]); expect(pancakeSwapProviderMock).toHaveBeenNthCalledWith( 3, @@ -273,7 +259,7 @@ describe("Token Converter", () => { describe("encodeExactInputPath - UniswapProvider", () => { const subscriberMock = jest.fn(); - const uniswapProvider = new UniswapProvider({ subscriber: subscriberMock, verbose: false }); + const uniswapProvider = new UniswapProvider({ subscriber: subscriberMock }); expect(uniswapProvider.encodeExactInputPath(mockUniswapRoute.trade.routes[0])).toEqual( "0xcf6bb5389c92bdda8a3747ddb454cb7a64626c630001f4bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", @@ -492,7 +478,7 @@ describe("Token Converter", () => { test("should report error if reduce reserves is unsuccessful", async () => { const { tokenConverter, subscriberMock } = createTokenConverterInstance(); - (tokenConverter.walletClient.writeContract as jest.Mock).mockImplementation(() => { + (tokenConverter.walletClient.writeContract as jest.Mock).mockImplementationOnce(() => { throw new Error("NETWORK ERROR"); }); @@ -630,6 +616,7 @@ describe("Token Converter", () => { test("should execute the release of funds", async () => { const { tokenConverter, subscriberMock } = createTokenConverterInstance(); + await tokenConverter.releaseFunds({ [addresses.stableCoinComptroller]: [addresses.WBNB], [addresses.coreComptroller]: [addresses.BUSD], @@ -653,8 +640,8 @@ describe("Token Converter", () => { test("should report error", async () => { const { tokenConverter, subscriberMock } = createTokenConverterInstance(); - (tokenConverter.walletClient.writeContract as jest.Mock).mockImplementationOnce(() => { - throw new Error("Failed Transation"); + (tokenConverter.walletClient.writeContract as jest.Mock).mockImplementation(() => { + throw new Error("Failed Transaction"); }); await tokenConverter.releaseFunds({ @@ -672,7 +659,7 @@ describe("Token Converter", () => { expect(subscriberMock).toHaveBeenCalledWith({ type: "ReleaseFunds", trx: undefined, - error: "Failed Transation", + error: "Failed Transaction", blockNumber: undefined, context: [addresses.stableCoinComptroller, [addresses.WBNB]], }); @@ -698,7 +685,8 @@ describe("Token Converter", () => { describe("checkAndRequestAllowance", () => { test("should request approval if needed", async () => { const { tokenConverter } = createTokenConverterInstance(); - (tokenConverter.publicClient.readContract as jest.Mock).mockImplementationOnce(() => 0n); + (tokenConverter.walletClient.writeContract as jest.Mock).mockReset(); + (publicClient.readContract as jest.Mock).mockImplementationOnce(() => 0n); await tokenConverter.checkAndRequestAllowance( addresses.usdcHolder, @@ -717,7 +705,8 @@ describe("Token Converter", () => { test("should do nothing if already approved", async () => { const { tokenConverter } = createTokenConverterInstance(); - (tokenConverter.publicClient.readContract as jest.Mock).mockImplementationOnce(() => 2000000000000000000n); + (tokenConverter.walletClient.writeContract as jest.Mock).mockReset(); + (publicClient.readContract as jest.Mock).mockImplementationOnce(() => 2000000000000000000n); await tokenConverter.checkAndRequestAllowance( addresses.usdcHolder, @@ -733,11 +722,7 @@ describe("Token Converter", () => { describe("arbitrage", () => { test("should simulate arbitrage", async () => { const { tokenConverter, subscriberMock } = createTokenConverterInstance({ simulate: true }); - (tokenConverter.publicClient.getBlock as jest.Mock).mockImplementation(() => ({ timestamp: 1713214109n })); - (tokenConverter.publicClient.waitForTransactionReceipt as jest.Mock).mockImplementation(() => ({ - blockNumber: 23486902n, - })); - + (tokenConverter.walletClient.writeContract as jest.Mock).mockReset(); const tokenConverterMock = jest .spyOn(TokenConverter.prototype, "checkAndRequestAllowance") .mockImplementationOnce(jest.fn()); @@ -789,14 +774,10 @@ describe("Token Converter", () => { test("should arbitrage and check approvals if minIncome is negative", async () => { const { tokenConverter, subscriberMock } = createTokenConverterInstance(); - (tokenConverter.publicClient.getBlock as jest.Mock).mockImplementation(() => ({ timestamp: 1713214109n })); - (tokenConverter.publicClient.waitForTransactionReceipt as jest.Mock).mockImplementation(() => ({ - blockNumber: 23486902n, - })); const tokenConverterMock = jest .spyOn(TokenConverter.prototype, "checkAndRequestAllowance") .mockImplementationOnce(jest.fn()); - + (walletClient.writeContract as jest.Mock).mockImplementation(() => "0xtransactionHash"); await tokenConverter.arbitrage(addresses.USDCPrimeConverter, mockTrade, 1000000000000000000000n, -10000n); expect(tokenConverterMock).toHaveBeenNthCalledWith( @@ -848,11 +829,8 @@ describe("Token Converter", () => { test("should handle error calling arbitrage", async () => { const { tokenConverter, subscriberMock } = createTokenConverterInstance(); - (tokenConverter.publicClient.getBlock as jest.Mock).mockImplementation(() => ({ timestamp: 1713214109n })); - (tokenConverter.publicClient.waitForTransactionReceipt as jest.Mock).mockImplementation(() => ({ - blockNumber: 23486902n, - })); - (tokenConverter.walletClient.writeContract as jest.Mock).mockImplementation(() => { + + (tokenConverter.walletClient.writeContract as jest.Mock).mockImplementationOnce(() => { throw new Error("NETWORK ERROR"); }); @@ -883,13 +861,6 @@ describe("Token Converter", () => { describe("prepareConversion", () => { test("should correctly return conversion arguments", async () => { const { tokenConverter, subscriberMock } = createTokenConverterInstance(); - (tokenConverter.publicClient.multicall as jest.Mock).mockImplementation(() => [ - { result: 18 }, - { result: "USDT" }, - ]); - (tokenConverter.publicClient.simulateContract as jest.Mock).mockImplementation(() => ({ - result: [1000000000000000000n, 1000000000000000000n], - })); const arbitrageArgs = await tokenConverter.prepareConversion( addresses.USDCPrimeConverter, @@ -898,7 +869,7 @@ describe("Token Converter", () => { 1000000000000000000000n, ); - expect(arbitrageArgs?.amount).toEqual(1000000000000000000n); + expect(arbitrageArgs?.amount).toEqual(8904975230019520420n); expect(arbitrageArgs?.trade.inputToken).toEqual({ address: "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c", amount: { @@ -959,23 +930,12 @@ describe("Token Converter", () => { }, tokenToReceiveFromConverter: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", tokenToSendToConverter: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", - tradeAmount: { - amountIn: 1000000000000000000n, - amountOut: 1000000000000000000n, - }, }, }); }); test("should handle error calling prepareConversion", async () => { const { tokenConverter, subscriberMock } = createTokenConverterInstance(); - (tokenConverter.publicClient.multicall as jest.Mock).mockImplementation(() => [ - { result: 18 }, - { result: "USDT" }, - ]); - (tokenConverter.publicClient.simulateContract as jest.Mock).mockImplementation(() => ({ - result: [1000000000000000000n, 1000000000000000000n], - })); (SmartRouter.getPriceImpact as jest.Mock).mockImplementation(() => new Percent(2n, 1000n)); (SmartRouter.getBestTrade as jest.Mock).mockImplementation(() => { diff --git a/packages/keeper-bots/src/converter-bot/TokenConverter.ts b/packages/keeper-bots/src/converter-bot/TokenConverter.ts index f4603c6b..0882116c 100644 --- a/packages/keeper-bots/src/converter-bot/TokenConverter.ts +++ b/packages/keeper-bots/src/converter-bot/TokenConverter.ts @@ -1,7 +1,7 @@ import { Fraction } from "@pancakeswap/sdk"; import { Address, BaseError, ContractFunctionRevertedError, erc20Abi, formatUnits } from "viem"; -import getConfig from "../config"; +import BotBase from "../bot-base"; import { coreVTokenAbi, poolLensAbi, @@ -11,78 +11,34 @@ import { venusLensAbi, } from "../config/abis/generated"; import getAddresses from "../config/addresses"; -import type { SUPPORTED_CHAINS } from "../config/chains"; -import getPublicClient from "../config/clients/publicClient"; -import getWalletClient from "../config/clients/walletClient"; -import logger from "./logger"; -import { SwapProvider } from "./providers"; +import { SwapProvider } from "../providers"; +import { TradeRoute } from "../types"; import getConverterConfigs from "./queries/getTokenConverterConfigs"; import getTokenConvertersTokenBalances, { BalanceResult } from "./queries/getTokenConvertersTokenBalances"; -import { GetBestTradeMessage, MarketAddresses, Message, TradeRoute } from "./types"; - -const config = getConfig(); +import { ConverterBotMessage, GetBestTradeMessage, MarketAddresses } from "./types"; const REVERT_IF_NOT_MINED_AFTER = 60n; // seconds -export class TokenConverter { - private chainName: SUPPORTED_CHAINS; +export class TokenConverter extends BotBase { private addresses: ReturnType; private operator: { address: Address; abi: typeof tokenConverterOperatorAbi }; - private subscriber: undefined | ((msg: Message) => void); - private simulate: boolean; - private verbose: boolean; - private swapProvider: SwapProvider; - public publicClient: ReturnType; - public walletClient: ReturnType; constructor({ subscriber, swapProvider, simulate, - verbose = false, }: { - subscriber?: (msg: Message) => void; - swapProvider: SwapProvider; + subscriber?: (msg: ConverterBotMessage) => void; + swapProvider: typeof SwapProvider; simulate: boolean; - verbose: boolean; }) { + super({ subscriber, simulate, swapProvider }); this.addresses = getAddresses(); - this.swapProvider = swapProvider; - this.chainName = config.network.name; - this.subscriber = subscriber; + this.swapProvider = new swapProvider({ subscriber }); this.operator = { address: this.addresses.TokenConverterOperator, abi: tokenConverterOperatorAbi, }; - this.publicClient = getPublicClient(); - this.walletClient = getWalletClient(); - this.simulate = simulate; - this.verbose = verbose; - } - - /** - * Function to post message to subscriber - * @param Message - * - */ - private sendMessage({ - type, - trx = undefined, - error = undefined, - context = undefined, - blockNumber = undefined, - }: Partial & Pick) { - if (this.subscriber) { - this.subscriber({ type, trx, error, context, blockNumber } as Message); - } - - if (this.verbose) { - if (error) { - logger.error(Array.isArray(error) ? error.join(",") : error, context); - } else { - logger.info(`${type} - ${trx}`, context); - } - } } /** @@ -98,7 +54,7 @@ export class TokenConverter { swapFrom: Address, swapTo: Address, amount: bigint, - ): Promise<[TradeRoute, readonly [bigint, bigint]]> { + ): Promise<[TradeRoute, bigint]> { return this.swapProvider.getBestTrade(tokenConverter, swapFrom, swapTo, amount); } @@ -432,16 +388,16 @@ export class TokenConverter { async prepareConversion(tokenConverter: Address, assetOut: Address, assetIn: Address, amountOut: bigint) { let error; let trade; - let tradeAmount; + let amount; + try { - [trade, tradeAmount] = await this.getBestTrade(tokenConverter, assetOut, assetIn, amountOut); + [trade, amount] = await this.getBestTrade(tokenConverter, assetOut, assetIn, amountOut); } catch (e) { error = (e as Error).message; } finally { - let tradeContext: Pick = {}; - if (trade && tradeAmount) { + let tradeContext: Pick = {}; + if (trade) { tradeContext = { - tradeAmount: { amountOut: tradeAmount && tradeAmount[0], amountIn: tradeAmount && tradeAmount[1] }, swap: { inputToken: { amount: trade.inputToken.amount.toFixed(0), @@ -467,13 +423,13 @@ export class TokenConverter { }); } - if (trade && tradeAmount) { + if (trade && amount) { // the difference between the token you get from TokenConverter and the token you pay to the MM - const minIncome = new Fraction(tradeAmount[0], 1).subtract(trade.inputToken.amount); + const minIncome = new Fraction(amount).subtract(trade.inputToken.amount); return { trade, - amount: tradeAmount[0], - minIncome: BigInt(minIncome.toFixed(0, { groupSeparator: "" })), + amount: trade.inputToken.amount.quotient, + minIncome: minIncome.quotient, }; } } diff --git a/packages/keeper-bots/src/converter-bot/logger/index.ts b/packages/keeper-bots/src/converter-bot/logger/index.ts deleted file mode 100644 index 2e10bb17..00000000 --- a/packages/keeper-bots/src/converter-bot/logger/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import winston from "winston"; - -const logger = winston.createLogger({ - format: winston.format.combine(winston.format.json()), - transports: [new winston.transports.Console()], -}); - -export default logger; diff --git a/packages/keeper-bots/src/converter-bot/queries/getCoreMarkets.ts b/packages/keeper-bots/src/converter-bot/queries/getCoreMarkets.ts index a298039f..133f89fc 100644 --- a/packages/keeper-bots/src/converter-bot/queries/getCoreMarkets.ts +++ b/packages/keeper-bots/src/converter-bot/queries/getCoreMarkets.ts @@ -3,14 +3,13 @@ import { Address } from "viem"; import getConfig from "../../config"; import { coreComptrollerAbi, vBep20InterfaceAbi } from "../../config/abis/generated"; import getAddresses from "../../config/addresses"; -import getPublicClient from "../../config/clients/publicClient"; +import publicClient from "../../config/clients/publicClient"; import type { PoolAddressArray } from "../types"; export const getCoreMarkets = async (): Promise => { const config = getConfig(); if (config.network.name === "bscmainnet" || config.network.name === "bsctestnet") { const addresses = getAddresses(); - const publicClient = getPublicClient(); const markets = await publicClient.readContract({ address: addresses.Unitroller as Address, abi: coreComptrollerAbi, diff --git a/packages/keeper-bots/src/converter-bot/queries/getIsolatedMarkets.ts b/packages/keeper-bots/src/converter-bot/queries/getIsolatedMarkets.ts index c5bc6484..4dc57953 100644 --- a/packages/keeper-bots/src/converter-bot/queries/getIsolatedMarkets.ts +++ b/packages/keeper-bots/src/converter-bot/queries/getIsolatedMarkets.ts @@ -3,12 +3,11 @@ import { Address } from "viem"; import { vBep20InterfaceAbi } from "../../config/abis/generated"; import { poolLensAbi } from "../../config/abis/generated"; import getAddresses from "../../config/addresses"; -import getPublicClient from "../../config/clients/publicClient"; +import publicClient from "../../config/clients/publicClient"; import type { PoolAddressArray } from "../types"; export const getIsolatedMarkets = async (): Promise => { const addresses = getAddresses(); - const publicClient = getPublicClient(); const pools = await publicClient.readContract({ address: addresses.PoolLens as Address, abi: poolLensAbi, diff --git a/packages/keeper-bots/src/converter-bot/queries/getTokenConvertersTokenBalances.ts b/packages/keeper-bots/src/converter-bot/queries/getTokenConvertersTokenBalances.ts index c116b44b..b2e0014c 100644 --- a/packages/keeper-bots/src/converter-bot/queries/getTokenConvertersTokenBalances.ts +++ b/packages/keeper-bots/src/converter-bot/queries/getTokenConvertersTokenBalances.ts @@ -4,7 +4,7 @@ import { Address, decodeFunctionResult, encodeFunctionData, erc20Abi, parseAbi } import { protocolShareReserveAbi } from "../../config/abis/generated"; import getAddresses from "../../config/addresses"; -import getPublicClient from "../../config/clients/publicClient"; +import publicClient from "../../config/clients/publicClient"; import { MULTICALL_ABI, MULTICALL_ADDRESS } from "../constants"; import getCoreMarkets from "./getCoreMarkets"; import getIsolatedMarkets from "./getIsolatedMarkets"; @@ -102,7 +102,6 @@ export const getTokenConvertersTokenBalances = async ( releaseFunds?: boolean, ): Promise<{ results: BalanceResult[]; blockNumber: bigint }> => { const addresses = getAddresses(); - const publicClient = getPublicClient(); const pools = await reduceConfigsToComptrollerAndTokens(tokenConverterConfigs); let releaseFundsCalls: { target: string; allowFailure: boolean; callData: string }[] = []; if (releaseFunds) { diff --git a/packages/keeper-bots/src/converter-bot/queries/getVTokensFromUnderlying.ts b/packages/keeper-bots/src/converter-bot/queries/getVTokensFromUnderlying.ts index 7a05f882..30b5c312 100644 --- a/packages/keeper-bots/src/converter-bot/queries/getVTokensFromUnderlying.ts +++ b/packages/keeper-bots/src/converter-bot/queries/getVTokensFromUnderlying.ts @@ -10,12 +10,19 @@ const getVTokensFromUnderlying = async ( coreVTokens: CoreVTokensFromUnderlyingQuery["markets"]; isolatedVTokens: IsolatedVTokensFromUnderlyingQuery["markets"]; }> => { - const isolatedPoolSubgraphClient = new SubgraphClient(getConfig().isolatedPoolsSubgraphUrl); - const corePoolSubgraphClient = new SubgraphClient(getConfig().corePoolSubgraphUrl); + const config = getConfig(); + const isolatedPoolSubgraphClient = new SubgraphClient(config.isolatedPoolsSubgraphUrl); const { data: { markets: isolatedVTokens = [] } = { isolatedVTokens: [] } } = await isolatedPoolSubgraphClient.getIsolatedVTokensFromUnderlying(underlyingAddress); - const { data: { markets: coreVTokens = [] } = { coreVTokens: [] } } = - await corePoolSubgraphClient.getCoreVTokensFromUnderlying(underlyingAddress); + const corePoolSubgraphUrl = config.corePoolSubgraphUrl; + let coreVTokens: CoreVTokensFromUnderlyingQuery["markets"] = []; + if (corePoolSubgraphUrl) { + const corePoolSubgraphClient = new SubgraphClient(corePoolSubgraphUrl); + const { data: { markets } = { markets: [] } } = await corePoolSubgraphClient.getCoreVTokensFromUnderlying( + underlyingAddress, + ); + coreVTokens = markets; + } return { coreVTokens, isolatedVTokens }; }; diff --git a/packages/keeper-bots/src/converter-bot/types.ts b/packages/keeper-bots/src/converter-bot/types.ts index 07198424..90d3f98a 100644 --- a/packages/keeper-bots/src/converter-bot/types.ts +++ b/packages/keeper-bots/src/converter-bot/types.ts @@ -1,18 +1,11 @@ -import { Fraction } from "@pancakeswap/sdk"; import { Address } from "viem"; +import { DefaultMessage } from "../types"; import { BalanceResult } from "./queries/getTokenConvertersTokenBalances"; export type MarketAddresses = { underlyingAddress: Address; vTokenAddress: Address }; export type PoolAddressArray = [Address, MarketAddresses[]]; -export interface DefaultMessage { - trx: string | undefined; - error: string | undefined; - blockNumber?: bigint | undefined; - context?: unknown; -} - export interface ReduceReservesMessage extends DefaultMessage { type: "ReduceReserves"; } @@ -24,6 +17,7 @@ export interface ReleaseFundsMessage extends DefaultMessage { export interface ArbitrageMessage extends DefaultMessage { type: "Arbitrage"; + error: string | undefined; context: { beneficiary: Address; tokenToReceiveFromConverter: Address; @@ -57,30 +51,17 @@ export interface PotentialConversionsMessage extends DefaultMessage { context: { conversions: BalanceResult[] }; } -export interface AccrueInterestMessage { +export interface AccrueInterestMessage extends DefaultMessage { type: "AccrueInterest"; - trx: string | undefined; - error: string | string[] | undefined; + trx?: string; blockNumber?: bigint | undefined; context: undefined; } -export type Message = +export type ConverterBotMessage = | ReduceReservesMessage | ReleaseFundsMessage | PotentialConversionsMessage | AccrueInterestMessage | ArbitrageMessage | GetBestTradeMessage; - -export interface TradeRoute { - inputToken: { - amount: Fraction; - address: Address; - }; - outputToken: { - amount: Fraction; - address: Address; - }; - path: Address; -} diff --git a/packages/keeper-bots/src/index.ts b/packages/keeper-bots/src/index.ts index 596e99cd..5f72538c 100644 --- a/packages/keeper-bots/src/index.ts +++ b/packages/keeper-bots/src/index.ts @@ -5,5 +5,5 @@ import getAddresses from "./config/addresses"; export * from "./converter-bot/TokenConverter"; export * from "./converter-bot/queries"; export * from "./converter-bot/types"; -export * from "./converter-bot/providers"; +export * from "./providers"; export { getAddresses }; diff --git a/packages/keeper-bots/src/converter-bot/providers/index.ts b/packages/keeper-bots/src/providers/index.ts similarity index 100% rename from packages/keeper-bots/src/converter-bot/providers/index.ts rename to packages/keeper-bots/src/providers/index.ts diff --git a/packages/keeper-bots/src/converter-bot/providers/pancake-swap.ts b/packages/keeper-bots/src/providers/pancake-swap.ts similarity index 89% rename from packages/keeper-bots/src/converter-bot/providers/pancake-swap.ts rename to packages/keeper-bots/src/providers/pancake-swap.ts index d93d65e5..57fd7b06 100644 --- a/packages/keeper-bots/src/converter-bot/providers/pancake-swap.ts +++ b/packages/keeper-bots/src/providers/pancake-swap.ts @@ -3,25 +3,24 @@ import { BaseRoute, Pool, QuoteProvider, SmartRouter, V3Pool } from "@pancakeswa import { Client as UrqlClient, createClient } from "urql/core"; import { Address, Hex, encodePacked, erc20Abi } from "viem"; -import getConfig from "../../config"; -import { tokenConverterAbi } from "../../config/abis/generated"; -import type { SUPPORTED_CHAINS } from "../../config/chains"; -import { chains } from "../../config/chains"; -import { Message, TradeRoute } from "../types"; +import getConfig from "../config"; +import { tokenConverterAbi } from "../config/abis/generated"; +import type { SUPPORTED_CHAINS } from "../config/chains"; +import { chains } from "../config/chains"; +import { ConverterBotMessage } from "../converter-bot/types"; +import { TradeRoute } from "../types"; import SwapProvider from "./swap-provider"; const config = getConfig(); -const MAX_HOPS = 5; - class PancakeSwapProvider extends SwapProvider { private chainName: SUPPORTED_CHAINS; private v3PancakeSubgraphClient: UrqlClient | undefined; private tokens: Map; private quoteProvider: QuoteProvider; - constructor({ subscriber, verbose }: { subscriber?: (msg: Message) => void; verbose?: boolean }) { - super({ subscriber, verbose }); + constructor({ subscriber }: { subscriber?: (msg: ConverterBotMessage) => void }) { + super({ subscriber }); this.v3PancakeSubgraphClient = config.swapSubgraphUrl ? createClient({ url: config.swapSubgraphUrl, @@ -73,9 +72,18 @@ class PancakeSwapProvider extends SwapProvider { swapFrom: Address, swapTo: Address, amount: bigint, - ): Promise<[TradeRoute, readonly [bigint, bigint]]> { + ): Promise<[TradeRoute, bigint]> { const swapFromToken = await this.getToken(swapFrom); const swapToToken = await this.getToken(swapTo); + + // [amount transferred out of converter, amount transferred in] + const { result: updatedAmountIn } = await this.publicClient.simulateContract({ + address: tokenConverter, + abi: tokenConverterAbi, + functionName: "getUpdatedAmountIn", + args: [amount, swapTo, swapFrom], + }); + const candidatePools = await SmartRouter.getV3CandidatePools({ onChainProvider: () => this.publicClient, subgraphProvider: () => this.v3PancakeSubgraphClient, @@ -86,14 +94,6 @@ class PancakeSwapProvider extends SwapProvider { let error; let priceImpact; - // [amount transferred out of converter, amount transferred in] - const { result: updatedAmountIn } = await this.publicClient.simulateContract({ - address: tokenConverter, - abi: tokenConverterAbi, - functionName: "getUpdatedAmountIn", - args: [amount, swapToToken.address, swapFromToken.address], - }); - try { const response = await SmartRouter.getBestTrade( CurrencyAmount.fromRawAmount(swapToToken, updatedAmountIn[1]), @@ -101,7 +101,7 @@ class PancakeSwapProvider extends SwapProvider { TradeType.EXACT_OUTPUT, { gasPriceWei: () => this.publicClient.getGasPrice(), - maxHops: MAX_HOPS, + maxHops: 5, maxSplits: 0, poolProvider: SmartRouter.createStaticPoolProvider(candidatePools), quoteProvider: this.quoteProvider, @@ -143,9 +143,10 @@ class PancakeSwapProvider extends SwapProvider { priceImpact: priceImpact.toFixed(), }, }); - return this.getBestTrade(tokenConverter, swapFrom, swapTo, (updatedAmountIn[0] * 75n) / 100n); + + return this.getBestTrade(tokenConverter, swapFrom, swapTo, (updatedAmountIn[1] * 75n) / 100n); } - return [trade, updatedAmountIn]; + return [trade, updatedAmountIn[0]]; } /** diff --git a/packages/keeper-bots/src/converter-bot/providers/swap-provider.ts b/packages/keeper-bots/src/providers/swap-provider.ts similarity index 53% rename from packages/keeper-bots/src/converter-bot/providers/swap-provider.ts rename to packages/keeper-bots/src/providers/swap-provider.ts index 0480ece0..714795b8 100644 --- a/packages/keeper-bots/src/converter-bot/providers/swap-provider.ts +++ b/packages/keeper-bots/src/providers/swap-provider.ts @@ -2,25 +2,23 @@ import { Token } from "@uniswap/sdk-core"; import { Pool } from "@uniswap/v3-sdk"; import { Address } from "viem"; -import getPublicClient from "../../config/clients/publicClient"; -import getWalletClient from "../../config/clients/walletClient"; -import logger from "../logger"; -import { Message, TradeRoute } from "../types"; +import publicClient from "../config/clients/publicClient"; +import walletClient from "../config/clients/walletClient"; +import { ConverterBotMessage } from "../converter-bot/types"; +import { DefaultMessage, TradeRoute } from "../types"; class SwapProvider { - private subscriber: undefined | ((msg: Message) => void); - private verbose: boolean; - public publicClient: ReturnType; - public walletClient: ReturnType; + protected subscriber: undefined | ((msg: ConverterBotMessage) => void); + public publicClient: typeof publicClient; + public walletClient: typeof walletClient; // @ts-expect-error defined in inheriting classes liquidityProviderId: number; - constructor({ subscriber, verbose }: { subscriber?: (msg: Message) => void; verbose?: boolean }) { + constructor({ subscriber }: { subscriber?: (msg: ConverterBotMessage) => void }) { this.subscriber = subscriber; - this.verbose = !!verbose; - this.publicClient = getPublicClient(); - this.walletClient = getWalletClient(); + this.publicClient = publicClient; + this.walletClient = walletClient; } getOutputCurrency =

(pool: P, inputToken: T): T => { @@ -31,7 +29,7 @@ class SwapProvider { /** * Function to post message to subscriber - * @param Message + * @param ConverterBotMessage * */ sendMessage({ @@ -40,17 +38,9 @@ class SwapProvider { error = undefined, context = undefined, blockNumber = undefined, - }: Partial & Pick) { + }: Partial & Pick) { if (this.subscriber) { - this.subscriber({ type, trx, error, context, blockNumber } as Message); - } - - if (this.verbose) { - if (error) { - logger.error(Array.isArray(error) ? error.join(",") : error, context); - } else { - logger.info(`${type} - ${trx}`, context); - } + this.subscriber({ type, trx, error, context, blockNumber } as ConverterBotMessage); } } @@ -63,7 +53,7 @@ class SwapProvider { swapTo: Address, // eslint-disable-next-line amount: bigint, - ): Promise<[TradeRoute, readonly [bigint, bigint]]> { + ): Promise<[TradeRoute, bigint]> { throw new Error("Not Implemented Error"); } } diff --git a/packages/keeper-bots/src/converter-bot/providers/uniswap.ts b/packages/keeper-bots/src/providers/uniswap.ts similarity index 90% rename from packages/keeper-bots/src/converter-bot/providers/uniswap.ts rename to packages/keeper-bots/src/providers/uniswap.ts index ef30add8..f413b603 100644 --- a/packages/keeper-bots/src/converter-bot/providers/uniswap.ts +++ b/packages/keeper-bots/src/providers/uniswap.ts @@ -7,11 +7,12 @@ import { ethers } from "ethers"; import JSBI from "jsbi"; import { Address, Hex, encodePacked, erc20Abi } from "viem"; -import getConfig from "../../config"; -import { tokenConverterAbi } from "../../config/abis/generated"; -import type { SUPPORTED_CHAINS } from "../../config/chains"; -import { chains } from "../../config/chains"; -import { Message, TradeRoute } from "../types"; +import getConfig from "../config"; +import { tokenConverterAbi } from "../config/abis/generated"; +import type { SUPPORTED_CHAINS } from "../config/chains"; +import { chains } from "../config/chains"; +import { ConverterBotMessage } from "../converter-bot/types"; +import { TradeRoute } from "../types"; import SwapProvider from "./swap-provider"; const config = getConfig(); @@ -20,8 +21,8 @@ class UniswapProvider extends SwapProvider { private chainName: SUPPORTED_CHAINS; private tokens: Map; - constructor({ subscriber, verbose }: { subscriber?: (msg: Message) => void; verbose?: boolean }) { - super({ subscriber, verbose }); + constructor({ subscriber }: { subscriber?: (msg: ConverterBotMessage) => void }) { + super({ subscriber }); this.tokens = new Map(); this.chainName = config.network.name; this.liquidityProviderId = 0; @@ -65,7 +66,7 @@ class UniswapProvider extends SwapProvider { swapFrom: Address, swapTo: Address, amount: bigint, - ): Promise<[TradeRoute, readonly [bigint, bigint]]> { + ): Promise<[TradeRoute, bigint]> { const swapFromToken = await this.getToken(swapFrom); const swapToToken = await this.getToken(swapTo); @@ -125,7 +126,7 @@ class UniswapProvider extends SwapProvider { } catch (e) { error = `Error getting best trade - ${(e as Error).message} toToken ${swapToToken.address} fromToken ${ swapFromToken.address - } amount ${JSBI.BigInt(updatedAmountIn[1].toString())}`; + } amount ${JSBI.BigInt(amount.toString())}`; throw new Error(error); } @@ -133,7 +134,7 @@ class UniswapProvider extends SwapProvider { throw new Error("No trade found"); } - return [trade, updatedAmountIn]; + return [trade, updatedAmountIn[0]]; } /** diff --git a/packages/keeper-bots/src/types.ts b/packages/keeper-bots/src/types.ts new file mode 100644 index 00000000..a75c8a3f --- /dev/null +++ b/packages/keeper-bots/src/types.ts @@ -0,0 +1,22 @@ +import { Fraction } from "@pancakeswap/sdk"; +import { Address } from "viem"; + +export interface DefaultMessage { + type: unknown; + trx?: string; + error?: string | string[]; + blockNumber?: bigint; + context?: unknown; +} + +export interface TradeRoute { + inputToken: { + amount: Fraction; + address: Address; + }; + outputToken: { + amount: Fraction; + address: Address; + }; + path: Address; +} diff --git a/yarn.lock b/yarn.lock index 72e1bf0d..847d6ba1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7196,7 +7196,32 @@ __metadata: languageName: unknown linkType: soft -"@venusprotocol/keeper-bots@1.0.0-dev.1, @venusprotocol/keeper-bots@workspace:packages/keeper-bots": +"@venusprotocol/keeper-bots@npm:1.0.0-dev.1": + version: 1.0.0-dev.1 + resolution: "@venusprotocol/keeper-bots@npm:1.0.0-dev.1" + dependencies: + "@graphprotocol/client-cli": 3.0.0 + "@pancakeswap/sdk": ^5.8.8 + "@pancakeswap/smart-router": ^6.0.17 + "@pancakeswap/v3-core": ^1.0.2 + "@uniswap/sdk": ^3.0.3 + "@uniswap/sdk-core": ^5.3.1 + "@uniswap/smart-order-router": ^3.36.0 + "@venusprotocol/keeper-bot-contracts": 1.2.0-dev.4 + "@venusprotocol/oracle": ^2.0.0 + "@wagmi/cli": ^2.0.4 + abitype: 0.10.0 + graphql: ^16.8.1 + hardhat: ^2.19.5 + jsbi: ^3.2.5 + urql: ^3.0.3 + viem: ^2.7.1 + winston: ^3.11.0 + checksum: 495bd8c85a66aa8c0ac14eb4cb236d7d9c8951122b393180cc9a4f89da65b27ef9045d0003b2982e5cb3a21dce9035c00094ba50c226aecdabbffadda0169a20 + languageName: node + linkType: hard + +"@venusprotocol/keeper-bots@workspace:packages/keeper-bots": version: 0.0.0-use.local resolution: "@venusprotocol/keeper-bots@workspace:packages/keeper-bots" dependencies: @@ -7233,7 +7258,6 @@ __metadata: typescript: ^5.3.2 urql: ^3.0.3 viem: ^2.7.1 - winston: ^3.11.0 languageName: unknown linkType: soft