eco-solver 0.0.1-security → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of eco-solver might be problematic. Click here for more details.
- package/.eslintignore +8 -0
- package/.eslintrc.js +24 -0
- package/.github/workflows/ci.yaml +38 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/Dockerfile +11 -0
- package/LICENSE +21 -0
- package/README.md +29 -5
- package/config/default.ts +135 -0
- package/config/development.ts +95 -0
- package/config/preproduction.ts +17 -0
- package/config/production.ts +17 -0
- package/config/staging.ts +17 -0
- package/config/test.ts +7 -0
- package/index.js +43 -0
- package/jest.config.ts +14 -0
- package/nest-cli.json +8 -0
- package/package.json +115 -6
- package/src/api/api.module.ts +27 -0
- package/src/api/balance.controller.ts +41 -0
- package/src/api/quote.controller.ts +54 -0
- package/src/api/tests/balance.controller.spec.ts +113 -0
- package/src/api/tests/quote.controller.spec.ts +83 -0
- package/src/app.module.ts +74 -0
- package/src/balance/balance.module.ts +14 -0
- package/src/balance/balance.service.ts +230 -0
- package/src/balance/balance.ws.service.ts +104 -0
- package/src/balance/types.ts +16 -0
- package/src/bullmq/bullmq.helper.ts +41 -0
- package/src/bullmq/processors/eth-ws.processor.ts +47 -0
- package/src/bullmq/processors/inbox.processor.ts +55 -0
- package/src/bullmq/processors/interval.processor.ts +54 -0
- package/src/bullmq/processors/processor.module.ts +14 -0
- package/src/bullmq/processors/signer.processor.ts +41 -0
- package/src/bullmq/processors/solve-intent.processor.ts +73 -0
- package/src/bullmq/processors/tests/solve-intent.processor.spec.ts +3 -0
- package/src/bullmq/utils/queue.ts +22 -0
- package/src/chain-monitor/chain-monitor.module.ts +12 -0
- package/src/chain-monitor/chain-sync.service.ts +134 -0
- package/src/chain-monitor/tests/chain-sync.service.spec.ts +190 -0
- package/src/commander/.eslintrc.js +6 -0
- package/src/commander/balance/balance-command.module.ts +12 -0
- package/src/commander/balance/balance.command.ts +73 -0
- package/src/commander/command-main.ts +15 -0
- package/src/commander/commander-app.module.ts +31 -0
- package/src/commander/eco-config.command.ts +20 -0
- package/src/commander/safe/safe-command.module.ts +11 -0
- package/src/commander/safe/safe.command.ts +70 -0
- package/src/commander/transfer/client.command.ts +24 -0
- package/src/commander/transfer/transfer-command.module.ts +26 -0
- package/src/commander/transfer/transfer.command.ts +138 -0
- package/src/commander/utils.ts +8 -0
- package/src/common/chains/definitions/arbitrum.ts +12 -0
- package/src/common/chains/definitions/base.ts +21 -0
- package/src/common/chains/definitions/eco.ts +54 -0
- package/src/common/chains/definitions/ethereum.ts +22 -0
- package/src/common/chains/definitions/helix.ts +53 -0
- package/src/common/chains/definitions/mantle.ts +12 -0
- package/src/common/chains/definitions/optimism.ts +22 -0
- package/src/common/chains/definitions/polygon.ts +12 -0
- package/src/common/chains/supported.ts +26 -0
- package/src/common/chains/transport.ts +19 -0
- package/src/common/errors/eco-error.ts +155 -0
- package/src/common/events/constants.ts +3 -0
- package/src/common/events/viem.ts +22 -0
- package/src/common/logging/eco-log-message.ts +74 -0
- package/src/common/redis/constants.ts +55 -0
- package/src/common/redis/redis-connection-utils.ts +106 -0
- package/src/common/routes/constants.ts +3 -0
- package/src/common/utils/objects.ts +34 -0
- package/src/common/utils/strings.ts +49 -0
- package/src/common/utils/tests/objects.spec.ts +23 -0
- package/src/common/utils/tests/strings.spec.ts +22 -0
- package/src/common/viem/contracts.ts +25 -0
- package/src/common/viem/tests/utils.spec.ts +115 -0
- package/src/common/viem/utils.ts +78 -0
- package/src/contracts/ERC20.contract.ts +389 -0
- package/src/contracts/EntryPoint.V6.contract.ts +1309 -0
- package/src/contracts/KernelAccount.abi.ts +87 -0
- package/src/contracts/OwnableExecutor.abi.ts +128 -0
- package/src/contracts/SimpleAccount.contract.ts +524 -0
- package/src/contracts/inbox.ts +8 -0
- package/src/contracts/index.ts +9 -0
- package/src/contracts/intent-source.ts +55 -0
- package/src/contracts/interfaces/index.ts +1 -0
- package/src/contracts/interfaces/prover.interface.ts +22 -0
- package/src/contracts/prover.ts +9 -0
- package/src/contracts/tests/erc20.contract.spec.ts +59 -0
- package/src/contracts/utils.ts +31 -0
- package/src/decoder/decoder.interface.ts +3 -0
- package/src/decoder/tests/utils.spec.ts +36 -0
- package/src/decoder/utils.ts +24 -0
- package/src/decorators/cacheable.decorator.ts +48 -0
- package/src/eco-configs/aws-config.service.ts +75 -0
- package/src/eco-configs/eco-config.module.ts +44 -0
- package/src/eco-configs/eco-config.service.ts +220 -0
- package/src/eco-configs/eco-config.types.ts +278 -0
- package/src/eco-configs/interfaces/config-source.interface.ts +3 -0
- package/src/eco-configs/tests/aws-config.service.spec.ts +52 -0
- package/src/eco-configs/tests/eco-config.service.spec.ts +137 -0
- package/src/eco-configs/tests/utils.spec.ts +84 -0
- package/src/eco-configs/utils.ts +49 -0
- package/src/fee/fee.module.ts +10 -0
- package/src/fee/fee.service.ts +467 -0
- package/src/fee/tests/fee.service.spec.ts +909 -0
- package/src/fee/tests/utils.spec.ts +49 -0
- package/src/fee/types.ts +44 -0
- package/src/fee/utils.ts +23 -0
- package/src/flags/flags.module.ts +10 -0
- package/src/flags/flags.service.ts +112 -0
- package/src/flags/tests/flags.service.spec.ts +68 -0
- package/src/flags/utils.ts +22 -0
- package/src/health/constants.ts +1 -0
- package/src/health/health.controller.ts +23 -0
- package/src/health/health.module.ts +25 -0
- package/src/health/health.service.ts +40 -0
- package/src/health/indicators/balance.indicator.ts +196 -0
- package/src/health/indicators/eco-redis.indicator.ts +23 -0
- package/src/health/indicators/git-commit.indicator.ts +67 -0
- package/src/health/indicators/mongodb.indicator.ts +11 -0
- package/src/health/indicators/permission.indicator.ts +64 -0
- package/src/intent/create-intent.service.ts +129 -0
- package/src/intent/feasable-intent.service.ts +80 -0
- package/src/intent/fulfill-intent.service.ts +318 -0
- package/src/intent/intent.controller.ts +199 -0
- package/src/intent/intent.module.ts +49 -0
- package/src/intent/schemas/intent-call-data.schema.ts +16 -0
- package/src/intent/schemas/intent-data.schema.ts +114 -0
- package/src/intent/schemas/intent-source.schema.ts +33 -0
- package/src/intent/schemas/intent-token-amount.schema.ts +14 -0
- package/src/intent/schemas/reward-data.schema.ts +48 -0
- package/src/intent/schemas/route-data.schema.ts +52 -0
- package/src/intent/schemas/watch-event.schema.ts +32 -0
- package/src/intent/tests/create-intent.service.spec.ts +215 -0
- package/src/intent/tests/feasable-intent.service.spec.ts +155 -0
- package/src/intent/tests/fulfill-intent.service.spec.ts +564 -0
- package/src/intent/tests/utils-intent.service.spec.ts +308 -0
- package/src/intent/tests/utils.spec.ts +62 -0
- package/src/intent/tests/validate-intent.service.spec.ts +297 -0
- package/src/intent/tests/validation.service.spec.ts +337 -0
- package/src/intent/utils-intent.service.ts +168 -0
- package/src/intent/utils.ts +37 -0
- package/src/intent/validate-intent.service.ts +176 -0
- package/src/intent/validation.sevice.ts +223 -0
- package/src/interceptors/big-int.interceptor.ts +30 -0
- package/src/intervals/interval.module.ts +18 -0
- package/src/intervals/retry-infeasable-intents.service.ts +89 -0
- package/src/intervals/tests/retry-infeasable-intents.service.spec.ts +167 -0
- package/src/kms/errors.ts +0 -0
- package/src/kms/kms.module.ts +12 -0
- package/src/kms/kms.service.ts +65 -0
- package/src/kms/tests/kms.service.spec.ts +60 -0
- package/src/liquidity-manager/jobs/check-balances-cron.job.ts +229 -0
- package/src/liquidity-manager/jobs/liquidity-manager.job.ts +52 -0
- package/src/liquidity-manager/jobs/rebalance.job.ts +61 -0
- package/src/liquidity-manager/liquidity-manager.module.ts +29 -0
- package/src/liquidity-manager/processors/base.processor.ts +117 -0
- package/src/liquidity-manager/processors/eco-protocol-intents.processor.ts +34 -0
- package/src/liquidity-manager/processors/grouped-jobs.processor.ts +103 -0
- package/src/liquidity-manager/queues/liquidity-manager.queue.ts +48 -0
- package/src/liquidity-manager/schemas/rebalance-token.schema.ts +32 -0
- package/src/liquidity-manager/schemas/rebalance.schema.ts +32 -0
- package/src/liquidity-manager/services/liquidity-manager.service.ts +188 -0
- package/src/liquidity-manager/services/liquidity-provider.service.ts +25 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.spec.ts +125 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.ts +117 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/utils/get-transaction-hashes.ts +16 -0
- package/src/liquidity-manager/tests/liquidity-manager.service.spec.ts +142 -0
- package/src/liquidity-manager/types/token-state.enum.ts +5 -0
- package/src/liquidity-manager/types/types.d.ts +52 -0
- package/src/liquidity-manager/utils/address.ts +5 -0
- package/src/liquidity-manager/utils/math.ts +9 -0
- package/src/liquidity-manager/utils/serialize.spec.ts +24 -0
- package/src/liquidity-manager/utils/serialize.ts +47 -0
- package/src/liquidity-manager/utils/token.ts +91 -0
- package/src/main.ts +63 -0
- package/src/nest-redlock/nest-redlock.config.ts +14 -0
- package/src/nest-redlock/nest-redlock.interface.ts +5 -0
- package/src/nest-redlock/nest-redlock.module.ts +64 -0
- package/src/nest-redlock/nest-redlock.service.ts +59 -0
- package/src/prover/proof.service.ts +184 -0
- package/src/prover/prover.module.ts +10 -0
- package/src/prover/tests/proof.service.spec.ts +154 -0
- package/src/quote/dto/quote.intent.data.dto.ts +35 -0
- package/src/quote/dto/quote.reward.data.dto.ts +67 -0
- package/src/quote/dto/quote.route.data.dto.ts +71 -0
- package/src/quote/dto/types.ts +18 -0
- package/src/quote/errors.ts +215 -0
- package/src/quote/quote.module.ts +17 -0
- package/src/quote/quote.service.ts +299 -0
- package/src/quote/schemas/quote-call.schema.ts +16 -0
- package/src/quote/schemas/quote-intent.schema.ts +27 -0
- package/src/quote/schemas/quote-reward.schema.ts +24 -0
- package/src/quote/schemas/quote-route.schema.ts +30 -0
- package/src/quote/schemas/quote-token.schema.ts +14 -0
- package/src/quote/tests/quote.service.spec.ts +444 -0
- package/src/sign/atomic-signer.service.ts +24 -0
- package/src/sign/atomic.nonce.service.ts +114 -0
- package/src/sign/kms-account/kmsToAccount.ts +73 -0
- package/src/sign/kms-account/signKms.ts +30 -0
- package/src/sign/kms-account/signKmsTransaction.ts +37 -0
- package/src/sign/kms-account/signKmsTypedData.ts +21 -0
- package/src/sign/nonce.service.ts +89 -0
- package/src/sign/schemas/nonce.schema.ts +36 -0
- package/src/sign/sign.controller.ts +52 -0
- package/src/sign/sign.helper.ts +23 -0
- package/src/sign/sign.module.ts +27 -0
- package/src/sign/signer-kms.service.ts +27 -0
- package/src/sign/signer.service.ts +26 -0
- package/src/solver/filters/tests/valid-smart-wallet.service.spec.ts +87 -0
- package/src/solver/filters/valid-smart-wallet.service.ts +58 -0
- package/src/solver/solver.module.ts +10 -0
- package/src/transaction/multichain-public-client.service.ts +15 -0
- package/src/transaction/smart-wallets/kernel/actions/encodeData.kernel.ts +57 -0
- package/src/transaction/smart-wallets/kernel/create-kernel-client-v2.account.ts +183 -0
- package/src/transaction/smart-wallets/kernel/create.kernel.account.ts +270 -0
- package/src/transaction/smart-wallets/kernel/index.ts +2 -0
- package/src/transaction/smart-wallets/kernel/kernel-account-client-v2.service.ts +90 -0
- package/src/transaction/smart-wallets/kernel/kernel-account-client.service.ts +107 -0
- package/src/transaction/smart-wallets/kernel/kernel-account.client.ts +105 -0
- package/src/transaction/smart-wallets/kernel/kernel-account.config.ts +34 -0
- package/src/transaction/smart-wallets/simple-account/create.simple.account.ts +19 -0
- package/src/transaction/smart-wallets/simple-account/index.ts +2 -0
- package/src/transaction/smart-wallets/simple-account/simple-account-client.service.ts +42 -0
- package/src/transaction/smart-wallets/simple-account/simple-account.client.ts +83 -0
- package/src/transaction/smart-wallets/simple-account/simple-account.config.ts +5 -0
- package/src/transaction/smart-wallets/smart-wallet.types.ts +38 -0
- package/src/transaction/smart-wallets/utils.ts +14 -0
- package/src/transaction/transaction.module.ts +25 -0
- package/src/transaction/viem_multichain_client.service.ts +100 -0
- package/src/transforms/viem-address.decorator.ts +14 -0
- package/src/utils/bigint.ts +44 -0
- package/src/utils/types.ts +18 -0
- package/src/watch/intent/tests/watch-create-intent.service.spec.ts +257 -0
- package/src/watch/intent/tests/watch-fulfillment.service.spec.ts +141 -0
- package/src/watch/intent/watch-create-intent.service.ts +106 -0
- package/src/watch/intent/watch-event.service.ts +133 -0
- package/src/watch/intent/watch-fulfillment.service.ts +115 -0
- package/src/watch/watch.module.ts +13 -0
- package/test/app.e2e-spec.ts +21 -0
- package/test/jest-e2e.json +9 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +29 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
jest.mock('@lifi/sdk')
|
2
|
+
|
3
|
+
import { FlowProducer, Queue } from 'bullmq'
|
4
|
+
import { Test, TestingModule } from '@nestjs/testing'
|
5
|
+
import { BullModule, getFlowProducerToken, getQueueToken } from '@nestjs/bullmq'
|
6
|
+
import { createMock, DeepMocked } from '@golevelup/ts-jest'
|
7
|
+
import * as LiFi from '@lifi/sdk'
|
8
|
+
import { EcoConfigService } from '@/eco-configs/eco-config.service'
|
9
|
+
import { LiquidityManagerQueue } from '@/liquidity-manager/queues/liquidity-manager.queue'
|
10
|
+
import { LiFiProviderService } from '@/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service'
|
11
|
+
import { KernelAccountClientV2Service } from '@/transaction/smart-wallets/kernel/kernel-account-client-v2.service'
|
12
|
+
|
13
|
+
describe('LiFiProviderService', () => {
|
14
|
+
let lifiProviderService: LiFiProviderService
|
15
|
+
let kernelAccountClientService: KernelAccountClientV2Service
|
16
|
+
let ecoConfigService: DeepMocked<EcoConfigService>
|
17
|
+
let queue: DeepMocked<Queue>
|
18
|
+
let flowProducer: DeepMocked<FlowProducer>
|
19
|
+
|
20
|
+
beforeEach(async () => {
|
21
|
+
const chainMod: TestingModule = await Test.createTestingModule({
|
22
|
+
providers: [
|
23
|
+
LiFiProviderService,
|
24
|
+
{ provide: EcoConfigService, useValue: createMock<EcoConfigService>() },
|
25
|
+
{
|
26
|
+
provide: KernelAccountClientV2Service,
|
27
|
+
useValue: createMock<KernelAccountClientV2Service>(),
|
28
|
+
},
|
29
|
+
],
|
30
|
+
imports: [
|
31
|
+
BullModule.registerQueue({ name: LiquidityManagerQueue.queueName }),
|
32
|
+
BullModule.registerFlowProducerAsync({ name: LiquidityManagerQueue.flowName }),
|
33
|
+
],
|
34
|
+
})
|
35
|
+
.overrideProvider(getQueueToken(LiquidityManagerQueue.queueName))
|
36
|
+
.useValue(createMock<Queue>())
|
37
|
+
.overrideProvider(getFlowProducerToken(LiquidityManagerQueue.flowName))
|
38
|
+
.useValue(createMock<FlowProducer>())
|
39
|
+
.compile()
|
40
|
+
|
41
|
+
ecoConfigService = chainMod.get(EcoConfigService)
|
42
|
+
lifiProviderService = chainMod.get(LiFiProviderService)
|
43
|
+
kernelAccountClientService = chainMod.get(KernelAccountClientV2Service)
|
44
|
+
|
45
|
+
queue = chainMod.get(getQueueToken(LiquidityManagerQueue.queueName))
|
46
|
+
flowProducer = chainMod.get(getFlowProducerToken(LiquidityManagerQueue.flowName))
|
47
|
+
})
|
48
|
+
|
49
|
+
afterEach(() => {
|
50
|
+
jest.restoreAllMocks()
|
51
|
+
})
|
52
|
+
|
53
|
+
describe('OnModuleInit', () => {
|
54
|
+
it('should configure LiFi SDK on init', async () => {
|
55
|
+
const mockGetClient = jest.spyOn(kernelAccountClientService, 'getClient')
|
56
|
+
mockGetClient.mockReturnValue({ account: { address: '0x123' } } as any)
|
57
|
+
|
58
|
+
jest.spyOn(ecoConfigService, 'getIntentSources').mockReturnValue([{ chainID: 10 }] as any)
|
59
|
+
|
60
|
+
const rpcUrls = { '10': 'http://op.rpc.com' }
|
61
|
+
jest.spyOn(ecoConfigService, 'getChainRPCs').mockReturnValue(rpcUrls)
|
62
|
+
|
63
|
+
await lifiProviderService.onModuleInit()
|
64
|
+
|
65
|
+
expect(mockGetClient).toHaveBeenCalled()
|
66
|
+
expect(lifiProviderService['walletAddress']).toEqual('0x123')
|
67
|
+
expect(LiFi.createConfig).toHaveBeenCalledWith(
|
68
|
+
expect.objectContaining({
|
69
|
+
integrator: 'Eco',
|
70
|
+
rpcUrls: { '10': [rpcUrls['10']] },
|
71
|
+
}),
|
72
|
+
)
|
73
|
+
})
|
74
|
+
})
|
75
|
+
|
76
|
+
describe('getQuote', () => {
|
77
|
+
it('should return a quote', async () => {
|
78
|
+
const mockTokenIn = {
|
79
|
+
chainId: 1,
|
80
|
+
config: { address: '0xTokenIn' },
|
81
|
+
balance: { decimals: 18 },
|
82
|
+
}
|
83
|
+
const mockTokenOut = {
|
84
|
+
chainId: 1,
|
85
|
+
config: { address: '0xTokenOut' },
|
86
|
+
balance: { decimals: 18 },
|
87
|
+
}
|
88
|
+
const mockRoute = {
|
89
|
+
fromAmount: '1000000000000000000',
|
90
|
+
toAmount: '2000000000000000000',
|
91
|
+
toAmountMin: '1900000000000000000',
|
92
|
+
steps: [],
|
93
|
+
}
|
94
|
+
jest.spyOn(LiFi, 'getRoutes').mockResolvedValue({ routes: [mockRoute] } as any)
|
95
|
+
|
96
|
+
const result = await lifiProviderService.getQuote(mockTokenIn as any, mockTokenOut as any, 1)
|
97
|
+
|
98
|
+
expect(result.amountIn).toEqual(BigInt(mockRoute.fromAmount))
|
99
|
+
expect(result.amountOut).toEqual(BigInt(mockRoute.toAmount))
|
100
|
+
expect(result.slippage).toBeCloseTo(0.05)
|
101
|
+
expect(result.tokenIn).toEqual(mockTokenIn)
|
102
|
+
expect(result.tokenOut).toEqual(mockTokenOut)
|
103
|
+
expect(result.strategy).toEqual('LiFi')
|
104
|
+
expect(result.context).toEqual(mockRoute)
|
105
|
+
})
|
106
|
+
})
|
107
|
+
|
108
|
+
describe('execute', () => {
|
109
|
+
it('should execute a quote', async () => {
|
110
|
+
const mockQuote = {
|
111
|
+
tokenIn: { config: { address: '0xTokenIn', chainId: 1 } },
|
112
|
+
tokenOut: { config: { address: '0xTokenOut', chainId: 1 } },
|
113
|
+
amountIn: BigInt(1000000000000000000),
|
114
|
+
amountOut: BigInt(2000000000000000000),
|
115
|
+
slippage: 0.05,
|
116
|
+
context: { gasCostUSD: 10, steps: [] },
|
117
|
+
}
|
118
|
+
const mockExecuteRoute = jest.spyOn(LiFi, 'executeRoute')
|
119
|
+
|
120
|
+
await lifiProviderService.execute(mockQuote as any)
|
121
|
+
|
122
|
+
expect(mockExecuteRoute).toHaveBeenCalledWith(mockQuote.context, expect.any(Object))
|
123
|
+
})
|
124
|
+
})
|
125
|
+
})
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import { Injectable, Logger, OnModuleInit } from '@nestjs/common'
|
2
|
+
import { parseUnits } from 'viem'
|
3
|
+
import { createConfig, EVM, executeRoute, getRoutes, RoutesRequest, SDKConfig } from '@lifi/sdk'
|
4
|
+
import { EcoLogMessage } from '@/common/logging/eco-log-message'
|
5
|
+
import { EcoConfigService } from '@/eco-configs/eco-config.service'
|
6
|
+
import { logLiFiProcess } from '@/liquidity-manager/services/liquidity-providers/LiFi/utils/get-transaction-hashes'
|
7
|
+
import { KernelAccountClientV2Service } from '@/transaction/smart-wallets/kernel/kernel-account-client-v2.service'
|
8
|
+
import { RebalanceQuote, Strategy, TokenData } from '@/liquidity-manager/types/types'
|
9
|
+
|
10
|
+
@Injectable()
|
11
|
+
export class LiFiProviderService implements OnModuleInit {
|
12
|
+
private logger = new Logger(LiFiProviderService.name)
|
13
|
+
private walletAddress: string
|
14
|
+
|
15
|
+
constructor(
|
16
|
+
private readonly ecoConfigService: EcoConfigService,
|
17
|
+
private readonly kernelAccountClientService: KernelAccountClientV2Service,
|
18
|
+
) {}
|
19
|
+
|
20
|
+
async onModuleInit() {
|
21
|
+
// Use first intent source's network as the default network
|
22
|
+
const [intentSource] = this.ecoConfigService.getIntentSources()
|
23
|
+
|
24
|
+
const client = await this.kernelAccountClientService.getClient(intentSource.chainID)
|
25
|
+
this.walletAddress = client.account!.address
|
26
|
+
|
27
|
+
// Configure LiFi providers
|
28
|
+
createConfig({
|
29
|
+
integrator: 'Eco',
|
30
|
+
rpcUrls: this.getLiFiRPCUrls(),
|
31
|
+
providers: [
|
32
|
+
EVM({
|
33
|
+
getWalletClient: () => Promise.resolve(client),
|
34
|
+
switchChain: (chainId) => this.kernelAccountClientService.getClient(chainId),
|
35
|
+
}),
|
36
|
+
],
|
37
|
+
})
|
38
|
+
}
|
39
|
+
|
40
|
+
getStrategy(): Strategy {
|
41
|
+
return 'LiFi'
|
42
|
+
}
|
43
|
+
|
44
|
+
async getQuote(
|
45
|
+
tokenIn: TokenData,
|
46
|
+
tokenOut: TokenData,
|
47
|
+
swapAmount: number,
|
48
|
+
): Promise<RebalanceQuote> {
|
49
|
+
const routesRequest: RoutesRequest = {
|
50
|
+
// Origin chain
|
51
|
+
fromAddress: this.walletAddress,
|
52
|
+
fromChainId: tokenIn.chainId,
|
53
|
+
fromTokenAddress: tokenIn.config.address,
|
54
|
+
fromAmount: parseUnits(swapAmount.toString(), tokenIn.balance.decimals).toString(),
|
55
|
+
|
56
|
+
// Destination chain
|
57
|
+
toAddress: this.walletAddress,
|
58
|
+
toChainId: tokenOut.chainId,
|
59
|
+
toTokenAddress: tokenOut.config.address,
|
60
|
+
}
|
61
|
+
|
62
|
+
const result = await getRoutes(routesRequest)
|
63
|
+
|
64
|
+
const [route] = result.routes
|
65
|
+
|
66
|
+
const slippage = 1 - parseFloat(route.toAmountMin) / parseFloat(route.toAmount)
|
67
|
+
|
68
|
+
return {
|
69
|
+
amountIn: BigInt(route.fromAmount),
|
70
|
+
amountOut: BigInt(route.toAmount),
|
71
|
+
slippage: slippage,
|
72
|
+
tokenIn: tokenIn,
|
73
|
+
tokenOut: tokenOut,
|
74
|
+
strategy: this.getStrategy(),
|
75
|
+
context: route,
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
async execute(quote: RebalanceQuote<'LiFi'>) {
|
80
|
+
this.logger.debug(
|
81
|
+
EcoLogMessage.fromDefault({
|
82
|
+
message: 'LiFiProviderService: executing quote',
|
83
|
+
properties: {
|
84
|
+
tokenIn: quote.tokenIn.config.address,
|
85
|
+
chainIn: quote.tokenIn.config.chainId,
|
86
|
+
tokenOut: quote.tokenIn.config.address,
|
87
|
+
chainOut: quote.tokenIn.config.chainId,
|
88
|
+
amountIn: quote.amountIn,
|
89
|
+
amountOut: quote.amountOut,
|
90
|
+
slippage: quote.slippage,
|
91
|
+
gasCostUSD: quote.context.gasCostUSD,
|
92
|
+
steps: quote.context.steps.map((step) => ({
|
93
|
+
type: step.type,
|
94
|
+
tool: step.tool,
|
95
|
+
})),
|
96
|
+
},
|
97
|
+
}),
|
98
|
+
)
|
99
|
+
|
100
|
+
// Execute the quote
|
101
|
+
return executeRoute(quote.context, {
|
102
|
+
updateRouteHook: (route) => logLiFiProcess(this.logger, route),
|
103
|
+
acceptExchangeRateUpdateHook: () => Promise.resolve(true),
|
104
|
+
})
|
105
|
+
}
|
106
|
+
|
107
|
+
private getLiFiRPCUrls() {
|
108
|
+
const rpcUrl = this.ecoConfigService.getChainRPCs()
|
109
|
+
const lifiRPCUrls: SDKConfig['rpcUrls'] = {}
|
110
|
+
|
111
|
+
for (const chainId in rpcUrl) {
|
112
|
+
lifiRPCUrls[parseInt(chainId)] = [rpcUrl[chainId]]
|
113
|
+
}
|
114
|
+
|
115
|
+
return lifiRPCUrls
|
116
|
+
}
|
117
|
+
}
|
package/src/liquidity-manager/services/liquidity-providers/LiFi/utils/get-transaction-hashes.ts
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
import { Logger } from '@nestjs/common'
|
2
|
+
import { RouteExtended } from '@lifi/sdk'
|
3
|
+
import { EcoLogMessage } from '@/common/logging/eco-log-message'
|
4
|
+
|
5
|
+
export function logLiFiProcess(logger: Logger, route: RouteExtended) {
|
6
|
+
route.steps.forEach((step, index) => {
|
7
|
+
step.execution?.process.forEach((process) => {
|
8
|
+
logger.log(
|
9
|
+
EcoLogMessage.fromDefault({
|
10
|
+
message: `LiFi: Step ${index + 1}, Process ${process.type}:`,
|
11
|
+
properties: process,
|
12
|
+
}),
|
13
|
+
)
|
14
|
+
})
|
15
|
+
})
|
16
|
+
}
|
@@ -0,0 +1,142 @@
|
|
1
|
+
import { FlowProducer, Queue } from 'bullmq'
|
2
|
+
import { Model } from 'mongoose'
|
3
|
+
import { getModelToken } from '@nestjs/mongoose'
|
4
|
+
import { Test, TestingModule } from '@nestjs/testing'
|
5
|
+
import { BullModule, getFlowProducerToken, getQueueToken } from '@nestjs/bullmq'
|
6
|
+
import { createMock, DeepMocked } from '@golevelup/ts-jest'
|
7
|
+
import { EcoConfigService } from '@/eco-configs/eco-config.service'
|
8
|
+
import { BalanceService } from '@/balance/balance.service'
|
9
|
+
import { LiquidityManagerQueue } from '@/liquidity-manager/queues/liquidity-manager.queue'
|
10
|
+
import { LiquidityManagerService } from '@/liquidity-manager/services/liquidity-manager.service'
|
11
|
+
import { LiquidityProviderService } from '@/liquidity-manager/services/liquidity-provider.service'
|
12
|
+
import { CheckBalancesCronJobManager } from '@/liquidity-manager/jobs/check-balances-cron.job'
|
13
|
+
import { RebalanceModel } from '@/liquidity-manager/schemas/rebalance.schema'
|
14
|
+
|
15
|
+
describe('LiquidityManagerService', () => {
|
16
|
+
let liquidityManagerService: LiquidityManagerService
|
17
|
+
let liquidityProviderService: LiquidityProviderService
|
18
|
+
let balanceService: DeepMocked<BalanceService>
|
19
|
+
let ecoConfigService: DeepMocked<EcoConfigService>
|
20
|
+
let queue: DeepMocked<Queue>
|
21
|
+
|
22
|
+
beforeEach(async () => {
|
23
|
+
const chainMod: TestingModule = await Test.createTestingModule({
|
24
|
+
providers: [
|
25
|
+
LiquidityManagerService,
|
26
|
+
{ provide: BalanceService, useValue: createMock<BalanceService>() },
|
27
|
+
{ provide: EcoConfigService, useValue: createMock<EcoConfigService>() },
|
28
|
+
{ provide: LiquidityProviderService, useValue: createMock<LiquidityProviderService>() },
|
29
|
+
{
|
30
|
+
provide: getModelToken(RebalanceModel.name),
|
31
|
+
useValue: createMock<Model<RebalanceModel>>(),
|
32
|
+
},
|
33
|
+
],
|
34
|
+
imports: [
|
35
|
+
BullModule.registerQueue({ name: LiquidityManagerQueue.queueName }),
|
36
|
+
BullModule.registerFlowProducerAsync({ name: LiquidityManagerQueue.flowName }),
|
37
|
+
],
|
38
|
+
})
|
39
|
+
.overrideProvider(getQueueToken(LiquidityManagerQueue.queueName))
|
40
|
+
.useValue(createMock<Queue>())
|
41
|
+
.overrideProvider(getFlowProducerToken(LiquidityManagerQueue.flowName))
|
42
|
+
.useValue(createMock<FlowProducer>())
|
43
|
+
.compile()
|
44
|
+
|
45
|
+
balanceService = chainMod.get(BalanceService)
|
46
|
+
ecoConfigService = chainMod.get(EcoConfigService)
|
47
|
+
liquidityManagerService = chainMod.get(LiquidityManagerService)
|
48
|
+
liquidityProviderService = chainMod.get(LiquidityProviderService)
|
49
|
+
queue = chainMod.get(getQueueToken(LiquidityManagerQueue.queueName))
|
50
|
+
})
|
51
|
+
|
52
|
+
const mockConfig = {
|
53
|
+
targetSlippage: 0.02,
|
54
|
+
intervalDuration: 1000,
|
55
|
+
thresholds: { surplus: 0.1, deficit: 0.2 },
|
56
|
+
}
|
57
|
+
|
58
|
+
afterEach(() => {
|
59
|
+
jest.restoreAllMocks()
|
60
|
+
})
|
61
|
+
|
62
|
+
describe('onApplicationBootstrap', () => {
|
63
|
+
it('should start cron job', async () => {
|
64
|
+
const intervalDuration = 1000
|
65
|
+
jest
|
66
|
+
.spyOn(ecoConfigService, 'getLiquidityManager')
|
67
|
+
.mockReturnValue({ intervalDuration } as any)
|
68
|
+
|
69
|
+
await liquidityManagerService.onApplicationBootstrap()
|
70
|
+
|
71
|
+
const upsertJobScheduler = jest.spyOn(queue, 'upsertJobScheduler')
|
72
|
+
expect(upsertJobScheduler).toHaveBeenCalledWith(
|
73
|
+
CheckBalancesCronJobManager.jobSchedulerName,
|
74
|
+
{ every: intervalDuration },
|
75
|
+
expect.anything(),
|
76
|
+
)
|
77
|
+
})
|
78
|
+
|
79
|
+
it('should set liquidity manager config', async () => {
|
80
|
+
const mockConfig = { test: 1000n }
|
81
|
+
jest.spyOn(ecoConfigService, 'getLiquidityManager').mockReturnValue(mockConfig as any)
|
82
|
+
await liquidityManagerService.onApplicationBootstrap()
|
83
|
+
expect(liquidityManagerService['config']).toEqual(mockConfig)
|
84
|
+
})
|
85
|
+
})
|
86
|
+
|
87
|
+
describe('analyzeTokens', () => {
|
88
|
+
it('should analyze tokens and return the analysis', async () => {
|
89
|
+
const mockTokens = [
|
90
|
+
{ config: { targetBalance: 10 }, balance: { balance: 100n } },
|
91
|
+
{ config: { targetBalance: 100 }, balance: { balance: 100n } },
|
92
|
+
{ config: { targetBalance: 200 }, balance: { balance: 100n } },
|
93
|
+
]
|
94
|
+
|
95
|
+
liquidityManagerService['config'] = mockConfig
|
96
|
+
|
97
|
+
jest.spyOn(balanceService, 'getAllTokenData').mockResolvedValue(mockTokens as any)
|
98
|
+
|
99
|
+
const result = await liquidityManagerService.analyzeTokens()
|
100
|
+
|
101
|
+
expect(result.items).toHaveLength(3)
|
102
|
+
expect(result.surplus.items).toHaveLength(1)
|
103
|
+
expect(result.deficit.items).toHaveLength(1)
|
104
|
+
})
|
105
|
+
})
|
106
|
+
|
107
|
+
describe('getOptimizedRebalancing', () => {
|
108
|
+
it('should return swap quotes if possible', async () => {
|
109
|
+
const mockDeficitToken = {
|
110
|
+
config: { chainId: 1 },
|
111
|
+
analysis: { diff: 100, balance: { current: 50 }, targetSlippage: { min: 150 } },
|
112
|
+
}
|
113
|
+
const mockSurplusTokens = [{ config: { chainId: 1 }, analysis: { diff: 200 } }]
|
114
|
+
|
115
|
+
jest
|
116
|
+
.spyOn(liquidityProviderService, 'getQuote')
|
117
|
+
.mockResolvedValue([{ amountOut: 100 }] as any)
|
118
|
+
|
119
|
+
const result = await liquidityManagerService.getOptimizedRebalancing(
|
120
|
+
mockDeficitToken as any,
|
121
|
+
mockSurplusTokens as any,
|
122
|
+
)
|
123
|
+
|
124
|
+
expect(result).toHaveLength(1)
|
125
|
+
})
|
126
|
+
})
|
127
|
+
|
128
|
+
describe('executeRebalancing', () => {
|
129
|
+
it('should execute rebalancing quotes', async () => {
|
130
|
+
const mockRebalanceData = {
|
131
|
+
rebalance: {
|
132
|
+
quotes: ['quote1', 'quote2'],
|
133
|
+
},
|
134
|
+
}
|
135
|
+
jest.spyOn(liquidityProviderService, 'execute').mockResolvedValue(undefined as any)
|
136
|
+
|
137
|
+
await liquidityManagerService.executeRebalancing(mockRebalanceData as any)
|
138
|
+
|
139
|
+
expect(liquidityProviderService.execute).toHaveBeenCalledTimes(2)
|
140
|
+
})
|
141
|
+
})
|
142
|
+
})
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import { TokenBalance, TokenConfig } from '@/balance/types'
|
2
|
+
import * as LiFi from '@lifi/sdk'
|
3
|
+
|
4
|
+
type TokenState = 'DEFICIT' | 'SURPLUS' | 'IN_RANGE'
|
5
|
+
|
6
|
+
interface TokenData {
|
7
|
+
chainId: number
|
8
|
+
config: TokenConfig
|
9
|
+
balance: TokenBalance
|
10
|
+
}
|
11
|
+
|
12
|
+
interface TokenBalanceAnalysis {
|
13
|
+
target: bigint
|
14
|
+
current: bigint
|
15
|
+
minimum: bigint
|
16
|
+
maximum: bigint
|
17
|
+
}
|
18
|
+
|
19
|
+
interface TokenAnalysis {
|
20
|
+
state: TokenState
|
21
|
+
diff: number
|
22
|
+
targetSlippage: { min: bigint; max: bigint }
|
23
|
+
balance: TokenBalanceAnalysis
|
24
|
+
}
|
25
|
+
|
26
|
+
interface TokenDataAnalyzed extends TokenData {
|
27
|
+
analysis: TokenAnalysis
|
28
|
+
}
|
29
|
+
|
30
|
+
// Strategy context
|
31
|
+
|
32
|
+
type LiFiStrategyContext = LiFi.Route
|
33
|
+
|
34
|
+
type Strategy = 'LiFi'
|
35
|
+
type StrategyContext<S extends Strategy = Strategy> = S extends 'LiFi' ? LiFiStrategyContext : never
|
36
|
+
|
37
|
+
// Quote
|
38
|
+
|
39
|
+
interface RebalanceQuote<S extends Strategy = Strategy> {
|
40
|
+
amountIn: bigint
|
41
|
+
amountOut: bigint
|
42
|
+
slippage: number
|
43
|
+
tokenIn: TokenData
|
44
|
+
tokenOut: TokenData
|
45
|
+
strategy: S
|
46
|
+
context: StrategyContext<S>
|
47
|
+
}
|
48
|
+
|
49
|
+
interface RebalanceRequest {
|
50
|
+
token: TokenData
|
51
|
+
quotes: RebalanceQuote[]
|
52
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export function getSlippageRange(amount: bigint, slippage: number) {
|
2
|
+
const slippageFactor = BigInt(Math.round(slippage * 100000))
|
3
|
+
const base = 100000n
|
4
|
+
|
5
|
+
const min = (amount * (base - slippageFactor)) / base
|
6
|
+
const max = (amount * (base + slippageFactor)) / base
|
7
|
+
|
8
|
+
return { min, max }
|
9
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { deserialize, serialize } from './serialize'
|
2
|
+
|
3
|
+
describe('Serialize BigInt', () => {
|
4
|
+
it('serialize arrays correctly', () => {
|
5
|
+
const obj = { array: [1, 2, 3] }
|
6
|
+
const objDeserialized = deserialize(serialize(obj))
|
7
|
+
|
8
|
+
expect(Array.isArray(objDeserialized.array)).toBeTruthy()
|
9
|
+
})
|
10
|
+
|
11
|
+
it('serialize bigint in array correctly', () => {
|
12
|
+
const obj = { array: [1n, 2n, 3n] }
|
13
|
+
const objDeserialized = deserialize(serialize(obj))
|
14
|
+
|
15
|
+
expect(objDeserialized.array[0]).toBe(1n)
|
16
|
+
})
|
17
|
+
|
18
|
+
it('serialize bigint correctly', () => {
|
19
|
+
const obj = { number: 1, bigInt: 1n }
|
20
|
+
const objDeserialized = deserialize(serialize(obj))
|
21
|
+
|
22
|
+
expect(typeof objDeserialized.bigInt).toBe('bigint')
|
23
|
+
})
|
24
|
+
})
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import * as _ from 'lodash'
|
2
|
+
|
3
|
+
export type Serialize<T> = {
|
4
|
+
[K in keyof T]: T[K] extends bigint
|
5
|
+
? { type: 'BigInt'; hex: string }
|
6
|
+
: T[K] extends object
|
7
|
+
? Serialize<T[K]>
|
8
|
+
: T[K]
|
9
|
+
}
|
10
|
+
|
11
|
+
type SerializedBigInt = { type: 'BigInt'; hex: string }
|
12
|
+
|
13
|
+
function isSerializedBigInt(data: any): data is SerializedBigInt {
|
14
|
+
return data && data.type === 'BigInt' && typeof data.hex === 'string'
|
15
|
+
}
|
16
|
+
|
17
|
+
function stringify(data: object) {
|
18
|
+
return JSON.stringify(data, (key, value) => {
|
19
|
+
if (typeof value === 'bigint') {
|
20
|
+
return { type: 'BigInt', hex: '0x' + value.toString(16) } as SerializedBigInt
|
21
|
+
}
|
22
|
+
return value
|
23
|
+
})
|
24
|
+
}
|
25
|
+
|
26
|
+
export function deserialize<T extends object>(data: Serialize<T>): T {
|
27
|
+
const deserialized: any = _.cloneDeep(data)
|
28
|
+
|
29
|
+
if (typeof data !== 'object') return data
|
30
|
+
|
31
|
+
for (const key in data) {
|
32
|
+
const item = data[key]
|
33
|
+
if (isSerializedBigInt(item)) {
|
34
|
+
deserialized[key] = BigInt(item.hex)
|
35
|
+
} else if (item && typeof item === 'object') {
|
36
|
+
deserialized[key] = deserialize(item)
|
37
|
+
} else {
|
38
|
+
deserialized[key] = item
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
return deserialized
|
43
|
+
}
|
44
|
+
|
45
|
+
export function serialize<T extends object>(data: T): Serialize<T> {
|
46
|
+
return JSON.parse(stringify(data))
|
47
|
+
}
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import { formatUnits, parseUnits } from 'viem'
|
2
|
+
import { TokenBalance, TokenConfig } from '@/balance/types'
|
3
|
+
import { TokenState } from '@/liquidity-manager/types/token-state.enum'
|
4
|
+
import { getSlippageRange } from '@/liquidity-manager/utils/math'
|
5
|
+
import { Mathb } from '@/utils/bigint'
|
6
|
+
import {
|
7
|
+
TokenAnalysis,
|
8
|
+
TokenBalanceAnalysis,
|
9
|
+
TokenDataAnalyzed,
|
10
|
+
} from '@/liquidity-manager/types/types'
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Analyzes a token's balance against its configuration and returns the analysis.
|
14
|
+
* @param tokenConfig - The configuration of the token.
|
15
|
+
* @param tokenBalance - The current balance of the token.
|
16
|
+
* @param percentage - The percentage thresholds for up and down.
|
17
|
+
* @returns The analysis of the token's balance.
|
18
|
+
*/
|
19
|
+
export function analyzeToken(
|
20
|
+
tokenConfig: TokenConfig,
|
21
|
+
tokenBalance: TokenBalance,
|
22
|
+
percentage: { down: number; up: number; targetSlippage: number },
|
23
|
+
): TokenAnalysis {
|
24
|
+
const { decimals } = tokenBalance
|
25
|
+
|
26
|
+
// Calculate the maximum and minimum acceptable balances
|
27
|
+
const maximum = tokenConfig.targetBalance * (1 + percentage.up)
|
28
|
+
const minimum = tokenConfig.targetBalance * (1 - percentage.down)
|
29
|
+
|
30
|
+
// Create a balance analysis object
|
31
|
+
const balance: TokenBalanceAnalysis = {
|
32
|
+
current: tokenBalance.balance,
|
33
|
+
maximum: parseUnits(maximum.toString(), decimals),
|
34
|
+
minimum: parseUnits(minimum.toString(), decimals),
|
35
|
+
target: parseUnits(tokenConfig.targetBalance.toString(), decimals),
|
36
|
+
}
|
37
|
+
|
38
|
+
// Determine the state of the token based on its balance
|
39
|
+
const state = getTokenState(balance)
|
40
|
+
// Calculate the difference between the current balance and the target balance
|
41
|
+
const diffWei = getTokenBalanceDiff(balance)
|
42
|
+
const diff = parseFloat(formatUnits(diffWei, decimals))
|
43
|
+
const targetSlippage = getSlippageRange(
|
44
|
+
parseUnits(tokenConfig.targetBalance.toString(), decimals),
|
45
|
+
percentage.targetSlippage,
|
46
|
+
)
|
47
|
+
|
48
|
+
return { balance, diff, state, targetSlippage }
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Determines the state of a token based on its balance.
|
53
|
+
* @param balance - The balance analysis of the token.
|
54
|
+
* @returns The state of the token.
|
55
|
+
*/
|
56
|
+
function getTokenState(balance: TokenBalanceAnalysis): TokenState {
|
57
|
+
const { current, minimum, maximum } = balance
|
58
|
+
if (current > maximum) return TokenState.SURPLUS
|
59
|
+
if (current < minimum) return TokenState.DEFICIT
|
60
|
+
return TokenState.IN_RANGE
|
61
|
+
}
|
62
|
+
|
63
|
+
/**
|
64
|
+
* Calculates the absolute difference between the current balance and the target balance of a token.
|
65
|
+
* @param balance - The balance analysis of the token.
|
66
|
+
* @returns The absolute difference of tokens balance and the target balance.
|
67
|
+
*/
|
68
|
+
function getTokenBalanceDiff(balance: TokenBalanceAnalysis): bigint {
|
69
|
+
return Mathb.abs(balance.current - balance.target)
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Analyzes a group of tokens and returns the total difference and the items in the group.
|
74
|
+
* @param group - The group of analyzed token data.
|
75
|
+
* @returns The total difference and the items in the group.
|
76
|
+
*/
|
77
|
+
export function analyzeTokenGroup(group: TokenDataAnalyzed[]) {
|
78
|
+
// Sort the group by diff in descending order
|
79
|
+
const items = group.sort((a, b) => Number(b.analysis.diff - a.analysis.diff))
|
80
|
+
// Calculate the total difference for the group
|
81
|
+
const total = getGroupTotal(items)
|
82
|
+
return { total, items }
|
83
|
+
}
|
84
|
+
|
85
|
+
export function getGroupTotal(group: TokenDataAnalyzed[]) {
|
86
|
+
return group.reduce((acc, item) => acc + item.analysis.diff, 0)
|
87
|
+
}
|
88
|
+
|
89
|
+
export function getSortGroupByDiff(group: TokenDataAnalyzed[]) {
|
90
|
+
return group.sort((a, b) => b.analysis.diff - a.analysis.diff)
|
91
|
+
}
|