@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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/lib/cjs/errors.d.ts +50 -0
  4. package/lib/cjs/errors.js +72 -0
  5. package/lib/cjs/index.d.ts +4 -0
  6. package/lib/cjs/index.js +17 -0
  7. package/lib/cjs/package.json +1 -0
  8. package/lib/cjs/screen-addresses/index.d.ts +1 -0
  9. package/lib/cjs/screen-addresses/index.js +5 -0
  10. package/lib/cjs/screen-addresses/sanctioned-addresses.d.ts +11 -0
  11. package/lib/cjs/screen-addresses/sanctioned-addresses.js +77 -0
  12. package/lib/cjs/screen-addresses/screen-addresses.d.ts +34 -0
  13. package/lib/cjs/screen-addresses/screen-addresses.js +152 -0
  14. package/lib/cjs/simulate/authorizations/index.d.ts +2 -0
  15. package/lib/cjs/simulate/authorizations/index.js +7 -0
  16. package/lib/cjs/simulate/authorizations/resolve.d.ts +9 -0
  17. package/lib/cjs/simulate/authorizations/resolve.js +29 -0
  18. package/lib/cjs/simulate/authorizations/validate.d.ts +6 -0
  19. package/lib/cjs/simulate/authorizations/validate.js +23 -0
  20. package/lib/cjs/simulate/backends/eth-simulate-v1.d.ts +15 -0
  21. package/lib/cjs/simulate/backends/eth-simulate-v1.js +76 -0
  22. package/lib/cjs/simulate/backends/index.d.ts +2 -0
  23. package/lib/cjs/simulate/backends/index.js +7 -0
  24. package/lib/cjs/simulate/backends/tenderly-rest.d.ts +20 -0
  25. package/lib/cjs/simulate/backends/tenderly-rest.js +269 -0
  26. package/lib/cjs/simulate/index.d.ts +1 -0
  27. package/lib/cjs/simulate/index.js +5 -0
  28. package/lib/cjs/simulate/parsing/index.d.ts +1 -0
  29. package/lib/cjs/simulate/parsing/index.js +5 -0
  30. package/lib/cjs/simulate/parsing/transfers.d.ts +27 -0
  31. package/lib/cjs/simulate/parsing/transfers.js +153 -0
  32. package/lib/cjs/simulate/pipeline/build-simulation-txs.d.ts +17 -0
  33. package/lib/cjs/simulate/pipeline/build-simulation-txs.js +50 -0
  34. package/lib/cjs/simulate/pipeline/bundler-retention.d.ts +19 -0
  35. package/lib/cjs/simulate/pipeline/bundler-retention.js +90 -0
  36. package/lib/cjs/simulate/pipeline/execute-simulation.d.ts +25 -0
  37. package/lib/cjs/simulate/pipeline/execute-simulation.js +87 -0
  38. package/lib/cjs/simulate/pipeline/index.d.ts +4 -0
  39. package/lib/cjs/simulate/pipeline/index.js +11 -0
  40. package/lib/cjs/simulate/pipeline/resolve-chain.d.ts +14 -0
  41. package/lib/cjs/simulate/pipeline/resolve-chain.js +19 -0
  42. package/lib/cjs/simulate/pipeline/validate-input.d.ts +10 -0
  43. package/lib/cjs/simulate/pipeline/validate-input.js +55 -0
  44. package/lib/cjs/simulate/simulate.d.ts +26 -0
  45. package/lib/cjs/simulate/simulate.js +46 -0
  46. package/lib/cjs/types.d.ts +131 -0
  47. package/lib/cjs/types.js +2 -0
  48. package/lib/esm/errors.d.ts +50 -0
  49. package/lib/esm/errors.js +62 -0
  50. package/lib/esm/index.d.ts +4 -0
  51. package/lib/esm/index.js +5 -0
  52. package/lib/esm/package.json +1 -0
  53. package/lib/esm/screen-addresses/index.d.ts +1 -0
  54. package/lib/esm/screen-addresses/index.js +1 -0
  55. package/lib/esm/screen-addresses/sanctioned-addresses.d.ts +11 -0
  56. package/lib/esm/screen-addresses/sanctioned-addresses.js +74 -0
  57. package/lib/esm/screen-addresses/screen-addresses.d.ts +34 -0
  58. package/lib/esm/screen-addresses/screen-addresses.js +149 -0
  59. package/lib/esm/simulate/authorizations/index.d.ts +2 -0
  60. package/lib/esm/simulate/authorizations/index.js +2 -0
  61. package/lib/esm/simulate/authorizations/resolve.d.ts +9 -0
  62. package/lib/esm/simulate/authorizations/resolve.js +26 -0
  63. package/lib/esm/simulate/authorizations/validate.d.ts +6 -0
  64. package/lib/esm/simulate/authorizations/validate.js +20 -0
  65. package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +15 -0
  66. package/lib/esm/simulate/backends/eth-simulate-v1.js +73 -0
  67. package/lib/esm/simulate/backends/index.d.ts +2 -0
  68. package/lib/esm/simulate/backends/index.js +2 -0
  69. package/lib/esm/simulate/backends/tenderly-rest.d.ts +20 -0
  70. package/lib/esm/simulate/backends/tenderly-rest.js +266 -0
  71. package/lib/esm/simulate/index.d.ts +1 -0
  72. package/lib/esm/simulate/index.js +1 -0
  73. package/lib/esm/simulate/parsing/index.d.ts +1 -0
  74. package/lib/esm/simulate/parsing/index.js +1 -0
  75. package/lib/esm/simulate/parsing/transfers.d.ts +27 -0
  76. package/lib/esm/simulate/parsing/transfers.js +149 -0
  77. package/lib/esm/simulate/pipeline/build-simulation-txs.d.ts +17 -0
  78. package/lib/esm/simulate/pipeline/build-simulation-txs.js +47 -0
  79. package/lib/esm/simulate/pipeline/bundler-retention.d.ts +19 -0
  80. package/lib/esm/simulate/pipeline/bundler-retention.js +87 -0
  81. package/lib/esm/simulate/pipeline/execute-simulation.d.ts +25 -0
  82. package/lib/esm/simulate/pipeline/execute-simulation.js +84 -0
  83. package/lib/esm/simulate/pipeline/index.d.ts +4 -0
  84. package/lib/esm/simulate/pipeline/index.js +4 -0
  85. package/lib/esm/simulate/pipeline/resolve-chain.d.ts +14 -0
  86. package/lib/esm/simulate/pipeline/resolve-chain.js +16 -0
  87. package/lib/esm/simulate/pipeline/validate-input.d.ts +10 -0
  88. package/lib/esm/simulate/pipeline/validate-input.js +52 -0
  89. package/lib/esm/simulate/simulate.d.ts +26 -0
  90. package/lib/esm/simulate/simulate.js +43 -0
  91. package/lib/esm/types.d.ts +131 -0
  92. package/lib/esm/types.js +1 -0
  93. package/package.json +49 -0
  94. package/src/index.ts +27 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Morpho Association
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @morpho-org/evm-simulation
2
+
3
+ EVM transaction simulation engine for Morpho — bundle execution preview,
4
+ transfer parsing, and compliance screening.
5
+
6
+ ## Install
7
+
8
+ Add to the consuming app's `package.json`:
9
+
10
+ ```jsonc
11
+ {
12
+ "dependencies": {
13
+ "@morpho-org/evm-simulation": "workspace:*"
14
+ }
15
+ }
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```ts
21
+ import {
22
+ simulate,
23
+ screenAddresses,
24
+ type SimulationConfig,
25
+ SimulationRevertedError
26
+ } from "@morpho-org/evm-simulation";
27
+
28
+ const config: SimulationConfig = {
29
+ tenderlyRest: {
30
+ apiBaseUrl: "https://api.tenderly.co",
31
+ accessToken: process.env.TENDERLY_ACCESS_TOKEN!,
32
+ accountSlug: "my-account",
33
+ projectSlug: "my-project",
34
+ supportedChainIds: new Set([1]),
35
+ },
36
+ chains: new Map([[1, { simulateV1Url: process.env.MAINNET_RPC_URL }]]),
37
+ timeoutMs: 5000,
38
+ };
39
+
40
+ try {
41
+ // Pass { shareable: true } when you want a shareable Tenderly URL (user-facing preview).
42
+ // Omit it for server-side verification — the same function, same return shape.
43
+ const { transfers, simulationTxs, tenderlyUrl } = await simulate(
44
+ config,
45
+ {
46
+ chainId: 1,
47
+ transactions: [{ from: user, to: vault, data: encodedDeposit }],
48
+ authorizations: [{ type: "signature", token: usdc, spender: vault }],
49
+ },
50
+ { shareable: true }
51
+ );
52
+
53
+ // For server-side flows, pipe into screenAddresses:
54
+ await screenAddresses({ simulationTxs, transfers });
55
+ } catch (err) {
56
+ if (err instanceof SimulationRevertedError) {
57
+ // show err.reason to the user
58
+ }
59
+ throw err;
60
+ }
61
+ ```
62
+
63
+ ## API surface
64
+
65
+ All symbols below are re-exported from the package root.
66
+
67
+ - `simulate(config, params, options?)` — run a bundle through the simulation pipeline. Pass `{ shareable: true }` for a shareable Tenderly URL; omit for server-side verification.
68
+ - `screenAddresses({ simulationTxs, transfers, chainalysisApiKey? })` — static sanctioned-set + optional Chainalysis screening.
69
+ - Config types: `SimulationConfig`, `TenderlyRestConfig`, `ChainSimulationConfig`, `SimulationLogger`.
70
+ - Data types: `SimulationTransaction`, `SimulationAuthorization`, `Transfer`, `SimulateParams`, `SimulationResult`.
71
+ - Errors: `SimulationPackageError` (abstract base — `instanceof` it to catch any package error), `SimulationRevertedError`, `BlacklistViolationError`, `AddressScreeningError`, `ExternalServiceError`, `SimulationValidationError`, `UnsupportedChainError`.
72
+
73
+ ## Deeper docs
74
+
75
+ See [`CLAUDE.md`](./CLAUDE.md) in this directory for the execution flow diagram,
76
+ backend tradeoffs, authorizations model, screening design, error-handling table,
77
+ and recipes for adding a chain or a new backend.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Base error for all simulation package errors.
3
+ * Transport-agnostic — no HTTP status codes.
4
+ * Consumers use try/catch + instanceof to handle specific error types.
5
+ */
6
+ export declare abstract class SimulationPackageError extends Error {
7
+ abstract readonly code: string;
8
+ constructor(message: string, options?: ErrorOptions);
9
+ }
10
+ /** Transaction would revert on-chain. Not bypassable. */
11
+ export declare class SimulationRevertedError extends SimulationPackageError {
12
+ readonly reason: string | undefined;
13
+ readonly details?: unknown | undefined;
14
+ readonly code = "SIMULATION_REVERTED";
15
+ constructor(reason: string | undefined, details?: unknown | undefined);
16
+ }
17
+ interface RetainedAsset {
18
+ address: string | undefined;
19
+ token: string | undefined;
20
+ netRetained: string;
21
+ }
22
+ /** Funds would flow to bundler3 contract addresses. Never bypassable. */
23
+ export declare class BlacklistViolationError extends SimulationPackageError {
24
+ readonly assetChanges?: RetainedAsset[] | undefined;
25
+ readonly code = "BLACKLIST_ERROR";
26
+ constructor(message: string, assetChanges?: RetainedAsset[] | undefined);
27
+ }
28
+ /** Sanctioned or compromised address detected. Never bypassable. */
29
+ export declare class AddressScreeningError extends SimulationPackageError {
30
+ readonly addresses: string[];
31
+ readonly code = "SCREENING_ERROR";
32
+ constructor(message: string, addresses: string[]);
33
+ }
34
+ /** Tenderly or RPC service is down. Bypassable — user can proceed. */
35
+ export declare class ExternalServiceError extends SimulationPackageError {
36
+ readonly code = "EXTERNAL_SERVICE_ERROR";
37
+ }
38
+ /** Bad input to the simulation functions. Not bypassable. */
39
+ export declare class SimulationValidationError extends SimulationPackageError {
40
+ readonly fieldErrors?: string[] | undefined;
41
+ readonly code = "VALIDATION_ERROR";
42
+ constructor(message: string, fieldErrors?: string[] | undefined);
43
+ }
44
+ /** Chain ID not configured for any simulation method. Not bypassable. */
45
+ export declare class UnsupportedChainError extends SimulationPackageError {
46
+ readonly chainId: number;
47
+ readonly code = "UNSUPPORTED_CHAIN";
48
+ constructor(chainId: number);
49
+ }
50
+ export {};
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnsupportedChainError = exports.SimulationValidationError = exports.ExternalServiceError = exports.AddressScreeningError = exports.BlacklistViolationError = exports.SimulationRevertedError = exports.SimulationPackageError = void 0;
4
+ /**
5
+ * Base error for all simulation package errors.
6
+ * Transport-agnostic — no HTTP status codes.
7
+ * Consumers use try/catch + instanceof to handle specific error types.
8
+ */
9
+ class SimulationPackageError extends Error {
10
+ constructor(message, options) {
11
+ super(message, options);
12
+ this.name = this.constructor.name;
13
+ }
14
+ }
15
+ exports.SimulationPackageError = SimulationPackageError;
16
+ /** Transaction would revert on-chain. Not bypassable. */
17
+ class SimulationRevertedError extends SimulationPackageError {
18
+ reason;
19
+ details;
20
+ code = "SIMULATION_REVERTED";
21
+ constructor(reason, details) {
22
+ super(reason ?? "Transaction simulation reverted");
23
+ this.reason = reason;
24
+ this.details = details;
25
+ }
26
+ }
27
+ exports.SimulationRevertedError = SimulationRevertedError;
28
+ /** Funds would flow to bundler3 contract addresses. Never bypassable. */
29
+ class BlacklistViolationError extends SimulationPackageError {
30
+ assetChanges;
31
+ code = "BLACKLIST_ERROR";
32
+ constructor(message, assetChanges) {
33
+ super(message);
34
+ this.assetChanges = assetChanges;
35
+ }
36
+ }
37
+ exports.BlacklistViolationError = BlacklistViolationError;
38
+ /** Sanctioned or compromised address detected. Never bypassable. */
39
+ class AddressScreeningError extends SimulationPackageError {
40
+ addresses;
41
+ code = "SCREENING_ERROR";
42
+ constructor(message, addresses) {
43
+ super(message);
44
+ this.addresses = addresses;
45
+ }
46
+ }
47
+ exports.AddressScreeningError = AddressScreeningError;
48
+ /** Tenderly or RPC service is down. Bypassable — user can proceed. */
49
+ class ExternalServiceError extends SimulationPackageError {
50
+ code = "EXTERNAL_SERVICE_ERROR";
51
+ }
52
+ exports.ExternalServiceError = ExternalServiceError;
53
+ /** Bad input to the simulation functions. Not bypassable. */
54
+ class SimulationValidationError extends SimulationPackageError {
55
+ fieldErrors;
56
+ code = "VALIDATION_ERROR";
57
+ constructor(message, fieldErrors) {
58
+ super(message);
59
+ this.fieldErrors = fieldErrors;
60
+ }
61
+ }
62
+ exports.SimulationValidationError = SimulationValidationError;
63
+ /** Chain ID not configured for any simulation method. Not bypassable. */
64
+ class UnsupportedChainError extends SimulationPackageError {
65
+ chainId;
66
+ code = "UNSUPPORTED_CHAIN";
67
+ constructor(chainId) {
68
+ super(`Chain ${chainId} is not configured for simulation`);
69
+ this.chainId = chainId;
70
+ }
71
+ }
72
+ exports.UnsupportedChainError = UnsupportedChainError;
@@ -0,0 +1,4 @@
1
+ export { screenAddresses } from "./screen-addresses/index.js";
2
+ export { simulate } from "./simulate/index.js";
3
+ export type { ChainSimulationConfig, SimulateParams, SimulationAuthorization, SimulationConfig, SimulationLogger, SimulationResult, SimulationTransaction, TenderlyRestConfig, Transfer, } from "./types.js";
4
+ export { SimulationPackageError, SimulationRevertedError, BlacklistViolationError, AddressScreeningError, ExternalServiceError, SimulationValidationError, UnsupportedChainError, } from "./errors.js";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnsupportedChainError = exports.SimulationValidationError = exports.ExternalServiceError = exports.AddressScreeningError = exports.BlacklistViolationError = exports.SimulationRevertedError = exports.SimulationPackageError = exports.simulate = exports.screenAddresses = void 0;
4
+ // Public fns (via feature-folder barrels)
5
+ var index_js_1 = require("./screen-addresses/index.js");
6
+ Object.defineProperty(exports, "screenAddresses", { enumerable: true, get: function () { return index_js_1.screenAddresses; } });
7
+ var index_js_2 = require("./simulate/index.js");
8
+ Object.defineProperty(exports, "simulate", { enumerable: true, get: function () { return index_js_2.simulate; } });
9
+ // Errors (for instanceof checks by consumers)
10
+ var errors_js_1 = require("./errors.js");
11
+ Object.defineProperty(exports, "SimulationPackageError", { enumerable: true, get: function () { return errors_js_1.SimulationPackageError; } });
12
+ Object.defineProperty(exports, "SimulationRevertedError", { enumerable: true, get: function () { return errors_js_1.SimulationRevertedError; } });
13
+ Object.defineProperty(exports, "BlacklistViolationError", { enumerable: true, get: function () { return errors_js_1.BlacklistViolationError; } });
14
+ Object.defineProperty(exports, "AddressScreeningError", { enumerable: true, get: function () { return errors_js_1.AddressScreeningError; } });
15
+ Object.defineProperty(exports, "ExternalServiceError", { enumerable: true, get: function () { return errors_js_1.ExternalServiceError; } });
16
+ Object.defineProperty(exports, "SimulationValidationError", { enumerable: true, get: function () { return errors_js_1.SimulationValidationError; } });
17
+ Object.defineProperty(exports, "UnsupportedChainError", { enumerable: true, get: function () { return errors_js_1.UnsupportedChainError; } });
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1 @@
1
+ export { screenAddresses } from "./screen-addresses.js";
@@ -0,0 +1,5 @@
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; } });
@@ -0,0 +1,11 @@
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>;
@@ -0,0 +1,77 @@
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
+ }
@@ -0,0 +1,34 @@
1
+ import type { SimulationLogger, SimulationTransaction, Transfer } from "../types.js";
2
+ /**
3
+ * Screen all relevant addresses from a simulation for sanctioned/compromised addresses.
4
+ *
5
+ * Screening targets (deduped, lowercased, zero-addr removed):
6
+ * - Transfer senders and recipients from simulation result
7
+ * - `from` and `to` of every tx in `simulationTxs` (catches native-value flows)
8
+ *
9
+ * Checks run in order:
10
+ * 1. Static {@link sanctionedAddresses} set (fast, no network, always on).
11
+ * 2. Chainalysis Entity API — one lookup per address, lookups across addresses
12
+ * run in parallel. Only fires if `chainalysisApiKey` is provided.
13
+ * **Fail-open on transport errors before registration** (network / timeout /
14
+ * non-ok POST): the address is treated as clean, a warn is logged, and the
15
+ * simulation proceeds. This is the documented posture — a Chainalysis
16
+ * outage must not block all simulations.
17
+ * **Fail-closed on partial registration**: if the registration POST succeeded
18
+ * but the subsequent lookup GET fails, the address is treated as Severe.
19
+ * The data already persisted server-side, so failing open at this point
20
+ * would let a pass slip through based on a half-completed check.
21
+ * **Fail-closed on schema drift**: if Chainalysis returns an unknown risk
22
+ * tier or a body that doesn't parse, the address is treated as Severe so a
23
+ * future stricter tier isn't silently downgraded.
24
+ *
25
+ * Throws `AddressScreeningError` on any Severe hit. Intended to be called after
26
+ * `simulate`, passing the returned `simulationTxs` and `transfers`.
27
+ */
28
+ export declare function screenAddresses(params: {
29
+ simulationTxs: SimulationTransaction[];
30
+ transfers: Transfer[];
31
+ chainalysisApiKey?: string;
32
+ signal?: AbortSignal;
33
+ logger?: SimulationLogger;
34
+ }): Promise<void>;
@@ -0,0 +1,152 @@
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
+ * Screen all relevant addresses from a simulation for sanctioned/compromised addresses.
23
+ *
24
+ * Screening targets (deduped, lowercased, zero-addr removed):
25
+ * - Transfer senders and recipients from simulation result
26
+ * - `from` and `to` of every tx in `simulationTxs` (catches native-value flows)
27
+ *
28
+ * Checks run in order:
29
+ * 1. Static {@link sanctionedAddresses} set (fast, no network, always on).
30
+ * 2. Chainalysis Entity API — one lookup per address, lookups across addresses
31
+ * run in parallel. Only fires if `chainalysisApiKey` is provided.
32
+ * **Fail-open on transport errors before registration** (network / timeout /
33
+ * non-ok POST): the address is treated as clean, a warn is logged, and the
34
+ * simulation proceeds. This is the documented posture — a Chainalysis
35
+ * outage must not block all simulations.
36
+ * **Fail-closed on partial registration**: if the registration POST succeeded
37
+ * but the subsequent lookup GET fails, the address is treated as Severe.
38
+ * The data already persisted server-side, so failing open at this point
39
+ * would let a pass slip through based on a half-completed check.
40
+ * **Fail-closed on schema drift**: if Chainalysis returns an unknown risk
41
+ * tier or a body that doesn't parse, the address is treated as Severe so a
42
+ * future stricter tier isn't silently downgraded.
43
+ *
44
+ * Throws `AddressScreeningError` on any Severe hit. Intended to be called after
45
+ * `simulate`, passing the returned `simulationTxs` and `transfers`.
46
+ */
47
+ async function screenAddresses(params) {
48
+ const { simulationTxs, transfers, chainalysisApiKey, signal, logger } = params;
49
+ const addressesToScreen = new Set();
50
+ for (const transfer of transfers) {
51
+ addressesToScreen.add(transfer.from.toLowerCase());
52
+ addressesToScreen.add(transfer.to.toLowerCase());
53
+ }
54
+ for (const tx of simulationTxs) {
55
+ addressesToScreen.add(tx.from.toLowerCase());
56
+ addressesToScreen.add(tx.to.toLowerCase());
57
+ }
58
+ addressesToScreen.delete(viem_1.zeroAddress);
59
+ // 1. Static sanctioned list — always on, no network.
60
+ const sanctioned = [];
61
+ for (const addr of addressesToScreen) {
62
+ if (sanctioned_addresses_js_1.sanctionedAddresses.has(addr))
63
+ sanctioned.push((0, viem_1.getAddress)(addr));
64
+ }
65
+ if (sanctioned.length > 0) {
66
+ throw new errors_js_1.AddressScreeningError("Transaction involves sanctioned addresses", sanctioned);
67
+ }
68
+ // 2. Chainalysis (optional).
69
+ if (!chainalysisApiKey) {
70
+ logger?.warn("Chainalysis screening skipped: no API key configured");
71
+ return;
72
+ }
73
+ const results = await Promise.allSettled([...addressesToScreen].map(async (addr) => {
74
+ const verdict = await screenWithChainalysis(addr, chainalysisApiKey, signal);
75
+ return { addr, verdict };
76
+ }));
77
+ const flagged = [];
78
+ for (const r of results) {
79
+ if (r.status === "fulfilled") {
80
+ if (r.value.verdict === "severe")
81
+ flagged.push((0, viem_1.getAddress)(r.value.addr));
82
+ }
83
+ else {
84
+ // Transport fail-open: registration POST couldn't even complete, so the
85
+ // address was never registered with Chainalysis and the package's
86
+ // fail-open posture applies.
87
+ logger?.warn("Chainalysis screening failed (fail-open)", {
88
+ error: r.reason instanceof Error ? r.reason.message : String(r.reason),
89
+ });
90
+ }
91
+ }
92
+ if (flagged.length > 0) {
93
+ throw new errors_js_1.AddressScreeningError("Transaction involves addresses flagged as Severe risk", flagged);
94
+ }
95
+ }
96
+ /**
97
+ * Run the Chainalysis Entity flow for one address.
98
+ *
99
+ * Returns `'severe'` or `'clean'`. Throws only if the initial registration POST
100
+ * fails before any server-side side effect — then the outer handler treats the
101
+ * rejection as fail-open. After registration succeeds, every error path returns
102
+ * `'severe'` so the check fails closed.
103
+ *
104
+ * A SINGLE deadline covers both the POST and the GET, keeping the per-address
105
+ * wall-time budget at `CHAINALYSIS_TIMEOUT_MS` (5 s) rather than doubling it.
106
+ */
107
+ async function screenWithChainalysis(address, apiKey, parentSignal) {
108
+ const headers = {
109
+ "Content-Type": "application/json",
110
+ token: apiKey,
111
+ };
112
+ const deadline = AbortSignal.timeout(CHAINALYSIS_TIMEOUT_MS);
113
+ const signal = parentSignal
114
+ ? AbortSignal.any([parentSignal, deadline])
115
+ : deadline;
116
+ // POST to register. Pre-registration failure → fail-open via thrown Error.
117
+ const registration = await fetch(CHAINALYSIS_API_URL, {
118
+ headers,
119
+ method: "POST",
120
+ body: JSON.stringify({ address }),
121
+ signal,
122
+ });
123
+ if (!registration.ok) {
124
+ throw new Error(`Chainalysis registration failed: ${registration.status} ${registration.statusText}`);
125
+ }
126
+ // From here on we've already told Chainalysis about the address — fail closed.
127
+ let response;
128
+ try {
129
+ response = await fetch(`${CHAINALYSIS_API_URL}/${encodeURIComponent(address)}`, {
130
+ method: "GET",
131
+ headers,
132
+ signal,
133
+ });
134
+ }
135
+ catch {
136
+ return "severe";
137
+ }
138
+ if (!response.ok)
139
+ return "severe";
140
+ let parsed;
141
+ try {
142
+ parsed = chainalysisEntitySchema.parse(await response.json());
143
+ }
144
+ catch {
145
+ return "severe";
146
+ }
147
+ if (parsed.risk === null)
148
+ return "clean";
149
+ if (NON_SEVERE_TIERS.has(parsed.risk))
150
+ return "clean";
151
+ return "severe";
152
+ }
@@ -0,0 +1,2 @@
1
+ export { resolveAuthorizations } from "./resolve.js";
2
+ export { validateAuthorizations } from "./validate.js";
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateAuthorizations = exports.resolveAuthorizations = void 0;
4
+ var resolve_js_1 = require("./resolve.js");
5
+ Object.defineProperty(exports, "resolveAuthorizations", { enumerable: true, get: function () { return resolve_js_1.resolveAuthorizations; } });
6
+ var validate_js_1 = require("./validate.js");
7
+ Object.defineProperty(exports, "validateAuthorizations", { enumerable: true, get: function () { return validate_js_1.validateAuthorizations; } });
@@ -0,0 +1,9 @@
1
+ import { type Address } from "viem";
2
+ import type { SimulationAuthorization, SimulationTransaction } from "../../types.js";
3
+ /**
4
+ * Resolve SimulationAuthorization[] into SimulationTransaction[].
5
+ *
6
+ * - "approval" → use authorization.transaction as-is
7
+ * - "signature" → encode approve(spender, amount) on the token contract
8
+ */
9
+ export declare function resolveAuthorizations(authorizations: SimulationAuthorization[], sender: Address): SimulationTransaction[];
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAuthorizations = resolveAuthorizations;
4
+ const viem_1 = require("viem");
5
+ /**
6
+ * Resolve SimulationAuthorization[] into SimulationTransaction[].
7
+ *
8
+ * - "approval" → use authorization.transaction as-is
9
+ * - "signature" → encode approve(spender, amount) on the token contract
10
+ */
11
+ function resolveAuthorizations(authorizations, sender) {
12
+ return authorizations.map((auth) => {
13
+ if (auth.type === "approval") {
14
+ return auth.transaction;
15
+ }
16
+ const amount = auth.amount ?? viem_1.maxUint256;
17
+ const data = (0, viem_1.encodeFunctionData)({
18
+ abi: viem_1.erc20Abi,
19
+ functionName: "approve",
20
+ args: [auth.spender, amount],
21
+ });
22
+ return {
23
+ from: sender,
24
+ to: auth.token,
25
+ data,
26
+ value: 0n,
27
+ };
28
+ });
29
+ }
@@ -0,0 +1,6 @@
1
+ import type { SimulationAuthorization } from "../../types.js";
2
+ /**
3
+ * Validate all signature authorizations have non-zero token and spender.
4
+ * Returns an array of error strings (empty if valid).
5
+ */
6
+ export declare function validateAuthorizations(authorizations: SimulationAuthorization[]): string[];
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateAuthorizations = validateAuthorizations;
4
+ const viem_1 = require("viem");
5
+ /**
6
+ * Validate all signature authorizations have non-zero token and spender.
7
+ * Returns an array of error strings (empty if valid).
8
+ */
9
+ function validateAuthorizations(authorizations) {
10
+ const errors = [];
11
+ for (let i = 0; i < authorizations.length; i++) {
12
+ const auth = authorizations[i];
13
+ if (auth.type === "signature") {
14
+ if (!auth.token || auth.token === viem_1.zeroAddress) {
15
+ errors.push(`authorizations[${i}]: signature authorization has zero-address token`);
16
+ }
17
+ if (!auth.spender || auth.spender === viem_1.zeroAddress) {
18
+ errors.push(`authorizations[${i}]: signature authorization has zero-address spender`);
19
+ }
20
+ }
21
+ }
22
+ return errors;
23
+ }
@@ -0,0 +1,15 @@
1
+ import { type BlockTag } from "viem";
2
+ import type { RawSimulationResult, SimulationTransaction } from "../../types.js";
3
+ /**
4
+ * Simulate transactions using eth_simulateV1 (viem simulateCalls).
5
+ *
6
+ * Uses stateOverride to set the user's ETH balance high enough to avoid
7
+ * false "insufficient balance for gas" reverts.
8
+ */
9
+ export declare function simulateV1(params: {
10
+ rpcUrl: string;
11
+ chainId: number;
12
+ transactions: SimulationTransaction[];
13
+ blockNumber?: bigint | BlockTag;
14
+ signal?: AbortSignal;
15
+ }): Promise<RawSimulationResult>;