eco-solver 0.0.1-security → 1.5.0

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 +66 -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,27 @@
1
+ import { BalanceController } from '@/api/balance.controller'
2
+ import { QuoteController } from '@/api/quote.controller'
3
+ import { BalanceModule } from '@/balance/balance.module'
4
+ import { EcoConfigService } from '@/eco-configs/eco-config.service'
5
+ import { QuoteModule } from '@/quote/quote.module'
6
+ import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager'
7
+ import { Module } from '@nestjs/common'
8
+ import { APP_INTERCEPTOR } from '@nestjs/core'
9
+
10
+ @Module({
11
+ imports: [
12
+ BalanceModule,
13
+ CacheModule.registerAsync({
14
+ useFactory: async (configService: EcoConfigService) => configService.getCache(),
15
+ inject: [EcoConfigService],
16
+ }),
17
+ QuoteModule,
18
+ ],
19
+ controllers: [BalanceController, QuoteController],
20
+ providers: [
21
+ {
22
+ provide: APP_INTERCEPTOR,
23
+ useClass: CacheInterceptor,
24
+ },
25
+ ],
26
+ })
27
+ export class ApiModule {}
@@ -0,0 +1,41 @@
1
+ import { BalanceService } from '@/balance/balance.service'
2
+ import { TokenBalance, TokenConfig } from '@/balance/types'
3
+ import { API_ROOT, BALANCE_ROUTE } from '@/common/routes/constants'
4
+ import { convertBigIntsToStrings } from '@/common/viem/utils'
5
+ import { CacheInterceptor } from '@nestjs/cache-manager'
6
+ import { Controller, Get, Query, UseInterceptors } from '@nestjs/common'
7
+ import * as _ from 'lodash'
8
+
9
+ @Controller(API_ROOT + BALANCE_ROUTE)
10
+ @UseInterceptors(CacheInterceptor)
11
+ export class BalanceController {
12
+ constructor(private readonly balanceService: BalanceService) {}
13
+
14
+ @Get()
15
+ async getBalances(@Query('flat') flat?: boolean) {
16
+ const data = await this.balanceService.getAllTokenData()
17
+ if (flat) {
18
+ return convertBigIntsToStrings(this.groupTokensByChain(data))
19
+ }
20
+ return convertBigIntsToStrings(data)
21
+ }
22
+
23
+ groupTokensByChain(
24
+ data: {
25
+ config: TokenConfig
26
+ balance: TokenBalance
27
+ chainId: number
28
+ }[],
29
+ ) {
30
+ // Group tokens by the chainId property.
31
+ const grouped = _.groupBy(data, 'chainId')
32
+
33
+ // For each chainId group, map the tokens to only include token address and balance.
34
+ return _.mapValues(grouped, (tokens) =>
35
+ tokens.map((token) => ({
36
+ address: token.balance.address,
37
+ balance: token.balance.balance,
38
+ })),
39
+ )
40
+ }
41
+ }
@@ -0,0 +1,54 @@
1
+ import { EcoLogMessage } from '@/common/logging/eco-log-message'
2
+ import { API_ROOT, QUOTE_ROUTE } from '@/common/routes/constants'
3
+ import { serialize } from '@/liquidity-manager/utils/serialize'
4
+ import { QuoteIntentDataDTO } from '@/quote/dto/quote.intent.data.dto'
5
+ import { QuoteErrorsInterface } from '@/quote/errors'
6
+ import { QuoteService } from '@/quote/quote.service'
7
+ import {
8
+ BadRequestException,
9
+ Body,
10
+ Controller,
11
+ InternalServerErrorException,
12
+ Logger,
13
+ Post,
14
+ } from '@nestjs/common'
15
+
16
+ @Controller(API_ROOT + QUOTE_ROUTE)
17
+ export class QuoteController {
18
+ private logger = new Logger(QuoteController.name)
19
+
20
+ constructor(private readonly quoteService: QuoteService) {}
21
+
22
+ @Post()
23
+ async getQuote(@Body() quoteIntentDataDTO: QuoteIntentDataDTO) {
24
+ this.logger.log(
25
+ EcoLogMessage.fromDefault({
26
+ message: `Received quote request:`,
27
+ properties: {
28
+ quoteIntentDataDTO,
29
+ },
30
+ }),
31
+ )
32
+ const quote = await this.quoteService.getQuote(quoteIntentDataDTO)
33
+ this.logger.log(
34
+ EcoLogMessage.fromDefault({
35
+ message: `Responding to quote request:`,
36
+ properties: {
37
+ quote,
38
+ },
39
+ }),
40
+ )
41
+
42
+ const errorStatus = (quote as QuoteErrorsInterface).statusCode
43
+ if (errorStatus) {
44
+ switch (errorStatus) {
45
+ case 400:
46
+ throw new BadRequestException(serialize(quote))
47
+ case 500:
48
+ default:
49
+ throw new InternalServerErrorException(serialize(quote))
50
+ }
51
+ }
52
+ return quote
53
+ }
54
+ }
@@ -0,0 +1,113 @@
1
+ import { Test, TestingModule } from '@nestjs/testing'
2
+ import { BalanceController } from '../balance.controller'
3
+ import { BalanceService } from '@/balance/balance.service'
4
+ import { CACHE_MANAGER } from '@nestjs/cache-manager'
5
+ import { createMock } from '@golevelup/ts-jest'
6
+
7
+ describe('BalanceController Test', () => {
8
+ let balanceController: BalanceController
9
+ let balanceService: BalanceService
10
+
11
+ beforeEach(async () => {
12
+ const module: TestingModule = await Test.createTestingModule({
13
+ controllers: [BalanceController],
14
+ providers: [
15
+ {
16
+ provide: BalanceService,
17
+ useValue: createMock<BalanceService>(),
18
+ },
19
+ {
20
+ provide: CACHE_MANAGER,
21
+ useValue: {
22
+ get: jest.fn(),
23
+ set: jest.fn(),
24
+ },
25
+ },
26
+ ],
27
+ }).compile()
28
+
29
+ balanceController = module.get<BalanceController>(BalanceController)
30
+ balanceService = module.get<BalanceService>(BalanceService)
31
+ })
32
+
33
+ it('should be defined', () => {
34
+ expect(balanceController).toBeDefined()
35
+ })
36
+
37
+ describe('getBalances', () => {
38
+ it('should return an array of balances', async () => {
39
+ const result = []
40
+ jest.spyOn(balanceService, 'getAllTokenData').mockResolvedValue(result)
41
+
42
+ expect(await balanceController.getBalances()).toEqual(result)
43
+ })
44
+
45
+ it('should call balanceService.getAllTokenData', async () => {
46
+ const getAllTokenDataSpy = jest.spyOn(balanceService, 'getAllTokenData').mockResolvedValue([])
47
+
48
+ await balanceController.getBalances()
49
+
50
+ expect(getAllTokenDataSpy).toHaveBeenCalled()
51
+ })
52
+
53
+ it('should take the flat query param', async () => {
54
+ const getAllTokenDataSpy = jest.spyOn(balanceService, 'getAllTokenData').mockResolvedValue([])
55
+ const groupSpy = jest.spyOn(balanceController, 'groupTokensByChain')
56
+ await balanceController.getBalances(true)
57
+
58
+ expect(getAllTokenDataSpy).toHaveBeenCalled()
59
+ expect(groupSpy).toHaveBeenCalled()
60
+ })
61
+ })
62
+
63
+ describe('groupTokensByChain', () => {
64
+ it('should flatten', async () => {
65
+ const data = [
66
+ {
67
+ balance: {
68
+ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
69
+ balance: '50995350000',
70
+ decimals: 6,
71
+ },
72
+ chainId: 1,
73
+ },
74
+ {
75
+ config: {
76
+ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
77
+ chainId: 1,
78
+ },
79
+ balance: {
80
+ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
81
+ balance: '50000000000',
82
+ decimals: 6,
83
+ },
84
+ chainId: 1,
85
+ },
86
+ {
87
+ config: {
88
+ address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
89
+ chainId: 42161,
90
+ type: 'erc20',
91
+ minBalance: 200,
92
+ targetBalance: 50000,
93
+ },
94
+ balance: {
95
+ address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
96
+ balance: '25368636844',
97
+ decimals: 6,
98
+ },
99
+ chainId: 42161,
100
+ },
101
+ ] as any
102
+
103
+ const result = balanceController.groupTokensByChain(data)
104
+ expect(result).toEqual({
105
+ 1: [
106
+ { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', balance: '50995350000' },
107
+ { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', balance: '50000000000' },
108
+ ],
109
+ 42161: [{ address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', balance: '25368636844' }],
110
+ })
111
+ })
112
+ })
113
+ })
@@ -0,0 +1,83 @@
1
+ import { Test, TestingModule } from '@nestjs/testing'
2
+ import { CACHE_MANAGER } from '@nestjs/cache-manager'
3
+ import { createMock } from '@golevelup/ts-jest'
4
+ import { QuoteController } from '@/api/quote.controller'
5
+ import { QuoteService } from '@/quote/quote.service'
6
+ import { InternalSaveError, SolverUnsupported } from '@/quote/errors'
7
+ import { BadRequestException, InternalServerErrorException } from '@nestjs/common'
8
+ import { serialize } from '@/liquidity-manager/utils/serialize'
9
+
10
+ describe('QuoteController Test', () => {
11
+ let quoteController: QuoteController
12
+ let quoteService: QuoteService
13
+ const mockLogLog = jest.fn()
14
+
15
+ beforeEach(async () => {
16
+ const module: TestingModule = await Test.createTestingModule({
17
+ controllers: [QuoteController],
18
+ providers: [
19
+ {
20
+ provide: QuoteService,
21
+ useValue: createMock<QuoteService>(),
22
+ },
23
+ {
24
+ provide: CACHE_MANAGER,
25
+ useValue: {
26
+ get: jest.fn(),
27
+ set: jest.fn(),
28
+ },
29
+ },
30
+ ],
31
+ }).compile()
32
+
33
+ quoteController = module.get(QuoteController)
34
+ quoteService = module.get(QuoteService)
35
+
36
+ quoteController['logger'].log = mockLogLog
37
+ })
38
+
39
+ afterEach(async () => {
40
+ // restore the spy created with spyOn
41
+ jest.restoreAllMocks()
42
+ mockLogLog.mockClear()
43
+ })
44
+
45
+ it('should be defined', () => {
46
+ expect(quoteController).toBeDefined()
47
+ })
48
+
49
+ describe('getQuote', () => {
50
+ const quote = {
51
+ tokens: [
52
+ {
53
+ address: '0x123',
54
+ amount: 100n,
55
+ },
56
+ ],
57
+ expiryTime: 0,
58
+ }
59
+ it('should return a 400 on bad request', async () => {
60
+ jest.spyOn(quoteService, 'getQuote').mockResolvedValue(SolverUnsupported)
61
+ await expect(quoteController.getQuote({} as any)).rejects.toThrow(
62
+ new BadRequestException(serialize(SolverUnsupported)),
63
+ )
64
+ })
65
+
66
+ it('should return a 500 on server error', async () => {
67
+ jest.spyOn(quoteService, 'getQuote').mockResolvedValue(InternalSaveError(quote as any))
68
+ jest.spyOn(quoteService, 'storeQuoteIntentData').mockResolvedValue(quote as any)
69
+ await expect(quoteController.getQuote({} as any)).rejects.toThrow(
70
+ new InternalServerErrorException(InternalSaveError(quote as any)),
71
+ )
72
+ })
73
+
74
+ it('should log and return a quote', async () => {
75
+ jest.spyOn(quoteService, 'getQuote').mockResolvedValue(quote as any)
76
+
77
+ const result = await quoteController.getQuote({} as any)
78
+ expect(result).toEqual(quote)
79
+ expect(quoteService.getQuote).toHaveBeenCalled()
80
+ expect(mockLogLog).toHaveBeenCalledTimes(2)
81
+ })
82
+ })
83
+ })
@@ -0,0 +1,74 @@
1
+ import { Module } from '@nestjs/common'
2
+ import { LoggerModule } from 'nestjs-pino'
3
+ import { MongooseModule } from '@nestjs/mongoose'
4
+ import { LiquidityManagerModule } from '@/liquidity-manager/liquidity-manager.module'
5
+ import { ApiModule } from '@/api/api.module'
6
+ import { WatchModule } from '@/watch/watch.module'
7
+ import { IntervalModule } from '@/intervals/interval.module'
8
+ import { QuoteModule } from '@/quote/quote.module'
9
+ import { FeeModule } from '@/fee/fee.module'
10
+ import { KmsModule } from '@/kms/kms.module'
11
+ import { BalanceModule } from '@/balance/balance.module'
12
+ import { ChainMonitorModule } from '@/chain-monitor/chain-monitor.module'
13
+ import { EcoConfigModule } from '@/eco-configs/eco-config.module'
14
+ import { FlagsModule } from '@/flags/flags.module'
15
+ import { HealthModule } from '@/health/health.module'
16
+ import { IntentModule } from '@/intent/intent.module'
17
+ import { SignModule } from '@/sign/sign.module'
18
+ import { ProcessorModule } from '@/bullmq/processors/processor.module'
19
+ import { EcoConfigService } from '@/eco-configs/eco-config.service'
20
+ import { ProverModule } from '@/prover/prover.module'
21
+ import { SolverModule } from '@/solver/solver.module'
22
+
23
+ @Module({
24
+ imports: [
25
+ ApiModule,
26
+ BalanceModule,
27
+ ChainMonitorModule,
28
+ EcoConfigModule.withAWS(),
29
+ FeeModule,
30
+ FlagsModule,
31
+ HealthModule,
32
+ IntentModule,
33
+ KmsModule,
34
+ SignModule,
35
+ IntervalModule,
36
+ ProcessorModule,
37
+ MongooseModule.forRootAsync({
38
+ inject: [EcoConfigService],
39
+ useFactory: async (configService: EcoConfigService) => {
40
+ const uri = configService.getMongooseUri()
41
+ return {
42
+ uri,
43
+ }
44
+ },
45
+ }),
46
+ ProverModule,
47
+ QuoteModule,
48
+ SolverModule,
49
+ LiquidityManagerModule,
50
+ WatchModule,
51
+ ...getPino(),
52
+ ],
53
+ controllers: [],
54
+ })
55
+ export class AppModule {}
56
+
57
+ /**
58
+ * Returns the Pino module if the configs have it on ( its off in dev )
59
+ */
60
+ function getPino() {
61
+ return EcoConfigService.getStaticConfig().logger.usePino
62
+ ? [
63
+ LoggerModule.forRootAsync({
64
+ inject: [EcoConfigService],
65
+ useFactory: async (configService: EcoConfigService) => {
66
+ const loggerConfig = configService.getLoggerConfig()
67
+ return {
68
+ pinoHttp: loggerConfig.pinoConfig.pinoHttp,
69
+ }
70
+ },
71
+ }),
72
+ ]
73
+ : []
74
+ }
@@ -0,0 +1,14 @@
1
+ import { Module } from '@nestjs/common'
2
+ import { BalanceService } from './balance.service'
3
+ import { initBullMQ } from '../bullmq/bullmq.helper'
4
+ import { QUEUES } from '../common/redis/constants'
5
+ import { BalanceWebsocketService } from './balance.ws.service'
6
+ import { TransactionModule } from '../transaction/transaction.module'
7
+ import { CacheModule } from '@nestjs/cache-manager'
8
+
9
+ @Module({
10
+ imports: [TransactionModule, initBullMQ(QUEUES.ETH_SOCKET), CacheModule.register()],
11
+ providers: [BalanceService, BalanceWebsocketService],
12
+ exports: [BalanceService],
13
+ })
14
+ export class BalanceModule {}
@@ -0,0 +1,230 @@
1
+ import { Inject, Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'
2
+ import { groupBy, zipWith } from 'lodash'
3
+ import { EcoConfigService } from '@/eco-configs/eco-config.service'
4
+ import { getDestinationNetworkAddressKey } from '@/common/utils/strings'
5
+ import { EcoLogMessage } from '@/common/logging/eco-log-message'
6
+ import { erc20Abi, Hex, MulticallParameters, MulticallReturnType } from 'viem'
7
+ import { ViemEventLog } from '@/common/events/viem'
8
+ import { decodeTransferLog, isSupportedTokenType } from '@/contracts'
9
+ import { KernelAccountClientService } from '@/transaction/smart-wallets/kernel/kernel-account-client.service'
10
+ import { TokenBalance, TokenConfig } from '@/balance/types'
11
+ import { EcoError } from '@/common/errors/eco-error'
12
+ import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager'
13
+ import { Cacheable } from '@/decorators/cacheable.decorator'
14
+
15
+ /**
16
+ * Composite data from fetching the token balances for a chain
17
+ */
18
+ export type TokenFetchAnalysis = {
19
+ config: TokenConfig
20
+ token: TokenBalance
21
+ chainId: number
22
+ }
23
+
24
+ /**
25
+ * Service class for getting configs for the app
26
+ */
27
+ @Injectable()
28
+ export class BalanceService implements OnApplicationBootstrap {
29
+ private logger = new Logger(BalanceService.name)
30
+
31
+ private readonly tokenBalances: Map<string, TokenBalance> = new Map()
32
+
33
+ constructor(
34
+ @Inject(CACHE_MANAGER) private cacheManager: Cache,
35
+ private readonly configService: EcoConfigService,
36
+ private readonly kernelAccountClientService: KernelAccountClientService,
37
+ ) {}
38
+
39
+ async onApplicationBootstrap() {
40
+ // iterate over all tokens
41
+ await Promise.all(this.getInboxTokens().map((token) => this.loadTokenBalance(token)))
42
+ }
43
+
44
+ /**
45
+ * Get the token balance of the solver
46
+ * @returns
47
+ */
48
+ async getTokenBalance(chainID: number, tokenAddress: Hex) {
49
+ return (
50
+ this.tokenBalances.get(getDestinationNetworkAddressKey(chainID, tokenAddress)) ?? {
51
+ balance: 0n,
52
+ decimals: 0n,
53
+ }
54
+ )
55
+ }
56
+
57
+ /**
58
+ * Updates the token balance of the solver, called from {@link EthWebsocketProcessor}
59
+ * @returns
60
+ */
61
+ updateBalance(balanceEvent: ViemEventLog) {
62
+ this.logger.debug(
63
+ EcoLogMessage.fromDefault({
64
+ message: `updateBalance ${balanceEvent.transactionHash}`,
65
+ properties: {
66
+ intentHash: balanceEvent.transactionHash,
67
+ },
68
+ }),
69
+ )
70
+
71
+ const intent = decodeTransferLog(balanceEvent.data, balanceEvent.topics)
72
+ const key = getDestinationNetworkAddressKey(balanceEvent.sourceChainID, balanceEvent.address)
73
+ const balanceObj = this.tokenBalances.get(key)
74
+ if (balanceObj) {
75
+ balanceObj.balance = balanceObj.balance + intent.args.value
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Gets the tokens that are in the solver wallets
81
+ * @returns List of tokens that are supported by the solver
82
+ */
83
+ getInboxTokens(): TokenConfig[] {
84
+ return Object.values(this.configService.getSolvers()).flatMap((solver) => {
85
+ return Object.entries(solver.targets)
86
+ .filter(([, targetContract]) => isSupportedTokenType(targetContract.contractType))
87
+ .map(([tokenAddress, targetContract]) => ({
88
+ address: tokenAddress as Hex,
89
+ chainId: solver.chainID,
90
+ type: targetContract.contractType,
91
+ minBalance: targetContract.minBalance,
92
+ targetBalance: targetContract.targetBalance,
93
+ }))
94
+ })
95
+ }
96
+
97
+ /**
98
+ * Fetches the balances of the kernel account client of the solver for the given tokens
99
+ * @param chainID the chain id
100
+ * @param tokenAddresses the tokens to fetch balances for
101
+ * @returns
102
+ */
103
+ @Cacheable()
104
+ async fetchTokenBalances(
105
+ chainID: number,
106
+ tokenAddresses: Hex[],
107
+ ): Promise<Record<Hex, TokenBalance>> {
108
+ const client = await this.kernelAccountClientService.getClient(chainID)
109
+ const walletAddress = client.kernelAccount.address
110
+
111
+ this.logger.debug(
112
+ EcoLogMessage.fromDefault({
113
+ message: `fetchTokenBalances`,
114
+ properties: {
115
+ chainID,
116
+ tokenAddresses,
117
+ walletAddress,
118
+ },
119
+ }),
120
+ )
121
+
122
+ const results = (await client.multicall({
123
+ contracts: tokenAddresses.flatMap((tokenAddress): MulticallParameters['contracts'] => [
124
+ {
125
+ abi: erc20Abi,
126
+ address: tokenAddress,
127
+ functionName: 'balanceOf',
128
+ args: [walletAddress],
129
+ },
130
+ {
131
+ abi: erc20Abi,
132
+ address: tokenAddress,
133
+ functionName: 'decimals',
134
+ },
135
+ ]),
136
+ allowFailure: false,
137
+ })) as MulticallReturnType
138
+
139
+ const tokenBalances: Record<Hex, TokenBalance> = {}
140
+
141
+ tokenAddresses.forEach((tokenAddress, index) => {
142
+ const [balance = 0n, decimals = 0] = [results[index * 2], results[index * 2 + 1]]
143
+ //throw if we suddenly start supporting tokens with not 6 decimals
144
+ //audit conversion of validity to see its support
145
+ if ((decimals as number) != 6) {
146
+ throw EcoError.BalanceServiceInvalidDecimals(tokenAddress)
147
+ }
148
+ tokenBalances[tokenAddress] = {
149
+ address: tokenAddress,
150
+ balance: balance as bigint,
151
+ decimals: decimals as number,
152
+ }
153
+ })
154
+ return tokenBalances
155
+ }
156
+
157
+ @Cacheable()
158
+ async fetchTokenBalance(chainID: number, tokenAddress: Hex): Promise<TokenBalance> {
159
+ const result = await this.fetchTokenBalances(chainID, [tokenAddress])
160
+ return result[tokenAddress]
161
+ }
162
+
163
+ @Cacheable()
164
+ async fetchTokenBalancesForChain(
165
+ chainID: number,
166
+ ): Promise<Record<Hex, TokenBalance> | undefined> {
167
+ const intentSource = this.configService.getIntentSource(chainID)
168
+ if (!intentSource) {
169
+ return undefined
170
+ }
171
+ return this.fetchTokenBalances(chainID, intentSource.tokens)
172
+ }
173
+
174
+ @Cacheable()
175
+ async fetchTokenData(chainID: number): Promise<TokenFetchAnalysis[]> {
176
+ const tokenConfigs = groupBy(this.getInboxTokens(), 'chainId')[chainID]
177
+ const tokenAddresses = tokenConfigs.map((token) => token.address)
178
+ const tokenBalances = await this.fetchTokenBalances(chainID, tokenAddresses)
179
+ return zipWith(tokenConfigs, Object.values(tokenBalances), (config, token) => ({
180
+ config,
181
+ token,
182
+ chainId: chainID,
183
+ }))
184
+ }
185
+
186
+ @Cacheable()
187
+ async getAllTokenData() {
188
+ const tokens = this.getInboxTokens()
189
+ const tokensByChainId = groupBy(tokens, 'chainId')
190
+ const chainIds = Object.keys(tokensByChainId)
191
+
192
+ const balancesPerChainIdPromise = chainIds.map(async (chainId) => {
193
+ const configs = tokensByChainId[chainId]
194
+ const tokenAddresses = configs.map((token) => token.address)
195
+ const balances = await this.fetchTokenBalances(parseInt(chainId), tokenAddresses)
196
+ return zipWith(configs, Object.values(balances), (config, balance) => ({
197
+ config,
198
+ balance,
199
+ chainId: parseInt(chainId),
200
+ }))
201
+ })
202
+
203
+ return Promise.all(balancesPerChainIdPromise).then((result) => result.flat())
204
+ }
205
+
206
+ /**
207
+ * Loads the token balance of the solver
208
+ * @returns
209
+ */
210
+ private async loadTokenBalance(token: TokenConfig) {
211
+ switch (token.type) {
212
+ case 'erc20':
213
+ return this.loadERC20TokenBalance(token.chainId, token.address)
214
+ default:
215
+ throw EcoError.IntentSourceUnsupportedTargetType(token.type)
216
+ }
217
+ }
218
+
219
+ private async loadERC20TokenBalance(
220
+ chainID: number,
221
+ tokenAddress: Hex,
222
+ ): Promise<TokenBalance | undefined> {
223
+ const key = getDestinationNetworkAddressKey(chainID, tokenAddress)
224
+ if (!this.tokenBalances.has(key)) {
225
+ const tokenBalance = await this.fetchTokenBalance(chainID, tokenAddress)
226
+ this.tokenBalances.set(key, tokenBalance)
227
+ }
228
+ return this.tokenBalances.get(key)
229
+ }
230
+ }