@morpho-org/evm-simulation 4.2.1 → 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.
Files changed (160) hide show
  1. package/README.md +109 -17
  2. package/lib/cjs/authorizations.d.ts +95 -0
  3. package/lib/cjs/authorizations.js +2 -0
  4. package/lib/cjs/errors.d.ts +199 -12
  5. package/lib/cjs/errors.js +190 -12
  6. package/lib/cjs/index.d.ts +10 -2
  7. package/lib/cjs/index.js +19 -3
  8. package/lib/cjs/limits.d.ts +112 -0
  9. package/lib/cjs/limits.js +50 -0
  10. package/lib/cjs/params.d.ts +26 -0
  11. package/lib/cjs/params.js +5 -0
  12. package/lib/cjs/requirements/index.d.ts +1 -0
  13. package/lib/cjs/requirements/index.js +5 -0
  14. package/lib/cjs/requirements/requirements.d.ts +64 -0
  15. package/lib/cjs/requirements/requirements.js +149 -0
  16. package/lib/cjs/result.d.ts +49 -0
  17. package/lib/cjs/result.js +2 -0
  18. package/lib/cjs/simulate/asset-changes.d.ts +7 -5
  19. package/lib/cjs/simulate/asset-changes.js +6 -5
  20. package/lib/cjs/simulate/backends/client.d.ts +16 -0
  21. package/lib/cjs/simulate/backends/client.js +27 -0
  22. package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +69 -20
  23. package/lib/cjs/simulate/backends/eth-simulate-v1.js +174 -98
  24. package/lib/cjs/simulate/backends/index.d.ts +1 -2
  25. package/lib/cjs/simulate/backends/index.js +3 -4
  26. package/lib/cjs/simulate/backends/parse-response.d.ts +69 -0
  27. package/lib/cjs/simulate/backends/parse-response.js +168 -0
  28. package/lib/cjs/simulate/backends/resolve-assets.d.ts +26 -0
  29. package/lib/cjs/simulate/backends/resolve-assets.js +111 -0
  30. package/lib/cjs/simulate/backends/resolve-pinned-block.d.ts +33 -0
  31. package/lib/cjs/simulate/backends/resolve-pinned-block.js +46 -0
  32. package/lib/cjs/simulate/context.d.ts +14 -0
  33. package/lib/cjs/simulate/context.js +2 -0
  34. package/lib/cjs/simulate/measurement-plan.d.ts +29 -0
  35. package/lib/cjs/simulate/measurement-plan.js +115 -0
  36. package/lib/cjs/simulate/parsing/transfers.d.ts +4 -3
  37. package/lib/cjs/simulate/parsing/transfers.js +4 -6
  38. package/lib/cjs/simulate/pipeline/bundles-retention.d.ts +7 -10
  39. package/lib/cjs/simulate/pipeline/bundles-retention.js +11 -14
  40. package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +5 -11
  41. package/lib/cjs/simulate/pipeline/resolve-chain.js +5 -10
  42. package/lib/cjs/simulate/plan/plan-execution.d.ts +58 -0
  43. package/lib/cjs/simulate/plan/plan-execution.js +75 -0
  44. package/lib/cjs/simulate/prepare.d.ts +30 -0
  45. package/lib/cjs/simulate/prepare.js +80 -0
  46. package/lib/cjs/simulate/request/index.d.ts +1 -0
  47. package/lib/cjs/simulate/request/index.js +5 -0
  48. package/lib/cjs/simulate/request/parse-request.d.ts +44 -0
  49. package/lib/cjs/simulate/request/parse-request.js +759 -0
  50. package/lib/cjs/simulate/result.d.ts +19 -0
  51. package/lib/cjs/simulate/result.js +29 -0
  52. package/lib/cjs/simulate/run-simulation.d.ts +18 -0
  53. package/lib/cjs/simulate/run-simulation.js +156 -0
  54. package/lib/cjs/simulate/simulate.d.ts +102 -51
  55. package/lib/cjs/simulate/simulate.js +101 -82
  56. package/lib/cjs/simulate/slippage.d.ts +18 -0
  57. package/lib/cjs/simulate/slippage.js +41 -0
  58. package/lib/cjs/simulate/state/contract.d.ts +19 -0
  59. package/lib/cjs/simulate/state/contract.js +2 -0
  60. package/lib/cjs/simulate/state/read-state.d.ts +60 -0
  61. package/lib/cjs/simulate/state/read-state.js +148 -0
  62. package/lib/cjs/simulate/verify-slippage.d.ts +24 -0
  63. package/lib/cjs/simulate/verify-slippage.js +85 -0
  64. package/lib/cjs/types.d.ts +37 -96
  65. package/lib/esm/authorizations.d.ts +95 -0
  66. package/lib/esm/authorizations.js +1 -0
  67. package/lib/esm/errors.d.ts +199 -12
  68. package/lib/esm/errors.js +182 -11
  69. package/lib/esm/index.d.ts +10 -2
  70. package/lib/esm/index.js +4 -1
  71. package/lib/esm/limits.d.ts +112 -0
  72. package/lib/esm/limits.js +47 -0
  73. package/lib/esm/params.d.ts +26 -0
  74. package/lib/esm/params.js +2 -0
  75. package/lib/esm/requirements/index.d.ts +1 -0
  76. package/lib/esm/requirements/index.js +1 -0
  77. package/lib/esm/requirements/requirements.d.ts +64 -0
  78. package/lib/esm/requirements/requirements.js +146 -0
  79. package/lib/esm/result.d.ts +49 -0
  80. package/lib/esm/result.js +1 -0
  81. package/lib/esm/simulate/asset-changes.d.ts +7 -5
  82. package/lib/esm/simulate/asset-changes.js +6 -5
  83. package/lib/esm/simulate/backends/client.d.ts +16 -0
  84. package/lib/esm/simulate/backends/client.js +24 -0
  85. package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +69 -20
  86. package/lib/esm/simulate/backends/eth-simulate-v1.js +172 -99
  87. package/lib/esm/simulate/backends/index.d.ts +1 -2
  88. package/lib/esm/simulate/backends/index.js +1 -2
  89. package/lib/esm/simulate/backends/parse-response.d.ts +69 -0
  90. package/lib/esm/simulate/backends/parse-response.js +165 -0
  91. package/lib/esm/simulate/backends/resolve-assets.d.ts +26 -0
  92. package/lib/esm/simulate/backends/resolve-assets.js +108 -0
  93. package/lib/esm/simulate/backends/resolve-pinned-block.d.ts +33 -0
  94. package/lib/esm/simulate/backends/resolve-pinned-block.js +43 -0
  95. package/lib/esm/simulate/context.d.ts +14 -0
  96. package/lib/esm/simulate/context.js +1 -0
  97. package/lib/esm/simulate/measurement-plan.d.ts +29 -0
  98. package/lib/esm/simulate/measurement-plan.js +112 -0
  99. package/lib/esm/simulate/parsing/transfers.d.ts +4 -3
  100. package/lib/esm/simulate/parsing/transfers.js +4 -6
  101. package/lib/esm/simulate/pipeline/bundles-retention.d.ts +7 -10
  102. package/lib/esm/simulate/pipeline/bundles-retention.js +11 -14
  103. package/lib/esm/simulate/pipeline/resolve-chain.d.ts +5 -11
  104. package/lib/esm/simulate/pipeline/resolve-chain.js +5 -10
  105. package/lib/esm/simulate/plan/plan-execution.d.ts +58 -0
  106. package/lib/esm/simulate/plan/plan-execution.js +72 -0
  107. package/lib/esm/simulate/prepare.d.ts +30 -0
  108. package/lib/esm/simulate/prepare.js +77 -0
  109. package/lib/esm/simulate/request/index.d.ts +1 -0
  110. package/lib/esm/simulate/request/index.js +1 -0
  111. package/lib/esm/simulate/request/parse-request.d.ts +44 -0
  112. package/lib/esm/simulate/request/parse-request.js +756 -0
  113. package/lib/esm/simulate/result.d.ts +19 -0
  114. package/lib/esm/simulate/result.js +26 -0
  115. package/lib/esm/simulate/run-simulation.d.ts +18 -0
  116. package/lib/esm/simulate/run-simulation.js +153 -0
  117. package/lib/esm/simulate/simulate.d.ts +102 -51
  118. package/lib/esm/simulate/simulate.js +101 -82
  119. package/lib/esm/simulate/slippage.d.ts +18 -0
  120. package/lib/esm/simulate/slippage.js +38 -0
  121. package/lib/esm/simulate/state/contract.d.ts +19 -0
  122. package/lib/esm/simulate/state/contract.js +1 -0
  123. package/lib/esm/simulate/state/read-state.d.ts +60 -0
  124. package/lib/esm/simulate/state/read-state.js +144 -0
  125. package/lib/esm/simulate/verify-slippage.d.ts +24 -0
  126. package/lib/esm/simulate/verify-slippage.js +82 -0
  127. package/lib/esm/types.d.ts +37 -96
  128. package/package.json +7 -6
  129. package/lib/cjs/simulate/authorizations/index.d.ts +0 -2
  130. package/lib/cjs/simulate/authorizations/index.js +0 -7
  131. package/lib/cjs/simulate/authorizations/resolve.d.ts +0 -9
  132. package/lib/cjs/simulate/authorizations/resolve.js +0 -29
  133. package/lib/cjs/simulate/authorizations/validate.d.ts +0 -6
  134. package/lib/cjs/simulate/authorizations/validate.js +0 -23
  135. package/lib/cjs/simulate/backends/tenderly-rpc.d.ts +0 -23
  136. package/lib/cjs/simulate/backends/tenderly-rpc.js +0 -234
  137. package/lib/cjs/simulate/pipeline/build-simulation-txs.d.ts +0 -17
  138. package/lib/cjs/simulate/pipeline/build-simulation-txs.js +0 -50
  139. package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +0 -22
  140. package/lib/cjs/simulate/pipeline/execute-simulation.js +0 -82
  141. package/lib/cjs/simulate/pipeline/index.d.ts +0 -4
  142. package/lib/cjs/simulate/pipeline/index.js +0 -11
  143. package/lib/cjs/simulate/pipeline/validate-input.d.ts +0 -10
  144. package/lib/cjs/simulate/pipeline/validate-input.js +0 -55
  145. package/lib/esm/simulate/authorizations/index.d.ts +0 -2
  146. package/lib/esm/simulate/authorizations/index.js +0 -2
  147. package/lib/esm/simulate/authorizations/resolve.d.ts +0 -9
  148. package/lib/esm/simulate/authorizations/resolve.js +0 -26
  149. package/lib/esm/simulate/authorizations/validate.d.ts +0 -6
  150. package/lib/esm/simulate/authorizations/validate.js +0 -20
  151. package/lib/esm/simulate/backends/tenderly-rpc.d.ts +0 -23
  152. package/lib/esm/simulate/backends/tenderly-rpc.js +0 -231
  153. package/lib/esm/simulate/pipeline/build-simulation-txs.d.ts +0 -17
  154. package/lib/esm/simulate/pipeline/build-simulation-txs.js +0 -47
  155. package/lib/esm/simulate/pipeline/execute-simulation.d.ts +0 -22
  156. package/lib/esm/simulate/pipeline/execute-simulation.js +0 -79
  157. package/lib/esm/simulate/pipeline/index.d.ts +0 -4
  158. package/lib/esm/simulate/pipeline/index.js +0 -4
  159. package/lib/esm/simulate/pipeline/validate-input.d.ts +0 -10
  160. package/lib/esm/simulate/pipeline/validate-input.js +0 -52
@@ -0,0 +1,19 @@
1
+ import type { AuthorizationPreparation, SimulatedOperation, VerifiedSimulationResult } from "../result.js";
2
+ import type { AccountAssetChanges, SimulationCall, Transfer } from "../types.js";
3
+ import type { CheckContext } from "./context.js";
4
+ import type { ParsedRequest } from "./request/parse-request.js";
5
+ /**
6
+ * Assemble the public result from the checked pipeline. Only user
7
+ * transactions appear in `simulationTxs`/`calls`/`transfers` — preparation
8
+ * calls and state reads never get a public `txIdx`.
9
+ * @internal
10
+ */
11
+ export declare function assembleResult(params: {
12
+ readonly ctx: CheckContext;
13
+ readonly request: ParsedRequest;
14
+ readonly operations: readonly SimulatedOperation[];
15
+ readonly authorizations: readonly AuthorizationPreparation[];
16
+ readonly userCalls: readonly SimulationCall[];
17
+ readonly transfers: readonly Transfer[];
18
+ readonly assetChanges: readonly AccountAssetChanges[];
19
+ }): VerifiedSimulationResult;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assembleResult = assembleResult;
4
+ const morpho_ts_1 = require("@morpho-org/morpho-ts");
5
+ /**
6
+ * Assemble the public result from the checked pipeline. Only user
7
+ * transactions appear in `simulationTxs`/`calls`/`transfers` — preparation
8
+ * calls and state reads never get a public `txIdx`.
9
+ * @internal
10
+ */
11
+ function assembleResult(params) {
12
+ const { ctx, request, operations, authorizations, userCalls, transfers, assetChanges, } = params;
13
+ const verification = {
14
+ mode: ctx.mode,
15
+ chainId: ctx.chainId,
16
+ blockNumber: ctx.block.blockNumber,
17
+ blockTimestamp: ctx.block.blockTimestamp,
18
+ limits: ctx.limits,
19
+ operations,
20
+ authorizations,
21
+ };
22
+ return (0, morpho_ts_1.deepFreeze)({
23
+ simulationTxs: request.transactions,
24
+ calls: userCalls,
25
+ transfers,
26
+ assetChanges,
27
+ verification,
28
+ });
29
+ }
@@ -0,0 +1,18 @@
1
+ import type { VerifiedSimulationResult } from "../result.js";
2
+ import type { SimulationConfig } from "../types.js";
3
+ import type { ParsedRequest } from "./request/parse-request.js";
4
+ /**
5
+ * Run the verified simulation pipeline in a single `eth_simulateV1` call:
6
+ * resolve quoted assets → plan quoted observations → execution →
7
+ * slippage comparisons → bundle-retention assertion → result assembly.
8
+ *
9
+ * One `AbortSignal` (the request timeout) covers every RPC step.
10
+ *
11
+ * @internal
12
+ * @param params - The simulation config and the parsed caller request.
13
+ * @returns The deep-frozen {@link VerifiedSimulationResult}.
14
+ */
15
+ export declare function runSimulation(params: {
16
+ readonly config: SimulationConfig;
17
+ readonly request: ParsedRequest;
18
+ }): Promise<VerifiedSimulationResult>;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSimulation = runSimulation;
4
+ const blue_sdk_1 = require("@morpho-org/blue-sdk");
5
+ const morpho_ts_1 = require("@morpho-org/morpho-ts");
6
+ const viem_1 = require("viem");
7
+ const errors_js_1 = require("../errors.js");
8
+ const asset_changes_js_1 = require("./asset-changes.js");
9
+ const client_js_1 = require("./backends/client.js");
10
+ const index_js_1 = require("./backends/index.js");
11
+ const resolve_assets_js_1 = require("./backends/resolve-assets.js");
12
+ const resolve_pinned_block_js_1 = require("./backends/resolve-pinned-block.js");
13
+ const index_js_2 = require("./parsing/index.js");
14
+ const bundles_retention_js_1 = require("./pipeline/bundles-retention.js");
15
+ const resolve_chain_js_1 = require("./pipeline/resolve-chain.js");
16
+ const plan_execution_js_1 = require("./plan/plan-execution.js");
17
+ const prepare_js_1 = require("./prepare.js");
18
+ const result_js_1 = require("./result.js");
19
+ const read_state_js_1 = require("./state/read-state.js");
20
+ const verify_slippage_js_1 = require("./verify-slippage.js");
21
+ /** Total execution budget for a single `simulate()` call. */
22
+ const DEFAULT_TIMEOUT_MS = 5000;
23
+ /**
24
+ * Run the verified simulation pipeline in a single `eth_simulateV1` call:
25
+ * resolve quoted assets → plan quoted observations → execution →
26
+ * slippage comparisons → bundle-retention assertion → result assembly.
27
+ *
28
+ * One `AbortSignal` (the request timeout) covers every RPC step.
29
+ *
30
+ * @internal
31
+ * @param params - The simulation config and the parsed caller request.
32
+ * @returns The deep-frozen {@link VerifiedSimulationResult}.
33
+ */
34
+ async function runSimulation(params) {
35
+ const { config, request } = params;
36
+ const chain = (0, resolve_chain_js_1.resolveChain)(config, request.chainId);
37
+ const signal = AbortSignal.timeout(config.timeoutMs ?? DEFAULT_TIMEOUT_MS);
38
+ const client = (0, client_js_1.createSimulationClient)(chain.simulateV1Url, signal);
39
+ const limits = { operations: request.limits?.operations ?? [] };
40
+ const preview = request.mode === "preview";
41
+ const owner = request.transactions[0].from;
42
+ const addresses = (0, morpho_ts_1._try)(() => (0, blue_sdk_1.getChainAddresses)(request.chainId), blue_sdk_1.UnsupportedChainIdError);
43
+ const requiresMorpho = limits.operations.length > 0 ||
44
+ (preview && request.authorizations.length > 0);
45
+ if (requiresMorpho && addresses?.blue == null)
46
+ throw new errors_js_1.UnsupportedChainError(request.chainId);
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
+ // 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;
58
+ const pinnedBlock = await (0, resolve_pinned_block_js_1.resolvePinnedBlock)({
59
+ client,
60
+ blockNumber: request.blockNumber ?? (monad ? "finalized" : undefined),
61
+ signal,
62
+ });
63
+ // With no calldata decoding, the caller's declared `limits.operations`
64
+ // entries are the operations: subjects for state reads and per-operation
65
+ // checks come straight from the limit list.
66
+ const resolved = await (0, resolve_assets_js_1.resolveAssets)({
67
+ client,
68
+ morpho,
69
+ operations: limits.operations,
70
+ chainId: request.chainId,
71
+ mode: request.mode,
72
+ blockNumber: pinnedBlock.number,
73
+ });
74
+ const observations = (0, read_state_js_1.planStateReads)({ operations: resolved, owner, morpho });
75
+ const preparations = preview
76
+ ? (0, prepare_js_1.prepareAuthorizations)({
77
+ authorizations: request.authorizations,
78
+ owner,
79
+ morpho,
80
+ })
81
+ : [];
82
+ const plan = (0, plan_execution_js_1.planExecution)({
83
+ request,
84
+ owner,
85
+ preparations,
86
+ reads: observations.reads,
87
+ });
88
+ // Execute against the block resolved once above; the boundary rechecks its hash.
89
+ const execution = await (0, index_js_1.executePlan)({
90
+ client,
91
+ plan,
92
+ stateBlock: pinnedBlock,
93
+ validation: monad,
94
+ });
95
+ const userCalls = execution.calls
96
+ .filter((call) => call.planned.type === "transaction")
97
+ .map((call) => call.result);
98
+ if (userCalls.length !== request.transactions.length)
99
+ throw new errors_js_1.InvalidSimulationResponseError(`Execution returned ${userCalls.length} user call result(s) for ${request.transactions.length} transaction(s)`);
100
+ const decodePhase = (phase) => new Map(execution.stateReads
101
+ .filter((read) => read.phase === phase)
102
+ .map((read) => [
103
+ read.read.id,
104
+ (0, read_state_js_1.decodeStateRead)(read.read, read.returnData, {
105
+ chainId: request.chainId,
106
+ mode: request.mode,
107
+ blockNumber: pinnedBlock.number,
108
+ }),
109
+ ]));
110
+ const transfers = (0, index_js_2.parseTransfers)(userCalls, {
111
+ wNative: addresses === undefined ? undefined : (addresses.wNative ?? null),
112
+ logger: config.logger,
113
+ });
114
+ const assetChanges = (0, asset_changes_js_1.groupAssetChanges)(transfers.flatMap(({ token, from, to, amount }) => [
115
+ { account: to, token, diff: amount },
116
+ { account: from, token, diff: -amount },
117
+ ]));
118
+ const ctx = {
119
+ chainId: request.chainId,
120
+ mode: request.mode,
121
+ block: execution.block,
122
+ owner,
123
+ limits,
124
+ };
125
+ const authorizations = request.authorizations.map((authorization, authorizationIndex) => ({
126
+ authorizationIndex,
127
+ authorization,
128
+ calls: execution.calls.flatMap(({ planned, result }) => planned.type === "preparation" &&
129
+ planned.authorizationIndex === authorizationIndex
130
+ ? [{ transaction: planned.transaction, result }]
131
+ : []),
132
+ }));
133
+ const checked = (0, verify_slippage_js_1.verifySlippage)({
134
+ ctx,
135
+ operations: observations.operations,
136
+ before: decodePhase("before"),
137
+ after: decodePhase("after"),
138
+ transfers,
139
+ requestTransactions: request.transactions,
140
+ });
141
+ (0, bundles_retention_js_1.assertNoBundlesRetention)({
142
+ chainId: request.chainId,
143
+ transfers,
144
+ assetChanges,
145
+ logger: config.logger,
146
+ });
147
+ return (0, result_js_1.assembleResult)({
148
+ ctx,
149
+ request,
150
+ operations: checked,
151
+ authorizations,
152
+ userCalls,
153
+ transfers,
154
+ assetChanges,
155
+ });
156
+ }
@@ -1,68 +1,119 @@
1
- import type { SimulateParams, SimulationConfig, SimulationResult } from "../types.js";
1
+ import type { SimulateParams } from "../params.js";
2
+ import type { VerifiedSimulationResult } from "../result.js";
3
+ import type { SimulationConfig } from "../types.js";
2
4
  /**
3
5
  * Simulate a bundle of EVM transactions.
4
6
  *
5
- * Validates input → resolves authorizations into prepended approve txs → runs the bundle
6
- * through Tenderly RPC (primary) or `eth_simulateV1` (fallback) with a shared timeout
7
- * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
8
- * events to the registered wrapped-native token, rejecting them on known tokenless chains,
9
- * and retaining signature-based parsing for unknown chains → asserts no funds are retained
10
- * by the restricted standalone bundles contracts (VaultExitBundlesV1, VaultBundlesV1,
11
- * BlueBundlesV1, MidnightBundlesV1) → returns the full result
12
- * set. The caller reads whichever fields they need:
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
12
+ * user calls only → decodes quoted balances/positions → runs slippage checks
13
+ * → asserts no funds are retained by standalone `bundles` periphery contracts
14
+ * → returns the result. The caller reads whichever fields they need:
13
15
  *
14
- * - `transfers` → user-facing preview / server-side verification.
15
- * - `simulationTxs` + `transfers` → server-side verification before broadcast.
16
- * - `calls[i]` → per-tx raw backend output (`logs`, `status`, `returnData`, `gasUsed`).
17
- * Aligned 1:1 with `simulationTxs[i]`. `gasUsed` is not a safe gas limit; consumers
18
- * deriving one must add their own headroom.
19
- * - `assetChanges` → net per-asset balance changes grouped by account (sender and
20
- * counterparties) over the whole bundle, normalized to the same shape across backends.
21
- * - `transfers[k].txIdx` → index into `simulationTxs` of the tx that emitted the
22
- * underlying log; consumers map back via `simulationTxs[transfer.txIdx]`.
16
+ * - `simulationTxs` → exactly the caller's ordered transactions, normalized
17
+ * (checksummed addresses, `value` defaulted to `0n`). Internal state reads are
18
+ * never exposed.
19
+ * - `calls[i]` → per-tx raw backend output (`logs`, `status`, `returnData`,
20
+ * `gasUsed`), aligned 1:1 with `simulationTxs[i]`. `gasUsed` is not a safe
21
+ * gas limit; consumers deriving one must add their own headroom.
22
+ * - `transfers` → user-facing preview / server-side verification; each
23
+ * transfer's `txIdx` indexes into `simulationTxs`.
24
+ * - `assetChanges` → net per-asset balance changes grouped by account (sender
25
+ * and counterparties) over the whole bundle.
23
26
  *
24
- * @param config - Backend configuration: per-chain Tenderly RPC and/or `eth_simulateV1`
25
- * URL, optional logger, and the overall timeout budget.
27
+ * **Modes.** `mode` defaults to `"final"`, which executes the signed calldata
28
+ * against actual permissions and accepts no `authorizations`. `mode:
29
+ * "preview"` accepts typed authorization descriptors, which the pipeline
30
+ * prepares as simulated approval calls. No authorization read-back checks run.
31
+ * `limits` are optional caller-supplied quotes and percentage tolerances;
32
+ * violations throw `ConsumerLimitViolationError`.
33
+ *
34
+ * **Funding.** `value` transfers are funded by the sender's real native
35
+ * balance — no balance inflation — so under-funded bundles revert exactly as
36
+ * they would on-chain. `validation: false` keeps gas from being charged,
37
+ * separating gas from economic effects. Monad nodes reject `false`, so Monad
38
+ * simulations send `true`; its simulated block charges no gas either way.
39
+ *
40
+ * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
41
+ * the overall timeout budget.
26
42
  * @param params - Per-call simulation input.
27
- * @param params.chainId - Chain id the bundle targets.
28
- * @param params.transactions - The bundle's transactions, in execution order. All must share the
29
- * same `from`.
30
- * @param params.authorizations - Optional token authorizations resolved into prepended approve
31
- * transactions before the main bundle runs.
32
- * @param params.blockNumber - Optional pinned block number or `BlockTag`. Defaults to `latest`.
33
- * @throws {SimulationValidationError} for invalid input (mixed senders, bad addresses,
34
- * empty transactions, malformed authorizations).
35
- * @throws {UnsupportedChainError} when the chain is not configured for any backend.
36
- * @throws {SimulationRevertedError} when the bundle reverts on either backend.
37
- * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
38
- * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
39
- * VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1). Never bypassable.
40
- * @throws {ExternalServiceError} (a) when both backends are unavailable within the
41
- * timeout budget, or (b) when a backend returns a `calls` array whose length does
42
- * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
43
- * per-tx output.
44
- * @returns A {@link SimulationResult} carrying the resolved `simulationTxs`, per-tx
45
- * `calls` (aligned 1:1 with `simulationTxs`), parsed `transfers` (each stamped
46
- * with `txIdx`), and per-account net `assetChanges`.
43
+ * @param params.chainId - Chain id the bundle targets; must match the endpoint.
44
+ * @param params.transactions - The bundle's transactions, in execution order.
45
+ * All must share the same `from`.
46
+ * @param params.mode - `"final"` (default) or `"preview"`.
47
+ * @param params.authorizations - Preview-only typed authorization descriptors.
48
+ * @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
49
+ * @param params.blockNumber - Optional pinned block number or `BlockTag` other
50
+ * than `"pending"`.
51
+ * Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
52
+ * final), resolved exactly once.
53
+ * @throws {SimulationValidationError} for invalid input (mixed senders, bad
54
+ * addresses, empty transactions, malformed authorizations, final-mode
55
+ * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
56
+ * `blueSupplyCollateral` / `blueWithdrawCollateral`).
57
+ * @throws {ConsumerLimitViolationError} when a declared `limits` bound is
58
+ * violated by the observed effects.
59
+ * @throws {UnsupportedChainError} when the chain has no `eth_simulateV1`
60
+ * endpoint configured, or limits/preview authorizations require a Morpho
61
+ * Blue address absent from blue-sdk's `getChainAddresses`.
62
+ * @throws {SimulationRevertedError} when a preparation or user transaction reverts.
63
+ * @throws {MissingVerificationEvidenceError} when a planned state read fails
64
+ * or returns empty data, native outgoing traces do not cover value
65
+ * sent, or required metadata reverts or returns empty/invalid data.
66
+ * @throws {InvalidSimulationResponseError} when the node response cannot be
67
+ * trusted (bad shape, call-count mismatch, block that is neither the pinned
68
+ * 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).
75
+ * @throws {BlacklistViolationError} when the simulation leaves value retained
76
+ * beyond the dust threshold by a `bundles` periphery contract
77
+ * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1).
78
+ * Never bypassable.
79
+ * @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`.
83
+ * @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
84
+ * `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
85
+ * stamped with `txIdx`), per-account net `assetChanges`, and per-operation
86
+ * verification outcomes and preparations. Each `verification.operations[i]`
87
+ * mirrors the `limits.operations[i]` entry it verified. Only the amounts
88
+ * quoted in each entry are checked; `checkedLimits` identifies precisely
89
+ * what was checked. Measurements are scoped to the whole bundle.
47
90
  * @example
48
91
  * ```ts
49
92
  * import { simulate } from "@morpho-org/evm-simulation";
93
+ * import { type Address, encodeFunctionData, erc20Abi, getAddress } from "viem";
94
+ *
95
+ * const rpcUrl = "https://rpc.example";
96
+ * const user: Address = getAddress("0x1111111111111111111111111111111111111111");
97
+ * const usdc: Address = getAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
98
+ * const recipient: Address = getAddress("0x2222222222222222222222222222222222222222");
50
99
  *
51
100
  * const result = await simulate(
52
- * {
53
- * chains: new Map([
54
- * [1, {
55
- * tenderlyRpc: { rpcUrl: process.env.TENDERLY_RPC_URL! },
56
- * simulateV1Url: process.env.MAINNET_RPC_URL,
57
- * }],
58
- * ]),
59
- * },
101
+ * { chains: new Map([[1, { simulateV1Url: rpcUrl }]]) },
60
102
  * {
61
103
  * chainId: 1,
62
- * transactions: [{ from: user, to: vaultAddress, data: encodedCalldata, value: 0n }],
104
+ * transactions: [
105
+ * {
106
+ * from: user,
107
+ * to: usdc,
108
+ * data: encodeFunctionData({
109
+ * abi: erc20Abi,
110
+ * functionName: "transfer",
111
+ * args: [recipient, 1_000_000n],
112
+ * }),
113
+ * },
114
+ * ],
63
115
  * },
64
116
  * );
65
- * // result satisfies SimulationResult
66
117
  * ```
67
118
  */
68
- export declare function simulate(config: SimulationConfig, params: SimulateParams): Promise<SimulationResult>;
119
+ export declare function simulate(config: SimulationConfig, params: SimulateParams): Promise<VerifiedSimulationResult>;
@@ -1,104 +1,123 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.simulate = simulate;
4
- const blue_sdk_1 = require("@morpho-org/blue-sdk");
5
- const errors_js_1 = require("../errors.js");
6
- const index_js_1 = require("./parsing/index.js");
7
- const index_js_2 = require("./pipeline/index.js");
4
+ const index_js_1 = require("./request/index.js");
5
+ const run_simulation_js_1 = require("./run-simulation.js");
8
6
  /**
9
7
  * Simulate a bundle of EVM transactions.
10
8
  *
11
- * Validates input → resolves authorizations into prepended approve txs → runs the bundle
12
- * through Tenderly RPC (primary) or `eth_simulateV1` (fallback) with a shared timeout
13
- * budget → parses ERC20 transfers and WETH9 events from per-tx logs, restricting WETH9
14
- * events to the registered wrapped-native token, rejecting them on known tokenless chains,
15
- * and retaining signature-based parsing for unknown chains → asserts no funds are retained
16
- * by the restricted standalone bundles contracts (VaultExitBundlesV1, VaultBundlesV1,
17
- * BlueBundlesV1, MidnightBundlesV1) → returns the full result
18
- * set. The caller reads whichever fields they need:
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
14
+ * user calls only → decodes quoted balances/positions → runs slippage checks
15
+ * → asserts no funds are retained by standalone `bundles` periphery contracts
16
+ * → returns the result. The caller reads whichever fields they need:
19
17
  *
20
- * - `transfers` → user-facing preview / server-side verification.
21
- * - `simulationTxs` + `transfers` → server-side verification before broadcast.
22
- * - `calls[i]` → per-tx raw backend output (`logs`, `status`, `returnData`, `gasUsed`).
23
- * Aligned 1:1 with `simulationTxs[i]`. `gasUsed` is not a safe gas limit; consumers
24
- * deriving one must add their own headroom.
25
- * - `assetChanges` → net per-asset balance changes grouped by account (sender and
26
- * counterparties) over the whole bundle, normalized to the same shape across backends.
27
- * - `transfers[k].txIdx` → index into `simulationTxs` of the tx that emitted the
28
- * underlying log; consumers map back via `simulationTxs[transfer.txIdx]`.
18
+ * - `simulationTxs` → exactly the caller's ordered transactions, normalized
19
+ * (checksummed addresses, `value` defaulted to `0n`). Internal state reads are
20
+ * never exposed.
21
+ * - `calls[i]` → per-tx raw backend output (`logs`, `status`, `returnData`,
22
+ * `gasUsed`), aligned 1:1 with `simulationTxs[i]`. `gasUsed` is not a safe
23
+ * gas limit; consumers deriving one must add their own headroom.
24
+ * - `transfers` → user-facing preview / server-side verification; each
25
+ * transfer's `txIdx` indexes into `simulationTxs`.
26
+ * - `assetChanges` → net per-asset balance changes grouped by account (sender
27
+ * and counterparties) over the whole bundle.
29
28
  *
30
- * @param config - Backend configuration: per-chain Tenderly RPC and/or `eth_simulateV1`
31
- * URL, optional logger, and the overall timeout budget.
29
+ * **Modes.** `mode` defaults to `"final"`, which executes the signed calldata
30
+ * against actual permissions and accepts no `authorizations`. `mode:
31
+ * "preview"` accepts typed authorization descriptors, which the pipeline
32
+ * prepares as simulated approval calls. No authorization read-back checks run.
33
+ * `limits` are optional caller-supplied quotes and percentage tolerances;
34
+ * violations throw `ConsumerLimitViolationError`.
35
+ *
36
+ * **Funding.** `value` transfers are funded by the sender's real native
37
+ * balance — no balance inflation — so under-funded bundles revert exactly as
38
+ * they would on-chain. `validation: false` keeps gas from being charged,
39
+ * separating gas from economic effects. Monad nodes reject `false`, so Monad
40
+ * simulations send `true`; its simulated block charges no gas either way.
41
+ *
42
+ * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
43
+ * the overall timeout budget.
32
44
  * @param params - Per-call simulation input.
33
- * @param params.chainId - Chain id the bundle targets.
34
- * @param params.transactions - The bundle's transactions, in execution order. All must share the
35
- * same `from`.
36
- * @param params.authorizations - Optional token authorizations resolved into prepended approve
37
- * transactions before the main bundle runs.
38
- * @param params.blockNumber - Optional pinned block number or `BlockTag`. Defaults to `latest`.
39
- * @throws {SimulationValidationError} for invalid input (mixed senders, bad addresses,
40
- * empty transactions, malformed authorizations).
41
- * @throws {UnsupportedChainError} when the chain is not configured for any backend.
42
- * @throws {SimulationRevertedError} when the bundle reverts on either backend.
43
- * @throws {BlacklistViolationError} when the simulation leaves value retained beyond
44
- * the dust threshold by a `bundles` periphery contract (VaultExitBundlesV1,
45
- * VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1). Never bypassable.
46
- * @throws {ExternalServiceError} (a) when both backends are unavailable within the
47
- * timeout budget, or (b) when a backend returns a `calls` array whose length does
48
- * not match the resolved `simulationTxs` — refusing to map transfers with mismatched
49
- * per-tx output.
50
- * @returns A {@link SimulationResult} carrying the resolved `simulationTxs`, per-tx
51
- * `calls` (aligned 1:1 with `simulationTxs`), parsed `transfers` (each stamped
52
- * with `txIdx`), and per-account net `assetChanges`.
45
+ * @param params.chainId - Chain id the bundle targets; must match the endpoint.
46
+ * @param params.transactions - The bundle's transactions, in execution order.
47
+ * All must share the same `from`.
48
+ * @param params.mode - `"final"` (default) or `"preview"`.
49
+ * @param params.authorizations - Preview-only typed authorization descriptors.
50
+ * @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
51
+ * @param params.blockNumber - Optional pinned block number or `BlockTag` other
52
+ * than `"pending"`.
53
+ * Defaults to `latest` (`finalized` on Monad, whose `latest` block is not
54
+ * final), resolved exactly once.
55
+ * @throws {SimulationValidationError} for invalid input (mixed senders, bad
56
+ * addresses, empty transactions, malformed authorizations, final-mode
57
+ * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
58
+ * `blueSupplyCollateral` / `blueWithdrawCollateral`).
59
+ * @throws {ConsumerLimitViolationError} when a declared `limits` bound is
60
+ * violated by the observed effects.
61
+ * @throws {UnsupportedChainError} when the chain has no `eth_simulateV1`
62
+ * endpoint configured, or limits/preview authorizations require a Morpho
63
+ * Blue address absent from blue-sdk's `getChainAddresses`.
64
+ * @throws {SimulationRevertedError} when a preparation or user transaction reverts.
65
+ * @throws {MissingVerificationEvidenceError} when a planned state read fails
66
+ * or returns empty data, native outgoing traces do not cover value
67
+ * sent, or required metadata reverts or returns empty/invalid data.
68
+ * @throws {InvalidSimulationResponseError} when the node response cannot be
69
+ * trusted (bad shape, call-count mismatch, block that is neither the pinned
70
+ * 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).
77
+ * @throws {BlacklistViolationError} when the simulation leaves value retained
78
+ * beyond the dust threshold by a `bundles` periphery contract
79
+ * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1).
80
+ * Never bypassable.
81
+ * @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`.
85
+ * @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
86
+ * `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
87
+ * stamped with `txIdx`), per-account net `assetChanges`, and per-operation
88
+ * verification outcomes and preparations. Each `verification.operations[i]`
89
+ * mirrors the `limits.operations[i]` entry it verified. Only the amounts
90
+ * quoted in each entry are checked; `checkedLimits` identifies precisely
91
+ * what was checked. Measurements are scoped to the whole bundle.
53
92
  * @example
54
93
  * ```ts
55
94
  * import { simulate } from "@morpho-org/evm-simulation";
95
+ * import { type Address, encodeFunctionData, erc20Abi, getAddress } from "viem";
96
+ *
97
+ * const rpcUrl = "https://rpc.example";
98
+ * const user: Address = getAddress("0x1111111111111111111111111111111111111111");
99
+ * const usdc: Address = getAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
100
+ * const recipient: Address = getAddress("0x2222222222222222222222222222222222222222");
56
101
  *
57
102
  * const result = await simulate(
58
- * {
59
- * chains: new Map([
60
- * [1, {
61
- * tenderlyRpc: { rpcUrl: process.env.TENDERLY_RPC_URL! },
62
- * simulateV1Url: process.env.MAINNET_RPC_URL,
63
- * }],
64
- * ]),
65
- * },
103
+ * { chains: new Map([[1, { simulateV1Url: rpcUrl }]]) },
66
104
  * {
67
105
  * chainId: 1,
68
- * transactions: [{ from: user, to: vaultAddress, data: encodedCalldata, value: 0n }],
106
+ * transactions: [
107
+ * {
108
+ * from: user,
109
+ * to: usdc,
110
+ * data: encodeFunctionData({
111
+ * abi: erc20Abi,
112
+ * functionName: "transfer",
113
+ * args: [recipient, 1_000_000n],
114
+ * }),
115
+ * },
116
+ * ],
69
117
  * },
70
118
  * );
71
- * // result satisfies SimulationResult
72
119
  * ```
73
120
  */
74
121
  async function simulate(config, params) {
75
- (0, index_js_2.validateInput)(params);
76
- const wNative = (0, blue_sdk_1._try)(() => (0, blue_sdk_1.getChainAddresses)(params.chainId).wNative ?? null, blue_sdk_1.UnsupportedChainIdError);
77
- const simulationTxs = (0, index_js_2.buildSimulationTxs)(params);
78
- const result = await (0, index_js_2.executeSimulation)({
79
- config,
80
- chainId: params.chainId,
81
- transactions: simulationTxs,
82
- blockNumber: params.blockNumber,
83
- wNative,
84
- });
85
- if (result.calls.length !== simulationTxs.length) {
86
- throw new errors_js_1.ExternalServiceError(`Backend returned ${result.calls.length} call result(s) for ${simulationTxs.length} transaction(s) — refusing to map transfers with mismatched lengths`);
87
- }
88
- const transfers = (0, index_js_1.parseTransfers)(result.calls, {
89
- wNative,
90
- logger: config.logger,
91
- });
92
- (0, index_js_2.assertNoBundlesRetention)({
93
- chainId: params.chainId,
94
- transfers,
95
- assetChanges: result.assetChanges,
96
- logger: config.logger,
97
- });
98
- return {
99
- simulationTxs,
100
- calls: result.calls,
101
- transfers,
102
- assetChanges: result.assetChanges,
103
- };
122
+ return (0, run_simulation_js_1.runSimulation)({ config, request: (0, index_js_1.parseRequest)(params) });
104
123
  }
@@ -0,0 +1,18 @@
1
+ import { type SimulationVerificationContext } from "../errors.js";
2
+ import type { SlippageLimits, SlippageQuote } from "../limits.js";
3
+ /**
4
+ * Compare observed amounts against the caller's quote and percentage tolerance.
5
+ * Minimum outputs round up and maximum inputs round down, so rounding never
6
+ * permits a deviation greater than the requested percentage.
7
+ * @param params - Parsed quote/tolerance, matching observations, and error context.
8
+ * @returns The checked quote and tolerance, deep-frozen.
9
+ * @throws {ConsumerLimitViolationError} When adverse slippage exceeds tolerance.
10
+ * @throws {MissingVerificationEvidenceError} When a quoted amount has no observation.
11
+ * @internal
12
+ */
13
+ export declare function checkSlippage(params: {
14
+ readonly limits: SlippageLimits;
15
+ readonly observed: SlippageQuote;
16
+ readonly context: SimulationVerificationContext;
17
+ readonly debtShares?: boolean;
18
+ }): SlippageLimits;