From 388d71fdaaaf591a5b61ebffce21aea46543e38e Mon Sep 17 00:00:00 2001 From: James Tuckett Date: Wed, 3 Apr 2024 11:43:44 +0100 Subject: [PATCH] feat: add simulated swap data to simulations (#147) - Add Swaps to Simulation output - Refactor `Simulation` -> `ISimulation` to align better with rest of the SDK - Refactor Swap related types from `swap-common` -> `sdk-common` to remove cyclic dependencies in simulation types Note: - `getSpotPrices` simplified to `getSpotPrice` in https://github.com/OasisDEX/summerfi-monorepo/pull/153 TODO: - Write tests for simulation swaps (done in https://github.com/OasisDEX/summerfi-monorepo/pull/153) - Update refinance strategy to handle swaps (current implementation is based on assumptions that no longer hold following ProtocolManager/Plugins/Pool work) (done in https://github.com/OasisDEX/summerfi-monorepo/pull/153) --- .../src/implementation/OrderPlanner.ts | 8 +- .../src/interfaces/IOrderPlanner.ts | 4 +- .../src/implementation/OrderPlannerService.ts | 4 +- .../src/interfaces/IOrderPlannerService.ts | 4 +- .../tests/mocks/SwapManagerMock.ts | 7 +- .../tests/service/OrderPlannerService.spec.ts | 18 ++-- .../RefinanceSimulation.ts | 5 +- .../tests/builders/SwapActionBuilder.spec.ts | 12 ++- sdk/sdk-client/src/implementation/User.ts | 4 +- .../simulations/RefinanceSimulationManager.ts | 4 +- sdk/sdk-client/src/interfaces/IUserClient.ts | 4 +- .../tests/queries/newOrder.subtest.ts | 7 +- .../queries/simulateRefinance.subtest.ts | 7 +- .../src/common/implementation/Price.ts | 5 + .../src/common/implementation/TokenAmount.ts | 23 ++++- .../src/orders/interfaces/common/Order.ts | 4 +- .../refinance/RefinanceParameters.ts | 21 ++++ sdk/sdk-common/src/simulation/Simulation.ts | 7 +- sdk/sdk-common/src/simulation/Steps.ts | 5 + .../src/swap/Enums.ts} | 0 sdk/sdk-common/src/swap/QuoteData.ts | 27 ++++++ sdk/sdk-common/src/swap/SimulatedSwapData.ts | 19 ++++ sdk/sdk-common/src/swap/SpotData.ts | 11 +++ .../types => sdk-common/src/swap}/SwapData.ts | 3 +- sdk/sdk-common/src/swap/index.ts | 5 + sdk/sdk-e2e/tests/refinanceMakerSpark.test.ts | 4 +- sdk/sdk-server/src/handlers/buildOrder.ts | 4 +- .../src/handlers/getRefinanceSimulation.ts | 13 ++- sdk/sdk-server/src/handlers/getSwapData.ts | 2 +- sdk/sdk-server/src/handlers/getSwapQuote.ts | 2 +- sdk/sdk-server/tests/utils/TestUtils.ts | 1 + sdk/simulator-service/package.json | 6 +- .../src/implementation/helpers/index.ts | 65 ------------- .../src/implementation/index.ts | 1 - .../reducer/depositBorrowReducer.ts | 8 +- .../reducer/flashloanReducer.ts | 8 +- .../reducer/paybackWithdrawReducer.ts | 8 +- .../reducer/pullTokenReducer.ts | 8 +- .../reducer/repayFlashloanReducer.ts | 8 +- .../reducer/returnFundsReducer.ts | 8 +- .../simulator-engine/reducer/stateReducers.ts | 4 +- .../simulator-engine/reducer/swapReducer.ts | 78 ++++++++++++++- .../simulator-engine/simulator.ts | 21 ++-- .../depositBorrowOutputProcessor.ts | 2 +- .../paybackWithdrawOutputProcessor.ts | 2 +- .../src/implementation/strategies/index.ts | 1 - .../src/implementation/utils/BalanceUtils.ts | 29 ++++++ .../implementation/utils/SimulatorUtils.ts | 36 +++++++ .../src/implementation/utils/index.ts | 2 + sdk/simulator-service/src/index.ts | 1 - .../src/interfaces/simulation.ts | 4 +- sdk/simulator-service/src/interfaces/steps.ts | 8 +- sdk/simulator-service/src/strategies/index.ts | 1 + .../refinance/RefinanceLendingToLending.ts} | 97 +++++++++++-------- .../src/strategies/refinance/Strategy.ts | 35 +++++++ .../src/strategies/refinance/Types.ts | 9 ++ .../src/strategies/refinance/index.ts | 3 + .../tests/mocks/contextMock.ts | 42 +++++++- sdk/simulator-service/tests/simulator.test.ts | 12 +-- sdk/swap-common/src/enums/index.ts | 1 - .../src/interfaces/ISwapManager.ts | 18 +++- .../src/interfaces/ISwapProvider.ts | 19 +++- sdk/swap-common/src/types/QuoteData.ts | 14 --- sdk/swap-common/src/types/index.ts | 2 - sdk/swap-service/e2e/oneinch.spec.ts | 3 +- .../src/implementation/SwapManager.ts | 19 +++- .../oneinch/OneInchSwapProvider.ts | 87 ++++++++++++++++- .../src/implementation/oneinch/types.ts | 16 ++- .../src/mocks/SwapManagerMock.ts | 22 ++++- 69 files changed, 698 insertions(+), 254 deletions(-) create mode 100644 sdk/sdk-common/src/orders/interfaces/refinance/RefinanceParameters.ts rename sdk/{swap-common/src/enums/SwapProviderType.ts => sdk-common/src/swap/Enums.ts} (100%) create mode 100644 sdk/sdk-common/src/swap/QuoteData.ts create mode 100644 sdk/sdk-common/src/swap/SimulatedSwapData.ts create mode 100644 sdk/sdk-common/src/swap/SpotData.ts rename sdk/{swap-common/src/types => sdk-common/src/swap}/SwapData.ts (80%) create mode 100644 sdk/sdk-common/src/swap/index.ts delete mode 100644 sdk/simulator-service/src/implementation/helpers/index.ts delete mode 100644 sdk/simulator-service/src/implementation/strategies/index.ts create mode 100644 sdk/simulator-service/src/implementation/utils/BalanceUtils.ts create mode 100644 sdk/simulator-service/src/implementation/utils/SimulatorUtils.ts create mode 100644 sdk/simulator-service/src/implementation/utils/index.ts delete mode 100644 sdk/simulator-service/src/index.ts create mode 100644 sdk/simulator-service/src/strategies/index.ts rename sdk/simulator-service/src/{implementation/strategies/Refinance.ts => strategies/refinance/RefinanceLendingToLending.ts} (53%) create mode 100644 sdk/simulator-service/src/strategies/refinance/Strategy.ts create mode 100644 sdk/simulator-service/src/strategies/refinance/Types.ts create mode 100644 sdk/simulator-service/src/strategies/refinance/index.ts delete mode 100644 sdk/swap-common/src/enums/index.ts delete mode 100644 sdk/swap-common/src/types/QuoteData.ts delete mode 100644 sdk/swap-common/src/types/index.ts diff --git a/sdk/order-planner-common/src/implementation/OrderPlanner.ts b/sdk/order-planner-common/src/implementation/OrderPlanner.ts index 1b330e8f0a..e94a582b5d 100644 --- a/sdk/order-planner-common/src/implementation/OrderPlanner.ts +++ b/sdk/order-planner-common/src/implementation/OrderPlanner.ts @@ -1,5 +1,5 @@ import { Order, type IPositionsManager } from '@summerfi/sdk-common/orders' -import { Simulation, SimulationType, steps } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationType, steps } from '@summerfi/sdk-common/simulation' import { Deployment } from '@summerfi/deployment-utils' import { Address, Maybe } from '@summerfi/sdk-common/common' import { HexData } from '@summerfi/sdk-common/common/aliases' @@ -22,7 +22,7 @@ export class OrderPlanner implements IOrderPlanner { async buildOrder(params: { user: IUser positionsManager: IPositionsManager - simulation: Simulation + simulation: ISimulation actionBuildersMap: ActionBuildersMap deployment: Deployment swapManager: ISwapManager @@ -75,12 +75,12 @@ export class OrderPlanner implements IOrderPlanner { return actionBuildersMap[step.type] as ActionBuilder } - private _getStrategyName(simulation: Simulation): string { + private _getStrategyName(simulation: ISimulation): string { return `${simulation.simulationType}${simulation.sourcePosition?.pool.protocol.name}${simulation.targetPosition?.pool.protocol.name}` } private _generateOrder( - simulation: Simulation, + simulation: ISimulation, simulationCalls: ActionCall[], positionsManager: IPositionsManager, deployment: Deployment, diff --git a/sdk/order-planner-common/src/interfaces/IOrderPlanner.ts b/sdk/order-planner-common/src/interfaces/IOrderPlanner.ts index 68d61b5c7b..928b9faf02 100644 --- a/sdk/order-planner-common/src/interfaces/IOrderPlanner.ts +++ b/sdk/order-planner-common/src/interfaces/IOrderPlanner.ts @@ -1,6 +1,6 @@ import { Deployment } from '@summerfi/deployment-utils' import { Order, type IPositionsManager } from '@summerfi/sdk-common/orders' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { ISwapManager } from '@summerfi/swap-common/interfaces' import { Maybe } from '@summerfi/sdk-common/common' import { IUser } from '@summerfi/sdk-common/user' @@ -10,7 +10,7 @@ export interface IOrderPlanner { buildOrder(params: { user: IUser positionsManager: IPositionsManager - simulation: Simulation + simulation: ISimulation actionBuildersMap: ActionBuildersMap deployment: Deployment swapManager: ISwapManager diff --git a/sdk/order-planner-service/src/implementation/OrderPlannerService.ts b/sdk/order-planner-service/src/implementation/OrderPlannerService.ts index a767999a47..b38bd55835 100644 --- a/sdk/order-planner-service/src/implementation/OrderPlannerService.ts +++ b/sdk/order-planner-service/src/implementation/OrderPlannerService.ts @@ -1,6 +1,6 @@ import { OrderPlanner } from '@summerfi/order-planner-common/implementation' import { Order, type IPositionsManager } from '@summerfi/sdk-common/orders' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { IUser } from '@summerfi/sdk-common/user' import { ChainInfo, Maybe } from '@summerfi/sdk-common/common' import { DeploymentIndex } from '@summerfi/deployment-utils' @@ -29,7 +29,7 @@ export class OrderPlannerService implements IOrderPlannerService { async buildOrder(params: { user: IUser positionsManager: IPositionsManager - simulation: Simulation + simulation: ISimulation swapManager: ISwapManager protocolsRegistry: IProtocolPluginsRegistry }): Promise> { diff --git a/sdk/order-planner-service/src/interfaces/IOrderPlannerService.ts b/sdk/order-planner-service/src/interfaces/IOrderPlannerService.ts index 4f81aa74c9..b9c89d5b74 100644 --- a/sdk/order-planner-service/src/interfaces/IOrderPlannerService.ts +++ b/sdk/order-planner-service/src/interfaces/IOrderPlannerService.ts @@ -1,5 +1,5 @@ import { Order, type IPositionsManager } from '@summerfi/sdk-common/orders' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { IUser } from '@summerfi/sdk-common/user' import { Maybe } from '@summerfi/sdk-common/common' import { ISwapManager } from '@summerfi/swap-common/interfaces' @@ -9,7 +9,7 @@ export interface IOrderPlannerService { buildOrder(params: { user: IUser positionsManager: IPositionsManager - simulation: Simulation + simulation: ISimulation swapManager: ISwapManager protocolsRegistry: IProtocolPluginsRegistry }): Promise> diff --git a/sdk/order-planner-service/tests/mocks/SwapManagerMock.ts b/sdk/order-planner-service/tests/mocks/SwapManagerMock.ts index 855a182013..353b8be50f 100644 --- a/sdk/order-planner-service/tests/mocks/SwapManagerMock.ts +++ b/sdk/order-planner-service/tests/mocks/SwapManagerMock.ts @@ -1,10 +1,11 @@ import { Address, ChainInfo, Percentage, Token, TokenAmount } from '@summerfi/sdk-common/common' import { ISwapManager } from '@summerfi/swap-common/interfaces' -import { QuoteData, SwapData } from '@summerfi/swap-common/types' +import { QuoteData, SwapData, SpotData } from '@summerfi/sdk-common/swap' export class SwapManagerMock implements ISwapManager { private _swapDataReturnValue: SwapData = {} as SwapData private _quoteDataReturnValue: QuoteData = {} as QuoteData + private _spotDataReturnValue: SpotData = {} as SpotData private _lastGetSwapDataExactInputParams: | { @@ -52,6 +53,10 @@ export class SwapManagerMock implements ISwapManager { return this._quoteDataReturnValue } + async getSpotPrices(params: { chainInfo: ChainInfo; tokens: Token[] }): Promise { + return this._spotDataReturnValue + } + get swapDataReturnValue(): SwapData { return this._swapDataReturnValue } diff --git a/sdk/order-planner-service/tests/service/OrderPlannerService.spec.ts b/sdk/order-planner-service/tests/service/OrderPlannerService.spec.ts index d47a16648d..4a00d371c6 100644 --- a/sdk/order-planner-service/tests/service/OrderPlannerService.spec.ts +++ b/sdk/order-planner-service/tests/service/OrderPlannerService.spec.ts @@ -1,15 +1,14 @@ -import { FlashloanProvider, Simulation, SimulationType } from '@summerfi/sdk-common/simulation' -import { DeploymentIndex } from '@summerfi/deployment-utils' -import { ISwapManager } from '@summerfi/swap-common/interfaces' -import { Address, AddressValue, ChainFamilyMap, ChainInfo } from '@summerfi/sdk-common/common' -import { IPositionsManager } from '@summerfi/sdk-common/orders' import { FlashloanAction, - ReturnFundsAction, SetApprovalAction, + ReturnFundsAction, } from '@summerfi/protocol-plugins/plugins/common' +import { FlashloanProvider, ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { DeploymentIndex } from '@summerfi/deployment-utils' +import { ISwapManager } from '@summerfi/swap-common/interfaces' +import { Address, AddressValue, ChainFamilyMap, ChainInfo } from '@summerfi/sdk-common/common' import { ProtocolName } from '@summerfi/sdk-common/protocols' - +import { IPositionsManager } from '@summerfi/sdk-common/orders' import { SetupDeployments } from '../utils/SetupDeployments' import { UserMock } from '../mocks/UserMock' import { SwapManagerMock } from '../mocks/SwapManagerMock' @@ -25,7 +24,6 @@ import { import assert from 'assert' import { IUser } from '@summerfi/sdk-common/user' import { - IContractProvider, IPriceService, IProtocolPluginsRegistry, ITokenService, @@ -90,7 +88,7 @@ describe('Order Planner Service', () => { const sourcePosition = getMakerPosition() const targetPosition = getSparkPosition() - const refinanceSimulation: Simulation = getRefinanceSimulation({ + const refinanceSimulation: ISimulation = getRefinanceSimulation({ sourcePosition, targetPosition, }) @@ -114,7 +112,7 @@ describe('Order Planner Service', () => { const sourcePosition = getMakerPosition() const targetPosition = getSparkPosition() - const refinanceSimulation: Simulation = getRefinanceSimulation({ + const refinanceSimulation: ISimulation = getRefinanceSimulation({ sourcePosition, targetPosition, }) diff --git a/sdk/order-planner-service/tests/utils/RefinanceSimulation/RefinanceSimulation.ts b/sdk/order-planner-service/tests/utils/RefinanceSimulation/RefinanceSimulation.ts index cdf55398cb..eae3cbcdb0 100644 --- a/sdk/order-planner-service/tests/utils/RefinanceSimulation/RefinanceSimulation.ts +++ b/sdk/order-planner-service/tests/utils/RefinanceSimulation/RefinanceSimulation.ts @@ -1,6 +1,6 @@ import { FlashloanProvider, - Simulation, + ISimulation, SimulationSteps, SimulationType, TokenTransferTargetType, @@ -11,13 +11,14 @@ import { Position } from '@summerfi/sdk-common/common' export function getRefinanceSimulation(params: { sourcePosition: Position targetPosition: Position -}): Simulation { +}): ISimulation { const { sourcePosition, targetPosition } = params return { simulationType: SimulationType.Refinance, sourcePosition: sourcePosition, targetPosition: targetPosition, + swaps: [], steps: [ { name: 'Flashloan', diff --git a/sdk/protocol-plugins/tests/builders/SwapActionBuilder.spec.ts b/sdk/protocol-plugins/tests/builders/SwapActionBuilder.spec.ts index 23f07d13a5..cf7e852550 100644 --- a/sdk/protocol-plugins/tests/builders/SwapActionBuilder.spec.ts +++ b/sdk/protocol-plugins/tests/builders/SwapActionBuilder.spec.ts @@ -3,12 +3,13 @@ import { AddressValue, ChainFamilyMap, ChainInfo, + Price, Percentage, Token, TokenAmount, } from '@summerfi/sdk-common/common' import { SimulationSteps, steps } from '@summerfi/sdk-common/simulation' -import { SwapProviderType } from '@summerfi/swap-common/enums' +import { SwapProviderType } from '@summerfi/sdk-common/swap' import { SetupBuilderReturnType, setupBuilderParams } from '../utils/SetupBuilderParams' import { SwapActionBuilder } from '../../src/plugins/common/builders' @@ -58,8 +59,17 @@ describe('Swap Action Builder', () => { type: SimulationSteps.Swap, name: 'SwapStep', inputs: { + provider: SwapProviderType.OneInch, + routes: [], fromTokenAmount: fromAmount, toTokenAmount: toAmount, + prices: [ + Price.createFrom({ + value: '0', + quoteToken: fromAmount.token, + baseToken: toAmount.token, + }), + ], fee: fee, slippage, }, diff --git a/sdk/sdk-client/src/implementation/User.ts b/sdk/sdk-client/src/implementation/User.ts index 83b3264353..17801bb872 100644 --- a/sdk/sdk-client/src/implementation/User.ts +++ b/sdk/sdk-client/src/implementation/User.ts @@ -7,7 +7,7 @@ import { Wallet, } from '@summerfi/sdk-common/common' import { IPositionsManager, Order } from '@summerfi/sdk-common/orders' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { IUserClient } from '../interfaces/IUserClient' import { IRPCClient } from '../interfaces/IRPCClient' import { RPCClientType } from '../rpc/SDKClient' @@ -49,7 +49,7 @@ export class User extends IRPCClient implements IUserClient { public async newOrder(params: { positionsManager: IPositionsManager - simulation: Simulation + simulation: ISimulation }): Promise> { return await this.rpcClient.orders.buildOrder.mutate({ user: { diff --git a/sdk/sdk-client/src/implementation/simulations/RefinanceSimulationManager.ts b/sdk/sdk-client/src/implementation/simulations/RefinanceSimulationManager.ts index 3c47c89ab6..c81f61c734 100644 --- a/sdk/sdk-client/src/implementation/simulations/RefinanceSimulationManager.ts +++ b/sdk/sdk-client/src/implementation/simulations/RefinanceSimulationManager.ts @@ -1,5 +1,5 @@ import { IRefinanceParameters } from '@summerfi/sdk-common/orders' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { RPCClientType } from '../../rpc/SDKClient' import { IRPCClient } from '../../interfaces/IRPCClient' @@ -10,7 +10,7 @@ export class RefinanceSimulationManager extends IRPCClient { public async simulateRefinancePosition( params: IRefinanceParameters, - ): Promise> { + ): Promise> { const refinanceParameters: IRefinanceParameters = { position: { positionId: params.position.positionId, diff --git a/sdk/sdk-client/src/interfaces/IUserClient.ts b/sdk/sdk-client/src/interfaces/IUserClient.ts index 432f1869cc..27b53bdfee 100644 --- a/sdk/sdk-client/src/interfaces/IUserClient.ts +++ b/sdk/sdk-client/src/interfaces/IUserClient.ts @@ -1,6 +1,6 @@ import { Maybe, Position, PositionId } from '@summerfi/sdk-common/common' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { IUser } from '@summerfi/sdk-common/user' import { Order } from '@summerfi/sdk-common/orders' import { IProtocol } from '@summerfi/sdk-common/protocols' @@ -39,5 +39,5 @@ export interface IUserClient extends IUser { * * @returns The new order created for the user */ - newOrder(params: { simulation: Simulation }): Promise> + newOrder(params: { simulation: ISimulation }): Promise> } diff --git a/sdk/sdk-client/tests/queries/newOrder.subtest.ts b/sdk/sdk-client/tests/queries/newOrder.subtest.ts index f963a664e4..61a147a1a3 100644 --- a/sdk/sdk-client/tests/queries/newOrder.subtest.ts +++ b/sdk/sdk-client/tests/queries/newOrder.subtest.ts @@ -2,7 +2,8 @@ import { IProtocol, PoolType, ProtocolName } from '@summerfi/sdk-common/protocol import { SDKManager } from '../../src/implementation/SDKManager' import { RPCClientType } from '../../src/rpc/SDKClient' import { MakerLendingPool } from '@summerfi/protocol-plugins/plugins/maker' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { SparkLendingPool } from '@summerfi/protocol-plugins/plugins/spark' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { Address, ChainFamilyMap, @@ -17,7 +18,6 @@ import { } from '@summerfi/sdk-common/common' import { IPositionsManager, Order } from '@summerfi/sdk-common/orders' import { User } from '../../src/implementation/User' -import { SparkLendingPool } from '@summerfi/protocol-plugins/plugins/spark' export default async function simulateNewOrder() { const chainInfo: ChainInfo = ChainFamilyMap.Ethereum.Mainnet @@ -77,9 +77,10 @@ export default async function simulateNewOrder() { baseCurrency: DAI, } as SparkLendingPool - const simulation: Simulation = { + const simulation: ISimulation = { simulationType: SimulationType.Refinance, sourcePosition: prevPosition, + swaps: [], targetPosition: { positionId: PositionId.createFrom({ id: '1234567890' }), debtAmount: TokenAmount.createFrom({ token: DAI, amount: '56.78' }), diff --git a/sdk/sdk-client/tests/queries/simulateRefinance.subtest.ts b/sdk/sdk-client/tests/queries/simulateRefinance.subtest.ts index 55118ad423..5dc1f70ebc 100644 --- a/sdk/sdk-client/tests/queries/simulateRefinance.subtest.ts +++ b/sdk/sdk-client/tests/queries/simulateRefinance.subtest.ts @@ -2,7 +2,8 @@ import { IProtocol, PoolType, ProtocolName } from '@summerfi/sdk-common/protocol import { SDKManager } from '../../src/implementation/SDKManager' import { RPCClientType } from '../../src/rpc/SDKClient' import { MakerLendingPool } from '@summerfi/protocol-plugins/plugins/maker' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { SparkLendingPool } from '@summerfi/protocol-plugins/plugins/spark' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { Address, ChainFamilyMap, @@ -15,7 +16,6 @@ import { TokenAmount, } from '@summerfi/sdk-common/common' import { IRefinanceParameters } from '@summerfi/sdk-common/orders' -import { SparkLendingPool } from '@summerfi/protocol-plugins/plugins/spark' export default async function simulateRefinanceTest() { type SimulateRefinanceType = RPCClientType['simulation']['refinance']['query'] @@ -23,6 +23,7 @@ export default async function simulateRefinanceTest() { return { simulationType: SimulationType.Refinance, sourcePosition: params.position, + swaps: [], targetPosition: { positionId: PositionId.createFrom({ id: '0987654321' }), debtAmount: params.position.debtAmount, @@ -30,7 +31,7 @@ export default async function simulateRefinanceTest() { pool: params.targetPool, }, steps: [], - } as Simulation + } as ISimulation }) const rpcClient = { diff --git a/sdk/sdk-common/src/common/implementation/Price.ts b/sdk/sdk-common/src/common/implementation/Price.ts index 89de67efdf..0af1a313dd 100644 --- a/sdk/sdk-common/src/common/implementation/Price.ts +++ b/sdk/sdk-common/src/common/implementation/Price.ts @@ -1,5 +1,6 @@ import { IPrice } from '../interfaces/IPrice' import { isToken } from '../interfaces/IToken' +import { BigNumber } from 'bignumber.js' import { SerializationService } from '../../services/SerializationService' import { CurrencySymbol } from '../enums/CurrencySymbol' import { Token } from './Token' @@ -32,6 +33,10 @@ export class Price implements IPrice { return `${this.value} ${this.baseToken.symbol}/${this.quoteToken}` } } + + public toBN(): BigNumber { + return new BigNumber(this.value) + } } SerializationService.registerClass(Price) diff --git a/sdk/sdk-common/src/common/implementation/TokenAmount.ts b/sdk/sdk-common/src/common/implementation/TokenAmount.ts index 68e6a22599..85f8ec0d7f 100644 --- a/sdk/sdk-common/src/common/implementation/TokenAmount.ts +++ b/sdk/sdk-common/src/common/implementation/TokenAmount.ts @@ -1,4 +1,5 @@ import { BigNumber } from 'bignumber.js' +import { Percentage } from './Percentage' import { Token } from './Token' import { SerializationService } from '../../services/SerializationService' import { ITokenAmount } from '../interfaces/ITokenAmount' @@ -60,14 +61,28 @@ export class TokenAmount implements ITokenAmount { }) } - public multiply(multiplier: string | number): TokenAmount { + public multiply(multiplier: Percentage | string | number): TokenAmount { + if (multiplier instanceof Percentage) { + return new TokenAmount({ + token: this.token, + amount: this.amountBN.times(multiplier.value).toString(), + }) + } + return new TokenAmount({ token: this.token, amount: this.amountBN.times(multiplier).toString(), }) } - public divide(divisor: string | number): TokenAmount { + public divide(divisor: Percentage | string | number): TokenAmount { + if (divisor instanceof Percentage) { + return new TokenAmount({ + token: this.token, + amount: this.amountBN.div(divisor.value).toString(), + }) + } + return new TokenAmount({ token: this.token, amount: this.amountBN.div(divisor).toString() }) } @@ -79,6 +94,10 @@ export class TokenAmount implements ITokenAmount { return new BigNumber(this.amount).times(this._baseUnitFactor).toFixed(0) } + public toBaseUnitAsBn(): BigNumber { + return new BigNumber(this.amount).times(this._baseUnitFactor) + } + public toBN(): BigNumber { return new BigNumber(this.amount) } diff --git a/sdk/sdk-common/src/orders/interfaces/common/Order.ts b/sdk/sdk-common/src/orders/interfaces/common/Order.ts index 0e4eb34313..6d65289066 100644 --- a/sdk/sdk-common/src/orders/interfaces/common/Order.ts +++ b/sdk/sdk-common/src/orders/interfaces/common/Order.ts @@ -1,5 +1,5 @@ +import { ISimulation } from '../../../simulation' import { SimulationType } from '../../../simulation/Enums' -import { Simulation } from '../../../simulation/Simulation' import { TransactionInfo } from './TransactionInfo' /** @@ -8,7 +8,7 @@ import { TransactionInfo } from './TransactionInfo' */ export interface Order { /** @description Simulation */ - simulation: Simulation + simulation: ISimulation /** @description Transaction info */ transactions: TransactionInfo[] } diff --git a/sdk/sdk-common/src/orders/interfaces/refinance/RefinanceParameters.ts b/sdk/sdk-common/src/orders/interfaces/refinance/RefinanceParameters.ts new file mode 100644 index 0000000000..6ec48f0eb3 --- /dev/null +++ b/sdk/sdk-common/src/orders/interfaces/refinance/RefinanceParameters.ts @@ -0,0 +1,21 @@ +import type { Percentage } from '../../../common/implementation/Percentage' +import type { Position } from '../../../common/implementation/Position' +// import type { Address } from '../../../common/implementation/Address' +import type { LendingPool } from '../../../protocols/implementation/LendingPool' + +/** + * @interface RefinanceParameters + * @description Parameters used to refinance a position + */ +export interface RefinanceParameters { + position: Position + targetPool: LendingPool + // TODO: Implement means of determining if a swap is required. Follow-up PR + // targetCollateral: Address + // targetDebt: Address + slippage: Percentage +} + +export function isRefinanceParameters(parameters: unknown): parameters is RefinanceParameters { + return typeof parameters === 'object' && parameters !== null && 'slippage' in parameters +} diff --git a/sdk/sdk-common/src/simulation/Simulation.ts b/sdk/sdk-common/src/simulation/Simulation.ts index daf780e6d8..b9925ff68f 100644 --- a/sdk/sdk-common/src/simulation/Simulation.ts +++ b/sdk/sdk-common/src/simulation/Simulation.ts @@ -1,15 +1,18 @@ import { IPosition } from '../common/interfaces/IPosition' +import { SimulatedSwapData } from '../swap' import type { SimulationType } from './Enums' import type { Steps } from './Steps' /** - * @interface Simulation + * @interface ISimulation * @description Simulation of a position. Specialized into the different types of simulations needed */ -export interface Simulation { +export interface ISimulation { simulationType: T sourcePosition?: IPosition // TODO figure what do to when opening position (empty position or optional) targetPosition: IPosition + /* The details of any swaps required as part of the simulation */ + swaps: SimulatedSwapData[] steps: Steps[] // TODO: OPEN QUESTION: where errors and warnings and info messages? } diff --git a/sdk/sdk-common/src/simulation/Steps.ts b/sdk/sdk-common/src/simulation/Steps.ts index 1f45edfb53..e6e74941b1 100644 --- a/sdk/sdk-common/src/simulation/Steps.ts +++ b/sdk/sdk-common/src/simulation/Steps.ts @@ -1,8 +1,10 @@ import { Percentage } from '../common/implementation/Percentage' +import { Price } from '../common/implementation/Price' import { Position } from '../common/implementation/Position' import { Token } from '../common/implementation/Token' import { TokenAmount } from '../common/implementation/TokenAmount' import { FlashloanProvider, SimulationSteps, TokenTransferTargetType } from './Enums' +import { SwapProviderType, SwapRoute } from '../swap' import { ReferenceableField, ValueReference } from './ValueReference' export interface Step { @@ -59,6 +61,9 @@ export interface SwapStep extends Step< SimulationSteps.Swap, { + provider: SwapProviderType + routes: SwapRoute[] + prices: Price[] fromTokenAmount: TokenAmount toTokenAmount: TokenAmount slippage: Percentage diff --git a/sdk/swap-common/src/enums/SwapProviderType.ts b/sdk/sdk-common/src/swap/Enums.ts similarity index 100% rename from sdk/swap-common/src/enums/SwapProviderType.ts rename to sdk/sdk-common/src/swap/Enums.ts diff --git a/sdk/sdk-common/src/swap/QuoteData.ts b/sdk/sdk-common/src/swap/QuoteData.ts new file mode 100644 index 0000000000..1289455290 --- /dev/null +++ b/sdk/sdk-common/src/swap/QuoteData.ts @@ -0,0 +1,27 @@ +import { TokenAmount, Percentage, Address } from '@summerfi/sdk-common/common' +import type { SwapProviderType } from './Enums' + +/** + * @name QuoteData + * @description Gives information about a swap operation without providing + * the data needed to perform the swap + */ +export type QuoteData = { + provider: SwapProviderType + fromTokenAmount: TokenAmount + toTokenAmount: TokenAmount + estimatedGas: string + /* Providers can provide multiple routes */ + routes: SwapRoute[] +} + +export type SwapRoute = SwapHop[] + +type SwapHop = SwapHopPart[] + +type SwapHopPart = { + name: string + part: Percentage + fromTokenAddress: Address + toTokenAddress: Address +} diff --git a/sdk/sdk-common/src/swap/SimulatedSwapData.ts b/sdk/sdk-common/src/swap/SimulatedSwapData.ts new file mode 100644 index 0000000000..3ead5c38ff --- /dev/null +++ b/sdk/sdk-common/src/swap/SimulatedSwapData.ts @@ -0,0 +1,19 @@ +import { TokenAmount } from '../common/implementation/TokenAmount' +import { Percentage } from '../common/implementation/Percentage' +import { Price } from '../common/implementation/Price' +import { QuoteData } from './QuoteData' + +/** + * Represents the data returned for each Swap in simulation. + * It is derived from the `QuoteData` type with the `estimatedGas` and 'routes' fields omitted, + * as gas estimation is not relevant for simulation purposes. + */ +export type SimulatedSwapData = Omit & { + slippage: Percentage + /* This is the impacted price that takes into account trade size */ + offerPrice: Price + /* This is the un-impacted blend of market prices from various DEXs */ + marketPrice: Price + priceImpact: Percentage + summerFee: TokenAmount +} diff --git a/sdk/sdk-common/src/swap/SpotData.ts b/sdk/sdk-common/src/swap/SpotData.ts new file mode 100644 index 0000000000..64b2e475ba --- /dev/null +++ b/sdk/sdk-common/src/swap/SpotData.ts @@ -0,0 +1,11 @@ +import { Price } from '../common' +import type { SwapProviderType } from './Enums' + +/** + * @name SpotData + * @description Gives the current market price for a specific asset + */ +export type SpotData = { + provider: SwapProviderType + prices: Price[] +} diff --git a/sdk/swap-common/src/types/SwapData.ts b/sdk/sdk-common/src/swap/SwapData.ts similarity index 80% rename from sdk/swap-common/src/types/SwapData.ts rename to sdk/sdk-common/src/swap/SwapData.ts index 61b1e5ed57..cbaf91f7d9 100644 --- a/sdk/swap-common/src/types/SwapData.ts +++ b/sdk/sdk-common/src/swap/SwapData.ts @@ -1,6 +1,6 @@ import { Address, TokenAmount } from '@summerfi/sdk-common/common' import { HexData } from '@summerfi/sdk-common/common/aliases' -import type { SwapProviderType } from '../enums/SwapProviderType' +import type { SwapProviderType } from './Enums' /** * @name SwapData @@ -13,5 +13,6 @@ export type SwapData = { calldata: HexData targetContract: Address value: string + /* The gas price for the swap portion of the t/x only */ gasPrice: string } diff --git a/sdk/sdk-common/src/swap/index.ts b/sdk/sdk-common/src/swap/index.ts new file mode 100644 index 0000000000..5997c37e20 --- /dev/null +++ b/sdk/sdk-common/src/swap/index.ts @@ -0,0 +1,5 @@ +export type { SwapData } from './SwapData' +export type { QuoteData, SwapRoute } from './QuoteData' +export type { SimulatedSwapData } from './SimulatedSwapData' +export { SwapProviderType } from './Enums' +export type { SpotData } from './SpotData' diff --git a/sdk/sdk-e2e/tests/refinanceMakerSpark.test.ts b/sdk/sdk-e2e/tests/refinanceMakerSpark.test.ts index ab788f5f64..2f64f82f58 100644 --- a/sdk/sdk-e2e/tests/refinanceMakerSpark.test.ts +++ b/sdk/sdk-e2e/tests/refinanceMakerSpark.test.ts @@ -14,7 +14,7 @@ import { ProtocolName, isLendingPool } from '@summerfi/sdk-common/protocols' import { makeSDK, type Chain, type User, Protocol } from '@summerfi/sdk-client' import { TokenSymbol } from '@summerfi/sdk-common/common/enums' import { IPositionsManager, IRefinanceParameters, Order } from '@summerfi/sdk-common/orders' -import { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import { TransactionUtils } from './utils/TransactionUtils' import { decodeActionCalldata, @@ -162,7 +162,7 @@ describe.skip('Refinance Maker Spark | SDK', () => { assert(false, 'Spark pool type is not lending') } - const refinanceSimulation: Simulation = + const refinanceSimulation: ISimulation = await sdk.simulator.refinance.simulateRefinancePosition({ position: makerPosition, targetPool: sparkPool, diff --git a/sdk/sdk-server/src/handlers/buildOrder.ts b/sdk/sdk-server/src/handlers/buildOrder.ts index e7909c971b..f53dbcf3aa 100644 --- a/sdk/sdk-server/src/handlers/buildOrder.ts +++ b/sdk/sdk-server/src/handlers/buildOrder.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import type { IPositionsManager, Order } from '@summerfi/sdk-common/orders' -import type { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' +import type { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' import type { IUser } from '@summerfi/sdk-common/user' import { Maybe } from '@summerfi/sdk-common/common' import { publicProcedure } from '../TRPC' @@ -12,7 +12,7 @@ export const buildOrder = publicProcedure positionsManager: z.custom( (positionsManager) => positionsManager !== undefined, ), - simulation: z.custom>((simulation) => simulation !== undefined), + simulation: z.custom>((simulation) => simulation !== undefined), }), ) .mutation(async (opts): Promise> => { diff --git a/sdk/sdk-server/src/handlers/getRefinanceSimulation.ts b/sdk/sdk-server/src/handlers/getRefinanceSimulation.ts index 32cf5eb91a..21a8f49a4b 100644 --- a/sdk/sdk-server/src/handlers/getRefinanceSimulation.ts +++ b/sdk/sdk-server/src/handlers/getRefinanceSimulation.ts @@ -1,7 +1,10 @@ import { z } from 'zod' import { Percentage } from '@summerfi/sdk-common/common' -import type { Simulation, SimulationType } from '@summerfi/sdk-common/simulation' -import { refinaceLendingToLending, type RefinanceDependencies } from '@summerfi/simulator-service' +import type { ISimulation, SimulationType } from '@summerfi/sdk-common/simulation' +import { + refinanceLendingToLending, + type IRefinanceDependencies, +} from '@summerfi/simulator-service/strategies' import type { IRefinanceParameters } from '@summerfi/sdk-common/orders' import { publicProcedure } from '../TRPC' @@ -9,15 +12,15 @@ const inputSchema = z.custom((parameters) => parameters != export const getRefinanceSimulation = publicProcedure .input(inputSchema) - .query(async (opts): Promise> => { + .query(async (opts): Promise> => { const args: IRefinanceParameters = opts.input - const dependencies: RefinanceDependencies = { + const dependencies: IRefinanceDependencies = { swapManager: opts.ctx.swapManager, protocolManager: opts.ctx.protocolManager, // TODO: get summer fee from the config provider getSummerFee: () => Percentage.createFrom({ value: 0 }), } - return refinaceLendingToLending(args, dependencies) + return await refinanceLendingToLending(args, dependencies) }) diff --git a/sdk/sdk-server/src/handlers/getSwapData.ts b/sdk/sdk-server/src/handlers/getSwapData.ts index fbb46a3809..6bb6f21670 100644 --- a/sdk/sdk-server/src/handlers/getSwapData.ts +++ b/sdk/sdk-server/src/handlers/getSwapData.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { ChainInfo, Token, TokenAmount, Address, Percentage } from '@summerfi/sdk-common/common' -import { SwapData } from '@summerfi/swap-common/types' +import { SwapData } from '@summerfi/sdk-common/swap' import { publicProcedure } from '../TRPC' export const getSwapDataExactInput = publicProcedure diff --git a/sdk/sdk-server/src/handlers/getSwapQuote.ts b/sdk/sdk-server/src/handlers/getSwapQuote.ts index 445fe9b16d..8da0d294ef 100644 --- a/sdk/sdk-server/src/handlers/getSwapQuote.ts +++ b/sdk/sdk-server/src/handlers/getSwapQuote.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { ChainInfo, Token, TokenAmount } from '@summerfi/sdk-common/common' -import { QuoteData } from '@summerfi/swap-common/types' +import { QuoteData } from '@summerfi/sdk-common/swap' import { publicProcedure } from '../TRPC' export const getSwapQuoteExactInput = publicProcedure diff --git a/sdk/sdk-server/tests/utils/TestUtils.ts b/sdk/sdk-server/tests/utils/TestUtils.ts index 47ae941bbd..8d2ca61e20 100644 --- a/sdk/sdk-server/tests/utils/TestUtils.ts +++ b/sdk/sdk-server/tests/utils/TestUtils.ts @@ -12,6 +12,7 @@ export const createTestContext = (opts: ContextOptions): SDKAppContext => { swapManager: {} as any, configProvider: {} as any, protocolsRegistry: {} as any, + protocolManager: {} as any, } } diff --git a/sdk/simulator-service/package.json b/sdk/simulator-service/package.json index 81ef91d352..98f0f4c352 100644 --- a/sdk/simulator-service/package.json +++ b/sdk/simulator-service/package.json @@ -4,9 +4,9 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { - ".": { - "import": "./src/index.ts", - "types": "./src/index.d.ts" + "./*": { + "import": "./src/*/index.ts", + "types": "./src/*/index.d.ts" } }, "scripts": { diff --git a/sdk/simulator-service/src/implementation/helpers/index.ts b/sdk/simulator-service/src/implementation/helpers/index.ts deleted file mode 100644 index 863f1943d1..0000000000 --- a/sdk/simulator-service/src/implementation/helpers/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { TokenAmount, type Token } from '@summerfi/sdk-common/common' -import type { - ReferenceableField, - SimulationStrategy, - ValueReference, -} from '@summerfi/sdk-common/simulation' -import type { Tail } from '../../interfaces/helperTypes' - -export function makeStrategy(strategy: T): T { - return strategy -} - -export function isValueReference(value: ReferenceableField): value is ValueReference { - return ( - (value as ValueReference).path !== undefined && - (value as ValueReference).estimatedValue !== undefined - ) -} - -export function getReferencedValue(referencableValue: ReferenceableField): T { - if (isValueReference(referencableValue)) { - return referencableValue.estimatedValue - } - return referencableValue -} - -export function getTokenBalance(token: Token, balances: Record): TokenAmount { - return balances[token.address.value] || TokenAmount.createFrom({ amount: '0', token }) -} - -export function addBalance( - amount: TokenAmount, - balance: Record, -): Record { - return { - ...balance, - [amount.token.address.value]: balance[amount.token.address.value] - ? balance[amount.token.address.value].add(amount) - : amount, - } -} - -export function subtractBalance( - amount: TokenAmount, - balance: Record, -): Record { - return { - ...balance, - [amount.token.address.value]: balance[amount.token.address.value] - ? balance[amount.token.address.value].subtract(amount) - : TokenAmount.createFrom({ amount: amount.toBN().negated().toString(), token: amount.token }), - } -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function tail(arr: T): Tail { - const [, ...rest] = arr - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return rest as any as Tail -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function head(arr: T): T[0] { - return arr[0] -} diff --git a/sdk/simulator-service/src/implementation/index.ts b/sdk/simulator-service/src/implementation/index.ts index ae0ec5ba34..e69de29bb2 100644 --- a/sdk/simulator-service/src/implementation/index.ts +++ b/sdk/simulator-service/src/implementation/index.ts @@ -1 +0,0 @@ -export * as strategies from './strategies' diff --git a/sdk/simulator-service/src/implementation/simulator-engine/reducer/depositBorrowReducer.ts b/sdk/simulator-service/src/implementation/simulator-engine/reducer/depositBorrowReducer.ts index 428025d4ba..6e58f1756f 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/reducer/depositBorrowReducer.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/reducer/depositBorrowReducer.ts @@ -1,12 +1,12 @@ import { borrowFromPosition, depositToPosition } from '@summerfi/sdk-common/common/utils' import { steps } from '@summerfi/sdk-common/simulation' -import { addBalance, getReferencedValue, subtractBalance } from '../../helpers' -import { SimulationState } from '../../../interfaces/simulation' +import { addBalance, subtractBalance, getReferencedValue } from '../../utils' +import { ISimulationState } from '../../../interfaces/simulation' export function depositBorrowReducer( step: steps.DepositBorrowStep, - state: SimulationState, -): SimulationState { + state: ISimulationState, +): ISimulationState { const afterDeposit = subtractBalance( getReferencedValue(step.inputs.depositAmount), state.balances, diff --git a/sdk/simulator-service/src/implementation/simulator-engine/reducer/flashloanReducer.ts b/sdk/simulator-service/src/implementation/simulator-engine/reducer/flashloanReducer.ts index e5ac6ce384..50ca342708 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/reducer/flashloanReducer.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/reducer/flashloanReducer.ts @@ -1,11 +1,11 @@ import { steps } from '@summerfi/sdk-common/simulation' -import { addBalance } from '../../helpers' -import { SimulationState } from '../../../interfaces/simulation' +import { addBalance } from '../../utils' +import { ISimulationState } from '../../../interfaces/simulation' export function flashloanReducer( step: steps.FlashloanStep, - state: SimulationState, -): SimulationState { + state: ISimulationState, +): ISimulationState { return { ...state, steps: { diff --git a/sdk/simulator-service/src/implementation/simulator-engine/reducer/paybackWithdrawReducer.ts b/sdk/simulator-service/src/implementation/simulator-engine/reducer/paybackWithdrawReducer.ts index cef48e167d..f87b139f8b 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/reducer/paybackWithdrawReducer.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/reducer/paybackWithdrawReducer.ts @@ -1,12 +1,12 @@ import { depositToPosition } from '@summerfi/sdk-common/common/utils' import { steps } from '@summerfi/sdk-common/simulation' -import { addBalance, getReferencedValue, subtractBalance } from '../../helpers' -import { SimulationState } from '../../../interfaces/simulation' +import { addBalance, getReferencedValue, subtractBalance } from '../../utils' +import { ISimulationState } from '../../../interfaces/simulation' export function paybackWithdrawReducer( step: steps.PaybackWithdrawStep, - state: SimulationState, -): SimulationState { + state: ISimulationState, +): ISimulationState { const afterPayback = addBalance(getReferencedValue(step.inputs.paybackAmount), state.balances) const afterWithdraw = subtractBalance( getReferencedValue(step.inputs.withdrawAmount), diff --git a/sdk/simulator-service/src/implementation/simulator-engine/reducer/pullTokenReducer.ts b/sdk/simulator-service/src/implementation/simulator-engine/reducer/pullTokenReducer.ts index bdc207ba28..493d13a83f 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/reducer/pullTokenReducer.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/reducer/pullTokenReducer.ts @@ -1,11 +1,11 @@ import { steps } from '@summerfi/sdk-common/simulation' -import { addBalance, getReferencedValue } from '../../helpers' -import { SimulationState } from '../../../interfaces/simulation' +import { addBalance, getReferencedValue } from '../../utils' +import { ISimulationState } from '../../../interfaces/simulation' export function pullTokenReducer( step: steps.PullTokenStep, - state: SimulationState, -): SimulationState { + state: ISimulationState, +): ISimulationState { return { ...state, steps: { diff --git a/sdk/simulator-service/src/implementation/simulator-engine/reducer/repayFlashloanReducer.ts b/sdk/simulator-service/src/implementation/simulator-engine/reducer/repayFlashloanReducer.ts index d2f6b44fa0..5ce116fe7c 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/reducer/repayFlashloanReducer.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/reducer/repayFlashloanReducer.ts @@ -1,11 +1,11 @@ import { steps } from '@summerfi/sdk-common/simulation' -import { subtractBalance } from '../../helpers' -import { SimulationState } from '../../../interfaces/simulation' +import { subtractBalance } from '../../utils' +import { ISimulationState } from '../../../interfaces/simulation' export function repayFlashloanReducer( step: steps.RepayFlashloan, - state: SimulationState, -): SimulationState { + state: ISimulationState, +): ISimulationState { return { ...state, steps: { diff --git a/sdk/simulator-service/src/implementation/simulator-engine/reducer/returnFundsReducer.ts b/sdk/simulator-service/src/implementation/simulator-engine/reducer/returnFundsReducer.ts index 4daa0e6168..75166b5328 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/reducer/returnFundsReducer.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/reducer/returnFundsReducer.ts @@ -1,11 +1,11 @@ import { steps } from '@summerfi/sdk-common/simulation' -import { getTokenBalance, subtractBalance } from '../../helpers' -import { SimulationState } from '../../../interfaces/simulation' +import { getTokenBalance, subtractBalance } from '../../utils' +import { ISimulationState } from '../../../interfaces/simulation' export function returnFundsReducer( step: steps.ReturnFunds, - state: SimulationState, -): SimulationState { + state: ISimulationState, +): ISimulationState { return { ...state, steps: { diff --git a/sdk/simulator-service/src/implementation/simulator-engine/reducer/stateReducers.ts b/sdk/simulator-service/src/implementation/simulator-engine/reducer/stateReducers.ts index 3630f2b75a..d88692759b 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/reducer/stateReducers.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/reducer/stateReducers.ts @@ -1,5 +1,5 @@ import { SimulationSteps, steps } from '@summerfi/sdk-common/simulation' -import { SimulationState } from '../../../interfaces/simulation' +import { ISimulationState } from '../../../interfaces/simulation' import type { StateReducer, StateReducers } from '../../../interfaces/steps' import { flashloanReducer } from './flashloanReducer' import { depositBorrowReducer } from './depositBorrowReducer' @@ -19,7 +19,7 @@ const stateReducers: StateReducers = { [SimulationSteps.PullToken]: pullTokenReducer, } -export function stateReducer(step: steps.Steps, state: SimulationState): SimulationState { +export function stateReducer(step: steps.Steps, state: ISimulationState): ISimulationState { const reducer = stateReducers[step.type] as StateReducer return reducer(step, state) diff --git a/sdk/simulator-service/src/implementation/simulator-engine/reducer/swapReducer.ts b/sdk/simulator-service/src/implementation/simulator-engine/reducer/swapReducer.ts index 2c0fd80529..d720f35a04 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/reducer/swapReducer.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/reducer/swapReducer.ts @@ -1,16 +1,88 @@ +import { TokenAmount, Price, Percentage } from '@summerfi/sdk-common/common' import { steps } from '@summerfi/sdk-common/simulation' -import { addBalance, subtractBalance } from '../../helpers' -import { SimulationState } from '../../../interfaces/simulation' +import { addBalance, subtractBalance } from '../../utils' +import { ISimulationState } from '../../../interfaces/simulation' -export function swapReducer(step: steps.SwapStep, state: SimulationState): SimulationState { +export function swapReducer(step: steps.SwapStep, state: ISimulationState): ISimulationState { const balanceWithoutFromToken = subtractBalance(step.inputs.fromTokenAmount, state.balances) const balanceWithToToken = addBalance(step.outputs.receivedAmount, balanceWithoutFromToken) + + const baseToken = step.inputs.toTokenAmount.token + const quoteToken = step.inputs.fromTokenAmount.token + + const offerPrice = Price.createFrom({ + value: step.inputs.toTokenAmount + .toBaseUnitAsBn() + .div(step.inputs.fromTokenAmount.toBaseUnitAsBn()) + .toString(), + baseToken, + quoteToken, + }) + + const spotPriceOfToToken = step.inputs.prices.find((price) => + price.baseToken.address.equals(baseToken.address), + ) + const spotPriceOfFromToken = step.inputs.prices.find((price) => + price.baseToken.address.equals(quoteToken.address), + ) + + if (!spotPriceOfToToken || !spotPriceOfFromToken) { + throw new Error('Spot price for either From/To token could not be found') + } + + const marketPrice = Price.createFrom({ + value: spotPriceOfToToken.toBN().div(spotPriceOfFromToken.toBN()).toString(), + baseToken: step.inputs.fromTokenAmount.token, + quoteToken: step.inputs.toTokenAmount.token, + }) + return { ...state, steps: { ...state.steps, [step.name]: step, }, + swaps: { + ...state.swaps, + [step.name]: { + provider: step.inputs.provider, + // Note: Can add routes back in later if we need them for the UI + // routes: step.inputs.routes, + fromTokenAmount: step.inputs.fromTokenAmount, + toTokenAmount: step.inputs.toTokenAmount, + slippage: Percentage.createFrom({ value: step.inputs.slippage.value }), + offerPrice, + marketPrice, + priceImpact: calculatePriceImpact(marketPrice, offerPrice), + summerFee: TokenAmount.createFrom({ + token: step.inputs.fromTokenAmount.token, + amount: step.inputs.fromTokenAmount.multiply(step.inputs.fee.value).amount, + }), + }, + }, balances: balanceWithToToken, } } + +/** + * + * @param marketPrice - This price represent how much it will cost for selling some very small amount + * such as 0.1. It is the best possible price on the market. + * @param offerPrice - If the amount we would like to sell we might get deeper into the liquidity + * meaning the price won't be a good as when you sell small amount. This is the price that is + * represent how much it will cost for us to sell the desired amount. + * + * Both prices might be equal which means that there is no price impact. Having no + * price impact means that you sell at the best possible price. + */ +export function calculatePriceImpact(marketPrice: Price, offerPrice: Price): Percentage { + return Percentage.createFrom({ + value: marketPrice + .toBN() + .minus(offerPrice.toBN()) + .div(marketPrice.toBN()) + .abs() + .times(100) + .toNumber(), + }) +} diff --git a/sdk/simulator-service/src/implementation/simulator-engine/simulator.ts b/sdk/simulator-service/src/implementation/simulator-engine/simulator.ts index eeb8dad7a7..66b1022536 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/simulator.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/simulator.ts @@ -1,7 +1,7 @@ -import type { SimulationState } from '../../interfaces/simulation' +import type { ISimulationState } from '../../interfaces/simulation' import type { Tail } from '../../interfaces/helperTypes' import type { NextFunction } from '../../interfaces' -import { head, tail } from '../helpers' +import { head, tail } from '../utils' import { processStepOutput } from './stepProcessor/stepOutputProcessors' import { stateReducer } from './reducer/stateReducers' import type { SimulationStrategy } from '@summerfi/sdk-common/simulation' @@ -14,13 +14,13 @@ export class Simulator< > { public schema: Strategy public originalSchema: SimulationStrategy - private state: SimulationState + private state: ISimulationState private readonly nextArray: NextArray private constructor( schema: Strategy, originalSchema: SimulationStrategy, - state: SimulationState = { balances: {}, positions: {}, steps: {} }, + state: ISimulationState = { swaps: {}, balances: {}, positions: {}, steps: {} }, nextArray: Readonly = [] as unknown as NextArray, ) { this.schema = schema @@ -30,14 +30,14 @@ export class Simulator< } static create(schema: S) { - // The second argument is the same as from the first schema we will substract steps - // with each next step added and we also need to keep the original schema for future reference + // The second argument is the same as from the first schema we will subtract steps + // with each next step added we also need to keep the original schema for future reference return new Simulator(schema, schema) } - public async run(): Promise { + public async run(): Promise { for (let i = 0; i < this.nextArray.length; i++) { - const proccesesedStepSchema = this.originalSchema[i] + const processedStepSchema = this.originalSchema[i] const getReference = (path: [string, string]) => { const [stepName, output] = path const step: Maybe = this.state.steps[stepName] @@ -76,13 +76,12 @@ export class Simulator< this.state = stateReducer(fullStep, this.state) } - if (nextStep.skip === true && proccesesedStepSchema.optional === false) { + if (nextStep.skip === true && processedStepSchema.optional === false) { throw new Error(`Step is required: ${nextStep.type}`) } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return this.state as any + return this.state } public next( diff --git a/sdk/simulator-service/src/implementation/simulator-engine/stepProcessor/depositBorrowOutputProcessor.ts b/sdk/simulator-service/src/implementation/simulator-engine/stepProcessor/depositBorrowOutputProcessor.ts index e1a559b4c5..3954fad229 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/stepProcessor/depositBorrowOutputProcessor.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/stepProcessor/depositBorrowOutputProcessor.ts @@ -1,5 +1,5 @@ import { steps } from '@summerfi/sdk-common/simulation' -import { getReferencedValue } from '../../helpers' +import { getReferencedValue } from '../../utils' import type { StepOutputProcessor } from '../../../interfaces/steps' export const depositBorrowOutputProcessor: StepOutputProcessor = async ( diff --git a/sdk/simulator-service/src/implementation/simulator-engine/stepProcessor/paybackWithdrawOutputProcessor.ts b/sdk/simulator-service/src/implementation/simulator-engine/stepProcessor/paybackWithdrawOutputProcessor.ts index cb8f26edc3..404fd7183a 100644 --- a/sdk/simulator-service/src/implementation/simulator-engine/stepProcessor/paybackWithdrawOutputProcessor.ts +++ b/sdk/simulator-service/src/implementation/simulator-engine/stepProcessor/paybackWithdrawOutputProcessor.ts @@ -1,5 +1,5 @@ import { steps } from '@summerfi/sdk-common/simulation' -import { getReferencedValue } from '../../helpers' +import { getReferencedValue } from '../../utils' import type { StepOutputProcessor } from '../../../interfaces/steps' export const paybackWithdrawOutputProcessor: StepOutputProcessor< diff --git a/sdk/simulator-service/src/implementation/strategies/index.ts b/sdk/simulator-service/src/implementation/strategies/index.ts deleted file mode 100644 index 6a4cecc123..0000000000 --- a/sdk/simulator-service/src/implementation/strategies/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Refinance' diff --git a/sdk/simulator-service/src/implementation/utils/BalanceUtils.ts b/sdk/simulator-service/src/implementation/utils/BalanceUtils.ts new file mode 100644 index 0000000000..6a80c95fd9 --- /dev/null +++ b/sdk/simulator-service/src/implementation/utils/BalanceUtils.ts @@ -0,0 +1,29 @@ +import { TokenAmount, type Token } from '@summerfi/sdk-common/common' + +export function getTokenBalance(token: Token, balances: Record): TokenAmount { + return balances[token.address.value] || TokenAmount.createFrom({ amount: '0', token }) +} + +export function addBalance( + amount: TokenAmount, + balance: Record, +): Record { + return { + ...balance, + [amount.token.address.value]: balance[amount.token.address.value] + ? balance[amount.token.address.value].add(amount) + : amount, + } +} + +export function subtractBalance( + amount: TokenAmount, + balance: Record, +): Record { + return { + ...balance, + [amount.token.address.value]: balance[amount.token.address.value] + ? balance[amount.token.address.value].subtract(amount) + : TokenAmount.createFrom({ amount: amount.toBN().negated().toString(), token: amount.token }), + } +} diff --git a/sdk/simulator-service/src/implementation/utils/SimulatorUtils.ts b/sdk/simulator-service/src/implementation/utils/SimulatorUtils.ts new file mode 100644 index 0000000000..6d663a9f0c --- /dev/null +++ b/sdk/simulator-service/src/implementation/utils/SimulatorUtils.ts @@ -0,0 +1,36 @@ +import type { + ReferenceableField, + SimulationStrategy, + ValueReference, +} from '@summerfi/sdk-common/simulation' +import type { Tail } from '../../interfaces/helperTypes' + +export function makeStrategy(strategy: T): T { + return strategy +} + +export function isValueReference(value: ReferenceableField): value is ValueReference { + return ( + (value as ValueReference).path !== undefined && + (value as ValueReference).estimatedValue !== undefined + ) +} + +export function getReferencedValue(referenceableValue: ReferenceableField): T { + if (isValueReference(referenceableValue)) { + return referenceableValue.estimatedValue + } + return referenceableValue +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function tail(arr: T): Tail { + const [, ...rest] = arr + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return rest as any as Tail +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function head(arr: T): T[0] { + return arr[0] +} diff --git a/sdk/simulator-service/src/implementation/utils/index.ts b/sdk/simulator-service/src/implementation/utils/index.ts new file mode 100644 index 0000000000..297bef7257 --- /dev/null +++ b/sdk/simulator-service/src/implementation/utils/index.ts @@ -0,0 +1,2 @@ +export { getTokenBalance, addBalance, subtractBalance } from './BalanceUtils' +export { makeStrategy, isValueReference, getReferencedValue, tail, head } from './SimulatorUtils' diff --git a/sdk/simulator-service/src/index.ts b/sdk/simulator-service/src/index.ts deleted file mode 100644 index 8aac1b5dce..0000000000 --- a/sdk/simulator-service/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './implementation/strategies' diff --git a/sdk/simulator-service/src/interfaces/simulation.ts b/sdk/simulator-service/src/interfaces/simulation.ts index 3346eaceae..a9ad7e21d3 100644 --- a/sdk/simulator-service/src/interfaces/simulation.ts +++ b/sdk/simulator-service/src/interfaces/simulation.ts @@ -1,7 +1,9 @@ import type { steps } from '@summerfi/sdk-common/simulation' import type { TokenAmount, Position } from '@summerfi/sdk-common/common' +import type { SimulatedSwapData } from '@summerfi/sdk-common/swap' -export interface SimulationState { +export interface ISimulationState { + swaps: Record balances: Record positions: Record steps: Record diff --git a/sdk/simulator-service/src/interfaces/steps.ts b/sdk/simulator-service/src/interfaces/steps.ts index 8948ec8353..adcea67930 100644 --- a/sdk/simulator-service/src/interfaces/steps.ts +++ b/sdk/simulator-service/src/interfaces/steps.ts @@ -1,6 +1,6 @@ import { SimulationStrategy, ValueReference, steps } from '@summerfi/sdk-common/simulation' import { EmptyArray, Where } from './helperTypes' -import { SimulationState } from './simulation' +import { ISimulationState } from './simulation' export type StepOutputProcessor = (step: Omit) => Promise export type StepOutputProcessors = { @@ -9,8 +9,8 @@ export type StepOutputProcessors = { export type StepsWithoutOutputs = Omit export type StateReducer = ( step: T, - state: SimulationState, -) => SimulationState + state: ISimulationState, +) => ISimulationState export type StateReducers = { [Type in steps.Steps['type']]: StateReducer> } @@ -21,7 +21,7 @@ export type NextFunction< > = Schema extends EmptyArray ? never : (ctx: { - state: SimulationState + state: ISimulationState // eslint-disable-next-line @typescript-eslint/no-explicit-any getReference: (path: [string, string]) => ValueReference }) => Promise, 'outputs'>> diff --git a/sdk/simulator-service/src/strategies/index.ts b/sdk/simulator-service/src/strategies/index.ts new file mode 100644 index 0000000000..0ee38d8f24 --- /dev/null +++ b/sdk/simulator-service/src/strategies/index.ts @@ -0,0 +1 @@ +export * from './refinance' diff --git a/sdk/simulator-service/src/implementation/strategies/Refinance.ts b/sdk/simulator-service/src/strategies/refinance/RefinanceLendingToLending.ts similarity index 53% rename from sdk/simulator-service/src/implementation/strategies/Refinance.ts rename to sdk/simulator-service/src/strategies/refinance/RefinanceLendingToLending.ts index 92c3874067..5ca9642c0f 100644 --- a/sdk/simulator-service/src/implementation/strategies/Refinance.ts +++ b/sdk/simulator-service/src/strategies/refinance/RefinanceLendingToLending.ts @@ -1,50 +1,23 @@ import { FlashloanProvider, - Simulation, + ISimulation, SimulationSteps, SimulationType, TokenTransferTargetType, } from '@summerfi/sdk-common/simulation' -import { makeStrategy } from '../helpers' -import { Simulator } from '../simulator-engine' -import { Percentage, Position, TokenAmount } from '@summerfi/sdk-common/common' +import { Simulator } from '../../implementation/simulator-engine' +import { Position, TokenAmount, Percentage } from '@summerfi/sdk-common/common' import { newEmptyPositionFromPool } from '@summerfi/sdk-common/common/utils' import { IRefinanceParameters } from '@summerfi/sdk-common/orders' -import { type ISwapManager } from '@summerfi/swap-common/interfaces' import { isLendingPool } from '@summerfi/sdk-common/protocols' -import { type IProtocolManager } from '@summerfi/protocol-manager-common' +import { getReferencedValue } from '../../implementation/utils' +import { refinanceLendingToLendingStrategy } from './Strategy' +import { type IRefinanceDependencies } from './Types' -export const refinanceStrategy = makeStrategy([ - { - step: SimulationSteps.Flashloan, - optional: false, - }, - { - step: SimulationSteps.PaybackWithdraw, - optional: false, - }, - { - step: SimulationSteps.DepositBorrow, - optional: false, - }, - { - step: SimulationSteps.RepayFlashloan, - optional: false, - }, -]) - -// TODO move those interfaces to more appropriate place - -export interface RefinanceDependencies { - swapManager: ISwapManager - protocolManager: IProtocolManager - getSummerFee: () => Percentage -} - -export async function refinaceLendingToLending( +export async function refinanceLendingToLending( args: IRefinanceParameters, - dependencies: RefinanceDependencies, -): Promise> { + dependencies: IRefinanceDependencies, +): Promise> { // args validation if (!isLendingPool(args.targetPool)) { throw new Error('Target pool is not a lending pool') @@ -59,13 +32,19 @@ export async function refinaceLendingToLending( const FLASHLOAN_MARGIN = 1.001 const flashloanAmount = position.debtAmount.multiply(FLASHLOAN_MARGIN) - const simulator = Simulator.create(refinanceStrategy) + const simulator = Simulator.create(refinanceLendingToLendingStrategy) const targetTokenConfig = targetPool.collaterals.get({ token: position.collateralAmount.token }) if (!targetTokenConfig) { throw new Error('Target token not found in pool') } + // TODO: Update this check + const collateralConfig = targetPool.collaterals.get({ token: position.collateralAmount.token }) + const debtConfig = targetPool.debts.get({ token: position.debtAmount.token }) + const isCollateralSwapSkipped = collateralConfig !== undefined + const isDebtSwapSkipped = debtConfig !== undefined + // TODO: read debt amount from chain (special step: ReadDebtAmount) // TODO: the swap quote should also include the summer fee, in this case we need to know when we are taking the fee, // before or after the swap, it influences actual call to oneInch api @@ -90,6 +69,25 @@ export async function refinaceLendingToLending( position: position, }, })) + .next(async () => ({ + name: 'CollateralSwap', + type: SimulationSteps.Swap, + inputs: { + ...(await dependencies.swapManager.getSwapQuoteExactInput({ + chainInfo: position.pool.protocol.chainInfo, + // TODO: Properly implement swaps + fromAmount: position.collateralAmount, + toToken: collateralConfig!.token, + })), + ...(await dependencies.swapManager.getSpotPrices({ + chainInfo: position.pool.protocol.chainInfo, + tokens: [collateralConfig!.token, collateralConfig!.token], + })), + slippage: Percentage.createFrom({ value: args.slippage.value }), + fee: dependencies.getSummerFee(), + }, + skip: isCollateralSwapSkipped, + })) .next(async (ctx) => ({ name: 'DepositBorrowToTarget', type: SimulationSteps.DepositBorrow, @@ -104,6 +102,27 @@ export async function refinaceLendingToLending( borrowTargetType: TokenTransferTargetType.PositionsManager, }, })) + // TODO: Implement swapping logic properly. Current implementation is just placeholder + .next(async (ctx) => ({ + name: 'DebtSwap', + type: SimulationSteps.Swap, + inputs: { + ...(await dependencies.swapManager.getSwapQuoteExactInput({ + chainInfo: args.position.pool.protocol.chainInfo, + fromAmount: getReferencedValue( + ctx.getReference(['DepositBorrowToTarget', 'borrowAmount']), + ), + toToken: debtConfig!.token, + })), + ...(await dependencies.swapManager.getSpotPrices({ + chainInfo: args.position.pool.protocol.chainInfo, + tokens: [debtConfig!.token, debtConfig!.token], + })), + slippage: Percentage.createFrom({ value: args.slippage.value }), + fee: dependencies.getSummerFee(), + }, + skip: isDebtSwapSkipped, + })) .next(async () => ({ name: 'RepayFlashloan', type: SimulationSteps.RepayFlashloan, @@ -113,7 +132,6 @@ export async function refinaceLendingToLending( })) .run() - // TODO: I think simulation should return the simulation position as a preperty targetPosition for easy discoverability const targetPosition = Object.values(simulation.positions).find( (p) => p.pool.protocol === targetPool.protocol, ) @@ -126,6 +144,7 @@ export async function refinaceLendingToLending( simulationType: SimulationType.Refinance, sourcePosition: position, targetPosition, + swaps: Object.values(simulation.swaps), steps: Object.values(simulation.steps), - } as Simulation + } as ISimulation } diff --git a/sdk/simulator-service/src/strategies/refinance/Strategy.ts b/sdk/simulator-service/src/strategies/refinance/Strategy.ts new file mode 100644 index 0000000000..aca24441ce --- /dev/null +++ b/sdk/simulator-service/src/strategies/refinance/Strategy.ts @@ -0,0 +1,35 @@ +import { SimulationSteps } from '@summerfi/sdk-common/simulation' +import { makeStrategy } from '../../implementation/utils' + +export const refinanceLendingToLendingStrategy = makeStrategy([ + { + step: SimulationSteps.Flashloan, + optional: false, + }, + { + step: SimulationSteps.PaybackWithdraw, + optional: false, + }, + { + step: SimulationSteps.Swap, + optional: true, + }, + { + step: SimulationSteps.DepositBorrow, + optional: false, + }, + { + step: SimulationSteps.Swap, + optional: true, + }, + { + step: SimulationSteps.RepayFlashloan, + optional: false, + }, + { + // In case of target debt being different then source debt we need a swap, + // We cannot forsee the exact amount of the swap, so we need to return excess tokens to user + step: SimulationSteps.ReturnFunds, + optional: true, + }, +]) diff --git a/sdk/simulator-service/src/strategies/refinance/Types.ts b/sdk/simulator-service/src/strategies/refinance/Types.ts new file mode 100644 index 0000000000..b2060c7e78 --- /dev/null +++ b/sdk/simulator-service/src/strategies/refinance/Types.ts @@ -0,0 +1,9 @@ +import { Percentage } from '@summerfi/sdk-common/common' +import { type ISwapManager } from '@summerfi/swap-common/interfaces' +import { type IProtocolManager } from '@summerfi/protocol-manager-common' + +export interface IRefinanceDependencies { + swapManager: ISwapManager + protocolManager: IProtocolManager + getSummerFee: () => Percentage +} diff --git a/sdk/simulator-service/src/strategies/refinance/index.ts b/sdk/simulator-service/src/strategies/refinance/index.ts new file mode 100644 index 0000000000..3b6c974181 --- /dev/null +++ b/sdk/simulator-service/src/strategies/refinance/index.ts @@ -0,0 +1,3 @@ +export * from './RefinanceLendingToLending' +export * from './Strategy' +export * from './Types' diff --git a/sdk/simulator-service/tests/mocks/contextMock.ts b/sdk/simulator-service/tests/mocks/contextMock.ts index 85efb2de1e..aa2407b73e 100644 --- a/sdk/simulator-service/tests/mocks/contextMock.ts +++ b/sdk/simulator-service/tests/mocks/contextMock.ts @@ -1,6 +1,15 @@ -import { Address, ChainInfo, Percentage, Token, TokenAmount } from '@summerfi/sdk-common/common' +import { + Address, + ChainInfo, + Percentage, + Token, + TokenAmount, + Price, + type AddressValue, +} from '@summerfi/sdk-common/common' +import { CurrencySymbol } from '@summerfi/sdk-common/common' import { IPool } from '@summerfi/sdk-common/protocols' -import { SwapProviderType } from '@summerfi/swap-common/enums' +import { SwapProviderType } from '@summerfi/sdk-common/swap' import { testTargetLendingPoolRequiredSwaps } from './testSourcePosition' async function getSwapDataExactInput(params: { @@ -34,7 +43,31 @@ async function getSwapQuoteExactInput(params: { } } -function mockGetFee() { +async function getSpotPrices(params: { chainInfo: ChainInfo; tokens: Token[] }) { + const MOCK_PRICE = 0.5 + const MOCK_QUOTE_CURRENCY = CurrencySymbol.USD + return { + provider: SwapProviderType.OneInch, + prices: params.tokens + .map((token) => [token.address.value, MOCK_PRICE]) + .map(([address, price]) => { + const baseToken = params.tokens.find((token) => + token.address.equals(Address.createFromEthereum({ value: address as AddressValue })), + ) + if (!baseToken) { + throw new Error('BaseToken not found in params.tokens list when fetching spot prices') + } + + return Price.createFrom({ + value: price.toString(), + baseToken, + quoteToken: MOCK_QUOTE_CURRENCY, + }) + }), + } +} + +export function mockGetFee() { return Percentage.createFrom({ value: 0 }) } @@ -50,7 +83,8 @@ export const mockRefinanceContext = { getPosition: () => {}, }, swapManager: { - getSwapDataExactInput: getSwapDataExactInput, + getSwapDataExactInput, getSwapQuoteExactInput: jest.fn().mockImplementation(getSwapQuoteExactInput), + getSpotPrices, }, } diff --git a/sdk/simulator-service/tests/simulator.test.ts b/sdk/simulator-service/tests/simulator.test.ts index c58d9a5add..a9a224900a 100644 --- a/sdk/simulator-service/tests/simulator.test.ts +++ b/sdk/simulator-service/tests/simulator.test.ts @@ -1,6 +1,6 @@ import { Percentage } from '@summerfi/sdk-common/common' -import { refinaceLendingToLending } from '../src/implementation/strategies' -import { Simulation, SimulationSteps, SimulationType } from '@summerfi/sdk-common/simulation' +import { ISimulation, SimulationSteps, SimulationType } from '@summerfi/sdk-common/simulation' +import { refinanceLendingToLending } from '../src/strategies' import { otherTestCollateral, otherTestDebt, @@ -12,9 +12,9 @@ import { mockRefinanceContext } from './mocks/contextMock' describe('Refinance', () => { describe('to the position with the same collateral and debt (no swaps)', () => { - let simulation: Simulation + let simulation: ISimulation beforeAll(async () => { - simulation = await refinaceLendingToLending( + simulation = await refinanceLendingToLending( { position: testSourcePosition, targetPool: testTargetLendingPool, @@ -57,9 +57,9 @@ describe('Refinance', () => { }) describe.skip('to the position with the different collateral and debt (with swaps)', () => { - let simulation: Simulation + let simulation: ISimulation beforeAll(async () => { - simulation = await refinaceLendingToLending( + simulation = await refinanceLendingToLending( { position: testSourcePosition, targetPool: testTargetLendingPoolRequiredSwaps, diff --git a/sdk/swap-common/src/enums/index.ts b/sdk/swap-common/src/enums/index.ts deleted file mode 100644 index caae5cdc87..0000000000 --- a/sdk/swap-common/src/enums/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SwapProviderType } from './SwapProviderType' diff --git a/sdk/swap-common/src/interfaces/ISwapManager.ts b/sdk/swap-common/src/interfaces/ISwapManager.ts index c065a56906..71596f740e 100644 --- a/sdk/swap-common/src/interfaces/ISwapManager.ts +++ b/sdk/swap-common/src/interfaces/ISwapManager.ts @@ -4,9 +4,9 @@ import type { Percentage, Token, Address, + CurrencySymbol, } from '@summerfi/sdk-common/common' -import type { QuoteData } from '../types/QuoteData' -import type { SwapData } from '../types/SwapData' +import type { QuoteData, SwapData, SpotData } from '@summerfi/sdk-common/swap' /** * @name ISwapManager @@ -44,4 +44,18 @@ export interface ISwapManager { fromAmount: TokenAmount toToken: Token }): Promise + + /** + * @name getSpotPrices + * @description Returns the prevailing market price for a given asset + * in terms of a base currency + * @param chainInfo The chain information + * @param tokens An array of tokens for which you require a price + * @param quoteCurrency The currency in which the token is quoted in + */ + getSpotPrices(params: { + chainInfo: ChainInfo + tokens: Token[] + quoteCurrency?: CurrencySymbol + }): Promise } diff --git a/sdk/swap-common/src/interfaces/ISwapProvider.ts b/sdk/swap-common/src/interfaces/ISwapProvider.ts index 71930bf8d6..7ed7019a03 100644 --- a/sdk/swap-common/src/interfaces/ISwapProvider.ts +++ b/sdk/swap-common/src/interfaces/ISwapProvider.ts @@ -1,7 +1,6 @@ import { Address, ChainInfo, Percentage, Token, TokenAmount } from '@summerfi/sdk-common/common' -import type { SwapProviderType } from '../enums/SwapProviderType' -import type { SwapData } from '../types/SwapData' -import type { QuoteData } from '../types/QuoteData' +import { CurrencySymbol } from '@summerfi/sdk-common/common' +import type { QuoteData, SwapData, SwapProviderType, SpotData } from '@summerfi/sdk-common/swap' /** * @name ISwapProvider @@ -41,4 +40,18 @@ export interface ISwapProvider { fromAmount: TokenAmount toToken: Token }): Promise + + /** + * @name getSpotPrices + * @description Returns the prevailing market price for a given asset + * in terms of a base currency + * @param chainInfo The chain information + * @param tokens An array of token's for which you require a price + * @param quoteCurrency The currency in which a token is quoted in (the denominator) + */ + getSpotPrices(params: { + chainInfo: ChainInfo + tokens: Token[] + quoteCurrency?: CurrencySymbol + }): Promise } diff --git a/sdk/swap-common/src/types/QuoteData.ts b/sdk/swap-common/src/types/QuoteData.ts deleted file mode 100644 index 2a0679e459..0000000000 --- a/sdk/swap-common/src/types/QuoteData.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { TokenAmount } from '@summerfi/sdk-common/common' -import type { SwapProviderType } from '../enums/SwapProviderType' - -/** - * @name QuoteData - * @description Gives information about a swap operation without providing - * the data needed to perform the swap - */ -export type QuoteData = { - provider: SwapProviderType - fromTokenAmount: TokenAmount - toTokenAmount: TokenAmount - estimatedGas: string -} diff --git a/sdk/swap-common/src/types/index.ts b/sdk/swap-common/src/types/index.ts deleted file mode 100644 index 8a053b1849..0000000000 --- a/sdk/swap-common/src/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { SwapData } from './SwapData' -export type { QuoteData } from './QuoteData' diff --git a/sdk/swap-service/e2e/oneinch.spec.ts b/sdk/swap-service/e2e/oneinch.spec.ts index dae0f28130..08b8ea423e 100644 --- a/sdk/swap-service/e2e/oneinch.spec.ts +++ b/sdk/swap-service/e2e/oneinch.spec.ts @@ -8,8 +8,7 @@ import { type ChainInfo, } from '@summerfi/sdk-common/common' import { subtractPercentage } from '@summerfi/sdk-common/utils' -import { SwapProviderType } from '@summerfi/swap-common/enums' -import { QuoteData, SwapData } from '@summerfi/swap-common/types' +import { QuoteData, SwapData, SwapProviderType } from '@summerfi/sdk-common/swap' import { SwapManagerFactory } from '../src/implementation/SwapManagerFactory' describe('OneInch | SwapManager | Integration', () => { diff --git a/sdk/swap-service/src/implementation/SwapManager.ts b/sdk/swap-service/src/implementation/SwapManager.ts index d1e42fda5b..c835a6fe6a 100644 --- a/sdk/swap-service/src/implementation/SwapManager.ts +++ b/sdk/swap-service/src/implementation/SwapManager.ts @@ -6,11 +6,10 @@ import type { Percentage, Address, } from '@summerfi/sdk-common/common' -import { ChainId } from '@summerfi/sdk-common/common' +import { ChainId, CurrencySymbol } from '@summerfi/sdk-common/common' import { ISwapProvider, ISwapManager } from '@summerfi/swap-common/interfaces' -import { QuoteData, SwapData } from '@summerfi/swap-common/types' -import { SwapProviderType } from '@summerfi/swap-common/enums' +import type { QuoteData, SwapData, SwapProviderType, SpotData } from '@summerfi/sdk-common/swap' export type SwapManagerProviderConfig = { provider: ISwapProvider @@ -60,6 +59,20 @@ export class SwapManager implements ISwapManager { return provider.getSwapQuoteExactInput(params) } + async getSpotPrices(params: { + chainInfo: ChainInfo + tokens: Token[] + quoteCurrency?: CurrencySymbol + forceUseProvider?: SwapProviderType + }): Promise { + const provider: Maybe = this._getBestProvider(params) + if (!provider) { + throw new Error('No swap provider available') + } + + return provider.getSpotPrices(params) + } + private _registerProvider(provider: ISwapProvider, forChainIds: number[]): void { for (const chainId of forChainIds) { const providers = this._providersByChainId.get(chainId) || [] diff --git a/sdk/swap-service/src/implementation/oneinch/OneInchSwapProvider.ts b/sdk/swap-service/src/implementation/oneinch/OneInchSwapProvider.ts index 5096a5e579..13441caaed 100644 --- a/sdk/swap-service/src/implementation/oneinch/OneInchSwapProvider.ts +++ b/sdk/swap-service/src/implementation/oneinch/OneInchSwapProvider.ts @@ -1,21 +1,31 @@ import { ISwapProvider } from '@summerfi/swap-common/interfaces' -import { SwapProviderType } from '@summerfi/swap-common/enums' -import { SwapData, QuoteData } from '@summerfi/swap-common/types' +import { + SwapProviderType, + SwapData, + SpotData, + SwapRoute, + QuoteData, +} from '@summerfi/sdk-common/swap' import { OneInchAuthHeader, OneInchAuthHeaderKey, OneInchQuoteResponse, + OneInchSpotResponse, OneInchSwapProviderConfig, OneInchSwapResponse, + OneInchSwapRoute, } from './types' import { HexData } from '@summerfi/sdk-common/common/aliases' import fetch from 'node-fetch' import { type ChainInfo, TokenAmount, - type Percentage, + Percentage, type Token, Address, + CurrencySymbol, + Price, + type AddressValue, } from '@summerfi/sdk-common/common' export class OneInchSwapProvider implements ISwapProvider { @@ -106,10 +116,55 @@ export class OneInchSwapProvider implements ISwapProvider { token: params.toToken, amount: responseData.toTokenAmount, }), + routes: this._extractSwapRoutes(responseData.protocols), estimatedGas: responseData.estimatedGas, } } + async getSpotPrices(params: { + chainInfo: ChainInfo + tokens: Token[] + quoteCurrency?: CurrencySymbol + }): Promise { + const spotUrl = this._formatOneInchSpotUrl({ + chainInfo: params.chainInfo, + tokenAddresses: params.tokens.map((token) => token.address), + quoteCurrency: params.quoteCurrency ?? CurrencySymbol.USD, + }) + + const authHeader = this._getOneInchAuthHeader() + + const response = await fetch(spotUrl, { + headers: authHeader, + }) + + if (!(response.status === 200 && response.statusText === 'OK')) { + throw new Error( + `Error performing 1inch spot price request ${spotUrl}: ${await response.body}`, + ) + } + + const responseData = (await response.json()) as OneInchSpotResponse + + return { + provider: SwapProviderType.OneInch, + prices: Object.entries(responseData).map(([address, price]) => { + const baseToken = params.tokens.find((token) => + token.address.equals(Address.createFromEthereum({ value: address as AddressValue })), + ) + if (!baseToken) { + throw new Error('BaseToken not found in params.tokens list when fetching spot prices') + } + + return Price.createFrom({ + value: price.toString(), + baseToken, + quoteToken: params.quoteCurrency || CurrencySymbol.USD, + }) + }), + } + } + private _getOneInchAuthHeader(): OneInchAuthHeader { return { [OneInchAuthHeaderKey]: this._apiKey } } @@ -152,4 +207,30 @@ export class OneInchSwapProvider implements ISwapProvider { return `${this._apiUrl}/${this._version}/${chainId}/quote?fromTokenAddress=${fromTokenAddress}&toTokenAddress=${toTokenAddress}&amount=${fromAmount}&protocols=${protocolsParam}` } + + private _formatOneInchSpotUrl(params: { + chainInfo: ChainInfo + tokenAddresses: Address[] + quoteCurrency: CurrencySymbol + }): string { + const chainId = params.chainInfo.chainId + const tokenAddresses = params.tokenAddresses.map((address) => address.value.toLowerCase()) + + return `${this._apiUrl}/${this._version}/${chainId}/price/${tokenAddresses.join(',')}?currency=${params.quoteCurrency.toUpperCase()}` + } + + private _extractSwapRoutes(protocols: OneInchSwapRoute[]): SwapRoute[] { + return protocols.map((route) => + route.map((hop) => + hop.map((hopPart) => ({ + name: hopPart.name, + part: Percentage.createFrom({ value: hopPart.part }), + fromTokenAddress: Address.createFromEthereum({ + value: hopPart.fromTokenAddress as HexData, + }), + toTokenAddress: Address.createFromEthereum({ value: hopPart.toTokenAddress as HexData }), + })), + ), + ) + } } diff --git a/sdk/swap-service/src/implementation/oneinch/types.ts b/sdk/swap-service/src/implementation/oneinch/types.ts index 6642b6b91d..0f161e6403 100644 --- a/sdk/swap-service/src/implementation/oneinch/types.ts +++ b/sdk/swap-service/src/implementation/oneinch/types.ts @@ -27,9 +27,23 @@ export interface OneInchSwapResponse extends OneInchBaseResponse { } } +export type OneInchSpotResponse = Record + export interface OneInchQuoteResponse extends OneInchBaseResponse { - protocols: unknown + /* One Inch can provide multiple routes */ + protocols: OneInchSwapRoute[] fromTokenAmount: string toTokenAmount: string estimatedGas: string } + +export type OneInchSwapRoute = OneInchSwapHop[] + +type OneInchSwapHop = OneInchSwapHopPart[] + +type OneInchSwapHopPart = { + name: string + part: number + fromTokenAddress: string + toTokenAddress: string +} diff --git a/sdk/testing-utils/src/mocks/SwapManagerMock.ts b/sdk/testing-utils/src/mocks/SwapManagerMock.ts index 855a182013..6592d70d19 100644 --- a/sdk/testing-utils/src/mocks/SwapManagerMock.ts +++ b/sdk/testing-utils/src/mocks/SwapManagerMock.ts @@ -1,11 +1,20 @@ -import { Address, ChainInfo, Percentage, Token, TokenAmount } from '@summerfi/sdk-common/common' +import { + Address, + ChainInfo, + Percentage, + Token, + TokenAmount, + CurrencySymbol, +} from '@summerfi/sdk-common/common' +import { SpotData, SwapData, QuoteData } from '@summerfi/sdk-common/swap' import { ISwapManager } from '@summerfi/swap-common/interfaces' -import { QuoteData, SwapData } from '@summerfi/swap-common/types' export class SwapManagerMock implements ISwapManager { private _swapDataReturnValue: SwapData = {} as SwapData private _quoteDataReturnValue: QuoteData = {} as QuoteData + private _spotPricesReturnValue: SpotData = {} as SpotData + private _lastGetSwapDataExactInputParams: | { chainInfo: ChainInfo @@ -32,6 +41,15 @@ export class SwapManagerMock implements ISwapManager { this._quoteDataReturnValue = quoteData } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getSpotPrices(params: { + chainInfo: ChainInfo + tokens: Token[] + quoteCurrency?: CurrencySymbol + }): Promise { + return this._spotPricesReturnValue + } + async getSwapDataExactInput(params: { chainInfo: ChainInfo fromAmount: TokenAmount