@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.
Files changed (51) hide show
  1. package/README.md +22 -30
  2. package/lib/cjs/errors.d.ts +0 -6
  3. package/lib/cjs/errors.js +1 -11
  4. package/lib/cjs/index.d.ts +2 -3
  5. package/lib/cjs/index.js +3 -6
  6. package/lib/cjs/simulate/backends/index.d.ts +1 -1
  7. package/lib/cjs/simulate/backends/index.js +3 -3
  8. package/lib/cjs/simulate/backends/tenderly-rpc.d.ts +23 -0
  9. package/lib/cjs/simulate/backends/tenderly-rpc.js +176 -0
  10. package/lib/cjs/simulate/parsing/transfers.js +3 -1
  11. package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +6 -10
  12. package/lib/cjs/simulate/pipeline/execute-simulation.js +11 -18
  13. package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +6 -5
  14. package/lib/cjs/simulate/pipeline/resolve-chain.js +10 -7
  15. package/lib/cjs/simulate/simulate.d.ts +16 -21
  16. package/lib/cjs/simulate/simulate.js +16 -18
  17. package/lib/cjs/types.d.ts +22 -29
  18. package/lib/esm/errors.d.ts +0 -6
  19. package/lib/esm/errors.js +0 -9
  20. package/lib/esm/index.d.ts +2 -3
  21. package/lib/esm/index.js +1 -2
  22. package/lib/esm/simulate/backends/index.d.ts +1 -1
  23. package/lib/esm/simulate/backends/index.js +1 -1
  24. package/lib/esm/simulate/backends/tenderly-rpc.d.ts +23 -0
  25. package/lib/esm/simulate/backends/tenderly-rpc.js +173 -0
  26. package/lib/esm/simulate/parsing/transfers.js +3 -1
  27. package/lib/esm/simulate/pipeline/execute-simulation.d.ts +6 -10
  28. package/lib/esm/simulate/pipeline/execute-simulation.js +12 -19
  29. package/lib/esm/simulate/pipeline/resolve-chain.d.ts +6 -5
  30. package/lib/esm/simulate/pipeline/resolve-chain.js +10 -7
  31. package/lib/esm/simulate/simulate.d.ts +16 -21
  32. package/lib/esm/simulate/simulate.js +16 -18
  33. package/lib/esm/types.d.ts +22 -29
  34. package/package.json +3 -3
  35. package/src/index.ts +1 -3
  36. package/lib/cjs/screen-addresses/index.d.ts +0 -1
  37. package/lib/cjs/screen-addresses/index.js +0 -5
  38. package/lib/cjs/screen-addresses/sanctioned-addresses.d.ts +0 -11
  39. package/lib/cjs/screen-addresses/sanctioned-addresses.js +0 -77
  40. package/lib/cjs/screen-addresses/screen-addresses.d.ts +0 -50
  41. package/lib/cjs/screen-addresses/screen-addresses.js +0 -171
  42. package/lib/cjs/simulate/backends/tenderly-rest.d.ts +0 -20
  43. package/lib/cjs/simulate/backends/tenderly-rest.js +0 -279
  44. package/lib/esm/screen-addresses/index.d.ts +0 -1
  45. package/lib/esm/screen-addresses/index.js +0 -1
  46. package/lib/esm/screen-addresses/sanctioned-addresses.d.ts +0 -11
  47. package/lib/esm/screen-addresses/sanctioned-addresses.js +0 -74
  48. package/lib/esm/screen-addresses/screen-addresses.d.ts +0 -50
  49. package/lib/esm/screen-addresses/screen-addresses.js +0 -168
  50. package/lib/esm/simulate/backends/tenderly-rest.d.ts +0 -20
  51. 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 REST (primary) or `eth_simulateV1` (fallback) with a shared timeout
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` + `tenderlyUrl` → user-facing preview.
11
- * - `simulationTxs` + `transfers` → server-side verification before broadcast, then pass
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: Tenderly credentials, per-chain RPC URLs, optional
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 with
43
- * `txIdx`), and the optional `tenderlyUrl`.
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
- * { chains: new Map([[1, { simulateV1Url: rpcUrl }]]) },
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, options?: {
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 REST (primary) or `eth_simulateV1` (fallback) with a shared timeout
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` + `tenderlyUrl` → user-facing preview.
13
- * - `simulationTxs` + `transfers` → server-side verification before broadcast, then pass
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: Tenderly credentials, per-chain RPC URLs, optional
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 with
45
- * `txIdx`), and the optional `tenderlyUrl`.
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
- * { chains: new Map([[1, { simulateV1Url: rpcUrl }]]) },
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
- // biome-ignore lint/complexity/useMaxParams: TODO refactor to ≤2 params
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
  }
@@ -1,36 +1,34 @@
1
1
  import type { Address, BlockTag, Hex } from "viem";
2
2
  /**
3
- * Credentials and routing for the Tenderly REST simulation backend.
4
- * Omit from `SimulationConfig.tenderlyRest` to disable the Tenderly path entirely.
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 TenderlyRestConfig {
7
- /** e.g. "https://api.tenderly.co" */
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. Stored as a `Map` value in
17
- * `SimulationConfig.chains`, keyed by chain ID.
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 interface ChainSimulationConfig {
20
- /** JSON-RPC URL supporting `eth_simulateV1`. undefined = not available for this chain. */
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
- * At least one backend must be reachable for a given chainId — either Tenderly
27
- * (via `tenderlyRest` with that chainId in `TenderlyRestConfig.supportedChainIds`)
28
- * or `ChainSimulationConfig.simulateV1Url` in the `chains` map. Otherwise the
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 REST)
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 `asset_changes` payload. Absent on `eth_simulateV1`. */
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 `asset_changes` payload for this tx. Opaque `unknown` —
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": "2.0.2",
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.1",
29
- "@morpho-org/morpho-ts": "^2.5.3"
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
- TenderlyRestConfig,
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>;