@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,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAssets = resolveAssets;
4
+ const abis_1 = require("@morpho-org/morpho-sdk/abis");
5
+ const viem_1 = require("viem");
6
+ const actions_1 = require("viem/actions");
7
+ const errors_js_1 = require("../../errors.js");
8
+ const measurement_plan_js_1 = require("../measurement-plan.js");
9
+ /**
10
+ * Resolve underlying tokens only when an asset quote lacks an explicit token.
11
+ * Share-only quotes need no RPC reads; vault versions and factories are not discovered.
12
+ * @param params - Caller operations, Morpho address, client, request context, and pinned state block.
13
+ * @returns Operations with the token addresses required by their asset quotes.
14
+ * @throws {ExternalServiceError} For transport failures during a required metadata read.
15
+ * @throws {MissingVerificationEvidenceError} For reverted, malformed, or empty metadata.
16
+ * @internal
17
+ */
18
+ async function resolveAssets(params) {
19
+ const { client, morpho, operations, chainId, mode, blockNumber } = params;
20
+ const tokens = new Map();
21
+ const resolved = [];
22
+ for (const limit of operations) {
23
+ const plan = (0, measurement_plan_js_1.operationMeasurementPlan)(limit);
24
+ const result = { limit };
25
+ for (const field of ["assetsPaid", "assetsReceived"]) {
26
+ if (limit.quote[field] === undefined)
27
+ continue;
28
+ const explicit = field === "assetsPaid" ? limit.assetPaid : limit.assetReceived;
29
+ if (explicit !== undefined) {
30
+ result[field] = explicit;
31
+ continue;
32
+ }
33
+ const source = plan[field];
34
+ const key = source.type === "market"
35
+ ? `market:${source.marketId.toLowerCase()}`
36
+ : `vault:${source.vault.toLowerCase()}`;
37
+ let pair = tokens.get(key);
38
+ if (pair === undefined) {
39
+ try {
40
+ if (source.type === "market") {
41
+ const values = await (0, actions_1.readContract)(client, {
42
+ address: morpho,
43
+ abi: abis_1.blueAbi,
44
+ functionName: "idToMarketParams",
45
+ args: [source.marketId],
46
+ blockNumber,
47
+ });
48
+ pair = [values[0], values[1]];
49
+ }
50
+ else {
51
+ const asset = await (0, actions_1.readContract)(client, {
52
+ address: source.vault,
53
+ abi: viem_1.erc4626Abi,
54
+ functionName: "asset",
55
+ blockNumber,
56
+ });
57
+ pair = [asset, asset];
58
+ }
59
+ }
60
+ catch (cause) {
61
+ const reverted = cause instanceof viem_1.BaseError &&
62
+ cause.walk((error) => error instanceof viem_1.ContractFunctionRevertedError ||
63
+ error instanceof viem_1.ExecutionRevertedError) !== null;
64
+ // Revert-shaped chains (code 3, or -32000 "execution reverted")
65
+ // can nest an RpcRequestError, so a revert must win over the
66
+ // transport match.
67
+ if (!reverted &&
68
+ ((cause instanceof viem_1.BaseError &&
69
+ cause.walk((error) => error instanceof viem_1.HttpRequestError ||
70
+ error instanceof viem_1.TimeoutError ||
71
+ error instanceof viem_1.RpcRequestError ||
72
+ error instanceof viem_1.RpcError ||
73
+ (error instanceof Error && error.name === "AbortError")) !== null) ||
74
+ (cause instanceof Error && cause.name === "AbortError"))) {
75
+ throw new errors_js_1.ExternalServiceError("Cannot resolve the quoted asset because its metadata request failed. Check the RPC endpoint.", { cause });
76
+ }
77
+ throw new errors_js_1.MissingVerificationEvidenceError(`Cannot resolve verification evidence for "${limit.type}" asset source "${key}".`, {
78
+ cause,
79
+ context: {
80
+ stage: "verification",
81
+ chainId,
82
+ mode,
83
+ blockNumber,
84
+ ...plan.subject,
85
+ field,
86
+ },
87
+ });
88
+ }
89
+ tokens.set(key, pair);
90
+ }
91
+ const token = source.type === "market"
92
+ ? pair[source.asset === "loan" ? 0 : 1]
93
+ : pair[0];
94
+ if ((0, viem_1.isAddressEqual)(token, viem_1.zeroAddress)) {
95
+ throw new errors_js_1.MissingVerificationEvidenceError(`Cannot resolve verification evidence for "${limit.type}" asset source "${key}": the resolved token address is zero.`, {
96
+ context: {
97
+ stage: "verification",
98
+ chainId,
99
+ mode,
100
+ blockNumber,
101
+ ...plan.subject,
102
+ field,
103
+ },
104
+ });
105
+ }
106
+ result[field] = token;
107
+ }
108
+ resolved.push(result);
109
+ }
110
+ return resolved;
111
+ }
@@ -0,0 +1,33 @@
1
+ import type { BlockTag, Client, Hex } from "viem";
2
+ /** One concrete canonical block: the state anchor for every pinned read. @internal */
3
+ export interface PinnedBlock {
4
+ readonly number: bigint;
5
+ readonly hash: Hex;
6
+ readonly timestamp: bigint;
7
+ }
8
+ /**
9
+ * Resolve the requested state block exactly once so `latest` cannot drift.
10
+ *
11
+ * Every downstream read and the `eth_simulateV1` call pin this number; the
12
+ * execution boundary re-fetches the hash afterwards to detect a reorg that
13
+ * swapped the block mid-flight.
14
+ *
15
+ * @param params - Resolution parameters.
16
+ * @param params.client - Shared simulation client (created by
17
+ * {@link createSimulationClient}).
18
+ * @param params.blockNumber - Requested block number or tag; defaults to
19
+ * `latest`.
20
+ * @param params.signal - Optional pipeline abort signal; checked around the
21
+ * request so an aborted pipeline never produces a pin.
22
+ * @returns The resolved {@link PinnedBlock}.
23
+ * @throws {ExternalServiceError} For transport failures, timeouts, or an
24
+ * aborted signal.
25
+ * @throws {InvalidSimulationResponseError} When the node returns a block
26
+ * without a number or hash.
27
+ * @internal
28
+ */
29
+ export declare function resolvePinnedBlock(params: {
30
+ readonly client: Client;
31
+ readonly blockNumber?: bigint | BlockTag;
32
+ readonly signal?: AbortSignal;
33
+ }): Promise<PinnedBlock>;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePinnedBlock = resolvePinnedBlock;
4
+ const actions_1 = require("viem/actions");
5
+ const errors_js_1 = require("../../errors.js");
6
+ const eth_simulate_v1_js_1 = require("./eth-simulate-v1.js");
7
+ /**
8
+ * Resolve the requested state block exactly once so `latest` cannot drift.
9
+ *
10
+ * Every downstream read and the `eth_simulateV1` call pin this number; the
11
+ * execution boundary re-fetches the hash afterwards to detect a reorg that
12
+ * swapped the block mid-flight.
13
+ *
14
+ * @param params - Resolution parameters.
15
+ * @param params.client - Shared simulation client (created by
16
+ * {@link createSimulationClient}).
17
+ * @param params.blockNumber - Requested block number or tag; defaults to
18
+ * `latest`.
19
+ * @param params.signal - Optional pipeline abort signal; checked around the
20
+ * request so an aborted pipeline never produces a pin.
21
+ * @returns The resolved {@link PinnedBlock}.
22
+ * @throws {ExternalServiceError} For transport failures, timeouts, or an
23
+ * aborted signal.
24
+ * @throws {InvalidSimulationResponseError} When the node returns a block
25
+ * without a number or hash.
26
+ * @internal
27
+ */
28
+ async function resolvePinnedBlock(params) {
29
+ const { client, blockNumber, signal } = params;
30
+ const block = await (0, eth_simulate_v1_js_1.rpc)("eth_getBlock", async () => {
31
+ signal?.throwIfAborted();
32
+ const resolved = await (0, actions_1.getBlock)(client, typeof blockNumber === "bigint"
33
+ ? { blockNumber }
34
+ : { blockTag: blockNumber ?? "latest" });
35
+ signal?.throwIfAborted();
36
+ return resolved;
37
+ });
38
+ if (block.number === null || block.hash === null) {
39
+ throw new errors_js_1.InvalidSimulationResponseError("eth_getBlock returned a block without number or hash. Check that the endpoint resolved the requested state block.");
40
+ }
41
+ return {
42
+ number: block.number,
43
+ hash: block.hash,
44
+ timestamp: block.timestamp,
45
+ };
46
+ }
@@ -0,0 +1,14 @@
1
+ import type { Address } from "viem";
2
+ import type { OperationLimit } from "../limits.js";
3
+ import type { SimulationMode } from "../params.js";
4
+ import type { ExecutionBlock } from "./backends/parse-response.js";
5
+ /** Explicit simulation context shared by the checker and result assembly. @internal */
6
+ export interface CheckContext {
7
+ readonly chainId: number;
8
+ readonly mode: SimulationMode;
9
+ readonly block: ExecutionBlock;
10
+ readonly owner: Address;
11
+ readonly limits: {
12
+ readonly operations: readonly OperationLimit[];
13
+ };
14
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,29 @@
1
+ import type { MarketId } from "@morpho-org/blue-sdk";
2
+ import type { Address } from "viem";
3
+ import type { OperationLimit, SimulationOperationSubject } from "../limits.js";
4
+ type AssetMeasurementSource = {
5
+ readonly type: "market";
6
+ readonly marketId: MarketId;
7
+ readonly asset: "loan" | "collateral";
8
+ } | {
9
+ readonly type: "vault";
10
+ readonly vault: Address;
11
+ };
12
+ type ShareMeasurementSource = {
13
+ readonly type: "position";
14
+ readonly marketId: MarketId;
15
+ readonly shares: "supplyShares" | "borrowShares";
16
+ } | {
17
+ readonly type: "balance";
18
+ readonly token: Address;
19
+ };
20
+ interface OperationMeasurementPlan {
21
+ readonly subject: SimulationOperationSubject;
22
+ readonly assetsPaid: AssetMeasurementSource;
23
+ readonly assetsReceived: AssetMeasurementSource;
24
+ readonly sharesMinted?: ShareMeasurementSource;
25
+ readonly sharesBurned?: ShareMeasurementSource;
26
+ }
27
+ /** Describe each quote field's evidence source with an exhaustive operation switch. @internal */
28
+ export declare function operationMeasurementPlan(limit: OperationLimit): OperationMeasurementPlan;
29
+ export {};
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.operationMeasurementPlan = operationMeasurementPlan;
4
+ const market = (marketId, asset) => ({ type: "market", marketId, asset });
5
+ const position = (marketId, shares) => ({ type: "position", marketId, shares });
6
+ const balance = (token) => ({
7
+ type: "balance",
8
+ token,
9
+ });
10
+ const vault = (address) => ({
11
+ type: "vault",
12
+ vault: address,
13
+ });
14
+ /** Describe each quote field's evidence source with an exhaustive operation switch. @internal */
15
+ function operationMeasurementPlan(limit) {
16
+ switch (limit.type) {
17
+ case "blueSupply":
18
+ case "blueWithdraw": {
19
+ const shares = position(limit.marketId, "supplyShares");
20
+ return {
21
+ subject: { operation: limit.type, marketId: limit.marketId },
22
+ assetsPaid: market(limit.marketId, "loan"),
23
+ assetsReceived: market(limit.marketId, "loan"),
24
+ sharesMinted: shares,
25
+ sharesBurned: shares,
26
+ };
27
+ }
28
+ case "blueSupplyCollateral":
29
+ case "blueWithdrawCollateral":
30
+ return {
31
+ subject: { operation: limit.type, marketId: limit.marketId },
32
+ assetsPaid: market(limit.marketId, "collateral"),
33
+ assetsReceived: market(limit.marketId, "collateral"),
34
+ };
35
+ case "blueBorrow":
36
+ case "blueRepay": {
37
+ const shares = position(limit.marketId, "borrowShares");
38
+ return {
39
+ subject: { operation: limit.type, marketId: limit.marketId },
40
+ assetsPaid: market(limit.marketId, "loan"),
41
+ assetsReceived: market(limit.marketId, "loan"),
42
+ sharesMinted: shares,
43
+ sharesBurned: shares,
44
+ };
45
+ }
46
+ case "blueSupplyCollateralBorrow": {
47
+ const shares = position(limit.marketId, "borrowShares");
48
+ return {
49
+ subject: { operation: limit.type, marketId: limit.marketId },
50
+ assetsPaid: market(limit.marketId, "collateral"),
51
+ assetsReceived: market(limit.marketId, "loan"),
52
+ sharesMinted: shares,
53
+ sharesBurned: shares,
54
+ };
55
+ }
56
+ case "blueRepayWithdrawCollateral": {
57
+ const shares = position(limit.marketId, "borrowShares");
58
+ return {
59
+ subject: { operation: limit.type, marketId: limit.marketId },
60
+ assetsPaid: market(limit.marketId, "loan"),
61
+ assetsReceived: market(limit.marketId, "collateral"),
62
+ sharesMinted: shares,
63
+ sharesBurned: shares,
64
+ };
65
+ }
66
+ case "blueRefinance":
67
+ return {
68
+ subject: {
69
+ operation: limit.type,
70
+ sourceMarketId: limit.sourceMarketId,
71
+ targetMarketId: limit.targetMarketId,
72
+ },
73
+ assetsPaid: market(limit.sourceMarketId, "loan"),
74
+ assetsReceived: market(limit.sourceMarketId, "loan"),
75
+ sharesMinted: position(limit.targetMarketId, "borrowShares"),
76
+ sharesBurned: position(limit.sourceMarketId, "borrowShares"),
77
+ };
78
+ case "vaultV1Deposit":
79
+ case "vaultV2Deposit":
80
+ case "vaultV1Withdraw":
81
+ case "vaultV2Withdraw":
82
+ case "vaultV1Redeem":
83
+ case "vaultV2Redeem":
84
+ case "vaultV2ForceWithdraw":
85
+ case "vaultV2ForceRedeem":
86
+ case "vaultV1InKindRedeem":
87
+ case "vaultV2InKindRedeem":
88
+ return {
89
+ subject: {
90
+ operation: limit.type,
91
+ vault: limit.vault,
92
+ },
93
+ assetsPaid: vault(limit.vault),
94
+ assetsReceived: vault(limit.vault),
95
+ sharesMinted: balance(limit.vault),
96
+ sharesBurned: balance(limit.vault),
97
+ };
98
+ case "vaultV1MigrateToV2":
99
+ return {
100
+ subject: {
101
+ operation: limit.type,
102
+ sourceVault: limit.sourceVault,
103
+ targetVault: limit.targetVault,
104
+ },
105
+ assetsPaid: vault(limit.sourceVault),
106
+ assetsReceived: vault(limit.sourceVault),
107
+ sharesMinted: balance(limit.targetVault),
108
+ sharesBurned: balance(limit.sourceVault),
109
+ };
110
+ default: {
111
+ const exhaustive = limit;
112
+ return exhaustive;
113
+ }
114
+ }
115
+ }
@@ -1,7 +1,10 @@
1
1
  import { type Address } from "viem";
2
2
  import type { RawCall, SimulationLogger, Transfer } from "../../types.js";
3
+ /** `keccak256("Transfer(address,address,uint256)")` — ERC-20 Transfer event topic. @internal */
3
4
  export declare const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
5
+ /** `keccak256("Withdrawal(address,uint256)")` — WETH9 Withdrawal event topic. @internal */
4
6
  export declare const WITHDRAWAL_TOPIC = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65";
7
+ /** `keccak256("Deposit(address,uint256)")` — WETH9 Deposit event topic. @internal */
5
8
  export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c";
6
9
  /**
7
10
  * Parse raw EVM logs into individual Transfer events.
@@ -23,9 +26,7 @@ export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d
23
26
  * those made through internal calls (e.g. a `WETH.withdraw` refund) — are
24
27
  * synthesized as `Transfer` events emitted from the native sentinel
25
28
  * `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
26
- * collapsed to viem's `ethAddress` so native deltas key consistently across
27
- * backends. Tenderly does not emit these synthetic logs (it derives native ETH
28
- * separately), so this path is inert there.
29
+ * collapsed to viem's `ethAddress` so native deltas key consistently.
29
30
  *
30
31
  * **WETH9 dedup assumption — canonical atomic emission.** Dedup for the
31
32
  * registered wrapped-native token is scoped to the same tx: a zero-address
@@ -4,11 +4,11 @@ exports.DEPOSIT_TOPIC = exports.WITHDRAWAL_TOPIC = exports.TRANSFER_TOPIC = void
4
4
  exports.parseTransfers = parseTransfers;
5
5
  const viem_1 = require("viem");
6
6
  const asset_changes_js_1 = require("../asset-changes.js");
7
- // keccak256("Transfer(address,address,uint256)") — ERC-20 transfer event
7
+ /** `keccak256("Transfer(address,address,uint256)")` — ERC-20 Transfer event topic. @internal */
8
8
  exports.TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
9
- // keccak256("Withdrawal(address,uint256)") — WETH9 unwrap event
9
+ /** `keccak256("Withdrawal(address,uint256)")` — WETH9 Withdrawal event topic. @internal */
10
10
  exports.WITHDRAWAL_TOPIC = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65";
11
- // keccak256("Deposit(address,uint256)") — WETH9 wrap event
11
+ /** `keccak256("Deposit(address,uint256)")` — WETH9 Deposit event topic. @internal */
12
12
  exports.DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c";
13
13
  const TOPIC_HEX_LENGTH = 66; // "0x" + 32 bytes
14
14
  const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
@@ -32,9 +32,7 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
32
32
  * those made through internal calls (e.g. a `WETH.withdraw` refund) — are
33
33
  * synthesized as `Transfer` events emitted from the native sentinel
34
34
  * `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
35
- * collapsed to viem's `ethAddress` so native deltas key consistently across
36
- * backends. Tenderly does not emit these synthetic logs (it derives native ETH
37
- * separately), so this path is inert there.
35
+ * collapsed to viem's `ethAddress` so native deltas key consistently.
38
36
  *
39
37
  * **WETH9 dedup assumption — canonical atomic emission.** Dedup for the
40
38
  * registered wrapped-native token is scoped to the same tx: a zero-address
@@ -20,14 +20,11 @@ interface AssertNoBundlesRetentionParams {
20
20
  * violation.
21
21
  *
22
22
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
23
- * from parsed `transfers` (log-derived, identical across backends). Native ETH
24
- * emits no event log, so it can never appear in `transfers` on the Tenderly
25
- * primary backend — Tenderly derives native moves into `assetChanges` instead.
26
- * Native ETH is therefore read from `assetChanges`, the cross-backend source of
27
- * truth for native value: Tenderly derives it from its trace, and
28
- * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
29
- * this, native ETH stuck in a bundles contract would pass the guard undetected
30
- * on the Tenderly path (Cantina finding 1440).
23
+ * from parsed `transfers` (log-derived). Native ETH emits no event log of its
24
+ * own, so it is read from `assetChanges`, the source of truth for native value,
25
+ * which `eth_simulateV1` derives from the synthetic `traceTransfers` logs.
26
+ * Without this, native ETH stuck in a bundles contract would pass the guard
27
+ * undetected (Cantina finding 1440).
31
28
  *
32
29
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
33
30
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
@@ -41,8 +38,8 @@ interface AssertNoBundlesRetentionParams {
41
38
  * neither are skipped (a `logger.warn` records the skip).
42
39
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
43
40
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
44
- * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
45
- * source of truth for native value stuck in a restricted contract.
41
+ * @param params.assetChanges - Per-account native-ETH deltas, the source of
42
+ * truth for native value stuck in a restricted contract.
46
43
  * @param params.logger - Optional logger. Receives retention-check skip warnings
47
44
  * and pre-existing-balance sweep telemetry (net-negative flow).
48
45
  * @returns Nothing. Returns silently when no restricted contract retains value
@@ -30,14 +30,11 @@ const DUST_THRESHOLD = 100n;
30
30
  * violation.
31
31
  *
32
32
  * **Two flow sources, one per asset class.** ERC20 / WETH9 retention is read
33
- * from parsed `transfers` (log-derived, identical across backends). Native ETH
34
- * emits no event log, so it can never appear in `transfers` on the Tenderly
35
- * primary backend — Tenderly derives native moves into `assetChanges` instead.
36
- * Native ETH is therefore read from `assetChanges`, the cross-backend source of
37
- * truth for native value: Tenderly derives it from its trace, and
38
- * `eth_simulateV1` derives it from the synthetic `traceTransfers` logs. Without
39
- * this, native ETH stuck in a bundles contract would pass the guard undetected
40
- * on the Tenderly path (Cantina finding 1440).
33
+ * from parsed `transfers` (log-derived). Native ETH emits no event log of its
34
+ * own, so it is read from `assetChanges`, the source of truth for native value,
35
+ * which `eth_simulateV1` derives from the synthetic `traceTransfers` logs.
36
+ * Without this, native ETH stuck in a bundles contract would pass the guard
37
+ * undetected (Cantina finding 1440).
41
38
  *
42
39
  * To avoid double-counting native ETH on `eth_simulateV1` — where the same
43
40
  * native move exists both as a synthetic `ethAddress` transfer log *and* in the
@@ -51,8 +48,8 @@ const DUST_THRESHOLD = 100n;
51
48
  * neither are skipped (a `logger.warn` records the skip).
52
49
  * @param params.transfers - Parsed ERC20 / WETH9 transfer flows (plus the
53
50
  * `eth_simulateV1` synthetic native sentinel) scanned for net retention.
54
- * @param params.assetChanges - Per-account native-ETH deltas, the cross-backend
55
- * source of truth for native value stuck in a restricted contract.
51
+ * @param params.assetChanges - Per-account native-ETH deltas, the source of
52
+ * truth for native value stuck in a restricted contract.
56
53
  * @param params.logger - Optional logger. Receives retention-check skip warnings
57
54
  * and pre-existing-balance sweep telemetry (net-negative flow).
58
55
  * @returns Nothing. Returns silently when no restricted contract retains value
@@ -107,9 +104,9 @@ function assertNoBundlesRetention(params) {
107
104
  flow.set(key, { address, token, netChange: delta });
108
105
  }
109
106
  };
110
- // Native ETH from `assetChanges` (authoritative, cross-backend). Track which
111
- // restricted contracts already have a native entry here so the transfer-log
112
- // pass below does not re-add the same native move on `eth_simulateV1`.
107
+ // Native ETH from `assetChanges` (authoritative). Track which restricted
108
+ // contracts already have a native entry here so the transfer-log pass below
109
+ // does not re-add the same native move on `eth_simulateV1`.
113
110
  const nativeFromAssetChanges = new Set();
114
111
  for (const { account, changes } of assetChanges) {
115
112
  if (!restrictedAddresses.has(account))
@@ -148,7 +145,7 @@ function assertNoBundlesRetention(params) {
148
145
  throw new errors_js_1.BlacklistViolationError("Simulation detected asset transfers retained by restricted bundles contracts", retained.map((e) => ({
149
146
  address: e.address,
150
147
  token: e.token,
151
- netRetained: e.netChange.toString(),
148
+ netRetained: e.netChange,
152
149
  })));
153
150
  }
154
151
  }
@@ -1,15 +1,9 @@
1
- import type { SimulationConfig, TenderlyRpcConfig } from "../../types.js";
2
- interface ChainCapability {
3
- tenderlyRpc?: TenderlyRpcConfig;
4
- simulateV1Url?: string;
5
- }
1
+ import type { ChainSimulationConfig, SimulationConfig } from "../../types.js";
6
2
  /**
7
3
  * Stage 2 of the simulate() pipeline.
8
4
  *
9
- * Looks up the per-chain `ChainSimulationConfig` and exposes whichever
10
- * backends were configured. Throws `UnsupportedChainError` when the chain is
11
- * absent from the map or has neither backend wired (defensive — the type
12
- * already enforces at least one at construction).
5
+ * Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
6
+ * when the chain is absent from the map or has no `simulateV1Url` (defensive —
7
+ * the type already requires it at construction).
13
8
  */
14
- export declare function resolveChain(config: SimulationConfig, chainId: number): ChainCapability;
15
- export {};
9
+ export declare function resolveChain(config: SimulationConfig, chainId: number): ChainSimulationConfig;
@@ -5,18 +5,13 @@ const errors_js_1 = require("../../errors.js");
5
5
  /**
6
6
  * Stage 2 of the simulate() pipeline.
7
7
  *
8
- * Looks up the per-chain `ChainSimulationConfig` and exposes whichever
9
- * backends were configured. Throws `UnsupportedChainError` when the chain is
10
- * absent from the map or has neither backend wired (defensive — the type
11
- * already enforces at least one at construction).
8
+ * Looks up the per-chain `ChainSimulationConfig`. Throws `UnsupportedChainError`
9
+ * when the chain is absent from the map or has no `simulateV1Url` (defensive —
10
+ * the type already requires it at construction).
12
11
  */
13
12
  function resolveChain(config, chainId) {
14
13
  const entry = config.chains.get(chainId);
15
- if (!entry || (!entry.tenderlyRpc && !entry.simulateV1Url)) {
14
+ if (!entry?.simulateV1Url)
16
15
  throw new errors_js_1.UnsupportedChainError(chainId);
17
- }
18
- return {
19
- tenderlyRpc: entry.tenderlyRpc,
20
- simulateV1Url: entry.simulateV1Url,
21
- };
16
+ return entry;
22
17
  }
@@ -0,0 +1,58 @@
1
+ import type { Address } from "viem";
2
+ import type { SimulationTransaction } from "../../types.js";
3
+ import type { ParsedRequest } from "../request/parse-request.js";
4
+ import type { ReadPhase, StateRead } from "../state/contract.js";
5
+ /** One call planned inside the single `eth_simulateV1` run. @internal */
6
+ export type PlannedCall = {
7
+ /** What the call carries on the wire. */
8
+ readonly transaction: SimulationTransaction & {
9
+ readonly value: bigint;
10
+ };
11
+ } & ({
12
+ /** A caller transaction. */
13
+ readonly type: "transaction";
14
+ readonly transactionIndex: number;
15
+ } | {
16
+ /** A preview-authorization preparation call. */
17
+ readonly type: "preparation";
18
+ readonly authorizationIndex: number;
19
+ readonly callIndex: number;
20
+ } | {
21
+ /** A state read-back call at the `before`/`after` phase. */
22
+ readonly type: "stateRead";
23
+ readonly phase: ReadPhase;
24
+ readonly read: StateRead;
25
+ });
26
+ /** The ordered call plan for one simulation. @internal */
27
+ export interface ExecutionPlan {
28
+ readonly request: ParsedRequest;
29
+ readonly calls: readonly PlannedCall[];
30
+ }
31
+ /**
32
+ * Plan the execution of a request as ordered `eth_simulateV1` calls.
33
+ *
34
+ * Sequence (design §plan order): every `before` state read → preparation
35
+ * calls → every user transaction → every `after` state read.
36
+ *
37
+ * Preparation calls run `from` the owner; state reads run `from`
38
+ * `zeroAddress` and never receive a public `txIdx`.
39
+ *
40
+ * @param request - The parsed request.
41
+ * @param owner - The bundle owner; sender of preparation calls.
42
+ * @param preparations - Ordered authorization preparations to simulate.
43
+ * @param reads - The full state-read list, replayed at `before` and `after`.
44
+ * @returns A deep-frozen {@link ExecutionPlan}; pure — equal inputs produce
45
+ * structurally equal plans.
46
+ * @internal
47
+ */
48
+ export declare function planExecution(params: {
49
+ readonly request: ParsedRequest;
50
+ readonly owner: Address;
51
+ readonly preparations: readonly {
52
+ readonly authorizationIndex: number;
53
+ readonly calls: readonly (SimulationTransaction & {
54
+ readonly value: bigint;
55
+ })[];
56
+ }[];
57
+ readonly reads: readonly StateRead[];
58
+ }): ExecutionPlan;