@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
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- EVM transaction simulation engine for Morpho — bundle execution preview,
6
- transfer parsing, and compliance screening.
5
+ EVM transaction simulation engine for Morpho — bundle execution preview
6
+ and transfer parsing.
7
7
 
8
8
  ## Installation
9
9
 
@@ -22,38 +22,29 @@ Add to the consuming app's `package.json`:
22
22
  ```ts
23
23
  import {
24
24
  simulate,
25
- screenAddresses,
26
25
  type SimulationConfig,
27
26
  SimulationRevertedError
28
27
  } from "@morpho-org/evm-simulation";
29
28
 
30
29
  const config: SimulationConfig = {
31
- tenderlyRest: {
32
- apiBaseUrl: "https://api.tenderly.co",
33
- accessToken: process.env.TENDERLY_ACCESS_TOKEN!,
34
- accountSlug: "my-account",
35
- projectSlug: "my-project",
36
- supportedChainIds: new Set([1]),
37
- },
38
- chains: new Map([[1, { simulateV1Url: process.env.MAINNET_RPC_URL }]]),
30
+ chains: new Map([
31
+ [
32
+ 1,
33
+ {
34
+ tenderlyRpc: { rpcUrl: process.env.TENDERLY_RPC_URL! },
35
+ simulateV1Url: process.env.MAINNET_RPC_URL,
36
+ },
37
+ ],
38
+ ]),
39
39
  timeoutMs: 5000,
40
40
  };
41
41
 
42
42
  try {
43
- // Pass { shareable: true } when you want a shareable Tenderly URL (user-facing preview).
44
- // Omit it for server-side verification — the same function, same return shape.
45
- const { transfers, simulationTxs, tenderlyUrl } = await simulate(
46
- config,
47
- {
48
- chainId: 1,
49
- transactions: [{ from: user, to: vault, data: encodedDeposit }],
50
- authorizations: [{ type: "signature", token: usdc, spender: vault }],
51
- },
52
- { shareable: true }
53
- );
54
-
55
- // For server-side flows, pipe into screenAddresses:
56
- await screenAddresses({ simulationTxs, transfers });
43
+ const { transfers, simulationTxs } = await simulate(config, {
44
+ chainId: 1,
45
+ transactions: [{ from: user, to: vault, data: encodedDeposit }],
46
+ authorizations: [{ type: "signature", token: usdc, spender: vault }],
47
+ });
57
48
  } catch (err) {
58
49
  if (err instanceof SimulationRevertedError) {
59
50
  // show err.reason to the user
@@ -62,20 +53,21 @@ try {
62
53
  }
63
54
  ```
64
55
 
56
+ Each chain entry must declare at least one backend — `tenderlyRpc` (primary), `simulateV1Url` (fallback), or both. The type system enforces this.
57
+
65
58
  ### API surface
66
59
 
67
60
  All symbols below are re-exported from the package root.
68
61
 
69
- - `simulate(config, params, options?)` — run a bundle through the simulation pipeline. Pass `{ shareable: true }` for a shareable Tenderly URL; omit for server-side verification.
70
- - `screenAddresses({ simulationTxs, transfers, chainalysisApiKey? })` — static sanctioned-set + optional Chainalysis screening.
71
- - Config types: `SimulationConfig`, `TenderlyRestConfig`, `ChainSimulationConfig`, `SimulationLogger`.
62
+ - `simulate(config, params)` — run a bundle through the simulation pipeline.
63
+ - Config types: `SimulationConfig`, `TenderlyRpcConfig`, `ChainSimulationConfig`, `SimulationLogger`.
72
64
  - Data types: `SimulationTransaction`, `SimulationAuthorization`, `Transfer`, `SimulateParams`, `SimulationResult`.
73
- - Errors: `SimulationPackageError` (abstract base — `instanceof` it to catch any package error), `SimulationRevertedError`, `BlacklistViolationError`, `AddressScreeningError`, `ExternalServiceError`, `SimulationValidationError`, `UnsupportedChainError`.
65
+ - Errors: `SimulationPackageError` (abstract base — `instanceof` it to catch any package error), `SimulationRevertedError`, `BlacklistViolationError`, `ExternalServiceError`, `SimulationValidationError`, `UnsupportedChainError`.
74
66
 
75
67
  ### Deeper docs
76
68
 
77
69
  See [`CLAUDE.md`](./CLAUDE.md) in this directory for the execution flow diagram,
78
- backend tradeoffs, authorizations model, screening design, error-handling table,
70
+ backend tradeoffs, authorizations model, error-handling table,
79
71
  and recipes for adding a chain or a new backend.
80
72
 
81
73
  ## Development
@@ -25,12 +25,6 @@ export declare class BlacklistViolationError extends SimulationPackageError {
25
25
  readonly code = "BLACKLIST_ERROR";
26
26
  constructor(message: string, assetChanges?: RetainedAsset[] | undefined);
27
27
  }
28
- /** Sanctioned or compromised address detected. Never bypassable. */
29
- export declare class AddressScreeningError extends SimulationPackageError {
30
- readonly addresses: string[];
31
- readonly code = "SCREENING_ERROR";
32
- constructor(message: string, addresses: string[]);
33
- }
34
28
  /** Tenderly or RPC service is down. Bypassable — user can proceed. */
35
29
  export declare class ExternalServiceError extends SimulationPackageError {
36
30
  readonly code = "EXTERNAL_SERVICE_ERROR";
package/lib/cjs/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UnsupportedChainError = exports.SimulationValidationError = exports.ExternalServiceError = exports.AddressScreeningError = exports.BlacklistViolationError = exports.SimulationRevertedError = exports.SimulationPackageError = void 0;
3
+ exports.UnsupportedChainError = exports.SimulationValidationError = exports.ExternalServiceError = exports.BlacklistViolationError = exports.SimulationRevertedError = exports.SimulationPackageError = void 0;
4
4
  /**
5
5
  * Base class for every error this package throws. Transport-agnostic — no HTTP status codes.
6
6
  * Consumers pattern-match with `instanceof` on the concrete subclass.
@@ -34,16 +34,6 @@ class BlacklistViolationError extends SimulationPackageError {
34
34
  }
35
35
  }
36
36
  exports.BlacklistViolationError = BlacklistViolationError;
37
- /** Sanctioned or compromised address detected. Never bypassable. */
38
- class AddressScreeningError extends SimulationPackageError {
39
- addresses;
40
- code = "SCREENING_ERROR";
41
- constructor(message, addresses) {
42
- super(message);
43
- this.addresses = addresses;
44
- }
45
- }
46
- exports.AddressScreeningError = AddressScreeningError;
47
37
  /** Tenderly or RPC service is down. Bypassable — user can proceed. */
48
38
  class ExternalServiceError extends SimulationPackageError {
49
39
  code = "EXTERNAL_SERVICE_ERROR";
@@ -1,4 +1,3 @@
1
- export { AddressScreeningError, BlacklistViolationError, ExternalServiceError, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnsupportedChainError, } from "./errors.js";
2
- export { screenAddresses } from "./screen-addresses/index.js";
1
+ export { BlacklistViolationError, ExternalServiceError, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnsupportedChainError, } from "./errors.js";
3
2
  export { simulate } from "./simulate/index.js";
4
- export type { ChainSimulationConfig, RawLog, SimulateParams, SimulationAuthorization, SimulationCall, SimulationConfig, SimulationLogger, SimulationResult, SimulationTransaction, TenderlyRestConfig, Transfer, } from "./types.js";
3
+ export type { ChainSimulationConfig, RawLog, SimulateParams, SimulationAuthorization, SimulationCall, SimulationConfig, SimulationLogger, SimulationResult, SimulationTransaction, TenderlyRpcConfig, Transfer, } from "./types.js";
package/lib/cjs/index.js CHANGED
@@ -1,17 +1,14 @@
1
1
  "use strict";
2
2
  // Public fns (via feature-folder barrels)
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.simulate = exports.screenAddresses = exports.UnsupportedChainError = exports.SimulationValidationError = exports.SimulationRevertedError = exports.SimulationPackageError = exports.ExternalServiceError = exports.BlacklistViolationError = exports.AddressScreeningError = void 0;
4
+ exports.simulate = exports.UnsupportedChainError = exports.SimulationValidationError = exports.SimulationRevertedError = exports.SimulationPackageError = exports.ExternalServiceError = exports.BlacklistViolationError = void 0;
5
5
  // Errors (for instanceof checks by consumers)
6
6
  var errors_js_1 = require("./errors.js");
7
- Object.defineProperty(exports, "AddressScreeningError", { enumerable: true, get: function () { return errors_js_1.AddressScreeningError; } });
8
7
  Object.defineProperty(exports, "BlacklistViolationError", { enumerable: true, get: function () { return errors_js_1.BlacklistViolationError; } });
9
8
  Object.defineProperty(exports, "ExternalServiceError", { enumerable: true, get: function () { return errors_js_1.ExternalServiceError; } });
10
9
  Object.defineProperty(exports, "SimulationPackageError", { enumerable: true, get: function () { return errors_js_1.SimulationPackageError; } });
11
10
  Object.defineProperty(exports, "SimulationRevertedError", { enumerable: true, get: function () { return errors_js_1.SimulationRevertedError; } });
12
11
  Object.defineProperty(exports, "SimulationValidationError", { enumerable: true, get: function () { return errors_js_1.SimulationValidationError; } });
13
12
  Object.defineProperty(exports, "UnsupportedChainError", { enumerable: true, get: function () { return errors_js_1.UnsupportedChainError; } });
14
- var index_js_1 = require("./screen-addresses/index.js");
15
- Object.defineProperty(exports, "screenAddresses", { enumerable: true, get: function () { return index_js_1.screenAddresses; } });
16
- var index_js_2 = require("./simulate/index.js");
17
- Object.defineProperty(exports, "simulate", { enumerable: true, get: function () { return index_js_2.simulate; } });
13
+ var index_js_1 = require("./simulate/index.js");
14
+ Object.defineProperty(exports, "simulate", { enumerable: true, get: function () { return index_js_1.simulate; } });
@@ -1,2 +1,2 @@
1
1
  export { simulateV1 } from "./eth-simulate-v1.js";
2
- export { simulateTenderlyRest } from "./tenderly-rest.js";
2
+ export { simulateTenderlyRpc } from "./tenderly-rpc.js";
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.simulateTenderlyRest = exports.simulateV1 = void 0;
3
+ exports.simulateTenderlyRpc = exports.simulateV1 = void 0;
4
4
  var eth_simulate_v1_js_1 = require("./eth-simulate-v1.js");
5
5
  Object.defineProperty(exports, "simulateV1", { enumerable: true, get: function () { return eth_simulate_v1_js_1.simulateV1; } });
6
- var tenderly_rest_js_1 = require("./tenderly-rest.js");
7
- Object.defineProperty(exports, "simulateTenderlyRest", { enumerable: true, get: function () { return tenderly_rest_js_1.simulateTenderlyRest; } });
6
+ var tenderly_rpc_js_1 = require("./tenderly-rpc.js");
7
+ Object.defineProperty(exports, "simulateTenderlyRpc", { enumerable: true, get: function () { return tenderly_rpc_js_1.simulateTenderlyRpc; } });
@@ -0,0 +1,23 @@
1
+ import { type BlockTag } from "viem";
2
+ import type { RawSimulationResult, SimulationTransaction, TenderlyRpcConfig } from "../../types.js";
3
+ /**
4
+ * Simulate one or more transactions via Tenderly's Node Web3 Gateway.
5
+ * Dispatches to `tenderly_simulateTransaction` for a single call and
6
+ * `tenderly_simulateBundle` for a multi-call bundle.
7
+ *
8
+ * @param params.config - Tenderly RPC config (URL with access key embedded).
9
+ * @param params.transactions - Bundle to simulate, in execution order.
10
+ * @param params.blockNumber - Optional pinned block number or `BlockTag`. Defaults to `latest`.
11
+ * @param params.signal - Optional `AbortSignal` for cancellation / timeout.
12
+ * @returns A {@link RawSimulationResult} with one `RawCall` per input transaction.
13
+ * @throws {SimulationValidationError} when `transactions` is empty.
14
+ * @throws {SimulationRevertedError} when any simulated tx reports `status: false`.
15
+ * @throws {ExternalServiceError} on non-2xx response, JSON-RPC envelope error,
16
+ * schema-validation failure, or fetch-layer failure.
17
+ */
18
+ export declare function simulateTenderlyRpc(params: {
19
+ config: TenderlyRpcConfig;
20
+ transactions: SimulationTransaction[];
21
+ blockNumber?: bigint | BlockTag;
22
+ signal?: AbortSignal;
23
+ }): Promise<RawSimulationResult>;
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.simulateTenderlyRpc = simulateTenderlyRpc;
4
+ const viem_1 = require("viem");
5
+ const zod_1 = require("zod");
6
+ const errors_js_1 = require("../../errors.js");
7
+ const addressSchema = zod_1.z.custom((val) => typeof val === "string" && (0, viem_1.isAddress)(val));
8
+ const hexSchema = zod_1.z.custom((val) => typeof val === "string" && (0, viem_1.isHex)(val));
9
+ const traceFrameSchema = zod_1.z
10
+ .object({
11
+ output: hexSchema.optional(),
12
+ error: zod_1.z.string().optional(),
13
+ errorReason: zod_1.z.string().optional(),
14
+ })
15
+ .passthrough();
16
+ const logSchema = zod_1.z
17
+ .object({
18
+ raw: zod_1.z
19
+ .object({
20
+ address: addressSchema,
21
+ topics: zod_1.z.array(hexSchema).optional(),
22
+ data: hexSchema.optional(),
23
+ })
24
+ .passthrough()
25
+ .optional(),
26
+ })
27
+ .passthrough();
28
+ const simResultSchema = zod_1.z
29
+ .object({
30
+ status: zod_1.z.boolean(),
31
+ gasUsed: hexSchema,
32
+ logs: zod_1.z.array(logSchema).optional(),
33
+ trace: zod_1.z.array(traceFrameSchema).optional(),
34
+ assetChanges: zod_1.z.unknown().optional(),
35
+ error: zod_1.z.string().optional(),
36
+ errorMessage: zod_1.z.string().optional(),
37
+ })
38
+ .passthrough();
39
+ const rpcErrorSchema = zod_1.z
40
+ .object({
41
+ code: zod_1.z.number().optional(),
42
+ message: zod_1.z.string(),
43
+ data: zod_1.z.unknown().optional(),
44
+ })
45
+ .passthrough();
46
+ function rpcEnvelope(result) {
47
+ return zod_1.z
48
+ .object({ result: result.optional(), error: rpcErrorSchema.optional() })
49
+ .passthrough();
50
+ }
51
+ const singleEnvelope = rpcEnvelope(simResultSchema);
52
+ const bundleEnvelope = rpcEnvelope(zod_1.z.array(simResultSchema).min(1));
53
+ /**
54
+ * Simulate one or more transactions via Tenderly's Node Web3 Gateway.
55
+ * Dispatches to `tenderly_simulateTransaction` for a single call and
56
+ * `tenderly_simulateBundle` for a multi-call bundle.
57
+ *
58
+ * @param params.config - Tenderly RPC config (URL with access key embedded).
59
+ * @param params.transactions - Bundle to simulate, in execution order.
60
+ * @param params.blockNumber - Optional pinned block number or `BlockTag`. Defaults to `latest`.
61
+ * @param params.signal - Optional `AbortSignal` for cancellation / timeout.
62
+ * @returns A {@link RawSimulationResult} with one `RawCall` per input transaction.
63
+ * @throws {SimulationValidationError} when `transactions` is empty.
64
+ * @throws {SimulationRevertedError} when any simulated tx reports `status: false`.
65
+ * @throws {ExternalServiceError} on non-2xx response, JSON-RPC envelope error,
66
+ * schema-validation failure, or fetch-layer failure.
67
+ */
68
+ async function simulateTenderlyRpc(params) {
69
+ const { config, transactions, blockNumber, signal } = params;
70
+ const firstTx = transactions[0];
71
+ if (!firstTx) {
72
+ throw new errors_js_1.SimulationValidationError("At least one transaction is required", []);
73
+ }
74
+ const block = encodeBlock(blockNumber);
75
+ // Inflate sender ETH balance to avoid false "insufficient funds for gas"
76
+ // reverts on wallets low on native gas token — mirrors simulateV1.
77
+ const stateOverrides = buildStateOverrides(firstTx.from);
78
+ try {
79
+ if (transactions.length === 1) {
80
+ const json = await rpcRequest({
81
+ rpcUrl: config.rpcUrl,
82
+ method: "tenderly_simulateTransaction",
83
+ params: [buildCall(firstTx), block, stateOverrides],
84
+ signal,
85
+ });
86
+ const result = unwrapResult(singleEnvelope.parse(json));
87
+ return { calls: [toRawCall(result)] };
88
+ }
89
+ const json = await rpcRequest({
90
+ rpcUrl: config.rpcUrl,
91
+ method: "tenderly_simulateBundle",
92
+ params: [transactions.map(buildCall), block, stateOverrides],
93
+ signal,
94
+ });
95
+ const result = unwrapResult(bundleEnvelope.parse(json));
96
+ return { calls: result.map(toRawCall) };
97
+ }
98
+ catch (error) {
99
+ if (error instanceof errors_js_1.SimulationRevertedError)
100
+ throw error;
101
+ if (error instanceof errors_js_1.ExternalServiceError)
102
+ throw error;
103
+ throw new errors_js_1.ExternalServiceError(`Tenderly RPC error: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
104
+ }
105
+ }
106
+ async function rpcRequest(params) {
107
+ const { rpcUrl, method, params: rpcParams, signal } = params;
108
+ const response = await fetch(rpcUrl, {
109
+ method: "POST",
110
+ headers: { "Content-Type": "application/json" },
111
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params: rpcParams }),
112
+ signal,
113
+ });
114
+ if (!response.ok) {
115
+ throw new errors_js_1.ExternalServiceError(`Tenderly RPC returned ${response.status}: ${response.statusText}`);
116
+ }
117
+ return await response.json();
118
+ }
119
+ function unwrapResult(envelope) {
120
+ if (envelope.error) {
121
+ throw new errors_js_1.ExternalServiceError(`Tenderly RPC error: ${envelope.error.message}`);
122
+ }
123
+ if (envelope.result === undefined) {
124
+ throw new errors_js_1.ExternalServiceError("Tenderly RPC returned no result");
125
+ }
126
+ return envelope.result;
127
+ }
128
+ function buildCall(tx) {
129
+ return {
130
+ from: tx.from,
131
+ to: tx.to,
132
+ input: tx.data,
133
+ data: tx.data,
134
+ value: (0, viem_1.numberToHex)(tx.value ?? 0n),
135
+ };
136
+ }
137
+ function buildStateOverrides(sender) {
138
+ return { [sender]: { balance: (0, viem_1.numberToHex)(viem_1.maxUint256) } };
139
+ }
140
+ function encodeBlock(blockNumber) {
141
+ if (blockNumber === undefined)
142
+ return "latest";
143
+ return typeof blockNumber === "bigint"
144
+ ? (0, viem_1.numberToHex)(blockNumber)
145
+ : blockNumber;
146
+ }
147
+ function toRawCall(data) {
148
+ if (data.status !== true) {
149
+ // Tenderly RPC surfaces the revert reason on the trace frame; the
150
+ // top-level fields are checked as a defensive fallback.
151
+ const traceError = data.trace?.find((f) => f.errorReason || f.error);
152
+ const message = traceError?.errorReason ||
153
+ traceError?.error ||
154
+ data.errorMessage ||
155
+ data.error ||
156
+ "Transaction simulation reverted";
157
+ throw new errors_js_1.SimulationRevertedError(message, data);
158
+ }
159
+ const logs = [];
160
+ for (const log of data.logs ?? []) {
161
+ if (log.raw) {
162
+ logs.push({
163
+ address: log.raw.address,
164
+ topics: log.raw.topics ?? [],
165
+ data: log.raw.data ?? "0x",
166
+ });
167
+ }
168
+ }
169
+ return {
170
+ logs,
171
+ status: true,
172
+ returnData: data.trace?.[0]?.output ?? "0x",
173
+ gasUsed: BigInt(data.gasUsed),
174
+ assetChanges: data.assetChanges,
175
+ };
176
+ }
@@ -145,7 +145,9 @@ function parseTransfers(calls, logger) {
145
145
  catch (error) {
146
146
  // Safety net for anything that slips past the length checks above
147
147
  // (e.g., viem checksum throws on non-hex input). Loud enough to diagnose.
148
- warnMalformed(logger, log, error instanceof Error ? error.message : String(error));
148
+ warnMalformed(logger, log,
149
+ /* v8 ignore next: parser paths throw Error instances for valid RawLog shapes. */
150
+ error instanceof Error ? error.message : String(error));
149
151
  }
150
152
  }
151
153
  }
@@ -5,21 +5,17 @@ import type { RawSimulationResult, SimulationConfig, SimulationTransaction } fro
5
5
  *
6
6
  * Dispatches the transaction bundle to the available backend with a shared timeout
7
7
  * budget:
8
- * - If Tenderly is configured for the chain, it gets `TENDERLY_BUDGET_RATIO` (60%) of
9
- * the timeout. On `ExternalServiceError`, falls back to `eth_simulateV1` with
10
- * `max(remaining, FALLBACK_MIN_BUDGET_MS)` so the fallback still has a viable
11
- * window when Tenderly eats its full slice. `SimulationRevertedError` (contract
12
- * revert) propagates immediately without retry — a revert is a property of the
13
- * bundle, not the backend.
8
+ * - If a Tenderly RPC URL is configured for the chain, it gets
9
+ * `TENDERLY_BUDGET_RATIO` (60%) of the timeout. On `ExternalServiceError`,
10
+ * falls back to `eth_simulateV1` with `max(remaining, FALLBACK_MIN_BUDGET_MS)`
11
+ * so the fallback still has a viable window when Tenderly eats its full slice.
12
+ * `SimulationRevertedError` (contract revert) propagates immediately without
13
+ * retry — a revert is a property of the bundle, not the backend.
14
14
  * - If only `eth_simulateV1` is configured, it gets the full timeout.
15
- *
16
- * `shareable` is Tenderly-only: when true, the backend persists the simulation and
17
- * returns a shareable URL. `eth_simulateV1` ignores it (no persistence concept).
18
15
  */
19
16
  export declare function executeSimulation(params: {
20
17
  config: SimulationConfig;
21
18
  chainId: number;
22
19
  transactions: SimulationTransaction[];
23
20
  blockNumber?: bigint | BlockTag;
24
- shareable: boolean;
25
21
  }): Promise<RawSimulationResult>;
@@ -23,33 +23,27 @@ const FALLBACK_MIN_BUDGET_MS = 1500;
23
23
  *
24
24
  * Dispatches the transaction bundle to the available backend with a shared timeout
25
25
  * budget:
26
- * - If Tenderly is configured for the chain, it gets `TENDERLY_BUDGET_RATIO` (60%) of
27
- * the timeout. On `ExternalServiceError`, falls back to `eth_simulateV1` with
28
- * `max(remaining, FALLBACK_MIN_BUDGET_MS)` so the fallback still has a viable
29
- * window when Tenderly eats its full slice. `SimulationRevertedError` (contract
30
- * revert) propagates immediately without retry — a revert is a property of the
31
- * bundle, not the backend.
26
+ * - If a Tenderly RPC URL is configured for the chain, it gets
27
+ * `TENDERLY_BUDGET_RATIO` (60%) of the timeout. On `ExternalServiceError`,
28
+ * falls back to `eth_simulateV1` with `max(remaining, FALLBACK_MIN_BUDGET_MS)`
29
+ * so the fallback still has a viable window when Tenderly eats its full slice.
30
+ * `SimulationRevertedError` (contract revert) propagates immediately without
31
+ * retry — a revert is a property of the bundle, not the backend.
32
32
  * - If only `eth_simulateV1` is configured, it gets the full timeout.
33
- *
34
- * `shareable` is Tenderly-only: when true, the backend persists the simulation and
35
- * returns a shareable URL. `eth_simulateV1` ignores it (no persistence concept).
36
33
  */
37
34
  async function executeSimulation(params) {
38
- const { config, chainId, transactions, blockNumber, shareable } = params;
35
+ const { config, chainId, transactions, blockNumber } = params;
39
36
  const chain = (0, resolve_chain_js_1.resolveChain)(config, chainId);
40
37
  const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
41
38
  const deadline = Date.now() + timeoutMs;
42
- if (chain.tenderlySupported && config.tenderlyRest) {
39
+ if (chain.tenderlyRpc) {
43
40
  const tenderlyTimeout = Math.floor(timeoutMs * TENDERLY_BUDGET_RATIO);
44
41
  try {
45
- return await (0, index_js_1.simulateTenderlyRest)({
46
- config: config.tenderlyRest,
47
- chainId,
42
+ return await (0, index_js_1.simulateTenderlyRpc)({
43
+ config: chain.tenderlyRpc,
48
44
  transactions,
49
45
  blockNumber,
50
- shareable,
51
46
  signal: AbortSignal.timeout(tenderlyTimeout),
52
- logger: config.logger,
53
47
  });
54
48
  }
55
49
  catch (error) {
@@ -62,8 +56,6 @@ async function executeSimulation(params) {
62
56
  });
63
57
  if (!chain.simulateV1Url)
64
58
  throw error;
65
- // Give the fallback a reasonable minimum even if Tenderly consumed the
66
- // whole budget — otherwise slow-Tenderly == total outage.
67
59
  const fallbackBudget = Math.max(deadline - Date.now(), FALLBACK_MIN_BUDGET_MS);
68
60
  return await (0, index_js_1.simulateV1)({
69
61
  rpcUrl: chain.simulateV1Url,
@@ -74,6 +66,7 @@ async function executeSimulation(params) {
74
66
  });
75
67
  }
76
68
  }
69
+ /* v8 ignore next: resolveChain rejects this state before executeSimulation reaches the fallback path. */
77
70
  if (!chain.simulateV1Url) {
78
71
  throw new errors_js_1.UnsupportedChainError(chainId);
79
72
  }
@@ -1,14 +1,15 @@
1
- import type { SimulationConfig } from "../../types.js";
1
+ import type { SimulationConfig, TenderlyRpcConfig } from "../../types.js";
2
2
  interface ChainCapability {
3
- tenderlySupported: boolean;
3
+ tenderlyRpc?: TenderlyRpcConfig;
4
4
  simulateV1Url?: string;
5
5
  }
6
6
  /**
7
7
  * Stage 2 of the simulate() pipeline.
8
8
  *
9
- * Determines which backend(s) are available for a chainId. Throws
10
- * `UnsupportedChainError` when neither Tenderly nor `eth_simulateV1` is reachable
11
- * for the requested chain.
9
+ * Looks up the per-chain `ChainSimulationConfig` and exposes whichever
10
+ * backends were configured. Throws `UnsupportedChainError` when the chain is
11
+ * absent from the map or has neither backend wired (defensive — the type
12
+ * already enforces at least one at construction).
12
13
  */
13
14
  export declare function resolveChain(config: SimulationConfig, chainId: number): ChainCapability;
14
15
  export {};
@@ -5,15 +5,18 @@ const errors_js_1 = require("../../errors.js");
5
5
  /**
6
6
  * Stage 2 of the simulate() pipeline.
7
7
  *
8
- * Determines which backend(s) are available for a chainId. Throws
9
- * `UnsupportedChainError` when neither Tenderly nor `eth_simulateV1` is reachable
10
- * for the requested chain.
8
+ * Looks up the per-chain `ChainSimulationConfig` and exposes whichever
9
+ * backends were configured. Throws `UnsupportedChainError` when the chain is
10
+ * absent from the map or has neither backend wired (defensive — the type
11
+ * already enforces at least one at construction).
11
12
  */
12
13
  function resolveChain(config, chainId) {
13
- const tenderlySupported = config.tenderlyRest?.supportedChainIds.has(chainId) ?? false;
14
- const simulateV1Url = config.chains.get(chainId)?.simulateV1Url;
15
- if (!tenderlySupported && !simulateV1Url) {
14
+ const entry = config.chains.get(chainId);
15
+ if (!entry || (!entry.tenderlyRpc && !entry.simulateV1Url)) {
16
16
  throw new errors_js_1.UnsupportedChainError(chainId);
17
17
  }
18
- return { tenderlySupported, simulateV1Url };
18
+ return {
19
+ tenderlyRpc: entry.tenderlyRpc,
20
+ simulateV1Url: entry.simulateV1Url,
21
+ };
19
22
  }
@@ -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>;