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,71 @@
|
|
1
|
+
import { CallDataInterface } from '@/contracts'
|
2
|
+
import { QuoteRewardTokensDTO } from '@/quote/dto/quote.reward.data.dto'
|
3
|
+
import { ViemAddressTransform } from '@/transforms/viem-address.decorator'
|
4
|
+
import { RouteType } from '@eco-foundation/routes-ts'
|
5
|
+
import { ApiProperty } from '@nestjs/swagger'
|
6
|
+
import { Transform, Type } from 'class-transformer'
|
7
|
+
import { ArrayNotEmpty, IsArray, IsNotEmpty, ValidateNested } from 'class-validator'
|
8
|
+
import { Hex } from 'viem'
|
9
|
+
|
10
|
+
/**
|
11
|
+
* The DTO for the route data that the sender wants to make.
|
12
|
+
* Similar to {@link RouteType} except that it does not contain the salt field.
|
13
|
+
* @param source denotes the source chain id of the route
|
14
|
+
* @param destination denotes the destination chain id of the route
|
15
|
+
* @param inbox denotes the inbox address
|
16
|
+
* @param calls denotes the array of {@link QuoteCallDataDTO} that the sender wants to make
|
17
|
+
*/
|
18
|
+
export class QuoteRouteDataDTO implements QuoteRouteDataInterface {
|
19
|
+
@IsNotEmpty()
|
20
|
+
@ApiProperty()
|
21
|
+
@Transform(({ value }) => BigInt(value))
|
22
|
+
source: bigint
|
23
|
+
|
24
|
+
@IsNotEmpty()
|
25
|
+
@Transform(({ value }) => BigInt(value))
|
26
|
+
@ApiProperty()
|
27
|
+
destination: bigint
|
28
|
+
|
29
|
+
@ViemAddressTransform()
|
30
|
+
@IsNotEmpty()
|
31
|
+
@ApiProperty()
|
32
|
+
inbox: Hex
|
33
|
+
|
34
|
+
@IsArray()
|
35
|
+
@ArrayNotEmpty()
|
36
|
+
@ValidateNested()
|
37
|
+
@ApiProperty()
|
38
|
+
@Type(() => QuoteRewardTokensDTO)
|
39
|
+
tokens: QuoteRewardTokensDTO[]
|
40
|
+
|
41
|
+
@IsArray()
|
42
|
+
@ArrayNotEmpty()
|
43
|
+
@ValidateNested()
|
44
|
+
@ApiProperty()
|
45
|
+
@Type(() => QuoteCallDataDTO)
|
46
|
+
calls: QuoteCallDataDTO[]
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* The DTO for the call data that the sender wants to make.
|
51
|
+
* @param target denotes the target address of the call
|
52
|
+
* @param data denotes the data of the call
|
53
|
+
* @param value denotes the native token value of the call
|
54
|
+
*/
|
55
|
+
export class QuoteCallDataDTO implements CallDataInterface {
|
56
|
+
@ViemAddressTransform()
|
57
|
+
@IsNotEmpty()
|
58
|
+
@ApiProperty()
|
59
|
+
target: Hex
|
60
|
+
|
61
|
+
@IsNotEmpty()
|
62
|
+
@ApiProperty()
|
63
|
+
data: Hex
|
64
|
+
|
65
|
+
@IsNotEmpty()
|
66
|
+
@Transform(({ value }) => BigInt(value))
|
67
|
+
@ApiProperty()
|
68
|
+
value: bigint
|
69
|
+
}
|
70
|
+
|
71
|
+
export interface QuoteRouteDataInterface extends Omit<RouteType, 'salt'> {}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { RewardType, RouteType } from '@eco-foundation/routes-ts'
|
2
|
+
import { Prettify } from 'viem'
|
3
|
+
|
4
|
+
/**
|
5
|
+
* The DTO for the reward tokens that the sender has and wants to send.
|
6
|
+
* @param token denotes the token address
|
7
|
+
* @param amount denotes the amount of tokens the caller wants to send
|
8
|
+
* @param balance denotes the amount of tokens the caller can send
|
9
|
+
*/
|
10
|
+
export type RewardTokensType = Prettify<RewardType['tokens'][number]>
|
11
|
+
|
12
|
+
/**
|
13
|
+
* The type for the route calls that the sender wants to make.
|
14
|
+
* @param target denotes the target address of the call
|
15
|
+
* @param data denotes the data of the call
|
16
|
+
* @param value denotes the native token value of the call
|
17
|
+
*/
|
18
|
+
export type CallDataType = RouteType['calls'][number]
|
@@ -0,0 +1,215 @@
|
|
1
|
+
import { EcoError } from '@/common/errors/eco-error'
|
2
|
+
import { FeeAlgorithm } from '@/eco-configs/eco-config.types'
|
3
|
+
import { ValidationChecks } from '@/intent/validation.sevice'
|
4
|
+
import { Hex } from 'viem'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Errors that can be thrown by the quote service
|
8
|
+
*/
|
9
|
+
export interface QuoteErrorsInterface {
|
10
|
+
statusCode: number
|
11
|
+
message: string
|
12
|
+
code: number
|
13
|
+
[key: string]: any
|
14
|
+
}
|
15
|
+
|
16
|
+
export type Quote400 = QuoteErrorsInterface & {
|
17
|
+
statusCode: 400
|
18
|
+
}
|
19
|
+
|
20
|
+
export type Quote500 = QuoteErrorsInterface & {
|
21
|
+
statusCode: 500
|
22
|
+
}
|
23
|
+
|
24
|
+
// The solver does not supoort the request prover
|
25
|
+
export const ProverUnsupported: Quote400 = {
|
26
|
+
statusCode: 400,
|
27
|
+
message: 'Bad Request: The prover selected is not supported.',
|
28
|
+
code: 1,
|
29
|
+
}
|
30
|
+
|
31
|
+
// The quote does not have a reward structure that would be accepted by solver
|
32
|
+
export const RewardInvalid: Quote400 = {
|
33
|
+
statusCode: 400,
|
34
|
+
message: "Bad Request: The reward structure is invalid. Solver doesn't accept the reward.",
|
35
|
+
code: 2,
|
36
|
+
}
|
37
|
+
|
38
|
+
// The quote does not support some of the callData
|
39
|
+
export const CallsUnsupported: Quote400 = {
|
40
|
+
statusCode: 400,
|
41
|
+
message: 'Bad Request: Some callData in calls are not supported.',
|
42
|
+
code: 3,
|
43
|
+
}
|
44
|
+
|
45
|
+
// The quote does not support some of the callData
|
46
|
+
export const SolverUnsupported: Quote400 = {
|
47
|
+
statusCode: 400,
|
48
|
+
message: "Bad Request: The solver doesn't support that chain.",
|
49
|
+
code: 4,
|
50
|
+
}
|
51
|
+
|
52
|
+
// The quote intent is deemed invalid by the validation service
|
53
|
+
export function InvalidQuoteIntent(validations: ValidationChecks): Quote400 {
|
54
|
+
return {
|
55
|
+
statusCode: 400,
|
56
|
+
message: 'Bad Request: The quote was deemed invalid.',
|
57
|
+
code: 4,
|
58
|
+
properties: {
|
59
|
+
validations,
|
60
|
+
},
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* The quote intent cannot be fulfilled because it doesn't have a
|
66
|
+
* reward hight enough to cover the ask
|
67
|
+
*
|
68
|
+
* @param totalAsk the total amount of the ask
|
69
|
+
* @param totalRewardAmount the total amount of the reward
|
70
|
+
* @returns
|
71
|
+
*/
|
72
|
+
export function InsufficientBalance(totalAsk: bigint, totalFulfillmentAmount: bigint): Quote400 {
|
73
|
+
return {
|
74
|
+
statusCode: 400,
|
75
|
+
message:
|
76
|
+
'Bad Request: The quote intent balance was insufficient for fulfillment. TotalAsk > TotalFulfillmentAmount',
|
77
|
+
code: 5,
|
78
|
+
properties: {
|
79
|
+
totalAsk,
|
80
|
+
totalFulfillmentAmount,
|
81
|
+
},
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
// The quote is deemed infeasible by the feasibility service
|
86
|
+
export function InfeasibleQuote(error: Error): Quote400 {
|
87
|
+
return {
|
88
|
+
statusCode: 400,
|
89
|
+
message: 'Bad Request: The quote was deemed infeasible.',
|
90
|
+
code: 6,
|
91
|
+
error,
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
// The quote is deemed invalid by the feasibility service
|
96
|
+
export function InvalidQuote(
|
97
|
+
results: (
|
98
|
+
| false
|
99
|
+
| {
|
100
|
+
solvent: boolean
|
101
|
+
profitable: boolean
|
102
|
+
}
|
103
|
+
| undefined
|
104
|
+
)[],
|
105
|
+
): Quote400 {
|
106
|
+
return {
|
107
|
+
statusCode: 400,
|
108
|
+
message: 'Bad Request: The quote was deemed invalid.',
|
109
|
+
code: 7,
|
110
|
+
results,
|
111
|
+
}
|
112
|
+
}
|
113
|
+
// The quote is deemed to be insolvent or unprofitable by the feasibility service
|
114
|
+
export function InsolventUnprofitableQuote(
|
115
|
+
results: (
|
116
|
+
| false
|
117
|
+
| {
|
118
|
+
solvent: boolean
|
119
|
+
profitable: boolean
|
120
|
+
}
|
121
|
+
| undefined
|
122
|
+
)[],
|
123
|
+
): Quote400 {
|
124
|
+
return {
|
125
|
+
statusCode: 400,
|
126
|
+
message: 'Bad Request: The quote was deemed to be insolvent or unprofitable.',
|
127
|
+
code: 8,
|
128
|
+
results,
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
/////////////
|
133
|
+
|
134
|
+
/**
|
135
|
+
* The server failed to save to db
|
136
|
+
* @param error the error that was thrown
|
137
|
+
* @returns
|
138
|
+
*/
|
139
|
+
export function InternalSaveError(error: Error): Quote500 {
|
140
|
+
return {
|
141
|
+
statusCode: 500,
|
142
|
+
message: 'Internal Server Error: Failed to save quote intent.',
|
143
|
+
code: 1,
|
144
|
+
error,
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
/**
|
149
|
+
* The server failed to generate the quote
|
150
|
+
* @returns
|
151
|
+
*/
|
152
|
+
export function InternalQuoteError(error?: Error): Quote500 {
|
153
|
+
return {
|
154
|
+
statusCode: 500,
|
155
|
+
message: 'Internal Server Error: Failed generate quote.',
|
156
|
+
code: 2,
|
157
|
+
error,
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
export class QuoteError extends Error {
|
162
|
+
static InvalidSolverAlgorithm(destination: bigint, algorithm: FeeAlgorithm) {
|
163
|
+
return new EcoError(
|
164
|
+
`The solver for destination chain ${destination} did not return a valid algorithm : ${algorithm} `,
|
165
|
+
)
|
166
|
+
}
|
167
|
+
|
168
|
+
static NoSolverForDestination(destination: bigint) {
|
169
|
+
return new EcoError(`No solver found for destination chain ${destination}`)
|
170
|
+
}
|
171
|
+
|
172
|
+
static NoIntentSourceForSource(source: bigint) {
|
173
|
+
return new EcoError(`No intent source found for source chain ${source}`)
|
174
|
+
}
|
175
|
+
|
176
|
+
static FetchingRewardTokensFailed(chainID: bigint) {
|
177
|
+
return new EcoError(`Error occured when fetching reward tokens for ${chainID}`)
|
178
|
+
}
|
179
|
+
|
180
|
+
static FetchingCallTokensFailed(chainID: bigint) {
|
181
|
+
return new EcoError(`Error occured when fetching call tokens for ${chainID}`)
|
182
|
+
}
|
183
|
+
|
184
|
+
static NonERC20TargetInCalls() {
|
185
|
+
return new EcoError(`One or more targets not erc20s`)
|
186
|
+
}
|
187
|
+
|
188
|
+
static SolverLacksLiquidity(
|
189
|
+
chainID: number,
|
190
|
+
target: Hex,
|
191
|
+
requested: bigint,
|
192
|
+
available: bigint,
|
193
|
+
normMinBalance: bigint,
|
194
|
+
) {
|
195
|
+
return new EcoError(
|
196
|
+
`The solver on chain ${chainID} lacks liquidity for ${target} requested ${requested} available ${available} with a normMinBalance of ${normMinBalance}`,
|
197
|
+
)
|
198
|
+
}
|
199
|
+
|
200
|
+
static RouteIsInfeasable(ask: bigint, reward: bigint) {
|
201
|
+
return new EcoError(
|
202
|
+
`The route is not infeasable: the reward ${reward} is less than the ask ${ask}`,
|
203
|
+
)
|
204
|
+
}
|
205
|
+
|
206
|
+
static MultiFulfillRoute() {
|
207
|
+
return new EcoError(`A route with more than 1 erc20 target is not supported`)
|
208
|
+
}
|
209
|
+
|
210
|
+
static FailedToFetchTarget(chainID: bigint, target: Hex) {
|
211
|
+
return new EcoError(
|
212
|
+
`Cannot resolve the decimals of a call target ${target} on chain ${chainID}`,
|
213
|
+
)
|
214
|
+
}
|
215
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { Module } from '@nestjs/common'
|
2
|
+
import { QuoteService } from './quote.service'
|
3
|
+
import { MongooseModule } from '@nestjs/mongoose'
|
4
|
+
import { QuoteIntentModel, QuoteIntentSchema } from '@/quote/schemas/quote-intent.schema'
|
5
|
+
import { IntentModule } from '@/intent/intent.module'
|
6
|
+
import { FeeModule } from '@/fee/fee.module'
|
7
|
+
|
8
|
+
@Module({
|
9
|
+
imports: [
|
10
|
+
FeeModule,
|
11
|
+
IntentModule,
|
12
|
+
MongooseModule.forFeature([{ name: QuoteIntentModel.name, schema: QuoteIntentSchema }]),
|
13
|
+
],
|
14
|
+
providers: [QuoteService],
|
15
|
+
exports: [QuoteService],
|
16
|
+
})
|
17
|
+
export class QuoteModule {}
|
@@ -0,0 +1,299 @@
|
|
1
|
+
import { EcoLogMessage } from '@/common/logging/eco-log-message'
|
2
|
+
import { RewardTokensInterface } from '@/contracts'
|
3
|
+
import { EcoConfigService } from '@/eco-configs/eco-config.service'
|
4
|
+
import { validationsSucceeded, ValidationService } from '@/intent/validation.sevice'
|
5
|
+
import { QuoteIntentDataDTO, QuoteIntentDataInterface } from '@/quote/dto/quote.intent.data.dto'
|
6
|
+
import {
|
7
|
+
InfeasibleQuote,
|
8
|
+
InsufficientBalance,
|
9
|
+
InternalQuoteError,
|
10
|
+
InternalSaveError,
|
11
|
+
InvalidQuoteIntent,
|
12
|
+
Quote400,
|
13
|
+
Quote500,
|
14
|
+
SolverUnsupported,
|
15
|
+
} from '@/quote/errors'
|
16
|
+
import { QuoteIntentModel } from '@/quote/schemas/quote-intent.schema'
|
17
|
+
import { Mathb } from '@/utils/bigint'
|
18
|
+
import { Injectable, Logger } from '@nestjs/common'
|
19
|
+
import { InjectModel } from '@nestjs/mongoose'
|
20
|
+
import { Model } from 'mongoose'
|
21
|
+
import * as dayjs from 'dayjs'
|
22
|
+
import { Hex } from 'viem'
|
23
|
+
import { FeeService } from '@/fee/fee.service'
|
24
|
+
import { CalculateTokensType } from '@/fee/types'
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Service class for getting configs for the app
|
28
|
+
*/
|
29
|
+
@Injectable()
|
30
|
+
export class QuoteService {
|
31
|
+
private logger = new Logger(QuoteService.name)
|
32
|
+
|
33
|
+
constructor(
|
34
|
+
@InjectModel(QuoteIntentModel.name) private quoteIntentModel: Model<QuoteIntentModel>,
|
35
|
+
private readonly feeService: FeeService,
|
36
|
+
private readonly validationService: ValidationService,
|
37
|
+
private readonly ecoConfigService: EcoConfigService,
|
38
|
+
) {}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Generates a quote for the quote intent data.
|
42
|
+
* The network quoteIntentDataDTO is stored in the db.
|
43
|
+
*
|
44
|
+
* @param quoteIntentDataDTO the quote intent data
|
45
|
+
* @returns
|
46
|
+
*/
|
47
|
+
async getQuote(quoteIntentDataDTO: QuoteIntentDataDTO) {
|
48
|
+
this.logger.log(
|
49
|
+
EcoLogMessage.fromDefault({
|
50
|
+
message: `Getting quote for intent`,
|
51
|
+
properties: {
|
52
|
+
quoteIntentDataDTO,
|
53
|
+
},
|
54
|
+
}),
|
55
|
+
)
|
56
|
+
const quoteIntent = await this.storeQuoteIntentData(quoteIntentDataDTO)
|
57
|
+
if (quoteIntent instanceof Error) {
|
58
|
+
return InternalSaveError(quoteIntent)
|
59
|
+
}
|
60
|
+
const res = await this.validateQuoteIntentData(quoteIntent)
|
61
|
+
if (res) {
|
62
|
+
return res
|
63
|
+
}
|
64
|
+
|
65
|
+
let quoteRes:
|
66
|
+
| Quote400
|
67
|
+
| Quote500
|
68
|
+
| {
|
69
|
+
tokens: RewardTokensInterface[]
|
70
|
+
expiryTime: string
|
71
|
+
}
|
72
|
+
| Error
|
73
|
+
try {
|
74
|
+
quoteRes = await this.generateQuote(quoteIntent)
|
75
|
+
} catch (e) {
|
76
|
+
quoteRes = InternalQuoteError(e)
|
77
|
+
} finally {
|
78
|
+
await this.updateQuoteDb(quoteIntent, quoteRes!)
|
79
|
+
}
|
80
|
+
|
81
|
+
return quoteRes
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Stores the quote into the db
|
86
|
+
* @param quoteIntentDataDTO the quote intent data
|
87
|
+
* @returns the stored record or an error
|
88
|
+
*/
|
89
|
+
async storeQuoteIntentData(
|
90
|
+
quoteIntentDataDTO: QuoteIntentDataDTO,
|
91
|
+
): Promise<QuoteIntentModel | Error> {
|
92
|
+
try {
|
93
|
+
const record = await this.quoteIntentModel.create(quoteIntentDataDTO)
|
94
|
+
this.logger.log(
|
95
|
+
EcoLogMessage.fromDefault({
|
96
|
+
message: `Recorded quote intent`,
|
97
|
+
properties: {
|
98
|
+
record,
|
99
|
+
},
|
100
|
+
}),
|
101
|
+
)
|
102
|
+
return record
|
103
|
+
} catch (e) {
|
104
|
+
this.logger.error(
|
105
|
+
EcoLogMessage.fromDefault({
|
106
|
+
message: `Error in storeQuoteIntentData`,
|
107
|
+
properties: {
|
108
|
+
quoteIntentDataDTO,
|
109
|
+
error: e,
|
110
|
+
},
|
111
|
+
}),
|
112
|
+
)
|
113
|
+
return e
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Validates that the quote intent data is valid.
|
119
|
+
* Checks that there is a solver, that the assert validations pass,
|
120
|
+
* and that the quote intent is feasible.
|
121
|
+
* @param quoteIntentModel the model to validate
|
122
|
+
* @returns an res 400, or undefined if the quote intent is valid
|
123
|
+
*/
|
124
|
+
async validateQuoteIntentData(quoteIntentModel: QuoteIntentModel): Promise<Quote400 | undefined> {
|
125
|
+
const solver = this.ecoConfigService.getSolver(quoteIntentModel.route.destination)
|
126
|
+
if (!solver) {
|
127
|
+
this.logger.log(
|
128
|
+
EcoLogMessage.fromDefault({
|
129
|
+
message: `validateQuoteIntentData: No solver found for destination : ${quoteIntentModel.route.destination}`,
|
130
|
+
properties: {
|
131
|
+
quoteIntentModel,
|
132
|
+
},
|
133
|
+
}),
|
134
|
+
)
|
135
|
+
await this.updateQuoteDb(quoteIntentModel, { error: SolverUnsupported })
|
136
|
+
return SolverUnsupported
|
137
|
+
}
|
138
|
+
|
139
|
+
const validations = await this.validationService.assertValidations(quoteIntentModel, solver)
|
140
|
+
if (!validationsSucceeded(validations)) {
|
141
|
+
this.logger.log(
|
142
|
+
EcoLogMessage.fromDefault({
|
143
|
+
message: `validateQuoteIntentData: Some validations failed`,
|
144
|
+
properties: {
|
145
|
+
quoteIntentModel,
|
146
|
+
validations,
|
147
|
+
},
|
148
|
+
}),
|
149
|
+
)
|
150
|
+
await this.updateQuoteDb(quoteIntentModel, { error: InvalidQuoteIntent(validations) })
|
151
|
+
return InvalidQuoteIntent(validations)
|
152
|
+
}
|
153
|
+
|
154
|
+
const { error } = await this.feeService.isRouteFeasible(quoteIntentModel)
|
155
|
+
|
156
|
+
if (error) {
|
157
|
+
const quoteError = InfeasibleQuote(error)
|
158
|
+
this.logger.log(
|
159
|
+
EcoLogMessage.fromDefault({
|
160
|
+
message: `validateQuoteIntentData: quote intent is not feasable ${quoteIntentModel._id}`,
|
161
|
+
properties: {
|
162
|
+
quoteIntentModel,
|
163
|
+
feasable: false,
|
164
|
+
error: quoteError,
|
165
|
+
},
|
166
|
+
}),
|
167
|
+
)
|
168
|
+
await this.updateQuoteDb(quoteIntentModel, { error: quoteError })
|
169
|
+
return quoteError
|
170
|
+
}
|
171
|
+
return
|
172
|
+
}
|
173
|
+
|
174
|
+
/**
|
175
|
+
* Generates a quote for the quote intent model. The quote is generated by:
|
176
|
+
* 1. Converting the call and reward tokens to a standard reserve value for comparisons
|
177
|
+
* 2. Adding a fee to the ask of the normalized call tokens
|
178
|
+
* 3. Fulfilling the ask with the reward tokens starting with any deficit tokens the solver
|
179
|
+
* has on the source chain
|
180
|
+
* 4. If there are any remaining tokens, they are used to fulfill the solver token
|
181
|
+
* starting with the smallest delta(minBalance - balance) tokens
|
182
|
+
* @param quoteIntentModel the quote intent model
|
183
|
+
* @returns the quote or an error 400 for insufficient reward to generate the quote
|
184
|
+
*/
|
185
|
+
async generateQuote(quoteIntentModel: QuoteIntentDataInterface) {
|
186
|
+
const { calculated, error } = await this.feeService.calculateTokens(quoteIntentModel)
|
187
|
+
if (error || !calculated) {
|
188
|
+
return InternalQuoteError(error)
|
189
|
+
}
|
190
|
+
|
191
|
+
const { deficitDescending: fundable, calls, rewards } = calculated as CalculateTokensType
|
192
|
+
|
193
|
+
const totalFulfill = calls.reduce((acc, call) => acc + call.balance, 0n)
|
194
|
+
const totalAsk = this.feeService.getAsk(totalFulfill, quoteIntentModel)
|
195
|
+
const totalAvailableRewardAmount = rewards.reduce((acc, reward) => acc + reward.balance, 0n)
|
196
|
+
if (totalAsk > totalAvailableRewardAmount) {
|
197
|
+
return InsufficientBalance(totalAsk, totalAvailableRewardAmount)
|
198
|
+
}
|
199
|
+
let filled = 0n
|
200
|
+
const quoteRecord: Record<Hex, RewardTokensInterface> = {}
|
201
|
+
for (const deficit of fundable) {
|
202
|
+
if (filled >= totalAsk) {
|
203
|
+
break
|
204
|
+
}
|
205
|
+
const left = totalAsk - filled
|
206
|
+
//Only fill defits first pass
|
207
|
+
if (deficit.delta.balance < 0n) {
|
208
|
+
const reward = rewards.find((r) => r.address === deficit.delta.address)
|
209
|
+
if (reward) {
|
210
|
+
const amount = Mathb.min(
|
211
|
+
Mathb.min(Mathb.abs(deficit.delta.balance), reward.balance),
|
212
|
+
left,
|
213
|
+
)
|
214
|
+
if (amount > 0n) {
|
215
|
+
deficit.delta.balance += amount
|
216
|
+
reward.balance -= amount
|
217
|
+
filled += amount
|
218
|
+
//add to quote record
|
219
|
+
const tokenToFund = quoteRecord[deficit.delta.address] || {
|
220
|
+
token: deficit.delta.address,
|
221
|
+
amount: 0n,
|
222
|
+
}
|
223
|
+
tokenToFund.amount += this.feeService.deconvertNormalize(amount, deficit.delta).balance
|
224
|
+
quoteRecord[deficit.delta.address] = tokenToFund
|
225
|
+
}
|
226
|
+
}
|
227
|
+
}
|
228
|
+
}
|
229
|
+
//resort fundable to reflect first round of fills
|
230
|
+
fundable.sort((a, b) => Mathb.compare(a.delta.balance, b.delta.balance))
|
231
|
+
|
232
|
+
//if remaining funds, for those with smallest deltas
|
233
|
+
if (filled < totalAsk) {
|
234
|
+
for (const deficit of fundable) {
|
235
|
+
if (filled >= totalAsk) {
|
236
|
+
break
|
237
|
+
}
|
238
|
+
const left = totalAsk - filled
|
239
|
+
const reward = rewards.find((r) => r.address === deficit.delta.address)
|
240
|
+
if (reward) {
|
241
|
+
const amount = Mathb.min(left, reward.balance)
|
242
|
+
if (amount > 0n) {
|
243
|
+
deficit.delta.balance += amount
|
244
|
+
reward.balance -= amount
|
245
|
+
filled += amount
|
246
|
+
//add to quote record
|
247
|
+
const tokenToFund = quoteRecord[deficit.delta.address] || {
|
248
|
+
token: deficit.delta.address,
|
249
|
+
amount: 0n,
|
250
|
+
}
|
251
|
+
tokenToFund.amount += Mathb.abs(
|
252
|
+
this.feeService.deconvertNormalize(amount, deficit.delta).balance,
|
253
|
+
)
|
254
|
+
quoteRecord[deficit.delta.address] = tokenToFund
|
255
|
+
}
|
256
|
+
}
|
257
|
+
}
|
258
|
+
}
|
259
|
+
|
260
|
+
//todo save quote to record
|
261
|
+
return {
|
262
|
+
tokens: Object.values(quoteRecord),
|
263
|
+
expiryTime: this.getQuoteExpiryTime(),
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
/**
|
268
|
+
* @returns the expiry time of the quote
|
269
|
+
*/
|
270
|
+
getQuoteExpiryTime(): string {
|
271
|
+
//todo implement expiry time logic
|
272
|
+
return dayjs().add(5, 'minutes').unix().toString()
|
273
|
+
}
|
274
|
+
|
275
|
+
/**
|
276
|
+
* Updates the quote intent model in the db
|
277
|
+
* @param quoteIntentModel the model to update
|
278
|
+
* @returns
|
279
|
+
*/
|
280
|
+
async updateQuoteDb(quoteIntentModel: QuoteIntentModel, receipt?: any) {
|
281
|
+
try {
|
282
|
+
if (receipt) {
|
283
|
+
quoteIntentModel.receipt = receipt
|
284
|
+
}
|
285
|
+
await this.quoteIntentModel.updateOne({ _id: quoteIntentModel._id }, quoteIntentModel)
|
286
|
+
} catch (e) {
|
287
|
+
this.logger.error(
|
288
|
+
EcoLogMessage.fromDefault({
|
289
|
+
message: `Error in updateQuoteDb`,
|
290
|
+
properties: {
|
291
|
+
quoteIntentModel,
|
292
|
+
error: e,
|
293
|
+
},
|
294
|
+
}),
|
295
|
+
)
|
296
|
+
return e
|
297
|
+
}
|
298
|
+
}
|
299
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { CallDataInterface } from '@/contracts'
|
2
|
+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
|
3
|
+
import { Hex } from 'viem'
|
4
|
+
|
5
|
+
@Schema({ timestamps: true })
|
6
|
+
export class QuoteRouteCallDataModel implements CallDataInterface {
|
7
|
+
@Prop({ required: true, type: String })
|
8
|
+
target: Hex
|
9
|
+
@Prop({ required: true, type: String })
|
10
|
+
data: Hex
|
11
|
+
@Prop({ required: true, type: BigInt })
|
12
|
+
value: bigint
|
13
|
+
}
|
14
|
+
|
15
|
+
export const QuoteRouteCallDataSchema = SchemaFactory.createForClass(QuoteRouteCallDataModel)
|
16
|
+
QuoteRouteCallDataSchema.index({ token: 1 }, { unique: false })
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { QuoteIntentDataInterface } from '@/quote/dto/quote.intent.data.dto'
|
2
|
+
import { QuoteRewardDataModel, QuoteRewardDataSchema } from '@/quote/schemas/quote-reward.schema'
|
3
|
+
import { QuoteRouteDataModel, QuoteRouteDataSchema } from '@/quote/schemas/quote-route.schema'
|
4
|
+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
|
5
|
+
import { Types } from 'mongoose'
|
6
|
+
|
7
|
+
@Schema({ timestamps: true })
|
8
|
+
export class QuoteIntentModel implements QuoteIntentDataInterface {
|
9
|
+
_id: Types.ObjectId
|
10
|
+
|
11
|
+
@Prop({ required: true, type: String })
|
12
|
+
dAppID: string
|
13
|
+
|
14
|
+
@Prop({ required: true, type: QuoteRouteDataSchema })
|
15
|
+
route: QuoteRouteDataModel
|
16
|
+
|
17
|
+
@Prop({ required: true, type: QuoteRewardDataSchema })
|
18
|
+
reward: QuoteRewardDataModel
|
19
|
+
|
20
|
+
@Prop({ type: Object })
|
21
|
+
receipt: any
|
22
|
+
}
|
23
|
+
|
24
|
+
export const QuoteIntentSchema = SchemaFactory.createForClass(QuoteIntentModel)
|
25
|
+
QuoteIntentSchema.index({ dAppID: 1 }, { unique: false })
|
26
|
+
QuoteIntentSchema.index({ 'route.source': 1 }, { unique: false })
|
27
|
+
QuoteIntentSchema.index({ 'route.destination': 1 }, { unique: false })
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { QuoteRewardDataType } from '@/quote/dto/quote.reward.data.dto'
|
2
|
+
import {
|
3
|
+
QuoteRewardTokenDataModel,
|
4
|
+
QuoteRewardTokenDataSchema,
|
5
|
+
} from '@/quote/schemas/quote-token.schema'
|
6
|
+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
|
7
|
+
import { Hex } from 'viem'
|
8
|
+
|
9
|
+
@Schema({ timestamps: true })
|
10
|
+
export class QuoteRewardDataModel implements QuoteRewardDataType {
|
11
|
+
@Prop({ required: true, type: String })
|
12
|
+
creator: Hex
|
13
|
+
@Prop({ required: true, type: String })
|
14
|
+
prover: Hex
|
15
|
+
@Prop({ required: true, type: BigInt })
|
16
|
+
deadline: bigint
|
17
|
+
@Prop({ required: true, type: BigInt })
|
18
|
+
nativeValue: bigint
|
19
|
+
@Prop({ required: true, type: [QuoteRewardTokenDataSchema] })
|
20
|
+
tokens: QuoteRewardTokenDataModel[]
|
21
|
+
}
|
22
|
+
|
23
|
+
export const QuoteRewardDataSchema = SchemaFactory.createForClass(QuoteRewardDataModel)
|
24
|
+
QuoteRewardDataSchema.index({ prover: 1 }, { unique: false })
|