@agirails/sdk 2.5.3 → 2.5.4

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 (166) hide show
  1. package/dist/ACTPClient.d.ts +18 -0
  2. package/dist/ACTPClient.d.ts.map +1 -1
  3. package/dist/ACTPClient.js +67 -22
  4. package/dist/ACTPClient.js.map +1 -1
  5. package/dist/adapters/BasicAdapter.d.ts +12 -0
  6. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  7. package/dist/adapters/BasicAdapter.js +30 -4
  8. package/dist/adapters/BasicAdapter.js.map +1 -1
  9. package/dist/adapters/StandardAdapter.d.ts +20 -3
  10. package/dist/adapters/StandardAdapter.d.ts.map +1 -1
  11. package/dist/adapters/StandardAdapter.js +45 -11
  12. package/dist/adapters/StandardAdapter.js.map +1 -1
  13. package/dist/cli/commands/publish.js +16 -4
  14. package/dist/cli/commands/publish.js.map +1 -1
  15. package/dist/cli/commands/register.js +16 -4
  16. package/dist/cli/commands/register.js.map +1 -1
  17. package/dist/cli/commands/tx.js +31 -3
  18. package/dist/cli/commands/tx.js.map +1 -1
  19. package/dist/config/networks.d.ts +2 -2
  20. package/dist/config/networks.d.ts.map +1 -1
  21. package/dist/config/networks.js +27 -22
  22. package/dist/config/networks.js.map +1 -1
  23. package/dist/level0/request.d.ts.map +1 -1
  24. package/dist/level0/request.js +2 -1
  25. package/dist/level0/request.js.map +1 -1
  26. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
  27. package/dist/runtime/BlockchainRuntime.js +11 -5
  28. package/dist/runtime/BlockchainRuntime.js.map +1 -1
  29. package/dist/utils/IPFSClient.d.ts +3 -1
  30. package/dist/utils/IPFSClient.d.ts.map +1 -1
  31. package/dist/utils/IPFSClient.js +27 -7
  32. package/dist/utils/IPFSClient.js.map +1 -1
  33. package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
  34. package/dist/wallet/AutoWalletProvider.js +52 -18
  35. package/dist/wallet/AutoWalletProvider.js.map +1 -1
  36. package/dist/wallet/SmartWalletRouter.d.ts +116 -0
  37. package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
  38. package/dist/wallet/SmartWalletRouter.js +212 -0
  39. package/dist/wallet/SmartWalletRouter.js.map +1 -0
  40. package/dist/wallet/aa/DualNonceManager.d.ts +19 -0
  41. package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
  42. package/dist/wallet/aa/DualNonceManager.js +100 -5
  43. package/dist/wallet/aa/DualNonceManager.js.map +1 -1
  44. package/package.json +3 -6
  45. package/src/ACTPClient.ts +0 -1579
  46. package/src/abi/ACTPKernel.json +0 -1356
  47. package/src/abi/AgentRegistry.json +0 -915
  48. package/src/abi/ERC20.json +0 -40
  49. package/src/abi/EscrowVault.json +0 -134
  50. package/src/abi/IdentityRegistry.json +0 -316
  51. package/src/adapters/AdapterRegistry.ts +0 -173
  52. package/src/adapters/AdapterRouter.ts +0 -416
  53. package/src/adapters/BaseAdapter.ts +0 -498
  54. package/src/adapters/BasicAdapter.ts +0 -514
  55. package/src/adapters/IAdapter.ts +0 -292
  56. package/src/adapters/StandardAdapter.ts +0 -555
  57. package/src/adapters/X402Adapter.ts +0 -731
  58. package/src/adapters/index.ts +0 -60
  59. package/src/builders/DeliveryProofBuilder.ts +0 -327
  60. package/src/builders/QuoteBuilder.ts +0 -483
  61. package/src/builders/index.ts +0 -17
  62. package/src/cli/commands/balance.ts +0 -110
  63. package/src/cli/commands/batch.ts +0 -487
  64. package/src/cli/commands/config.ts +0 -231
  65. package/src/cli/commands/deploy-check.ts +0 -364
  66. package/src/cli/commands/deploy-env.ts +0 -120
  67. package/src/cli/commands/diff.ts +0 -141
  68. package/src/cli/commands/init.ts +0 -469
  69. package/src/cli/commands/mint.ts +0 -116
  70. package/src/cli/commands/pay.ts +0 -113
  71. package/src/cli/commands/publish.ts +0 -475
  72. package/src/cli/commands/pull.ts +0 -124
  73. package/src/cli/commands/register.ts +0 -247
  74. package/src/cli/commands/simulate.ts +0 -345
  75. package/src/cli/commands/time.ts +0 -302
  76. package/src/cli/commands/tx.ts +0 -448
  77. package/src/cli/commands/watch.ts +0 -211
  78. package/src/cli/index.ts +0 -134
  79. package/src/cli/utils/client.ts +0 -252
  80. package/src/cli/utils/config.ts +0 -389
  81. package/src/cli/utils/output.ts +0 -465
  82. package/src/cli/utils/wallet.ts +0 -109
  83. package/src/config/agirailsmd.ts +0 -262
  84. package/src/config/networks.ts +0 -275
  85. package/src/config/pendingPublish.ts +0 -237
  86. package/src/config/publishPipeline.ts +0 -359
  87. package/src/config/syncOperations.ts +0 -279
  88. package/src/erc8004/ERC8004Bridge.ts +0 -462
  89. package/src/erc8004/ReputationReporter.ts +0 -468
  90. package/src/erc8004/index.ts +0 -61
  91. package/src/errors/index.ts +0 -427
  92. package/src/index.ts +0 -364
  93. package/src/level0/Provider.ts +0 -117
  94. package/src/level0/ServiceDirectory.ts +0 -131
  95. package/src/level0/index.ts +0 -10
  96. package/src/level0/provide.ts +0 -132
  97. package/src/level0/request.ts +0 -432
  98. package/src/level1/Agent.ts +0 -1426
  99. package/src/level1/index.ts +0 -10
  100. package/src/level1/pricing/PriceCalculator.ts +0 -255
  101. package/src/level1/pricing/PricingStrategy.ts +0 -198
  102. package/src/level1/types/Job.ts +0 -179
  103. package/src/level1/types/Options.ts +0 -291
  104. package/src/level1/types/index.ts +0 -8
  105. package/src/protocol/ACTPKernel.ts +0 -808
  106. package/src/protocol/AgentRegistry.ts +0 -559
  107. package/src/protocol/DIDManager.ts +0 -629
  108. package/src/protocol/DIDResolver.ts +0 -554
  109. package/src/protocol/EASHelper.ts +0 -378
  110. package/src/protocol/EscrowVault.ts +0 -255
  111. package/src/protocol/EventMonitor.ts +0 -204
  112. package/src/protocol/MessageSigner.ts +0 -510
  113. package/src/protocol/ProofGenerator.ts +0 -339
  114. package/src/protocol/QuoteBuilder.ts +0 -15
  115. package/src/registry/AgentRegistryClient.ts +0 -202
  116. package/src/runtime/BlockchainRuntime.ts +0 -1015
  117. package/src/runtime/IACTPRuntime.ts +0 -306
  118. package/src/runtime/MockRuntime.ts +0 -1298
  119. package/src/runtime/MockStateManager.ts +0 -577
  120. package/src/runtime/index.ts +0 -25
  121. package/src/runtime/types/MockState.ts +0 -237
  122. package/src/storage/ArchiveBundleBuilder.ts +0 -561
  123. package/src/storage/ArweaveClient.ts +0 -946
  124. package/src/storage/FilebaseClient.ts +0 -790
  125. package/src/storage/index.ts +0 -96
  126. package/src/storage/types.ts +0 -348
  127. package/src/types/adapter.ts +0 -310
  128. package/src/types/agent.ts +0 -79
  129. package/src/types/did.ts +0 -223
  130. package/src/types/eip712.ts +0 -175
  131. package/src/types/erc8004.ts +0 -293
  132. package/src/types/escrow.ts +0 -27
  133. package/src/types/index.ts +0 -17
  134. package/src/types/message.ts +0 -145
  135. package/src/types/state.ts +0 -87
  136. package/src/types/transaction.ts +0 -69
  137. package/src/types/x402.ts +0 -251
  138. package/src/utils/ErrorRecoveryGuide.ts +0 -676
  139. package/src/utils/Helpers.ts +0 -688
  140. package/src/utils/IPFSClient.ts +0 -368
  141. package/src/utils/Logger.ts +0 -484
  142. package/src/utils/NonceManager.ts +0 -591
  143. package/src/utils/RateLimiter.ts +0 -534
  144. package/src/utils/ReceivedNonceTracker.ts +0 -567
  145. package/src/utils/SDKLifecycle.ts +0 -416
  146. package/src/utils/SecureNonce.ts +0 -78
  147. package/src/utils/Semaphore.ts +0 -276
  148. package/src/utils/UsedAttestationTracker.ts +0 -385
  149. package/src/utils/canonicalJson.ts +0 -38
  150. package/src/utils/circuitBreaker.ts +0 -324
  151. package/src/utils/computeTypeHash.ts +0 -48
  152. package/src/utils/fsSafe.ts +0 -80
  153. package/src/utils/index.ts +0 -80
  154. package/src/utils/retry.ts +0 -364
  155. package/src/utils/security.ts +0 -418
  156. package/src/utils/validation.ts +0 -540
  157. package/src/wallet/AutoWalletProvider.ts +0 -299
  158. package/src/wallet/EOAWalletProvider.ts +0 -69
  159. package/src/wallet/IWalletProvider.ts +0 -135
  160. package/src/wallet/aa/BundlerClient.ts +0 -274
  161. package/src/wallet/aa/DualNonceManager.ts +0 -173
  162. package/src/wallet/aa/PaymasterClient.ts +0 -174
  163. package/src/wallet/aa/TransactionBatcher.ts +0 -353
  164. package/src/wallet/aa/UserOpBuilder.ts +0 -246
  165. package/src/wallet/aa/constants.ts +0 -60
  166. package/src/wallet/keystore.ts +0 -240
@@ -1,1298 +0,0 @@
1
- /**
2
- * MockRuntime - Core mock blockchain engine for ACTP protocol testing
3
- *
4
- * Provides a complete mock blockchain environment for testing ACTP transactions
5
- * without real blockchain interactions. Implements the 8-state ACTP transaction
6
- * lifecycle with strict state machine validation.
7
- *
8
- * Features:
9
- * - 8-state transaction lifecycle (INITIATED -> QUOTED -> COMMITTED -> IN_PROGRESS -> DELIVERED -> SETTLED)
10
- * - Time manipulation (advanceTime, setTime, advanceBlocks)
11
- * - Balance management (mint, transfer)
12
- * - Event recording and querying
13
- * - Escrow operations (link, release, balance tracking)
14
- *
15
- * @module runtime/MockRuntime
16
- * @see ADR-004 (Mock Blockchain Emulation Scope)
17
- * @see MOCK_MODE_SPECIFICATION.md
18
- */
19
-
20
- import * as crypto from 'crypto';
21
- import { MockStateManager } from './MockStateManager';
22
- import {
23
- MockState,
24
- MockTransaction,
25
- MockEscrow,
26
- MockEvent,
27
- TransactionState,
28
- } from './types/MockState';
29
- import { IACTPRuntime, CreateTransactionParams } from './IACTPRuntime';
30
-
31
- // ============================================================================
32
- // Custom Error Classes
33
- // ============================================================================
34
-
35
- /**
36
- * Error thrown when a transaction is not found.
37
- */
38
- export class TransactionNotFoundError extends Error {
39
- public readonly txId: string;
40
-
41
- constructor(txId: string) {
42
- super(`Transaction not found: ${txId}`);
43
- this.name = 'TransactionNotFoundError';
44
- this.txId = txId;
45
- }
46
- }
47
-
48
- /**
49
- * Error thrown when an invalid state transition is attempted.
50
- */
51
- export class InvalidStateTransitionError extends Error {
52
- public readonly txId: string;
53
- public readonly currentState: TransactionState;
54
- public readonly targetState: TransactionState;
55
-
56
- constructor(txId: string, currentState: TransactionState, targetState: TransactionState) {
57
- super(
58
- `Invalid state transition for transaction ${txId}: ${currentState} -> ${targetState}`
59
- );
60
- this.name = 'InvalidStateTransitionError';
61
- this.txId = txId;
62
- this.currentState = currentState;
63
- this.targetState = targetState;
64
- }
65
- }
66
-
67
- /**
68
- * Error thrown when there are insufficient funds for an operation.
69
- */
70
- export class InsufficientBalanceError extends Error {
71
- public readonly address: string;
72
- public readonly required: string;
73
- public readonly available: string;
74
-
75
- constructor(address: string, required: string, available: string) {
76
- super(
77
- `Insufficient balance for ${address}: required ${required}, available ${available}`
78
- );
79
- this.name = 'InsufficientBalanceError';
80
- this.address = address;
81
- this.required = required;
82
- this.available = available;
83
- }
84
- }
85
-
86
- /**
87
- * Error thrown when an escrow is not found.
88
- */
89
- export class EscrowNotFoundError extends Error {
90
- public readonly escrowId: string;
91
-
92
- constructor(escrowId: string) {
93
- super(`Escrow not found: ${escrowId}`);
94
- this.name = 'EscrowNotFoundError';
95
- this.escrowId = escrowId;
96
- }
97
- }
98
-
99
- /**
100
- * Error thrown when the deadline has passed.
101
- */
102
- export class DeadlinePassedError extends Error {
103
- public readonly txId: string;
104
- public readonly deadline: number;
105
- public readonly currentTime: number;
106
-
107
- constructor(txId: string, deadline: number, currentTime: number) {
108
- super(
109
- `Deadline passed for transaction ${txId}: deadline ${deadline}, current time ${currentTime}`
110
- );
111
- this.name = 'DeadlinePassedError';
112
- this.txId = txId;
113
- this.deadline = deadline;
114
- this.currentTime = currentTime;
115
- }
116
- }
117
-
118
- /**
119
- * Error thrown when the contract is paused.
120
- */
121
- export class ContractPausedError extends Error {
122
- constructor() {
123
- super('Contract is paused');
124
- this.name = 'ContractPausedError';
125
- }
126
- }
127
-
128
- /**
129
- * Error thrown when an invalid amount is provided.
130
- *
131
- * Thrown when zero or negative amounts are passed to transaction
132
- * or escrow operations.
133
- *
134
- * @example
135
- * ```typescript
136
- * // This will throw InvalidAmountError
137
- * await runtime.createTransaction({
138
- * provider: '0x...',
139
- * requester: '0x...',
140
- * amount: '0', // Invalid - must be positive
141
- * deadline: Date.now() + 86400
142
- * });
143
- * ```
144
- */
145
- export class InvalidAmountError extends Error {
146
- public readonly amount: string;
147
- public readonly reason: string;
148
-
149
- constructor(amount: string, reason: string) {
150
- super(`Invalid amount "${amount}": ${reason}`);
151
- this.name = 'InvalidAmountError';
152
- this.amount = amount;
153
- this.reason = reason;
154
- }
155
- }
156
-
157
- /**
158
- * Error thrown when dispute window is still active.
159
- *
160
- * Thrown when attempting to release escrow funds before the dispute
161
- * window has expired. Use `runtime.time.advanceTime()` in tests to
162
- * simulate waiting for the window to close.
163
- *
164
- * @example
165
- * ```typescript
166
- * // Transaction delivered, but dispute window still active
167
- * await runtime.releaseEscrow(escrowId);
168
- * // Throws: DisputeWindowActiveError
169
- *
170
- * // Solution: advance time past the dispute window
171
- * runtime.time.advanceTime(tx.disputeWindow + 1);
172
- * await runtime.releaseEscrow(escrowId); // Now works
173
- * ```
174
- */
175
- export class DisputeWindowActiveError extends Error {
176
- public readonly txId: string;
177
- public readonly windowEnd: number;
178
- public readonly currentTime: number;
179
-
180
- constructor(txId: string, windowEnd: number, currentTime: number) {
181
- super(
182
- `Dispute window still active for transaction ${txId}. ` +
183
- `Window ends at ${windowEnd}, current time is ${currentTime}. ` +
184
- `Use time.advanceTime() to simulate waiting.`
185
- );
186
- this.name = 'DisputeWindowActiveError';
187
- this.txId = txId;
188
- this.windowEnd = windowEnd;
189
- this.currentTime = currentTime;
190
- }
191
- }
192
-
193
- // ============================================================================
194
- // Types
195
- // ============================================================================
196
-
197
- // CreateTransactionParams now imported from IACTPRuntime interface
198
-
199
- /**
200
- * Valid state transitions for the ACTP 8-state machine.
201
- *
202
- * AUDIT FIX (2026-02): Synced with on-chain ACTPKernel contract.
203
- *
204
- * State machine:
205
- * - INITIATED -> QUOTED (optional), COMMITTED, CANCELLED
206
- * - QUOTED -> COMMITTED, CANCELLED
207
- * - COMMITTED -> IN_PROGRESS, CANCELLED (cannot skip to DELIVERED)
208
- * - IN_PROGRESS -> DELIVERED, CANCELLED
209
- * - DELIVERED -> SETTLED, DISPUTED
210
- * - DISPUTED -> SETTLED, CANCELLED (admin/pauser emergency)
211
- * - SETTLED (terminal)
212
- * - CANCELLED (terminal)
213
- */
214
- const VALID_TRANSITIONS: Record<TransactionState, TransactionState[]> = {
215
- INITIATED: ['QUOTED', 'COMMITTED', 'CANCELLED'],
216
- QUOTED: ['COMMITTED', 'CANCELLED'],
217
- // AUDIT FIX: Cannot skip IN_PROGRESS - must transition through it
218
- COMMITTED: ['IN_PROGRESS', 'CANCELLED'],
219
- IN_PROGRESS: ['DELIVERED', 'CANCELLED'],
220
- DELIVERED: ['SETTLED', 'DISPUTED'],
221
- // AUDIT FIX: DISPUTED can also be CANCELLED by admin/pauser
222
- DISPUTED: ['SETTLED', 'CANCELLED'],
223
- SETTLED: [], // Terminal state
224
- CANCELLED: [], // Terminal state
225
- };
226
-
227
- /**
228
- * States from which cancellation is allowed.
229
- */
230
- const CANCELLABLE_STATES: TransactionState[] = [
231
- 'INITIATED',
232
- 'QUOTED',
233
- 'COMMITTED',
234
- 'IN_PROGRESS',
235
- ];
236
-
237
- // ============================================================================
238
- // MockRuntime Class
239
- // ============================================================================
240
-
241
- /**
242
- * MockRuntime - Core mock blockchain engine for ACTP protocol testing.
243
- *
244
- * Implements the IACTPRuntime interface for mock/testing mode.
245
- * Provides a complete mock blockchain environment with:
246
- * - Transaction state machine (8 states)
247
- * - Time manipulation
248
- * - Balance management
249
- * - Event recording
250
- *
251
- * @example
252
- * ```typescript
253
- * const runtime = new MockRuntime();
254
- *
255
- * // Mint initial funds
256
- * await runtime.mintTokens('0xRequester', '10000000000'); // 10,000 USDC
257
- *
258
- * // Create transaction
259
- * const txId = await runtime.createTransaction({
260
- * provider: '0xProvider',
261
- * requester: '0xRequester',
262
- * amount: '1000000', // 1 USDC
263
- * deadline: runtime.time.now() + 86400,
264
- * });
265
- *
266
- * // Advance time and process
267
- * runtime.time.advanceTime(3600);
268
- * ```
269
- */
270
- export class MockRuntime implements IACTPRuntime {
271
- private stateManager: MockStateManager;
272
- /**
273
- * In-memory event log, also persisted to state file.
274
- *
275
- * SECURITY FIX (L-4): Events are now persisted to the state file
276
- * so they survive across CLI invocations.
277
- */
278
- private eventLog: MockEvent[] = [];
279
-
280
- /**
281
- * Time management interface.
282
- *
283
- * SECURITY NOTE: All time-modifying operations are async and use
284
- * file locking to prevent race conditions.
285
- */
286
- public readonly time: {
287
- /** Get current mock timestamp (seconds) */
288
- now: () => number;
289
- /** Advance time by specified seconds (async for locking) */
290
- advanceTime: (seconds: number) => Promise<void>;
291
- /** Advance time by specified blocks (block time * blocks) (async for locking) */
292
- advanceBlocks: (blocks: number) => Promise<void>;
293
- /** Set exact timestamp (must be >= current time) (async for locking) */
294
- setTime: (timestamp: number) => Promise<void>;
295
- };
296
-
297
- /**
298
- * Event access interface.
299
- */
300
- public readonly events: {
301
- /** Get all recorded events */
302
- getAll: () => MockEvent[];
303
- /** Get events filtered by type */
304
- getByType: (type: string) => MockEvent[];
305
- /** Get events for a specific transaction */
306
- getByTransaction: (txId: string) => MockEvent[];
307
- /** Clear all events */
308
- clear: () => void;
309
- };
310
-
311
- /**
312
- * Creates a new MockRuntime instance.
313
- *
314
- * @param stateManager - Optional custom state manager (default: creates new one)
315
- */
316
- constructor(stateManager?: MockStateManager) {
317
- this.stateManager = stateManager ?? new MockStateManager();
318
-
319
- // SECURITY FIX (L-4): Load persisted events from state file
320
- this.loadPersistedEvents();
321
-
322
- // Initialize time interface
323
- // SECURITY FIX: Time operations now use withLock to prevent race conditions
324
- this.time = {
325
- now: () => this.getCurrentTime(),
326
- advanceTime: (seconds: number) => this.advanceTimeWithLock(seconds),
327
- advanceBlocks: (blocks: number) => this.advanceBlocksWithLock(blocks),
328
- setTime: (timestamp: number) => this.setTimeWithLock(timestamp),
329
- };
330
-
331
- // Initialize events interface
332
- this.events = {
333
- getAll: () => [...this.eventLog],
334
- getByType: (type: string) => this.eventLog.filter((e) => e.type === type),
335
- getByTransaction: (txId: string) => this.getEventsByTransaction(txId),
336
- clear: () => {
337
- this.eventLog = [];
338
- // Also clear persisted events
339
- this.clearPersistedEvents();
340
- },
341
- };
342
- }
343
-
344
- /**
345
- * Maximum transaction amount - no limit in mock mode.
346
- *
347
- * Mock mode is for testing, so we don't enforce limits.
348
- * Real blockchain runtimes may have limits for security.
349
- */
350
- readonly maxTransactionAmount: number | undefined = undefined;
351
-
352
- /**
353
- * Load events from persisted state file.
354
- *
355
- * SECURITY FIX (L-4): Events survive across CLI invocations.
356
- */
357
- private loadPersistedEvents(): void {
358
- try {
359
- const state = this.stateManager.loadState();
360
- this.eventLog = state.events ?? [];
361
- } catch {
362
- // If state doesn't exist yet, start with empty events
363
- this.eventLog = [];
364
- }
365
- }
366
-
367
- /**
368
- * Clear persisted events from state file.
369
- */
370
- private clearPersistedEvents(): void {
371
- try {
372
- const state = this.stateManager.loadState();
373
- state.events = [];
374
- this.stateManager.saveState(state);
375
- } catch {
376
- // Ignore errors during clear
377
- }
378
- }
379
-
380
- /**
381
- * Persist an event to both in-memory log and state file.
382
- *
383
- * SECURITY FIX (L-4): Events are persisted for audit trail.
384
- *
385
- * @param event - Event to persist
386
- * @param state - Current state (to avoid re-loading)
387
- */
388
- private persistEvent(event: MockEvent, state: MockState): void {
389
- this.eventLog.push(event);
390
- // Ensure events array exists
391
- if (!state.events) {
392
- state.events = [];
393
- }
394
- state.events.push(event);
395
- }
396
-
397
- // ============================================================================
398
- // Transaction Operations
399
- // ============================================================================
400
-
401
- /**
402
- * Creates a new transaction.
403
- *
404
- * @param params - Transaction creation parameters
405
- * @returns Promise resolving to the transaction ID (bytes32 hex string)
406
- *
407
- * @throws {DeadlinePassedError} If deadline is in the past
408
- *
409
- * @example
410
- * ```typescript
411
- * const txId = await runtime.createTransaction({
412
- * provider: '0xProvider',
413
- * requester: '0xRequester',
414
- * amount: '1000000', // 1 USDC
415
- * deadline: runtime.time.now() + 86400,
416
- * });
417
- * ```
418
- */
419
- async createTransaction(params: CreateTransactionParams): Promise<string> {
420
- return this.stateManager.withLock(async (state) => {
421
- const currentTime = state.blockchain.currentTime;
422
- const blockNumber = state.blockchain.blockNumber;
423
-
424
- // Validate amount (Issue #2 fix)
425
- const amountBigInt = BigInt(params.amount);
426
- if (amountBigInt <= 0n) {
427
- throw new InvalidAmountError(params.amount, 'Amount must be positive');
428
- }
429
-
430
- // Validate deadline
431
- if (params.deadline <= currentTime) {
432
- throw new DeadlinePassedError('new', params.deadline, currentTime);
433
- }
434
-
435
- // SECURITY FIX (M-4): Generate transaction ID with collision check
436
- const txId = this.generateTransactionIdWithCollisionCheck(state);
437
-
438
- // Create transaction
439
- const transaction: MockTransaction = {
440
- id: txId,
441
- requester: params.requester,
442
- provider: params.provider,
443
- amount: params.amount,
444
- state: 'INITIATED',
445
- createdAt: currentTime,
446
- updatedAt: currentTime,
447
- deadline: params.deadline,
448
- disputeWindow: params.disputeWindow ?? 172800, // Default 2 days
449
- completedAt: null,
450
- escrowId: null,
451
- serviceDescription: params.serviceDescription ?? '',
452
- deliveryProof: null,
453
- events: [],
454
- agentId: params.agentId,
455
- };
456
-
457
- // Record event
458
- const event: MockEvent = {
459
- type: 'TransactionCreated',
460
- timestamp: currentTime,
461
- blockNumber,
462
- data: {
463
- txId,
464
- requester: params.requester,
465
- provider: params.provider,
466
- amount: params.amount,
467
- deadline: params.deadline,
468
- agentId: params.agentId,
469
- },
470
- };
471
-
472
- transaction.events.push(event);
473
- state.transactions[txId] = transaction;
474
-
475
- // SECURITY FIX (L-4): Persist event to state file
476
- this.persistEvent(event, state);
477
-
478
- return txId;
479
- });
480
- }
481
-
482
- /**
483
- * Gets a transaction by ID.
484
- *
485
- * AUTO-RELEASE: If transaction is DELIVERED and dispute window has passed,
486
- * automatically settles the transaction (lazy auto-release).
487
- *
488
- * @param txId - Transaction ID
489
- * @returns Promise resolving to the transaction or null if not found
490
- */
491
- async getTransaction(txId: string): Promise<MockTransaction | null> {
492
- // First, check if auto-settle is needed
493
- await this.autoSettleIfReady(txId);
494
-
495
- // Then return the (possibly updated) transaction
496
- const state = this.stateManager.loadState();
497
- return state.transactions[txId] ?? null;
498
- }
499
-
500
- /**
501
- * Auto-settle a transaction if dispute window has passed.
502
- *
503
- * This implements "lazy auto-release" - when anyone checks a transaction
504
- * that is DELIVERED with expired dispute window, it automatically settles.
505
- *
506
- * @param txId - Transaction ID to check
507
- */
508
- private async autoSettleIfReady(txId: string): Promise<void> {
509
- const state = this.stateManager.loadState();
510
- const tx = state.transactions[txId];
511
-
512
- if (!tx) return;
513
- if (tx.state !== 'DELIVERED') return;
514
- if (tx.completedAt === null) return;
515
-
516
- const currentTime = state.blockchain.currentTime;
517
- const disputeWindowEnd = tx.completedAt + tx.disputeWindow;
518
-
519
- // Dispute window still active - don't auto-settle
520
- if (currentTime < disputeWindowEnd) return;
521
-
522
- // Dispute window passed - auto-settle!
523
- // Find the escrow and release it
524
- if (tx.escrowId) {
525
- try {
526
- await this.releaseEscrow(tx.escrowId);
527
- } catch {
528
- // Already settled or other issue - ignore
529
- }
530
- }
531
- }
532
-
533
- /**
534
- * Gets all transactions.
535
- *
536
- * @returns Promise resolving to array of all transactions
537
- */
538
- async getAllTransactions(): Promise<MockTransaction[]> {
539
- const state = this.stateManager.loadState();
540
- return Object.values(state.transactions);
541
- }
542
-
543
- /**
544
- * Gets transactions filtered by provider address and optionally state.
545
- *
546
- * SECURITY FIX (H-1): Filtered query to prevent DoS via memory exhaustion.
547
- * Instead of loading all transactions and filtering client-side, we filter
548
- * server-side and limit the result set.
549
- *
550
- * @param provider - Provider address to filter by
551
- * @param state - Optional state filter (e.g., 'INITIATED', 'DELIVERED')
552
- * @param limit - Maximum number of transactions to return (default 100)
553
- * @returns Promise resolving to filtered transactions
554
- *
555
- * @example
556
- * ```typescript
557
- * // Get up to 100 INITIATED transactions for this provider
558
- * const pending = await runtime.getTransactionsByProvider(
559
- * providerAddress,
560
- * 'INITIATED',
561
- * 100
562
- * );
563
- * ```
564
- */
565
- async getTransactionsByProvider(
566
- provider: string,
567
- state?: TransactionState,
568
- limit: number = 100
569
- ): Promise<MockTransaction[]> {
570
- return this.stateManager.withLock(async (s) => {
571
- let txs = Object.values(s.transactions).filter(
572
- (tx) => tx.provider === provider
573
- );
574
-
575
- if (state) {
576
- txs = txs.filter((tx) => tx.state === state);
577
- }
578
-
579
- if (limit > 0) {
580
- txs = txs.slice(0, limit);
581
- }
582
-
583
- return txs;
584
- });
585
- }
586
-
587
- /**
588
- * Transitions a transaction to a new state.
589
- *
590
- * Validates the transition against the ACTP 8-state machine:
591
- * - INITIATED -> QUOTED, COMMITTED, CANCELLED
592
- * - QUOTED -> COMMITTED, CANCELLED
593
- * - COMMITTED -> IN_PROGRESS, DELIVERED, CANCELLED
594
- * - IN_PROGRESS -> DELIVERED, CANCELLED
595
- * - DELIVERED -> SETTLED, DISPUTED
596
- * - DISPUTED -> SETTLED
597
- *
598
- * @param txId - Transaction ID
599
- * @param newState - Target state
600
- *
601
- * @throws {TransactionNotFoundError} If transaction doesn't exist
602
- * @throws {InvalidStateTransitionError} If transition is not valid
603
- * @throws {DeadlinePassedError} If deadline passed (for CANCELLED transition)
604
- *
605
- * @example
606
- * ```typescript
607
- * // Transition to DELIVERED state
608
- * await runtime.transitionState(txId, 'DELIVERED');
609
- *
610
- * // With delivery proof
611
- * await runtime.transitionState(txId, 'DELIVERED', deliveryProofBytes);
612
- * ```
613
- */
614
- async transitionState(txId: string, newState: TransactionState, proof?: string): Promise<void> {
615
- // SECURITY FIX (PROOF-PARAM): Accept optional proof parameter for interface compliance
616
- // In MockRuntime, proof is stored but not validated (no on-chain verification)
617
- // This allows testing delivery proof flows without actual blockchain interaction
618
- return this.stateManager.withLock(async (state) => {
619
- const tx = state.transactions[txId];
620
- if (!tx) {
621
- throw new TransactionNotFoundError(txId);
622
- }
623
-
624
- const currentState = tx.state;
625
- const currentTime = state.blockchain.currentTime;
626
- const blockNumber = state.blockchain.blockNumber;
627
-
628
- // Validate transition
629
- const validTargets = VALID_TRANSITIONS[currentState];
630
- if (!validTargets.includes(newState)) {
631
- throw new InvalidStateTransitionError(txId, currentState, newState);
632
- }
633
-
634
- // For cancellation before deadline, check deadline hasn't passed
635
- if (newState === 'CANCELLED' && CANCELLABLE_STATES.includes(currentState)) {
636
- // Allow cancellation if deadline passed OR if before deadline
637
- // (This matches real contract behavior - can cancel anytime before DELIVERED)
638
- }
639
-
640
- // Update state
641
- const oldState = tx.state;
642
- tx.state = newState;
643
- tx.updatedAt = currentTime;
644
-
645
- // Record completion time for DELIVERED state
646
- if (newState === 'DELIVERED') {
647
- tx.completedAt = currentTime;
648
- // SECURITY FIX (PROOF-PARAM): Store delivery proof if provided
649
- // Only set if not already populated (Agent sets deliveryProof before transitioning,
650
- // and passes disputeWindowProof as the proof param — don't overwrite the real proof)
651
- if (proof && !tx.deliveryProof) {
652
- tx.deliveryProof = proof;
653
- }
654
- }
655
-
656
- // Handle escrow refund on CANCELLED state (Issue #1 fix)
657
- if (newState === 'CANCELLED' && tx.escrowId !== null) {
658
- const escrow = state.escrows[tx.escrowId];
659
- if (escrow && BigInt(escrow.balance) > 0n) {
660
- const refundAmount = BigInt(escrow.balance);
661
-
662
- // Create requester account if doesn't exist
663
- if (!state.accounts[tx.requester]) {
664
- state.accounts[tx.requester] = {
665
- address: tx.requester,
666
- usdcBalance: '0',
667
- };
668
- }
669
-
670
- // Return funds to requester
671
- const requesterBalance = BigInt(state.accounts[tx.requester].usdcBalance);
672
- state.accounts[tx.requester].usdcBalance = (requesterBalance + refundAmount).toString();
673
-
674
- // Clear escrow balance
675
- escrow.balance = '0';
676
- escrow.locked = false;
677
-
678
- // Record EscrowRefunded event
679
- const refundEvent: MockEvent = {
680
- type: 'EscrowRefunded',
681
- timestamp: currentTime,
682
- blockNumber,
683
- data: {
684
- txId,
685
- escrowId: tx.escrowId,
686
- requester: tx.requester,
687
- amount: refundAmount.toString(),
688
- },
689
- };
690
-
691
- tx.events.push(refundEvent);
692
- // SECURITY FIX (L-4): Persist event
693
- this.persistEvent(refundEvent, state);
694
- }
695
- }
696
-
697
- // Record event
698
- const event: MockEvent = {
699
- type: 'StateTransitioned',
700
- timestamp: currentTime,
701
- blockNumber,
702
- data: {
703
- txId,
704
- oldState,
705
- newState,
706
- },
707
- };
708
-
709
- tx.events.push(event);
710
- // SECURITY FIX (L-4): Persist event
711
- this.persistEvent(event, state);
712
- });
713
- }
714
-
715
- // ============================================================================
716
- // Escrow Operations
717
- // ============================================================================
718
-
719
- /**
720
- * Links an escrow to a transaction and locks funds.
721
- *
722
- * Automatically transitions INITIATED or QUOTED -> COMMITTED (per ACTP spec).
723
- * Deducts funds from requester and adds to escrow balance.
724
- *
725
- * @param txId - Transaction ID
726
- * @param amount - Amount to lock (must match transaction amount)
727
- * @returns Promise resolving to the escrow ID
728
- *
729
- * @throws {TransactionNotFoundError} If transaction doesn't exist
730
- * @throws {InvalidStateTransitionError} If not in INITIATED or QUOTED state
731
- * @throws {InsufficientBalanceError} If requester has insufficient funds
732
- *
733
- * @example
734
- * ```typescript
735
- * // Ensure requester has funds
736
- * await runtime.mintTokens(requester, '10000000');
737
- *
738
- * // Link escrow
739
- * const escrowId = await runtime.linkEscrow(txId, '1000000');
740
- * ```
741
- */
742
- async linkEscrow(txId: string, amount: string): Promise<string> {
743
- return this.stateManager.withLock(async (state) => {
744
- // Validate amount (Issue #2 fix)
745
- const amountBigInt = BigInt(amount);
746
- if (amountBigInt <= 0n) {
747
- throw new InvalidAmountError(amount, 'Amount must be positive');
748
- }
749
-
750
- const tx = state.transactions[txId];
751
- if (!tx) {
752
- throw new TransactionNotFoundError(txId);
753
- }
754
-
755
- // Validate state - can only link from INITIATED or QUOTED
756
- if (tx.state !== 'INITIATED' && tx.state !== 'QUOTED') {
757
- throw new InvalidStateTransitionError(txId, tx.state, 'COMMITTED');
758
- }
759
-
760
- // Check deadline
761
- const currentTime = state.blockchain.currentTime;
762
- if (currentTime > tx.deadline) {
763
- throw new DeadlinePassedError(txId, tx.deadline, currentTime);
764
- }
765
-
766
- // Check requester balance
767
- const requesterAccount = state.accounts[tx.requester];
768
- const requesterBalance = BigInt(requesterAccount?.usdcBalance ?? '0');
769
-
770
- if (requesterBalance < amountBigInt) {
771
- throw new InsufficientBalanceError(
772
- tx.requester,
773
- amount,
774
- requesterBalance.toString()
775
- );
776
- }
777
-
778
- // Generate escrow ID
779
- const escrowId = this.generateEscrowId();
780
-
781
- // Create or update requester account
782
- if (!state.accounts[tx.requester]) {
783
- state.accounts[tx.requester] = {
784
- address: tx.requester,
785
- usdcBalance: '0',
786
- };
787
- }
788
-
789
- // Deduct from requester
790
- state.accounts[tx.requester].usdcBalance = (
791
- requesterBalance - amountBigInt
792
- ).toString();
793
-
794
- // Create escrow
795
- const escrow: MockEscrow = {
796
- id: escrowId,
797
- balance: amount,
798
- locked: true,
799
- transactions: [txId],
800
- createdAt: currentTime,
801
- };
802
-
803
- state.escrows[escrowId] = escrow;
804
-
805
- // Link to transaction and auto-transition to COMMITTED
806
- const oldState = tx.state;
807
- tx.escrowId = escrowId;
808
- tx.state = 'COMMITTED';
809
- tx.updatedAt = currentTime;
810
-
811
- // Record events
812
- const blockNumber = state.blockchain.blockNumber;
813
-
814
- const linkEvent: MockEvent = {
815
- type: 'EscrowLinked',
816
- timestamp: currentTime,
817
- blockNumber,
818
- data: {
819
- txId,
820
- escrowId,
821
- amount,
822
- },
823
- };
824
-
825
- const transitionEvent: MockEvent = {
826
- type: 'StateTransitioned',
827
- timestamp: currentTime,
828
- blockNumber,
829
- data: {
830
- txId,
831
- oldState,
832
- newState: 'COMMITTED',
833
- },
834
- };
835
-
836
- tx.events.push(linkEvent, transitionEvent);
837
- // SECURITY FIX (L-4): Persist events
838
- this.persistEvent(linkEvent, state);
839
- this.persistEvent(transitionEvent, state);
840
-
841
- return escrowId;
842
- });
843
- }
844
-
845
- /**
846
- * Releases escrow funds to the provider and settles the transaction.
847
- *
848
- * Can only be called when transaction is in DELIVERED state.
849
- *
850
- * @param escrowId - Escrow ID
851
- *
852
- * @throws {EscrowNotFoundError} If escrow doesn't exist
853
- * @throws {TransactionNotFoundError} If linked transaction doesn't exist
854
- * @throws {InvalidStateTransitionError} If transaction not in DELIVERED state
855
- *
856
- * @example
857
- * ```typescript
858
- * // After delivery is confirmed
859
- * await runtime.releaseEscrow(escrowId);
860
- *
861
- * // Provider now has funds
862
- * const balance = await runtime.getBalance(provider);
863
- * ```
864
- */
865
- async releaseEscrow(escrowId: string, _attestationUID?: string): Promise<void> {
866
- return this.stateManager.withLock(async (state) => {
867
- const escrow = state.escrows[escrowId];
868
- if (!escrow) {
869
- throw new EscrowNotFoundError(escrowId);
870
- }
871
-
872
- // Get linked transaction (assume single transaction per escrow for now)
873
- const txId = escrow.transactions[0];
874
- const tx = state.transactions[txId];
875
- if (!tx) {
876
- throw new TransactionNotFoundError(txId);
877
- }
878
-
879
- // Validate state
880
- if (tx.state !== 'DELIVERED') {
881
- throw new InvalidStateTransitionError(txId, tx.state, 'SETTLED');
882
- }
883
-
884
- const currentTime = state.blockchain.currentTime;
885
- const blockNumber = state.blockchain.blockNumber;
886
-
887
- // Enforce dispute window (Issue #3 fix)
888
- if (tx.completedAt !== null) {
889
- const disputeWindowEnd = tx.completedAt + tx.disputeWindow;
890
- if (currentTime < disputeWindowEnd) {
891
- throw new DisputeWindowActiveError(txId, disputeWindowEnd, currentTime);
892
- }
893
- }
894
-
895
- const amount = BigInt(escrow.balance);
896
-
897
- // Create or update provider account
898
- if (!state.accounts[tx.provider]) {
899
- state.accounts[tx.provider] = {
900
- address: tx.provider,
901
- usdcBalance: '0',
902
- };
903
- }
904
-
905
- // Transfer to provider
906
- const providerBalance = BigInt(state.accounts[tx.provider].usdcBalance);
907
- state.accounts[tx.provider].usdcBalance = (providerBalance + amount).toString();
908
-
909
- // Clear escrow balance
910
- escrow.balance = '0';
911
- escrow.locked = false;
912
-
913
- // Transition to SETTLED
914
- const oldState = tx.state;
915
- tx.state = 'SETTLED';
916
- tx.updatedAt = currentTime;
917
-
918
- // Record events
919
- const releaseEvent: MockEvent = {
920
- type: 'EscrowReleased',
921
- timestamp: currentTime,
922
- blockNumber,
923
- data: {
924
- txId,
925
- escrowId,
926
- provider: tx.provider,
927
- amount: amount.toString(),
928
- },
929
- };
930
-
931
- const transitionEvent: MockEvent = {
932
- type: 'StateTransitioned',
933
- timestamp: currentTime,
934
- blockNumber,
935
- data: {
936
- txId,
937
- oldState,
938
- newState: 'SETTLED',
939
- },
940
- };
941
-
942
- tx.events.push(releaseEvent, transitionEvent);
943
- // SECURITY FIX (L-4): Persist events
944
- this.persistEvent(releaseEvent, state);
945
- this.persistEvent(transitionEvent, state);
946
- });
947
- }
948
-
949
- /**
950
- * Gets the balance of an escrow.
951
- *
952
- * @param escrowId - Escrow ID
953
- * @returns Promise resolving to the balance as string
954
- *
955
- * @throws {EscrowNotFoundError} If escrow doesn't exist
956
- */
957
- async getEscrowBalance(escrowId: string): Promise<string> {
958
- const state = this.stateManager.loadState();
959
- const escrow = state.escrows[escrowId];
960
- if (!escrow) {
961
- throw new EscrowNotFoundError(escrowId);
962
- }
963
- return escrow.balance;
964
- }
965
-
966
- // ============================================================================
967
- // Account Operations
968
- // ============================================================================
969
-
970
- /**
971
- * Gets the USDC balance of an address.
972
- *
973
- * @param address - Ethereum address
974
- * @returns Promise resolving to the balance as string (0 if account doesn't exist)
975
- */
976
- async getBalance(address: string): Promise<string> {
977
- const state = this.stateManager.loadState();
978
- return state.accounts[address]?.usdcBalance ?? '0';
979
- }
980
-
981
- /**
982
- * Mints USDC tokens to an address (testing only).
983
- *
984
- * @param address - Ethereum address to receive tokens
985
- * @param amount - Amount to mint in USDC wei
986
- *
987
- * @example
988
- * ```typescript
989
- * // Mint 10,000 USDC (6 decimals)
990
- * await runtime.mintTokens('0xRequester', '10000000000');
991
- * ```
992
- */
993
- async mintTokens(address: string, amount: string): Promise<void> {
994
- return this.stateManager.withLock(async (state) => {
995
- // Create account if doesn't exist
996
- if (!state.accounts[address]) {
997
- state.accounts[address] = {
998
- address,
999
- usdcBalance: '0',
1000
- };
1001
- }
1002
-
1003
- const currentBalance = BigInt(state.accounts[address].usdcBalance);
1004
- const mintAmount = BigInt(amount);
1005
- state.accounts[address].usdcBalance = (currentBalance + mintAmount).toString();
1006
-
1007
- // Record event
1008
- const event: MockEvent = {
1009
- type: 'TokensMinted',
1010
- timestamp: state.blockchain.currentTime,
1011
- blockNumber: state.blockchain.blockNumber,
1012
- data: {
1013
- address,
1014
- amount,
1015
- newBalance: state.accounts[address].usdcBalance,
1016
- },
1017
- };
1018
-
1019
- // SECURITY FIX (L-4): Persist event
1020
- this.persistEvent(event, state);
1021
- });
1022
- }
1023
-
1024
- /**
1025
- * Transfers USDC tokens between addresses.
1026
- *
1027
- * @param from - Sender address
1028
- * @param to - Recipient address
1029
- * @param amount - Amount to transfer in USDC wei
1030
- *
1031
- * @throws {InsufficientBalanceError} If sender has insufficient funds
1032
- *
1033
- * @example
1034
- * ```typescript
1035
- * await runtime.transfer('0xFrom', '0xTo', '1000000');
1036
- * ```
1037
- */
1038
- async transfer(from: string, to: string, amount: string): Promise<void> {
1039
- return this.stateManager.withLock(async (state) => {
1040
- // Check sender balance
1041
- const fromAccount = state.accounts[from];
1042
- const fromBalance = BigInt(fromAccount?.usdcBalance ?? '0');
1043
- const transferAmount = BigInt(amount);
1044
-
1045
- if (fromBalance < transferAmount) {
1046
- throw new InsufficientBalanceError(from, amount, fromBalance.toString());
1047
- }
1048
-
1049
- // Create receiver account if doesn't exist
1050
- if (!state.accounts[to]) {
1051
- state.accounts[to] = {
1052
- address: to,
1053
- usdcBalance: '0',
1054
- };
1055
- }
1056
-
1057
- // Create sender account if doesn't exist (shouldn't happen but be safe)
1058
- if (!state.accounts[from]) {
1059
- state.accounts[from] = {
1060
- address: from,
1061
- usdcBalance: '0',
1062
- };
1063
- }
1064
-
1065
- // Transfer
1066
- state.accounts[from].usdcBalance = (fromBalance - transferAmount).toString();
1067
- const toBalance = BigInt(state.accounts[to].usdcBalance);
1068
- state.accounts[to].usdcBalance = (toBalance + transferAmount).toString();
1069
-
1070
- // Record event
1071
- const event: MockEvent = {
1072
- type: 'Transfer',
1073
- timestamp: state.blockchain.currentTime,
1074
- blockNumber: state.blockchain.blockNumber,
1075
- data: {
1076
- from,
1077
- to,
1078
- amount,
1079
- },
1080
- };
1081
-
1082
- // SECURITY FIX (L-4): Persist event
1083
- this.persistEvent(event, state);
1084
- });
1085
- }
1086
-
1087
- // ============================================================================
1088
- // State Management
1089
- // ============================================================================
1090
-
1091
- /**
1092
- * Resets the runtime to initial state.
1093
- *
1094
- * Clears all transactions, escrows, accounts, and events.
1095
- */
1096
- async reset(): Promise<void> {
1097
- this.stateManager.reset();
1098
- this.eventLog = [];
1099
- // Note: reset() on stateManager will also clear persisted events via getDefaultState()
1100
- }
1101
-
1102
- /**
1103
- * Gets the complete mock state.
1104
- *
1105
- * @returns Current mock state snapshot
1106
- */
1107
- getState(): MockState {
1108
- return this.stateManager.loadState();
1109
- }
1110
-
1111
- // ============================================================================
1112
- // Private Methods - Time Management
1113
- // ============================================================================
1114
-
1115
- /**
1116
- * Gets the current mock timestamp.
1117
- */
1118
- private getCurrentTime(): number {
1119
- const state = this.stateManager.loadState();
1120
- return state.blockchain.currentTime;
1121
- }
1122
-
1123
- /**
1124
- * Advances time by specified seconds (with file locking).
1125
- *
1126
- * SECURITY FIX: Uses withLock to prevent race conditions when
1127
- * multiple processes access the state file concurrently.
1128
- */
1129
- private async advanceTimeWithLock(seconds: number): Promise<void> {
1130
- if (seconds < 0) {
1131
- throw new Error('Cannot advance time by negative amount');
1132
- }
1133
-
1134
- return this.stateManager.withLock(async (state) => {
1135
- const blockTimeSeconds = state.blockchain.blockTime;
1136
-
1137
- state.blockchain.currentTime += seconds;
1138
- state.blockchain.blockNumber += Math.floor(seconds / blockTimeSeconds);
1139
-
1140
- // Record event
1141
- const event: MockEvent = {
1142
- type: 'TimeAdvanced',
1143
- timestamp: state.blockchain.currentTime,
1144
- blockNumber: state.blockchain.blockNumber,
1145
- data: {
1146
- seconds,
1147
- newTime: state.blockchain.currentTime,
1148
- newBlock: state.blockchain.blockNumber,
1149
- },
1150
- };
1151
-
1152
- // SECURITY FIX (L-4): Persist event
1153
- this.persistEvent(event, state);
1154
- });
1155
- }
1156
-
1157
- /**
1158
- * Advances time by specified blocks (with file locking).
1159
- *
1160
- * SECURITY FIX: Uses withLock to prevent race conditions.
1161
- */
1162
- private async advanceBlocksWithLock(blocks: number): Promise<void> {
1163
- if (blocks < 0) {
1164
- throw new Error('Cannot advance blocks by negative amount');
1165
- }
1166
-
1167
- return this.stateManager.withLock(async (state) => {
1168
- const blockTimeSeconds = state.blockchain.blockTime;
1169
- const secondsToAdvance = blocks * blockTimeSeconds;
1170
-
1171
- state.blockchain.blockNumber += blocks;
1172
- state.blockchain.currentTime += secondsToAdvance;
1173
-
1174
- // Record event
1175
- const event: MockEvent = {
1176
- type: 'BlocksAdvanced',
1177
- timestamp: state.blockchain.currentTime,
1178
- blockNumber: state.blockchain.blockNumber,
1179
- data: {
1180
- blocks,
1181
- newTime: state.blockchain.currentTime,
1182
- newBlock: state.blockchain.blockNumber,
1183
- },
1184
- };
1185
-
1186
- // SECURITY FIX (L-4): Persist event
1187
- this.persistEvent(event, state);
1188
- });
1189
- }
1190
-
1191
- /**
1192
- * Sets exact timestamp (must be >= current time) (with file locking).
1193
- *
1194
- * SECURITY FIX: Uses withLock to prevent race conditions.
1195
- */
1196
- private async setTimeWithLock(timestamp: number): Promise<void> {
1197
- return this.stateManager.withLock(async (state) => {
1198
- if (timestamp < state.blockchain.currentTime) {
1199
- throw new Error(
1200
- `Cannot move time backwards: ${timestamp} < ${state.blockchain.currentTime}`
1201
- );
1202
- }
1203
-
1204
- const timeDiff = timestamp - state.blockchain.currentTime;
1205
- const blockTimeSeconds = state.blockchain.blockTime;
1206
-
1207
- state.blockchain.currentTime = timestamp;
1208
- state.blockchain.blockNumber += Math.floor(timeDiff / blockTimeSeconds);
1209
-
1210
- // Record event
1211
- const event: MockEvent = {
1212
- type: 'TimeSet',
1213
- timestamp: state.blockchain.currentTime,
1214
- blockNumber: state.blockchain.blockNumber,
1215
- data: {
1216
- newTime: timestamp,
1217
- newBlock: state.blockchain.blockNumber,
1218
- },
1219
- };
1220
-
1221
- // SECURITY FIX (L-4): Persist event
1222
- this.persistEvent(event, state);
1223
- });
1224
- }
1225
-
1226
- // ============================================================================
1227
- // Private Methods - Event Management
1228
- // ============================================================================
1229
-
1230
- /**
1231
- * Gets events for a specific transaction from both global log and transaction events.
1232
- */
1233
- private getEventsByTransaction(txId: string): MockEvent[] {
1234
- const state = this.stateManager.loadState();
1235
- const tx = state.transactions[txId];
1236
-
1237
- if (!tx) {
1238
- return [];
1239
- }
1240
-
1241
- // Return transaction-specific events
1242
- return [...tx.events];
1243
- }
1244
-
1245
- // ============================================================================
1246
- // Private Methods - ID Generation
1247
- // ============================================================================
1248
-
1249
- /**
1250
- * Generates a unique transaction ID (bytes32 hex string).
1251
- *
1252
- * SECURITY FIX (M-4): Now checks for collisions against existing transactions.
1253
- * Uses cryptographically secure random bytes (32 bytes = 256 bits of entropy).
1254
- *
1255
- * @param state - Current mock state to check for collisions
1256
- * @returns Unique transaction ID
1257
- */
1258
- private generateTransactionIdWithCollisionCheck(state: MockState): string {
1259
- const maxAttempts = 10;
1260
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1261
- const txId = `0x${crypto.randomBytes(32).toString('hex')}`;
1262
-
1263
- // Check for collision (extremely unlikely with 256 bits of randomness)
1264
- if (!state.transactions[txId]) {
1265
- return txId;
1266
- }
1267
- }
1268
-
1269
- // This should never happen with 256-bit IDs, but fail safely
1270
- throw new Error(
1271
- 'Failed to generate unique transaction ID after multiple attempts. ' +
1272
- 'This indicates a critical system issue.'
1273
- );
1274
- }
1275
-
1276
- /**
1277
- * Generates a unique transaction ID (bytes32 hex string).
1278
- *
1279
- * Note: This version is used when state is not available.
1280
- * Prefer generateTransactionIdWithCollisionCheck when possible.
1281
- */
1282
- private generateTransactionId(): string {
1283
- return `0x${crypto.randomBytes(32).toString('hex')}`;
1284
- }
1285
-
1286
- /**
1287
- * Generates a unique escrow ID with improved randomness.
1288
- *
1289
- * SECURITY FIX (L-5): Uses 16 bytes (128 bits) of cryptographic randomness
1290
- * instead of just 4 bytes. Removes timestamp to prevent predictability.
1291
- *
1292
- * @returns Unique escrow ID
1293
- */
1294
- private generateEscrowId(): string {
1295
- // Use 16 bytes of cryptographic randomness (128 bits of entropy)
1296
- return `escrow-${crypto.randomBytes(16).toString('hex')}`;
1297
- }
1298
- }