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