@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.
- package/README.md +22 -30
- package/lib/cjs/errors.d.ts +0 -6
- package/lib/cjs/errors.js +1 -11
- package/lib/cjs/index.d.ts +2 -3
- package/lib/cjs/index.js +3 -6
- package/lib/cjs/simulate/asset-changes.d.ts +19 -0
- package/lib/cjs/simulate/asset-changes.js +47 -0
- package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +9 -0
- package/lib/cjs/simulate/backends/eth-simulate-v1.js +39 -1
- package/lib/cjs/simulate/backends/index.d.ts +1 -1
- package/lib/cjs/simulate/backends/index.js +3 -3
- package/lib/cjs/simulate/backends/tenderly-rpc.d.ts +23 -0
- package/lib/cjs/simulate/backends/tenderly-rpc.js +230 -0
- package/lib/cjs/simulate/parsing/transfers.js +3 -1
- package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +6 -10
- package/lib/cjs/simulate/pipeline/execute-simulation.js +11 -18
- package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +6 -5
- package/lib/cjs/simulate/pipeline/resolve-chain.js +10 -7
- package/lib/cjs/simulate/simulate.d.ts +20 -23
- package/lib/cjs/simulate/simulate.js +21 -20
- package/lib/cjs/types.d.ts +58 -39
- package/lib/esm/errors.d.ts +0 -6
- package/lib/esm/errors.js +0 -9
- package/lib/esm/index.d.ts +2 -3
- package/lib/esm/index.js +1 -2
- package/lib/esm/simulate/asset-changes.d.ts +19 -0
- package/lib/esm/simulate/asset-changes.js +44 -0
- package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +9 -0
- package/lib/esm/simulate/backends/eth-simulate-v1.js +40 -2
- package/lib/esm/simulate/backends/index.d.ts +1 -1
- package/lib/esm/simulate/backends/index.js +1 -1
- package/lib/esm/simulate/backends/tenderly-rpc.d.ts +23 -0
- package/lib/esm/simulate/backends/tenderly-rpc.js +227 -0
- package/lib/esm/simulate/parsing/transfers.js +3 -1
- package/lib/esm/simulate/pipeline/execute-simulation.d.ts +6 -10
- package/lib/esm/simulate/pipeline/execute-simulation.js +12 -19
- package/lib/esm/simulate/pipeline/resolve-chain.d.ts +6 -5
- package/lib/esm/simulate/pipeline/resolve-chain.js +10 -7
- package/lib/esm/simulate/simulate.d.ts +20 -23
- package/lib/esm/simulate/simulate.js +21 -20
- package/lib/esm/types.d.ts +58 -39
- package/package.json +7 -5
- package/src/index.ts +3 -3
- package/lib/cjs/screen-addresses/index.d.ts +0 -1
- package/lib/cjs/screen-addresses/index.js +0 -5
- package/lib/cjs/screen-addresses/sanctioned-addresses.d.ts +0 -11
- package/lib/cjs/screen-addresses/sanctioned-addresses.js +0 -77
- package/lib/cjs/screen-addresses/screen-addresses.d.ts +0 -50
- package/lib/cjs/screen-addresses/screen-addresses.js +0 -171
- package/lib/cjs/simulate/backends/tenderly-rest.d.ts +0 -20
- package/lib/cjs/simulate/backends/tenderly-rest.js +0 -279
- package/lib/esm/screen-addresses/index.d.ts +0 -1
- package/lib/esm/screen-addresses/index.js +0 -1
- package/lib/esm/screen-addresses/sanctioned-addresses.d.ts +0 -11
- package/lib/esm/screen-addresses/sanctioned-addresses.js +0 -74
- package/lib/esm/screen-addresses/screen-addresses.d.ts +0 -50
- package/lib/esm/screen-addresses/screen-addresses.js +0 -168
- package/lib/esm/simulate/backends/tenderly-rest.d.ts +0 -20
- package/lib/esm/simulate/backends/tenderly-rest.js +0 -276
package/lib/esm/types.d.ts
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
import type { Address, BlockTag, Hex } from "viem";
|
|
2
2
|
/**
|
|
3
|
-
* Credentials
|
|
4
|
-
*
|
|
3
|
+
* Credentials for the Tenderly Node Web3 Gateway. The `rpcUrl` is the
|
|
4
|
+
* chain-specific gateway URL with the access key embedded in the path,
|
|
5
|
+
* e.g. `https://mainnet.gateway.tenderly.co/{ACCESS_KEY}`.
|
|
5
6
|
*/
|
|
6
|
-
export interface
|
|
7
|
-
|
|
8
|
-
apiBaseUrl: string;
|
|
9
|
-
accessToken: string;
|
|
10
|
-
accountSlug: string;
|
|
11
|
-
projectSlug: string;
|
|
12
|
-
/** Chain IDs that Tenderly supports for simulation */
|
|
13
|
-
supportedChainIds: Set<number>;
|
|
7
|
+
export interface TenderlyRpcConfig {
|
|
8
|
+
rpcUrl: string;
|
|
14
9
|
}
|
|
15
10
|
/**
|
|
16
|
-
* Per-chain capabilities.
|
|
17
|
-
*
|
|
11
|
+
* Per-chain backend capabilities. Exactly one configuration shape per chain;
|
|
12
|
+
* the discriminated union enforces that **at least one** of `tenderlyRpc`
|
|
13
|
+
* (primary) or `simulateV1Url` (fallback) is supplied.
|
|
18
14
|
*/
|
|
19
|
-
export
|
|
20
|
-
/**
|
|
15
|
+
export type ChainSimulationConfig = {
|
|
16
|
+
/** Tenderly RPC config — `tenderly_simulateTransaction` / `tenderly_simulateBundle`. */
|
|
17
|
+
tenderlyRpc: TenderlyRpcConfig;
|
|
18
|
+
/** JSON-RPC URL supporting `eth_simulateV1`. Optional when Tenderly is set. */
|
|
21
19
|
simulateV1Url?: string;
|
|
22
|
-
}
|
|
20
|
+
} | {
|
|
21
|
+
tenderlyRpc?: TenderlyRpcConfig;
|
|
22
|
+
simulateV1Url: string;
|
|
23
|
+
};
|
|
23
24
|
/**
|
|
24
25
|
* Top-level configuration for `simulate`.
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* orchestrator throws `UnsupportedChainError`.
|
|
27
|
+
* Every chain entry must declare at least one backend (Tenderly RPC primary,
|
|
28
|
+
* `eth_simulateV1` fallback, or both). Calling `simulate` for a `chainId`
|
|
29
|
+
* missing from `chains` throws `UnsupportedChainError`.
|
|
30
30
|
*/
|
|
31
31
|
export interface SimulationConfig {
|
|
32
|
-
/** Tenderly REST API config. undefined = Tenderly not available. */
|
|
33
|
-
tenderlyRest?: TenderlyRestConfig;
|
|
34
32
|
/** Per-chain simulation capabilities. */
|
|
35
33
|
chains: Map<number, ChainSimulationConfig>;
|
|
36
34
|
logger?: SimulationLogger;
|
|
@@ -66,6 +64,33 @@ export type SimulationAuthorization = {
|
|
|
66
64
|
/** Defaults to maxUint256. Caller can set explicit amount for tokens that don't support max approval. */
|
|
67
65
|
amount?: bigint;
|
|
68
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Net balance change for a single asset (one token) within an account's entry.
|
|
69
|
+
* Native ETH uses viem's `ethAddress` sentinel as `token`. `symbol`/`decimals`
|
|
70
|
+
* are best-effort and may be absent, notably on the `eth_simulateV1` fallback.
|
|
71
|
+
*/
|
|
72
|
+
export interface AssetChange {
|
|
73
|
+
readonly token: Address;
|
|
74
|
+
readonly symbol?: string;
|
|
75
|
+
readonly decimals?: number;
|
|
76
|
+
/** Signed net change of the account's balance, in raw token units. */
|
|
77
|
+
readonly diff: bigint;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Net per-token balance changes for one account over the whole bundle. Returned
|
|
81
|
+
* for every account that nets a non-zero change, the sender and counterparties
|
|
82
|
+
* alike (the zero address is kept for mints/burns). Accounts and their `changes`
|
|
83
|
+
* are sorted by address for deterministic, cross-backend output.
|
|
84
|
+
*
|
|
85
|
+
* Native-ETH coverage differs by backend: Tenderly reports the full net ETH
|
|
86
|
+
* delta (including ETH moved via internal calls), while the `eth_simulateV1`
|
|
87
|
+
* fallback derives native ETH only from the top-level transaction `value`s —
|
|
88
|
+
* ETH moved internally (e.g. a `WETH.withdraw` refund) is not captured there.
|
|
89
|
+
*/
|
|
90
|
+
export interface AccountAssetChanges {
|
|
91
|
+
readonly account: Address;
|
|
92
|
+
readonly changes: readonly AssetChange[];
|
|
93
|
+
}
|
|
69
94
|
/**
|
|
70
95
|
* A parsed ERC20 / WETH9 transfer extracted from simulation logs. Returned in
|
|
71
96
|
* `SimulationResult.transfers`.
|
|
@@ -89,25 +114,25 @@ export interface Transfer {
|
|
|
89
114
|
* - `simulationTxs` is the full resolved transaction list (including
|
|
90
115
|
* prepended authorization txs).
|
|
91
116
|
* - `calls[i]` corresponds 1:1 with `simulationTxs[i]` — read raw logs,
|
|
92
|
-
* status, returnData/gasUsed
|
|
117
|
+
* status, returnData/gasUsed.
|
|
118
|
+
* - `assetChanges` is the net per-asset balance change over the whole bundle,
|
|
119
|
+
* grouped by account (sender and counterparties), normalized to the same
|
|
120
|
+
* shape across backends — see `AccountAssetChanges`.
|
|
93
121
|
* - `transfers[k].txIdx` indexes into `simulationTxs` to attribute each
|
|
94
122
|
* transfer to its emitting transaction.
|
|
95
|
-
* - `tenderlyUrl` is set only when `shareable: true` AND the Tenderly backend
|
|
96
|
-
* ran successfully (not the `eth_simulateV1` fallback).
|
|
97
123
|
*/
|
|
98
124
|
export interface SimulationResult {
|
|
99
125
|
/** The full resolved transaction list (including prepended authorization txs). */
|
|
100
126
|
readonly simulationTxs: readonly SimulationTransaction[];
|
|
101
127
|
/**
|
|
102
128
|
* Per-transaction normalized output. `calls[i]` corresponds 1:1 with
|
|
103
|
-
* `simulationTxs[i]`. Use this to read raw logs, status, return data, gas
|
|
104
|
-
* used, and (Tenderly only) asset changes per transaction.
|
|
129
|
+
* `simulationTxs[i]`. Use this to read raw logs, status, return data, gas used.
|
|
105
130
|
*/
|
|
106
131
|
readonly calls: readonly SimulationCall[];
|
|
107
132
|
/** Parsed ERC-20 / WETH9 transfers from the simulation. */
|
|
108
133
|
readonly transfers: readonly Transfer[];
|
|
109
|
-
/**
|
|
110
|
-
readonly
|
|
134
|
+
/** Net per-asset balance changes, grouped by account, over the whole bundle. */
|
|
135
|
+
readonly assetChanges: readonly AccountAssetChanges[];
|
|
111
136
|
}
|
|
112
137
|
/** Minimal structured logger the package calls for warnings and info. */
|
|
113
138
|
export interface SimulationLogger {
|
|
@@ -128,15 +153,15 @@ export interface SimulateParams {
|
|
|
128
153
|
/**
|
|
129
154
|
* Internal raw result from a simulation adapter before normalization.
|
|
130
155
|
* `calls[i]` corresponds 1:1 with the i-th transaction passed to the
|
|
131
|
-
* backend.
|
|
156
|
+
* backend; `assetChanges` is the bundle-level aggregate grouped by account.
|
|
132
157
|
*/
|
|
133
158
|
export interface RawSimulationResult {
|
|
134
159
|
calls: RawCall[];
|
|
135
|
-
|
|
160
|
+
assetChanges: AccountAssetChanges[];
|
|
136
161
|
}
|
|
137
162
|
/**
|
|
138
163
|
* Normalized EVM log emitted by a single simulated call. The shape is the
|
|
139
|
-
* common subset both backends (`eth_simulateV1` via viem and Tenderly
|
|
164
|
+
* common subset both backends (`eth_simulateV1` via viem and Tenderly RPC)
|
|
140
165
|
* produce after schema validation. Returned indirectly via
|
|
141
166
|
* `SimulationCall.logs` and consumed by the SDK's transfer parser.
|
|
142
167
|
*/
|
|
@@ -156,15 +181,14 @@ export interface RawCall {
|
|
|
156
181
|
status: boolean;
|
|
157
182
|
returnData: Hex;
|
|
158
183
|
gasUsed: bigint;
|
|
159
|
-
/** Tenderly-only `asset_changes` payload. Absent on `eth_simulateV1`. */
|
|
160
|
-
assetChanges?: unknown;
|
|
161
184
|
}
|
|
162
185
|
/**
|
|
163
186
|
* Per-transaction normalized output from the simulation backend.
|
|
164
187
|
*
|
|
165
188
|
* `SimulationResult.calls[i]` corresponds 1:1 with
|
|
166
189
|
* `SimulationResult.simulationTxs[i]`. Use this to read raw logs, status,
|
|
167
|
-
* return data, gas used
|
|
190
|
+
* return data, and gas used. Net asset changes are reported at the bundle
|
|
191
|
+
* level — see `SimulationResult.assetChanges`.
|
|
168
192
|
*/
|
|
169
193
|
export interface SimulationCall {
|
|
170
194
|
readonly logs: readonly RawLog[];
|
|
@@ -179,9 +203,4 @@ export interface SimulationCall {
|
|
|
179
203
|
readonly returnData: Hex;
|
|
180
204
|
/** Gas used by this call (root frame). */
|
|
181
205
|
readonly gasUsed: bigint;
|
|
182
|
-
/**
|
|
183
|
-
* Tenderly-only `asset_changes` payload for this tx. Opaque `unknown` —
|
|
184
|
-
* do not destructure without validation. Absent on `eth_simulateV1`.
|
|
185
|
-
*/
|
|
186
|
-
readonly assetChanges?: unknown;
|
|
187
206
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morpho-org/evm-simulation",
|
|
3
3
|
"description": "EVM simulation engine for Morpho transactions.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"author": "Morpho Association <contact@morpho.org>",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Rubilmax <rmilon@gmail.com>"
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
|
-
"@morpho-org/blue-sdk": "^6.0
|
|
29
|
-
"@morpho-org/morpho-ts": "^2.
|
|
28
|
+
"@morpho-org/blue-sdk": "^6.1.0",
|
|
29
|
+
"@morpho-org/morpho-ts": "^2.6.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"viem": "^2.0.0"
|
|
@@ -34,14 +34,16 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"typescript": "^6.0.3",
|
|
36
36
|
"viem": "^2.50.4",
|
|
37
|
-
"vitest": "^4.1.7"
|
|
37
|
+
"vitest": "^4.1.7",
|
|
38
|
+
"@morpho-org/test": "^2.8.1"
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|
|
40
41
|
"prepublish": "$npm_execpath build",
|
|
41
42
|
"build": "tsc --noEmit && $npm_execpath build:cjs && $npm_execpath build:esm",
|
|
42
43
|
"build:cjs": "tsc --build tsconfig.build.cjs.json && echo '{\"type\":\"commonjs\"}' > lib/cjs/package.json",
|
|
43
44
|
"build:esm": "tsc --build tsconfig.build.esm.json && echo '{\"type\":\"module\"}' > lib/esm/package.json",
|
|
44
|
-
"test": "vitest --root ../.. --project evm-simulation"
|
|
45
|
+
"test": "vitest --root ../.. --project evm-simulation",
|
|
46
|
+
"test:fork": "dotenv -- vitest --root ../.. --project evm-simulation-fork"
|
|
45
47
|
},
|
|
46
48
|
"exports": {
|
|
47
49
|
".": {
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// Errors (for instanceof checks by consumers)
|
|
4
4
|
export {
|
|
5
|
-
AddressScreeningError,
|
|
6
5
|
BlacklistViolationError,
|
|
7
6
|
ExternalServiceError,
|
|
8
7
|
SimulationPackageError,
|
|
@@ -10,10 +9,11 @@ export {
|
|
|
10
9
|
SimulationValidationError,
|
|
11
10
|
UnsupportedChainError,
|
|
12
11
|
} from "./errors.js";
|
|
13
|
-
export { screenAddresses } from "./screen-addresses/index.js";
|
|
14
12
|
export { simulate } from "./simulate/index.js";
|
|
15
13
|
// Types
|
|
16
14
|
export type {
|
|
15
|
+
AccountAssetChanges,
|
|
16
|
+
AssetChange,
|
|
17
17
|
ChainSimulationConfig,
|
|
18
18
|
RawLog,
|
|
19
19
|
SimulateParams,
|
|
@@ -23,6 +23,6 @@ export type {
|
|
|
23
23
|
SimulationLogger,
|
|
24
24
|
SimulationResult,
|
|
25
25
|
SimulationTransaction,
|
|
26
|
-
|
|
26
|
+
TenderlyRpcConfig,
|
|
27
27
|
Transfer,
|
|
28
28
|
} from "./types.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { screenAddresses } from "./screen-addresses.js";
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.screenAddresses = void 0;
|
|
4
|
-
var screen_addresses_js_1 = require("./screen-addresses.js");
|
|
5
|
-
Object.defineProperty(exports, "screenAddresses", { enumerable: true, get: function () { return screen_addresses_js_1.screenAddresses; } });
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static list of sanctioned/compromised addresses.
|
|
3
|
-
* Ported from offchain-services-server/src/functions/screen/blacklist.ts.
|
|
4
|
-
* These addresses are checked before calling the Chainalysis API (fast, no network).
|
|
5
|
-
*
|
|
6
|
-
* **Invariant** (enforced at module load, see below): every entry is a
|
|
7
|
-
* well-formed lowercased EVM address. `screenAddresses` does
|
|
8
|
-
* `sanctionedAddresses.has(addr.toLowerCase())` — a mixed-case entry would
|
|
9
|
-
* never match and would silently bypass the sanctioned check.
|
|
10
|
-
*/
|
|
11
|
-
export declare const sanctionedAddresses: Set<string>;
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sanctionedAddresses = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Static list of sanctioned/compromised addresses.
|
|
6
|
-
* Ported from offchain-services-server/src/functions/screen/blacklist.ts.
|
|
7
|
-
* These addresses are checked before calling the Chainalysis API (fast, no network).
|
|
8
|
-
*
|
|
9
|
-
* **Invariant** (enforced at module load, see below): every entry is a
|
|
10
|
-
* well-formed lowercased EVM address. `screenAddresses` does
|
|
11
|
-
* `sanctionedAddresses.has(addr.toLowerCase())` — a mixed-case entry would
|
|
12
|
-
* never match and would silently bypass the sanctioned check.
|
|
13
|
-
*/
|
|
14
|
-
exports.sanctionedAddresses = new Set([
|
|
15
|
-
// Bybit hack addresses
|
|
16
|
-
"0x47666Fab8bd0Ac7003bce3f5C3585383F09486E2",
|
|
17
|
-
"0xA4B2Fd68593B6F34E51cB9eDB66E71c1B4Ab449e",
|
|
18
|
-
"0x36ed3C0213565530C35115d93A80F9c04d94E4Cb",
|
|
19
|
-
"0x1542368a03ad1f03d96D51B414f4738961Cf4443",
|
|
20
|
-
"0xdD90071D52F20e85c89802e5Dc1eC0A7B6475f92",
|
|
21
|
-
"0x40e98FeEEbaD7Ddb0F0534Ccaa617427eA10187e",
|
|
22
|
-
"0x30a822CDD2782D2B2A12a08526452e885978FA1D",
|
|
23
|
-
"0x6d46bd3AfF100f23C194e5312f93507978a6DC91",
|
|
24
|
-
"0x660BfcEa3A5FAF823e8f8bF57dd558db034dea1d",
|
|
25
|
-
"0x140c9Ab92347734641b1A7c124ffDeE58c20C3E3",
|
|
26
|
-
"0x8c7235e1A6EeF91b980D0FcA083347FBb7EE1806",
|
|
27
|
-
"0xb172F7e99452446f18FF49A71bfEeCf0873003b4",
|
|
28
|
-
"0xCd7eC020121Ead6f99855cbB972dF502dB5bC63a",
|
|
29
|
-
"0x0e8C1E2881F35Ef20343264862A242FB749d6b35",
|
|
30
|
-
"0x2290937A4498C96eFfb87b8371a33D108F8D433f",
|
|
31
|
-
"0x1bb0970508316DC735329752a4581E0a4bAbc6B4",
|
|
32
|
-
"0xf0a16603289eAF35F64077Ba3681af41194a1c09",
|
|
33
|
-
"0x5Af75eAB6BEC227657fA3E749a8BFd55f02e4b1D",
|
|
34
|
-
"0xB4a862A81aBB2f952FcA4C6f5510962e18c7f1A2",
|
|
35
|
-
"0x959c4CA19c4532C97A657D82d97acCBAb70e6fb4",
|
|
36
|
-
"0xB72334cB9D0b614D30C4c60e2bd12fF5Ed03c305",
|
|
37
|
-
"0xFc926659Dd8808f6e3e0a8d61B20B871F3Fa6465",
|
|
38
|
-
"0xfa3FcCCB897079fD83bfBA690E7D47Eb402d6c49",
|
|
39
|
-
"0x51E9d833Ecae4E8D9D8Be17300AEE6D3398C135D",
|
|
40
|
-
"0x1eB27f136BFe7947f80d6ceE3Cf0bfDf92b45e57",
|
|
41
|
-
"0x83Ef5E80faD88288F770152875Ab0bb16641a09E",
|
|
42
|
-
"0x52207Ec7B1b43AA5DB116931a904371ae2C1619e",
|
|
43
|
-
"0x23Db729908137cb60852f2936D2b5c6De0e1c887",
|
|
44
|
-
"0xAF620E6d32B1c67f3396EF5d2F7d7642Dc2e6CE9",
|
|
45
|
-
"0xCd1a4A457cA8b0931c3BF81Df3CFa227ADBdb6E9",
|
|
46
|
-
"0x96244D83DC15d36847C35209bBDc5bdDE9bEc3D8",
|
|
47
|
-
"0x09278b36863bE4cCd3d0c22d643E8062D7a11377",
|
|
48
|
-
"0x3A21F4E6Bbe527D347ca7c157F4233c935779847",
|
|
49
|
-
"0xBC3e5e8C10897a81b63933348f53f2e052F89a7E",
|
|
50
|
-
"0x9271EDdda0F0f2bB7b1A0c712bdF8dbD0A38d1Ab",
|
|
51
|
-
"0x4C198B3B5F3a4b1Aa706daC73D826c2B795ccd67",
|
|
52
|
-
"0x684d4b58Dc32af786BF6D572A792fF7A883428B9",
|
|
53
|
-
"0xD3C611AeD139107DEC2294032da3913BC26507fb",
|
|
54
|
-
"0x9eF42873Ae015AA3da0c4354AeF94a18D2B3407b",
|
|
55
|
-
"0xbdE2Cc5375fa9E0383309A2cA31213f2D6cabcbd",
|
|
56
|
-
"0xe69753Ddfbedbd249E703EB374452E78dae1ae49",
|
|
57
|
-
"0xE9bc552fdFa54b30296d95F147e3e0280FF7f7e6",
|
|
58
|
-
"0xBCA02B395747D62626a65016F2e64A20bd254A39",
|
|
59
|
-
"0xF302572594a68aA8F951faE64ED3aE7DA41c72Be",
|
|
60
|
-
"0x21032176B43d9f7E9410fB37290a78f4fEd6044C",
|
|
61
|
-
"0xD5b58Cf7813c1eDC412367b97876bD400ea5c489",
|
|
62
|
-
"0xA5A023E052243b7cce34Cbd4ba20180e8Dea6Ad6",
|
|
63
|
-
"0x723a7084028421994d4a7829108D63aB44658315",
|
|
64
|
-
"0x1512fcb09463A61862B73ec09B9b354aF1790268",
|
|
65
|
-
"0xEB0bAA3A556586192590CAD296b1e48dF62a8549",
|
|
66
|
-
"0xf03AfB1c6A11A7E370920ad42e6eE735dBedF0b1",
|
|
67
|
-
"0x55CCa2f5eB07907696afe4b9Db5102bcE5feB734",
|
|
68
|
-
].map((a) => a.toLowerCase()));
|
|
69
|
-
// Runtime invariant check at module load. Catches a future edit that adds an
|
|
70
|
-
// entry via `sanctionedAddresses.add(...)` bypassing the `.map(toLowerCase)` or
|
|
71
|
-
// inserts a malformed string. Throwing at load is the loudest signal — the
|
|
72
|
-
// package won't import if the dataset is invalid.
|
|
73
|
-
for (const entry of exports.sanctionedAddresses) {
|
|
74
|
-
if (!/^0x[0-9a-f]{40}$/.test(entry)) {
|
|
75
|
-
throw new Error(`sanctioned-addresses.ts: entry "${entry}" is not a well-formed lowercased EVM address. Every entry must match /^0x[0-9a-f]{40}$/ so lowercase lookup matches.`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { SimulationLogger, SimulationTransaction, Transfer } from "../types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Screens every address that appears in a simulated bundle (transfer senders and recipients,
|
|
4
|
-
* plus tx `from` / `to`) against the static {@link sanctionedAddresses} list and, when
|
|
5
|
-
* configured, the Chainalysis Entity API.
|
|
6
|
-
*
|
|
7
|
-
* Addresses are deduped and lowercased; the zero address is removed. Checks run in order:
|
|
8
|
-
*
|
|
9
|
-
* 1. **Static sanctioned list** — always on, no network. Throws on the first hit.
|
|
10
|
-
* 2. **Chainalysis Entity API** — one lookup per address, lookups parallelized. Only runs when
|
|
11
|
-
* `chainalysisApiKey` is provided.
|
|
12
|
-
* - **Fail-open on transport errors before registration** (network / timeout / non-ok POST):
|
|
13
|
-
* treat the address as clean and log a warn. A Chainalysis outage must not block all
|
|
14
|
-
* simulations.
|
|
15
|
-
* - **Fail-closed on partial registration**: if the registration POST succeeded but the
|
|
16
|
-
* lookup GET fails, treat the address as Severe.
|
|
17
|
-
* - **Fail-closed on schema drift**: an unknown risk tier or unparsed body is treated as
|
|
18
|
-
* Severe so a future stricter tier is not silently downgraded.
|
|
19
|
-
*
|
|
20
|
-
* Intended to be called after {@link simulate}, passing the returned `simulationTxs` and
|
|
21
|
-
* `transfers`.
|
|
22
|
-
*
|
|
23
|
-
* @param params.simulationTxs - The resolved transactions returned by `simulate()`.
|
|
24
|
-
* @param params.transfers - The parsed transfers returned by `simulate()`.
|
|
25
|
-
* @param params.chainalysisApiKey - Optional Chainalysis token. When absent, only the static
|
|
26
|
-
* sanctioned list runs.
|
|
27
|
-
* @param params.signal - Optional `AbortSignal` propagated to every Chainalysis request.
|
|
28
|
-
* @param params.logger - Optional `SimulationLogger` for warn-level diagnostics.
|
|
29
|
-
* @returns Resolves to `void` on a clean screening; throws on any Severe hit.
|
|
30
|
-
* @throws {AddressScreeningError} when any address matches the static sanctioned list or
|
|
31
|
-
* resolves to Severe via Chainalysis.
|
|
32
|
-
* @example
|
|
33
|
-
* ```ts
|
|
34
|
-
* import { screenAddresses, simulate } from "@morpho-org/evm-simulation";
|
|
35
|
-
*
|
|
36
|
-
* const result = await simulate(config, params);
|
|
37
|
-
* await screenAddresses({
|
|
38
|
-
* simulationTxs: result.simulationTxs,
|
|
39
|
-
* transfers: result.transfers,
|
|
40
|
-
* chainalysisApiKey: process.env.CHAINALYSIS_API_KEY,
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export declare function screenAddresses(params: {
|
|
45
|
-
simulationTxs: SimulationTransaction[];
|
|
46
|
-
transfers: Transfer[];
|
|
47
|
-
chainalysisApiKey?: string;
|
|
48
|
-
signal?: AbortSignal;
|
|
49
|
-
logger?: SimulationLogger;
|
|
50
|
-
}): Promise<void>;
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.screenAddresses = screenAddresses;
|
|
4
|
-
const viem_1 = require("viem");
|
|
5
|
-
const zod_1 = require("zod");
|
|
6
|
-
const errors_js_1 = require("../errors.js");
|
|
7
|
-
const sanctioned_addresses_js_1 = require("./sanctioned-addresses.js");
|
|
8
|
-
const CHAINALYSIS_API_URL = "https://api.chainalysis.com/api/risk/v2/entities";
|
|
9
|
-
const CHAINALYSIS_TIMEOUT_MS = 5_000;
|
|
10
|
-
/**
|
|
11
|
-
* Known tiers whose presence means NOT severe. Any other value (unknown future
|
|
12
|
-
* tier, malformed field) is treated as Severe so a stricter tier Chainalysis
|
|
13
|
-
* might introduce later isn't silently downgraded to clean.
|
|
14
|
-
*/
|
|
15
|
-
const NON_SEVERE_TIERS = new Set(["High", "Medium", "Low"]);
|
|
16
|
-
const chainalysisEntitySchema = zod_1.z
|
|
17
|
-
.object({
|
|
18
|
-
risk: zod_1.z.string().nullable(),
|
|
19
|
-
})
|
|
20
|
-
.passthrough();
|
|
21
|
-
/**
|
|
22
|
-
* Screens every address that appears in a simulated bundle (transfer senders and recipients,
|
|
23
|
-
* plus tx `from` / `to`) against the static {@link sanctionedAddresses} list and, when
|
|
24
|
-
* configured, the Chainalysis Entity API.
|
|
25
|
-
*
|
|
26
|
-
* Addresses are deduped and lowercased; the zero address is removed. Checks run in order:
|
|
27
|
-
*
|
|
28
|
-
* 1. **Static sanctioned list** — always on, no network. Throws on the first hit.
|
|
29
|
-
* 2. **Chainalysis Entity API** — one lookup per address, lookups parallelized. Only runs when
|
|
30
|
-
* `chainalysisApiKey` is provided.
|
|
31
|
-
* - **Fail-open on transport errors before registration** (network / timeout / non-ok POST):
|
|
32
|
-
* treat the address as clean and log a warn. A Chainalysis outage must not block all
|
|
33
|
-
* simulations.
|
|
34
|
-
* - **Fail-closed on partial registration**: if the registration POST succeeded but the
|
|
35
|
-
* lookup GET fails, treat the address as Severe.
|
|
36
|
-
* - **Fail-closed on schema drift**: an unknown risk tier or unparsed body is treated as
|
|
37
|
-
* Severe so a future stricter tier is not silently downgraded.
|
|
38
|
-
*
|
|
39
|
-
* Intended to be called after {@link simulate}, passing the returned `simulationTxs` and
|
|
40
|
-
* `transfers`.
|
|
41
|
-
*
|
|
42
|
-
* @param params.simulationTxs - The resolved transactions returned by `simulate()`.
|
|
43
|
-
* @param params.transfers - The parsed transfers returned by `simulate()`.
|
|
44
|
-
* @param params.chainalysisApiKey - Optional Chainalysis token. When absent, only the static
|
|
45
|
-
* sanctioned list runs.
|
|
46
|
-
* @param params.signal - Optional `AbortSignal` propagated to every Chainalysis request.
|
|
47
|
-
* @param params.logger - Optional `SimulationLogger` for warn-level diagnostics.
|
|
48
|
-
* @returns Resolves to `void` on a clean screening; throws on any Severe hit.
|
|
49
|
-
* @throws {AddressScreeningError} when any address matches the static sanctioned list or
|
|
50
|
-
* resolves to Severe via Chainalysis.
|
|
51
|
-
* @example
|
|
52
|
-
* ```ts
|
|
53
|
-
* import { screenAddresses, simulate } from "@morpho-org/evm-simulation";
|
|
54
|
-
*
|
|
55
|
-
* const result = await simulate(config, params);
|
|
56
|
-
* await screenAddresses({
|
|
57
|
-
* simulationTxs: result.simulationTxs,
|
|
58
|
-
* transfers: result.transfers,
|
|
59
|
-
* chainalysisApiKey: process.env.CHAINALYSIS_API_KEY,
|
|
60
|
-
* });
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
async function screenAddresses(params) {
|
|
64
|
-
const { simulationTxs, transfers, chainalysisApiKey, signal, logger } = params;
|
|
65
|
-
const addressesToScreen = new Set();
|
|
66
|
-
for (const transfer of transfers) {
|
|
67
|
-
addressesToScreen.add(transfer.from.toLowerCase());
|
|
68
|
-
addressesToScreen.add(transfer.to.toLowerCase());
|
|
69
|
-
}
|
|
70
|
-
for (const tx of simulationTxs) {
|
|
71
|
-
addressesToScreen.add(tx.from.toLowerCase());
|
|
72
|
-
addressesToScreen.add(tx.to.toLowerCase());
|
|
73
|
-
}
|
|
74
|
-
addressesToScreen.delete(viem_1.zeroAddress);
|
|
75
|
-
// 1. Static sanctioned list — always on, no network.
|
|
76
|
-
const sanctioned = [];
|
|
77
|
-
for (const addr of addressesToScreen) {
|
|
78
|
-
if (sanctioned_addresses_js_1.sanctionedAddresses.has(addr))
|
|
79
|
-
sanctioned.push((0, viem_1.getAddress)(addr));
|
|
80
|
-
}
|
|
81
|
-
if (sanctioned.length > 0) {
|
|
82
|
-
throw new errors_js_1.AddressScreeningError("Transaction involves sanctioned addresses", sanctioned);
|
|
83
|
-
}
|
|
84
|
-
// 2. Chainalysis (optional).
|
|
85
|
-
if (!chainalysisApiKey) {
|
|
86
|
-
logger?.warn("Chainalysis screening skipped: no API key configured");
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const results = await Promise.allSettled([...addressesToScreen].map(async (addr) => {
|
|
90
|
-
const verdict = await screenWithChainalysis(addr, chainalysisApiKey, signal);
|
|
91
|
-
return { addr, verdict };
|
|
92
|
-
}));
|
|
93
|
-
const flagged = [];
|
|
94
|
-
for (const r of results) {
|
|
95
|
-
if (r.status === "fulfilled") {
|
|
96
|
-
if (r.value.verdict === "severe")
|
|
97
|
-
flagged.push((0, viem_1.getAddress)(r.value.addr));
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
// Transport fail-open: registration POST couldn't even complete, so the
|
|
101
|
-
// address was never registered with Chainalysis and the package's
|
|
102
|
-
// fail-open posture applies.
|
|
103
|
-
logger?.warn("Chainalysis screening failed (fail-open)", {
|
|
104
|
-
error: r.reason instanceof Error ? r.reason.message : String(r.reason),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (flagged.length > 0) {
|
|
109
|
-
throw new errors_js_1.AddressScreeningError("Transaction involves addresses flagged as Severe risk", flagged);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Run the Chainalysis Entity flow for one address.
|
|
114
|
-
*
|
|
115
|
-
* Returns `'severe'` or `'clean'`. Throws only if the initial registration POST
|
|
116
|
-
* fails before any server-side side effect — then the outer handler treats the
|
|
117
|
-
* rejection as fail-open. After registration succeeds, every error path returns
|
|
118
|
-
* `'severe'` so the check fails closed.
|
|
119
|
-
*
|
|
120
|
-
* A SINGLE deadline covers both the POST and the GET, keeping the per-address
|
|
121
|
-
* wall-time budget at `CHAINALYSIS_TIMEOUT_MS` (5 s) rather than doubling it.
|
|
122
|
-
*
|
|
123
|
-
* @internal
|
|
124
|
-
*/
|
|
125
|
-
// biome-ignore lint/complexity/useMaxParams: TODO refactor to ≤2 params
|
|
126
|
-
async function screenWithChainalysis(address, apiKey, parentSignal) {
|
|
127
|
-
const headers = {
|
|
128
|
-
"Content-Type": "application/json",
|
|
129
|
-
token: apiKey,
|
|
130
|
-
};
|
|
131
|
-
const deadline = AbortSignal.timeout(CHAINALYSIS_TIMEOUT_MS);
|
|
132
|
-
const signal = parentSignal
|
|
133
|
-
? AbortSignal.any([parentSignal, deadline])
|
|
134
|
-
: deadline;
|
|
135
|
-
// POST to register. Pre-registration failure → fail-open via thrown Error.
|
|
136
|
-
const registration = await fetch(CHAINALYSIS_API_URL, {
|
|
137
|
-
headers,
|
|
138
|
-
method: "POST",
|
|
139
|
-
body: JSON.stringify({ address }),
|
|
140
|
-
signal,
|
|
141
|
-
});
|
|
142
|
-
if (!registration.ok) {
|
|
143
|
-
throw new Error(`Chainalysis registration failed: ${registration.status} ${registration.statusText}`);
|
|
144
|
-
}
|
|
145
|
-
// From here on we've already told Chainalysis about the address — fail closed.
|
|
146
|
-
let response;
|
|
147
|
-
try {
|
|
148
|
-
response = await fetch(`${CHAINALYSIS_API_URL}/${encodeURIComponent(address)}`, {
|
|
149
|
-
method: "GET",
|
|
150
|
-
headers,
|
|
151
|
-
signal,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return "severe";
|
|
156
|
-
}
|
|
157
|
-
if (!response.ok)
|
|
158
|
-
return "severe";
|
|
159
|
-
let parsed;
|
|
160
|
-
try {
|
|
161
|
-
parsed = chainalysisEntitySchema.parse(await response.json());
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
return "severe";
|
|
165
|
-
}
|
|
166
|
-
if (parsed.risk === null)
|
|
167
|
-
return "clean";
|
|
168
|
-
if (NON_SEVERE_TIERS.has(parsed.risk))
|
|
169
|
-
return "clean";
|
|
170
|
-
return "severe";
|
|
171
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type BlockTag } from "viem";
|
|
2
|
-
import type { RawSimulationResult, SimulationLogger, SimulationTransaction, TenderlyRestConfig } from "../../types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Simulate transactions using Tenderly REST API.
|
|
5
|
-
*
|
|
6
|
-
* @param shareable - true persists the simulation in Tenderly and returns a shareable URL;
|
|
7
|
-
* false runs ephemerally with no URL. Translates to Tenderly's `save` wire field
|
|
8
|
-
* (`save_if_fails` is also set to `shareable` — so failed preview simulations are
|
|
9
|
-
* ALSO persisted and shareable. Callers that preview sensitive calldata should be
|
|
10
|
-
* aware this persists in Tenderly's dashboard regardless of success/failure).
|
|
11
|
-
*/
|
|
12
|
-
export declare function simulateTenderlyRest(params: {
|
|
13
|
-
config: TenderlyRestConfig;
|
|
14
|
-
chainId: number;
|
|
15
|
-
transactions: SimulationTransaction[];
|
|
16
|
-
blockNumber?: bigint | BlockTag;
|
|
17
|
-
shareable: boolean;
|
|
18
|
-
signal?: AbortSignal;
|
|
19
|
-
logger?: SimulationLogger;
|
|
20
|
-
}): Promise<RawSimulationResult>;
|