@morpho-org/evm-simulation 5.0.0 → 5.0.1

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.
package/README.md CHANGED
@@ -46,11 +46,11 @@ try {
46
46
  }
47
47
  ```
48
48
 
49
- Every chain entry requires `simulateV1Url`, pointing to a JSON-RPC endpoint that supports `eth_simulateV1`. 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 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.
50
50
 
51
51
  Native-ETH movements are observed through `traceTransfers` logs on the simulated calls — no `stateOverrides` or helper contracts are injected.
52
52
 
53
- This is the unreleased v5 integration stack. See the [v4 → v5 migration guide](../../docs/migrations/evm-simulation-v4-to-v5.md) for the backend cutover and remaining release gates.
53
+ Upgrading from v4? See the [v4 → v5 migration guide](../../docs/migrations/evm-simulation-v4-to-v5.md) for the backend cutover.
54
54
 
55
55
  ### API surface
56
56
 
@@ -17,7 +17,8 @@ export interface SimulateParams {
17
17
  readonly mode?: SimulationMode;
18
18
  /** Pending wallet requests, in order. Preview only; rejected in final. */
19
19
  readonly authorizations?: readonly SimulationAuthorization[];
20
- /** Resolved once; defaults to "latest". Only canonical (mined) blocks can be
20
+ /** Resolved once; defaults to "latest" ("finalized" on Monad, whose
21
+ * "latest" block is not final). Only canonical (mined) blocks can be
21
22
  * pinned; `pending` has no stable hash and is rejected at runtime. */
22
23
  readonly blockNumber?: bigint | Exclude<BlockTag, "pending">;
23
24
  /** Caller-selected quotes and percentage tolerances. Omitted limits are unchecked. */
@@ -26,9 +26,11 @@ export declare function assertEndpointChain(params: {
26
26
  *
27
27
  * 1. **`eth_simulateV1`** — one `blockStateCalls` entry carrying the planned
28
28
  * calls with their per-call `from`, `traceTransfers: true` so the node
29
- * synthesizes native-ETH moves as transfer logs, and
30
- * `validation: false`. Validation-off means gas is not charged, which is
31
- * how gas is separated from economic effects. **No balance override is
29
+ * synthesizes native-ETH moves as transfer logs, and `validation: false`
30
+ * (`true` on Monad, whose nodes reject `false` and charge no gas in the
31
+ * simulated block). Validation-off means gas is not charged, which is how
32
+ * gas is separated from economic effects.
33
+ * **No balance override is
32
34
  * applied** — `value` transfers are funded by the sender's real native
33
35
  * balance.
34
36
  * 2. **Response validation** — the response is parsed before the reorg
@@ -45,8 +47,8 @@ export declare function assertEndpointChain(params: {
45
47
  * The endpoint must support `eth_simulateV1` with per-call `from`; there is
46
48
  * no fallback backend.
47
49
  *
48
- * @param params - Shared simulation client, execution plan, and already-pinned
49
- * state block.
50
+ * @param params - Shared simulation client, execution plan, already-pinned
51
+ * state block, and the `validation` flag to send.
50
52
  * @returns Deep-frozen {@link SimulationExecution} — per-transaction call
51
53
  * results and the resolved {@link ExecutionBlock}.
52
54
  * @throws {ExternalServiceError} For transport failures, timeouts,
@@ -66,5 +68,6 @@ export declare function executePlan(params: {
66
68
  client: Client & Pick<PublicClient, "getBlock">;
67
69
  plan: ExecutionPlan;
68
70
  stateBlock: PinnedBlock;
71
+ validation: boolean;
69
72
  }): Promise<SimulationExecution>;
70
73
  export {};
@@ -89,9 +89,11 @@ async function assertEndpointChain(params) {
89
89
  *
90
90
  * 1. **`eth_simulateV1`** — one `blockStateCalls` entry carrying the planned
91
91
  * calls with their per-call `from`, `traceTransfers: true` so the node
92
- * synthesizes native-ETH moves as transfer logs, and
93
- * `validation: false`. Validation-off means gas is not charged, which is
94
- * how gas is separated from economic effects. **No balance override is
92
+ * synthesizes native-ETH moves as transfer logs, and `validation: false`
93
+ * (`true` on Monad, whose nodes reject `false` and charge no gas in the
94
+ * simulated block). Validation-off means gas is not charged, which is how
95
+ * gas is separated from economic effects.
96
+ * **No balance override is
95
97
  * applied** — `value` transfers are funded by the sender's real native
96
98
  * balance.
97
99
  * 2. **Response validation** — the response is parsed before the reorg
@@ -108,8 +110,8 @@ async function assertEndpointChain(params) {
108
110
  * The endpoint must support `eth_simulateV1` with per-call `from`; there is
109
111
  * no fallback backend.
110
112
  *
111
- * @param params - Shared simulation client, execution plan, and already-pinned
112
- * state block.
113
+ * @param params - Shared simulation client, execution plan, already-pinned
114
+ * state block, and the `validation` flag to send.
113
115
  * @returns Deep-frozen {@link SimulationExecution} — per-transaction call
114
116
  * results and the resolved {@link ExecutionBlock}.
115
117
  * @throws {ExternalServiceError} For transport failures, timeouts,
@@ -126,7 +128,7 @@ async function assertEndpointChain(params) {
126
128
  * @internal
127
129
  */
128
130
  async function executePlan(params) {
129
- const { client, plan, stateBlock } = params;
131
+ const { client, plan, stateBlock, validation } = params;
130
132
  const response = await (0, exports.rpc)("eth_simulateV1", () => client.request({
131
133
  method: "eth_simulateV1",
132
134
  params: [
@@ -142,7 +144,7 @@ async function executePlan(params) {
142
144
  },
143
145
  ],
144
146
  traceTransfers: true,
145
- validation: false,
147
+ validation,
146
148
  },
147
149
  (0, viem_1.numberToHex)(stateBlock.number),
148
150
  ],
@@ -51,9 +51,13 @@ async function runSimulation(params) {
51
51
  mode: request.mode,
52
52
  blockNumber: typeof request.blockNumber === "bigint" ? request.blockNumber : undefined,
53
53
  });
54
+ // Monad nodes reject `validation: false` and charge no gas in the simulated
55
+ // block either way. Its `latest` block is not final: simulating at its number
56
+ // runs on another parent, so default to `finalized`.
57
+ const monad = request.chainId === blue_sdk_1.ChainId.MonadMainnet;
54
58
  const pinnedBlock = await (0, resolve_pinned_block_js_1.resolvePinnedBlock)({
55
59
  client,
56
- blockNumber: request.blockNumber,
60
+ blockNumber: request.blockNumber ?? (monad ? "finalized" : undefined),
57
61
  signal,
58
62
  });
59
63
  // With no calldata decoding, the caller's declared `limits.operations`
@@ -86,6 +90,7 @@ async function runSimulation(params) {
86
90
  client,
87
91
  plan,
88
92
  stateBlock: pinnedBlock,
93
+ validation: monad,
89
94
  });
90
95
  const userCalls = execution.calls
91
96
  .filter((call) => call.planned.type === "transaction")
@@ -34,7 +34,8 @@ import type { SimulationConfig } from "../types.js";
34
34
  * **Funding.** `value` transfers are funded by the sender's real native
35
35
  * balance — no balance inflation — so under-funded bundles revert exactly as
36
36
  * they would on-chain. `validation: false` keeps gas from being charged,
37
- * separating gas from economic effects.
37
+ * separating gas from economic effects. Monad nodes reject `false`, so Monad
38
+ * simulations send `true`; its simulated block charges no gas either way.
38
39
  *
39
40
  * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
40
41
  * the overall timeout budget.
@@ -47,7 +48,8 @@ import type { SimulationConfig } from "../types.js";
47
48
  * @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
48
49
  * @param params.blockNumber - Optional pinned block number or `BlockTag` other
49
50
  * than `"pending"`.
50
- * Defaults to `latest`, resolved exactly once.
51
+ * Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
52
+ * final), resolved exactly once.
51
53
  * @throws {SimulationValidationError} for invalid input (mixed senders, bad
52
54
  * addresses, empty transactions, malformed authorizations, final-mode
53
55
  * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
@@ -36,7 +36,8 @@ const run_simulation_js_1 = require("./run-simulation.js");
36
36
  * **Funding.** `value` transfers are funded by the sender's real native
37
37
  * balance — no balance inflation — so under-funded bundles revert exactly as
38
38
  * they would on-chain. `validation: false` keeps gas from being charged,
39
- * separating gas from economic effects.
39
+ * separating gas from economic effects. Monad nodes reject `false`, so Monad
40
+ * simulations send `true`; its simulated block charges no gas either way.
40
41
  *
41
42
  * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
42
43
  * the overall timeout budget.
@@ -49,7 +50,8 @@ const run_simulation_js_1 = require("./run-simulation.js");
49
50
  * @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
50
51
  * @param params.blockNumber - Optional pinned block number or `BlockTag` other
51
52
  * than `"pending"`.
52
- * Defaults to `latest`, resolved exactly once.
53
+ * Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
54
+ * final), resolved exactly once.
53
55
  * @throws {SimulationValidationError} for invalid input (mixed senders, bad
54
56
  * addresses, empty transactions, malformed authorizations, final-mode
55
57
  * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
@@ -145,7 +145,8 @@ export interface SimulationCall {
145
145
  * Gas consumed by this call's root frame, not a safe gas limit. This is
146
146
  * post-refund consumption and does not account for EIP-150's 63/64 rule in
147
147
  * nested calls, so consumers deriving a limit must add their own headroom,
148
- * larger than headroom derived from `eth_estimateGas`.
148
+ * larger than headroom derived from `eth_estimateGas`. On Monad it reports the
149
+ * call's gas limit (150M by default) instead of gas consumed.
149
150
  */
150
151
  readonly gasUsed: bigint;
151
152
  }
@@ -17,7 +17,8 @@ export interface SimulateParams {
17
17
  readonly mode?: SimulationMode;
18
18
  /** Pending wallet requests, in order. Preview only; rejected in final. */
19
19
  readonly authorizations?: readonly SimulationAuthorization[];
20
- /** Resolved once; defaults to "latest". Only canonical (mined) blocks can be
20
+ /** Resolved once; defaults to "latest" ("finalized" on Monad, whose
21
+ * "latest" block is not final). Only canonical (mined) blocks can be
21
22
  * pinned; `pending` has no stable hash and is rejected at runtime. */
22
23
  readonly blockNumber?: bigint | Exclude<BlockTag, "pending">;
23
24
  /** Caller-selected quotes and percentage tolerances. Omitted limits are unchecked. */
@@ -26,9 +26,11 @@ export declare function assertEndpointChain(params: {
26
26
  *
27
27
  * 1. **`eth_simulateV1`** — one `blockStateCalls` entry carrying the planned
28
28
  * calls with their per-call `from`, `traceTransfers: true` so the node
29
- * synthesizes native-ETH moves as transfer logs, and
30
- * `validation: false`. Validation-off means gas is not charged, which is
31
- * how gas is separated from economic effects. **No balance override is
29
+ * synthesizes native-ETH moves as transfer logs, and `validation: false`
30
+ * (`true` on Monad, whose nodes reject `false` and charge no gas in the
31
+ * simulated block). Validation-off means gas is not charged, which is how
32
+ * gas is separated from economic effects.
33
+ * **No balance override is
32
34
  * applied** — `value` transfers are funded by the sender's real native
33
35
  * balance.
34
36
  * 2. **Response validation** — the response is parsed before the reorg
@@ -45,8 +47,8 @@ export declare function assertEndpointChain(params: {
45
47
  * The endpoint must support `eth_simulateV1` with per-call `from`; there is
46
48
  * no fallback backend.
47
49
  *
48
- * @param params - Shared simulation client, execution plan, and already-pinned
49
- * state block.
50
+ * @param params - Shared simulation client, execution plan, already-pinned
51
+ * state block, and the `validation` flag to send.
50
52
  * @returns Deep-frozen {@link SimulationExecution} — per-transaction call
51
53
  * results and the resolved {@link ExecutionBlock}.
52
54
  * @throws {ExternalServiceError} For transport failures, timeouts,
@@ -66,5 +68,6 @@ export declare function executePlan(params: {
66
68
  client: Client & Pick<PublicClient, "getBlock">;
67
69
  plan: ExecutionPlan;
68
70
  stateBlock: PinnedBlock;
71
+ validation: boolean;
69
72
  }): Promise<SimulationExecution>;
70
73
  export {};
@@ -83,9 +83,11 @@ export async function assertEndpointChain(params) {
83
83
  *
84
84
  * 1. **`eth_simulateV1`** — one `blockStateCalls` entry carrying the planned
85
85
  * calls with their per-call `from`, `traceTransfers: true` so the node
86
- * synthesizes native-ETH moves as transfer logs, and
87
- * `validation: false`. Validation-off means gas is not charged, which is
88
- * how gas is separated from economic effects. **No balance override is
86
+ * synthesizes native-ETH moves as transfer logs, and `validation: false`
87
+ * (`true` on Monad, whose nodes reject `false` and charge no gas in the
88
+ * simulated block). Validation-off means gas is not charged, which is how
89
+ * gas is separated from economic effects.
90
+ * **No balance override is
89
91
  * applied** — `value` transfers are funded by the sender's real native
90
92
  * balance.
91
93
  * 2. **Response validation** — the response is parsed before the reorg
@@ -102,8 +104,8 @@ export async function assertEndpointChain(params) {
102
104
  * The endpoint must support `eth_simulateV1` with per-call `from`; there is
103
105
  * no fallback backend.
104
106
  *
105
- * @param params - Shared simulation client, execution plan, and already-pinned
106
- * state block.
107
+ * @param params - Shared simulation client, execution plan, already-pinned
108
+ * state block, and the `validation` flag to send.
107
109
  * @returns Deep-frozen {@link SimulationExecution} — per-transaction call
108
110
  * results and the resolved {@link ExecutionBlock}.
109
111
  * @throws {ExternalServiceError} For transport failures, timeouts,
@@ -120,7 +122,7 @@ export async function assertEndpointChain(params) {
120
122
  * @internal
121
123
  */
122
124
  export async function executePlan(params) {
123
- const { client, plan, stateBlock } = params;
125
+ const { client, plan, stateBlock, validation } = params;
124
126
  const response = await rpc("eth_simulateV1", () => client.request({
125
127
  method: "eth_simulateV1",
126
128
  params: [
@@ -136,7 +138,7 @@ export async function executePlan(params) {
136
138
  },
137
139
  ],
138
140
  traceTransfers: true,
139
- validation: false,
141
+ validation,
140
142
  },
141
143
  numberToHex(stateBlock.number),
142
144
  ],
@@ -1,4 +1,4 @@
1
- import { getChainAddresses, UnsupportedChainIdError, } from "@morpho-org/blue-sdk";
1
+ import { ChainId, getChainAddresses, UnsupportedChainIdError, } from "@morpho-org/blue-sdk";
2
2
  import { _try } from "@morpho-org/morpho-ts";
3
3
  import { zeroAddress } from "viem";
4
4
  import { InvalidSimulationResponseError, UnsupportedChainError, } from "../errors.js";
@@ -48,9 +48,13 @@ export async function runSimulation(params) {
48
48
  mode: request.mode,
49
49
  blockNumber: typeof request.blockNumber === "bigint" ? request.blockNumber : undefined,
50
50
  });
51
+ // Monad nodes reject `validation: false` and charge no gas in the simulated
52
+ // block either way. Its `latest` block is not final: simulating at its number
53
+ // runs on another parent, so default to `finalized`.
54
+ const monad = request.chainId === ChainId.MonadMainnet;
51
55
  const pinnedBlock = await resolvePinnedBlock({
52
56
  client,
53
- blockNumber: request.blockNumber,
57
+ blockNumber: request.blockNumber ?? (monad ? "finalized" : undefined),
54
58
  signal,
55
59
  });
56
60
  // With no calldata decoding, the caller's declared `limits.operations`
@@ -83,6 +87,7 @@ export async function runSimulation(params) {
83
87
  client,
84
88
  plan,
85
89
  stateBlock: pinnedBlock,
90
+ validation: monad,
86
91
  });
87
92
  const userCalls = execution.calls
88
93
  .filter((call) => call.planned.type === "transaction")
@@ -34,7 +34,8 @@ import type { SimulationConfig } from "../types.js";
34
34
  * **Funding.** `value` transfers are funded by the sender's real native
35
35
  * balance — no balance inflation — so under-funded bundles revert exactly as
36
36
  * they would on-chain. `validation: false` keeps gas from being charged,
37
- * separating gas from economic effects.
37
+ * separating gas from economic effects. Monad nodes reject `false`, so Monad
38
+ * simulations send `true`; its simulated block charges no gas either way.
38
39
  *
39
40
  * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
40
41
  * the overall timeout budget.
@@ -47,7 +48,8 @@ import type { SimulationConfig } from "../types.js";
47
48
  * @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
48
49
  * @param params.blockNumber - Optional pinned block number or `BlockTag` other
49
50
  * than `"pending"`.
50
- * Defaults to `latest`, resolved exactly once.
51
+ * Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
52
+ * final), resolved exactly once.
51
53
  * @throws {SimulationValidationError} for invalid input (mixed senders, bad
52
54
  * addresses, empty transactions, malformed authorizations, final-mode
53
55
  * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
@@ -33,7 +33,8 @@ import { runSimulation } from "./run-simulation.js";
33
33
  * **Funding.** `value` transfers are funded by the sender's real native
34
34
  * balance — no balance inflation — so under-funded bundles revert exactly as
35
35
  * they would on-chain. `validation: false` keeps gas from being charged,
36
- * separating gas from economic effects.
36
+ * separating gas from economic effects. Monad nodes reject `false`, so Monad
37
+ * simulations send `true`; its simulated block charges no gas either way.
37
38
  *
38
39
  * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
39
40
  * the overall timeout budget.
@@ -46,7 +47,8 @@ import { runSimulation } from "./run-simulation.js";
46
47
  * @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
47
48
  * @param params.blockNumber - Optional pinned block number or `BlockTag` other
48
49
  * than `"pending"`.
49
- * Defaults to `latest`, resolved exactly once.
50
+ * Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
51
+ * final), resolved exactly once.
50
52
  * @throws {SimulationValidationError} for invalid input (mixed senders, bad
51
53
  * addresses, empty transactions, malformed authorizations, final-mode
52
54
  * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
@@ -145,7 +145,8 @@ export interface SimulationCall {
145
145
  * Gas consumed by this call's root frame, not a safe gas limit. This is
146
146
  * post-refund consumption and does not account for EIP-150's 63/64 rule in
147
147
  * nested calls, so consumers deriving a limit must add their own headroom,
148
- * larger than headroom derived from `eth_estimateGas`.
148
+ * larger than headroom derived from `eth_estimateGas`. On Monad it reports the
149
+ * call's gas limit (150M by default) instead of gas consumed.
149
150
  */
150
151
  readonly gasUsed: bigint;
151
152
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morpho-org/evm-simulation",
3
3
  "description": "EVM simulation engine for Morpho transactions.",
4
- "version": "5.0.0",
4
+ "version": "5.0.1",
5
5
  "author": "Morpho Association <contact@morpho.org>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -22,14 +22,14 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@morpho-org/blue-sdk": "^7.2.0",
25
- "@morpho-org/morpho-sdk": "^6.5.0",
26
- "@morpho-org/morpho-ts": "^3.2.0"
25
+ "@morpho-org/morpho-sdk": "^6.6.0",
26
+ "@morpho-org/morpho-ts": "^3.2.1"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "viem": "^2.0.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@morpho-org/test": "^2.10.0",
32
+ "@morpho-org/test": "^2.10.1",
33
33
  "fast-check": "^4.9.0",
34
34
  "typescript": "7.0.2",
35
35
  "viem": "^2.55.10",