@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
package/README.md CHANGED
@@ -46,11 +46,13 @@ try {
46
46
  }
47
47
  ```
48
48
 
49
- Every chain entry requires `simulateV1Url`, pointing to a JSON-RPC endpoint that supports `eth_simulateV1`. Requests send `validation: false` so gas is not charged. Monad (chain 143) is handled internally: its nodes reject `false`, so it sends `true` (its simulated block charges no gas either way), and it pins to the `finalized` block by default because its `latest` block is not final. On Monad, `gasUsed` reports the call's gas limit, not the gas consumed. Execution uses the full `timeoutMs` budget (default 5000 ms), with no retries or provider fallback. RPC failures, timeouts and reverts throw typed errors. The optional logger still reports parsing and retention warnings.
49
+ Every call inside `eth_simulateV1`, including preparation calls and state reads, carries the request's `chainId` as a hex quantity, so nodes that check it (geth, Anvil, Monad and Stable do) reject an endpoint on another chain with `InvalidChainIdError`.
50
+
51
+ Every chain entry requires `simulateV1Url`, pointing to a JSON-RPC endpoint that supports `eth_simulateV1`. Requests send `validation: false` so gas is not charged. Monad (chain 143) is handled internally: its nodes reject `false`, so it sends `true` (its simulated block charges no gas either way), and it pins to the `finalized` block by default because its `latest` block is not final — supply a finalized block when passing `block`. On Monad, `gasUsed` reports the call's gas limit, not the gas consumed. Set `blockOverrides.gasLimit` on a chain entry to send that gas limit as the simulated block's `blockOverrides.gasLimit`; without it, no block override is sent. Set `parentHashCheck` on a chain entry to turn on or off the check that the simulated block's `parentHash` is the pinned block hash. It defaults to off on Stable (chain 988), whose nodes report a `parentHash` that never matches the pinned block, and on for every other chain. The block number and timestamp checks always apply. Execution uses the full `timeoutMs` budget (default 5000 ms), with no retries or provider fallback. RPC failures, timeouts and reverts throw typed errors. The optional logger still reports parsing and retention warnings.
50
52
 
51
53
  Native-ETH movements are observed through `traceTransfers` logs on the simulated calls — no `stateOverrides` or helper contracts are injected.
52
54
 
53
- Upgrading from v4? See the [v4 → v5 migration guide](../../docs/migrations/evm-simulation-v4-to-v5.md) for the backend cutover.
55
+ Upgrading from v4? See the [v4 → v5 migration guide](../../docs/migrations/evm-simulation-v4-to-v5.md) for the backend cutover. Upgrading from v5? See the [v5 → v6 migration guide](../../docs/migrations/evm-simulation-v5-to-v6.md) for the retention-guard address change.
54
56
 
55
57
  ### API surface
56
58
 
@@ -58,12 +60,12 @@ All symbols below are re-exported from the package root.
58
60
 
59
61
  - `simulate(config, params)` — run a bundle through the simulation pipeline.
60
62
  - Config types: `SimulationConfig`, `ChainSimulationConfig`, `SimulationLogger`.
61
- - Input types: `SimulateParams` (v5 shape: `mode`, `SimulationAuthorization` requests, `SimulationLimits`), `SimulationMode`, `SimulationTransaction`.
63
+ - Input types: `SimulateParams` (v5 shape: `mode`, `SimulationAuthorization` requests, `SimulationLimits`), `SimulationMode`, `SimulationTransaction`, `StateBlock` (the optional `SimulateParams.block`: `{ number, hash, timestamp }`).
62
64
  - `toSimulationAuthorizations({ chainId, mode, blockNumber, owner, requirements })` — map morpho-sdk `ActionRequirement[]` onto `SimulationAuthorization[]` straight from `action.args` — nothing is decoded or validated; validation happens in `simulate()`'s request parser.
63
65
  - Authorizations and limits: `SimulationAuthorization` and its members (`Erc20ApprovalAuthorization`, `Erc2612PermitAuthorization`, `Permit2TransferAuthorization`, `BlueAuthorization`, `BlueAuthorizationSignature`) with their EIP-712 payloads (`Eip712Domain`, `Eip712Field`, `Erc2612PermitTypedData`, `Permit2TransferTypedData`, `BlueAuthorizationTypedData`); `SimulationLimits`, `OperationLimit`, and the shared `SlippageLimits` and `SlippageQuote`.
64
66
  - Slippage-check result types: `VerifiedSimulationResult`, `SimulationVerification`, `SimulatedOperation`, `AuthorizationPreparation`.
65
67
  - Result types: `SimulationResult`, `SimulationCall`, `Transfer`, `AccountAssetChanges`, `AssetChange`, `RawLog`.
66
- - Errors: `SimulationPackageError` (abstract base — `instanceof` it to catch any package error), `SimulationRevertedError`, `BlacklistViolationError`, `ExternalServiceError`, `SimulationValidationError`, `UnsupportedChainError`, and the verification errors `UnsupportedOperationError`, `InvalidSimulationResponseError`, `MissingVerificationEvidenceError`, `AuthorizationRequestMismatchError`, `ConsumerLimitViolationError`, `UnexpectedSimulationError`.
68
+ - Errors: `SimulationPackageError` (abstract base — `instanceof` it to catch any package error), `SimulationRevertedError`, `BlacklistViolationError`, `ExternalServiceError`, `SimulationValidationError`, `UnsupportedChainError`, and the verification errors `UnsupportedOperationError`, `InvalidSimulationResponseError`, `InvalidChainIdError`, `MissingVerificationEvidenceError`, `AuthorizationRequestMismatchError`, `ConsumerLimitViolationError`, `UnexpectedSimulationError`.
67
69
  - Error helpers: `SIMULATION_ERROR_CODES` / `SimulationErrorCode` (every `error.code`), `SimulationErrorContext` (frozen `error.context`; union of the per-stage `SimulationValidationContext`, `SimulationTransportContext`, `SimulationPreparationContext`, `SimulationExecutionContext`, `SimulationVerificationContext`), `SimulationStage`, `SimulationExecutionReason` (`SimulationRevertedError.reasonCode`), `isSimulationPackageError` (structural guard narrowing to `SimulationPackageError`), `RetainedAsset`.
68
70
  - Verification vocabulary: `SIMULATION_MODES` / `SimulationMode`, `OPERATION_TYPES` / `OperationType`, `BLUE_MARKET_OPERATION_TYPES` / `BlueMarketOperationType`, `VAULT_OPERATION_TYPES` / `VaultOperationType`, `SimulationOperationSubject` and its members `BlueMarketOperationSubject`, `BlueRefinanceSubject`, `VaultOperationSubject`, `VaultV1MigrateToV2Subject` (operation groups and the operation-keyed subject union that key limit-bound verification `SimulationErrorContext`s).
69
71
  ### Optional limits
@@ -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/cjs/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UnexpectedSimulationError = exports.ConsumerLimitViolationError = exports.AuthorizationRequestMismatchError = exports.MissingVerificationEvidenceError = exports.InvalidSimulationResponseError = exports.UnsupportedOperationError = exports.UnsupportedChainError = exports.SimulationValidationError = exports.ExternalServiceError = exports.BlacklistViolationError = exports.SimulationRevertedError = exports.SimulationPackageError = exports.SIMULATION_ERROR_CODES = void 0;
3
+ exports.UnexpectedSimulationError = exports.ConsumerLimitViolationError = exports.AuthorizationRequestMismatchError = exports.MissingVerificationEvidenceError = exports.InvalidChainIdError = exports.InvalidSimulationResponseError = exports.UnsupportedOperationError = exports.UnsupportedChainError = exports.SimulationValidationError = exports.ExternalServiceError = exports.BlacklistViolationError = exports.SimulationRevertedError = exports.SimulationPackageError = exports.SIMULATION_ERROR_CODES = void 0;
4
4
  exports.isSimulationPackageError = isSimulationPackageError;
5
5
  const limits_js_1 = require("./limits.js");
6
6
  const params_js_1 = require("./params.js");
@@ -66,7 +66,7 @@ class SimulationRevertedError extends SimulationPackageError {
66
66
  exports.SimulationRevertedError = SimulationRevertedError;
67
67
  /**
68
68
  * Thrown when net value above the dust threshold is retained by a restricted bundles contract
69
- * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or MidnightBundlesV1) after simulation;
69
+ * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1 or MidnightBundlesV2) after simulation;
70
70
  * pass-through flows and net outflows are allowed. Never bypassable.
71
71
  */
72
72
  class BlacklistViolationError extends SimulationPackageError {
@@ -120,12 +120,28 @@ class UnsupportedOperationError extends SimulationPackageError {
120
120
  code = "UNSUPPORTED_OPERATION";
121
121
  }
122
122
  exports.UnsupportedOperationError = UnsupportedOperationError;
123
- /** The simulation backend returned a response that cannot be parsed. */
123
+ /**
124
+ * The simulation backend returned a response that cannot be parsed.
125
+ * {@link InvalidChainIdError} subclasses this class and shares its `name`
126
+ * and `code`; check `instanceof InvalidChainIdError` first to tell the two
127
+ * apart.
128
+ */
124
129
  class InvalidSimulationResponseError extends SimulationPackageError {
125
130
  name = "InvalidSimulationResponseError";
126
131
  code = "INVALID_SIMULATION_RESPONSE";
127
132
  }
128
133
  exports.InvalidSimulationResponseError = InvalidSimulationResponseError;
134
+ /**
135
+ * The node rejected the request `chainId`: the endpoint is on a different
136
+ * chain than configured. This is misconfiguration, not an outage, so unlike
137
+ * {@link ExternalServiceError} callers must not bypass it.
138
+ * Extends {@link InvalidSimulationResponseError} and shares its `name` and
139
+ * `code`, so v5 callers that match on either still catch it;
140
+ * `instanceof InvalidChainIdError` is how to tell the two apart.
141
+ */
142
+ class InvalidChainIdError extends InvalidSimulationResponseError {
143
+ }
144
+ exports.InvalidChainIdError = InvalidChainIdError;
129
145
  /** State needed to verify an operation could not be fetched or derived. */
130
146
  class MissingVerificationEvidenceError extends SimulationPackageError {
131
147
  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/cjs/index.js CHANGED
@@ -1,13 +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.toSimulationAuthorizations = exports.SIMULATION_MODES = exports.VAULT_OPERATION_TYPES = exports.OPERATION_TYPES = exports.BLUE_MARKET_OPERATION_TYPES = exports.UnsupportedOperationError = exports.UnsupportedChainError = exports.UnexpectedSimulationError = exports.SimulationValidationError = exports.SimulationRevertedError = exports.SimulationPackageError = exports.SIMULATION_ERROR_CODES = exports.MissingVerificationEvidenceError = exports.isSimulationPackageError = exports.InvalidSimulationResponseError = exports.ExternalServiceError = exports.ConsumerLimitViolationError = exports.BlacklistViolationError = exports.AuthorizationRequestMismatchError = void 0;
4
+ exports.simulate = exports.toSimulationAuthorizations = exports.SIMULATION_MODES = exports.VAULT_OPERATION_TYPES = exports.OPERATION_TYPES = exports.BLUE_MARKET_OPERATION_TYPES = exports.UnsupportedOperationError = exports.UnsupportedChainError = exports.UnexpectedSimulationError = exports.SimulationValidationError = exports.SimulationRevertedError = exports.SimulationPackageError = exports.SIMULATION_ERROR_CODES = exports.MissingVerificationEvidenceError = exports.isSimulationPackageError = exports.InvalidSimulationResponseError = exports.InvalidChainIdError = exports.ExternalServiceError = exports.ConsumerLimitViolationError = exports.BlacklistViolationError = exports.AuthorizationRequestMismatchError = void 0;
5
5
  // Errors (for instanceof checks by consumers)
6
6
  var errors_js_1 = require("./errors.js");
7
7
  Object.defineProperty(exports, "AuthorizationRequestMismatchError", { enumerable: true, get: function () { return errors_js_1.AuthorizationRequestMismatchError; } });
8
8
  Object.defineProperty(exports, "BlacklistViolationError", { enumerable: true, get: function () { return errors_js_1.BlacklistViolationError; } });
9
9
  Object.defineProperty(exports, "ConsumerLimitViolationError", { enumerable: true, get: function () { return errors_js_1.ConsumerLimitViolationError; } });
10
10
  Object.defineProperty(exports, "ExternalServiceError", { enumerable: true, get: function () { return errors_js_1.ExternalServiceError; } });
11
+ Object.defineProperty(exports, "InvalidChainIdError", { enumerable: true, get: function () { return errors_js_1.InvalidChainIdError; } });
11
12
  Object.defineProperty(exports, "InvalidSimulationResponseError", { enumerable: true, get: function () { return errors_js_1.InvalidSimulationResponseError; } });
12
13
  Object.defineProperty(exports, "isSimulationPackageError", { enumerable: true, get: function () { return errors_js_1.isSimulationPackageError; } });
13
14
  Object.defineProperty(exports, "MissingVerificationEvidenceError", { enumerable: true, get: function () { return errors_js_1.MissingVerificationEvidenceError; } });
@@ -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
  }
@@ -133,6 +133,7 @@ function toSimulationAuthorizations(params) {
133
133
  }
134
134
  if ((0, morpho_sdk_1.isRequirementSignature)(requirement)) {
135
135
  const { action } = requirement;
136
+ const actionType = action.type;
136
137
  switch (action.type) {
137
138
  case "permit":
138
139
  return toErc2612Permit(action, ctx);
@@ -141,7 +142,7 @@ function toSimulationAuthorizations(params) {
141
142
  case "authorization":
142
143
  return toBlueAuthorizationSignature(action, ctx);
143
144
  default:
144
- return unsupported(ctx)(`Signature requirement action type "${action.type}" is not supported. Only permit, permit2SignatureTransfer, and authorization signatures are supported`);
145
+ return unsupported(ctx)(`Signature requirement action type "${actionType}" is not supported. Only permit, permit2SignatureTransfer, and authorization signatures are supported`);
145
146
  }
146
147
  }
147
148
  return unsupported(ctx)(`Requirement action type "${requirement.action.type}" is not supported. Only erc20Approval, blueAuthorization, permit, permit2SignatureTransfer, and authorization requirements are supported`);
@@ -1,63 +1,53 @@
1
- import { type Client, type PublicClient } from "viem";
2
- import type { SimulationMode } from "../../params.js";
1
+ import { type Client } from "viem";
2
+ import type { StateBlock } from "../../params.js";
3
3
  import type { ExecutionPlan } from "../plan/plan-execution.js";
4
4
  import type { SimulationExecution } from "./parse-response.js";
5
- import type { PinnedBlock } from "./resolve-pinned-block.js";
6
5
  /** The JSON-RPC methods this boundary calls. */
7
- type RpcLabel = "eth_getBlock" | "eth_chainId" | "eth_simulateV1";
6
+ type RpcLabel = "eth_getBlock" | "eth_simulateV1";
8
7
  /** Run one RPC call; anything thrown becomes a typed boundary error. @internal */
9
8
  export declare const rpc: <T>(label: RpcLabel, call: () => Promise<T>) => Promise<T>;
10
- /** Validate the endpoint identity through the same URL-safe RPC boundary. @internal */
11
- export declare function assertEndpointChain(params: {
12
- readonly client: Pick<PublicClient, "getChainId">;
13
- readonly chainId: number;
14
- readonly mode: SimulationMode;
15
- readonly blockNumber?: bigint;
16
- }): Promise<void>;
17
9
  /**
18
10
  * Execute an {@link ExecutionPlan} through a single `eth_simulateV1` call and
19
- * collect the pinned execution.
11
+ * collect the pinned execution. This is the boundary's only RPC request.
20
12
  *
21
- * The caller checks the endpoint chain identity and resolves the pinned state
22
- * block before this boundary. This function uses that same block for the
23
- * simulation and reorg check under the shared abort/timeout budget.
24
- *
25
- * The boundary performs these steps:
13
+ * The caller resolves (or supplies) the state block before this boundary.
26
14
  *
27
15
  * 1. **`eth_simulateV1`** — one `blockStateCalls` entry carrying the planned
28
- * calls with their per-call `from`, `traceTransfers: true` so the node
29
- * synthesizes native-ETH moves as transfer logs, and `validation: false`
16
+ * calls with their per-call `from` and `chainId`, `traceTransfers: true`
17
+ * so the node synthesizes native-ETH moves as transfer logs, and `validation: false`
30
18
  * (`true` on Monad, whose nodes reject `false` and charge no gas in the
31
19
  * simulated block). Validation-off means gas is not charged, which is how
32
- * gas is separated from economic effects.
20
+ * gas is separated from economic effects. When `blockGasLimit` is set,
21
+ * it is sent as the block's `blockOverrides.gasLimit`; otherwise no block
22
+ * override is sent.
33
23
  * **No balance override is
34
24
  * applied** — `value` transfers are funded by the sender's real native
35
25
  * balance.
36
- * 2. **Response validation** — the response is parsed before the reorg
37
- * re-fetch so revert/mismatch evidence already in hand surfaces instead
38
- * of being downgraded to a bypassable {@link ExternalServiceError} by a
39
- * failing re-fetch. The simulated block must be exactly
26
+ * 2. **Response validation** — the simulated block must be exactly
40
27
  * `stateBlockNumber` or `stateBlockNumber + 1`: geth-style nodes report
41
28
  * the former's successor while Anvil reports the pinned block itself.
42
29
  * The result records whatever the node returns; consumers must read
43
30
  * {@link ExecutionBlock.blockNumber} and never assume +1.
44
- * 3. **Reorg check** — the pinned state block is re-fetched last to detect a reorg
45
- * that swapped its hash mid-flight (`InvalidSimulationResponseError`).
46
31
  *
47
32
  * The endpoint must support `eth_simulateV1` with per-call `from`; there is
48
33
  * no fallback backend.
49
34
  *
50
- * @param params - Shared simulation client, execution plan, already-pinned
51
- * state block, and the `validation` flag to send.
35
+ * @param params - Shared simulation client, execution plan (its request
36
+ * `chainId` is sent on every call), state block, the `validation` flag to
37
+ * send, the optional simulated block gas limit, and whether to check the
38
+ * successor `parentHash`.
52
39
  * @returns Deep-frozen {@link SimulationExecution} — per-transaction call
53
40
  * results and the resolved {@link ExecutionBlock}.
54
41
  * @throws {ExternalServiceError} For transport failures, timeouts,
55
42
  * or malformed JSON-RPC envelopes.
56
43
  * @throws {InvalidSimulationResponseError} For a response that cannot be
57
- * trusted (bad shape, call-count mismatch, block
58
- * other than the pinned state block or its successor, a block timestamp
59
- * earlier than the pinned block's, a per-call result that fails normalization,
60
- * or a state-block hash that changed or is no longer served mid-flight).
44
+ * trusted (bad shape, call-count mismatch, block other than the state block
45
+ * or its successor, a block timestamp earlier than the state block's, or a
46
+ * per-call result that fails normalization).
47
+ * @throws {InvalidChainIdError} When the node rejects the request `chainId`
48
+ * because it serves another chain (a subclass of
49
+ * `InvalidSimulationResponseError` sharing its `name`/`code`;
50
+ * `instanceof InvalidChainIdError` tells them apart).
61
51
  * @throws {MissingVerificationEvidenceError} When a planned state read fails.
62
52
  * @throws {SimulationRevertedError} When a preparation or user transaction
63
53
  * reverts or the node reports a bundle-level revert (code 3 / insufficient
@@ -65,9 +55,11 @@ export declare function assertEndpointChain(params: {
65
55
  * @internal
66
56
  */
67
57
  export declare function executePlan(params: {
68
- client: Client & Pick<PublicClient, "getBlock">;
58
+ client: Client;
69
59
  plan: ExecutionPlan;
70
- stateBlock: PinnedBlock;
60
+ stateBlock: StateBlock;
71
61
  validation: boolean;
62
+ blockGasLimit?: bigint | undefined;
63
+ parentHashCheck: boolean;
72
64
  }): Promise<SimulationExecution>;
73
65
  export {};
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rpc = void 0;
4
- exports.assertEndpointChain = assertEndpointChain;
5
4
  exports.executePlan = executePlan;
6
5
  const viem_1 = require("viem");
7
6
  const errors_js_1 = require("../../errors.js");
@@ -44,6 +43,15 @@ const toBoundaryError = (label, error) => {
44
43
  reverted.cause = error;
45
44
  return reverted;
46
45
  }
46
+ // A wrong-chain endpoint is misconfiguration, not an outage: callers must
47
+ // not bypass it like an `ExternalServiceError`. Each node words the per-call
48
+ // `chainId` rejection differently: geth "chainId does not match node's",
49
+ // Anvil "invalid chain id for signer", Monad "Invalid chain ID: expected …".
50
+ if (label === "eth_simulateV1" &&
51
+ error instanceof Error &&
52
+ /chainId does not match node's|invalid chain id/i.test(error.message)) {
53
+ return new errors_js_1.InvalidChainIdError(`eth_simulateV1 endpoint rejected the request chainId: ${safeMessage(error)}. Point SimulationConfig.chains at an RPC URL for this chain.`, { cause: error });
54
+ }
47
55
  return new errors_js_1.ExternalServiceError(`${label} error: ${safeMessage(error)}`, {
48
56
  cause: error,
49
57
  });
@@ -58,69 +66,48 @@ const rpc = async (label, call) => {
58
66
  }
59
67
  };
60
68
  exports.rpc = rpc;
61
- /** Validate the endpoint identity through the same URL-safe RPC boundary. @internal */
62
- async function assertEndpointChain(params) {
63
- const { client, chainId, mode, blockNumber } = params;
64
- const rpcChainId = await (0, exports.rpc)("eth_chainId", () => client.getChainId());
65
- if (rpcChainId === chainId)
66
- return;
67
- throw new errors_js_1.InvalidSimulationResponseError(`The RPC configured for chain ${chainId} reports chain ${rpcChainId}. Fix SimulationConfig.chains.`, {
68
- ...(blockNumber !== undefined
69
- ? {
70
- context: {
71
- stage: "transport",
72
- chainId,
73
- mode,
74
- blockNumber,
75
- },
76
- }
77
- : {}),
78
- });
79
- }
80
69
  /**
81
70
  * Execute an {@link ExecutionPlan} through a single `eth_simulateV1` call and
82
- * collect the pinned execution.
83
- *
84
- * The caller checks the endpoint chain identity and resolves the pinned state
85
- * block before this boundary. This function uses that same block for the
86
- * simulation and reorg check under the shared abort/timeout budget.
71
+ * collect the pinned execution. This is the boundary's only RPC request.
87
72
  *
88
- * The boundary performs these steps:
73
+ * The caller resolves (or supplies) the state block before this boundary.
89
74
  *
90
75
  * 1. **`eth_simulateV1`** — one `blockStateCalls` entry carrying the planned
91
- * calls with their per-call `from`, `traceTransfers: true` so the node
92
- * synthesizes native-ETH moves as transfer logs, and `validation: false`
76
+ * calls with their per-call `from` and `chainId`, `traceTransfers: true`
77
+ * so the node synthesizes native-ETH moves as transfer logs, and `validation: false`
93
78
  * (`true` on Monad, whose nodes reject `false` and charge no gas in the
94
79
  * simulated block). Validation-off means gas is not charged, which is how
95
- * gas is separated from economic effects.
80
+ * gas is separated from economic effects. When `blockGasLimit` is set,
81
+ * it is sent as the block's `blockOverrides.gasLimit`; otherwise no block
82
+ * override is sent.
96
83
  * **No balance override is
97
84
  * applied** — `value` transfers are funded by the sender's real native
98
85
  * balance.
99
- * 2. **Response validation** — the response is parsed before the reorg
100
- * re-fetch so revert/mismatch evidence already in hand surfaces instead
101
- * of being downgraded to a bypassable {@link ExternalServiceError} by a
102
- * failing re-fetch. The simulated block must be exactly
86
+ * 2. **Response validation** — the simulated block must be exactly
103
87
  * `stateBlockNumber` or `stateBlockNumber + 1`: geth-style nodes report
104
88
  * the former's successor while Anvil reports the pinned block itself.
105
89
  * The result records whatever the node returns; consumers must read
106
90
  * {@link ExecutionBlock.blockNumber} and never assume +1.
107
- * 3. **Reorg check** — the pinned state block is re-fetched last to detect a reorg
108
- * that swapped its hash mid-flight (`InvalidSimulationResponseError`).
109
91
  *
110
92
  * The endpoint must support `eth_simulateV1` with per-call `from`; there is
111
93
  * no fallback backend.
112
94
  *
113
- * @param params - Shared simulation client, execution plan, already-pinned
114
- * state block, and the `validation` flag to send.
95
+ * @param params - Shared simulation client, execution plan (its request
96
+ * `chainId` is sent on every call), state block, the `validation` flag to
97
+ * send, the optional simulated block gas limit, and whether to check the
98
+ * successor `parentHash`.
115
99
  * @returns Deep-frozen {@link SimulationExecution} — per-transaction call
116
100
  * results and the resolved {@link ExecutionBlock}.
117
101
  * @throws {ExternalServiceError} For transport failures, timeouts,
118
102
  * or malformed JSON-RPC envelopes.
119
103
  * @throws {InvalidSimulationResponseError} For a response that cannot be
120
- * trusted (bad shape, call-count mismatch, block
121
- * other than the pinned state block or its successor, a block timestamp
122
- * earlier than the pinned block's, a per-call result that fails normalization,
123
- * or a state-block hash that changed or is no longer served mid-flight).
104
+ * trusted (bad shape, call-count mismatch, block other than the state block
105
+ * or its successor, a block timestamp earlier than the state block's, or a
106
+ * per-call result that fails normalization).
107
+ * @throws {InvalidChainIdError} When the node rejects the request `chainId`
108
+ * because it serves another chain (a subclass of
109
+ * `InvalidSimulationResponseError` sharing its `name`/`code`;
110
+ * `instanceof InvalidChainIdError` tells them apart).
124
111
  * @throws {MissingVerificationEvidenceError} When a planned state read fails.
125
112
  * @throws {SimulationRevertedError} When a preparation or user transaction
126
113
  * reverts or the node reports a bundle-level revert (code 3 / insufficient
@@ -128,14 +115,18 @@ async function assertEndpointChain(params) {
128
115
  * @internal
129
116
  */
130
117
  async function executePlan(params) {
131
- const { client, plan, stateBlock, validation } = params;
118
+ const { client, plan, stateBlock, validation, blockGasLimit, parentHashCheck, } = params;
132
119
  const response = await (0, exports.rpc)("eth_simulateV1", () => client.request({
133
120
  method: "eth_simulateV1",
134
121
  params: [
135
122
  {
136
123
  blockStateCalls: [
137
124
  {
125
+ ...(blockGasLimit !== undefined && {
126
+ blockOverrides: { gasLimit: (0, viem_1.numberToHex)(blockGasLimit) },
127
+ }),
138
128
  calls: plan.calls.map((call) => ({
129
+ chainId: (0, viem_1.numberToHex)(plan.request.chainId),
139
130
  from: call.transaction.from,
140
131
  to: call.transaction.to,
141
132
  data: call.transaction.data,
@@ -149,42 +140,12 @@ async function executePlan(params) {
149
140
  (0, viem_1.numberToHex)(stateBlock.number),
150
141
  ],
151
142
  }));
152
- // Response parsing is validation, not transport — it runs before the
153
- // reorg re-fetch so evidence already in hand reaches the caller as
154
- // InvalidSimulationResponseError / SimulationRevertedError instead of
155
- // being downgraded to a bypassable ExternalServiceError by a failing
156
- // re-fetch.
157
- const execution = (0, parse_response_js_1.parseSimulationResponse)({
143
+ return (0, parse_response_js_1.parseSimulationResponse)({
158
144
  plan,
159
145
  blocks: response,
160
146
  stateBlockNumber: stateBlock.number,
161
147
  stateBlockHash: stateBlock.hash,
162
148
  stateBlockTimestamp: stateBlock.timestamp,
149
+ parentHashCheck,
163
150
  });
164
- // Reorg window: the pinned state block must still carry the same hash
165
- // after simulation, or the result may describe a different chain tip.
166
- const stateBlockAfter = await (0, exports.rpc)("eth_getBlock", () => client.getBlock({ blockNumber: stateBlock.number })).catch((error) => {
167
- if (error instanceof errors_js_1.ExternalServiceError &&
168
- error.cause instanceof viem_1.BlockNotFoundError)
169
- return error;
170
- throw error;
171
- });
172
- if (stateBlockAfter instanceof errors_js_1.ExternalServiceError ||
173
- stateBlockAfter.hash !== stateBlock.hash) {
174
- const after = stateBlockAfter instanceof errors_js_1.ExternalServiceError
175
- ? "unavailable"
176
- : stateBlockAfter.hash;
177
- throw new errors_js_1.InvalidSimulationResponseError(`State block ${stateBlock.number} hash changed during simulation (reorg): ${stateBlock.hash} became ${after}. Re-submit the simulation.`, {
178
- context: {
179
- stage: "transport",
180
- chainId: plan.request.chainId,
181
- mode: plan.request.mode,
182
- blockNumber: stateBlock.number,
183
- },
184
- ...(stateBlockAfter instanceof errors_js_1.ExternalServiceError && {
185
- cause: stateBlockAfter.cause,
186
- }),
187
- });
188
- }
189
- return execution;
190
151
  }
@@ -1 +1 @@
1
- export { assertEndpointChain, executePlan } from "./eth-simulate-v1.js";
1
+ export { executePlan } from "./eth-simulate-v1.js";
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.executePlan = exports.assertEndpointChain = void 0;
3
+ exports.executePlan = void 0;
4
4
  var eth_simulate_v1_js_1 = require("./eth-simulate-v1.js");
5
- Object.defineProperty(exports, "assertEndpointChain", { enumerable: true, get: function () { return eth_simulate_v1_js_1.assertEndpointChain; } });
6
5
  Object.defineProperty(exports, "executePlan", { enumerable: true, get: function () { return eth_simulate_v1_js_1.executePlan; } });
@@ -65,5 +65,6 @@ export declare function parseSimulationResponse(params: {
65
65
  readonly stateBlockNumber: bigint;
66
66
  readonly stateBlockHash: Hex;
67
67
  readonly stateBlockTimestamp: bigint;
68
+ readonly parentHashCheck: boolean;
68
69
  }): SimulationExecution;
69
70
  export {};
@@ -57,8 +57,10 @@ function parseSimulationResponse(params) {
57
57
  }
58
58
  // Anvil re-hashes the pinned block, so only the geth-style successor can be
59
59
  // pinned by hash: it must report a parentHash (geth always does) equal to
60
- // the pinned state block hash.
61
- if (blockNumber === params.stateBlockNumber + 1n &&
60
+ // the pinned state block hash. With `parentHashCheck` off (Stable by
61
+ // default) the block number and timestamp checks still apply.
62
+ if (params.parentHashCheck &&
63
+ blockNumber === params.stateBlockNumber + 1n &&
62
64
  block.parentHash !== params.stateBlockHash) {
63
65
  throw new errors_js_1.InvalidSimulationResponseError(`eth_simulateV1 reported block ${blockNumber} whose parent ${String(block.parentHash)} is not the pinned state block hash ${params.stateBlockHash}; the node did not simulate on top of the pinned block.`, { context: errorContext });
64
66
  }
@@ -1,16 +1,10 @@
1
- import type { BlockTag, Client, Hex } from "viem";
2
- /** One concrete canonical block: the state anchor for every pinned read. @internal */
3
- export interface PinnedBlock {
4
- readonly number: bigint;
5
- readonly hash: Hex;
6
- readonly timestamp: bigint;
7
- }
1
+ import type { BlockTag, Client } from "viem";
2
+ import type { StateBlock } from "../../params.js";
8
3
  /**
9
4
  * Resolve the requested state block exactly once so `latest` cannot drift.
10
5
  *
11
- * Every downstream read and the `eth_simulateV1` call pin this number; the
12
- * execution boundary re-fetches the hash afterwards to detect a reorg that
13
- * swapped the block mid-flight.
6
+ * Every downstream read and the `eth_simulateV1` call pin this number. Skipped
7
+ * when the caller supplies `SimulateParams.block`.
14
8
  *
15
9
  * @param params - Resolution parameters.
16
10
  * @param params.client - Shared simulation client (created by
@@ -19,7 +13,7 @@ export interface PinnedBlock {
19
13
  * `latest`.
20
14
  * @param params.signal - Optional pipeline abort signal; checked around the
21
15
  * request so an aborted pipeline never produces a pin.
22
- * @returns The resolved {@link PinnedBlock}.
16
+ * @returns The resolved {@link StateBlock}.
23
17
  * @throws {ExternalServiceError} For transport failures, timeouts, or an
24
18
  * aborted signal.
25
19
  * @throws {InvalidSimulationResponseError} When the node returns a block
@@ -30,4 +24,4 @@ export declare function resolvePinnedBlock(params: {
30
24
  readonly client: Client;
31
25
  readonly blockNumber?: bigint | BlockTag;
32
26
  readonly signal?: AbortSignal;
33
- }): Promise<PinnedBlock>;
27
+ }): Promise<StateBlock>;
@@ -7,9 +7,8 @@ const eth_simulate_v1_js_1 = require("./eth-simulate-v1.js");
7
7
  /**
8
8
  * Resolve the requested state block exactly once so `latest` cannot drift.
9
9
  *
10
- * Every downstream read and the `eth_simulateV1` call pin this number; the
11
- * execution boundary re-fetches the hash afterwards to detect a reorg that
12
- * swapped the block mid-flight.
10
+ * Every downstream read and the `eth_simulateV1` call pin this number. Skipped
11
+ * when the caller supplies `SimulateParams.block`.
13
12
  *
14
13
  * @param params - Resolution parameters.
15
14
  * @param params.client - Shared simulation client (created by
@@ -18,7 +17,7 @@ const eth_simulate_v1_js_1 = require("./eth-simulate-v1.js");
18
17
  * `latest`.
19
18
  * @param params.signal - Optional pipeline abort signal; checked around the
20
19
  * request so an aborted pipeline never produces a pin.
21
- * @returns The resolved {@link PinnedBlock}.
20
+ * @returns The resolved {@link StateBlock}.
22
21
  * @throws {ExternalServiceError} For transport failures, timeouts, or an
23
22
  * aborted signal.
24
23
  * @throws {InvalidSimulationResponseError} When the node returns a block
@@ -8,7 +8,7 @@ interface AssertNoBundlesRetentionParams {
8
8
  /**
9
9
  * Assert that no value is retained by standalone `bundles` periphery contracts
10
10
  * (`VaultExitBundlesV1`, `VaultBundlesV1`, `BlueBundlesV1`) or by
11
- * `MidnightBundlesV1` (`midnightBundles`).
11
+ * `MidnightBundlesV2` (`midnightBundlesV2`).
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 →
@@ -33,7 +33,7 @@ interface AssertNoBundlesRetentionParams {
33
33
  *
34
34
  * @internal Internal pipeline stage, composed by `simulate`; not part of the
35
35
  * package's public API (only re-exported from the internal pipeline barrel).
36
- * @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundles`
36
+ * @param params.chainId - Chain whose blue-sdk `bundles` and `midnightBundlesV2`
37
37
  * registry entries define the restricted address set. Chains cataloging
38
38
  * neither are skipped (a `logger.warn` records the skip).
39
39
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
@@ -45,7 +45,7 @@ interface AssertNoBundlesRetentionParams {
45
45
  * @returns Nothing. Returns silently when no restricted contract retains value
46
46
  * above `DUST_THRESHOLD`; otherwise throws.
47
47
  * @throws {BlacklistViolationError} when net inbound flow to a restricted
48
- * `bundles` or `midnightBundles` contract exceeds `DUST_THRESHOLD` for any
48
+ * `bundles` or `midnightBundlesV2` contract exceeds `DUST_THRESHOLD` for any
49
49
  * token.
50
50
  */
51
51
  export declare function assertNoBundlesRetention(params: AssertNoBundlesRetentionParams): void;