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.

Files changed (244) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.js +24 -0
  3. package/.github/workflows/ci.yaml +38 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc +8 -0
  7. package/Dockerfile +11 -0
  8. package/LICENSE +21 -0
  9. package/README.md +29 -5
  10. package/config/default.ts +135 -0
  11. package/config/development.ts +95 -0
  12. package/config/preproduction.ts +17 -0
  13. package/config/production.ts +17 -0
  14. package/config/staging.ts +17 -0
  15. package/config/test.ts +7 -0
  16. package/index.js +43 -0
  17. package/jest.config.ts +14 -0
  18. package/nest-cli.json +8 -0
  19. package/package.json +115 -6
  20. package/src/api/api.module.ts +27 -0
  21. package/src/api/balance.controller.ts +41 -0
  22. package/src/api/quote.controller.ts +54 -0
  23. package/src/api/tests/balance.controller.spec.ts +113 -0
  24. package/src/api/tests/quote.controller.spec.ts +83 -0
  25. package/src/app.module.ts +74 -0
  26. package/src/balance/balance.module.ts +14 -0
  27. package/src/balance/balance.service.ts +230 -0
  28. package/src/balance/balance.ws.service.ts +104 -0
  29. package/src/balance/types.ts +16 -0
  30. package/src/bullmq/bullmq.helper.ts +41 -0
  31. package/src/bullmq/processors/eth-ws.processor.ts +47 -0
  32. package/src/bullmq/processors/inbox.processor.ts +55 -0
  33. package/src/bullmq/processors/interval.processor.ts +54 -0
  34. package/src/bullmq/processors/processor.module.ts +14 -0
  35. package/src/bullmq/processors/signer.processor.ts +41 -0
  36. package/src/bullmq/processors/solve-intent.processor.ts +73 -0
  37. package/src/bullmq/processors/tests/solve-intent.processor.spec.ts +3 -0
  38. package/src/bullmq/utils/queue.ts +22 -0
  39. package/src/chain-monitor/chain-monitor.module.ts +12 -0
  40. package/src/chain-monitor/chain-sync.service.ts +134 -0
  41. package/src/chain-monitor/tests/chain-sync.service.spec.ts +190 -0
  42. package/src/commander/.eslintrc.js +6 -0
  43. package/src/commander/balance/balance-command.module.ts +12 -0
  44. package/src/commander/balance/balance.command.ts +73 -0
  45. package/src/commander/command-main.ts +15 -0
  46. package/src/commander/commander-app.module.ts +31 -0
  47. package/src/commander/eco-config.command.ts +20 -0
  48. package/src/commander/safe/safe-command.module.ts +11 -0
  49. package/src/commander/safe/safe.command.ts +70 -0
  50. package/src/commander/transfer/client.command.ts +24 -0
  51. package/src/commander/transfer/transfer-command.module.ts +26 -0
  52. package/src/commander/transfer/transfer.command.ts +138 -0
  53. package/src/commander/utils.ts +8 -0
  54. package/src/common/chains/definitions/arbitrum.ts +12 -0
  55. package/src/common/chains/definitions/base.ts +21 -0
  56. package/src/common/chains/definitions/eco.ts +54 -0
  57. package/src/common/chains/definitions/ethereum.ts +22 -0
  58. package/src/common/chains/definitions/helix.ts +53 -0
  59. package/src/common/chains/definitions/mantle.ts +12 -0
  60. package/src/common/chains/definitions/optimism.ts +22 -0
  61. package/src/common/chains/definitions/polygon.ts +12 -0
  62. package/src/common/chains/supported.ts +26 -0
  63. package/src/common/chains/transport.ts +19 -0
  64. package/src/common/errors/eco-error.ts +155 -0
  65. package/src/common/events/constants.ts +3 -0
  66. package/src/common/events/viem.ts +22 -0
  67. package/src/common/logging/eco-log-message.ts +74 -0
  68. package/src/common/redis/constants.ts +55 -0
  69. package/src/common/redis/redis-connection-utils.ts +106 -0
  70. package/src/common/routes/constants.ts +3 -0
  71. package/src/common/utils/objects.ts +34 -0
  72. package/src/common/utils/strings.ts +49 -0
  73. package/src/common/utils/tests/objects.spec.ts +23 -0
  74. package/src/common/utils/tests/strings.spec.ts +22 -0
  75. package/src/common/viem/contracts.ts +25 -0
  76. package/src/common/viem/tests/utils.spec.ts +115 -0
  77. package/src/common/viem/utils.ts +78 -0
  78. package/src/contracts/ERC20.contract.ts +389 -0
  79. package/src/contracts/EntryPoint.V6.contract.ts +1309 -0
  80. package/src/contracts/KernelAccount.abi.ts +87 -0
  81. package/src/contracts/OwnableExecutor.abi.ts +128 -0
  82. package/src/contracts/SimpleAccount.contract.ts +524 -0
  83. package/src/contracts/inbox.ts +8 -0
  84. package/src/contracts/index.ts +9 -0
  85. package/src/contracts/intent-source.ts +55 -0
  86. package/src/contracts/interfaces/index.ts +1 -0
  87. package/src/contracts/interfaces/prover.interface.ts +22 -0
  88. package/src/contracts/prover.ts +9 -0
  89. package/src/contracts/tests/erc20.contract.spec.ts +59 -0
  90. package/src/contracts/utils.ts +31 -0
  91. package/src/decoder/decoder.interface.ts +3 -0
  92. package/src/decoder/tests/utils.spec.ts +36 -0
  93. package/src/decoder/utils.ts +24 -0
  94. package/src/decorators/cacheable.decorator.ts +48 -0
  95. package/src/eco-configs/aws-config.service.ts +75 -0
  96. package/src/eco-configs/eco-config.module.ts +44 -0
  97. package/src/eco-configs/eco-config.service.ts +220 -0
  98. package/src/eco-configs/eco-config.types.ts +278 -0
  99. package/src/eco-configs/interfaces/config-source.interface.ts +3 -0
  100. package/src/eco-configs/tests/aws-config.service.spec.ts +52 -0
  101. package/src/eco-configs/tests/eco-config.service.spec.ts +137 -0
  102. package/src/eco-configs/tests/utils.spec.ts +84 -0
  103. package/src/eco-configs/utils.ts +49 -0
  104. package/src/fee/fee.module.ts +10 -0
  105. package/src/fee/fee.service.ts +467 -0
  106. package/src/fee/tests/fee.service.spec.ts +909 -0
  107. package/src/fee/tests/utils.spec.ts +49 -0
  108. package/src/fee/types.ts +44 -0
  109. package/src/fee/utils.ts +23 -0
  110. package/src/flags/flags.module.ts +10 -0
  111. package/src/flags/flags.service.ts +112 -0
  112. package/src/flags/tests/flags.service.spec.ts +68 -0
  113. package/src/flags/utils.ts +22 -0
  114. package/src/health/constants.ts +1 -0
  115. package/src/health/health.controller.ts +23 -0
  116. package/src/health/health.module.ts +25 -0
  117. package/src/health/health.service.ts +40 -0
  118. package/src/health/indicators/balance.indicator.ts +196 -0
  119. package/src/health/indicators/eco-redis.indicator.ts +23 -0
  120. package/src/health/indicators/git-commit.indicator.ts +67 -0
  121. package/src/health/indicators/mongodb.indicator.ts +11 -0
  122. package/src/health/indicators/permission.indicator.ts +64 -0
  123. package/src/intent/create-intent.service.ts +129 -0
  124. package/src/intent/feasable-intent.service.ts +80 -0
  125. package/src/intent/fulfill-intent.service.ts +318 -0
  126. package/src/intent/intent.controller.ts +199 -0
  127. package/src/intent/intent.module.ts +49 -0
  128. package/src/intent/schemas/intent-call-data.schema.ts +16 -0
  129. package/src/intent/schemas/intent-data.schema.ts +114 -0
  130. package/src/intent/schemas/intent-source.schema.ts +33 -0
  131. package/src/intent/schemas/intent-token-amount.schema.ts +14 -0
  132. package/src/intent/schemas/reward-data.schema.ts +48 -0
  133. package/src/intent/schemas/route-data.schema.ts +52 -0
  134. package/src/intent/schemas/watch-event.schema.ts +32 -0
  135. package/src/intent/tests/create-intent.service.spec.ts +215 -0
  136. package/src/intent/tests/feasable-intent.service.spec.ts +155 -0
  137. package/src/intent/tests/fulfill-intent.service.spec.ts +564 -0
  138. package/src/intent/tests/utils-intent.service.spec.ts +308 -0
  139. package/src/intent/tests/utils.spec.ts +62 -0
  140. package/src/intent/tests/validate-intent.service.spec.ts +297 -0
  141. package/src/intent/tests/validation.service.spec.ts +337 -0
  142. package/src/intent/utils-intent.service.ts +168 -0
  143. package/src/intent/utils.ts +37 -0
  144. package/src/intent/validate-intent.service.ts +176 -0
  145. package/src/intent/validation.sevice.ts +223 -0
  146. package/src/interceptors/big-int.interceptor.ts +30 -0
  147. package/src/intervals/interval.module.ts +18 -0
  148. package/src/intervals/retry-infeasable-intents.service.ts +89 -0
  149. package/src/intervals/tests/retry-infeasable-intents.service.spec.ts +167 -0
  150. package/src/kms/errors.ts +0 -0
  151. package/src/kms/kms.module.ts +12 -0
  152. package/src/kms/kms.service.ts +65 -0
  153. package/src/kms/tests/kms.service.spec.ts +60 -0
  154. package/src/liquidity-manager/jobs/check-balances-cron.job.ts +229 -0
  155. package/src/liquidity-manager/jobs/liquidity-manager.job.ts +52 -0
  156. package/src/liquidity-manager/jobs/rebalance.job.ts +61 -0
  157. package/src/liquidity-manager/liquidity-manager.module.ts +29 -0
  158. package/src/liquidity-manager/processors/base.processor.ts +117 -0
  159. package/src/liquidity-manager/processors/eco-protocol-intents.processor.ts +34 -0
  160. package/src/liquidity-manager/processors/grouped-jobs.processor.ts +103 -0
  161. package/src/liquidity-manager/queues/liquidity-manager.queue.ts +48 -0
  162. package/src/liquidity-manager/schemas/rebalance-token.schema.ts +32 -0
  163. package/src/liquidity-manager/schemas/rebalance.schema.ts +32 -0
  164. package/src/liquidity-manager/services/liquidity-manager.service.ts +188 -0
  165. package/src/liquidity-manager/services/liquidity-provider.service.ts +25 -0
  166. package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.spec.ts +125 -0
  167. package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.ts +117 -0
  168. package/src/liquidity-manager/services/liquidity-providers/LiFi/utils/get-transaction-hashes.ts +16 -0
  169. package/src/liquidity-manager/tests/liquidity-manager.service.spec.ts +142 -0
  170. package/src/liquidity-manager/types/token-state.enum.ts +5 -0
  171. package/src/liquidity-manager/types/types.d.ts +52 -0
  172. package/src/liquidity-manager/utils/address.ts +5 -0
  173. package/src/liquidity-manager/utils/math.ts +9 -0
  174. package/src/liquidity-manager/utils/serialize.spec.ts +24 -0
  175. package/src/liquidity-manager/utils/serialize.ts +47 -0
  176. package/src/liquidity-manager/utils/token.ts +91 -0
  177. package/src/main.ts +63 -0
  178. package/src/nest-redlock/nest-redlock.config.ts +14 -0
  179. package/src/nest-redlock/nest-redlock.interface.ts +5 -0
  180. package/src/nest-redlock/nest-redlock.module.ts +64 -0
  181. package/src/nest-redlock/nest-redlock.service.ts +59 -0
  182. package/src/prover/proof.service.ts +184 -0
  183. package/src/prover/prover.module.ts +10 -0
  184. package/src/prover/tests/proof.service.spec.ts +154 -0
  185. package/src/quote/dto/quote.intent.data.dto.ts +35 -0
  186. package/src/quote/dto/quote.reward.data.dto.ts +67 -0
  187. package/src/quote/dto/quote.route.data.dto.ts +71 -0
  188. package/src/quote/dto/types.ts +18 -0
  189. package/src/quote/errors.ts +215 -0
  190. package/src/quote/quote.module.ts +17 -0
  191. package/src/quote/quote.service.ts +299 -0
  192. package/src/quote/schemas/quote-call.schema.ts +16 -0
  193. package/src/quote/schemas/quote-intent.schema.ts +27 -0
  194. package/src/quote/schemas/quote-reward.schema.ts +24 -0
  195. package/src/quote/schemas/quote-route.schema.ts +30 -0
  196. package/src/quote/schemas/quote-token.schema.ts +14 -0
  197. package/src/quote/tests/quote.service.spec.ts +444 -0
  198. package/src/sign/atomic-signer.service.ts +24 -0
  199. package/src/sign/atomic.nonce.service.ts +114 -0
  200. package/src/sign/kms-account/kmsToAccount.ts +73 -0
  201. package/src/sign/kms-account/signKms.ts +30 -0
  202. package/src/sign/kms-account/signKmsTransaction.ts +37 -0
  203. package/src/sign/kms-account/signKmsTypedData.ts +21 -0
  204. package/src/sign/nonce.service.ts +89 -0
  205. package/src/sign/schemas/nonce.schema.ts +36 -0
  206. package/src/sign/sign.controller.ts +52 -0
  207. package/src/sign/sign.helper.ts +23 -0
  208. package/src/sign/sign.module.ts +27 -0
  209. package/src/sign/signer-kms.service.ts +27 -0
  210. package/src/sign/signer.service.ts +26 -0
  211. package/src/solver/filters/tests/valid-smart-wallet.service.spec.ts +87 -0
  212. package/src/solver/filters/valid-smart-wallet.service.ts +58 -0
  213. package/src/solver/solver.module.ts +10 -0
  214. package/src/transaction/multichain-public-client.service.ts +15 -0
  215. package/src/transaction/smart-wallets/kernel/actions/encodeData.kernel.ts +57 -0
  216. package/src/transaction/smart-wallets/kernel/create-kernel-client-v2.account.ts +183 -0
  217. package/src/transaction/smart-wallets/kernel/create.kernel.account.ts +270 -0
  218. package/src/transaction/smart-wallets/kernel/index.ts +2 -0
  219. package/src/transaction/smart-wallets/kernel/kernel-account-client-v2.service.ts +90 -0
  220. package/src/transaction/smart-wallets/kernel/kernel-account-client.service.ts +107 -0
  221. package/src/transaction/smart-wallets/kernel/kernel-account.client.ts +105 -0
  222. package/src/transaction/smart-wallets/kernel/kernel-account.config.ts +34 -0
  223. package/src/transaction/smart-wallets/simple-account/create.simple.account.ts +19 -0
  224. package/src/transaction/smart-wallets/simple-account/index.ts +2 -0
  225. package/src/transaction/smart-wallets/simple-account/simple-account-client.service.ts +42 -0
  226. package/src/transaction/smart-wallets/simple-account/simple-account.client.ts +83 -0
  227. package/src/transaction/smart-wallets/simple-account/simple-account.config.ts +5 -0
  228. package/src/transaction/smart-wallets/smart-wallet.types.ts +38 -0
  229. package/src/transaction/smart-wallets/utils.ts +14 -0
  230. package/src/transaction/transaction.module.ts +25 -0
  231. package/src/transaction/viem_multichain_client.service.ts +100 -0
  232. package/src/transforms/viem-address.decorator.ts +14 -0
  233. package/src/utils/bigint.ts +44 -0
  234. package/src/utils/types.ts +18 -0
  235. package/src/watch/intent/tests/watch-create-intent.service.spec.ts +257 -0
  236. package/src/watch/intent/tests/watch-fulfillment.service.spec.ts +141 -0
  237. package/src/watch/intent/watch-create-intent.service.ts +106 -0
  238. package/src/watch/intent/watch-event.service.ts +133 -0
  239. package/src/watch/intent/watch-fulfillment.service.ts +115 -0
  240. package/src/watch/watch.module.ts +13 -0
  241. package/test/app.e2e-spec.ts +21 -0
  242. package/test/jest-e2e.json +9 -0
  243. package/tsconfig.build.json +4 -0
  244. 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
+ }
@@ -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,5 @@
1
+ export enum TokenState {
2
+ DEFICIT = 'DEFICIT',
3
+ SURPLUS = 'SURPLUS',
4
+ IN_RANGE = 'IN_RANGE',
5
+ }
@@ -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,5 @@
1
+ import { Hex } from 'viem'
2
+
3
+ export function shortAddr(addr: Hex | string): string {
4
+ return `${addr.slice(0, 6)}...${addr.slice(-4)}`
5
+ }
@@ -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
+ }