@morpho-org/evm-simulation 4.2.1 → 5.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 (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 +25 -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 +66 -20
  23. package/lib/cjs/simulate/backends/eth-simulate-v1.js +172 -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 +151 -0
  54. package/lib/cjs/simulate/simulate.d.ts +100 -51
  55. package/lib/cjs/simulate/simulate.js +99 -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 +35 -95
  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 +25 -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 +66 -20
  86. package/lib/esm/simulate/backends/eth-simulate-v1.js +170 -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 +148 -0
  117. package/lib/esm/simulate/simulate.d.ts +100 -51
  118. package/lib/esm/simulate/simulate.js +99 -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 +35 -95
  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,151 @@
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
+ const pinnedBlock = await (0, resolve_pinned_block_js_1.resolvePinnedBlock)({
55
+ client,
56
+ blockNumber: request.blockNumber,
57
+ signal,
58
+ });
59
+ // With no calldata decoding, the caller's declared `limits.operations`
60
+ // entries are the operations: subjects for state reads and per-operation
61
+ // checks come straight from the limit list.
62
+ const resolved = await (0, resolve_assets_js_1.resolveAssets)({
63
+ client,
64
+ morpho,
65
+ operations: limits.operations,
66
+ chainId: request.chainId,
67
+ mode: request.mode,
68
+ blockNumber: pinnedBlock.number,
69
+ });
70
+ const observations = (0, read_state_js_1.planStateReads)({ operations: resolved, owner, morpho });
71
+ const preparations = preview
72
+ ? (0, prepare_js_1.prepareAuthorizations)({
73
+ authorizations: request.authorizations,
74
+ owner,
75
+ morpho,
76
+ })
77
+ : [];
78
+ const plan = (0, plan_execution_js_1.planExecution)({
79
+ request,
80
+ owner,
81
+ preparations,
82
+ reads: observations.reads,
83
+ });
84
+ // Execute against the block resolved once above; the boundary rechecks its hash.
85
+ const execution = await (0, index_js_1.executePlan)({
86
+ client,
87
+ plan,
88
+ stateBlock: pinnedBlock,
89
+ });
90
+ const userCalls = execution.calls
91
+ .filter((call) => call.planned.type === "transaction")
92
+ .map((call) => call.result);
93
+ if (userCalls.length !== request.transactions.length)
94
+ throw new errors_js_1.InvalidSimulationResponseError(`Execution returned ${userCalls.length} user call result(s) for ${request.transactions.length} transaction(s)`);
95
+ const decodePhase = (phase) => new Map(execution.stateReads
96
+ .filter((read) => read.phase === phase)
97
+ .map((read) => [
98
+ read.read.id,
99
+ (0, read_state_js_1.decodeStateRead)(read.read, read.returnData, {
100
+ chainId: request.chainId,
101
+ mode: request.mode,
102
+ blockNumber: pinnedBlock.number,
103
+ }),
104
+ ]));
105
+ const transfers = (0, index_js_2.parseTransfers)(userCalls, {
106
+ wNative: addresses === undefined ? undefined : (addresses.wNative ?? null),
107
+ logger: config.logger,
108
+ });
109
+ const assetChanges = (0, asset_changes_js_1.groupAssetChanges)(transfers.flatMap(({ token, from, to, amount }) => [
110
+ { account: to, token, diff: amount },
111
+ { account: from, token, diff: -amount },
112
+ ]));
113
+ const ctx = {
114
+ chainId: request.chainId,
115
+ mode: request.mode,
116
+ block: execution.block,
117
+ owner,
118
+ limits,
119
+ };
120
+ const authorizations = request.authorizations.map((authorization, authorizationIndex) => ({
121
+ authorizationIndex,
122
+ authorization,
123
+ calls: execution.calls.flatMap(({ planned, result }) => planned.type === "preparation" &&
124
+ planned.authorizationIndex === authorizationIndex
125
+ ? [{ transaction: planned.transaction, result }]
126
+ : []),
127
+ }));
128
+ const checked = (0, verify_slippage_js_1.verifySlippage)({
129
+ ctx,
130
+ operations: observations.operations,
131
+ before: decodePhase("before"),
132
+ after: decodePhase("after"),
133
+ transfers,
134
+ requestTransactions: request.transactions,
135
+ });
136
+ (0, bundles_retention_js_1.assertNoBundlesRetention)({
137
+ chainId: request.chainId,
138
+ transfers,
139
+ assetChanges,
140
+ logger: config.logger,
141
+ });
142
+ return (0, result_js_1.assembleResult)({
143
+ ctx,
144
+ request,
145
+ operations: checked,
146
+ authorizations,
147
+ userCalls,
148
+ transfers,
149
+ assetChanges,
150
+ });
151
+ }
@@ -1,68 +1,117 @@
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.
38
+ *
39
+ * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
40
+ * the overall timeout budget.
26
41
  * @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`.
42
+ * @param params.chainId - Chain id the bundle targets; must match the endpoint.
43
+ * @param params.transactions - The bundle's transactions, in execution order.
44
+ * All must share the same `from`.
45
+ * @param params.mode - `"final"` (default) or `"preview"`.
46
+ * @param params.authorizations - Preview-only typed authorization descriptors.
47
+ * @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
48
+ * @param params.blockNumber - Optional pinned block number or `BlockTag` other
49
+ * than `"pending"`.
50
+ * Defaults to `latest`, resolved exactly once.
51
+ * @throws {SimulationValidationError} for invalid input (mixed senders, bad
52
+ * addresses, empty transactions, malformed authorizations, final-mode
53
+ * authorizations, malformed limits, unknown fields, a `"pending"` block tag, or share quotes for
54
+ * `blueSupplyCollateral` / `blueWithdrawCollateral`).
55
+ * @throws {ConsumerLimitViolationError} when a declared `limits` bound is
56
+ * violated by the observed effects.
57
+ * @throws {UnsupportedChainError} when the chain has no `eth_simulateV1`
58
+ * endpoint configured, or limits/preview authorizations require a Morpho
59
+ * Blue address absent from blue-sdk's `getChainAddresses`.
60
+ * @throws {SimulationRevertedError} when a preparation or user transaction reverts.
61
+ * @throws {MissingVerificationEvidenceError} when a planned state read fails
62
+ * or returns empty data, native outgoing traces do not cover value
63
+ * sent, or required metadata reverts or returns empty/invalid data.
64
+ * @throws {InvalidSimulationResponseError} when the node response cannot be
65
+ * trusted (bad shape, call-count mismatch, block that is neither the pinned
66
+ * state block nor its immediate successor, a successor with a mismatched
67
+ * `parentHash`, a block timestamp earlier than the pinned block's, a
68
+ * malformed per-call result, a quoted balance/position read whose non-empty
69
+ * return data cannot be decoded, a state-block hash that changed or a pinned
70
+ * block that vanished mid-flight, or an
71
+ * endpoint whose `eth_chainId` differs from `params.chainId`; chain identity
72
+ * is checked before any block lookup).
73
+ * @throws {BlacklistViolationError} when the simulation leaves value retained
74
+ * beyond the dust threshold by a `bundles` periphery contract
75
+ * (VaultExitBundlesV1, VaultBundlesV1, BlueBundlesV1, MidnightBundlesV1).
76
+ * Never bypassable.
77
+ * @throws {ExternalServiceError} when the RPC is unavailable within the
78
+ * timeout budget or returns a malformed JSON-RPC envelope. Chain-id
79
+ * mismatches and a state block without number/hash are reported as
80
+ * `InvalidSimulationResponseError`.
81
+ * @returns A frozen {@link VerifiedSimulationResult} carrying the normalized
82
+ * `simulationTxs`, per-tx `calls` (aligned 1:1), parsed `transfers` (each
83
+ * stamped with `txIdx`), per-account net `assetChanges`, and per-operation
84
+ * verification outcomes and preparations. Each `verification.operations[i]`
85
+ * mirrors the `limits.operations[i]` entry it verified. Only the amounts
86
+ * quoted in each entry are checked; `checkedLimits` identifies precisely
87
+ * what was checked. Measurements are scoped to the whole bundle.
47
88
  * @example
48
89
  * ```ts
49
90
  * import { simulate } from "@morpho-org/evm-simulation";
91
+ * import { type Address, encodeFunctionData, erc20Abi, getAddress } from "viem";
92
+ *
93
+ * const rpcUrl = "https://rpc.example";
94
+ * const user: Address = getAddress("0x1111111111111111111111111111111111111111");
95
+ * const usdc: Address = getAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
96
+ * const recipient: Address = getAddress("0x2222222222222222222222222222222222222222");
50
97
  *
51
98
  * 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
- * },
99
+ * { chains: new Map([[1, { simulateV1Url: rpcUrl }]]) },
60
100
  * {
61
101
  * chainId: 1,
62
- * transactions: [{ from: user, to: vaultAddress, data: encodedCalldata, value: 0n }],
102
+ * transactions: [
103
+ * {
104
+ * from: user,
105
+ * to: usdc,
106
+ * data: encodeFunctionData({
107
+ * abi: erc20Abi,
108
+ * functionName: "transfer",
109
+ * args: [recipient, 1_000_000n],
110
+ * }),
111
+ * },
112
+ * ],
63
113
  * },
64
114
  * );
65
- * // result satisfies SimulationResult
66
115
  * ```
67
116
  */
68
- export declare function simulate(config: SimulationConfig, params: SimulateParams): Promise<SimulationResult>;
117
+ export declare function simulate(config: SimulationConfig, params: SimulateParams): Promise<VerifiedSimulationResult>;
@@ -1,104 +1,121 @@
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.
40
+ *
41
+ * @param config - Required per-chain `eth_simulateV1` URL, optional logger, and
42
+ * the overall timeout budget.
32
43
  * @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`.
44
+ * @param params.chainId - Chain id the bundle targets; must match the endpoint.
45
+ * @param params.transactions - The bundle's transactions, in execution order.
46
+ * All must share the same `from`.
47
+ * @param params.mode - `"final"` (default) or `"preview"`.
48
+ * @param params.authorizations - Preview-only typed authorization descriptors.
49
+ * @param params.limits - Optional quotes and percentage tolerances; unquoted amounts require no reads.
50
+ * @param params.blockNumber - Optional pinned block number or `BlockTag` other
51
+ * than `"pending"`.
52
+ * Defaults to `latest`, 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.
53
90
  * @example
54
91
  * ```ts
55
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");
56
99
  *
57
100
  * 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
- * },
101
+ * { chains: new Map([[1, { simulateV1Url: rpcUrl }]]) },
66
102
  * {
67
103
  * chainId: 1,
68
- * 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
+ * ],
69
115
  * },
70
116
  * );
71
- * // result satisfies SimulationResult
72
117
  * ```
73
118
  */
74
119
  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
- };
120
+ return (0, run_simulation_js_1.runSimulation)({ config, request: (0, index_js_1.parseRequest)(params) });
104
121
  }
@@ -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;