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.
- package/.eslintignore +8 -0
- package/.eslintrc.js +24 -0
- package/.github/workflows/ci.yaml +38 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/Dockerfile +11 -0
- package/LICENSE +21 -0
- package/README.md +29 -5
- package/config/default.ts +135 -0
- package/config/development.ts +95 -0
- package/config/preproduction.ts +17 -0
- package/config/production.ts +17 -0
- package/config/staging.ts +17 -0
- package/config/test.ts +7 -0
- package/index.js +43 -0
- package/jest.config.ts +14 -0
- package/nest-cli.json +8 -0
- package/package.json +117 -6
- package/src/api/api.module.ts +27 -0
- package/src/api/balance.controller.ts +41 -0
- package/src/api/quote.controller.ts +54 -0
- package/src/api/tests/balance.controller.spec.ts +113 -0
- package/src/api/tests/quote.controller.spec.ts +83 -0
- package/src/app.module.ts +74 -0
- package/src/balance/balance.module.ts +14 -0
- package/src/balance/balance.service.ts +230 -0
- package/src/balance/balance.ws.service.ts +104 -0
- package/src/balance/types.ts +16 -0
- package/src/bullmq/bullmq.helper.ts +41 -0
- package/src/bullmq/processors/eth-ws.processor.ts +47 -0
- package/src/bullmq/processors/inbox.processor.ts +55 -0
- package/src/bullmq/processors/interval.processor.ts +54 -0
- package/src/bullmq/processors/processor.module.ts +14 -0
- package/src/bullmq/processors/signer.processor.ts +41 -0
- package/src/bullmq/processors/solve-intent.processor.ts +73 -0
- package/src/bullmq/processors/tests/solve-intent.processor.spec.ts +3 -0
- package/src/bullmq/utils/queue.ts +22 -0
- package/src/chain-monitor/chain-monitor.module.ts +12 -0
- package/src/chain-monitor/chain-sync.service.ts +134 -0
- package/src/chain-monitor/tests/chain-sync.service.spec.ts +190 -0
- package/src/commander/.eslintrc.js +6 -0
- package/src/commander/balance/balance-command.module.ts +12 -0
- package/src/commander/balance/balance.command.ts +73 -0
- package/src/commander/command-main.ts +15 -0
- package/src/commander/commander-app.module.ts +31 -0
- package/src/commander/eco-config.command.ts +20 -0
- package/src/commander/safe/safe-command.module.ts +11 -0
- package/src/commander/safe/safe.command.ts +70 -0
- package/src/commander/transfer/client.command.ts +24 -0
- package/src/commander/transfer/transfer-command.module.ts +26 -0
- package/src/commander/transfer/transfer.command.ts +138 -0
- package/src/commander/utils.ts +8 -0
- package/src/common/chains/definitions/arbitrum.ts +12 -0
- package/src/common/chains/definitions/base.ts +21 -0
- package/src/common/chains/definitions/eco.ts +54 -0
- package/src/common/chains/definitions/ethereum.ts +22 -0
- package/src/common/chains/definitions/helix.ts +53 -0
- package/src/common/chains/definitions/mantle.ts +12 -0
- package/src/common/chains/definitions/optimism.ts +22 -0
- package/src/common/chains/definitions/polygon.ts +12 -0
- package/src/common/chains/supported.ts +26 -0
- package/src/common/chains/transport.ts +19 -0
- package/src/common/errors/eco-error.ts +155 -0
- package/src/common/events/constants.ts +3 -0
- package/src/common/events/viem.ts +22 -0
- package/src/common/logging/eco-log-message.ts +74 -0
- package/src/common/redis/constants.ts +55 -0
- package/src/common/redis/redis-connection-utils.ts +106 -0
- package/src/common/routes/constants.ts +3 -0
- package/src/common/utils/objects.ts +34 -0
- package/src/common/utils/strings.ts +49 -0
- package/src/common/utils/tests/objects.spec.ts +23 -0
- package/src/common/utils/tests/strings.spec.ts +22 -0
- package/src/common/viem/contracts.ts +25 -0
- package/src/common/viem/tests/utils.spec.ts +115 -0
- package/src/common/viem/utils.ts +78 -0
- package/src/contracts/ERC20.contract.ts +389 -0
- package/src/contracts/EntryPoint.V6.contract.ts +1309 -0
- package/src/contracts/KernelAccount.abi.ts +87 -0
- package/src/contracts/OwnableExecutor.abi.ts +128 -0
- package/src/contracts/SimpleAccount.contract.ts +524 -0
- package/src/contracts/inbox.ts +8 -0
- package/src/contracts/index.ts +9 -0
- package/src/contracts/intent-source.ts +55 -0
- package/src/contracts/interfaces/index.ts +1 -0
- package/src/contracts/interfaces/prover.interface.ts +22 -0
- package/src/contracts/prover.ts +9 -0
- package/src/contracts/tests/erc20.contract.spec.ts +59 -0
- package/src/contracts/utils.ts +31 -0
- package/src/decoder/decoder.interface.ts +3 -0
- package/src/decoder/tests/utils.spec.ts +36 -0
- package/src/decoder/utils.ts +24 -0
- package/src/decorators/cacheable.decorator.ts +48 -0
- package/src/eco-configs/aws-config.service.ts +75 -0
- package/src/eco-configs/eco-config.module.ts +44 -0
- package/src/eco-configs/eco-config.service.ts +220 -0
- package/src/eco-configs/eco-config.types.ts +278 -0
- package/src/eco-configs/interfaces/config-source.interface.ts +3 -0
- package/src/eco-configs/tests/aws-config.service.spec.ts +52 -0
- package/src/eco-configs/tests/eco-config.service.spec.ts +137 -0
- package/src/eco-configs/tests/utils.spec.ts +84 -0
- package/src/eco-configs/utils.ts +49 -0
- package/src/fee/fee.module.ts +10 -0
- package/src/fee/fee.service.ts +467 -0
- package/src/fee/tests/fee.service.spec.ts +909 -0
- package/src/fee/tests/utils.spec.ts +49 -0
- package/src/fee/types.ts +44 -0
- package/src/fee/utils.ts +23 -0
- package/src/flags/flags.module.ts +10 -0
- package/src/flags/flags.service.ts +112 -0
- package/src/flags/tests/flags.service.spec.ts +68 -0
- package/src/flags/utils.ts +22 -0
- package/src/health/constants.ts +1 -0
- package/src/health/health.controller.ts +23 -0
- package/src/health/health.module.ts +25 -0
- package/src/health/health.service.ts +40 -0
- package/src/health/indicators/balance.indicator.ts +196 -0
- package/src/health/indicators/eco-redis.indicator.ts +23 -0
- package/src/health/indicators/git-commit.indicator.ts +67 -0
- package/src/health/indicators/mongodb.indicator.ts +11 -0
- package/src/health/indicators/permission.indicator.ts +64 -0
- package/src/intent/create-intent.service.ts +129 -0
- package/src/intent/feasable-intent.service.ts +80 -0
- package/src/intent/fulfill-intent.service.ts +318 -0
- package/src/intent/intent.controller.ts +199 -0
- package/src/intent/intent.module.ts +49 -0
- package/src/intent/schemas/intent-call-data.schema.ts +16 -0
- package/src/intent/schemas/intent-data.schema.ts +114 -0
- package/src/intent/schemas/intent-source.schema.ts +33 -0
- package/src/intent/schemas/intent-token-amount.schema.ts +14 -0
- package/src/intent/schemas/reward-data.schema.ts +48 -0
- package/src/intent/schemas/route-data.schema.ts +52 -0
- package/src/intent/schemas/watch-event.schema.ts +32 -0
- package/src/intent/tests/create-intent.service.spec.ts +215 -0
- package/src/intent/tests/feasable-intent.service.spec.ts +155 -0
- package/src/intent/tests/fulfill-intent.service.spec.ts +564 -0
- package/src/intent/tests/utils-intent.service.spec.ts +308 -0
- package/src/intent/tests/utils.spec.ts +62 -0
- package/src/intent/tests/validate-intent.service.spec.ts +297 -0
- package/src/intent/tests/validation.service.spec.ts +337 -0
- package/src/intent/utils-intent.service.ts +168 -0
- package/src/intent/utils.ts +37 -0
- package/src/intent/validate-intent.service.ts +176 -0
- package/src/intent/validation.sevice.ts +223 -0
- package/src/interceptors/big-int.interceptor.ts +30 -0
- package/src/intervals/interval.module.ts +18 -0
- package/src/intervals/retry-infeasable-intents.service.ts +89 -0
- package/src/intervals/tests/retry-infeasable-intents.service.spec.ts +167 -0
- package/src/kms/errors.ts +0 -0
- package/src/kms/kms.module.ts +12 -0
- package/src/kms/kms.service.ts +65 -0
- package/src/kms/tests/kms.service.spec.ts +60 -0
- package/src/liquidity-manager/jobs/check-balances-cron.job.ts +229 -0
- package/src/liquidity-manager/jobs/liquidity-manager.job.ts +52 -0
- package/src/liquidity-manager/jobs/rebalance.job.ts +61 -0
- package/src/liquidity-manager/liquidity-manager.module.ts +29 -0
- package/src/liquidity-manager/processors/base.processor.ts +117 -0
- package/src/liquidity-manager/processors/eco-protocol-intents.processor.ts +34 -0
- package/src/liquidity-manager/processors/grouped-jobs.processor.ts +103 -0
- package/src/liquidity-manager/queues/liquidity-manager.queue.ts +48 -0
- package/src/liquidity-manager/schemas/rebalance-token.schema.ts +32 -0
- package/src/liquidity-manager/schemas/rebalance.schema.ts +32 -0
- package/src/liquidity-manager/services/liquidity-manager.service.ts +188 -0
- package/src/liquidity-manager/services/liquidity-provider.service.ts +25 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.spec.ts +125 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/lifi-provider.service.ts +117 -0
- package/src/liquidity-manager/services/liquidity-providers/LiFi/utils/get-transaction-hashes.ts +16 -0
- package/src/liquidity-manager/tests/liquidity-manager.service.spec.ts +142 -0
- package/src/liquidity-manager/types/token-state.enum.ts +5 -0
- package/src/liquidity-manager/types/types.d.ts +52 -0
- package/src/liquidity-manager/utils/address.ts +5 -0
- package/src/liquidity-manager/utils/math.ts +9 -0
- package/src/liquidity-manager/utils/serialize.spec.ts +24 -0
- package/src/liquidity-manager/utils/serialize.ts +47 -0
- package/src/liquidity-manager/utils/token.ts +91 -0
- package/src/main.ts +63 -0
- package/src/nest-redlock/nest-redlock.config.ts +14 -0
- package/src/nest-redlock/nest-redlock.interface.ts +5 -0
- package/src/nest-redlock/nest-redlock.module.ts +64 -0
- package/src/nest-redlock/nest-redlock.service.ts +59 -0
- package/src/prover/proof.service.ts +184 -0
- package/src/prover/prover.module.ts +10 -0
- package/src/prover/tests/proof.service.spec.ts +154 -0
- package/src/quote/dto/quote.intent.data.dto.ts +35 -0
- package/src/quote/dto/quote.reward.data.dto.ts +67 -0
- package/src/quote/dto/quote.route.data.dto.ts +71 -0
- package/src/quote/dto/types.ts +18 -0
- package/src/quote/errors.ts +215 -0
- package/src/quote/quote.module.ts +17 -0
- package/src/quote/quote.service.ts +299 -0
- package/src/quote/schemas/quote-call.schema.ts +16 -0
- package/src/quote/schemas/quote-intent.schema.ts +27 -0
- package/src/quote/schemas/quote-reward.schema.ts +24 -0
- package/src/quote/schemas/quote-route.schema.ts +30 -0
- package/src/quote/schemas/quote-token.schema.ts +14 -0
- package/src/quote/tests/quote.service.spec.ts +444 -0
- package/src/sign/atomic-signer.service.ts +24 -0
- package/src/sign/atomic.nonce.service.ts +114 -0
- package/src/sign/kms-account/kmsToAccount.ts +73 -0
- package/src/sign/kms-account/signKms.ts +30 -0
- package/src/sign/kms-account/signKmsTransaction.ts +37 -0
- package/src/sign/kms-account/signKmsTypedData.ts +21 -0
- package/src/sign/nonce.service.ts +89 -0
- package/src/sign/schemas/nonce.schema.ts +36 -0
- package/src/sign/sign.controller.ts +52 -0
- package/src/sign/sign.helper.ts +23 -0
- package/src/sign/sign.module.ts +27 -0
- package/src/sign/signer-kms.service.ts +27 -0
- package/src/sign/signer.service.ts +26 -0
- package/src/solver/filters/tests/valid-smart-wallet.service.spec.ts +87 -0
- package/src/solver/filters/valid-smart-wallet.service.ts +58 -0
- package/src/solver/solver.module.ts +10 -0
- package/src/transaction/multichain-public-client.service.ts +15 -0
- package/src/transaction/smart-wallets/kernel/actions/encodeData.kernel.ts +57 -0
- package/src/transaction/smart-wallets/kernel/create-kernel-client-v2.account.ts +183 -0
- package/src/transaction/smart-wallets/kernel/create.kernel.account.ts +270 -0
- package/src/transaction/smart-wallets/kernel/index.ts +2 -0
- package/src/transaction/smart-wallets/kernel/kernel-account-client-v2.service.ts +90 -0
- package/src/transaction/smart-wallets/kernel/kernel-account-client.service.ts +107 -0
- package/src/transaction/smart-wallets/kernel/kernel-account.client.ts +105 -0
- package/src/transaction/smart-wallets/kernel/kernel-account.config.ts +34 -0
- package/src/transaction/smart-wallets/simple-account/create.simple.account.ts +19 -0
- package/src/transaction/smart-wallets/simple-account/index.ts +2 -0
- package/src/transaction/smart-wallets/simple-account/simple-account-client.service.ts +42 -0
- package/src/transaction/smart-wallets/simple-account/simple-account.client.ts +83 -0
- package/src/transaction/smart-wallets/simple-account/simple-account.config.ts +5 -0
- package/src/transaction/smart-wallets/smart-wallet.types.ts +38 -0
- package/src/transaction/smart-wallets/utils.ts +14 -0
- package/src/transaction/transaction.module.ts +25 -0
- package/src/transaction/viem_multichain_client.service.ts +100 -0
- package/src/transforms/viem-address.decorator.ts +14 -0
- package/src/utils/bigint.ts +44 -0
- package/src/utils/types.ts +18 -0
- package/src/watch/intent/tests/watch-create-intent.service.spec.ts +257 -0
- package/src/watch/intent/tests/watch-fulfillment.service.spec.ts +141 -0
- package/src/watch/intent/watch-create-intent.service.ts +106 -0
- package/src/watch/intent/watch-event.service.ts +133 -0
- package/src/watch/intent/watch-fulfillment.service.ts +115 -0
- package/src/watch/watch.module.ts +13 -0
- package/test/app.e2e-spec.ts +21 -0
- package/test/jest-e2e.json +9 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +29 -0
@@ -0,0 +1,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
|
+
})
|
package/src/fee/types.ts
ADDED
@@ -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
|
+
}
|
package/src/fee/utils.ts
ADDED
@@ -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
|
+
}
|