@morpho-org/evm-simulation 5.0.1 → 6.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 (50) hide show
  1. package/README.md +6 -4
  2. package/lib/cjs/errors.d.ts +17 -2
  3. package/lib/cjs/errors.js +19 -3
  4. package/lib/cjs/index.d.ts +2 -2
  5. package/lib/cjs/index.js +2 -1
  6. package/lib/cjs/params.d.ts +20 -1
  7. package/lib/cjs/requirements/requirements.js +2 -1
  8. package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +26 -34
  9. package/lib/cjs/simulate/backends/eth-simulate-v1.js +35 -74
  10. package/lib/cjs/simulate/backends/index.d.ts +1 -1
  11. package/lib/cjs/simulate/backends/index.js +1 -2
  12. package/lib/cjs/simulate/backends/parse-response.d.ts +1 -0
  13. package/lib/cjs/simulate/backends/parse-response.js +4 -2
  14. package/lib/cjs/simulate/backends/resolve-pinned-block.d.ts +6 -12
  15. package/lib/cjs/simulate/backends/resolve-pinned-block.js +3 -4
  16. package/lib/cjs/simulate/pipeline/bundles-retention.d.ts +3 -3
  17. package/lib/cjs/simulate/pipeline/bundles-retention.js +6 -6
  18. package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +5 -1
  19. package/lib/cjs/simulate/pipeline/resolve-chain.js +12 -1
  20. package/lib/cjs/simulate/request/parse-request.d.ts +2 -1
  21. package/lib/cjs/simulate/request/parse-request.js +21 -0
  22. package/lib/cjs/simulate/run-simulation.js +9 -12
  23. package/lib/cjs/simulate/simulate.d.ts +30 -19
  24. package/lib/cjs/simulate/simulate.js +30 -19
  25. package/lib/cjs/types.d.ts +15 -0
  26. package/lib/esm/errors.d.ts +17 -2
  27. package/lib/esm/errors.js +17 -2
  28. package/lib/esm/index.d.ts +2 -2
  29. package/lib/esm/index.js +1 -1
  30. package/lib/esm/params.d.ts +20 -1
  31. package/lib/esm/requirements/requirements.js +2 -1
  32. package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +26 -34
  33. package/lib/esm/simulate/backends/eth-simulate-v1.js +37 -75
  34. package/lib/esm/simulate/backends/index.d.ts +1 -1
  35. package/lib/esm/simulate/backends/index.js +1 -1
  36. package/lib/esm/simulate/backends/parse-response.d.ts +1 -0
  37. package/lib/esm/simulate/backends/parse-response.js +4 -2
  38. package/lib/esm/simulate/backends/resolve-pinned-block.d.ts +6 -12
  39. package/lib/esm/simulate/backends/resolve-pinned-block.js +3 -4
  40. package/lib/esm/simulate/pipeline/bundles-retention.d.ts +3 -3
  41. package/lib/esm/simulate/pipeline/bundles-retention.js +6 -6
  42. package/lib/esm/simulate/pipeline/resolve-chain.d.ts +5 -1
  43. package/lib/esm/simulate/pipeline/resolve-chain.js +13 -2
  44. package/lib/esm/simulate/request/parse-request.d.ts +2 -1
  45. package/lib/esm/simulate/request/parse-request.js +21 -0
  46. package/lib/esm/simulate/run-simulation.js +10 -13
  47. package/lib/esm/simulate/simulate.d.ts +30 -19
  48. package/lib/esm/simulate/simulate.js +30 -19
  49. package/lib/esm/types.d.ts +15 -0
  50. package/package.json +4 -4
@@ -18,7 +18,7 @@ const DUST_THRESHOLD = 100n;
18
18
  /**
19
19
  * Assert that no value is retained by standalone `bundles` periphery contracts
20
20
  * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`) or by
21
- * `MidnightBundlesV1` (`midnightBundles`).
21
+ * `MidnightBundlesV2` (`midnightBundlesV2`).
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 →
@@ -43,7 +43,7 @@ const DUST_THRESHOLD = 100n;
43
43
  *
44
44
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
45
45
  * package's public API (only re-exported from the internal pipeline barrel).
46
- * @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundles`
46
+ * @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundlesV2`
47
47
  * registry entries define the restricted address set. Chains cataloging
48
48
  * neither are skipped (a `logger.warn` records the skip).
49
49
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
@@ -55,12 +55,12 @@ const DUST_THRESHOLD = 100n;
55
55
  * @returns Nothing. Returns silently when no restricted contract retains value
56
56
  * above `DUST_THRESHOLD`; otherwise throws.
57
57
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
58
- * `bundles` or `midnightBundles` contract exceeds `DUST_THRESHOLD` for any
58
+ * `bundles` or `midnightBundlesV2` contract exceeds `DUST_THRESHOLD` for any
59
59
  * token.
60
60
  */
61
61
  function assertNoBundlesRetention(params) {
62
62
  const { chainId, transfers, assetChanges, logger } = params;
63
- // Standalone bundles contracts and `MidnightBundlesV1` are transient
63
+ // Standalone bundles contracts and `MidnightBundlesV2` are transient
64
64
  // intermediaries that route user value and must never retain it.
65
65
  let addresses;
66
66
  try {
@@ -82,8 +82,8 @@ function assertNoBundlesRetention(params) {
82
82
  for (const addr of Object.values(addresses.bundles).filter(morpho_ts_1.isDefined))
83
83
  restrictedAddresses.add((0, viem_1.getAddress)(addr));
84
84
  }
85
- if (addresses.midnightBundles) {
86
- restrictedAddresses.add((0, viem_1.getAddress)(addresses.midnightBundles));
85
+ if (addresses.midnightBundlesV2) {
86
+ restrictedAddresses.add((0, viem_1.getAddress)(addresses.midnightBundlesV2));
87
87
  }
88
88
  if (restrictedAddresses.size === 0) {
89
89
  // blue-sdk knows the chain but cataloged no restricted intermediary for it.
@@ -4,6 +4,10 @@ import type { ChainSimulationConfig, SimulationConfig } from "../../types.js";
4
4
  *
5
5
  * Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
6
6
  * when the chain is absent from the map or has no `simulateV1Url` (defensive —
7
- * the type already requires it at construction).
7
+ * the type already requires it at construction). Throws
8
+ * `SimulationValidationError` when `blockOverrides.gasLimit` is set but is
9
+ * not a positive bigint, or `parentHashCheck` is set but is not a boolean, so a
10
+ * misconfiguration never surfaces as a bypassable
11
+ * `ExternalServiceError`.
8
12
  */
9
13
  export declare function resolveChain(config: SimulationConfig, chainId: number): ChainSimulationConfig;
@@ -7,11 +7,22 @@ const errors_js_1 = require("../../errors.js");
7
7
  *
8
8
  * Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
9
9
  * when the chain is absent from the map or has no `simulateV1Url` (defensive —
10
- * the type already requires it at construction).
10
+ * the type already requires it at construction). Throws
11
+ * `SimulationValidationError` when `blockOverrides.gasLimit` is set but is
12
+ * not a positive bigint, or `parentHashCheck` is set but is not a boolean, so a
13
+ * misconfiguration never surfaces as a bypassable
14
+ * `ExternalServiceError`.
11
15
  */
12
16
  function resolveChain(config, chainId) {
13
17
  const entry = config.chains.get(chainId);
14
18
  if (!entry?.simulateV1Url)
15
19
  throw new errors_js_1.UnsupportedChainError(chainId);
20
+ const gasLimit = entry.blockOverrides?.gasLimit;
21
+ if (gasLimit !== undefined &&
22
+ (typeof gasLimit !== "bigint" || gasLimit <= 0n))
23
+ throw new errors_js_1.SimulationValidationError(`Chain ${chainId} blockOverrides.gasLimit must be a positive bigint, got "${String(gasLimit)}" (${typeof gasLimit}). Pass a bigint greater than 0n or omit gasLimit.`, ["blockOverrides.gasLimit: must be a positive bigint"]);
24
+ const parentHashCheck = entry.parentHashCheck;
25
+ if (parentHashCheck !== undefined && typeof parentHashCheck !== "boolean")
26
+ throw new errors_js_1.SimulationValidationError(`Chain ${chainId} parentHashCheck must be a boolean, got "${String(parentHashCheck)}" (${typeof parentHashCheck}). Pass true or false, or omit parentHashCheck.`, ["parentHashCheck: must be a boolean"]);
16
27
  return entry;
17
28
  }
@@ -1,7 +1,7 @@
1
1
  import { type Address, type BlockTag, type Hex } from "viem";
2
2
  import type { SimulationAuthorization } from "../../authorizations.js";
3
3
  import type { SimulationLimits } from "../../limits.js";
4
- import { type SimulateParams, type SimulationMode } from "../../params.js";
4
+ import { type SimulateParams, type SimulationMode, type StateBlock } from "../../params.js";
5
5
  /** A normalized user transaction: checksummed addresses, `value` defaulted to `0n`.
6
6
  * @internal
7
7
  */
@@ -21,6 +21,7 @@ export interface ParsedRequest {
21
21
  /** Always empty in final mode. */
22
22
  readonly authorizations: readonly SimulationAuthorization[];
23
23
  readonly blockNumber?: bigint | Exclude<BlockTag, "pending">;
24
+ readonly block?: StateBlock;
24
25
  readonly limits?: SimulationLimits;
25
26
  }
26
27
  /**
@@ -52,6 +52,7 @@ const PERMIT2_MESSAGE_KEYS = PERMIT2_TRANSFER_FIELDS.map((field) => field.name);
52
52
  const PERMIT2_PERMITTED_KEYS = ["token", "amount"];
53
53
  const BLUE_AUTHORIZATION_MESSAGE_KEYS = BLUE_AUTHORIZATION_FIELDS.map((field) => field.name);
54
54
  const TRANSACTION_KEYS = ["from", "to", "data", "value"];
55
+ const BLOCK_KEYS = ["number", "hash", "timestamp"];
55
56
  const LIMITS_KEYS = ["operations"];
56
57
  const AUTHORIZATION_KEYS = {
57
58
  erc20Approval: ["type", "token", "owner", "spender", "amount"],
@@ -570,6 +571,7 @@ function parseRequest(input) {
570
571
  "chainId",
571
572
  "transactions",
572
573
  "blockNumber",
574
+ "block",
573
575
  "mode",
574
576
  "authorizations",
575
577
  "limits",
@@ -652,6 +654,24 @@ function parseRequest(input) {
652
654
  }
653
655
  }
654
656
  }
657
+ // block
658
+ const rawBlock = input.block;
659
+ let block;
660
+ if (rawBlock !== undefined) {
661
+ if (!isRecord(rawBlock)) {
662
+ fieldErrors.push("block: must be an object");
663
+ }
664
+ else {
665
+ check.keys(rawBlock, { allow: BLOCK_KEYS, path: "block" });
666
+ const number = check.uint256(readField(rawBlock, "number"), "block.number");
667
+ const hash = check.bytes32(readField(rawBlock, "hash"), "block.hash");
668
+ const timestamp = check.uint256(readField(rawBlock, "timestamp"), "block.timestamp");
669
+ if (number !== undefined && hash !== undefined && timestamp !== undefined)
670
+ block = { number, hash: hash.toLowerCase(), timestamp };
671
+ }
672
+ if (rawBlockNumber !== undefined)
673
+ fieldErrors.push("block: cannot be combined with blockNumber");
674
+ }
655
675
  // authorizations
656
676
  const rawAuthorizations = input.authorizations;
657
677
  const authorizations = [];
@@ -754,6 +774,7 @@ function parseRequest(input) {
754
774
  transactions,
755
775
  authorizations: mode === "preview" ? authorizations : [],
756
776
  ...(blockNumber !== undefined ? { blockNumber } : {}),
777
+ ...(block !== undefined ? { block } : {}),
757
778
  ...(normalizedLimits !== undefined ? { limits: normalizedLimits } : {}),
758
779
  });
759
780
  }
@@ -45,21 +45,16 @@ async function runSimulation(params) {
45
45
  if (requiresMorpho && addresses?.blue == null)
46
46
  throw new errors_js_1.UnsupportedChainError(request.chainId);
47
47
  const morpho = addresses?.blue ?? viem_1.zeroAddress;
48
- await (0, index_js_1.assertEndpointChain)({
49
- client,
50
- chainId: request.chainId,
51
- mode: request.mode,
52
- blockNumber: typeof request.blockNumber === "bigint" ? request.blockNumber : undefined,
53
- });
54
48
  // Monad nodes reject `validation: false` and charge no gas in the simulated
55
49
  // block either way. Its `latest` block is not final: simulating at its number
56
50
  // runs on another parent, so default to `finalized`.
57
51
  const monad = request.chainId === blue_sdk_1.ChainId.MonadMainnet;
58
- const pinnedBlock = await (0, resolve_pinned_block_js_1.resolvePinnedBlock)({
59
- client,
60
- blockNumber: request.blockNumber ?? (monad ? "finalized" : undefined),
61
- signal,
62
- });
52
+ const pinnedBlock = request.block ??
53
+ (await (0, resolve_pinned_block_js_1.resolvePinnedBlock)({
54
+ client,
55
+ blockNumber: request.blockNumber ?? (monad ? "finalized" : undefined),
56
+ signal,
57
+ }));
63
58
  // With no calldata decoding, the caller's declared `limits.operations`
64
59
  // entries are the operations: subjects for state reads and per-operation
65
60
  // checks come straight from the limit list.
@@ -85,12 +80,14 @@ async function runSimulation(params) {
85
80
  preparations,
86
81
  reads: observations.reads,
87
82
  });
88
- // Execute against the block resolved once above; the boundary rechecks its hash.
83
+ // Execute against the block resolved once above.
89
84
  const execution = await (0, index_js_1.executePlan)({
90
85
  client,
91
86
  plan,
92
87
  stateBlock: pinnedBlock,
93
88
  validation: monad,
89
+ blockGasLimit: chain.blockOverrides?.gasLimit,
90
+ parentHashCheck: chain.parentHashCheck ?? request.chainId !== blue_sdk_1.ChainId.StableMainnet,
94
91
  });
95
92
  const userCalls = execution.calls
96
93
  .filter((call) => call.planned.type === "transaction")
@@ -4,11 +4,11 @@ import type { SimulationConfig } from "../types.js";
4
4
  /**
5
5
  * Simulate a bundle of EVM transactions.
6
6
  *
7
- * Parses and normalizes the request → resolves the chain identity → resolves
8
- * a single pinned block → resolves quoted-asset metadata → plans state reads
9
- * and the execution → executes once through `eth_simulateV1` under the full
10
- * timeout budget (simulation with in-block state reads → response parsing →
11
- * reorg check) → derives ERC20/WETH9 transfers and net asset changes from the
7
+ * Parses and normalizes the request → resolves a single pinned block (skipped
8
+ * when `params.block` is supplied) → resolves quoted-asset metadata → plans
9
+ * state reads and the execution → executes once through `eth_simulateV1` under
10
+ * the full timeout budget (simulation with in-block state reads → response
11
+ * parsing) → derives ERC20/WETH9 transfers and net asset changes from the
12
12
  * user calls only → decodes quoted balances/positions → runs slippage checks
13
13
  * → asserts no funds are retained by standalone `bundles` periphery contracts
14
14
  * → returns the result. The caller reads whichever fields they need:
@@ -37,10 +37,13 @@ import type { SimulationConfig } from "../types.js";
37
37
  * separating gas from economic effects. Monad nodes reject `false`, so Monad
38
38
  * simulations send `true`; its simulated block charges no gas either way.
39
39
  *
40
- * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
41
- * the overall timeout budget.
40
+ * @param config - Required per-chain `eth_simulateV1` URL, optional per-chain
41
+ * `blockOverrides.gasLimit` and `parentHashCheck`, optional logger, and the
42
+ * overall timeout budget.
42
43
  * @param params - Per-call simulation input.
43
- * @param params.chainId - Chain id the bundle targets; must match the endpoint.
44
+ * @param params.chainId - Chain id the bundle targets. Sent on every call inside
45
+ * `eth_simulateV1`, so nodes that check it reject a wrong-chain endpoint;
46
+ * there is no separate `eth_chainId` lookup.
44
47
  * @param params.transactions - The bundle's transactions, in execution order.
45
48
  * All must share the same `from`.
46
49
  * @param params.mode - `"final"` (default) or `"preview"`.
@@ -50,9 +53,16 @@ import type { SimulationConfig } from "../types.js";
50
53
  * than `"pending"`.
51
54
  * Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
52
55
  * final), resolved exactly once.
56
+ * @param params.block - Optional caller-supplied state block (`number`,
57
+ * `hash`, `timestamp`). Skips the block lookup, so a simulation without
58
+ * asset metadata reads makes a single `eth_simulateV1` request. Cannot be
59
+ * combined with `blockNumber`.
53
60
  * @throws {SimulationValidationError} for invalid input (mixed senders, bad
54
61
  * addresses, empty transactions, malformed authorizations, final-mode
55
- * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
62
+ * authorizations, malformed limits, unknown fields, a `"pending"` block tag,
63
+ * a malformed `block` or one combined with `blockNumber`, a
64
+ * `blockOverrides.gasLimit` that is not a positive bigint, a `parentHashCheck`
65
+ * that is not a boolean, or share quotes for
56
66
  * `blueSupplyCollateral` / `blueWithdrawCollateral`).
57
67
  * @throws {ConsumerLimitViolationError} when a declared `limits` bound is
58
68
  * violated by the observed effects.
@@ -66,20 +76,21 @@ import type { SimulationConfig } from "../types.js";
66
76
  * @throws {InvalidSimulationResponseError} when the node response cannot be
67
77
  * trusted (bad shape, call-count mismatch, block that is neither the pinned
68
78
  * state block nor its immediate successor, a successor with a mismatched
69
- * `parentHash`, a block timestamp earlier than the pinned block's, a
70
- * malformed per-call result, a quoted balance/position read whose non-empty
71
- * return data cannot be decoded, a state-block hash that changed or a pinned
72
- * block that vanished mid-flight, or an
73
- * endpoint whose `eth_chainId` differs from `params.chainId`; chain identity
74
- * is checked before any block lookup).
79
+ * `parentHash` (unless `parentHashCheck` is off; off by default on Stable,
80
+ * chain 988), a block timestamp earlier than the pinned block's, a
81
+ * malformed per-call result, or a quoted balance/position read whose
82
+ * non-empty return data cannot be decoded).
83
+ * @throws {InvalidChainIdError} when the node rejects the request `chainId`
84
+ * because the endpoint serves another chain (a subclass of
85
+ * `InvalidSimulationResponseError` sharing its `name`/`code`;
86
+ * `instanceof InvalidChainIdError` tells them apart). Never bypassable.
75
87
  * @throws {BlacklistViolationError} when the simulation leaves value retained
76
88
  * beyond the dust threshold by a `bundles` periphery contract
77
- * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1).
89
+ * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV2).
78
90
  * Never bypassable.
79
91
  * @throws {ExternalServiceError} when the RPC is unavailable within the
80
- * timeout budget or returns a malformed JSON-RPC envelope. Chain-id
81
- * mismatches and a state block without number/hash are reported as
82
- * `InvalidSimulationResponseError`.
92
+ * timeout budget or returns a malformed JSON-RPC envelope. A looked-up state
93
+ * block without number/hash is reported as `InvalidSimulationResponseError`.
83
94
  * @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
84
95
  * `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
85
96
  * stamped with `txIdx`), per-account net `assetChanges`, and per-operation
@@ -6,11 +6,11 @@ const run_simulation_js_1 = require("./run-simulation.js");
6
6
  /**
7
7
  * Simulate a bundle of EVM transactions.
8
8
  *
9
- * Parses and normalizes the request → resolves the chain identity → resolves
10
- * a single pinned block → resolves quoted-asset metadata → plans state reads
11
- * and the execution → executes once through `eth_simulateV1` under the full
12
- * timeout budget (simulation with in-block state reads → response parsing →
13
- * reorg check) → derives ERC20/WETH9 transfers and net asset changes from the
9
+ * Parses and normalizes the request → resolves a single pinned block (skipped
10
+ * when `params.block` is supplied) → resolves quoted-asset metadata → plans
11
+ * state reads and the execution → executes once through `eth_simulateV1` under
12
+ * the full timeout budget (simulation with in-block state reads → response
13
+ * parsing) → derives ERC20/WETH9 transfers and net asset changes from the
14
14
  * user calls only → decodes quoted balances/positions → runs slippage checks
15
15
  * → asserts no funds are retained by standalone `bundles` periphery contracts
16
16
  * → returns the result. The caller reads whichever fields they need:
@@ -39,10 +39,13 @@ const run_simulation_js_1 = require("./run-simulation.js");
39
39
  * separating gas from economic effects. Monad nodes reject `false`, so Monad
40
40
  * simulations send `true`; its simulated block charges no gas either way.
41
41
  *
42
- * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
43
- * the overall timeout budget.
42
+ * @param config - Required per-chain `eth_simulateV1` URL, optional per-chain
43
+ * `blockOverrides.gasLimit` and `parentHashCheck`, optional logger, and the
44
+ * overall timeout budget.
44
45
  * @param params - Per-call simulation input.
45
- * @param params.chainId - Chain id the bundle targets; must match the endpoint.
46
+ * @param params.chainId - Chain id the bundle targets. Sent on every call inside
47
+ * `eth_simulateV1`, so nodes that check it reject a wrong-chain endpoint;
48
+ * there is no separate `eth_chainId` lookup.
46
49
  * @param params.transactions - The bundle's transactions, in execution order.
47
50
  * All must share the same `from`.
48
51
  * @param params.mode - `"final"` (default) or `"preview"`.
@@ -52,9 +55,16 @@ const run_simulation_js_1 = require("./run-simulation.js");
52
55
  * than `"pending"`.
53
56
  * Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
54
57
  * final), resolved exactly once.
58
+ * @param params.block - Optional caller-supplied state block (`number`,
59
+ * `hash`, `timestamp`). Skips the block lookup, so a simulation without
60
+ * asset metadata reads makes a single `eth_simulateV1` request. Cannot be
61
+ * combined with `blockNumber`.
55
62
  * @throws {SimulationValidationError} for invalid input (mixed senders, bad
56
63
  * addresses, empty transactions, malformed authorizations, final-mode
57
- * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
64
+ * authorizations, malformed limits, unknown fields, a `"pending"` block tag,
65
+ * a malformed `block` or one combined with `blockNumber`, a
66
+ * `blockOverrides.gasLimit` that is not a positive bigint, a `parentHashCheck`
67
+ * that is not a boolean, or share quotes for
58
68
  * `blueSupplyCollateral` / `blueWithdrawCollateral`).
59
69
  * @throws {ConsumerLimitViolationError} when a declared `limits` bound is
60
70
  * violated by the observed effects.
@@ -68,20 +78,21 @@ const run_simulation_js_1 = require("./run-simulation.js");
68
78
  * @throws {InvalidSimulationResponseError} when the node response cannot be
69
79
  * trusted (bad shape, call-count mismatch, block that is neither the pinned
70
80
  * state block nor its immediate successor, a successor with a mismatched
71
- * `parentHash`, a block timestamp earlier than the pinned block's, a
72
- * malformed per-call result, a quoted balance/position read whose non-empty
73
- * return data cannot be decoded, a state-block hash that changed or a pinned
74
- * block that vanished mid-flight, or an
75
- * endpoint whose `eth_chainId` differs from `params.chainId`; chain identity
76
- * is checked before any block lookup).
81
+ * `parentHash` (unless `parentHashCheck` is off; off by default on Stable,
82
+ * chain 988), a block timestamp earlier than the pinned block's, a
83
+ * malformed per-call result, or a quoted balance/position read whose
84
+ * non-empty return data cannot be decoded).
85
+ * @throws {InvalidChainIdError} when the node rejects the request `chainId`
86
+ * because the endpoint serves another chain (a subclass of
87
+ * `InvalidSimulationResponseError` sharing its `name`/`code`;
88
+ * `instanceof InvalidChainIdError` tells them apart). Never bypassable.
77
89
  * @throws {BlacklistViolationError} when the simulation leaves value retained
78
90
  * beyond the dust threshold by a `bundles` periphery contract
79
- * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1).
91
+ * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV2).
80
92
  * Never bypassable.
81
93
  * @throws {ExternalServiceError} when the RPC is unavailable within the
82
- * timeout budget or returns a malformed JSON-RPC envelope. Chain-id
83
- * mismatches and a state block without number/hash are reported as
84
- * `InvalidSimulationResponseError`.
94
+ * timeout budget or returns a malformed JSON-RPC envelope. A looked-up state
95
+ * block without number/hash is reported as `InvalidSimulationResponseError`.
85
96
  * @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
86
97
  * `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
87
98
  * stamped with `txIdx`), per-account net `assetChanges`, and per-operation
@@ -3,6 +3,21 @@ import type { Address, Hex } from "viem";
3
3
  export interface ChainSimulationConfig {
4
4
  /** JSON-RPC URL supporting `eth_simulateV1`. Required for every configured chain. */
5
5
  readonly simulateV1Url: string;
6
+ /**
7
+ * Block overrides forwarded to `eth_simulateV1` as the simulated block's
8
+ * `blockOverrides`. Omitted fields leave the node's defaults untouched.
9
+ */
10
+ readonly blockOverrides?: {
11
+ /** Gas limit of the simulated block. Omitted: no gas limit override is sent. */
12
+ readonly gasLimit?: bigint;
13
+ };
14
+ /**
15
+ * Whether a simulated successor block's `parentHash` must equal the pinned
16
+ * state block hash. Block number and timestamp checks always run.
17
+ * Omitted: on everywhere except Stable (988), whose nodes report a
18
+ * `parentHash` that never matches the pinned block.
19
+ */
20
+ readonly parentHashCheck?: boolean;
6
21
  }
7
22
  /**
8
23
  * Top-level configuration for `simulate`.
@@ -136,7 +136,7 @@ export interface RetainedAsset {
136
136
  }
137
137
  /**
138
138
  * Thrown when net value above the dust threshold is retained by a restricted bundles contract
139
- * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or MidnightBundlesV1) after simulation;
139
+ * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or MidnightBundlesV2) after simulation;
140
140
  * pass-through flows and net outflows are allowed. Never bypassable.
141
141
  */
142
142
  export declare class BlacklistViolationError extends SimulationPackageError {
@@ -175,11 +175,26 @@ export declare class UnsupportedOperationError extends SimulationPackageError {
175
175
  readonly name = "UnsupportedOperationError";
176
176
  readonly code = "UNSUPPORTED_OPERATION";
177
177
  }
178
- /** The simulation backend returned a response that cannot be parsed. */
178
+ /**
179
+ * The simulation backend returned a response that cannot be parsed.
180
+ * {@link InvalidChainIdError} subclasses this class and shares its `name`
181
+ * and `code`; check `instanceof InvalidChainIdError` first to tell the two
182
+ * apart.
183
+ */
179
184
  export declare class InvalidSimulationResponseError extends SimulationPackageError {
180
185
  readonly name = "InvalidSimulationResponseError";
181
186
  readonly code = "INVALID_SIMULATION_RESPONSE";
182
187
  }
188
+ /**
189
+ * The node rejected the request `chainId`: the endpoint is on a different
190
+ * chain than configured. This is misconfiguration, not an outage, so unlike
191
+ * {@link ExternalServiceError} callers must not bypass it.
192
+ * Extends {@link InvalidSimulationResponseError} and shares its `name` and
193
+ * `code`, so v5 callers that match on either still catch it;
194
+ * `instanceof InvalidChainIdError` is how to tell the two apart.
195
+ */
196
+ export declare class InvalidChainIdError extends InvalidSimulationResponseError {
197
+ }
183
198
  /** State needed to verify an operation could not be fetched or derived. */
184
199
  export declare class MissingVerificationEvidenceError extends SimulationPackageError {
185
200
  readonly name = "MissingVerificationEvidenceError";
package/lib/esm/errors.js CHANGED
@@ -60,7 +60,7 @@ export class SimulationRevertedError extends SimulationPackageError {
60
60
  }
61
61
  /**
62
62
  * Thrown when net value above the dust threshold is retained by a restricted bundles contract
63
- * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or MidnightBundlesV1) after simulation;
63
+ * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or MidnightBundlesV2) after simulation;
64
64
  * pass-through flows and net outflows are allowed. Never bypassable.
65
65
  */
66
66
  export class BlacklistViolationError extends SimulationPackageError {
@@ -109,11 +109,26 @@ export class UnsupportedOperationError extends SimulationPackageError {
109
109
  name = "UnsupportedOperationError";
110
110
  code = "UNSUPPORTED_OPERATION";
111
111
  }
112
- /** The simulation backend returned a response that cannot be parsed. */
112
+ /**
113
+ * The simulation backend returned a response that cannot be parsed.
114
+ * {@link InvalidChainIdError} subclasses this class and shares its `name`
115
+ * and `code`; check `instanceof InvalidChainIdError` first to tell the two
116
+ * apart.
117
+ */
113
118
  export class InvalidSimulationResponseError extends SimulationPackageError {
114
119
  name = "InvalidSimulationResponseError";
115
120
  code = "INVALID_SIMULATION_RESPONSE";
116
121
  }
122
+ /**
123
+ * The node rejected the request `chainId`: the endpoint is on a different
124
+ * chain than configured. This is misconfiguration, not an outage, so unlike
125
+ * {@link ExternalServiceError} callers must not bypass it.
126
+ * Extends {@link InvalidSimulationResponseError} and shares its `name` and
127
+ * `code`, so v5 callers that match on either still catch it;
128
+ * `instanceof InvalidChainIdError` is how to tell the two apart.
129
+ */
130
+ export class InvalidChainIdError extends InvalidSimulationResponseError {
131
+ }
117
132
  /** State needed to verify an operation could not be fetched or derived. */
118
133
  export class MissingVerificationEvidenceError extends SimulationPackageError {
119
134
  name = "MissingVerificationEvidenceError";
@@ -1,9 +1,9 @@
1
1
  export type { BlueAuthorization, BlueAuthorizationSignature, BlueAuthorizationTypedData, Eip712Domain, Eip712Field, Erc20ApprovalAuthorization, Erc2612PermitAuthorization, Erc2612PermitTypedData, Permit2TransferAuthorization, Permit2TransferTypedData, SimulationAuthorization, } from "./authorizations.js";
2
2
  export type { RetainedAsset, SimulationErrorCode, SimulationErrorContext, SimulationExecutionContext, SimulationExecutionReason, SimulationPreparationContext, SimulationStage, SimulationTransportContext, SimulationValidationContext, SimulationVerificationContext, } from "./errors.js";
3
- export { AuthorizationRequestMismatchError, BlacklistViolationError, ConsumerLimitViolationError, ExternalServiceError, InvalidSimulationResponseError, isSimulationPackageError, MissingVerificationEvidenceError, SIMULATION_ERROR_CODES, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnexpectedSimulationError, UnsupportedChainError, UnsupportedOperationError, } from "./errors.js";
3
+ export { AuthorizationRequestMismatchError, BlacklistViolationError, ConsumerLimitViolationError, ExternalServiceError, InvalidChainIdError, InvalidSimulationResponseError, isSimulationPackageError, MissingVerificationEvidenceError, SIMULATION_ERROR_CODES, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnexpectedSimulationError, UnsupportedChainError, UnsupportedOperationError, } from "./errors.js";
4
4
  export type { BlueMarketOperationSubject, BlueMarketOperationType, BlueRefinanceSubject, OperationLimit, OperationType, SimulationLimits, SimulationOperationSubject, SlippageLimits, SlippageQuote, VaultOperationSubject, VaultOperationType, VaultV1MigrateToV2Subject, } from "./limits.js";
5
5
  export { BLUE_MARKET_OPERATION_TYPES, OPERATION_TYPES, VAULT_OPERATION_TYPES, } from "./limits.js";
6
- export type { SimulateParams, SimulationMode, } from "./params.js";
6
+ export type { SimulateParams, SimulationMode, StateBlock, } from "./params.js";
7
7
  export { SIMULATION_MODES } from "./params.js";
8
8
  export { toSimulationAuthorizations } from "./requirements/index.js";
9
9
  export type { AuthorizationPreparation, SimulatedOperation, SimulationVerification, VerifiedSimulationResult, } from "./result.js";
package/lib/esm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Public fns (via feature-folder barrels)
2
2
  // Errors (for instanceof checks by consumers)
3
- export { AuthorizationRequestMismatchError, BlacklistViolationError, ConsumerLimitViolationError, ExternalServiceError, InvalidSimulationResponseError, isSimulationPackageError, MissingVerificationEvidenceError, SIMULATION_ERROR_CODES, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnexpectedSimulationError, UnsupportedChainError, UnsupportedOperationError, } from "./errors.js";
3
+ export { AuthorizationRequestMismatchError, BlacklistViolationError, ConsumerLimitViolationError, ExternalServiceError, InvalidChainIdError, InvalidSimulationResponseError, isSimulationPackageError, MissingVerificationEvidenceError, SIMULATION_ERROR_CODES, SimulationPackageError, SimulationRevertedError, SimulationValidationError, UnexpectedSimulationError, UnsupportedChainError, UnsupportedOperationError, } from "./errors.js";
4
4
  export { BLUE_MARKET_OPERATION_TYPES, OPERATION_TYPES, VAULT_OPERATION_TYPES, } from "./limits.js";
5
5
  export { SIMULATION_MODES } from "./params.js";
6
6
  export { toSimulationAuthorizations } from "./requirements/index.js";
@@ -1,4 +1,4 @@
1
- import type { BlockTag } from "viem";
1
+ import type { BlockTag, Hex } from "viem";
2
2
  import type { SimulationAuthorization } from "./authorizations.js";
3
3
  import type { SimulationLimits } from "./limits.js";
4
4
  import type { SimulationTransaction } from "./types.js";
@@ -6,6 +6,15 @@ import type { SimulationTransaction } from "./types.js";
6
6
  export declare const SIMULATION_MODES: readonly ["preview", "final"];
7
7
  /** Simulation mode: `"preview"` accepts pending authorizations, `"final"` does not. */
8
8
  export type SimulationMode = (typeof SIMULATION_MODES)[number];
9
+ /** A mined block the simulation runs on top of. */
10
+ export interface StateBlock {
11
+ /** Block number the simulation runs on top of. */
12
+ readonly number: bigint;
13
+ /** Hash of that block; compared to the next block's `parentHash`. */
14
+ readonly hash: Hex;
15
+ /** Block timestamp in Unix seconds; the simulated block must not be earlier. */
16
+ readonly timestamp: bigint;
17
+ }
9
18
  /**
10
19
  * Input to `simulate()`. `authorizations` is accepted only in "preview";
11
20
  * `mode` defaults to "final".
@@ -21,6 +30,16 @@ export interface SimulateParams {
21
30
  * "latest" block is not final). Only canonical (mined) blocks can be
22
31
  * pinned; `pending` has no stable hash and is rejected at runtime. */
23
32
  readonly blockNumber?: bigint | Exclude<BlockTag, "pending">;
33
+ /** State block supplied by the caller, so `simulate()` skips the block
34
+ * lookup; with no asset metadata reads it then sends only `eth_simulateV1`.
35
+ * Cannot be combined with `blockNumber`.
36
+ * Not checked against the endpoint: `hash` is compared only when the node
37
+ * reports the next block (its `parentHash`) and `parentHashCheck` is on (off
38
+ * by default on Stable, 988), and `timestamp` only bounds the simulated
39
+ * block's timestamp. On Monad (143), supply a finalized block: `latest` is
40
+ * not final there, so the block at that number can change and fail the
41
+ * `parentHash` check. */
42
+ readonly block?: StateBlock;
24
43
  /** Caller-selected quotes and percentage tolerances. Omitted limits are unchecked. */
25
44
  readonly limits?: SimulationLimits;
26
45
  }
@@ -130,6 +130,7 @@ export function toSimulationAuthorizations(params) {
130
130
  }
131
131
  if (isRequirementSignature(requirement)) {
132
132
  const { action } = requirement;
133
+ const actionType = action.type;
133
134
  switch (action.type) {
134
135
  case "permit":
135
136
  return toErc2612Permit(action, ctx);
@@ -138,7 +139,7 @@ export function toSimulationAuthorizations(params) {
138
139
  case "authorization":
139
140
  return toBlueAuthorizationSignature(action, ctx);
140
141
  default:
141
- return unsupported(ctx)(`Signature requirement action type "${action.type}" is not supported. Only permit, permit2SignatureTransfer, and authorization signatures are supported`);
142
+ return unsupported(ctx)(`Signature requirement action type "${actionType}" is not supported. Only permit, permit2SignatureTransfer, and authorization signatures are supported`);
142
143
  }
143
144
  }
144
145
  return unsupported(ctx)(`Requirement action type "${requirement.action.type}" is not supported. Only erc20Approval, blueAuthorization, permit, permit2SignatureTransfer, and authorization requirements are supported`);