@morpho-org/evm-simulation 2.0.2 → 4.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 (59) hide show
  1. package/README.md +22 -30
  2. package/lib/cjs/errors.d.ts +0 -6
  3. package/lib/cjs/errors.js +1 -11
  4. package/lib/cjs/index.d.ts +2 -3
  5. package/lib/cjs/index.js +3 -6
  6. package/lib/cjs/simulate/asset-changes.d.ts +19 -0
  7. package/lib/cjs/simulate/asset-changes.js +47 -0
  8. package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +9 -0
  9. package/lib/cjs/simulate/backends/eth-simulate-v1.js +39 -1
  10. package/lib/cjs/simulate/backends/index.d.ts +1 -1
  11. package/lib/cjs/simulate/backends/index.js +3 -3
  12. package/lib/cjs/simulate/backends/tenderly-rpc.d.ts +23 -0
  13. package/lib/cjs/simulate/backends/tenderly-rpc.js +230 -0
  14. package/lib/cjs/simulate/parsing/transfers.js +3 -1
  15. package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +6 -10
  16. package/lib/cjs/simulate/pipeline/execute-simulation.js +11 -18
  17. package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +6 -5
  18. package/lib/cjs/simulate/pipeline/resolve-chain.js +10 -7
  19. package/lib/cjs/simulate/simulate.d.ts +20 -23
  20. package/lib/cjs/simulate/simulate.js +21 -20
  21. package/lib/cjs/types.d.ts +58 -39
  22. package/lib/esm/errors.d.ts +0 -6
  23. package/lib/esm/errors.js +0 -9
  24. package/lib/esm/index.d.ts +2 -3
  25. package/lib/esm/index.js +1 -2
  26. package/lib/esm/simulate/asset-changes.d.ts +19 -0
  27. package/lib/esm/simulate/asset-changes.js +44 -0
  28. package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +9 -0
  29. package/lib/esm/simulate/backends/eth-simulate-v1.js +40 -2
  30. package/lib/esm/simulate/backends/index.d.ts +1 -1
  31. package/lib/esm/simulate/backends/index.js +1 -1
  32. package/lib/esm/simulate/backends/tenderly-rpc.d.ts +23 -0
  33. package/lib/esm/simulate/backends/tenderly-rpc.js +227 -0
  34. package/lib/esm/simulate/parsing/transfers.js +3 -1
  35. package/lib/esm/simulate/pipeline/execute-simulation.d.ts +6 -10
  36. package/lib/esm/simulate/pipeline/execute-simulation.js +12 -19
  37. package/lib/esm/simulate/pipeline/resolve-chain.d.ts +6 -5
  38. package/lib/esm/simulate/pipeline/resolve-chain.js +10 -7
  39. package/lib/esm/simulate/simulate.d.ts +20 -23
  40. package/lib/esm/simulate/simulate.js +21 -20
  41. package/lib/esm/types.d.ts +58 -39
  42. package/package.json +7 -5
  43. package/src/index.ts +3 -3
  44. package/lib/cjs/screen-addresses/index.d.ts +0 -1
  45. package/lib/cjs/screen-addresses/index.js +0 -5
  46. package/lib/cjs/screen-addresses/sanctioned-addresses.d.ts +0 -11
  47. package/lib/cjs/screen-addresses/sanctioned-addresses.js +0 -77
  48. package/lib/cjs/screen-addresses/screen-addresses.d.ts +0 -50
  49. package/lib/cjs/screen-addresses/screen-addresses.js +0 -171
  50. package/lib/cjs/simulate/backends/tenderly-rest.d.ts +0 -20
  51. package/lib/cjs/simulate/backends/tenderly-rest.js +0 -279
  52. package/lib/esm/screen-addresses/index.d.ts +0 -1
  53. package/lib/esm/screen-addresses/index.js +0 -1
  54. package/lib/esm/screen-addresses/sanctioned-addresses.d.ts +0 -11
  55. package/lib/esm/screen-addresses/sanctioned-addresses.js +0 -74
  56. package/lib/esm/screen-addresses/screen-addresses.d.ts +0 -50
  57. package/lib/esm/screen-addresses/screen-addresses.js +0 -168
  58. package/lib/esm/simulate/backends/tenderly-rest.d.ts +0 -20
  59. package/lib/esm/simulate/backends/tenderly-rest.js +0 -276
@@ -1,276 +0,0 @@
1
- import { isAddress, isHex } from "viem";
2
- import { z } from "zod";
3
- import { ExternalServiceError, SimulationRevertedError } from "../../errors.js";
4
- // Zod validators for the subset of Tenderly responses we consume.
5
- // Addresses must be 40-hex-digit `0x...` (not just any string); topics and
6
- // data must be `0x`-prefixed hex so downstream `getAddress()` / `BigInt()`
7
- // calls don't throw inside `parseTransfers`, silently dropping transfers.
8
- const addressSchema = z.custom((val) => typeof val === "string" && isAddress(val));
9
- const hexSchema = z.custom((val) => typeof val === "string" && isHex(val));
10
- const tenderlyRawResponseSchema = z
11
- .object({
12
- simulation: z
13
- .object({
14
- id: z.string().optional(),
15
- status: z.boolean().optional(),
16
- error_message: z.string().optional(),
17
- })
18
- .passthrough(),
19
- transaction: z
20
- .object({
21
- error_message: z.string().optional(),
22
- gas_used: z.number(),
23
- transaction_info: z
24
- .object({
25
- logs: z
26
- .array(z
27
- .object({
28
- raw: z
29
- .object({
30
- address: addressSchema,
31
- topics: z.array(hexSchema).optional(),
32
- data: hexSchema.optional(),
33
- })
34
- .passthrough()
35
- .optional(),
36
- })
37
- .passthrough())
38
- .optional(),
39
- asset_changes: z.unknown().optional(),
40
- call_trace: z
41
- .object({
42
- output: hexSchema,
43
- })
44
- .passthrough(),
45
- })
46
- .passthrough(),
47
- })
48
- .passthrough(),
49
- })
50
- .passthrough();
51
- const tenderlyBundleRawResponseSchema = z
52
- .object({
53
- simulation_results: z.array(tenderlyRawResponseSchema).min(1),
54
- })
55
- .passthrough();
56
- /**
57
- * Simulate transactions using Tenderly REST API.
58
- *
59
- * @param shareable - true persists the simulation in Tenderly and returns a shareable URL;
60
- * false runs ephemerally with no URL. Translates to Tenderly's `save` wire field
61
- * (`save_if_fails` is also set to `shareable` — so failed preview simulations are
62
- * ALSO persisted and shareable. Callers that preview sensitive calldata should be
63
- * aware this persists in Tenderly's dashboard regardless of success/failure).
64
- */
65
- export async function simulateTenderlyRest(params) {
66
- const { config, chainId, transactions, blockNumber, shareable, signal, logger, } = params;
67
- const baseUrl = buildBaseUrl(config);
68
- try {
69
- if (transactions.length === 1) {
70
- return await simulateSingle({
71
- baseUrl,
72
- config,
73
- chainId,
74
- tx: transactions[0],
75
- blockNumber,
76
- shareable,
77
- signal,
78
- logger,
79
- });
80
- }
81
- return await simulateBundle({
82
- baseUrl,
83
- config,
84
- chainId,
85
- transactions,
86
- blockNumber,
87
- shareable,
88
- signal,
89
- logger,
90
- });
91
- }
92
- catch (error) {
93
- if (error instanceof SimulationRevertedError)
94
- throw error;
95
- if (error instanceof ExternalServiceError)
96
- throw error;
97
- throw new ExternalServiceError(`Tenderly REST API error: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
98
- }
99
- }
100
- /** URL-encode the path-segment config values so `/`, `?`, `#` can't retarget the request. */
101
- function buildBaseUrl(config) {
102
- const account = encodeURIComponent(config.accountSlug);
103
- const project = encodeURIComponent(config.projectSlug);
104
- return `${config.apiBaseUrl}/api/v1/account/${account}/project/${project}`;
105
- }
106
- /** Build the `TenderlySimulateRequest` body for one transaction. Shared by single + bundle. */
107
- // biome-ignore lint/complexity/useMaxParams: TODO refactor to ≤2 params
108
- function buildTxBody(tx, chainId, shareable, blockNumber) {
109
- return {
110
- network_id: String(chainId),
111
- from: tx.from,
112
- to: tx.to,
113
- input: tx.data,
114
- value: String(tx.value ?? 0n),
115
- save: shareable,
116
- save_if_fails: shareable,
117
- simulation_type: "full",
118
- ...(blockNumber !== undefined && {
119
- block_number: typeof blockNumber === "bigint" ? String(blockNumber) : blockNumber,
120
- }),
121
- };
122
- }
123
- async function simulateSingle(params) {
124
- const { baseUrl, config, chainId, tx, blockNumber, shareable, signal, logger, } = params;
125
- const body = buildTxBody(tx, chainId, shareable, blockNumber);
126
- const response = await fetch(`${baseUrl}/simulate`, {
127
- method: "POST",
128
- headers: {
129
- "Content-Type": "application/json",
130
- "X-Access-Key": config.accessToken,
131
- },
132
- body: JSON.stringify(body),
133
- signal,
134
- });
135
- if (!response.ok) {
136
- throw new ExternalServiceError(`Tenderly REST API returned ${response.status}: ${response.statusText}`);
137
- }
138
- const data = tenderlyRawResponseSchema.parse(await response.json());
139
- const call = parseTenderlyCall(data);
140
- const tenderlyUrl = await resolveTenderlyUrl({
141
- baseUrl,
142
- config,
143
- simulationId: data.simulation.id,
144
- shareable,
145
- signal,
146
- logger,
147
- });
148
- return { calls: [call], tenderlyUrl };
149
- }
150
- async function simulateBundle(params) {
151
- const { baseUrl, config, chainId, transactions, blockNumber, shareable, signal, logger, } = params;
152
- const body = {
153
- simulations: transactions.map((tx) => buildTxBody(tx, chainId, shareable, blockNumber)),
154
- };
155
- const response = await fetch(`${baseUrl}/simulate-bundle`, {
156
- method: "POST",
157
- headers: {
158
- "Content-Type": "application/json",
159
- "X-Access-Key": config.accessToken,
160
- },
161
- body: JSON.stringify(body),
162
- signal,
163
- });
164
- if (!response.ok) {
165
- throw new ExternalServiceError(`Tenderly REST API returned ${response.status}: ${response.statusText}`);
166
- }
167
- const data = tenderlyBundleRawResponseSchema.parse(await response.json());
168
- const simulations = data.simulation_results;
169
- const calls = simulations.map((sim) => parseTenderlyCall(sim));
170
- const lastSim = simulations[simulations.length - 1];
171
- const tenderlyUrl = await resolveTenderlyUrl({
172
- baseUrl,
173
- config,
174
- simulationId: lastSim.simulation.id,
175
- shareable,
176
- signal,
177
- logger,
178
- });
179
- return { calls, tenderlyUrl };
180
- }
181
- /**
182
- * Build the shareable Tenderly dashboard URL for the given simulation, if
183
- * `shareable=true` and `/share` succeeds. Returns `undefined` otherwise.
184
- *
185
- * Mirrors the legacy "build optimistically, clear on failure" pattern: the
186
- * URL is constructed up-front and cleared if `shareSimulation` returns false.
187
- */
188
- async function resolveTenderlyUrl(params) {
189
- const { baseUrl, config, simulationId, shareable, signal, logger } = params;
190
- if (!shareable || !simulationId)
191
- return undefined;
192
- let tenderlyUrl = `https://dashboard.tenderly.co/shared/simulation/${simulationId}`;
193
- const shared = await shareSimulation({
194
- baseUrl,
195
- config,
196
- simulationId,
197
- signal,
198
- logger,
199
- });
200
- if (!shared)
201
- tenderlyUrl = undefined;
202
- return tenderlyUrl;
203
- }
204
- /**
205
- * Try to make a simulation publicly shareable. Returns true iff Tenderly's
206
- * `/share` endpoint responded 2xx. On any other outcome (non-ok response,
207
- * abort, network error), logs via `logger` and returns false — the caller
208
- * then clears `tenderlyUrl`. `AbortError` is re-thrown so cancellation
209
- * propagates cleanly instead of silently downgrading to "no URL".
210
- */
211
- async function shareSimulation(params) {
212
- const { baseUrl, config, simulationId, signal, logger } = params;
213
- const safeId = encodeURIComponent(simulationId);
214
- try {
215
- const response = await fetch(`${baseUrl}/simulations/${safeId}/share`, {
216
- method: "POST",
217
- headers: { "X-Access-Key": config.accessToken },
218
- signal,
219
- });
220
- if (!response.ok) {
221
- logger?.warn("Tenderly /share returned non-ok; tenderlyUrl will be cleared", {
222
- simulationId,
223
- status: response.status,
224
- statusText: response.statusText,
225
- });
226
- return false;
227
- }
228
- return true;
229
- }
230
- catch (error) {
231
- // Propagate cancellation; swallow only genuine fetch-layer failures.
232
- if (error instanceof Error && error.name === "AbortError")
233
- throw error;
234
- logger?.warn("Tenderly /share fetch failed; tenderlyUrl will be cleared", {
235
- simulationId,
236
- error: error instanceof Error ? error.message : String(error),
237
- });
238
- return false;
239
- }
240
- }
241
- /**
242
- * Parse one Tenderly simulation response into a `RawCall`. The caller
243
- * (single-sim or bundle) assembles the `RawSimulationResult` around it.
244
- *
245
- * Throws `SimulationRevertedError` if `simulation.status !== true`.
246
- */
247
- function parseTenderlyCall(data) {
248
- const { simulation, transaction } = data;
249
- if (simulation.status !== true) {
250
- const errorMessage = transaction.error_message ||
251
- simulation.error_message ||
252
- "Transaction simulation reverted";
253
- // NOTE: `data` includes the full Tenderly response (request body, calldata,
254
- // asset changes). Callers that log `error.details` should redact — see
255
- // `errors.ts` doc for `SimulationRevertedError.details`.
256
- throw new SimulationRevertedError(errorMessage, data);
257
- }
258
- const rawLogs = [];
259
- const logs = transaction.transaction_info.logs ?? [];
260
- for (const log of logs) {
261
- if (log.raw) {
262
- rawLogs.push({
263
- address: log.raw.address,
264
- topics: log.raw.topics ?? [],
265
- data: log.raw.data ?? "0x",
266
- });
267
- }
268
- }
269
- return {
270
- logs: rawLogs,
271
- status: true,
272
- returnData: transaction.transaction_info.call_trace.output,
273
- gasUsed: BigInt(transaction.gas_used),
274
- assetChanges: transaction.transaction_info.asset_changes,
275
- };
276
- }