eco-solver 0.0.1-security → 1.5.1
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 +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,67 @@
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common'
|
2
|
+
import { HealthIndicator, HealthIndicatorResult } from '@nestjs/terminus'
|
3
|
+
import { exec } from 'child_process'
|
4
|
+
import { readFileSync } from 'fs'
|
5
|
+
import { join } from 'path'
|
6
|
+
import { EcoLogMessage } from '../../common/logging/eco-log-message'
|
7
|
+
|
8
|
+
const ECO_ROUTES_PACKAGE_NAME = '@eco-foundation/routes-ts'
|
9
|
+
@Injectable()
|
10
|
+
export class GitCommitHealthIndicator extends HealthIndicator {
|
11
|
+
private logger = new Logger(GitCommitHealthIndicator.name)
|
12
|
+
constructor() {
|
13
|
+
super()
|
14
|
+
}
|
15
|
+
|
16
|
+
async gitCommit(): Promise<HealthIndicatorResult> {
|
17
|
+
const npmLib = this.getDependencyVersion(ECO_ROUTES_PACKAGE_NAME)
|
18
|
+
return this.getStatus('git-commit', !!npmLib, {
|
19
|
+
commitHash: await this.getCommitHash(),
|
20
|
+
ecoRoutesVersion: npmLib,
|
21
|
+
})
|
22
|
+
}
|
23
|
+
|
24
|
+
private async getCommitHash(): Promise<string> {
|
25
|
+
return new Promise((resolve, reject) => {
|
26
|
+
exec('git rev-parse HEAD', (error, stdout) => {
|
27
|
+
if (error) {
|
28
|
+
reject(error)
|
29
|
+
} else {
|
30
|
+
resolve(stdout.trim())
|
31
|
+
}
|
32
|
+
})
|
33
|
+
})
|
34
|
+
}
|
35
|
+
|
36
|
+
private getDependencyVersion(
|
37
|
+
dependencyName: string,
|
38
|
+
): { version: string; npm: string } | undefined {
|
39
|
+
try {
|
40
|
+
// Path to the project's package.json file
|
41
|
+
const packageJsonPath = join(process.cwd(), 'package.json')
|
42
|
+
|
43
|
+
// Read and parse the package.json file
|
44
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
45
|
+
|
46
|
+
// Check dependencies and devDependencies for the specified dependency
|
47
|
+
const version =
|
48
|
+
packageJson.dependencies?.[dependencyName] ||
|
49
|
+
packageJson.devDependencies?.[dependencyName] ||
|
50
|
+
'undefined'
|
51
|
+
return {
|
52
|
+
version,
|
53
|
+
npm: `https://www.npmjs.com/package/${dependencyName}/v/${version.replace('^', '')}?activeTab=code`,
|
54
|
+
}
|
55
|
+
} catch (error) {
|
56
|
+
this.logger.error(
|
57
|
+
EcoLogMessage.fromDefault({
|
58
|
+
message: 'Error reading package.json:',
|
59
|
+
properties: {
|
60
|
+
error,
|
61
|
+
},
|
62
|
+
}),
|
63
|
+
)
|
64
|
+
return undefined // Return undefined if there is an error
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common'
|
2
|
+
import { HealthIndicatorResult, MongooseHealthIndicator } from '@nestjs/terminus'
|
3
|
+
|
4
|
+
@Injectable()
|
5
|
+
export class MongoDBHealthIndicator extends MongooseHealthIndicator {
|
6
|
+
private logger = new Logger(MongoDBHealthIndicator.name)
|
7
|
+
|
8
|
+
async checkMongoDB(): Promise<HealthIndicatorResult> {
|
9
|
+
return this.pingCheck('mongoDB')
|
10
|
+
}
|
11
|
+
}
|
@@ -0,0 +1,64 @@
|
|
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 { Solver } from '../../eco-configs/eco-config.types'
|
5
|
+
import { Hex } from 'viem'
|
6
|
+
import { KernelAccountClientService } from '../../transaction/smart-wallets/kernel/kernel-account-client.service'
|
7
|
+
import { InboxAbi } from '@eco-foundation/routes-ts'
|
8
|
+
|
9
|
+
@Injectable()
|
10
|
+
export class PermissionHealthIndicator extends HealthIndicator {
|
11
|
+
private logger = new Logger(PermissionHealthIndicator.name)
|
12
|
+
private readonly solverPermissions: Map<string, { account: Hex; whitelisted: boolean }> =
|
13
|
+
new Map()
|
14
|
+
|
15
|
+
constructor(
|
16
|
+
private readonly kernelAccountClientService: KernelAccountClientService,
|
17
|
+
private readonly configService: EcoConfigService,
|
18
|
+
) {
|
19
|
+
super()
|
20
|
+
}
|
21
|
+
|
22
|
+
async checkPermissions(): Promise<HealthIndicatorResult> {
|
23
|
+
//iterate over all solvers
|
24
|
+
await Promise.all(
|
25
|
+
Object.entries(this.configService.getSolvers()).map(async (entry) => {
|
26
|
+
const [, solver] = entry
|
27
|
+
await this.loadPermissions(solver)
|
28
|
+
}),
|
29
|
+
)
|
30
|
+
const isHealthy = Array.from(this.solverPermissions.values()).every(
|
31
|
+
(value) => value.whitelisted,
|
32
|
+
)
|
33
|
+
const permissionsString = {}
|
34
|
+
this.solverPermissions.forEach((value, key) => {
|
35
|
+
permissionsString[key] = value
|
36
|
+
})
|
37
|
+
|
38
|
+
const results = this.getStatus('permissions', isHealthy, { permissions: permissionsString })
|
39
|
+
if (isHealthy) {
|
40
|
+
return results
|
41
|
+
}
|
42
|
+
throw new HealthCheckError('Permissions failed', results)
|
43
|
+
}
|
44
|
+
|
45
|
+
private async loadPermissions(solver: Solver) {
|
46
|
+
const key = this.getSolverKey(solver.network, solver.chainID, solver.inboxAddress)
|
47
|
+
const client = await this.kernelAccountClientService.getClient(solver.chainID)
|
48
|
+
const address = client.kernelAccount.address
|
49
|
+
const whitelisted = await client.readContract({
|
50
|
+
address: solver.inboxAddress,
|
51
|
+
abi: InboxAbi,
|
52
|
+
functionName: 'solverWhitelist',
|
53
|
+
args: [address],
|
54
|
+
})
|
55
|
+
this.solverPermissions.set(key, {
|
56
|
+
account: address,
|
57
|
+
whitelisted,
|
58
|
+
})
|
59
|
+
}
|
60
|
+
|
61
|
+
private getSolverKey(network: string, chainID: number, address: Hex): string {
|
62
|
+
return `${network}-${chainID}-${address}`
|
63
|
+
}
|
64
|
+
}
|
@@ -0,0 +1,129 @@
|
|
1
|
+
import { Injectable, Logger, OnModuleInit } from '@nestjs/common'
|
2
|
+
import { EcoConfigService } from '../eco-configs/eco-config.service'
|
3
|
+
import { EcoLogMessage } from '../common/logging/eco-log-message'
|
4
|
+
import { QUEUES } from '../common/redis/constants'
|
5
|
+
import { JobsOptions, Queue } from 'bullmq'
|
6
|
+
import { InjectQueue } from '@nestjs/bullmq'
|
7
|
+
import { IntentSourceModel } from './schemas/intent-source.schema'
|
8
|
+
import { InjectModel } from '@nestjs/mongoose'
|
9
|
+
import { Model } from 'mongoose'
|
10
|
+
import { getIntentJobId } from '../common/utils/strings'
|
11
|
+
import { Hex } from 'viem'
|
12
|
+
import { ValidSmartWalletService } from '../solver/filters/valid-smart-wallet.service'
|
13
|
+
import { decodeCreateIntentLog, IntentCreatedLog } from '../contracts'
|
14
|
+
import { IntentDataModel } from './schemas/intent-data.schema'
|
15
|
+
import { FlagService } from '../flags/flags.service'
|
16
|
+
import { deserialize, Serialize } from '@/liquidity-manager/utils/serialize'
|
17
|
+
|
18
|
+
/**
|
19
|
+
* This service is responsible for creating a new intent record in the database. It is
|
20
|
+
* triggered when a new intent is created recieved in {@link WatchIntentService}.
|
21
|
+
* It validates that the record doesn't exist yet, and that its creator is a valid BEND wallet
|
22
|
+
*/
|
23
|
+
@Injectable()
|
24
|
+
export class CreateIntentService implements OnModuleInit {
|
25
|
+
private logger = new Logger(CreateIntentService.name)
|
26
|
+
private intentJobConfig: JobsOptions
|
27
|
+
|
28
|
+
constructor(
|
29
|
+
@InjectQueue(QUEUES.SOURCE_INTENT.queue) private readonly intentQueue: Queue,
|
30
|
+
@InjectModel(IntentSourceModel.name) private intentModel: Model<IntentSourceModel>,
|
31
|
+
private readonly validSmartWalletService: ValidSmartWalletService,
|
32
|
+
private readonly flagService: FlagService,
|
33
|
+
private readonly ecoConfigService: EcoConfigService,
|
34
|
+
) {}
|
35
|
+
|
36
|
+
onModuleInit() {
|
37
|
+
this.intentJobConfig = this.ecoConfigService.getRedis().jobs.intentJobConfig
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Decodes the intent log, validates the creator is a valid BEND wallet, and creates a new record in the database
|
42
|
+
* if one doesn't yet exist. Finally it enqueue the intent for validation
|
43
|
+
*
|
44
|
+
* @param serializedIntentWs the serialized intent created log
|
45
|
+
* @returns
|
46
|
+
*/
|
47
|
+
async createIntent(serializedIntentWs: Serialize<IntentCreatedLog>) {
|
48
|
+
const intentWs = deserialize(serializedIntentWs)
|
49
|
+
|
50
|
+
this.logger.debug(
|
51
|
+
EcoLogMessage.fromDefault({
|
52
|
+
message: `createIntent ${intentWs.transactionHash}`,
|
53
|
+
properties: {
|
54
|
+
intentHash: intentWs.transactionHash,
|
55
|
+
},
|
56
|
+
}),
|
57
|
+
)
|
58
|
+
|
59
|
+
const ei = decodeCreateIntentLog(intentWs.data, intentWs.topics)
|
60
|
+
const intent = IntentDataModel.fromEvent(ei, intentWs.logIndex || 0)
|
61
|
+
|
62
|
+
try {
|
63
|
+
//check db if the intent is already filled
|
64
|
+
const model = await this.intentModel.findOne({
|
65
|
+
'intent.hash': intent.hash,
|
66
|
+
})
|
67
|
+
|
68
|
+
if (model) {
|
69
|
+
// Record already exists, do nothing and return
|
70
|
+
this.logger.debug(
|
71
|
+
EcoLogMessage.fromDefault({
|
72
|
+
message: `Record for intent already exists ${intent.hash}`,
|
73
|
+
properties: {
|
74
|
+
intentHash: intent.hash,
|
75
|
+
intent: intent,
|
76
|
+
},
|
77
|
+
}),
|
78
|
+
)
|
79
|
+
return
|
80
|
+
}
|
81
|
+
|
82
|
+
const validWallet = this.flagService.getFlagValue('bendWalletOnly')
|
83
|
+
? await this.validSmartWalletService.validateSmartWallet(
|
84
|
+
intent.reward.creator as Hex,
|
85
|
+
intentWs.sourceChainID,
|
86
|
+
)
|
87
|
+
: true
|
88
|
+
|
89
|
+
//create db record
|
90
|
+
const record = await this.intentModel.create({
|
91
|
+
event: intentWs,
|
92
|
+
intent: intent,
|
93
|
+
receipt: null,
|
94
|
+
status: validWallet ? 'PENDING' : 'NON-BEND-WALLET',
|
95
|
+
})
|
96
|
+
|
97
|
+
const jobId = getIntentJobId('create', intent.hash as Hex, intent.logIndex)
|
98
|
+
if (validWallet) {
|
99
|
+
//add to processing queue
|
100
|
+
await this.intentQueue.add(QUEUES.SOURCE_INTENT.jobs.validate_intent, intent.hash, {
|
101
|
+
jobId,
|
102
|
+
...this.intentJobConfig,
|
103
|
+
})
|
104
|
+
}
|
105
|
+
|
106
|
+
this.logger.log(
|
107
|
+
EcoLogMessage.fromDefault({
|
108
|
+
message: `Recorded intent ${record.intent.hash}`,
|
109
|
+
properties: {
|
110
|
+
intentHash: intent.hash,
|
111
|
+
intent: record.intent,
|
112
|
+
validWallet,
|
113
|
+
...(validWallet ? { jobId } : {}),
|
114
|
+
},
|
115
|
+
}),
|
116
|
+
)
|
117
|
+
} catch (e) {
|
118
|
+
this.logger.error(
|
119
|
+
EcoLogMessage.fromDefault({
|
120
|
+
message: `Error in createIntent ${intentWs.transactionHash}`,
|
121
|
+
properties: {
|
122
|
+
intentHash: intentWs.transactionHash,
|
123
|
+
error: e,
|
124
|
+
},
|
125
|
+
}),
|
126
|
+
)
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import { Injectable, Logger, OnModuleInit } from '@nestjs/common'
|
2
|
+
import { EcoConfigService } from '../eco-configs/eco-config.service'
|
3
|
+
import { JobsOptions, Queue } from 'bullmq'
|
4
|
+
import { InjectQueue } from '@nestjs/bullmq'
|
5
|
+
import { QUEUES } from '../common/redis/constants'
|
6
|
+
import { UtilsIntentService } from './utils-intent.service'
|
7
|
+
import { EcoLogMessage } from '../common/logging/eco-log-message'
|
8
|
+
import { getIntentJobId } from '../common/utils/strings'
|
9
|
+
import { Hex } from 'viem'
|
10
|
+
import { QuoteIntentModel } from '@/quote/schemas/quote-intent.schema'
|
11
|
+
import { FeeService } from '@/fee/fee.service'
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Service class for getting configs for the app
|
15
|
+
*/
|
16
|
+
@Injectable()
|
17
|
+
export class FeasableIntentService implements OnModuleInit {
|
18
|
+
private logger = new Logger(FeasableIntentService.name)
|
19
|
+
private intentJobConfig: JobsOptions
|
20
|
+
constructor(
|
21
|
+
@InjectQueue(QUEUES.SOURCE_INTENT.queue) private readonly intentQueue: Queue,
|
22
|
+
private readonly feeService: FeeService,
|
23
|
+
private readonly utilsIntentService: UtilsIntentService,
|
24
|
+
private readonly ecoConfigService: EcoConfigService,
|
25
|
+
) {}
|
26
|
+
|
27
|
+
async onModuleInit() {
|
28
|
+
this.intentJobConfig = this.ecoConfigService.getRedis().jobs.intentJobConfig
|
29
|
+
}
|
30
|
+
async feasableQuote(quoteIntent: QuoteIntentModel) {
|
31
|
+
this.logger.debug(
|
32
|
+
EcoLogMessage.fromDefault({
|
33
|
+
message: `feasableQuote intent ${quoteIntent._id}`,
|
34
|
+
}),
|
35
|
+
)
|
36
|
+
}
|
37
|
+
/**
|
38
|
+
* Validates that the execution of the intent is feasible. This means that the solver can execute
|
39
|
+
* the transaction and that transaction cost is profitable to the solver.
|
40
|
+
* @param intentHash the intent hash to fetch the intent data from the db with
|
41
|
+
* @returns
|
42
|
+
*/
|
43
|
+
async feasableIntent(intentHash: Hex) {
|
44
|
+
this.logger.debug(
|
45
|
+
EcoLogMessage.fromDefault({
|
46
|
+
message: `FeasableIntent intent ${intentHash}`,
|
47
|
+
}),
|
48
|
+
)
|
49
|
+
const data = await this.utilsIntentService.getIntentProcessData(intentHash)
|
50
|
+
const { model, solver, err } = data ?? {}
|
51
|
+
if (!model || !solver) {
|
52
|
+
if (err) {
|
53
|
+
throw err
|
54
|
+
}
|
55
|
+
return
|
56
|
+
}
|
57
|
+
|
58
|
+
const { error } = await this.feeService.isRouteFeasible(model.intent)
|
59
|
+
|
60
|
+
const jobId = getIntentJobId('feasable', intentHash, model!.intent.logIndex)
|
61
|
+
this.logger.debug(
|
62
|
+
EcoLogMessage.fromDefault({
|
63
|
+
message: `FeasableIntent intent ${intentHash}`,
|
64
|
+
properties: {
|
65
|
+
feasable: !error,
|
66
|
+
...(!error ? { jobId } : {}),
|
67
|
+
},
|
68
|
+
}),
|
69
|
+
)
|
70
|
+
if (!error) {
|
71
|
+
//add to processing queue
|
72
|
+
await this.intentQueue.add(QUEUES.SOURCE_INTENT.jobs.fulfill_intent, intentHash, {
|
73
|
+
jobId,
|
74
|
+
...this.intentJobConfig,
|
75
|
+
})
|
76
|
+
} else {
|
77
|
+
await this.utilsIntentService.updateInfeasableIntentModel(model, error)
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
@@ -0,0 +1,318 @@
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common'
|
2
|
+
import {
|
3
|
+
ContractFunctionArgs,
|
4
|
+
ContractFunctionName,
|
5
|
+
encodeAbiParameters,
|
6
|
+
encodeFunctionData,
|
7
|
+
erc20Abi,
|
8
|
+
Hex,
|
9
|
+
pad,
|
10
|
+
zeroAddress,
|
11
|
+
} from 'viem'
|
12
|
+
import {
|
13
|
+
IntentProcessData,
|
14
|
+
TransactionTargetData,
|
15
|
+
UtilsIntentService,
|
16
|
+
} from './utils-intent.service'
|
17
|
+
import { getERC20Selector } from '../contracts'
|
18
|
+
import { EcoError } from '../common/errors/eco-error'
|
19
|
+
import { EcoLogMessage } from '../common/logging/eco-log-message'
|
20
|
+
import { Solver } from '../eco-configs/eco-config.types'
|
21
|
+
import { IntentSourceModel } from './schemas/intent-source.schema'
|
22
|
+
import { EcoConfigService } from '../eco-configs/eco-config.service'
|
23
|
+
import { ProofService } from '../prover/proof.service'
|
24
|
+
import { ExecuteSmartWalletArg } from '../transaction/smart-wallets/smart-wallet.types'
|
25
|
+
import { KernelAccountClientService } from '../transaction/smart-wallets/kernel/kernel-account-client.service'
|
26
|
+
import { InboxAbi } from '@eco-foundation/routes-ts'
|
27
|
+
import { getTransactionTargetData } from '@/intent/utils'
|
28
|
+
import { FeeService } from '@/fee/fee.service'
|
29
|
+
import { IntentDataModel } from '@/intent/schemas/intent-data.schema'
|
30
|
+
|
31
|
+
type FulfillmentMethod = ContractFunctionName<typeof InboxAbi>
|
32
|
+
|
33
|
+
/**
|
34
|
+
* This class fulfills an intent by creating the transactions for the intent targets and the fulfill intent transaction.
|
35
|
+
*/
|
36
|
+
@Injectable()
|
37
|
+
export class FulfillIntentService {
|
38
|
+
private logger = new Logger(FulfillIntentService.name)
|
39
|
+
|
40
|
+
constructor(
|
41
|
+
private readonly kernelAccountClientService: KernelAccountClientService,
|
42
|
+
private readonly proofService: ProofService,
|
43
|
+
private readonly feeService: FeeService,
|
44
|
+
private readonly utilsIntentService: UtilsIntentService,
|
45
|
+
private readonly ecoConfigService: EcoConfigService,
|
46
|
+
) {}
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Executes the fulfill intent process for an intent. It creates the transaction for fulfillment, and posts it
|
50
|
+
* to the chain. It then updates the db model of the intent with the status and receipt.
|
51
|
+
*
|
52
|
+
* @param intentHash the intent hash to fulfill
|
53
|
+
* @returns
|
54
|
+
*/
|
55
|
+
async executeFulfillIntent(intentHash: Hex) {
|
56
|
+
const data = await this.utilsIntentService.getIntentProcessData(intentHash)
|
57
|
+
const { model, solver, err } = data ?? {}
|
58
|
+
if (!data || !model || !solver) {
|
59
|
+
if (err) {
|
60
|
+
throw err
|
61
|
+
}
|
62
|
+
return
|
63
|
+
}
|
64
|
+
// If the intent is already solved, return
|
65
|
+
// Could happen if redis has pending job while this is still executing
|
66
|
+
if (model.status === 'SOLVED') {
|
67
|
+
return
|
68
|
+
}
|
69
|
+
|
70
|
+
const kernelAccountClient = await this.kernelAccountClientService.getClient(solver.chainID)
|
71
|
+
|
72
|
+
// Create transactions for intent targets
|
73
|
+
const targetSolveTxs = this.getTransactionsForTargets(data)
|
74
|
+
|
75
|
+
// Create fulfill tx
|
76
|
+
const fulfillTx = await this.getFulfillIntentTx(solver.inboxAddress, model)
|
77
|
+
|
78
|
+
// Combine all transactions
|
79
|
+
const transactions = [...targetSolveTxs, fulfillTx]
|
80
|
+
|
81
|
+
this.logger.debug(
|
82
|
+
EcoLogMessage.fromDefault({
|
83
|
+
message: `Fulfilling ${this.getFulfillment()} transaction`,
|
84
|
+
properties: {
|
85
|
+
transactions,
|
86
|
+
},
|
87
|
+
}),
|
88
|
+
)
|
89
|
+
|
90
|
+
try {
|
91
|
+
await this.finalFeasibilityCheck(model.intent)
|
92
|
+
|
93
|
+
const transactionHash = await kernelAccountClient.execute(transactions)
|
94
|
+
|
95
|
+
const receipt = await kernelAccountClient.waitForTransactionReceipt({ hash: transactionHash })
|
96
|
+
|
97
|
+
// set the status and receipt for the model
|
98
|
+
model.receipt = receipt as any
|
99
|
+
if (receipt.status === 'reverted') {
|
100
|
+
throw EcoError.FulfillIntentRevertError(receipt)
|
101
|
+
}
|
102
|
+
model.status = 'SOLVED'
|
103
|
+
|
104
|
+
this.logger.debug(
|
105
|
+
EcoLogMessage.fromDefault({
|
106
|
+
message: `Fulfilled transactionHash ${receipt.transactionHash}`,
|
107
|
+
properties: {
|
108
|
+
userOPHash: receipt,
|
109
|
+
destinationChainID: model.intent.route.destination,
|
110
|
+
sourceChainID: model.event.sourceChainID,
|
111
|
+
},
|
112
|
+
}),
|
113
|
+
)
|
114
|
+
} catch (e) {
|
115
|
+
model.status = 'FAILED'
|
116
|
+
model.receipt = model.receipt ? { previous: model.receipt, current: e } : e
|
117
|
+
|
118
|
+
this.logger.error(
|
119
|
+
EcoLogMessage.withError({
|
120
|
+
message: `fulfillIntent: Invalid transaction`,
|
121
|
+
error: EcoError.FulfillIntentBatchError,
|
122
|
+
properties: {
|
123
|
+
model: model,
|
124
|
+
flatExecuteData: transactions,
|
125
|
+
errorPassed: e,
|
126
|
+
},
|
127
|
+
}),
|
128
|
+
)
|
129
|
+
|
130
|
+
// Throw error to retry job
|
131
|
+
throw e
|
132
|
+
} finally {
|
133
|
+
// Update the db model
|
134
|
+
await this.utilsIntentService.updateIntentModel(model)
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* Checks that the intent is feasible for the fulfillment. This
|
140
|
+
* could occur due to changes to the fees/limits of the intent. A failed
|
141
|
+
* intent might retry later when its no longer profitable, etc.
|
142
|
+
* Throws an error if the intent is not feasible.
|
143
|
+
* @param intent the intent to check
|
144
|
+
*/
|
145
|
+
async finalFeasibilityCheck(intent: IntentDataModel) {
|
146
|
+
const { error } = await this.feeService.isRouteFeasible(intent)
|
147
|
+
if (error) {
|
148
|
+
throw error
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Checks if the transaction is feasible for an erc20 token transfer.
|
154
|
+
*
|
155
|
+
* @param tt the transaction target data
|
156
|
+
* @param solver the target solver
|
157
|
+
* @param target the target ERC20 address
|
158
|
+
* @returns
|
159
|
+
*/
|
160
|
+
handleErc20(tt: TransactionTargetData, solver: Solver, target: Hex) {
|
161
|
+
switch (tt.selector) {
|
162
|
+
case getERC20Selector('transfer'):
|
163
|
+
const dstAmount = tt.decodedFunctionData.args?.[1] as bigint
|
164
|
+
// Approve the inbox to spend the amount, inbox contract pulls the funds
|
165
|
+
// then does the transfer call for the target
|
166
|
+
const transferFunctionData = encodeFunctionData({
|
167
|
+
abi: erc20Abi,
|
168
|
+
functionName: 'approve',
|
169
|
+
args: [solver.inboxAddress, dstAmount], //spender, amount
|
170
|
+
})
|
171
|
+
|
172
|
+
return [{ to: target, data: transferFunctionData }]
|
173
|
+
default:
|
174
|
+
return []
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
/**
|
179
|
+
* Returns the transactions for the intent targets
|
180
|
+
* @param intentProcessData
|
181
|
+
* @private
|
182
|
+
*/
|
183
|
+
private getTransactionsForTargets(intentProcessData: IntentProcessData) {
|
184
|
+
const { model, solver } = intentProcessData
|
185
|
+
if (!model || !solver) {
|
186
|
+
return []
|
187
|
+
}
|
188
|
+
|
189
|
+
// Create transactions for intent targets
|
190
|
+
return model.intent.route.calls.flatMap((call) => {
|
191
|
+
const tt = getTransactionTargetData(solver, call)
|
192
|
+
if (tt === null) {
|
193
|
+
this.logger.error(
|
194
|
+
EcoLogMessage.withError({
|
195
|
+
message: `fulfillIntent: Invalid transaction data`,
|
196
|
+
error: EcoError.FulfillIntentNoTransactionError,
|
197
|
+
properties: {
|
198
|
+
model: model,
|
199
|
+
},
|
200
|
+
}),
|
201
|
+
)
|
202
|
+
return []
|
203
|
+
}
|
204
|
+
|
205
|
+
switch (tt.targetConfig.contractType) {
|
206
|
+
case 'erc20':
|
207
|
+
return this.handleErc20(tt, solver, call.target)
|
208
|
+
case 'erc721':
|
209
|
+
case 'erc1155':
|
210
|
+
default:
|
211
|
+
return []
|
212
|
+
}
|
213
|
+
})
|
214
|
+
}
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Returns the fulfill intent data
|
218
|
+
* @param inboxAddress
|
219
|
+
* @param model
|
220
|
+
* @private
|
221
|
+
*/
|
222
|
+
private async getFulfillIntentTx(
|
223
|
+
inboxAddress: Hex,
|
224
|
+
model: IntentSourceModel,
|
225
|
+
): Promise<ExecuteSmartWalletArg> {
|
226
|
+
const claimant = this.ecoConfigService.getEth().claimant
|
227
|
+
const isHyperlane = this.proofService.isHyperlaneProver(model.intent.reward.prover)
|
228
|
+
const functionName: FulfillmentMethod = this.proofService.isStorageProver(
|
229
|
+
model.intent.reward.prover,
|
230
|
+
)
|
231
|
+
? 'fulfillStorage'
|
232
|
+
: this.getFulfillment()
|
233
|
+
|
234
|
+
const args = [
|
235
|
+
model.intent.route,
|
236
|
+
// @ts-expect-error we dynamically set the args
|
237
|
+
model.intent.reward.getHash(),
|
238
|
+
claimant,
|
239
|
+
// @ts-expect-error we dynamically set the args
|
240
|
+
model.intent.getHash().intentHash,
|
241
|
+
]
|
242
|
+
|
243
|
+
if (isHyperlane) {
|
244
|
+
args.push(model.intent.reward.prover)
|
245
|
+
if (functionName === 'fulfillHyperInstantWithRelayer') {
|
246
|
+
args.push('0x0')
|
247
|
+
args.push(zeroAddress)
|
248
|
+
}
|
249
|
+
}
|
250
|
+
let fee = 0n
|
251
|
+
if (isHyperlane && functionName === 'fulfillHyperInstantWithRelayer') {
|
252
|
+
fee = BigInt((await this.getHyperlaneFee(inboxAddress, model)) || '0x0')
|
253
|
+
}
|
254
|
+
|
255
|
+
const fulfillIntentData = encodeFunctionData({
|
256
|
+
abi: InboxAbi,
|
257
|
+
functionName,
|
258
|
+
// @ts-expect-error we dynamically set the args
|
259
|
+
args,
|
260
|
+
})
|
261
|
+
|
262
|
+
return {
|
263
|
+
to: inboxAddress,
|
264
|
+
data: fulfillIntentData,
|
265
|
+
// ...(isHyperlane && fee > 0 && { value: fee }),
|
266
|
+
value: fee,
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
/**
|
271
|
+
* Returns the hyperlane fee
|
272
|
+
* @param prover
|
273
|
+
* @private
|
274
|
+
*/
|
275
|
+
private async getHyperlaneFee(
|
276
|
+
inboxAddress: Hex,
|
277
|
+
model: IntentSourceModel,
|
278
|
+
): Promise<Hex | undefined> {
|
279
|
+
const client = await this.kernelAccountClientService.getClient(
|
280
|
+
Number(model.intent.route.destination),
|
281
|
+
)
|
282
|
+
const encodedMessageBody = encodeAbiParameters(
|
283
|
+
[{ type: 'bytes[]' }, { type: 'address[]' }],
|
284
|
+
[[model.intent.hash], [this.ecoConfigService.getEth().claimant]],
|
285
|
+
)
|
286
|
+
const functionName = 'fetchFee'
|
287
|
+
const args: ContractFunctionArgs<typeof InboxAbi, 'view', typeof functionName> = [
|
288
|
+
model.event.sourceChainID, //_sourceChainID
|
289
|
+
pad(model.intent.reward.prover), //_prover
|
290
|
+
encodedMessageBody, //_messageBody
|
291
|
+
'0x0', //_metadata
|
292
|
+
zeroAddress, //_postDispatchHook
|
293
|
+
]
|
294
|
+
const callData = encodeFunctionData({
|
295
|
+
abi: InboxAbi,
|
296
|
+
functionName,
|
297
|
+
args,
|
298
|
+
})
|
299
|
+
const proverData = await client.call({
|
300
|
+
to: inboxAddress,
|
301
|
+
data: callData,
|
302
|
+
})
|
303
|
+
return proverData.data
|
304
|
+
}
|
305
|
+
|
306
|
+
/**
|
307
|
+
* @returns the fulfillment method
|
308
|
+
*/
|
309
|
+
private getFulfillment(): FulfillmentMethod {
|
310
|
+
switch (this.ecoConfigService.getFulfill().run) {
|
311
|
+
case 'batch':
|
312
|
+
return 'fulfillHyperBatched'
|
313
|
+
case 'single':
|
314
|
+
default:
|
315
|
+
return 'fulfillHyperInstantWithRelayer'
|
316
|
+
}
|
317
|
+
}
|
318
|
+
}
|