@agirails/sdk 2.5.3 → 2.5.5
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/dist/ACTPClient.d.ts +18 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +72 -23
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +15 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +33 -4
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/StandardAdapter.d.ts +20 -3
- package/dist/adapters/StandardAdapter.d.ts.map +1 -1
- package/dist/adapters/StandardAdapter.js +90 -12
- package/dist/adapters/StandardAdapter.js.map +1 -1
- package/dist/cli/commands/publish.js +16 -4
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/register.js +16 -4
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/tx.js +31 -3
- package/dist/cli/commands/tx.js.map +1 -1
- package/dist/config/networks.d.ts +10 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +31 -22
- package/dist/config/networks.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +2 -1
- package/dist/level0/request.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +11 -5
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/utils/IPFSClient.d.ts +3 -1
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +27 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts +11 -1
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +84 -19
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/IWalletProvider.d.ts +34 -0
- package/dist/wallet/IWalletProvider.d.ts.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +128 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +248 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +26 -1
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +140 -6
- package/dist/wallet/aa/DualNonceManager.js.map +1 -1
- package/package.json +3 -6
- package/src/ACTPClient.ts +0 -1579
- package/src/abi/ACTPKernel.json +0 -1356
- package/src/abi/AgentRegistry.json +0 -915
- package/src/abi/ERC20.json +0 -40
- package/src/abi/EscrowVault.json +0 -134
- package/src/abi/IdentityRegistry.json +0 -316
- package/src/adapters/AdapterRegistry.ts +0 -173
- package/src/adapters/AdapterRouter.ts +0 -416
- package/src/adapters/BaseAdapter.ts +0 -498
- package/src/adapters/BasicAdapter.ts +0 -514
- package/src/adapters/IAdapter.ts +0 -292
- package/src/adapters/StandardAdapter.ts +0 -555
- package/src/adapters/X402Adapter.ts +0 -731
- package/src/adapters/index.ts +0 -60
- package/src/builders/DeliveryProofBuilder.ts +0 -327
- package/src/builders/QuoteBuilder.ts +0 -483
- package/src/builders/index.ts +0 -17
- package/src/cli/commands/balance.ts +0 -110
- package/src/cli/commands/batch.ts +0 -487
- package/src/cli/commands/config.ts +0 -231
- package/src/cli/commands/deploy-check.ts +0 -364
- package/src/cli/commands/deploy-env.ts +0 -120
- package/src/cli/commands/diff.ts +0 -141
- package/src/cli/commands/init.ts +0 -469
- package/src/cli/commands/mint.ts +0 -116
- package/src/cli/commands/pay.ts +0 -113
- package/src/cli/commands/publish.ts +0 -475
- package/src/cli/commands/pull.ts +0 -124
- package/src/cli/commands/register.ts +0 -247
- package/src/cli/commands/simulate.ts +0 -345
- package/src/cli/commands/time.ts +0 -302
- package/src/cli/commands/tx.ts +0 -448
- package/src/cli/commands/watch.ts +0 -211
- package/src/cli/index.ts +0 -134
- package/src/cli/utils/client.ts +0 -252
- package/src/cli/utils/config.ts +0 -389
- package/src/cli/utils/output.ts +0 -465
- package/src/cli/utils/wallet.ts +0 -109
- package/src/config/agirailsmd.ts +0 -262
- package/src/config/networks.ts +0 -275
- package/src/config/pendingPublish.ts +0 -237
- package/src/config/publishPipeline.ts +0 -359
- package/src/config/syncOperations.ts +0 -279
- package/src/erc8004/ERC8004Bridge.ts +0 -462
- package/src/erc8004/ReputationReporter.ts +0 -468
- package/src/erc8004/index.ts +0 -61
- package/src/errors/index.ts +0 -427
- package/src/index.ts +0 -364
- package/src/level0/Provider.ts +0 -117
- package/src/level0/ServiceDirectory.ts +0 -131
- package/src/level0/index.ts +0 -10
- package/src/level0/provide.ts +0 -132
- package/src/level0/request.ts +0 -432
- package/src/level1/Agent.ts +0 -1426
- package/src/level1/index.ts +0 -10
- package/src/level1/pricing/PriceCalculator.ts +0 -255
- package/src/level1/pricing/PricingStrategy.ts +0 -198
- package/src/level1/types/Job.ts +0 -179
- package/src/level1/types/Options.ts +0 -291
- package/src/level1/types/index.ts +0 -8
- package/src/protocol/ACTPKernel.ts +0 -808
- package/src/protocol/AgentRegistry.ts +0 -559
- package/src/protocol/DIDManager.ts +0 -629
- package/src/protocol/DIDResolver.ts +0 -554
- package/src/protocol/EASHelper.ts +0 -378
- package/src/protocol/EscrowVault.ts +0 -255
- package/src/protocol/EventMonitor.ts +0 -204
- package/src/protocol/MessageSigner.ts +0 -510
- package/src/protocol/ProofGenerator.ts +0 -339
- package/src/protocol/QuoteBuilder.ts +0 -15
- package/src/registry/AgentRegistryClient.ts +0 -202
- package/src/runtime/BlockchainRuntime.ts +0 -1015
- package/src/runtime/IACTPRuntime.ts +0 -306
- package/src/runtime/MockRuntime.ts +0 -1298
- package/src/runtime/MockStateManager.ts +0 -577
- package/src/runtime/index.ts +0 -25
- package/src/runtime/types/MockState.ts +0 -237
- package/src/storage/ArchiveBundleBuilder.ts +0 -561
- package/src/storage/ArweaveClient.ts +0 -946
- package/src/storage/FilebaseClient.ts +0 -790
- package/src/storage/index.ts +0 -96
- package/src/storage/types.ts +0 -348
- package/src/types/adapter.ts +0 -310
- package/src/types/agent.ts +0 -79
- package/src/types/did.ts +0 -223
- package/src/types/eip712.ts +0 -175
- package/src/types/erc8004.ts +0 -293
- package/src/types/escrow.ts +0 -27
- package/src/types/index.ts +0 -17
- package/src/types/message.ts +0 -145
- package/src/types/state.ts +0 -87
- package/src/types/transaction.ts +0 -69
- package/src/types/x402.ts +0 -251
- package/src/utils/ErrorRecoveryGuide.ts +0 -676
- package/src/utils/Helpers.ts +0 -688
- package/src/utils/IPFSClient.ts +0 -368
- package/src/utils/Logger.ts +0 -484
- package/src/utils/NonceManager.ts +0 -591
- package/src/utils/RateLimiter.ts +0 -534
- package/src/utils/ReceivedNonceTracker.ts +0 -567
- package/src/utils/SDKLifecycle.ts +0 -416
- package/src/utils/SecureNonce.ts +0 -78
- package/src/utils/Semaphore.ts +0 -276
- package/src/utils/UsedAttestationTracker.ts +0 -385
- package/src/utils/canonicalJson.ts +0 -38
- package/src/utils/circuitBreaker.ts +0 -324
- package/src/utils/computeTypeHash.ts +0 -48
- package/src/utils/fsSafe.ts +0 -80
- package/src/utils/index.ts +0 -80
- package/src/utils/retry.ts +0 -364
- package/src/utils/security.ts +0 -418
- package/src/utils/validation.ts +0 -540
- package/src/wallet/AutoWalletProvider.ts +0 -299
- package/src/wallet/EOAWalletProvider.ts +0 -69
- package/src/wallet/IWalletProvider.ts +0 -135
- package/src/wallet/aa/BundlerClient.ts +0 -274
- package/src/wallet/aa/DualNonceManager.ts +0 -173
- package/src/wallet/aa/PaymasterClient.ts +0 -174
- package/src/wallet/aa/TransactionBatcher.ts +0 -353
- package/src/wallet/aa/UserOpBuilder.ts +0 -246
- package/src/wallet/aa/constants.ts +0 -60
- package/src/wallet/keystore.ts +0 -240
|
@@ -1,577 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MockStateManager - Persistent state management for Mock Mode
|
|
3
|
-
*
|
|
4
|
-
* Responsible for persisting mock blockchain state to disk, enabling
|
|
5
|
-
* state sharing across CLI commands, SDK library usage, and Dashboard.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - File-based JSON persistence in `.actp/mock-state.json`
|
|
9
|
-
* - Atomic file operations (write to temp file, then rename)
|
|
10
|
-
* - File locking to prevent concurrent access corruption
|
|
11
|
-
* - Error recovery for corrupted state files
|
|
12
|
-
*
|
|
13
|
-
* @module runtime/MockStateManager
|
|
14
|
-
* @see ADR-001 (Mock State Persistence Strategy)
|
|
15
|
-
* @see MOCK_STATE_MANAGER_SPEC.md
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import * as fs from 'fs';
|
|
19
|
-
import * as path from 'path';
|
|
20
|
-
import lockfile from 'proper-lockfile';
|
|
21
|
-
import { MockState, MOCK_STATE_DEFAULTS } from './types/MockState';
|
|
22
|
-
import { assertSafeFileForRead, ensureSafeDir } from '../utils/fsSafe';
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Error thrown when mock state file is corrupted.
|
|
26
|
-
*/
|
|
27
|
-
export class MockStateCorruptedError extends Error {
|
|
28
|
-
public readonly statePath: string;
|
|
29
|
-
|
|
30
|
-
constructor(statePath: string, cause?: Error) {
|
|
31
|
-
super(
|
|
32
|
-
`Mock state file corrupted: ${statePath}\n` +
|
|
33
|
-
`Delete it manually or run: actp mock reset\n` +
|
|
34
|
-
(cause ? `Cause: ${cause.message}` : '')
|
|
35
|
-
);
|
|
36
|
-
this.name = 'MockStateCorruptedError';
|
|
37
|
-
this.statePath = statePath;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Error thrown when state version is unsupported.
|
|
43
|
-
*/
|
|
44
|
-
export class MockStateVersionError extends Error {
|
|
45
|
-
public readonly version: string;
|
|
46
|
-
public readonly supportedVersion: string;
|
|
47
|
-
|
|
48
|
-
constructor(version: string, supportedVersion: string = MOCK_STATE_DEFAULTS.VERSION) {
|
|
49
|
-
super(
|
|
50
|
-
`Unsupported state version: ${version}\n` +
|
|
51
|
-
`Supported version: ${supportedVersion}\n` +
|
|
52
|
-
`Run: actp mock reset to create new state file`
|
|
53
|
-
);
|
|
54
|
-
this.name = 'MockStateVersionError';
|
|
55
|
-
this.version = version;
|
|
56
|
-
this.supportedVersion = supportedVersion;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Error thrown when lock cannot be acquired.
|
|
62
|
-
*/
|
|
63
|
-
export class MockStateLockError extends Error {
|
|
64
|
-
public readonly statePath: string;
|
|
65
|
-
public readonly cause?: Error;
|
|
66
|
-
|
|
67
|
-
constructor(statePath: string, cause?: Error) {
|
|
68
|
-
super(
|
|
69
|
-
`Could not acquire lock on mock state: ${statePath}\n` +
|
|
70
|
-
`Another ACTP process may be running.\n` +
|
|
71
|
-
`Please wait a moment and try again.` +
|
|
72
|
-
(cause ? `\nCause: ${cause.message}` : '')
|
|
73
|
-
);
|
|
74
|
-
this.name = 'MockStateLockError';
|
|
75
|
-
this.statePath = statePath;
|
|
76
|
-
this.cause = cause;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Lock options for proper-lockfile.
|
|
82
|
-
* - 5 retries with exponential backoff (100ms -> 1000ms)
|
|
83
|
-
* - 10 second stale lock detection (handles process crashes)
|
|
84
|
-
*/
|
|
85
|
-
const LOCK_OPTIONS: lockfile.LockOptions = {
|
|
86
|
-
retries: {
|
|
87
|
-
retries: 5,
|
|
88
|
-
minTimeout: 100,
|
|
89
|
-
maxTimeout: 1000,
|
|
90
|
-
},
|
|
91
|
-
stale: 10000, // 10 seconds
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Maximum allowed state file size (10 MB).
|
|
96
|
-
* Prevents disk exhaustion from runaway state growth.
|
|
97
|
-
*
|
|
98
|
-
* Note: For states approaching this limit, JSON.stringify/parse may
|
|
99
|
-
* temporarily use ~3x the file size in memory (~30 MB for 10 MB state).
|
|
100
|
-
* This is acceptable for a local development tool with <1000 transactions.
|
|
101
|
-
*/
|
|
102
|
-
const MAX_STATE_FILE_SIZE = 10 * 1024 * 1024;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Maximum nesting depth for JSON parsing.
|
|
106
|
-
* Prevents DoS attacks via deeply nested JSON structures.
|
|
107
|
-
*
|
|
108
|
-
* SECURITY: Deeply nested JSON can cause:
|
|
109
|
-
* - Stack overflow during parsing
|
|
110
|
-
* - Excessive CPU usage during traversal
|
|
111
|
-
* - Memory exhaustion from recursion
|
|
112
|
-
*/
|
|
113
|
-
const MAX_NESTING_DEPTH = 100;
|
|
114
|
-
|
|
115
|
-
// ============================================================================
|
|
116
|
-
// Security: Path Sanitization and JSON Depth Check
|
|
117
|
-
// ============================================================================
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Sanitize a file path for safe display in error messages.
|
|
121
|
-
*
|
|
122
|
-
* SECURITY: Replaces home directory with ~ to prevent information disclosure.
|
|
123
|
-
*
|
|
124
|
-
* @param fullPath - The full file path to sanitize
|
|
125
|
-
* @returns Sanitized path safe for display
|
|
126
|
-
*/
|
|
127
|
-
function sanitizePath(fullPath: string): string {
|
|
128
|
-
// Import os dynamically to get home directory
|
|
129
|
-
const home = process.env.HOME || process.env.USERPROFILE || '/';
|
|
130
|
-
return fullPath.replace(home, '~');
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Check the nesting depth of a parsed JSON object.
|
|
135
|
-
*
|
|
136
|
-
* SECURITY: Prevents DoS via deeply nested JSON structures that can cause:
|
|
137
|
-
* - Stack overflow
|
|
138
|
-
* - Excessive CPU usage
|
|
139
|
-
* - Memory exhaustion
|
|
140
|
-
*
|
|
141
|
-
* @param obj - The object to check
|
|
142
|
-
* @param maxDepth - Maximum allowed nesting depth
|
|
143
|
-
* @param currentDepth - Current depth (used for recursion)
|
|
144
|
-
* @throws Error if object exceeds maximum nesting depth
|
|
145
|
-
*/
|
|
146
|
-
function checkNestingDepth(
|
|
147
|
-
obj: unknown,
|
|
148
|
-
maxDepth: number = MAX_NESTING_DEPTH,
|
|
149
|
-
currentDepth: number = 0
|
|
150
|
-
): void {
|
|
151
|
-
if (currentDepth > maxDepth) {
|
|
152
|
-
throw new Error(
|
|
153
|
-
`State file exceeds maximum nesting depth of ${maxDepth}. ` +
|
|
154
|
-
'This may indicate a corrupted or malicious state file.'
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (obj !== null && typeof obj === 'object') {
|
|
159
|
-
// Check arrays and objects
|
|
160
|
-
const values = Array.isArray(obj) ? obj : Object.values(obj);
|
|
161
|
-
for (const value of values) {
|
|
162
|
-
checkNestingDepth(value, maxDepth, currentDepth + 1);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* MockStateManager handles persistence of mock blockchain state.
|
|
169
|
-
*
|
|
170
|
-
* State is stored in `.actp/mock-state.json` within the project root.
|
|
171
|
-
* Uses file locking to prevent corruption from concurrent access.
|
|
172
|
-
*
|
|
173
|
-
* @example
|
|
174
|
-
* ```typescript
|
|
175
|
-
* const manager = new MockStateManager();
|
|
176
|
-
*
|
|
177
|
-
* // Read-only access (no lock)
|
|
178
|
-
* const state = manager.loadState();
|
|
179
|
-
* console.log('Transactions:', Object.keys(state.transactions).length);
|
|
180
|
-
*
|
|
181
|
-
* // Read-modify-write with lock
|
|
182
|
-
* const txId = await manager.withLock(async (state) => {
|
|
183
|
-
* const txId = generateId();
|
|
184
|
-
* state.transactions[txId] = { ... };
|
|
185
|
-
* return txId;
|
|
186
|
-
* });
|
|
187
|
-
* ```
|
|
188
|
-
*/
|
|
189
|
-
export class MockStateManager {
|
|
190
|
-
/** Path to the state JSON file */
|
|
191
|
-
private readonly statePath: string;
|
|
192
|
-
|
|
193
|
-
/** Path to the .actp directory */
|
|
194
|
-
private readonly actpDir: string;
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Creates a new MockStateManager instance.
|
|
198
|
-
*
|
|
199
|
-
* @param projectRoot - Root directory for `.actp/` folder.
|
|
200
|
-
* Defaults to current working directory.
|
|
201
|
-
*
|
|
202
|
-
* @example
|
|
203
|
-
* ```typescript
|
|
204
|
-
* // Use current directory
|
|
205
|
-
* const manager = new MockStateManager();
|
|
206
|
-
*
|
|
207
|
-
* // Use specific project root
|
|
208
|
-
* const manager = new MockStateManager('/path/to/project');
|
|
209
|
-
* ```
|
|
210
|
-
*/
|
|
211
|
-
constructor(projectRoot: string = process.cwd()) {
|
|
212
|
-
this.actpDir = path.join(projectRoot, '.actp');
|
|
213
|
-
this.statePath = path.join(this.actpDir, 'mock-state.json');
|
|
214
|
-
|
|
215
|
-
// Ensure .actp directory exists
|
|
216
|
-
this.ensureDirectory();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Ensures the .actp directory exists.
|
|
221
|
-
* Creates it with secure permissions if missing.
|
|
222
|
-
*/
|
|
223
|
-
private ensureDirectory(): void {
|
|
224
|
-
ensureSafeDir(this.actpDir, 0o755);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Loads state from disk.
|
|
229
|
-
*
|
|
230
|
-
* Returns default state if file doesn't exist (first run).
|
|
231
|
-
* Validates version compatibility and file size limits.
|
|
232
|
-
*
|
|
233
|
-
* SECURITY: Validates nesting depth to prevent DoS attacks.
|
|
234
|
-
*
|
|
235
|
-
* @returns The current mock state
|
|
236
|
-
*
|
|
237
|
-
* @throws {MockStateCorruptedError} If state file contains invalid JSON
|
|
238
|
-
* @throws {MockStateVersionError} If state version is not supported
|
|
239
|
-
* @throws {Error} If file exceeds size limit or other I/O errors
|
|
240
|
-
*
|
|
241
|
-
* @example
|
|
242
|
-
* ```typescript
|
|
243
|
-
* const state = manager.loadState();
|
|
244
|
-
* console.log('Current time:', state.blockchain.currentTime);
|
|
245
|
-
* ```
|
|
246
|
-
*/
|
|
247
|
-
loadState(): MockState {
|
|
248
|
-
// If file doesn't exist, return default state
|
|
249
|
-
if (!fs.existsSync(this.statePath)) {
|
|
250
|
-
return this.getDefaultState();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// SECURITY: Refuse to read from symlinked state files
|
|
254
|
-
assertSafeFileForRead(this.statePath);
|
|
255
|
-
|
|
256
|
-
// Check file size limit
|
|
257
|
-
const stats = fs.statSync(this.statePath);
|
|
258
|
-
if (stats.size > MAX_STATE_FILE_SIZE) {
|
|
259
|
-
throw new Error(
|
|
260
|
-
`Mock state file exceeds ${MAX_STATE_FILE_SIZE / 1024 / 1024} MB limit.\n` +
|
|
261
|
-
`Run: actp mock compact or actp mock reset`
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Read and parse file
|
|
266
|
-
let raw: string;
|
|
267
|
-
try {
|
|
268
|
-
raw = fs.readFileSync(this.statePath, 'utf-8');
|
|
269
|
-
} catch (error) {
|
|
270
|
-
// SECURITY: Sanitize path in error message
|
|
271
|
-
throw new Error(
|
|
272
|
-
`Failed to read mock state file: ${sanitizePath(this.statePath)}\n` +
|
|
273
|
-
`Error: ${(error as Error).message}`
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Parse JSON
|
|
278
|
-
let state: MockState;
|
|
279
|
-
try {
|
|
280
|
-
state = JSON.parse(raw);
|
|
281
|
-
} catch (error) {
|
|
282
|
-
// SECURITY: Sanitize path in error message
|
|
283
|
-
throw new MockStateCorruptedError(sanitizePath(this.statePath), error as Error);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// SECURITY FIX: Check nesting depth to prevent DoS via deeply nested JSON
|
|
287
|
-
try {
|
|
288
|
-
checkNestingDepth(state);
|
|
289
|
-
} catch (error) {
|
|
290
|
-
throw new MockStateCorruptedError(
|
|
291
|
-
sanitizePath(this.statePath),
|
|
292
|
-
error as Error
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Validate version
|
|
297
|
-
if (state.version !== MOCK_STATE_DEFAULTS.VERSION) {
|
|
298
|
-
throw new MockStateVersionError(state.version);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Basic schema validation
|
|
302
|
-
if (
|
|
303
|
-
typeof state.mode !== 'string' ||
|
|
304
|
-
typeof state.blockchain !== 'object' ||
|
|
305
|
-
typeof state.transactions !== 'object' ||
|
|
306
|
-
typeof state.escrows !== 'object' ||
|
|
307
|
-
typeof state.accounts !== 'object'
|
|
308
|
-
) {
|
|
309
|
-
throw new MockStateCorruptedError(sanitizePath(this.statePath));
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return state;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Saves state to disk atomically.
|
|
317
|
-
*
|
|
318
|
-
* Uses write-to-temp-then-rename pattern to ensure atomicity:
|
|
319
|
-
* 1. Write state to `.tmp` file
|
|
320
|
-
* 2. Rename `.tmp` to final path (atomic on POSIX)
|
|
321
|
-
*
|
|
322
|
-
* This prevents corruption if process crashes during write.
|
|
323
|
-
*
|
|
324
|
-
* @param state - The state to save
|
|
325
|
-
*
|
|
326
|
-
* @throws {Error} If write fails (disk full, permissions, etc.)
|
|
327
|
-
*
|
|
328
|
-
* @example
|
|
329
|
-
* ```typescript
|
|
330
|
-
* const state = manager.loadState();
|
|
331
|
-
* state.blockchain.currentTime += 3600; // Advance 1 hour
|
|
332
|
-
* manager.saveState(state);
|
|
333
|
-
* ```
|
|
334
|
-
*/
|
|
335
|
-
saveState(state: MockState): void {
|
|
336
|
-
// Ensure directory exists (may have been deleted)
|
|
337
|
-
this.ensureDirectory();
|
|
338
|
-
|
|
339
|
-
const tempPath = `${this.statePath}.tmp`;
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
// Prevent clobbering via pre-created temp symlink/file
|
|
343
|
-
if (fs.existsSync(tempPath)) {
|
|
344
|
-
fs.unlinkSync(tempPath);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Write to temp file first (pretty-printed for human readability)
|
|
348
|
-
// BigInt replacer: ethers v6 returns BigInt from contracts; JSON.stringify can't handle them natively
|
|
349
|
-
const json = JSON.stringify(state, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2);
|
|
350
|
-
fs.writeFileSync(tempPath, json, {
|
|
351
|
-
encoding: 'utf-8',
|
|
352
|
-
mode: 0o644,
|
|
353
|
-
flag: 'wx', // exclusive create: do not follow existing symlink
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// Rename atomically (POSIX rename is atomic)
|
|
357
|
-
fs.renameSync(tempPath, this.statePath);
|
|
358
|
-
} catch (error) {
|
|
359
|
-
// Clean up temp file if it exists
|
|
360
|
-
if (fs.existsSync(tempPath)) {
|
|
361
|
-
try {
|
|
362
|
-
fs.unlinkSync(tempPath);
|
|
363
|
-
} catch {
|
|
364
|
-
// Ignore cleanup errors
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
throw new Error(
|
|
369
|
-
`Failed to save mock state: ${(error as Error).message}\n` +
|
|
370
|
-
`Path: ${sanitizePath(this.statePath)}`
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Executes an operation with exclusive file lock.
|
|
377
|
-
*
|
|
378
|
-
* Provides read-modify-write semantics with concurrency protection:
|
|
379
|
-
* 1. Acquires exclusive lock on state file
|
|
380
|
-
* 2. Loads current state
|
|
381
|
-
* 3. Executes operation (may modify state)
|
|
382
|
-
* 4. Saves updated state
|
|
383
|
-
* 5. Releases lock
|
|
384
|
-
*
|
|
385
|
-
* Lock is always released, even if operation throws.
|
|
386
|
-
*
|
|
387
|
-
* @typeParam T - Return type of the operation
|
|
388
|
-
* @param operation - Function that receives state and returns result.
|
|
389
|
-
* Can be sync or async. May modify state object.
|
|
390
|
-
*
|
|
391
|
-
* @returns Promise resolving to operation's return value
|
|
392
|
-
*
|
|
393
|
-
* @throws {MockStateLockError} If lock cannot be acquired after retries
|
|
394
|
-
* @throws {Error} If operation throws or save fails
|
|
395
|
-
*
|
|
396
|
-
* @example
|
|
397
|
-
* ```typescript
|
|
398
|
-
* // Create transaction with lock
|
|
399
|
-
* const txId = await manager.withLock(async (state) => {
|
|
400
|
-
* const txId = '0x' + crypto.randomBytes(32).toString('hex');
|
|
401
|
-
* state.transactions[txId] = {
|
|
402
|
-
* id: txId,
|
|
403
|
-
* state: 'INITIATED',
|
|
404
|
-
* // ...
|
|
405
|
-
* };
|
|
406
|
-
* return txId;
|
|
407
|
-
* });
|
|
408
|
-
* ```
|
|
409
|
-
*/
|
|
410
|
-
async withLock<T>(operation: (state: MockState) => T | Promise<T>): Promise<T> {
|
|
411
|
-
// Ensure state file exists before locking
|
|
412
|
-
// (proper-lockfile requires the file to exist)
|
|
413
|
-
if (!fs.existsSync(this.statePath)) {
|
|
414
|
-
this.saveState(this.getDefaultState());
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// SECURITY: Refuse to lock/use symlinked state files
|
|
418
|
-
assertSafeFileForRead(this.statePath);
|
|
419
|
-
|
|
420
|
-
let release: (() => Promise<void>) | null = null;
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
// Acquire exclusive lock
|
|
424
|
-
release = await lockfile.lock(this.statePath, LOCK_OPTIONS);
|
|
425
|
-
|
|
426
|
-
// Load current state
|
|
427
|
-
const state = this.loadState();
|
|
428
|
-
|
|
429
|
-
// Execute operation (may be async)
|
|
430
|
-
const result = await operation(state);
|
|
431
|
-
|
|
432
|
-
// Save modified state
|
|
433
|
-
this.saveState(state);
|
|
434
|
-
|
|
435
|
-
return result;
|
|
436
|
-
} catch (error) {
|
|
437
|
-
// Transform lock errors into friendlier messages
|
|
438
|
-
if (
|
|
439
|
-
error instanceof Error &&
|
|
440
|
-
(error.message.includes('ELOCKED') || error.message.includes('lock'))
|
|
441
|
-
) {
|
|
442
|
-
throw new MockStateLockError(this.statePath, error);
|
|
443
|
-
}
|
|
444
|
-
throw error;
|
|
445
|
-
} finally {
|
|
446
|
-
// Always release lock
|
|
447
|
-
if (release) {
|
|
448
|
-
try {
|
|
449
|
-
await release();
|
|
450
|
-
} catch {
|
|
451
|
-
// Ignore release errors (lock may have expired)
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Resets state to default (fresh blockchain).
|
|
459
|
-
*
|
|
460
|
-
* Useful for starting fresh during testing or after corruption.
|
|
461
|
-
* Does not require lock (overwrites entire file).
|
|
462
|
-
*
|
|
463
|
-
* @example
|
|
464
|
-
* ```typescript
|
|
465
|
-
* // Reset to clean state
|
|
466
|
-
* manager.reset();
|
|
467
|
-
*
|
|
468
|
-
* // Verify reset
|
|
469
|
-
* const state = manager.loadState();
|
|
470
|
-
* console.log('Transactions:', Object.keys(state.transactions).length); // 0
|
|
471
|
-
* ```
|
|
472
|
-
*/
|
|
473
|
-
reset(): void {
|
|
474
|
-
const defaultState = this.getDefaultState();
|
|
475
|
-
this.saveState(defaultState);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Checks if mock mode is initialized (state file exists).
|
|
480
|
-
*
|
|
481
|
-
* @returns true if state file exists, false otherwise
|
|
482
|
-
*
|
|
483
|
-
* @example
|
|
484
|
-
* ```typescript
|
|
485
|
-
* if (!manager.exists()) {
|
|
486
|
-
* console.log('Run: actp init to initialize mock mode');
|
|
487
|
-
* }
|
|
488
|
-
* ```
|
|
489
|
-
*/
|
|
490
|
-
exists(): boolean {
|
|
491
|
-
return fs.existsSync(this.statePath);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Gets the path to the state file.
|
|
496
|
-
*
|
|
497
|
-
* @returns Absolute path to mock-state.json
|
|
498
|
-
*/
|
|
499
|
-
getStatePath(): string {
|
|
500
|
-
return this.statePath;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Gets the path to the .actp directory.
|
|
505
|
-
*
|
|
506
|
-
* @returns Absolute path to .actp directory
|
|
507
|
-
*/
|
|
508
|
-
getActpDir(): string {
|
|
509
|
-
return this.actpDir;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Creates default/initial mock state.
|
|
514
|
-
*
|
|
515
|
-
* Used when state file doesn't exist or after reset.
|
|
516
|
-
* Initializes blockchain with current timestamp.
|
|
517
|
-
*
|
|
518
|
-
* @returns Fresh mock state with empty transactions/escrows/accounts
|
|
519
|
-
*/
|
|
520
|
-
getDefaultState(): MockState {
|
|
521
|
-
return {
|
|
522
|
-
version: MOCK_STATE_DEFAULTS.VERSION,
|
|
523
|
-
mode: 'mock',
|
|
524
|
-
blockchain: {
|
|
525
|
-
currentTime: Math.floor(Date.now() / 1000),
|
|
526
|
-
blockNumber: MOCK_STATE_DEFAULTS.INITIAL_BLOCK_NUMBER,
|
|
527
|
-
chainId: MOCK_STATE_DEFAULTS.CHAIN_ID,
|
|
528
|
-
blockTime: MOCK_STATE_DEFAULTS.BLOCK_TIME,
|
|
529
|
-
},
|
|
530
|
-
transactions: {},
|
|
531
|
-
escrows: {},
|
|
532
|
-
accounts: {},
|
|
533
|
-
// SECURITY FIX (L-4): Initialize events array for persistence
|
|
534
|
-
events: [],
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Deletes the state file and .actp directory if empty.
|
|
540
|
-
*
|
|
541
|
-
* Used for cleanup during tests or uninitialization.
|
|
542
|
-
*
|
|
543
|
-
* @param force - If true, delete .actp directory even if not empty
|
|
544
|
-
*
|
|
545
|
-
* @example
|
|
546
|
-
* ```typescript
|
|
547
|
-
* // Clean up after tests
|
|
548
|
-
* manager.destroy();
|
|
549
|
-
* ```
|
|
550
|
-
*/
|
|
551
|
-
destroy(force: boolean = false): void {
|
|
552
|
-
// Remove state file
|
|
553
|
-
if (fs.existsSync(this.statePath)) {
|
|
554
|
-
fs.unlinkSync(this.statePath);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Remove temp file if exists
|
|
558
|
-
const tempPath = `${this.statePath}.tmp`;
|
|
559
|
-
if (fs.existsSync(tempPath)) {
|
|
560
|
-
fs.unlinkSync(tempPath);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Remove lock file if exists (created by proper-lockfile)
|
|
564
|
-
const lockPath = `${this.statePath}.lock`;
|
|
565
|
-
if (fs.existsSync(lockPath)) {
|
|
566
|
-
fs.unlinkSync(lockPath);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// Remove .actp directory if empty or forced
|
|
570
|
-
if (fs.existsSync(this.actpDir)) {
|
|
571
|
-
const files = fs.readdirSync(this.actpDir);
|
|
572
|
-
if (files.length === 0 || force) {
|
|
573
|
-
fs.rmSync(this.actpDir, { recursive: true, force: true });
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
package/src/runtime/index.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime module exports.
|
|
3
|
-
*
|
|
4
|
-
* Provides both the runtime interface and concrete implementations.
|
|
5
|
-
*
|
|
6
|
-
* @module runtime
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export { IACTPRuntime, IMockRuntime, CreateTransactionParams } from './IACTPRuntime';
|
|
10
|
-
export { MockRuntime } from './MockRuntime';
|
|
11
|
-
export { MockStateManager } from './MockStateManager';
|
|
12
|
-
export { BlockchainRuntime, BlockchainRuntimeConfig } from './BlockchainRuntime';
|
|
13
|
-
export * from './types/MockState';
|
|
14
|
-
|
|
15
|
-
// Re-export all custom errors from MockRuntime
|
|
16
|
-
export {
|
|
17
|
-
TransactionNotFoundError,
|
|
18
|
-
InvalidStateTransitionError,
|
|
19
|
-
InsufficientBalanceError,
|
|
20
|
-
EscrowNotFoundError,
|
|
21
|
-
DeadlinePassedError,
|
|
22
|
-
ContractPausedError,
|
|
23
|
-
InvalidAmountError,
|
|
24
|
-
DisputeWindowActiveError,
|
|
25
|
-
} from './MockRuntime';
|