@morpho-org/evm-simulation 1.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.
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/lib/cjs/errors.d.ts +50 -0
- package/lib/cjs/errors.js +72 -0
- package/lib/cjs/index.d.ts +4 -0
- package/lib/cjs/index.js +17 -0
- package/lib/cjs/package.json +1 -0
- package/lib/cjs/screen-addresses/index.d.ts +1 -0
- package/lib/cjs/screen-addresses/index.js +5 -0
- package/lib/cjs/screen-addresses/sanctioned-addresses.d.ts +11 -0
- package/lib/cjs/screen-addresses/sanctioned-addresses.js +77 -0
- package/lib/cjs/screen-addresses/screen-addresses.d.ts +34 -0
- package/lib/cjs/screen-addresses/screen-addresses.js +152 -0
- package/lib/cjs/simulate/authorizations/index.d.ts +2 -0
- package/lib/cjs/simulate/authorizations/index.js +7 -0
- package/lib/cjs/simulate/authorizations/resolve.d.ts +9 -0
- package/lib/cjs/simulate/authorizations/resolve.js +29 -0
- package/lib/cjs/simulate/authorizations/validate.d.ts +6 -0
- package/lib/cjs/simulate/authorizations/validate.js +23 -0
- package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +15 -0
- package/lib/cjs/simulate/backends/eth-simulate-v1.js +76 -0
- package/lib/cjs/simulate/backends/index.d.ts +2 -0
- package/lib/cjs/simulate/backends/index.js +7 -0
- package/lib/cjs/simulate/backends/tenderly-rest.d.ts +20 -0
- package/lib/cjs/simulate/backends/tenderly-rest.js +269 -0
- package/lib/cjs/simulate/index.d.ts +1 -0
- package/lib/cjs/simulate/index.js +5 -0
- package/lib/cjs/simulate/parsing/index.d.ts +1 -0
- package/lib/cjs/simulate/parsing/index.js +5 -0
- package/lib/cjs/simulate/parsing/transfers.d.ts +27 -0
- package/lib/cjs/simulate/parsing/transfers.js +153 -0
- package/lib/cjs/simulate/pipeline/build-simulation-txs.d.ts +17 -0
- package/lib/cjs/simulate/pipeline/build-simulation-txs.js +50 -0
- package/lib/cjs/simulate/pipeline/bundler-retention.d.ts +19 -0
- package/lib/cjs/simulate/pipeline/bundler-retention.js +90 -0
- package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +25 -0
- package/lib/cjs/simulate/pipeline/execute-simulation.js +87 -0
- package/lib/cjs/simulate/pipeline/index.d.ts +4 -0
- package/lib/cjs/simulate/pipeline/index.js +11 -0
- package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +14 -0
- package/lib/cjs/simulate/pipeline/resolve-chain.js +19 -0
- package/lib/cjs/simulate/pipeline/validate-input.d.ts +10 -0
- package/lib/cjs/simulate/pipeline/validate-input.js +55 -0
- package/lib/cjs/simulate/simulate.d.ts +26 -0
- package/lib/cjs/simulate/simulate.js +46 -0
- package/lib/cjs/types.d.ts +131 -0
- package/lib/cjs/types.js +2 -0
- package/lib/esm/errors.d.ts +50 -0
- package/lib/esm/errors.js +62 -0
- package/lib/esm/index.d.ts +4 -0
- package/lib/esm/index.js +5 -0
- package/lib/esm/package.json +1 -0
- package/lib/esm/screen-addresses/index.d.ts +1 -0
- package/lib/esm/screen-addresses/index.js +1 -0
- package/lib/esm/screen-addresses/sanctioned-addresses.d.ts +11 -0
- package/lib/esm/screen-addresses/sanctioned-addresses.js +74 -0
- package/lib/esm/screen-addresses/screen-addresses.d.ts +34 -0
- package/lib/esm/screen-addresses/screen-addresses.js +149 -0
- package/lib/esm/simulate/authorizations/index.d.ts +2 -0
- package/lib/esm/simulate/authorizations/index.js +2 -0
- package/lib/esm/simulate/authorizations/resolve.d.ts +9 -0
- package/lib/esm/simulate/authorizations/resolve.js +26 -0
- package/lib/esm/simulate/authorizations/validate.d.ts +6 -0
- package/lib/esm/simulate/authorizations/validate.js +20 -0
- package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +15 -0
- package/lib/esm/simulate/backends/eth-simulate-v1.js +73 -0
- package/lib/esm/simulate/backends/index.d.ts +2 -0
- package/lib/esm/simulate/backends/index.js +2 -0
- package/lib/esm/simulate/backends/tenderly-rest.d.ts +20 -0
- package/lib/esm/simulate/backends/tenderly-rest.js +266 -0
- package/lib/esm/simulate/index.d.ts +1 -0
- package/lib/esm/simulate/index.js +1 -0
- package/lib/esm/simulate/parsing/index.d.ts +1 -0
- package/lib/esm/simulate/parsing/index.js +1 -0
- package/lib/esm/simulate/parsing/transfers.d.ts +27 -0
- package/lib/esm/simulate/parsing/transfers.js +149 -0
- package/lib/esm/simulate/pipeline/build-simulation-txs.d.ts +17 -0
- package/lib/esm/simulate/pipeline/build-simulation-txs.js +47 -0
- package/lib/esm/simulate/pipeline/bundler-retention.d.ts +19 -0
- package/lib/esm/simulate/pipeline/bundler-retention.js +87 -0
- package/lib/esm/simulate/pipeline/execute-simulation.d.ts +25 -0
- package/lib/esm/simulate/pipeline/execute-simulation.js +84 -0
- package/lib/esm/simulate/pipeline/index.d.ts +4 -0
- package/lib/esm/simulate/pipeline/index.js +4 -0
- package/lib/esm/simulate/pipeline/resolve-chain.d.ts +14 -0
- package/lib/esm/simulate/pipeline/resolve-chain.js +16 -0
- package/lib/esm/simulate/pipeline/validate-input.d.ts +10 -0
- package/lib/esm/simulate/pipeline/validate-input.js +52 -0
- package/lib/esm/simulate/simulate.d.ts +26 -0
- package/lib/esm/simulate/simulate.js +43 -0
- package/lib/esm/types.d.ts +131 -0
- package/lib/esm/types.js +1 -0
- package/package.json +49 -0
- package/src/index.ts +27 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ExternalServiceError, UnsupportedChainError } from "../../errors.js";
|
|
2
|
+
import { simulateTenderlyRest, simulateV1 } from "../backends/index.js";
|
|
3
|
+
import { resolveChain } from "./resolve-chain.js";
|
|
4
|
+
/** Total budget for a single `simulate()` call across both backends. */
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
6
|
+
/** Fraction of `timeoutMs` given to Tenderly before falling back to `eth_simulateV1`. */
|
|
7
|
+
const TENDERLY_BUDGET_RATIO = 0.6;
|
|
8
|
+
/**
|
|
9
|
+
* Minimum budget (ms) handed to the `eth_simulateV1` fallback when Tenderly fails.
|
|
10
|
+
*
|
|
11
|
+
* Without a floor, if Tenderly exhausts its slice and dies at the deadline, the
|
|
12
|
+
* fallback would get ~0 ms and abort immediately — turning a degraded-Tenderly
|
|
13
|
+
* scenario into a total outage even when the fallback backend is healthy. A
|
|
14
|
+
* modest floor keeps the fallback viable at the cost of the overall
|
|
15
|
+
* `timeoutMs` being treated as a soft ceiling.
|
|
16
|
+
*/
|
|
17
|
+
const FALLBACK_MIN_BUDGET_MS = 1500;
|
|
18
|
+
/**
|
|
19
|
+
* Stage 4 of the simulate() pipeline.
|
|
20
|
+
*
|
|
21
|
+
* Dispatches the transaction bundle to the available backend with a shared timeout
|
|
22
|
+
* budget:
|
|
23
|
+
* - If Tenderly is configured for the chain, it gets `TENDERLY_BUDGET_RATIO` (60%) of
|
|
24
|
+
* the timeout. On `ExternalServiceError`, falls back to `eth_simulateV1` with
|
|
25
|
+
* `max(remaining, FALLBACK_MIN_BUDGET_MS)` so the fallback still has a viable
|
|
26
|
+
* window when Tenderly eats its full slice. `SimulationRevertedError` (contract
|
|
27
|
+
* revert) propagates immediately without retry — a revert is a property of the
|
|
28
|
+
* bundle, not the backend.
|
|
29
|
+
* - If only `eth_simulateV1` is configured, it gets the full timeout.
|
|
30
|
+
*
|
|
31
|
+
* `shareable` is Tenderly-only: when true, the backend persists the simulation and
|
|
32
|
+
* returns a shareable URL. `eth_simulateV1` ignores it (no persistence concept).
|
|
33
|
+
*/
|
|
34
|
+
export async function executeSimulation(params) {
|
|
35
|
+
const { config, chainId, transactions, blockNumber, shareable } = params;
|
|
36
|
+
const chain = resolveChain(config, chainId);
|
|
37
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
38
|
+
const deadline = Date.now() + timeoutMs;
|
|
39
|
+
if (chain.tenderlySupported && config.tenderlyRest) {
|
|
40
|
+
const tenderlyTimeout = Math.floor(timeoutMs * TENDERLY_BUDGET_RATIO);
|
|
41
|
+
try {
|
|
42
|
+
return await simulateTenderlyRest({
|
|
43
|
+
config: config.tenderlyRest,
|
|
44
|
+
chainId,
|
|
45
|
+
transactions,
|
|
46
|
+
blockNumber,
|
|
47
|
+
shareable,
|
|
48
|
+
signal: AbortSignal.timeout(tenderlyTimeout),
|
|
49
|
+
logger: config.logger,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (!(error instanceof ExternalServiceError))
|
|
54
|
+
throw error;
|
|
55
|
+
config.logger?.warn("Tenderly simulation failed, attempting fallback", {
|
|
56
|
+
chainId,
|
|
57
|
+
error: error.message,
|
|
58
|
+
cause: error.cause,
|
|
59
|
+
});
|
|
60
|
+
if (!chain.simulateV1Url)
|
|
61
|
+
throw error;
|
|
62
|
+
// Give the fallback a reasonable minimum even if Tenderly consumed the
|
|
63
|
+
// whole budget — otherwise slow-Tenderly == total outage.
|
|
64
|
+
const fallbackBudget = Math.max(deadline - Date.now(), FALLBACK_MIN_BUDGET_MS);
|
|
65
|
+
return await simulateV1({
|
|
66
|
+
rpcUrl: chain.simulateV1Url,
|
|
67
|
+
chainId,
|
|
68
|
+
transactions,
|
|
69
|
+
blockNumber,
|
|
70
|
+
signal: AbortSignal.timeout(fallbackBudget),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!chain.simulateV1Url) {
|
|
75
|
+
throw new UnsupportedChainError(chainId);
|
|
76
|
+
}
|
|
77
|
+
return await simulateV1({
|
|
78
|
+
rpcUrl: chain.simulateV1Url,
|
|
79
|
+
chainId,
|
|
80
|
+
transactions,
|
|
81
|
+
blockNumber,
|
|
82
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SimulationConfig } from "../../types.js";
|
|
2
|
+
interface ChainCapability {
|
|
3
|
+
tenderlySupported: boolean;
|
|
4
|
+
simulateV1Url?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Stage 2 of the simulate() pipeline.
|
|
8
|
+
*
|
|
9
|
+
* Determines which backend(s) are available for a chainId. Throws
|
|
10
|
+
* `UnsupportedChainError` when neither Tenderly nor `eth_simulateV1` is reachable
|
|
11
|
+
* for the requested chain.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveChain(config: SimulationConfig, chainId: number): ChainCapability;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UnsupportedChainError } from "../../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Stage 2 of the simulate() pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Determines which backend(s) are available for a chainId. Throws
|
|
6
|
+
* `UnsupportedChainError` when neither Tenderly nor `eth_simulateV1` is reachable
|
|
7
|
+
* for the requested chain.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveChain(config, chainId) {
|
|
10
|
+
const tenderlySupported = config.tenderlyRest?.supportedChainIds.has(chainId) ?? false;
|
|
11
|
+
const simulateV1Url = config.chains.get(chainId)?.simulateV1Url;
|
|
12
|
+
if (!tenderlySupported && !simulateV1Url) {
|
|
13
|
+
throw new UnsupportedChainError(chainId);
|
|
14
|
+
}
|
|
15
|
+
return { tenderlySupported, simulateV1Url };
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SimulateParams } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Stage 1 of the simulate() pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Throws `SimulationValidationError` with a `fieldErrors[]` list on any invalid input:
|
|
6
|
+
* empty transactions, malformed / zero-addr fields, missing `data`, negative `value`,
|
|
7
|
+
* bad `chainId`, or mixed senders (all txs in a bundle must share the same `from`).
|
|
8
|
+
* Also runs `validateAuthorizations` on the optional authorizations array.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateInput(params: SimulateParams): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isAddress, zeroAddress } from "viem";
|
|
2
|
+
import { SimulationValidationError } from "../../errors.js";
|
|
3
|
+
import { validateAuthorizations } from "../authorizations/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Stage 1 of the simulate() pipeline.
|
|
6
|
+
*
|
|
7
|
+
* Throws `SimulationValidationError` with a `fieldErrors[]` list on any invalid input:
|
|
8
|
+
* empty transactions, malformed / zero-addr fields, missing `data`, negative `value`,
|
|
9
|
+
* bad `chainId`, or mixed senders (all txs in a bundle must share the same `from`).
|
|
10
|
+
* Also runs `validateAuthorizations` on the optional authorizations array.
|
|
11
|
+
*/
|
|
12
|
+
export function validateInput(params) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
if (!Number.isInteger(params.chainId) || params.chainId <= 0) {
|
|
15
|
+
errors.push(`chainId: must be a positive integer (got ${params.chainId})`);
|
|
16
|
+
}
|
|
17
|
+
const transactions = params.transactions ?? [];
|
|
18
|
+
if (transactions.length === 0) {
|
|
19
|
+
errors.push("transactions: must contain at least 1 transaction");
|
|
20
|
+
}
|
|
21
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
22
|
+
const tx = transactions[i];
|
|
23
|
+
if (!tx.from || !isAddress(tx.from) || tx.from === zeroAddress) {
|
|
24
|
+
errors.push(`transactions[${i}].from: must be a valid non-zero address`);
|
|
25
|
+
}
|
|
26
|
+
if (!tx.to || !isAddress(tx.to) || tx.to === zeroAddress) {
|
|
27
|
+
errors.push(`transactions[${i}].to: must be a valid non-zero address`);
|
|
28
|
+
}
|
|
29
|
+
if (!tx.data) {
|
|
30
|
+
errors.push(`transactions[${i}].data: must be provided`);
|
|
31
|
+
}
|
|
32
|
+
if (tx.value !== undefined && tx.value < 0n) {
|
|
33
|
+
errors.push(`transactions[${i}].value: must be non-negative`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Same-sender check uses RAW `.from` strings (lowercased) so the invariant
|
|
37
|
+
// fires even when some tx `from`s are malformed — individual txs already
|
|
38
|
+
// flagged above, but we still want one loud signal that the bundle's
|
|
39
|
+
// senders don't match.
|
|
40
|
+
if (transactions.length > 1) {
|
|
41
|
+
const distinct = new Set(transactions.map((tx) => (tx.from ?? "").toLowerCase()));
|
|
42
|
+
if (distinct.size > 1) {
|
|
43
|
+
errors.push("transactions: all transactions must share the same from address");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (params.authorizations) {
|
|
47
|
+
errors.push(...validateAuthorizations(params.authorizations));
|
|
48
|
+
}
|
|
49
|
+
if (errors.length > 0) {
|
|
50
|
+
throw new SimulationValidationError("Invalid simulation input", errors);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SimulateParams, SimulationConfig, SimulationResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Simulate a bundle of EVM transactions.
|
|
4
|
+
*
|
|
5
|
+
* Validates input → resolves authorizations into prepended approve txs → runs the bundle
|
|
6
|
+
* through Tenderly REST (primary) or `eth_simulateV1` (fallback) with a shared timeout
|
|
7
|
+
* budget → parses ERC20/WETH transfers from logs → asserts no funds are retained by
|
|
8
|
+
* `bundler3` → returns the full result set. The caller reads whichever fields they need:
|
|
9
|
+
*
|
|
10
|
+
* - `transfers` + `tenderlyUrl` → user-facing preview.
|
|
11
|
+
* - `simulationTxs` + `transfers` → server-side verification before broadcast, then pass
|
|
12
|
+
* both to `screenAddresses` for compliance.
|
|
13
|
+
*
|
|
14
|
+
* Throws `SimulationValidationError` for bad input, `UnsupportedChainError` when the chain
|
|
15
|
+
* is not configured, `SimulationRevertedError` if the bundle reverts,
|
|
16
|
+
* `BlacklistViolationError` if bundler retention is detected, or `ExternalServiceError` if
|
|
17
|
+
* both backends are unavailable within the timeout budget.
|
|
18
|
+
*/
|
|
19
|
+
export declare function simulate(config: SimulationConfig, params: SimulateParams, options?: {
|
|
20
|
+
/**
|
|
21
|
+
* When true, persist the simulation in Tenderly and return a shareable
|
|
22
|
+
* `tenderlyUrl` in the result. Only honored when the Tenderly backend runs
|
|
23
|
+
* (not the `eth_simulateV1` fallback). Defaults to `false`.
|
|
24
|
+
*/
|
|
25
|
+
shareable?: boolean;
|
|
26
|
+
}): Promise<SimulationResult>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { parseTransfers } from "./parsing/index.js";
|
|
2
|
+
import { assertNoBundlerRetention, buildSimulationTxs, executeSimulation, validateInput, } from "./pipeline/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Simulate a bundle of EVM transactions.
|
|
5
|
+
*
|
|
6
|
+
* Validates input → resolves authorizations into prepended approve txs → runs the bundle
|
|
7
|
+
* through Tenderly REST (primary) or `eth_simulateV1` (fallback) with a shared timeout
|
|
8
|
+
* budget → parses ERC20/WETH transfers from logs → asserts no funds are retained by
|
|
9
|
+
* `bundler3` → returns the full result set. The caller reads whichever fields they need:
|
|
10
|
+
*
|
|
11
|
+
* - `transfers` + `tenderlyUrl` → user-facing preview.
|
|
12
|
+
* - `simulationTxs` + `transfers` → server-side verification before broadcast, then pass
|
|
13
|
+
* both to `screenAddresses` for compliance.
|
|
14
|
+
*
|
|
15
|
+
* Throws `SimulationValidationError` for bad input, `UnsupportedChainError` when the chain
|
|
16
|
+
* is not configured, `SimulationRevertedError` if the bundle reverts,
|
|
17
|
+
* `BlacklistViolationError` if bundler retention is detected, or `ExternalServiceError` if
|
|
18
|
+
* both backends are unavailable within the timeout budget.
|
|
19
|
+
*/
|
|
20
|
+
export async function simulate(config, params, options = {}) {
|
|
21
|
+
const { shareable = false } = options;
|
|
22
|
+
validateInput(params);
|
|
23
|
+
const simulationTxs = buildSimulationTxs(params);
|
|
24
|
+
const result = await executeSimulation({
|
|
25
|
+
config,
|
|
26
|
+
chainId: params.chainId,
|
|
27
|
+
transactions: simulationTxs,
|
|
28
|
+
blockNumber: params.blockNumber,
|
|
29
|
+
shareable,
|
|
30
|
+
});
|
|
31
|
+
const transfers = parseTransfers(result.logs, config.logger);
|
|
32
|
+
assertNoBundlerRetention({
|
|
33
|
+
chainId: params.chainId,
|
|
34
|
+
transfers,
|
|
35
|
+
logger: config.logger,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
simulationTxs,
|
|
39
|
+
transfers,
|
|
40
|
+
tenderlyUrl: result.tenderlyUrl,
|
|
41
|
+
assetChanges: result.rawAssetChanges,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Address, BlockTag, Hex } from "viem";
|
|
2
|
+
/**
|
|
3
|
+
* Credentials and routing for the Tenderly REST simulation backend.
|
|
4
|
+
* Omit from `SimulationConfig.tenderlyRest` to disable the Tenderly path entirely.
|
|
5
|
+
*/
|
|
6
|
+
export interface TenderlyRestConfig {
|
|
7
|
+
/** e.g. "https://api.tenderly.co" */
|
|
8
|
+
apiBaseUrl: string;
|
|
9
|
+
accessToken: string;
|
|
10
|
+
accountSlug: string;
|
|
11
|
+
projectSlug: string;
|
|
12
|
+
/** Chain IDs that Tenderly supports for simulation */
|
|
13
|
+
supportedChainIds: Set<number>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Per-chain capabilities. Stored as a `Map` value in
|
|
17
|
+
* `SimulationConfig.chains`, keyed by chain ID.
|
|
18
|
+
*/
|
|
19
|
+
export interface ChainSimulationConfig {
|
|
20
|
+
/** JSON-RPC URL supporting `eth_simulateV1`. undefined = not available for this chain. */
|
|
21
|
+
simulateV1Url?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Top-level configuration for `simulate`.
|
|
25
|
+
*
|
|
26
|
+
* At least one backend must be reachable for a given chainId — either Tenderly
|
|
27
|
+
* (via `tenderlyRest` with that chainId in `TenderlyRestConfig.supportedChainIds`)
|
|
28
|
+
* or `ChainSimulationConfig.simulateV1Url` in the `chains` map. Otherwise the
|
|
29
|
+
* orchestrator throws `UnsupportedChainError`.
|
|
30
|
+
*/
|
|
31
|
+
export interface SimulationConfig {
|
|
32
|
+
/** Tenderly REST API config. undefined = Tenderly not available. */
|
|
33
|
+
tenderlyRest?: TenderlyRestConfig;
|
|
34
|
+
/** Per-chain simulation capabilities. */
|
|
35
|
+
chains: Map<number, ChainSimulationConfig>;
|
|
36
|
+
logger?: SimulationLogger;
|
|
37
|
+
/**
|
|
38
|
+
* Overall timeout budget in ms (default 5000). Tenderly gets ~60% of budget.
|
|
39
|
+
* On timeout/failure, fallback gets remaining time (deadline - now).
|
|
40
|
+
*/
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A single EVM call to simulate. `from` must be identical across all transactions
|
|
45
|
+
* in a bundle — the orchestrator rejects mixed senders with `SimulationValidationError`.
|
|
46
|
+
*/
|
|
47
|
+
export interface SimulationTransaction {
|
|
48
|
+
from: Address;
|
|
49
|
+
to: Address;
|
|
50
|
+
data: Hex;
|
|
51
|
+
value?: bigint;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* How the caller expresses a token authorization that must be in place before the
|
|
55
|
+
* main transactions run. The package decides HOW to simulate each one:
|
|
56
|
+
* - "approval" → prepend tx as-is
|
|
57
|
+
* - "signature" → today: encode approve(spender, amount); future: ecrecover override?
|
|
58
|
+
*/
|
|
59
|
+
export type SimulationAuthorization = {
|
|
60
|
+
type: "approval";
|
|
61
|
+
transaction: SimulationTransaction;
|
|
62
|
+
} | {
|
|
63
|
+
type: "signature";
|
|
64
|
+
token: Address;
|
|
65
|
+
spender: Address;
|
|
66
|
+
/** Defaults to maxUint256. Caller can set explicit amount for tokens that don't support max approval. */
|
|
67
|
+
amount?: bigint;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* A parsed ERC20 / WETH9 transfer extracted from simulation logs. Returned in
|
|
71
|
+
* `SimulationResult.transfers`.
|
|
72
|
+
*/
|
|
73
|
+
export interface Transfer {
|
|
74
|
+
token: Address;
|
|
75
|
+
from: Address;
|
|
76
|
+
to: Address;
|
|
77
|
+
amount: bigint;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Happy-path return of `simulate`. All failures throw typed errors. The same shape is
|
|
81
|
+
* returned regardless of `options.shareable` — fields unused by your use case can be
|
|
82
|
+
* ignored:
|
|
83
|
+
*
|
|
84
|
+
* - Preview (`shareable: true`): read `transfers` + `tenderlyUrl`.
|
|
85
|
+
* - Verify (`shareable: false`, default): read `simulationTxs` + `transfers`, pass both
|
|
86
|
+
* to `screenAddresses`.
|
|
87
|
+
*
|
|
88
|
+
* `tenderlyUrl` is set only when `shareable: true` AND the Tenderly backend ran
|
|
89
|
+
* successfully (not the `eth_simulateV1` fallback). `assetChanges` is Tenderly-only
|
|
90
|
+
* raw data.
|
|
91
|
+
*/
|
|
92
|
+
export interface SimulationResult {
|
|
93
|
+
/** The full resolved transaction list (including prepended authorization txs). */
|
|
94
|
+
simulationTxs: SimulationTransaction[];
|
|
95
|
+
/** Parsed ERC-20 / WETH9 transfers from the simulation. */
|
|
96
|
+
transfers: Transfer[];
|
|
97
|
+
/** Shareable Tenderly URL. Present only when `shareable: true` and Tenderly ran (not fallback). */
|
|
98
|
+
tenderlyUrl?: string;
|
|
99
|
+
/** Raw Tenderly `asset_changes` payload. Opaque `unknown` — do not destructure without validation. */
|
|
100
|
+
assetChanges?: unknown;
|
|
101
|
+
}
|
|
102
|
+
/** Minimal structured logger the package calls for warnings and info. */
|
|
103
|
+
export interface SimulationLogger {
|
|
104
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
105
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
106
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Input to `simulate`. Pin a `blockNumber` for deterministic / historical
|
|
110
|
+
* simulation; omit to simulate against `latest`.
|
|
111
|
+
*/
|
|
112
|
+
export interface SimulateParams {
|
|
113
|
+
chainId: number;
|
|
114
|
+
transactions: SimulationTransaction[];
|
|
115
|
+
authorizations?: SimulationAuthorization[];
|
|
116
|
+
blockNumber?: bigint | BlockTag;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Internal raw result from a simulation adapter before normalization.
|
|
120
|
+
* Both Tenderly and simulateV1 produce this.
|
|
121
|
+
*/
|
|
122
|
+
export interface RawSimulationResult {
|
|
123
|
+
logs: RawLog[];
|
|
124
|
+
tenderlyUrl?: string;
|
|
125
|
+
rawAssetChanges?: unknown;
|
|
126
|
+
}
|
|
127
|
+
export interface RawLog {
|
|
128
|
+
address: Address;
|
|
129
|
+
topics: Hex[];
|
|
130
|
+
data: Hex;
|
|
131
|
+
}
|
package/lib/esm/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@morpho-org/evm-simulation",
|
|
3
|
+
"description": "EVM simulation engine for Morpho transactions.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "Morpho Association <contact@morpho.org>",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Rubilmax <rmilon@gmail.com>"
|
|
8
|
+
],
|
|
9
|
+
"repository": "github:morpho-org/sdks",
|
|
10
|
+
"homepage": "https://github.com/morpho-org/sdks",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/morpho-org/sdks/issues",
|
|
13
|
+
"email": "contact@morpho.org"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"main": "src/index.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"lib"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"zod": "^4.1.12",
|
|
24
|
+
"@morpho-org/blue-sdk": "^5.23.1",
|
|
25
|
+
"@morpho-org/morpho-ts": "^2.5.1"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"viem": "^2.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"viem": "^2.41.2",
|
|
33
|
+
"vitest": "^4.1.4"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"prepublish": "$npm_execpath build",
|
|
37
|
+
"build": "tsc --noEmit && $npm_execpath build:cjs && $npm_execpath build:esm",
|
|
38
|
+
"build:cjs": "tsc --build tsconfig.build.cjs.json && echo '{\"type\":\"commonjs\"}' > lib/cjs/package.json",
|
|
39
|
+
"build:esm": "tsc --build tsconfig.build.esm.json && echo '{\"type\":\"module\"}' > lib/esm/package.json",
|
|
40
|
+
"test": "vitest --root ../.. --project evm-simulation"
|
|
41
|
+
},
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./lib/esm/index.d.ts",
|
|
45
|
+
"import": "./lib/esm/index.js",
|
|
46
|
+
"require": "./lib/cjs/index.js"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Public fns (via feature-folder barrels)
|
|
2
|
+
export { screenAddresses } from "./screen-addresses/index.js";
|
|
3
|
+
export { simulate } from "./simulate/index.js";
|
|
4
|
+
|
|
5
|
+
// Types
|
|
6
|
+
export type {
|
|
7
|
+
ChainSimulationConfig,
|
|
8
|
+
SimulateParams,
|
|
9
|
+
SimulationAuthorization,
|
|
10
|
+
SimulationConfig,
|
|
11
|
+
SimulationLogger,
|
|
12
|
+
SimulationResult,
|
|
13
|
+
SimulationTransaction,
|
|
14
|
+
TenderlyRestConfig,
|
|
15
|
+
Transfer,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
|
|
18
|
+
// Errors (for instanceof checks by consumers)
|
|
19
|
+
export {
|
|
20
|
+
SimulationPackageError,
|
|
21
|
+
SimulationRevertedError,
|
|
22
|
+
BlacklistViolationError,
|
|
23
|
+
AddressScreeningError,
|
|
24
|
+
ExternalServiceError,
|
|
25
|
+
SimulationValidationError,
|
|
26
|
+
UnsupportedChainError,
|
|
27
|
+
} from "./errors.js";
|