@morpho-org/evm-simulation 2.0.2 → 3.0.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.
- package/README.md +22 -30
- package/lib/cjs/errors.d.ts +0 -6
- package/lib/cjs/errors.js +1 -11
- package/lib/cjs/index.d.ts +2 -3
- package/lib/cjs/index.js +3 -6
- package/lib/cjs/simulate/backends/index.d.ts +1 -1
- package/lib/cjs/simulate/backends/index.js +3 -3
- package/lib/cjs/simulate/backends/tenderly-rpc.d.ts +23 -0
- package/lib/cjs/simulate/backends/tenderly-rpc.js +176 -0
- package/lib/cjs/simulate/parsing/transfers.js +3 -1
- package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +6 -10
- package/lib/cjs/simulate/pipeline/execute-simulation.js +11 -18
- package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +6 -5
- package/lib/cjs/simulate/pipeline/resolve-chain.js +10 -7
- package/lib/cjs/simulate/simulate.d.ts +16 -21
- package/lib/cjs/simulate/simulate.js +16 -18
- package/lib/cjs/types.d.ts +22 -29
- package/lib/esm/errors.d.ts +0 -6
- package/lib/esm/errors.js +0 -9
- package/lib/esm/index.d.ts +2 -3
- package/lib/esm/index.js +1 -2
- package/lib/esm/simulate/backends/index.d.ts +1 -1
- package/lib/esm/simulate/backends/index.js +1 -1
- package/lib/esm/simulate/backends/tenderly-rpc.d.ts +23 -0
- package/lib/esm/simulate/backends/tenderly-rpc.js +173 -0
- package/lib/esm/simulate/parsing/transfers.js +3 -1
- package/lib/esm/simulate/pipeline/execute-simulation.d.ts +6 -10
- package/lib/esm/simulate/pipeline/execute-simulation.js +12 -19
- package/lib/esm/simulate/pipeline/resolve-chain.d.ts +6 -5
- package/lib/esm/simulate/pipeline/resolve-chain.js +10 -7
- package/lib/esm/simulate/simulate.d.ts +16 -21
- package/lib/esm/simulate/simulate.js +16 -18
- package/lib/esm/types.d.ts +22 -29
- package/package.json +3 -3
- package/src/index.ts +1 -3
- package/lib/cjs/screen-addresses/index.d.ts +0 -1
- package/lib/cjs/screen-addresses/index.js +0 -5
- package/lib/cjs/screen-addresses/sanctioned-addresses.d.ts +0 -11
- package/lib/cjs/screen-addresses/sanctioned-addresses.js +0 -77
- package/lib/cjs/screen-addresses/screen-addresses.d.ts +0 -50
- package/lib/cjs/screen-addresses/screen-addresses.js +0 -171
- package/lib/cjs/simulate/backends/tenderly-rest.d.ts +0 -20
- package/lib/cjs/simulate/backends/tenderly-rest.js +0 -279
- package/lib/esm/screen-addresses/index.d.ts +0 -1
- package/lib/esm/screen-addresses/index.js +0 -1
- package/lib/esm/screen-addresses/sanctioned-addresses.d.ts +0 -11
- package/lib/esm/screen-addresses/sanctioned-addresses.js +0 -74
- package/lib/esm/screen-addresses/screen-addresses.d.ts +0 -50
- package/lib/esm/screen-addresses/screen-addresses.js +0 -168
- package/lib/esm/simulate/backends/tenderly-rest.d.ts +0 -20
- package/lib/esm/simulate/backends/tenderly-rest.js +0 -276
|
@@ -3,20 +3,19 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
|
|
|
3
3
|
* Simulate a bundle of EVM transactions.
|
|
4
4
|
*
|
|
5
5
|
* Validates input → resolves authorizations into prepended approve txs → runs the bundle
|
|
6
|
-
* through Tenderly
|
|
6
|
+
* through Tenderly RPC (primary) or `eth_simulateV1` (fallback) with a shared timeout
|
|
7
7
|
* budget → parses ERC20/WETH transfers from per-tx logs → asserts no funds are retained
|
|
8
8
|
* by `bundler3` → returns the full result set. The caller reads whichever fields they need:
|
|
9
9
|
*
|
|
10
|
-
* - `transfers`
|
|
11
|
-
* - `simulationTxs` + `transfers` → server-side verification before broadcast
|
|
12
|
-
* both to `screenAddresses` for compliance.
|
|
10
|
+
* - `transfers` → user-facing preview / server-side verification.
|
|
11
|
+
* - `simulationTxs` + `transfers` → server-side verification before broadcast.
|
|
13
12
|
* - `calls[i]` → per-tx raw backend output (`logs`, `status`, `returnData`, `gasUsed`,
|
|
14
13
|
* and Tenderly-only `assetChanges`). Aligned 1:1 with `simulationTxs[i]`.
|
|
15
14
|
* - `transfers[k].txIdx` → index into `simulationTxs` of the tx that emitted the
|
|
16
15
|
* underlying log; consumers map back via `simulationTxs[transfer.txIdx]`.
|
|
17
16
|
*
|
|
18
|
-
* @param config - Backend configuration:
|
|
19
|
-
* logger, and the overall timeout budget.
|
|
17
|
+
* @param config - Backend configuration: per-chain Tenderly RPC and/or `eth_simulateV1`
|
|
18
|
+
* URL, optional logger, and the overall timeout budget.
|
|
20
19
|
* @param params - Per-call simulation input.
|
|
21
20
|
* @param params.chainId - Chain id the bundle targets.
|
|
22
21
|
* @param params.transactions - The bundle's transactions, in execution order. All must share the
|
|
@@ -24,10 +23,6 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
|
|
|
24
23
|
* @param params.authorizations - Optional token authorizations resolved into prepended approve
|
|
25
24
|
* transactions before the main bundle runs.
|
|
26
25
|
* @param params.blockNumber - Optional pinned block number or `BlockTag`. Defaults to `latest`.
|
|
27
|
-
* @param options - Per-call simulation options.
|
|
28
|
-
* @param options.shareable - When `true`, persists the simulation in Tenderly and returns a
|
|
29
|
-
* shareable `tenderlyUrl`. Honored only on the Tenderly backend, not on the `eth_simulateV1`
|
|
30
|
-
* fallback. Defaults to `false`.
|
|
31
26
|
* @throws {SimulationValidationError} for invalid input (mixed senders, bad addresses,
|
|
32
27
|
* empty transactions, malformed authorizations).
|
|
33
28
|
* @throws {UnsupportedChainError} when the chain is not configured for any backend.
|
|
@@ -39,14 +34,21 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
|
|
|
39
34
|
* not match the resolved `simulationTxs` — refusing to map transfers with mismatched
|
|
40
35
|
* per-tx output.
|
|
41
36
|
* @returns A {@link SimulationResult} carrying the resolved `simulationTxs`, per-tx
|
|
42
|
-
* `calls` (aligned 1:1 with `simulationTxs`), parsed `transfers` (each stamped
|
|
43
|
-
* `txIdx`)
|
|
37
|
+
* `calls` (aligned 1:1 with `simulationTxs`), and parsed `transfers` (each stamped
|
|
38
|
+
* with `txIdx`).
|
|
44
39
|
* @example
|
|
45
40
|
* ```ts
|
|
46
41
|
* import { simulate } from "@morpho-org/evm-simulation";
|
|
47
42
|
*
|
|
48
43
|
* const result = await simulate(
|
|
49
|
-
* {
|
|
44
|
+
* {
|
|
45
|
+
* chains: new Map([
|
|
46
|
+
* [1, {
|
|
47
|
+
* tenderlyRpc: { rpcUrl: process.env.TENDERLY_RPC_URL! },
|
|
48
|
+
* simulateV1Url: process.env.MAINNET_RPC_URL,
|
|
49
|
+
* }],
|
|
50
|
+
* ]),
|
|
51
|
+
* },
|
|
50
52
|
* {
|
|
51
53
|
* chainId: 1,
|
|
52
54
|
* transactions: [{ from: user, to: vaultAddress, data: encodedCalldata, value: 0n }],
|
|
@@ -55,11 +57,4 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
|
|
|
55
57
|
* // result satisfies SimulationResult
|
|
56
58
|
* ```
|
|
57
59
|
*/
|
|
58
|
-
export declare function simulate(config: SimulationConfig, params: SimulateParams
|
|
59
|
-
/**
|
|
60
|
-
* When true, persist the simulation in Tenderly and return a shareable
|
|
61
|
-
* `tenderlyUrl` in the result. Only honored when the Tenderly backend runs
|
|
62
|
-
* (not the `eth_simulateV1` fallback). Defaults to `false`.
|
|
63
|
-
*/
|
|
64
|
-
shareable?: boolean;
|
|
65
|
-
}): Promise<SimulationResult>;
|
|
60
|
+
export declare function simulate(config: SimulationConfig, params: SimulateParams): Promise<SimulationResult>;
|
|
@@ -5,20 +5,19 @@ import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, valida
|
|
|
5
5
|
* Simulate a bundle of EVM transactions.
|
|
6
6
|
*
|
|
7
7
|
* Validates input → resolves authorizations into prepended approve txs → runs the bundle
|
|
8
|
-
* through Tenderly
|
|
8
|
+
* through Tenderly RPC (primary) or `eth_simulateV1` (fallback) with a shared timeout
|
|
9
9
|
* budget → parses ERC20/WETH transfers from per-tx logs → asserts no funds are retained
|
|
10
10
|
* by `bundler3` → returns the full result set. The caller reads whichever fields they need:
|
|
11
11
|
*
|
|
12
|
-
* - `transfers`
|
|
13
|
-
* - `simulationTxs` + `transfers` → server-side verification before broadcast
|
|
14
|
-
* both to `screenAddresses` for compliance.
|
|
12
|
+
* - `transfers` → user-facing preview / server-side verification.
|
|
13
|
+
* - `simulationTxs` + `transfers` → server-side verification before broadcast.
|
|
15
14
|
* - `calls[i]` → per-tx raw backend output (`logs`, `status`, `returnData`, `gasUsed`,
|
|
16
15
|
* and Tenderly-only `assetChanges`). Aligned 1:1 with `simulationTxs[i]`.
|
|
17
16
|
* - `transfers[k].txIdx` → index into `simulationTxs` of the tx that emitted the
|
|
18
17
|
* underlying log; consumers map back via `simulationTxs[transfer.txIdx]`.
|
|
19
18
|
*
|
|
20
|
-
* @param config - Backend configuration:
|
|
21
|
-
* logger, and the overall timeout budget.
|
|
19
|
+
* @param config - Backend configuration: per-chain Tenderly RPC and/or `eth_simulateV1`
|
|
20
|
+
* URL, optional logger, and the overall timeout budget.
|
|
22
21
|
* @param params - Per-call simulation input.
|
|
23
22
|
* @param params.chainId - Chain id the bundle targets.
|
|
24
23
|
* @param params.transactions - The bundle's transactions, in execution order. All must share the
|
|
@@ -26,10 +25,6 @@ import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, valida
|
|
|
26
25
|
* @param params.authorizations - Optional token authorizations resolved into prepended approve
|
|
27
26
|
* transactions before the main bundle runs.
|
|
28
27
|
* @param params.blockNumber - Optional pinned block number or `BlockTag`. Defaults to `latest`.
|
|
29
|
-
* @param options - Per-call simulation options.
|
|
30
|
-
* @param options.shareable - When `true`, persists the simulation in Tenderly and returns a
|
|
31
|
-
* shareable `tenderlyUrl`. Honored only on the Tenderly backend, not on the `eth_simulateV1`
|
|
32
|
-
* fallback. Defaults to `false`.
|
|
33
28
|
* @throws {SimulationValidationError} for invalid input (mixed senders, bad addresses,
|
|
34
29
|
* empty transactions, malformed authorizations).
|
|
35
30
|
* @throws {UnsupportedChainError} when the chain is not configured for any backend.
|
|
@@ -41,14 +36,21 @@ import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, valida
|
|
|
41
36
|
* not match the resolved `simulationTxs` — refusing to map transfers with mismatched
|
|
42
37
|
* per-tx output.
|
|
43
38
|
* @returns A {@link SimulationResult} carrying the resolved `simulationTxs`, per-tx
|
|
44
|
-
* `calls` (aligned 1:1 with `simulationTxs`), parsed `transfers` (each stamped
|
|
45
|
-
* `txIdx`)
|
|
39
|
+
* `calls` (aligned 1:1 with `simulationTxs`), and parsed `transfers` (each stamped
|
|
40
|
+
* with `txIdx`).
|
|
46
41
|
* @example
|
|
47
42
|
* ```ts
|
|
48
43
|
* import { simulate } from "@morpho-org/evm-simulation";
|
|
49
44
|
*
|
|
50
45
|
* const result = await simulate(
|
|
51
|
-
* {
|
|
46
|
+
* {
|
|
47
|
+
* chains: new Map([
|
|
48
|
+
* [1, {
|
|
49
|
+
* tenderlyRpc: { rpcUrl: process.env.TENDERLY_RPC_URL! },
|
|
50
|
+
* simulateV1Url: process.env.MAINNET_RPC_URL,
|
|
51
|
+
* }],
|
|
52
|
+
* ]),
|
|
53
|
+
* },
|
|
52
54
|
* {
|
|
53
55
|
* chainId: 1,
|
|
54
56
|
* transactions: [{ from: user, to: vaultAddress, data: encodedCalldata, value: 0n }],
|
|
@@ -57,9 +59,7 @@ import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, valida
|
|
|
57
59
|
* // result satisfies SimulationResult
|
|
58
60
|
* ```
|
|
59
61
|
*/
|
|
60
|
-
|
|
61
|
-
export async function simulate(config, params, options = {}) {
|
|
62
|
-
const { shareable = false } = options;
|
|
62
|
+
export async function simulate(config, params) {
|
|
63
63
|
validateInput(params);
|
|
64
64
|
const simulationTxs = buildSimulationTxs(params);
|
|
65
65
|
const result = await executeSimulation({
|
|
@@ -67,7 +67,6 @@ export async function simulate(config, params, options = {}) {
|
|
|
67
67
|
chainId: params.chainId,
|
|
68
68
|
transactions: simulationTxs,
|
|
69
69
|
blockNumber: params.blockNumber,
|
|
70
|
-
shareable,
|
|
71
70
|
});
|
|
72
71
|
if (result.calls.length !== simulationTxs.length) {
|
|
73
72
|
throw new ExternalServiceError(`Backend returned ${result.calls.length} call result(s) for ${simulationTxs.length} transaction(s) — refusing to map transfers with mismatched lengths`);
|
|
@@ -82,6 +81,5 @@ export async function simulate(config, params, options = {}) {
|
|
|
82
81
|
simulationTxs,
|
|
83
82
|
calls: result.calls,
|
|
84
83
|
transfers,
|
|
85
|
-
tenderlyUrl: result.tenderlyUrl,
|
|
86
84
|
};
|
|
87
85
|
}
|
package/lib/esm/types.d.ts
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
import type { Address, BlockTag, Hex } from "viem";
|
|
2
2
|
/**
|
|
3
|
-
* Credentials
|
|
4
|
-
*
|
|
3
|
+
* Credentials for the Tenderly Node Web3 Gateway. The `rpcUrl` is the
|
|
4
|
+
* chain-specific gateway URL with the access key embedded in the path,
|
|
5
|
+
* e.g. `https://mainnet.gateway.tenderly.co/{ACCESS_KEY}`.
|
|
5
6
|
*/
|
|
6
|
-
export interface
|
|
7
|
-
|
|
8
|
-
apiBaseUrl: string;
|
|
9
|
-
accessToken: string;
|
|
10
|
-
accountSlug: string;
|
|
11
|
-
projectSlug: string;
|
|
12
|
-
/** Chain IDs that Tenderly supports for simulation */
|
|
13
|
-
supportedChainIds: Set<number>;
|
|
7
|
+
export interface TenderlyRpcConfig {
|
|
8
|
+
rpcUrl: string;
|
|
14
9
|
}
|
|
15
10
|
/**
|
|
16
|
-
* Per-chain capabilities.
|
|
17
|
-
*
|
|
11
|
+
* Per-chain backend capabilities. Exactly one configuration shape per chain;
|
|
12
|
+
* the discriminated union enforces that **at least one** of `tenderlyRpc`
|
|
13
|
+
* (primary) or `simulateV1Url` (fallback) is supplied.
|
|
18
14
|
*/
|
|
19
|
-
export
|
|
20
|
-
/**
|
|
15
|
+
export type ChainSimulationConfig = {
|
|
16
|
+
/** Tenderly RPC config — `tenderly_simulateTransaction` / `tenderly_simulateBundle`. */
|
|
17
|
+
tenderlyRpc: TenderlyRpcConfig;
|
|
18
|
+
/** JSON-RPC URL supporting `eth_simulateV1`. Optional when Tenderly is set. */
|
|
21
19
|
simulateV1Url?: string;
|
|
22
|
-
}
|
|
20
|
+
} | {
|
|
21
|
+
tenderlyRpc?: TenderlyRpcConfig;
|
|
22
|
+
simulateV1Url: string;
|
|
23
|
+
};
|
|
23
24
|
/**
|
|
24
25
|
* Top-level configuration for `simulate`.
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* orchestrator throws `UnsupportedChainError`.
|
|
27
|
+
* Every chain entry must declare at least one backend (Tenderly RPC primary,
|
|
28
|
+
* `eth_simulateV1` fallback, or both). Calling `simulate` for a `chainId`
|
|
29
|
+
* missing from `chains` throws `UnsupportedChainError`.
|
|
30
30
|
*/
|
|
31
31
|
export interface SimulationConfig {
|
|
32
|
-
/** Tenderly REST API config. undefined = Tenderly not available. */
|
|
33
|
-
tenderlyRest?: TenderlyRestConfig;
|
|
34
32
|
/** Per-chain simulation capabilities. */
|
|
35
33
|
chains: Map<number, ChainSimulationConfig>;
|
|
36
34
|
logger?: SimulationLogger;
|
|
@@ -92,8 +90,6 @@ export interface Transfer {
|
|
|
92
90
|
* status, returnData/gasUsed, and (Tenderly only) assetChanges per tx.
|
|
93
91
|
* - `transfers[k].txIdx` indexes into `simulationTxs` to attribute each
|
|
94
92
|
* transfer to its emitting transaction.
|
|
95
|
-
* - `tenderlyUrl` is set only when `shareable: true` AND the Tenderly backend
|
|
96
|
-
* ran successfully (not the `eth_simulateV1` fallback).
|
|
97
93
|
*/
|
|
98
94
|
export interface SimulationResult {
|
|
99
95
|
/** The full resolved transaction list (including prepended authorization txs). */
|
|
@@ -106,8 +102,6 @@ export interface SimulationResult {
|
|
|
106
102
|
readonly calls: readonly SimulationCall[];
|
|
107
103
|
/** Parsed ERC-20 / WETH9 transfers from the simulation. */
|
|
108
104
|
readonly transfers: readonly Transfer[];
|
|
109
|
-
/** Shareable Tenderly URL. Present only when `shareable: true` and Tenderly ran (not fallback). */
|
|
110
|
-
readonly tenderlyUrl?: string;
|
|
111
105
|
}
|
|
112
106
|
/** Minimal structured logger the package calls for warnings and info. */
|
|
113
107
|
export interface SimulationLogger {
|
|
@@ -132,11 +126,10 @@ export interface SimulateParams {
|
|
|
132
126
|
*/
|
|
133
127
|
export interface RawSimulationResult {
|
|
134
128
|
calls: RawCall[];
|
|
135
|
-
tenderlyUrl?: string;
|
|
136
129
|
}
|
|
137
130
|
/**
|
|
138
131
|
* Normalized EVM log emitted by a single simulated call. The shape is the
|
|
139
|
-
* common subset both backends (`eth_simulateV1` via viem and Tenderly
|
|
132
|
+
* common subset both backends (`eth_simulateV1` via viem and Tenderly RPC)
|
|
140
133
|
* produce after schema validation. Returned indirectly via
|
|
141
134
|
* `SimulationCall.logs` and consumed by the SDK's transfer parser.
|
|
142
135
|
*/
|
|
@@ -156,7 +149,7 @@ export interface RawCall {
|
|
|
156
149
|
status: boolean;
|
|
157
150
|
returnData: Hex;
|
|
158
151
|
gasUsed: bigint;
|
|
159
|
-
/** Tenderly-only `
|
|
152
|
+
/** Tenderly-only `assetChanges` payload. Absent on `eth_simulateV1`. */
|
|
160
153
|
assetChanges?: unknown;
|
|
161
154
|
}
|
|
162
155
|
/**
|
|
@@ -180,7 +173,7 @@ export interface SimulationCall {
|
|
|
180
173
|
/** Gas used by this call (root frame). */
|
|
181
174
|
readonly gasUsed: bigint;
|
|
182
175
|
/**
|
|
183
|
-
* Tenderly-only `
|
|
176
|
+
* Tenderly-only `assetChanges` payload for this tx. Opaque `unknown` —
|
|
184
177
|
* do not destructure without validation. Absent on `eth_simulateV1`.
|
|
185
178
|
*/
|
|
186
179
|
readonly assetChanges?: unknown;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morpho-org/evm-simulation",
|
|
3
3
|
"description": "EVM simulation engine for Morpho transactions.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"author": "Morpho Association <contact@morpho.org>",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Rubilmax <rmilon@gmail.com>"
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
|
-
"@morpho-org/blue-sdk": "^6.0
|
|
29
|
-
"@morpho-org/morpho-ts": "^2.
|
|
28
|
+
"@morpho-org/blue-sdk": "^6.1.0",
|
|
29
|
+
"@morpho-org/morpho-ts": "^2.6.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"viem": "^2.0.0"
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// Errors (for instanceof checks by consumers)
|
|
4
4
|
export {
|
|
5
|
-
AddressScreeningError,
|
|
6
5
|
BlacklistViolationError,
|
|
7
6
|
ExternalServiceError,
|
|
8
7
|
SimulationPackageError,
|
|
@@ -10,7 +9,6 @@ export {
|
|
|
10
9
|
SimulationValidationError,
|
|
11
10
|
UnsupportedChainError,
|
|
12
11
|
} from "./errors.js";
|
|
13
|
-
export { screenAddresses } from "./screen-addresses/index.js";
|
|
14
12
|
export { simulate } from "./simulate/index.js";
|
|
15
13
|
// Types
|
|
16
14
|
export type {
|
|
@@ -23,6 +21,6 @@ export type {
|
|
|
23
21
|
SimulationLogger,
|
|
24
22
|
SimulationResult,
|
|
25
23
|
SimulationTransaction,
|
|
26
|
-
|
|
24
|
+
TenderlyRpcConfig,
|
|
27
25
|
Transfer,
|
|
28
26
|
} from "./types.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { screenAddresses } from "./screen-addresses.js";
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.screenAddresses = void 0;
|
|
4
|
-
var screen_addresses_js_1 = require("./screen-addresses.js");
|
|
5
|
-
Object.defineProperty(exports, "screenAddresses", { enumerable: true, get: function () { return screen_addresses_js_1.screenAddresses; } });
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static list of sanctioned/compromised addresses.
|
|
3
|
-
* Ported from offchain-services-server/src/functions/screen/blacklist.ts.
|
|
4
|
-
* These addresses are checked before calling the Chainalysis API (fast, no network).
|
|
5
|
-
*
|
|
6
|
-
* **Invariant** (enforced at module load, see below): every entry is a
|
|
7
|
-
* well-formed lowercased EVM address. `screenAddresses` does
|
|
8
|
-
* `sanctionedAddresses.has(addr.toLowerCase())` — a mixed-case entry would
|
|
9
|
-
* never match and would silently bypass the sanctioned check.
|
|
10
|
-
*/
|
|
11
|
-
export declare const sanctionedAddresses: Set<string>;
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sanctionedAddresses = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Static list of sanctioned/compromised addresses.
|
|
6
|
-
* Ported from offchain-services-server/src/functions/screen/blacklist.ts.
|
|
7
|
-
* These addresses are checked before calling the Chainalysis API (fast, no network).
|
|
8
|
-
*
|
|
9
|
-
* **Invariant** (enforced at module load, see below): every entry is a
|
|
10
|
-
* well-formed lowercased EVM address. `screenAddresses` does
|
|
11
|
-
* `sanctionedAddresses.has(addr.toLowerCase())` — a mixed-case entry would
|
|
12
|
-
* never match and would silently bypass the sanctioned check.
|
|
13
|
-
*/
|
|
14
|
-
exports.sanctionedAddresses = new Set([
|
|
15
|
-
// Bybit hack addresses
|
|
16
|
-
"0x47666Fab8bd0Ac7003bce3f5C3585383F09486E2",
|
|
17
|
-
"0xA4B2Fd68593B6F34E51cB9eDB66E71c1B4Ab449e",
|
|
18
|
-
"0x36ed3C0213565530C35115d93A80F9c04d94E4Cb",
|
|
19
|
-
"0x1542368a03ad1f03d96D51B414f4738961Cf4443",
|
|
20
|
-
"0xdD90071D52F20e85c89802e5Dc1eC0A7B6475f92",
|
|
21
|
-
"0x40e98FeEEbaD7Ddb0F0534Ccaa617427eA10187e",
|
|
22
|
-
"0x30a822CDD2782D2B2A12a08526452e885978FA1D",
|
|
23
|
-
"0x6d46bd3AfF100f23C194e5312f93507978a6DC91",
|
|
24
|
-
"0x660BfcEa3A5FAF823e8f8bF57dd558db034dea1d",
|
|
25
|
-
"0x140c9Ab92347734641b1A7c124ffDeE58c20C3E3",
|
|
26
|
-
"0x8c7235e1A6EeF91b980D0FcA083347FBb7EE1806",
|
|
27
|
-
"0xb172F7e99452446f18FF49A71bfEeCf0873003b4",
|
|
28
|
-
"0xCd7eC020121Ead6f99855cbB972dF502dB5bC63a",
|
|
29
|
-
"0x0e8C1E2881F35Ef20343264862A242FB749d6b35",
|
|
30
|
-
"0x2290937A4498C96eFfb87b8371a33D108F8D433f",
|
|
31
|
-
"0x1bb0970508316DC735329752a4581E0a4bAbc6B4",
|
|
32
|
-
"0xf0a16603289eAF35F64077Ba3681af41194a1c09",
|
|
33
|
-
"0x5Af75eAB6BEC227657fA3E749a8BFd55f02e4b1D",
|
|
34
|
-
"0xB4a862A81aBB2f952FcA4C6f5510962e18c7f1A2",
|
|
35
|
-
"0x959c4CA19c4532C97A657D82d97acCBAb70e6fb4",
|
|
36
|
-
"0xB72334cB9D0b614D30C4c60e2bd12fF5Ed03c305",
|
|
37
|
-
"0xFc926659Dd8808f6e3e0a8d61B20B871F3Fa6465",
|
|
38
|
-
"0xfa3FcCCB897079fD83bfBA690E7D47Eb402d6c49",
|
|
39
|
-
"0x51E9d833Ecae4E8D9D8Be17300AEE6D3398C135D",
|
|
40
|
-
"0x1eB27f136BFe7947f80d6ceE3Cf0bfDf92b45e57",
|
|
41
|
-
"0x83Ef5E80faD88288F770152875Ab0bb16641a09E",
|
|
42
|
-
"0x52207Ec7B1b43AA5DB116931a904371ae2C1619e",
|
|
43
|
-
"0x23Db729908137cb60852f2936D2b5c6De0e1c887",
|
|
44
|
-
"0xAF620E6d32B1c67f3396EF5d2F7d7642Dc2e6CE9",
|
|
45
|
-
"0xCd1a4A457cA8b0931c3BF81Df3CFa227ADBdb6E9",
|
|
46
|
-
"0x96244D83DC15d36847C35209bBDc5bdDE9bEc3D8",
|
|
47
|
-
"0x09278b36863bE4cCd3d0c22d643E8062D7a11377",
|
|
48
|
-
"0x3A21F4E6Bbe527D347ca7c157F4233c935779847",
|
|
49
|
-
"0xBC3e5e8C10897a81b63933348f53f2e052F89a7E",
|
|
50
|
-
"0x9271EDdda0F0f2bB7b1A0c712bdF8dbD0A38d1Ab",
|
|
51
|
-
"0x4C198B3B5F3a4b1Aa706daC73D826c2B795ccd67",
|
|
52
|
-
"0x684d4b58Dc32af786BF6D572A792fF7A883428B9",
|
|
53
|
-
"0xD3C611AeD139107DEC2294032da3913BC26507fb",
|
|
54
|
-
"0x9eF42873Ae015AA3da0c4354AeF94a18D2B3407b",
|
|
55
|
-
"0xbdE2Cc5375fa9E0383309A2cA31213f2D6cabcbd",
|
|
56
|
-
"0xe69753Ddfbedbd249E703EB374452E78dae1ae49",
|
|
57
|
-
"0xE9bc552fdFa54b30296d95F147e3e0280FF7f7e6",
|
|
58
|
-
"0xBCA02B395747D62626a65016F2e64A20bd254A39",
|
|
59
|
-
"0xF302572594a68aA8F951faE64ED3aE7DA41c72Be",
|
|
60
|
-
"0x21032176B43d9f7E9410fB37290a78f4fEd6044C",
|
|
61
|
-
"0xD5b58Cf7813c1eDC412367b97876bD400ea5c489",
|
|
62
|
-
"0xA5A023E052243b7cce34Cbd4ba20180e8Dea6Ad6",
|
|
63
|
-
"0x723a7084028421994d4a7829108D63aB44658315",
|
|
64
|
-
"0x1512fcb09463A61862B73ec09B9b354aF1790268",
|
|
65
|
-
"0xEB0bAA3A556586192590CAD296b1e48dF62a8549",
|
|
66
|
-
"0xf03AfB1c6A11A7E370920ad42e6eE735dBedF0b1",
|
|
67
|
-
"0x55CCa2f5eB07907696afe4b9Db5102bcE5feB734",
|
|
68
|
-
].map((a) => a.toLowerCase()));
|
|
69
|
-
// Runtime invariant check at module load. Catches a future edit that adds an
|
|
70
|
-
// entry via `sanctionedAddresses.add(...)` bypassing the `.map(toLowerCase)` or
|
|
71
|
-
// inserts a malformed string. Throwing at load is the loudest signal — the
|
|
72
|
-
// package won't import if the dataset is invalid.
|
|
73
|
-
for (const entry of exports.sanctionedAddresses) {
|
|
74
|
-
if (!/^0x[0-9a-f]{40}$/.test(entry)) {
|
|
75
|
-
throw new Error(`sanctioned-addresses.ts: entry "${entry}" is not a well-formed lowercased EVM address. Every entry must match /^0x[0-9a-f]{40}$/ so lowercase lookup matches.`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { SimulationLogger, SimulationTransaction, Transfer } from "../types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Screens every address that appears in a simulated bundle (transfer senders and recipients,
|
|
4
|
-
* plus tx `from` / `to`) against the static {@link sanctionedAddresses} list and, when
|
|
5
|
-
* configured, the Chainalysis Entity API.
|
|
6
|
-
*
|
|
7
|
-
* Addresses are deduped and lowercased; the zero address is removed. Checks run in order:
|
|
8
|
-
*
|
|
9
|
-
* 1. **Static sanctioned list** — always on, no network. Throws on the first hit.
|
|
10
|
-
* 2. **Chainalysis Entity API** — one lookup per address, lookups parallelized. Only runs when
|
|
11
|
-
* `chainalysisApiKey` is provided.
|
|
12
|
-
* - **Fail-open on transport errors before registration** (network / timeout / non-ok POST):
|
|
13
|
-
* treat the address as clean and log a warn. A Chainalysis outage must not block all
|
|
14
|
-
* simulations.
|
|
15
|
-
* - **Fail-closed on partial registration**: if the registration POST succeeded but the
|
|
16
|
-
* lookup GET fails, treat the address as Severe.
|
|
17
|
-
* - **Fail-closed on schema drift**: an unknown risk tier or unparsed body is treated as
|
|
18
|
-
* Severe so a future stricter tier is not silently downgraded.
|
|
19
|
-
*
|
|
20
|
-
* Intended to be called after {@link simulate}, passing the returned `simulationTxs` and
|
|
21
|
-
* `transfers`.
|
|
22
|
-
*
|
|
23
|
-
* @param params.simulationTxs - The resolved transactions returned by `simulate()`.
|
|
24
|
-
* @param params.transfers - The parsed transfers returned by `simulate()`.
|
|
25
|
-
* @param params.chainalysisApiKey - Optional Chainalysis token. When absent, only the static
|
|
26
|
-
* sanctioned list runs.
|
|
27
|
-
* @param params.signal - Optional `AbortSignal` propagated to every Chainalysis request.
|
|
28
|
-
* @param params.logger - Optional `SimulationLogger` for warn-level diagnostics.
|
|
29
|
-
* @returns Resolves to `void` on a clean screening; throws on any Severe hit.
|
|
30
|
-
* @throws {AddressScreeningError} when any address matches the static sanctioned list or
|
|
31
|
-
* resolves to Severe via Chainalysis.
|
|
32
|
-
* @example
|
|
33
|
-
* ```ts
|
|
34
|
-
* import { screenAddresses, simulate } from "@morpho-org/evm-simulation";
|
|
35
|
-
*
|
|
36
|
-
* const result = await simulate(config, params);
|
|
37
|
-
* await screenAddresses({
|
|
38
|
-
* simulationTxs: result.simulationTxs,
|
|
39
|
-
* transfers: result.transfers,
|
|
40
|
-
* chainalysisApiKey: process.env.CHAINALYSIS_API_KEY,
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export declare function screenAddresses(params: {
|
|
45
|
-
simulationTxs: SimulationTransaction[];
|
|
46
|
-
transfers: Transfer[];
|
|
47
|
-
chainalysisApiKey?: string;
|
|
48
|
-
signal?: AbortSignal;
|
|
49
|
-
logger?: SimulationLogger;
|
|
50
|
-
}): Promise<void>;
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.screenAddresses = screenAddresses;
|
|
4
|
-
const viem_1 = require("viem");
|
|
5
|
-
const zod_1 = require("zod");
|
|
6
|
-
const errors_js_1 = require("../errors.js");
|
|
7
|
-
const sanctioned_addresses_js_1 = require("./sanctioned-addresses.js");
|
|
8
|
-
const CHAINALYSIS_API_URL = "https://api.chainalysis.com/api/risk/v2/entities";
|
|
9
|
-
const CHAINALYSIS_TIMEOUT_MS = 5_000;
|
|
10
|
-
/**
|
|
11
|
-
* Known tiers whose presence means NOT severe. Any other value (unknown future
|
|
12
|
-
* tier, malformed field) is treated as Severe so a stricter tier Chainalysis
|
|
13
|
-
* might introduce later isn't silently downgraded to clean.
|
|
14
|
-
*/
|
|
15
|
-
const NON_SEVERE_TIERS = new Set(["High", "Medium", "Low"]);
|
|
16
|
-
const chainalysisEntitySchema = zod_1.z
|
|
17
|
-
.object({
|
|
18
|
-
risk: zod_1.z.string().nullable(),
|
|
19
|
-
})
|
|
20
|
-
.passthrough();
|
|
21
|
-
/**
|
|
22
|
-
* Screens every address that appears in a simulated bundle (transfer senders and recipients,
|
|
23
|
-
* plus tx `from` / `to`) against the static {@link sanctionedAddresses} list and, when
|
|
24
|
-
* configured, the Chainalysis Entity API.
|
|
25
|
-
*
|
|
26
|
-
* Addresses are deduped and lowercased; the zero address is removed. Checks run in order:
|
|
27
|
-
*
|
|
28
|
-
* 1. **Static sanctioned list** — always on, no network. Throws on the first hit.
|
|
29
|
-
* 2. **Chainalysis Entity API** — one lookup per address, lookups parallelized. Only runs when
|
|
30
|
-
* `chainalysisApiKey` is provided.
|
|
31
|
-
* - **Fail-open on transport errors before registration** (network / timeout / non-ok POST):
|
|
32
|
-
* treat the address as clean and log a warn. A Chainalysis outage must not block all
|
|
33
|
-
* simulations.
|
|
34
|
-
* - **Fail-closed on partial registration**: if the registration POST succeeded but the
|
|
35
|
-
* lookup GET fails, treat the address as Severe.
|
|
36
|
-
* - **Fail-closed on schema drift**: an unknown risk tier or unparsed body is treated as
|
|
37
|
-
* Severe so a future stricter tier is not silently downgraded.
|
|
38
|
-
*
|
|
39
|
-
* Intended to be called after {@link simulate}, passing the returned `simulationTxs` and
|
|
40
|
-
* `transfers`.
|
|
41
|
-
*
|
|
42
|
-
* @param params.simulationTxs - The resolved transactions returned by `simulate()`.
|
|
43
|
-
* @param params.transfers - The parsed transfers returned by `simulate()`.
|
|
44
|
-
* @param params.chainalysisApiKey - Optional Chainalysis token. When absent, only the static
|
|
45
|
-
* sanctioned list runs.
|
|
46
|
-
* @param params.signal - Optional `AbortSignal` propagated to every Chainalysis request.
|
|
47
|
-
* @param params.logger - Optional `SimulationLogger` for warn-level diagnostics.
|
|
48
|
-
* @returns Resolves to `void` on a clean screening; throws on any Severe hit.
|
|
49
|
-
* @throws {AddressScreeningError} when any address matches the static sanctioned list or
|
|
50
|
-
* resolves to Severe via Chainalysis.
|
|
51
|
-
* @example
|
|
52
|
-
* ```ts
|
|
53
|
-
* import { screenAddresses, simulate } from "@morpho-org/evm-simulation";
|
|
54
|
-
*
|
|
55
|
-
* const result = await simulate(config, params);
|
|
56
|
-
* await screenAddresses({
|
|
57
|
-
* simulationTxs: result.simulationTxs,
|
|
58
|
-
* transfers: result.transfers,
|
|
59
|
-
* chainalysisApiKey: process.env.CHAINALYSIS_API_KEY,
|
|
60
|
-
* });
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
async function screenAddresses(params) {
|
|
64
|
-
const { simulationTxs, transfers, chainalysisApiKey, signal, logger } = params;
|
|
65
|
-
const addressesToScreen = new Set();
|
|
66
|
-
for (const transfer of transfers) {
|
|
67
|
-
addressesToScreen.add(transfer.from.toLowerCase());
|
|
68
|
-
addressesToScreen.add(transfer.to.toLowerCase());
|
|
69
|
-
}
|
|
70
|
-
for (const tx of simulationTxs) {
|
|
71
|
-
addressesToScreen.add(tx.from.toLowerCase());
|
|
72
|
-
addressesToScreen.add(tx.to.toLowerCase());
|
|
73
|
-
}
|
|
74
|
-
addressesToScreen.delete(viem_1.zeroAddress);
|
|
75
|
-
// 1. Static sanctioned list — always on, no network.
|
|
76
|
-
const sanctioned = [];
|
|
77
|
-
for (const addr of addressesToScreen) {
|
|
78
|
-
if (sanctioned_addresses_js_1.sanctionedAddresses.has(addr))
|
|
79
|
-
sanctioned.push((0, viem_1.getAddress)(addr));
|
|
80
|
-
}
|
|
81
|
-
if (sanctioned.length > 0) {
|
|
82
|
-
throw new errors_js_1.AddressScreeningError("Transaction involves sanctioned addresses", sanctioned);
|
|
83
|
-
}
|
|
84
|
-
// 2. Chainalysis (optional).
|
|
85
|
-
if (!chainalysisApiKey) {
|
|
86
|
-
logger?.warn("Chainalysis screening skipped: no API key configured");
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const results = await Promise.allSettled([...addressesToScreen].map(async (addr) => {
|
|
90
|
-
const verdict = await screenWithChainalysis(addr, chainalysisApiKey, signal);
|
|
91
|
-
return { addr, verdict };
|
|
92
|
-
}));
|
|
93
|
-
const flagged = [];
|
|
94
|
-
for (const r of results) {
|
|
95
|
-
if (r.status === "fulfilled") {
|
|
96
|
-
if (r.value.verdict === "severe")
|
|
97
|
-
flagged.push((0, viem_1.getAddress)(r.value.addr));
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
// Transport fail-open: registration POST couldn't even complete, so the
|
|
101
|
-
// address was never registered with Chainalysis and the package's
|
|
102
|
-
// fail-open posture applies.
|
|
103
|
-
logger?.warn("Chainalysis screening failed (fail-open)", {
|
|
104
|
-
error: r.reason instanceof Error ? r.reason.message : String(r.reason),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (flagged.length > 0) {
|
|
109
|
-
throw new errors_js_1.AddressScreeningError("Transaction involves addresses flagged as Severe risk", flagged);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Run the Chainalysis Entity flow for one address.
|
|
114
|
-
*
|
|
115
|
-
* Returns `'severe'` or `'clean'`. Throws only if the initial registration POST
|
|
116
|
-
* fails before any server-side side effect — then the outer handler treats the
|
|
117
|
-
* rejection as fail-open. After registration succeeds, every error path returns
|
|
118
|
-
* `'severe'` so the check fails closed.
|
|
119
|
-
*
|
|
120
|
-
* A SINGLE deadline covers both the POST and the GET, keeping the per-address
|
|
121
|
-
* wall-time budget at `CHAINALYSIS_TIMEOUT_MS` (5 s) rather than doubling it.
|
|
122
|
-
*
|
|
123
|
-
* @internal
|
|
124
|
-
*/
|
|
125
|
-
// biome-ignore lint/complexity/useMaxParams: TODO refactor to ≤2 params
|
|
126
|
-
async function screenWithChainalysis(address, apiKey, parentSignal) {
|
|
127
|
-
const headers = {
|
|
128
|
-
"Content-Type": "application/json",
|
|
129
|
-
token: apiKey,
|
|
130
|
-
};
|
|
131
|
-
const deadline = AbortSignal.timeout(CHAINALYSIS_TIMEOUT_MS);
|
|
132
|
-
const signal = parentSignal
|
|
133
|
-
? AbortSignal.any([parentSignal, deadline])
|
|
134
|
-
: deadline;
|
|
135
|
-
// POST to register. Pre-registration failure → fail-open via thrown Error.
|
|
136
|
-
const registration = await fetch(CHAINALYSIS_API_URL, {
|
|
137
|
-
headers,
|
|
138
|
-
method: "POST",
|
|
139
|
-
body: JSON.stringify({ address }),
|
|
140
|
-
signal,
|
|
141
|
-
});
|
|
142
|
-
if (!registration.ok) {
|
|
143
|
-
throw new Error(`Chainalysis registration failed: ${registration.status} ${registration.statusText}`);
|
|
144
|
-
}
|
|
145
|
-
// From here on we've already told Chainalysis about the address — fail closed.
|
|
146
|
-
let response;
|
|
147
|
-
try {
|
|
148
|
-
response = await fetch(`${CHAINALYSIS_API_URL}/${encodeURIComponent(address)}`, {
|
|
149
|
-
method: "GET",
|
|
150
|
-
headers,
|
|
151
|
-
signal,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return "severe";
|
|
156
|
-
}
|
|
157
|
-
if (!response.ok)
|
|
158
|
-
return "severe";
|
|
159
|
-
let parsed;
|
|
160
|
-
try {
|
|
161
|
-
parsed = chainalysisEntitySchema.parse(await response.json());
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
return "severe";
|
|
165
|
-
}
|
|
166
|
-
if (parsed.risk === null)
|
|
167
|
-
return "clean";
|
|
168
|
-
if (NON_SEVERE_TIERS.has(parsed.risk))
|
|
169
|
-
return "clean";
|
|
170
|
-
return "severe";
|
|
171
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type BlockTag } from "viem";
|
|
2
|
-
import type { RawSimulationResult, SimulationLogger, SimulationTransaction, TenderlyRestConfig } from "../../types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Simulate transactions using Tenderly REST API.
|
|
5
|
-
*
|
|
6
|
-
* @param shareable - true persists the simulation in Tenderly and returns a shareable URL;
|
|
7
|
-
* false runs ephemerally with no URL. Translates to Tenderly's `save` wire field
|
|
8
|
-
* (`save_if_fails` is also set to `shareable` — so failed preview simulations are
|
|
9
|
-
* ALSO persisted and shareable. Callers that preview sensitive calldata should be
|
|
10
|
-
* aware this persists in Tenderly's dashboard regardless of success/failure).
|
|
11
|
-
*/
|
|
12
|
-
export declare function simulateTenderlyRest(params: {
|
|
13
|
-
config: TenderlyRestConfig;
|
|
14
|
-
chainId: number;
|
|
15
|
-
transactions: SimulationTransaction[];
|
|
16
|
-
blockNumber?: bigint | BlockTag;
|
|
17
|
-
shareable: boolean;
|
|
18
|
-
signal?: AbortSignal;
|
|
19
|
-
logger?: SimulationLogger;
|
|
20
|
-
}): Promise<RawSimulationResult>;
|