@morpho-org/evm-simulation 4.1.10 → 4.2.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,7 +19,7 @@ interface RetainedAsset {
19
19
  token: string | undefined;
20
20
  netRetained: string;
21
21
  }
22
- /** Funds would flow to restricted bundler3 or bundles contract addresses. Never bypassable. */
22
+ /** Funds would flow to restricted standalone bundles contract addresses. Never bypassable. */
23
23
  export declare class BlacklistViolationError extends SimulationPackageError {
24
24
  readonly assetChanges?: RetainedAsset[] | undefined;
25
25
  readonly code = "BLACKLIST_ERROR";
package/lib/cjs/errors.js CHANGED
@@ -24,7 +24,7 @@ class SimulationRevertedError extends SimulationPackageError {
24
24
  }
25
25
  }
26
26
  exports.SimulationRevertedError = SimulationRevertedError;
27
- /** Funds would flow to restricted bundler3 or bundles contract addresses. Never bypassable. */
27
+ /** Funds would flow to restricted standalone bundles contract addresses. Never bypassable. */
28
28
  class BlacklistViolationError extends SimulationPackageError {
29
29
  assetChanges;
30
30
  code = "BLACKLIST_ERROR";
@@ -13,7 +13,7 @@ export interface AssetChangeEntry {
13
13
  * any real token address. `eth_simulateV1` synthesizes native moves from the
14
14
  * sentinel `0xeee…eee`, and Tenderly may echo it — in checksummed or any other
15
15
  * case — as an asset change's `contractAddress`. Native ETH is keyed by the exact
16
- * `ethAddress` constant here and in the bundler-retention guard, so a checksummed
16
+ * `ethAddress` constant here and in the bundles-retention guard, so a checksummed
17
17
  * sentinel would land on a separate map key and silently escape retention checks.
18
18
  * This is the single source of truth for token normalization shared by the
19
19
  * transfer-log parser and the Tenderly asset-change mapper.
@@ -8,7 +8,7 @@ const viem_1 = require("viem");
8
8
  * any real token address. `eth_simulateV1` synthesizes native moves from the
9
9
  * sentinel `0xeee…eee`, and Tenderly may echo it — in checksummed or any other
10
10
  * case — as an asset change's `contractAddress`. Native ETH is keyed by the exact
11
- * `ethAddress` constant here and in the bundler-retention guard, so a checksummed
11
+ * `ethAddress` constant here and in the bundles-retention guard, so a checksummed
12
12
  * sentinel would land on a separate map key and silently escape retention checks.
13
13
  * This is the single source of truth for token normalization shared by the
14
14
  * transfer-log parser and the Tenderly asset-change mapper.
@@ -36,13 +36,17 @@ export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d
36
36
  * atomically inside a single call frame. A **non-canonical wrapped-native**
37
37
  * that splits these emissions across two txs in the same bundle would leave
38
38
  * a phantom zero-address `Transfer` in the parsed output, which can be summed
39
- * by `assertNoBundlerRetention` and produce a false `BlacklistViolationError`.
39
+ * by `assertNoBundlesRetention` and produce a false `BlacklistViolationError`.
40
40
  * When its zero-address `Transfer` misses same-tx dedup, the parser emits a
41
41
  * `warn` so the assumption break is observable before it reaches retention.
42
42
  * None of `morpho-sdk`'s currently supported chains require cross-tx dedup;
43
43
  * the assumption is rechecked when onboarding a new chain (see
44
44
  * `evm-simulation/CLAUDE.md`).
45
45
  *
46
+ * **Hex casing.** JSON-RPC `DATA` is case-insensitive, so `topics` and `data`
47
+ * are lowercased before signature dispatch and pair matching; a backend that
48
+ * echoes mixed-case hex cannot drop a transfer from the parsed output.
49
+ *
46
50
  * **Failure mode:** on a per-log parse failure (malformed topic length,
47
51
  * non-hex data), the log is skipped and a `warn` is emitted via the logger.
48
52
  *
@@ -45,13 +45,17 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
45
45
  * atomically inside a single call frame. A **non-canonical wrapped-native**
46
46
  * that splits these emissions across two txs in the same bundle would leave
47
47
  * a phantom zero-address `Transfer` in the parsed output, which can be summed
48
- * by `assertNoBundlerRetention` and produce a false `BlacklistViolationError`.
48
+ * by `assertNoBundlesRetention` and produce a false `BlacklistViolationError`.
49
49
  * When its zero-address `Transfer` misses same-tx dedup, the parser emits a
50
50
  * `warn` so the assumption break is observable before it reaches retention.
51
51
  * None of `morpho-sdk`'s currently supported chains require cross-tx dedup;
52
52
  * the assumption is rechecked when onboarding a new chain (see
53
53
  * `evm-simulation/CLAUDE.md`).
54
54
  *
55
+ * **Hex casing.** JSON-RPC `DATA` is case-insensitive, so `topics` and `data`
56
+ * are lowercased before signature dispatch and pair matching; a backend that
57
+ * echoes mixed-case hex cannot drop a transfer from the parsed output.
58
+ *
55
59
  * **Failure mode:** on a per-log parse failure (malformed topic length,
56
60
  * non-hex data), the log is skipped and a `warn` is emitted via the logger.
57
61
  *
@@ -68,11 +72,17 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
68
72
  function parseTransfers(calls, options = {}) {
69
73
  const transfers = [];
70
74
  const { logger, wNative } = options;
71
- const wnativeShapedTokens = collectWnativeShapedTokens(calls);
75
+ // Lowercase `topics`/`data` so signature dispatch and pair matching are case-insensitive.
76
+ const normalizedCalls = calls.map((call) => call.logs.map((log) => ({
77
+ ...log,
78
+ topics: log.topics.map((topic) => topic.toLowerCase()),
79
+ data: log.data.toLowerCase(),
80
+ })));
81
+ const wnativeShapedTokens = collectWnativeShapedTokens(normalizedCalls);
72
82
  const acceptsWnativeEvent = (address) => wNative === undefined ||
73
83
  (wNative !== null && (0, viem_1.isAddressEqual)(address, wNative));
74
- for (let txIdx = 0; txIdx < calls.length; txIdx++) {
75
- const logs = calls[txIdx].logs;
84
+ for (let txIdx = 0; txIdx < normalizedCalls.length; txIdx++) {
85
+ const logs = normalizedCalls[txIdx];
76
86
  for (const log of logs) {
77
87
  try {
78
88
  const topic0 = log.topics[0];
@@ -195,8 +205,8 @@ function warnMalformed(logger, log, reason) {
195
205
  /** Collect contracts that emit WETH9-shaped events anywhere in the bundle. */
196
206
  function collectWnativeShapedTokens(calls) {
197
207
  const tokens = new Set();
198
- for (const call of calls) {
199
- for (const log of call.logs) {
208
+ for (const logs of calls) {
209
+ for (const log of logs) {
200
210
  const topic0 = log.topics[0];
201
211
  if (topic0 === exports.WITHDRAWAL_TOPIC || topic0 === exports.DEPOSIT_TOPIC) {
202
212
  tokens.add(log.address.toLowerCase());
@@ -1,22 +1,23 @@
1
1
  import type { AccountAssetChanges, SimulationLogger, Transfer } from "../../types.js";
2
- interface AssertNoBundlerRetentionParams {
2
+ interface AssertNoBundlesRetentionParams {
3
3
  chainId: number;
4
4
  transfers: Transfer[];
5
5
  assetChanges: readonly AccountAssetChanges[];
6
6
  logger?: SimulationLogger;
7
7
  }
8
8
  /**
9
- * Assert that no value is retained by restricted contract addresses — the
10
- * `bundler3` executor and adapters, plus the standalone `bundles` periphery
11
- * contracts (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
9
+ * Assert that no value is retained by standalone `bundles` periphery contracts
10
+ * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`) or by
11
+ * `MidnightBundlesV1` (`midnightBundles`).
12
12
  *
13
13
  * Uses net flow (inbound minus outbound) per (restricted address, token) pair.
14
14
  * These contracts legitimately receive tokens as an intermediary (user →
15
- * bundler → vault), so gross inbound would fire false positives. We only block
16
- * positive net flow above `DUST_THRESHOLD` — value stuck in a restricted
17
- * address. Negative net flow means the simulated bundle sweeps a pre-existing or
18
- * state-overridden balance. That is useful telemetry, but not current-bundle
19
- * retention, so it is warned instead of raising a blacklist violation.
15
+ * bundles contract → vault), so gross inbound would fire false positives. We
16
+ * only block positive net flow above `DUST_THRESHOLD` — value stuck in a
17
+ * restricted address. Negative net flow means the simulated bundle sweeps a
18
+ * pre-existing or state-overridden balance. That is useful telemetry, but not
19
+ * current-bundle retention, so it is warned instead of raising a blacklist
20
+ * violation.
20
21
  *
21
22
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
22
23
  * from parsed `transfers` (log-derived, identical across backends). Native ETH
@@ -25,19 +26,19 @@ interface AssertNoBundlerRetentionParams {
25
26
  * Native ETH is therefore read from `assetChanges`, the cross-backend source of
26
27
  * truth for native value: Tenderly derives it from its trace, and
27
28
  * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
28
- * this, native ETH stuck in bundler3 would pass the guard undetected on the
29
- * Tenderly path (Cantina finding 1440).
29
+ * this, native ETH stuck in a bundles contract would pass the guard undetected
30
+ * on the Tenderly path (Cantina finding 1440).
30
31
  *
31
32
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
32
33
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
33
34
  * `assetChanges` derived from it — native transfer logs are only used as a
34
- * fallback for bundler addresses that carry no native `assetChanges` entry.
35
+ * fallback for bundles addresses that carry no native `assetChanges` entry.
35
36
  *
36
37
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
37
38
  * package's public API (only re-exported from the internal pipeline barrel).
38
- * @param params.chainId - Chain whose blue-sdk `bundler3` and `bundles`
39
- * registries define the restricted address set. Chains cataloging neither are
40
- * skipped (a `logger.warn` records the skip).
39
+ * @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundles`
40
+ * registry entries define the restricted address set. Chains cataloging
41
+ * neither are skipped (a `logger.warn` records the skip).
41
42
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
42
43
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
43
44
  * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
@@ -47,7 +48,8 @@ interface AssertNoBundlerRetentionParams {
47
48
  * @returns Nothing. Returns silently when no restricted contract retains value
48
49
  * above `DUST_THRESHOLD`; otherwise throws.
49
50
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
50
- * `bundler3` or `bundles` contract exceeds `DUST_THRESHOLD` for any token.
51
+ * `bundles` or `midnightBundles` contract exceeds `DUST_THRESHOLD` for any
52
+ * token.
51
53
  */
52
- export declare function assertNoBundlerRetention(params: AssertNoBundlerRetentionParams): void;
54
+ export declare function assertNoBundlesRetention(params: AssertNoBundlesRetentionParams): void;
53
55
  export {};
@@ -1,32 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertNoBundlerRetention = assertNoBundlerRetention;
3
+ exports.assertNoBundlesRetention = assertNoBundlesRetention;
4
4
  const blue_sdk_1 = require("@morpho-org/blue-sdk");
5
5
  const morpho_ts_1 = require("@morpho-org/morpho-ts");
6
6
  const viem_1 = require("viem");
7
7
  const errors_js_1 = require("../../errors.js");
8
8
  /**
9
- * Dust tolerance for bundler retention, in raw token units.
9
+ * Dust tolerance for standalone bundles retention, in raw token units.
10
10
  *
11
11
  * 100 units is negligible for 18-decimal tokens (~1e-16). For low-decimal
12
12
  * tokens (e.g., 2-decimal stablecoins) this represents ~1.00 token — worth
13
13
  * keeping in mind. The threshold is intentionally token-agnostic: its purpose
14
- * is to absorb rounding noise from bundler3 routing, not to define an
15
- * acceptable retention amount.
14
+ * is to absorb routing rounding noise, not to define an acceptable retention
15
+ * amount.
16
16
  */
17
17
  const DUST_THRESHOLD = 100n;
18
18
  /**
19
- * Assert that no value is retained by restricted contract addresses — the
20
- * `bundler3` executor and adapters, plus the standalone `bundles` periphery
21
- * contracts (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
19
+ * Assert that no value is retained by standalone `bundles` periphery contracts
20
+ * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`) or by
21
+ * `MidnightBundlesV1` (`midnightBundles`).
22
22
  *
23
23
  * Uses net flow (inbound minus outbound) per (restricted address, token) pair.
24
24
  * These contracts legitimately receive tokens as an intermediary (user →
25
- * bundler → vault), so gross inbound would fire false positives. We only block
26
- * positive net flow above `DUST_THRESHOLD` — value stuck in a restricted
27
- * address. Negative net flow means the simulated bundle sweeps a pre-existing or
28
- * state-overridden balance. That is useful telemetry, but not current-bundle
29
- * retention, so it is warned instead of raising a blacklist violation.
25
+ * bundles contract → vault), so gross inbound would fire false positives. We
26
+ * only block positive net flow above `DUST_THRESHOLD` — value stuck in a
27
+ * restricted address. Negative net flow means the simulated bundle sweeps a
28
+ * pre-existing or state-overridden balance. That is useful telemetry, but not
29
+ * current-bundle retention, so it is warned instead of raising a blacklist
30
+ * violation.
30
31
  *
31
32
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
32
33
  * from parsed `transfers` (log-derived, identical across backends). Native ETH
@@ -35,19 +36,19 @@ const DUST_THRESHOLD = 100n;
35
36
  * Native ETH is therefore read from `assetChanges`, the cross-backend source of
36
37
  * truth for native value: Tenderly derives it from its trace, and
37
38
  * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
38
- * this, native ETH stuck in bundler3 would pass the guard undetected on the
39
- * Tenderly path (Cantina finding 1440).
39
+ * this, native ETH stuck in a bundles contract would pass the guard undetected
40
+ * on the Tenderly path (Cantina finding 1440).
40
41
  *
41
42
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
42
43
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
43
44
  * `assetChanges` derived from it — native transfer logs are only used as a
44
- * fallback for bundler addresses that carry no native `assetChanges` entry.
45
+ * fallback for bundles addresses that carry no native `assetChanges` entry.
45
46
  *
46
47
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
47
48
  * package's public API (only re-exported from the internal pipeline barrel).
48
- * @param params.chainId - Chain whose blue-sdk `bundler3` and `bundles`
49
- * registries define the restricted address set. Chains cataloging neither are
50
- * skipped (a `logger.warn` records the skip).
49
+ * @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundles`
50
+ * registry entries define the restricted address set. Chains cataloging
51
+ * neither are skipped (a `logger.warn` records the skip).
51
52
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
52
53
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
53
54
  * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
@@ -57,15 +58,13 @@ const DUST_THRESHOLD = 100n;
57
58
  * @returns Nothing. Returns silently when no restricted contract retains value
58
59
  * above `DUST_THRESHOLD`; otherwise throws.
59
60
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
60
- * `bundler3` or `bundles` contract exceeds `DUST_THRESHOLD` for any token.
61
+ * `bundles` or `midnightBundles` contract exceeds `DUST_THRESHOLD` for any
62
+ * token.
61
63
  */
62
- function assertNoBundlerRetention(params) {
64
+ function assertNoBundlesRetention(params) {
63
65
  const { chainId, transfers, assetChanges, logger } = params;
64
- // Restricted address set: the full `bundler3` sub-registry (executor + every
65
- // adapter) plus the standalone `bundles` periphery contracts
66
- // (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`). Both families are
67
- // transient intermediaries that route user value and must never retain it, so
68
- // both are guarded identically.
66
+ // Standalone bundles contracts and `MidnightBundlesV1` are transient
67
+ // intermediaries that route user value and must never retain it.
69
68
  let addresses;
70
69
  try {
71
70
  addresses = (0, blue_sdk_1.getChainAddresses)(chainId);
@@ -82,18 +81,17 @@ function assertNoBundlerRetention(params) {
82
81
  throw error;
83
82
  }
84
83
  const restrictedAddresses = new Set();
85
- if (addresses.bundler3) {
86
- for (const addr of Object.values(addresses.bundler3).filter(morpho_ts_1.isDefined))
87
- restrictedAddresses.add((0, viem_1.getAddress)(addr));
88
- }
89
84
  if (addresses.bundles) {
90
85
  for (const addr of Object.values(addresses.bundles).filter(morpho_ts_1.isDefined))
91
86
  restrictedAddresses.add((0, viem_1.getAddress)(addr));
92
87
  }
88
+ if (addresses.midnightBundles) {
89
+ restrictedAddresses.add((0, viem_1.getAddress)(addresses.midnightBundles));
90
+ }
93
91
  if (restrictedAddresses.size === 0) {
94
- // blue-sdk knows the chain but cataloged neither bundler3 nor bundles for
95
- // it. Treat the same as UnsupportedChainIdError — retention check skipped.
96
- logger?.warn("Chain known to blue-sdk but has no bundler3 or bundles config, retention check skipped", { chainId });
92
+ // blue-sdk knows the chain but cataloged no restricted intermediary for it.
93
+ // Treat the same as UnsupportedChainIdError — retention check skipped.
94
+ logger?.warn("Chain known to blue-sdk but has no restricted intermediary config, retention check skipped", { chainId });
97
95
  return;
98
96
  }
99
97
  // Map keyed by (address, token) → structured entry. Avoids string parse-back.
@@ -110,8 +108,8 @@ function assertNoBundlerRetention(params) {
110
108
  }
111
109
  };
112
110
  // Native ETH from `assetChanges` (authoritative, cross-backend). Track which
113
- // bundlers already have a native entry here so the transfer-log pass below
114
- // does not re-add the same native move on `eth_simulateV1`.
111
+ // restricted contracts already have a native entry here so the transfer-log
112
+ // pass below does not re-add the same native move on `eth_simulateV1`.
115
113
  const nativeFromAssetChanges = new Set();
116
114
  for (const { account, changes } of assetChanges) {
117
115
  if (!restrictedAddresses.has(account))
@@ -124,9 +122,9 @@ function assertNoBundlerRetention(params) {
124
122
  }
125
123
  }
126
124
  // ERC20 / WETH9 retention from transfer logs. Native ETH transfers (the
127
- // `eth_simulateV1` synthetic sentinel) only backfill a bundler endpoint that
128
- // is absent from `nativeFromAssetChanges`, so native is never summed on top
129
- // of the `assetChanges` value already recorded for that address.
125
+ // `eth_simulateV1` synthetic sentinel) only backfill a restricted endpoint
126
+ // that is absent from `nativeFromAssetChanges`, so native is never summed on
127
+ // top of the `assetChanges` value already recorded for that address.
130
128
  const usesAssetChangeNative = (t, addr) => t.token === viem_1.ethAddress && nativeFromAssetChanges.has(addr.toLowerCase());
131
129
  for (const t of transfers) {
132
130
  if (restrictedAddresses.has(t.to) && !usesAssetChangeNative(t, t.to))
@@ -137,7 +135,7 @@ function assertNoBundlerRetention(params) {
137
135
  const entries = [...flow.values()];
138
136
  const drained = entries.filter((e) => e.netChange < -DUST_THRESHOLD);
139
137
  if (drained.length > 0) {
140
- logger?.warn("Simulation detected pre-existing balance being swept from a restricted bundler or bundles contract", {
138
+ logger?.warn("Simulation detected pre-existing balance being swept from a restricted bundles contract", {
141
139
  changes: drained.map((e) => ({
142
140
  address: e.address,
143
141
  token: e.token,
@@ -147,7 +145,7 @@ function assertNoBundlerRetention(params) {
147
145
  }
148
146
  const retained = entries.filter((e) => e.netChange > DUST_THRESHOLD);
149
147
  if (retained.length > 0) {
150
- throw new errors_js_1.BlacklistViolationError("Simulation detected asset transfers retained by restricted bundler or bundles contracts", retained.map((e) => ({
148
+ throw new errors_js_1.BlacklistViolationError("Simulation detected asset transfers retained by restricted bundles contracts", retained.map((e) => ({
151
149
  address: e.address,
152
150
  token: e.token,
153
151
  netRetained: e.netChange.toString(),
@@ -1,4 +1,4 @@
1
1
  export { buildSimulationTxs } from "./build-simulation-txs.js";
2
- export { assertNoBundlerRetention } from "./bundler-retention.js";
2
+ export { assertNoBundlesRetention } from "./bundles-retention.js";
3
3
  export { executeSimulation } from "./execute-simulation.js";
4
4
  export { validateInput } from "./validate-input.js";
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateInput = exports.executeSimulation = exports.assertNoBundlerRetention = exports.buildSimulationTxs = void 0;
3
+ exports.validateInput = exports.executeSimulation = exports.assertNoBundlesRetention = exports.buildSimulationTxs = void 0;
4
4
  var build_simulation_txs_js_1 = require("./build-simulation-txs.js");
5
5
  Object.defineProperty(exports, "buildSimulationTxs", { enumerable: true, get: function () { return build_simulation_txs_js_1.buildSimulationTxs; } });
6
- var bundler_retention_js_1 = require("./bundler-retention.js");
7
- Object.defineProperty(exports, "assertNoBundlerRetention", { enumerable: true, get: function () { return bundler_retention_js_1.assertNoBundlerRetention; } });
6
+ var bundles_retention_js_1 = require("./bundles-retention.js");
7
+ Object.defineProperty(exports, "assertNoBundlesRetention", { enumerable: true, get: function () { return bundles_retention_js_1.assertNoBundlesRetention; } });
8
8
  var execute_simulation_js_1 = require("./execute-simulation.js");
9
9
  Object.defineProperty(exports, "executeSimulation", { enumerable: true, get: function () { return execute_simulation_js_1.executeSimulation; } });
10
10
  var validate_input_js_1 = require("./validate-input.js");
@@ -7,7 +7,7 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
7
7
  * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
8
8
  * events to the registered wrapped-native token, rejecting them on known tokenless chains,
9
9
  * and retaining signature-based parsing for unknown chains → asserts no funds are retained
10
- * by `bundler3` or the standalone `bundles` periphery contracts → returns the full result
10
+ * by standalone `bundles` periphery contracts → returns the full result
11
11
  * set. The caller reads whichever fields they need:
12
12
  *
13
13
  * - `transfers` → user-facing preview / server-side verification.
@@ -34,8 +34,8 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
34
34
  * @throws {UnsupportedChainError} when the chain is not configured for any backend.
35
35
  * @throws {SimulationRevertedError} when the bundle reverts on either backend.
36
36
  * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
37
- * the dust threshold by a restricted `bundler3` address or a `bundles` periphery
38
- * contract (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1). Never bypassable.
37
+ * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
38
+ * VaultBundlesV1, BlueBundlesV1). Never bypassable.
39
39
  * @throws {ExternalServiceError} (a) when both backends are unavailable within the
40
40
  * timeout budget, or (b) when a backend returns a `calls` array whose length does
41
41
  * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
@@ -13,7 +13,7 @@ const index_js_2 = require("./pipeline/index.js");
13
13
  * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
14
14
  * events to the registered wrapped-native token, rejecting them on known tokenless chains,
15
15
  * and retaining signature-based parsing for unknown chains → asserts no funds are retained
16
- * by `bundler3` or the standalone `bundles` periphery contracts → returns the full result
16
+ * by standalone `bundles` periphery contracts → returns the full result
17
17
  * set. The caller reads whichever fields they need:
18
18
  *
19
19
  * - `transfers` → user-facing preview / server-side verification.
@@ -40,8 +40,8 @@ const index_js_2 = require("./pipeline/index.js");
40
40
  * @throws {UnsupportedChainError} when the chain is not configured for any backend.
41
41
  * @throws {SimulationRevertedError} when the bundle reverts on either backend.
42
42
  * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
43
- * the dust threshold by a restricted `bundler3` address or a `bundles` periphery
44
- * contract (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1). Never bypassable.
43
+ * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
44
+ * VaultBundlesV1, BlueBundlesV1). Never bypassable.
45
45
  * @throws {ExternalServiceError} (a) when both backends are unavailable within the
46
46
  * timeout budget, or (b) when a backend returns a `calls` array whose length does
47
47
  * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
@@ -88,7 +88,7 @@ async function simulate(config, params) {
88
88
  wNative,
89
89
  logger: config.logger,
90
90
  });
91
- (0, index_js_2.assertNoBundlerRetention)({
91
+ (0, index_js_2.assertNoBundlesRetention)({
92
92
  chainId: params.chainId,
93
93
  transfers,
94
94
  assetChanges: result.assetChanges,
@@ -19,7 +19,7 @@ interface RetainedAsset {
19
19
  token: string | undefined;
20
20
  netRetained: string;
21
21
  }
22
- /** Funds would flow to restricted bundler3 or bundles contract addresses. Never bypassable. */
22
+ /** Funds would flow to restricted standalone bundles contract addresses. Never bypassable. */
23
23
  export declare class BlacklistViolationError extends SimulationPackageError {
24
24
  readonly assetChanges?: RetainedAsset[] | undefined;
25
25
  readonly code = "BLACKLIST_ERROR";
package/lib/esm/errors.js CHANGED
@@ -19,7 +19,7 @@ export class SimulationRevertedError extends SimulationPackageError {
19
19
  this.details = details;
20
20
  }
21
21
  }
22
- /** Funds would flow to restricted bundler3 or bundles contract addresses. Never bypassable. */
22
+ /** Funds would flow to restricted standalone bundles contract addresses. Never bypassable. */
23
23
  export class BlacklistViolationError extends SimulationPackageError {
24
24
  assetChanges;
25
25
  code = "BLACKLIST_ERROR";
@@ -13,7 +13,7 @@ export interface AssetChangeEntry {
13
13
  * any real token address. `eth_simulateV1` synthesizes native moves from the
14
14
  * sentinel `0xeee…eee`, and Tenderly may echo it — in checksummed or any other
15
15
  * case — as an asset change's `contractAddress`. Native ETH is keyed by the exact
16
- * `ethAddress` constant here and in the bundler-retention guard, so a checksummed
16
+ * `ethAddress` constant here and in the bundles-retention guard, so a checksummed
17
17
  * sentinel would land on a separate map key and silently escape retention checks.
18
18
  * This is the single source of truth for token normalization shared by the
19
19
  * transfer-log parser and the Tenderly asset-change mapper.
@@ -4,7 +4,7 @@ import { ethAddress, getAddress } from "viem";
4
4
  * any real token address. `eth_simulateV1` synthesizes native moves from the
5
5
  * sentinel `0xeee…eee`, and Tenderly may echo it — in checksummed or any other
6
6
  * case — as an asset change's `contractAddress`. Native ETH is keyed by the exact
7
- * `ethAddress` constant here and in the bundler-retention guard, so a checksummed
7
+ * `ethAddress` constant here and in the bundles-retention guard, so a checksummed
8
8
  * sentinel would land on a separate map key and silently escape retention checks.
9
9
  * This is the single source of truth for token normalization shared by the
10
10
  * transfer-log parser and the Tenderly asset-change mapper.
@@ -36,13 +36,17 @@ export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d
36
36
  * atomically inside a single call frame. A **non-canonical wrapped-native**
37
37
  * that splits these emissions across two txs in the same bundle would leave
38
38
  * a phantom zero-address `Transfer` in the parsed output, which can be summed
39
- * by `assertNoBundlerRetention` and produce a false `BlacklistViolationError`.
39
+ * by `assertNoBundlesRetention` and produce a false `BlacklistViolationError`.
40
40
  * When its zero-address `Transfer` misses same-tx dedup, the parser emits a
41
41
  * `warn` so the assumption break is observable before it reaches retention.
42
42
  * None of `morpho-sdk`'s currently supported chains require cross-tx dedup;
43
43
  * the assumption is rechecked when onboarding a new chain (see
44
44
  * `evm-simulation/CLAUDE.md`).
45
45
  *
46
+ * **Hex casing.** JSON-RPC `DATA` is case-insensitive, so `topics` and `data`
47
+ * are lowercased before signature dispatch and pair matching; a backend that
48
+ * echoes mixed-case hex cannot drop a transfer from the parsed output.
49
+ *
46
50
  * **Failure mode:** on a per-log parse failure (malformed topic length,
47
51
  * non-hex data), the log is skipped and a `warn` is emitted via the logger.
48
52
  *
@@ -41,13 +41,17 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
41
41
  * atomically inside a single call frame. A **non-canonical wrapped-native**
42
42
  * that splits these emissions across two txs in the same bundle would leave
43
43
  * a phantom zero-address `Transfer` in the parsed output, which can be summed
44
- * by `assertNoBundlerRetention` and produce a false `BlacklistViolationError`.
44
+ * by `assertNoBundlesRetention` and produce a false `BlacklistViolationError`.
45
45
  * When its zero-address `Transfer` misses same-tx dedup, the parser emits a
46
46
  * `warn` so the assumption break is observable before it reaches retention.
47
47
  * None of `morpho-sdk`'s currently supported chains require cross-tx dedup;
48
48
  * the assumption is rechecked when onboarding a new chain (see
49
49
  * `evm-simulation/CLAUDE.md`).
50
50
  *
51
+ * **Hex casing.** JSON-RPC `DATA` is case-insensitive, so `topics` and `data`
52
+ * are lowercased before signature dispatch and pair matching; a backend that
53
+ * echoes mixed-case hex cannot drop a transfer from the parsed output.
54
+ *
51
55
  * **Failure mode:** on a per-log parse failure (malformed topic length,
52
56
  * non-hex data), the log is skipped and a `warn` is emitted via the logger.
53
57
  *
@@ -64,11 +68,17 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
64
68
  export function parseTransfers(calls, options = {}) {
65
69
  const transfers = [];
66
70
  const { logger, wNative } = options;
67
- const wnativeShapedTokens = collectWnativeShapedTokens(calls);
71
+ // Lowercase `topics`/`data` so signature dispatch and pair matching are case-insensitive.
72
+ const normalizedCalls = calls.map((call) => call.logs.map((log) => ({
73
+ ...log,
74
+ topics: log.topics.map((topic) => topic.toLowerCase()),
75
+ data: log.data.toLowerCase(),
76
+ })));
77
+ const wnativeShapedTokens = collectWnativeShapedTokens(normalizedCalls);
68
78
  const acceptsWnativeEvent = (address) => wNative === undefined ||
69
79
  (wNative !== null && isAddressEqual(address, wNative));
70
- for (let txIdx = 0; txIdx < calls.length; txIdx++) {
71
- const logs = calls[txIdx].logs;
80
+ for (let txIdx = 0; txIdx < normalizedCalls.length; txIdx++) {
81
+ const logs = normalizedCalls[txIdx];
72
82
  for (const log of logs) {
73
83
  try {
74
84
  const topic0 = log.topics[0];
@@ -191,8 +201,8 @@ function warnMalformed(logger, log, reason) {
191
201
  /** Collect contracts that emit WETH9-shaped events anywhere in the bundle. */
192
202
  function collectWnativeShapedTokens(calls) {
193
203
  const tokens = new Set();
194
- for (const call of calls) {
195
- for (const log of call.logs) {
204
+ for (const logs of calls) {
205
+ for (const log of logs) {
196
206
  const topic0 = log.topics[0];
197
207
  if (topic0 === WITHDRAWAL_TOPIC || topic0 === DEPOSIT_TOPIC) {
198
208
  tokens.add(log.address.toLowerCase());
@@ -1,22 +1,23 @@
1
1
  import type { AccountAssetChanges, SimulationLogger, Transfer } from "../../types.js";
2
- interface AssertNoBundlerRetentionParams {
2
+ interface AssertNoBundlesRetentionParams {
3
3
  chainId: number;
4
4
  transfers: Transfer[];
5
5
  assetChanges: readonly AccountAssetChanges[];
6
6
  logger?: SimulationLogger;
7
7
  }
8
8
  /**
9
- * Assert that no value is retained by restricted contract addresses — the
10
- * `bundler3` executor and adapters, plus the standalone `bundles` periphery
11
- * contracts (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
9
+ * Assert that no value is retained by standalone `bundles` periphery contracts
10
+ * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`) or by
11
+ * `MidnightBundlesV1` (`midnightBundles`).
12
12
  *
13
13
  * Uses net flow (inbound minus outbound) per (restricted address, token) pair.
14
14
  * These contracts legitimately receive tokens as an intermediary (user →
15
- * bundler → vault), so gross inbound would fire false positives. We only block
16
- * positive net flow above `DUST_THRESHOLD` — value stuck in a restricted
17
- * address. Negative net flow means the simulated bundle sweeps a pre-existing or
18
- * state-overridden balance. That is useful telemetry, but not current-bundle
19
- * retention, so it is warned instead of raising a blacklist violation.
15
+ * bundles contract → vault), so gross inbound would fire false positives. We
16
+ * only block positive net flow above `DUST_THRESHOLD` — value stuck in a
17
+ * restricted address. Negative net flow means the simulated bundle sweeps a
18
+ * pre-existing or state-overridden balance. That is useful telemetry, but not
19
+ * current-bundle retention, so it is warned instead of raising a blacklist
20
+ * violation.
20
21
  *
21
22
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
22
23
  * from parsed `transfers` (log-derived, identical across backends). Native ETH
@@ -25,19 +26,19 @@ interface AssertNoBundlerRetentionParams {
25
26
  * Native ETH is therefore read from `assetChanges`, the cross-backend source of
26
27
  * truth for native value: Tenderly derives it from its trace, and
27
28
  * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
28
- * this, native ETH stuck in bundler3 would pass the guard undetected on the
29
- * Tenderly path (Cantina finding 1440).
29
+ * this, native ETH stuck in a bundles contract would pass the guard undetected
30
+ * on the Tenderly path (Cantina finding 1440).
30
31
  *
31
32
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
32
33
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
33
34
  * `assetChanges` derived from it — native transfer logs are only used as a
34
- * fallback for bundler addresses that carry no native `assetChanges` entry.
35
+ * fallback for bundles addresses that carry no native `assetChanges` entry.
35
36
  *
36
37
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
37
38
  * package's public API (only re-exported from the internal pipeline barrel).
38
- * @param params.chainId - Chain whose blue-sdk `bundler3` and `bundles`
39
- * registries define the restricted address set. Chains cataloging neither are
40
- * skipped (a `logger.warn` records the skip).
39
+ * @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundles`
40
+ * registry entries define the restricted address set. Chains cataloging
41
+ * neither are skipped (a `logger.warn` records the skip).
41
42
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
42
43
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
43
44
  * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
@@ -47,7 +48,8 @@ interface AssertNoBundlerRetentionParams {
47
48
  * @returns Nothing. Returns silently when no restricted contract retains value
48
49
  * above `DUST_THRESHOLD`; otherwise throws.
49
50
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
50
- * `bundler3` or `bundles` contract exceeds `DUST_THRESHOLD` for any token.
51
+ * `bundles` or `midnightBundles` contract exceeds `DUST_THRESHOLD` for any
52
+ * token.
51
53
  */
52
- export declare function assertNoBundlerRetention(params: AssertNoBundlerRetentionParams): void;
54
+ export declare function assertNoBundlesRetention(params: AssertNoBundlesRetentionParams): void;
53
55
  export {};
@@ -3,27 +3,28 @@ import { isDefined } from "@morpho-org/morpho-ts";
3
3
  import { ethAddress, getAddress } from "viem";
4
4
  import { BlacklistViolationError } from "../../errors.js";
5
5
  /**
6
- * Dust tolerance for bundler retention, in raw token units.
6
+ * Dust tolerance for standalone bundles retention, in raw token units.
7
7
  *
8
8
  * 100 units is negligible for 18-decimal tokens (~1e-16). For low-decimal
9
9
  * tokens (e.g., 2-decimal stablecoins) this represents ~1.00 token — worth
10
10
  * keeping in mind. The threshold is intentionally token-agnostic: its purpose
11
- * is to absorb rounding noise from bundler3 routing, not to define an
12
- * acceptable retention amount.
11
+ * is to absorb routing rounding noise, not to define an acceptable retention
12
+ * amount.
13
13
  */
14
14
  const DUST_THRESHOLD = 100n;
15
15
  /**
16
- * Assert that no value is retained by restricted contract addresses — the
17
- * `bundler3` executor and adapters, plus the standalone `bundles` periphery
18
- * contracts (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`).
16
+ * Assert that no value is retained by standalone `bundles` periphery contracts
17
+ * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`) or by
18
+ * `MidnightBundlesV1` (`midnightBundles`).
19
19
  *
20
20
  * Uses net flow (inbound minus outbound) per (restricted address, token) pair.
21
21
  * These contracts legitimately receive tokens as an intermediary (user →
22
- * bundler → vault), so gross inbound would fire false positives. We only block
23
- * positive net flow above `DUST_THRESHOLD` — value stuck in a restricted
24
- * address. Negative net flow means the simulated bundle sweeps a pre-existing or
25
- * state-overridden balance. That is useful telemetry, but not current-bundle
26
- * retention, so it is warned instead of raising a blacklist violation.
22
+ * bundles contract → vault), so gross inbound would fire false positives. We
23
+ * only block positive net flow above `DUST_THRESHOLD` — value stuck in a
24
+ * restricted address. Negative net flow means the simulated bundle sweeps a
25
+ * pre-existing or state-overridden balance. That is useful telemetry, but not
26
+ * current-bundle retention, so it is warned instead of raising a blacklist
27
+ * violation.
27
28
  *
28
29
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
29
30
  * from parsed `transfers` (log-derived, identical across backends). Native ETH
@@ -32,19 +33,19 @@ const DUST_THRESHOLD = 100n;
32
33
  * Native ETH is therefore read from `assetChanges`, the cross-backend source of
33
34
  * truth for native value: Tenderly derives it from its trace, and
34
35
  * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
35
- * this, native ETH stuck in bundler3 would pass the guard undetected on the
36
- * Tenderly path (Cantina finding 1440).
36
+ * this, native ETH stuck in a bundles contract would pass the guard undetected
37
+ * on the Tenderly path (Cantina finding 1440).
37
38
  *
38
39
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
39
40
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
40
41
  * `assetChanges` derived from it — native transfer logs are only used as a
41
- * fallback for bundler addresses that carry no native `assetChanges` entry.
42
+ * fallback for bundles addresses that carry no native `assetChanges` entry.
42
43
  *
43
44
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
44
45
  * package's public API (only re-exported from the internal pipeline barrel).
45
- * @param params.chainId - Chain whose blue-sdk `bundler3` and `bundles`
46
- * registries define the restricted address set. Chains cataloging neither are
47
- * skipped (a `logger.warn` records the skip).
46
+ * @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundles`
47
+ * registry entries define the restricted address set. Chains cataloging
48
+ * neither are skipped (a `logger.warn` records the skip).
48
49
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
49
50
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
50
51
  * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
@@ -54,15 +55,13 @@ const DUST_THRESHOLD = 100n;
54
55
  * @returns Nothing. Returns silently when no restricted contract retains value
55
56
  * above `DUST_THRESHOLD`; otherwise throws.
56
57
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
57
- * `bundler3` or `bundles` contract exceeds `DUST_THRESHOLD` for any token.
58
+ * `bundles` or `midnightBundles` contract exceeds `DUST_THRESHOLD` for any
59
+ * token.
58
60
  */
59
- export function assertNoBundlerRetention(params) {
61
+ export function assertNoBundlesRetention(params) {
60
62
  const { chainId, transfers, assetChanges, logger } = params;
61
- // Restricted address set: the full `bundler3` sub-registry (executor + every
62
- // adapter) plus the standalone `bundles` periphery contracts
63
- // (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`). Both families are
64
- // transient intermediaries that route user value and must never retain it, so
65
- // both are guarded identically.
63
+ // Standalone bundles contracts and `MidnightBundlesV1` are transient
64
+ // intermediaries that route user value and must never retain it.
66
65
  let addresses;
67
66
  try {
68
67
  addresses = getChainAddresses(chainId);
@@ -79,18 +78,17 @@ export function assertNoBundlerRetention(params) {
79
78
  throw error;
80
79
  }
81
80
  const restrictedAddresses = new Set();
82
- if (addresses.bundler3) {
83
- for (const addr of Object.values(addresses.bundler3).filter(isDefined))
84
- restrictedAddresses.add(getAddress(addr));
85
- }
86
81
  if (addresses.bundles) {
87
82
  for (const addr of Object.values(addresses.bundles).filter(isDefined))
88
83
  restrictedAddresses.add(getAddress(addr));
89
84
  }
85
+ if (addresses.midnightBundles) {
86
+ restrictedAddresses.add(getAddress(addresses.midnightBundles));
87
+ }
90
88
  if (restrictedAddresses.size === 0) {
91
- // blue-sdk knows the chain but cataloged neither bundler3 nor bundles for
92
- // it. Treat the same as UnsupportedChainIdError — retention check skipped.
93
- logger?.warn("Chain known to blue-sdk but has no bundler3 or bundles config, retention check skipped", { chainId });
89
+ // blue-sdk knows the chain but cataloged no restricted intermediary for it.
90
+ // Treat the same as UnsupportedChainIdError — retention check skipped.
91
+ logger?.warn("Chain known to blue-sdk but has no restricted intermediary config, retention check skipped", { chainId });
94
92
  return;
95
93
  }
96
94
  // Map keyed by (address, token) → structured entry. Avoids string parse-back.
@@ -107,8 +105,8 @@ export function assertNoBundlerRetention(params) {
107
105
  }
108
106
  };
109
107
  // Native ETH from `assetChanges` (authoritative, cross-backend). Track which
110
- // bundlers already have a native entry here so the transfer-log pass below
111
- // does not re-add the same native move on `eth_simulateV1`.
108
+ // restricted contracts already have a native entry here so the transfer-log
109
+ // pass below does not re-add the same native move on `eth_simulateV1`.
112
110
  const nativeFromAssetChanges = new Set();
113
111
  for (const { account, changes } of assetChanges) {
114
112
  if (!restrictedAddresses.has(account))
@@ -121,9 +119,9 @@ export function assertNoBundlerRetention(params) {
121
119
  }
122
120
  }
123
121
  // ERC20 / WETH9 retention from transfer logs. Native ETH transfers (the
124
- // `eth_simulateV1` synthetic sentinel) only backfill a bundler endpoint that
125
- // is absent from `nativeFromAssetChanges`, so native is never summed on top
126
- // of the `assetChanges` value already recorded for that address.
122
+ // `eth_simulateV1` synthetic sentinel) only backfill a restricted endpoint
123
+ // that is absent from `nativeFromAssetChanges`, so native is never summed on
124
+ // top of the `assetChanges` value already recorded for that address.
127
125
  const usesAssetChangeNative = (t, addr) => t.token === ethAddress && nativeFromAssetChanges.has(addr.toLowerCase());
128
126
  for (const t of transfers) {
129
127
  if (restrictedAddresses.has(t.to) && !usesAssetChangeNative(t, t.to))
@@ -134,7 +132,7 @@ export function assertNoBundlerRetention(params) {
134
132
  const entries = [...flow.values()];
135
133
  const drained = entries.filter((e) => e.netChange < -DUST_THRESHOLD);
136
134
  if (drained.length > 0) {
137
- logger?.warn("Simulation detected pre-existing balance being swept from a restricted bundler or bundles contract", {
135
+ logger?.warn("Simulation detected pre-existing balance being swept from a restricted bundles contract", {
138
136
  changes: drained.map((e) => ({
139
137
  address: e.address,
140
138
  token: e.token,
@@ -144,7 +142,7 @@ export function assertNoBundlerRetention(params) {
144
142
  }
145
143
  const retained = entries.filter((e) => e.netChange > DUST_THRESHOLD);
146
144
  if (retained.length > 0) {
147
- throw new BlacklistViolationError("Simulation detected asset transfers retained by restricted bundler or bundles contracts", retained.map((e) => ({
145
+ throw new BlacklistViolationError("Simulation detected asset transfers retained by restricted bundles contracts", retained.map((e) => ({
148
146
  address: e.address,
149
147
  token: e.token,
150
148
  netRetained: e.netChange.toString(),
@@ -1,4 +1,4 @@
1
1
  export { buildSimulationTxs } from "./build-simulation-txs.js";
2
- export { assertNoBundlerRetention } from "./bundler-retention.js";
2
+ export { assertNoBundlesRetention } from "./bundles-retention.js";
3
3
  export { executeSimulation } from "./execute-simulation.js";
4
4
  export { validateInput } from "./validate-input.js";
@@ -1,4 +1,4 @@
1
1
  export { buildSimulationTxs } from "./build-simulation-txs.js";
2
- export { assertNoBundlerRetention } from "./bundler-retention.js";
2
+ export { assertNoBundlesRetention } from "./bundles-retention.js";
3
3
  export { executeSimulation } from "./execute-simulation.js";
4
4
  export { validateInput } from "./validate-input.js";
@@ -7,7 +7,7 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
7
7
  * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
8
8
  * events to the registered wrapped-native token, rejecting them on known tokenless chains,
9
9
  * and retaining signature-based parsing for unknown chains → asserts no funds are retained
10
- * by `bundler3` or the standalone `bundles` periphery contracts → returns the full result
10
+ * by standalone `bundles` periphery contracts → returns the full result
11
11
  * set. The caller reads whichever fields they need:
12
12
  *
13
13
  * - `transfers` → user-facing preview / server-side verification.
@@ -34,8 +34,8 @@ import type { SimulateParams, SimulationConfig, SimulationResult } from "../type
34
34
  * @throws {UnsupportedChainError} when the chain is not configured for any backend.
35
35
  * @throws {SimulationRevertedError} when the bundle reverts on either backend.
36
36
  * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
37
- * the dust threshold by a restricted `bundler3` address or a `bundles` periphery
38
- * contract (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1). Never bypassable.
37
+ * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
38
+ * VaultBundlesV1, BlueBundlesV1). Never bypassable.
39
39
  * @throws {ExternalServiceError} (a) when both backends are unavailable within the
40
40
  * timeout budget, or (b) when a backend returns a `calls` array whose length does
41
41
  * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
@@ -1,7 +1,7 @@
1
1
  import { _try, getChainAddresses, UnsupportedChainIdError, } from "@morpho-org/blue-sdk";
2
2
  import { ExternalServiceError } from "../errors.js";
3
3
  import { parseTransfers } from "./parsing/index.js";
4
- import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, validateInput, } from "./pipeline/index.js";
4
+ import { assertNoBundlesRetention, buildSimulationTxs, executeSimulation, validateInput, } from "./pipeline/index.js";
5
5
  /**
6
6
  * Simulate a bundle of EVM transactions.
7
7
  *
@@ -10,7 +10,7 @@ import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, valida
10
10
  * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
11
11
  * events to the registered wrapped-native token, rejecting them on known tokenless chains,
12
12
  * and retaining signature-based parsing for unknown chains → asserts no funds are retained
13
- * by `bundler3` or the standalone `bundles` periphery contracts → returns the full result
13
+ * by standalone `bundles` periphery contracts → returns the full result
14
14
  * set. The caller reads whichever fields they need:
15
15
  *
16
16
  * - `transfers` → user-facing preview / server-side verification.
@@ -37,8 +37,8 @@ import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, valida
37
37
  * @throws {UnsupportedChainError} when the chain is not configured for any backend.
38
38
  * @throws {SimulationRevertedError} when the bundle reverts on either backend.
39
39
  * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
40
- * the dust threshold by a restricted `bundler3` address or a `bundles` periphery
41
- * contract (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1). Never bypassable.
40
+ * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
41
+ * VaultBundlesV1, BlueBundlesV1). Never bypassable.
42
42
  * @throws {ExternalServiceError} (a) when both backends are unavailable within the
43
43
  * timeout budget, or (b) when a backend returns a `calls` array whose length does
44
44
  * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
@@ -85,7 +85,7 @@ export async function simulate(config, params) {
85
85
  wNative,
86
86
  logger: config.logger,
87
87
  });
88
- assertNoBundlerRetention({
88
+ assertNoBundlesRetention({
89
89
  chainId: params.chainId,
90
90
  transfers,
91
91
  assetChanges: result.assetChanges,
package/package.json CHANGED
@@ -1,11 +1,8 @@
1
1
  {
2
2
  "name": "@morpho-org/evm-simulation",
3
3
  "description": "EVM simulation engine for Morpho transactions.",
4
- "version": "4.1.10",
4
+ "version": "4.2.0-next.2",
5
5
  "author": "Morpho Association <contact@morpho.org>",
6
- "contributors": [
7
- "Rubilmax <rmilon@gmail.com>"
8
- ],
9
6
  "repository": {
10
7
  "type": "git",
11
8
  "url": "git+https://github.com/morpho-org/sdks.git",
@@ -25,8 +22,8 @@
25
22
  ],
26
23
  "dependencies": {
27
24
  "zod": "^4.4.3",
28
- "@morpho-org/blue-sdk": "^6.11.0",
29
- "@morpho-org/morpho-ts": "^2.15.0"
25
+ "@morpho-org/blue-sdk": "^7.0.0-next.2",
26
+ "@morpho-org/morpho-ts": "^3.0.0-next.1"
30
27
  },
31
28
  "peerDependencies": {
32
29
  "viem": "^2.0.0"