eco-solver 0.0.1-security → 1.5.2

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 +117 -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,49 @@
1
+ import { normalizeBalance } from '@/fee/utils'
2
+
3
+ describe('Utils Tests', () => {
4
+ describe('normalizeBalance', () => {
5
+ it('should throw if decimals are not integers', () => {
6
+ expect(() => normalizeBalance({ balance: 1n, decimal: 1.5 }, 1)).toThrow(
7
+ new Error('Decimal values must be integers'),
8
+ )
9
+ expect(() => normalizeBalance({ balance: 1n, decimal: 1 }, 1.5)).toThrow(
10
+ new Error('Decimal values must be integers'),
11
+ )
12
+ expect(() => normalizeBalance({ balance: 1n, decimal: 1.5 }, 1.5)).toThrow(
13
+ new Error('Decimal values must be integers'),
14
+ )
15
+ })
16
+
17
+ it('should return the same value if current and target decimals are the same', () => {
18
+ expect(normalizeBalance({ balance: 1234n, decimal: 6 }, 6)).toEqual({
19
+ balance: 1234n,
20
+ decimal: 6,
21
+ })
22
+ })
23
+
24
+ it('should scale up', () => {
25
+ expect(normalizeBalance({ balance: 10_000n, decimal: 4 }, 6)).toEqual({
26
+ balance: 1_000_000n,
27
+ decimal: 6,
28
+ })
29
+ })
30
+
31
+ it('should scale down', () => {
32
+ expect(normalizeBalance({ balance: 1_000_000n, decimal: 6 }, 4)).toEqual({
33
+ balance: 10_000n,
34
+ decimal: 4,
35
+ })
36
+ })
37
+
38
+ it('should work with zero', () => {
39
+ expect(normalizeBalance({ balance: 1n, decimal: 0 }, 3)).toEqual({
40
+ balance: 1_000n,
41
+ decimal: 3,
42
+ })
43
+ expect(normalizeBalance({ balance: 100n, decimal: 2 }, 0)).toEqual({
44
+ balance: 1n,
45
+ decimal: 0,
46
+ })
47
+ })
48
+ })
49
+ })
@@ -0,0 +1,44 @@
1
+ import { TokenFetchAnalysis } from '@/balance/balance.service'
2
+ import { RewardTokensInterface } from '@/contracts'
3
+ import { Solver } from '@/eco-configs/eco-config.types'
4
+ import { Hex, Prettify } from 'viem'
5
+ /**
6
+ * The response quote data
7
+ */
8
+ export interface QuoteData {
9
+ tokens: RewardTokensInterface[]
10
+ expiryTime: string
11
+ }
12
+
13
+ /**
14
+ * The normalized tokens for the quote intent
15
+ */
16
+ export interface NormalizedTokens {
17
+ rewardTokens: RewardTokensInterface[]
18
+ callTokens: RewardTokensInterface[]
19
+ }
20
+
21
+ /**
22
+ * The normalized token type
23
+ */
24
+ export type NormalizedToken = {
25
+ balance: bigint
26
+ chainID: bigint
27
+ address: Hex
28
+ decimals: number
29
+ }
30
+
31
+ /**
32
+ * The type for the token fetch analysis with the normalized delta
33
+ */
34
+ export type DeficitDescending = Prettify<TokenFetchAnalysis & { delta: NormalizedToken }>
35
+
36
+ /**
37
+ * The type for the calculated tokens
38
+ */
39
+ export type CalculateTokensType = {
40
+ solver: Solver
41
+ rewards: NormalizedToken[]
42
+ calls: NormalizedToken[]
43
+ deficitDescending: DeficitDescending[]
44
+ }
@@ -0,0 +1,23 @@
1
+ type BalanceObject = {
2
+ balance: bigint
3
+ decimal: number
4
+ }
5
+
6
+ /**
7
+ * Normalizes the balance to a new decimal precision.
8
+ */
9
+ export function normalizeBalance(value: BalanceObject, targetDecimal: number): BalanceObject {
10
+ if (!Number.isInteger(value.decimal) || !Number.isInteger(targetDecimal)) {
11
+ throw new Error('Decimal values must be integers')
12
+ }
13
+ const scaleFactor = BigInt(10 ** Math.abs(targetDecimal - value.decimal))
14
+
15
+ let newBalance: bigint
16
+ if (targetDecimal > value.decimal) {
17
+ newBalance = value.balance * scaleFactor // Scale up
18
+ } else {
19
+ newBalance = value.balance / scaleFactor // Scale down
20
+ }
21
+
22
+ return { balance: newBalance, decimal: targetDecimal }
23
+ }
@@ -0,0 +1,10 @@
1
+ import { Module } from '@nestjs/common'
2
+ import { FlagService } from './flags.service'
3
+ import { TransactionModule } from '../transaction/transaction.module'
4
+
5
+ @Module({
6
+ imports: [TransactionModule],
7
+ providers: [FlagService],
8
+ exports: [FlagService],
9
+ })
10
+ export class FlagsModule {}
@@ -0,0 +1,112 @@
1
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common'
2
+ import { EcoConfigService } from '../eco-configs/eco-config.service'
3
+ import * as ld from '@launchdarkly/node-server-sdk'
4
+ import { KernelAccountClientService } from '../transaction/smart-wallets/kernel/kernel-account-client.service'
5
+ import { EcoLogMessage } from '../common/logging/eco-log-message'
6
+ import { waitForInitialization } from './utils'
7
+
8
+ export type LaunchDarklyFlags = {
9
+ bendWalletOnly: boolean
10
+ }
11
+
12
+ export type FlagType = keyof LaunchDarklyFlags
13
+
14
+ export const FlagVariationKeys: Record<FlagType, string> = {
15
+ bendWalletOnly: 'bendWalletOnly',
16
+ }
17
+
18
+ /**
19
+ * Service class for interacting with the Launch Darkly feature flagging service
20
+ */
21
+ @Injectable()
22
+ export class FlagService implements OnModuleInit {
23
+ private logger = new Logger(FlagService.name)
24
+ private flagsClient: ld.LDClient
25
+ private context: ld.LDContext
26
+ private flagValues: LaunchDarklyFlags = {
27
+ bendWalletOnly: false,
28
+ }
29
+ constructor(
30
+ private readonly kernelAccountService: KernelAccountClientService,
31
+ private readonly ecoConfigService: EcoConfigService,
32
+ ) {}
33
+
34
+ async onModuleInit() {
35
+ await this.initLaunchDarklyClient()
36
+ this.registerFlagListeners()
37
+ }
38
+
39
+ public getFlagValue<T extends FlagType>(flag: T): LaunchDarklyFlags[T] {
40
+ return this.flagValues[flag]
41
+ }
42
+
43
+ public static isSupportedFlag(flag: string): boolean {
44
+ return Object.keys(FlagVariationKeys).includes(flag)
45
+ }
46
+
47
+ /**
48
+ * Initializes the Launch Darkly client with the provided API key. Sets an
49
+ * on ready listener to initialize the flags
50
+ */
51
+ private async initLaunchDarklyClient() {
52
+ this.context = { kind: 'solver-pod', key: await this.kernelAccountService.getAddress() }
53
+ this.flagsClient = ld.init(this.ecoConfigService.getLaunchDarkly().apiKey)
54
+ const lock = { initialized: false }
55
+ this.flagsClient.on('ready', async () => {
56
+ await Promise.all(
57
+ Object.values(FlagVariationKeys).map(async (flag) => {
58
+ this.flagValues[flag] = await this.flagsClient.variation(
59
+ flag,
60
+ this.context,
61
+ this.flagValues[flag],
62
+ )
63
+ }),
64
+ )
65
+ lock.initialized = true
66
+ this.logger.log(
67
+ EcoLogMessage.fromDefault({
68
+ message: `FlagService ready`,
69
+ properties: {
70
+ flags: this.flagValues,
71
+ },
72
+ }),
73
+ )
74
+ })
75
+ // Wait for the flags to be initialized
76
+ await waitForInitialization(lock)
77
+ }
78
+
79
+ /**
80
+ * Registers update listeners for when flags are updated. Also occures on first init
81
+ */
82
+ private registerFlagListeners() {
83
+ this.flagsClient.on('update', async (param) => {
84
+ if (!FlagService.isSupportedFlag(param.key)) {
85
+ this.logger.log(
86
+ EcoLogMessage.fromDefault({
87
+ message: `FlagService update: unsupported flag`,
88
+ properties: {
89
+ flagName: param.key,
90
+ },
91
+ }),
92
+ )
93
+ return
94
+ }
95
+ const flag = param.key
96
+ this.flagValues[flag] = await this.flagsClient.variation(
97
+ flag,
98
+ this.context,
99
+ this.flagValues[flag],
100
+ )
101
+ this.logger.log(
102
+ EcoLogMessage.fromDefault({
103
+ message: `FlagService update`,
104
+ properties: {
105
+ flagName: flag,
106
+ flag: this.flagValues[flag],
107
+ },
108
+ }),
109
+ )
110
+ })
111
+ }
112
+ }
@@ -0,0 +1,68 @@
1
+ const mockInit = jest.fn()
2
+ const mockWait = jest.fn()
3
+ import { Test, TestingModule } from '@nestjs/testing'
4
+ import { FlagService, FlagVariationKeys } from '../flags.service'
5
+ import { KernelAccountClientService } from '../../transaction/smart-wallets/kernel/kernel-account-client.service'
6
+ import { createMock } from '@golevelup/ts-jest'
7
+ import { EcoConfigService } from '../../eco-configs/eco-config.service'
8
+
9
+ jest.mock('@launchdarkly/node-server-sdk', () => {
10
+ return {
11
+ ...jest.requireActual('@launchdarkly/node-server-sdk'),
12
+ init: mockInit,
13
+ }
14
+ })
15
+
16
+ jest.mock('../utils', () => {
17
+ return {
18
+ ...jest.requireActual('../utils'),
19
+ waitForInitialization: mockWait,
20
+ }
21
+ })
22
+
23
+ describe('Flags', () => {
24
+ let flagService: FlagService
25
+ const mockLogDebug = jest.fn()
26
+ const mockLogLog = jest.fn()
27
+ const mockVariation = jest.fn()
28
+
29
+ beforeEach(async () => {
30
+ const module: TestingModule = await Test.createTestingModule({
31
+ providers: [
32
+ FlagService,
33
+ { provide: EcoConfigService, useValue: createMock<EcoConfigService>() },
34
+ { provide: KernelAccountClientService, useValue: createMock<KernelAccountClientService>() },
35
+ ],
36
+ }).compile()
37
+ //turn off the services from logging durring testing
38
+ module.useLogger(false)
39
+ flagService = module.get<FlagService>(FlagService)
40
+
41
+ flagService['logger'].debug = mockLogDebug
42
+ flagService['logger'].log = mockLogLog
43
+
44
+ mockInit.mockReturnValue({ on: jest.fn(), variation: mockVariation })
45
+ })
46
+
47
+ afterEach(async () => {
48
+ // restore the spy created with spyOn
49
+ jest.restoreAllMocks()
50
+ mockLogDebug.mockClear()
51
+ mockLogLog.mockClear()
52
+ mockInit.mockClear()
53
+ mockVariation.mockClear()
54
+ mockWait.mockClear()
55
+ })
56
+
57
+ describe('onModuleInit', () => {
58
+ it('should init the launch darkly sdk', async () => {
59
+ await flagService.onModuleInit()
60
+ expect(mockInit).toHaveBeenCalledTimes(1)
61
+ })
62
+
63
+ it('should wait for initialization', async () => {
64
+ await flagService.onModuleInit()
65
+ expect(mockWait).toHaveBeenCalledTimes(1)
66
+ })
67
+ })
68
+ })
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Waits for initialization to be set
3
+ * @param lock the lock to wait for
4
+ * @param interval the interval to check the variable, default 100ms
5
+ * @param timeout the max time to wait before throwing an error, default 10s
6
+ */
7
+ export async function waitForInitialization(
8
+ lock: { initialized: boolean },
9
+ interval: number = 100,
10
+ timeout: number = 10000,
11
+ ) {
12
+ let timeSpent = 0
13
+ while (!lock.initialized) {
14
+ await new Promise((resolve) => setTimeout(resolve, interval))
15
+
16
+ if (timeSpent > timeout) {
17
+ throw new Error(`Timeout waiting for initialization`)
18
+ } else {
19
+ timeSpent += interval
20
+ }
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ export const HealthPath = '/health'
@@ -0,0 +1,23 @@
1
+ import { Controller, Get, Logger } from '@nestjs/common'
2
+ import { HealthCheck } from '@nestjs/terminus'
3
+ import { HealthPath } from './constants'
4
+ import { HealthService } from './health.service'
5
+ import { API_ROOT } from '../common/routes/constants'
6
+ import { EcoLogMessage } from '../common/logging/eco-log-message'
7
+
8
+ @Controller(API_ROOT)
9
+ export class HealthController {
10
+ private logger = new Logger(HealthController.name)
11
+ constructor(private healthService: HealthService) {}
12
+
13
+ @Get(HealthPath)
14
+ @HealthCheck()
15
+ check() {
16
+ this.logger.debug(
17
+ EcoLogMessage.fromDefault({
18
+ message: `HealthController`,
19
+ }),
20
+ )
21
+ return this.healthService.checkHealth()
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ import { Module } from '@nestjs/common'
2
+ import { TerminusModule } from '@nestjs/terminus'
3
+ import { RedisHealthModule } from '@liaoliaots/nestjs-redis-health'
4
+ import { TransactionModule } from '@/transaction/transaction.module'
5
+ import { HealthController } from '@/health/health.controller'
6
+ import { HealthService } from '@/health/health.service'
7
+ import { BalanceHealthIndicator } from '@/health/indicators/balance.indicator'
8
+ import { EcoRedisHealthIndicator } from '@/health/indicators/eco-redis.indicator'
9
+ import { GitCommitHealthIndicator } from '@/health/indicators/git-commit.indicator'
10
+ import { PermissionHealthIndicator } from '@/health/indicators/permission.indicator'
11
+ import { MongoDBHealthIndicator } from '@/health/indicators/mongodb.indicator'
12
+
13
+ @Module({
14
+ imports: [TransactionModule, RedisHealthModule, TerminusModule],
15
+ controllers: [HealthController],
16
+ providers: [
17
+ HealthService,
18
+ BalanceHealthIndicator,
19
+ EcoRedisHealthIndicator,
20
+ GitCommitHealthIndicator,
21
+ PermissionHealthIndicator,
22
+ MongoDBHealthIndicator,
23
+ ],
24
+ })
25
+ export class HealthModule {}
@@ -0,0 +1,40 @@
1
+ import { Injectable, Logger } from '@nestjs/common'
2
+
3
+ import { HealthCheckService } from '@nestjs/terminus'
4
+
5
+ import { BalanceHealthIndicator } from './indicators/balance.indicator'
6
+ import { EcoRedisHealthIndicator } from './indicators/eco-redis.indicator'
7
+ import { MongoDBHealthIndicator } from './indicators/mongodb.indicator'
8
+ import { EcoLogMessage } from '../common/logging/eco-log-message'
9
+ import { GitCommitHealthIndicator } from './indicators/git-commit.indicator'
10
+
11
+ @Injectable()
12
+ export class HealthService {
13
+ private logger = new Logger(HealthService.name)
14
+
15
+ constructor(
16
+ private readonly health: HealthCheckService,
17
+ private readonly balanceIndicator: BalanceHealthIndicator,
18
+ private readonly gitCommitHealthIndicator: GitCommitHealthIndicator,
19
+ private readonly mongoDBHealthIndicator: MongoDBHealthIndicator,
20
+ private readonly redisIndicator: EcoRedisHealthIndicator,
21
+ ) {}
22
+
23
+ async checkHealth() {
24
+ const healthCheck = await this.health.check([
25
+ () => this.gitCommitHealthIndicator.gitCommit(),
26
+ () => this.redisIndicator.checkRedis(),
27
+ () => this.mongoDBHealthIndicator.checkMongoDB(),
28
+ () => this.balanceIndicator.checkBalances(),
29
+ ])
30
+ this.logger.log(
31
+ EcoLogMessage.fromDefault({
32
+ message: `HealthService.checkHealth()`,
33
+ properties: {
34
+ healthCheck: healthCheck,
35
+ },
36
+ }),
37
+ )
38
+ return healthCheck
39
+ }
40
+ }
@@ -0,0 +1,196 @@
1
+ import { Injectable, Logger } from '@nestjs/common'
2
+ import { HealthCheckError, HealthIndicator, HealthIndicatorResult } from '@nestjs/terminus'
3
+ import { EcoConfigService } from '../../eco-configs/eco-config.service'
4
+ import { erc20Abi, Hex } from 'viem'
5
+ import { Network } from 'alchemy-sdk'
6
+ import { entries } from 'lodash'
7
+ import { TargetContract } from '../../eco-configs/eco-config.types'
8
+ import { KernelAccountClientService } from '../../transaction/smart-wallets/kernel/kernel-account-client.service'
9
+
10
+ type TokenType = { decimal: string; value: string; minBalances?: number }
11
+ @Injectable()
12
+ export class BalanceHealthIndicator extends HealthIndicator {
13
+ private logger = new Logger(BalanceHealthIndicator.name)
14
+ constructor(
15
+ private readonly kernelAccountClientService: KernelAccountClientService,
16
+ private readonly configService: EcoConfigService,
17
+ ) {
18
+ super()
19
+ }
20
+ async checkBalances(): Promise<HealthIndicatorResult> {
21
+ const minEthBalanceWei = this.configService.getEth().simpleAccount.minEthBalanceWei
22
+ const [accounts, solvers, sources] = await Promise.all([
23
+ this.getAccount(),
24
+ this.getSolvers(),
25
+ this.getSources(),
26
+ ])
27
+ let isHealthy = solvers.balances.every((solver) => {
28
+ const tokens = solver.tokens
29
+ return Object.values(tokens).every((token) => {
30
+ if (!token.minBalances) {
31
+ return true
32
+ }
33
+ const minBalanceDecimal = BigInt(token.minBalances) * BigInt(token.decimal) * 10n
34
+ return BigInt(token.value) >= minBalanceDecimal
35
+ })
36
+ })
37
+
38
+ isHealthy =
39
+ isHealthy &&
40
+ accounts.every((bal) => {
41
+ return BigInt(bal.balance) >= minEthBalanceWei
42
+ })
43
+ const results = this.getStatus('balances', isHealthy, {
44
+ totalBalance: solvers.totalTokenBalance,
45
+ accounts,
46
+ solvers: solvers.balances,
47
+ sources,
48
+ })
49
+ if (isHealthy) {
50
+ return results
51
+ }
52
+ throw new HealthCheckError('Balances failed', results)
53
+ }
54
+ private async getAccount(): Promise<any[]> {
55
+ const minEthBalanceWei = this.configService.getEth().simpleAccount.minEthBalanceWei
56
+ const accountBalance: {
57
+ kernelAddress: `0x${string}`
58
+ eocAddress: `0x${string}`
59
+ chainID: number
60
+ balance: string
61
+ minEthBalanceWei: number
62
+ }[] = []
63
+ const solvers = this.configService.getSolvers()
64
+ const balanceTasks = entries(solvers).map(async ([, solver]) => {
65
+ const clientKernel = await this.kernelAccountClientService.getClient(solver.chainID)
66
+ const kernelAddress = clientKernel.kernelAccount?.address
67
+ const eocAddress = clientKernel.account?.address
68
+
69
+ if (eocAddress && kernelAddress) {
70
+ const bal = await clientKernel.getBalance({ address: eocAddress })
71
+ accountBalance.push({
72
+ kernelAddress,
73
+ eocAddress,
74
+ chainID: solver.chainID,
75
+ balance: ' ' + BigInt(bal).toString(), //makes comparing easier in json
76
+ minEthBalanceWei,
77
+ })
78
+ }
79
+ })
80
+ await Promise.all(balanceTasks)
81
+
82
+ return accountBalance.reverse()
83
+ }
84
+
85
+ private async getSources(): Promise<any[]> {
86
+ const sources: Array<{
87
+ accountAddress: `0x${string}` | undefined
88
+ tokens: Record<string, TokenType>
89
+ network: Network
90
+ chainID: number
91
+ sourceAddress: Hex
92
+ provers: Hex[]
93
+ }> = []
94
+ const IntentSources = this.configService.getIntentSources()
95
+ for (const IntentSource of IntentSources) {
96
+ const client = await this.kernelAccountClientService.getClient(IntentSource.chainID)
97
+ const accountAddress = client.kernelAccountAddress
98
+
99
+ const balances = await this.getBalanceCalls(IntentSource.chainID, IntentSource.tokens)
100
+
101
+ const sourceBalancesString = this.joinBalance(balances, IntentSource.tokens)
102
+ sources.push({ ...IntentSource, accountAddress, tokens: sourceBalancesString })
103
+ }
104
+ sources.reverse()
105
+ return sources
106
+ }
107
+
108
+ private async getSolvers(): Promise<{
109
+ balances: { tokens: Record<string, TokenType> }[]
110
+ totalTokenBalance: number
111
+ }> {
112
+ const solverBalances: Array<{
113
+ accountAddress: `0x${string}` | undefined
114
+ tokens: Record<string, TokenType>
115
+ inboxAddress: Hex
116
+ network: Network
117
+ chainID: number
118
+ }> = []
119
+ let totalTokenBalance = 0
120
+ const solverConfig = this.configService.getSolvers()
121
+ await Promise.all(
122
+ Object.entries(solverConfig).map(async ([, solver]) => {
123
+ const client = await this.kernelAccountClientService.getClient(solver.chainID)
124
+ const accountAddress = client.kernelAccountAddress
125
+ const tokens = Object.keys(solver.targets) as Hex[]
126
+ const balances = await this.getBalanceCalls(solver.chainID, tokens)
127
+ const mins = Object.values(solver.targets).map((target) => target.minBalance)
128
+ const sourceBalancesString = this.joinBalance(balances, tokens, mins)
129
+ entries(solver.targets).forEach((target) => {
130
+ ;(target[1] as TargetContract & { balance: object }).balance =
131
+ sourceBalancesString[target[0]]
132
+ totalTokenBalance += parseInt(sourceBalancesString[target[0]].value)
133
+ })
134
+
135
+ solverBalances.push({
136
+ ...solver,
137
+ accountAddress,
138
+ tokens: sourceBalancesString,
139
+ })
140
+ }),
141
+ )
142
+ return { balances: solverBalances, totalTokenBalance }
143
+ }
144
+
145
+ private async getBalanceCalls(chainID: number, tokens: Hex[]) {
146
+ const client = await this.kernelAccountClientService.getClient(chainID)
147
+ const accountAddress = client.kernelAccountAddress
148
+
149
+ const balanceCalls = tokens.map((token) => {
150
+ return [
151
+ {
152
+ address: token,
153
+ abi: erc20Abi,
154
+ functionName: 'balanceOf',
155
+ args: [accountAddress],
156
+ },
157
+ {
158
+ address: token,
159
+ abi: erc20Abi,
160
+ functionName: 'decimals',
161
+ },
162
+ ]
163
+ })
164
+
165
+ return await client.multicall({
166
+ contracts: balanceCalls.flat(),
167
+ })
168
+ }
169
+
170
+ private joinBalance(
171
+ balances: any,
172
+ tokens: string[],
173
+ minBalances: number[] = [],
174
+ ): Record<string, TokenType> {
175
+ let decimal = 0n,
176
+ value = 0n,
177
+ i = 0
178
+ const sourceBalancesString: Record<string, TokenType> = {}
179
+
180
+ while (
181
+ balances.length > 0 &&
182
+ ([{ result: value as unknown }, { result: decimal as unknown }] = [
183
+ balances.shift(),
184
+ balances.shift(),
185
+ ])
186
+ ) {
187
+ sourceBalancesString[tokens[i]] = {
188
+ decimal: BigInt(decimal).toString(),
189
+ value: BigInt(value).toString(),
190
+ ...(minBalances ? { minBalances: minBalances[i] } : {}),
191
+ }
192
+ i++
193
+ }
194
+ return sourceBalancesString
195
+ }
196
+ }
@@ -0,0 +1,23 @@
1
+ import { Injectable, Logger } from '@nestjs/common'
2
+ import { HealthIndicatorResult } from '@nestjs/terminus'
3
+ import { RedisHealthIndicator } from '@liaoliaots/nestjs-redis-health'
4
+ import { EcoConfigService } from '../../eco-configs/eco-config.service'
5
+ import { RedisConnectionUtils } from '../../common/redis/redis-connection-utils'
6
+
7
+ @Injectable()
8
+ export class EcoRedisHealthIndicator extends RedisHealthIndicator {
9
+ private logger = new Logger(EcoRedisHealthIndicator.name)
10
+ private readonly redis: any
11
+ constructor(private readonly configService: EcoConfigService) {
12
+ super()
13
+ const serviceConfig = configService.getRedis()
14
+ this.redis = RedisConnectionUtils.getRedisConnection(serviceConfig)
15
+ }
16
+ async checkRedis(): Promise<HealthIndicatorResult> {
17
+ return this.checkHealth('redis', {
18
+ type: 'redis',
19
+ client: this.redis,
20
+ timeout: 1000,
21
+ })
22
+ }
23
+ }