@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.
Files changed (169) hide show
  1. package/dist/ACTPClient.d.ts +18 -0
  2. package/dist/ACTPClient.d.ts.map +1 -1
  3. package/dist/ACTPClient.js +72 -23
  4. package/dist/ACTPClient.js.map +1 -1
  5. package/dist/adapters/BasicAdapter.d.ts +15 -0
  6. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  7. package/dist/adapters/BasicAdapter.js +33 -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 +90 -12
  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 +10 -2
  20. package/dist/config/networks.d.ts.map +1 -1
  21. package/dist/config/networks.js +31 -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 +11 -1
  34. package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
  35. package/dist/wallet/AutoWalletProvider.js +84 -19
  36. package/dist/wallet/AutoWalletProvider.js.map +1 -1
  37. package/dist/wallet/IWalletProvider.d.ts +34 -0
  38. package/dist/wallet/IWalletProvider.d.ts.map +1 -1
  39. package/dist/wallet/SmartWalletRouter.d.ts +128 -0
  40. package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
  41. package/dist/wallet/SmartWalletRouter.js +248 -0
  42. package/dist/wallet/SmartWalletRouter.js.map +1 -0
  43. package/dist/wallet/aa/DualNonceManager.d.ts +26 -1
  44. package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
  45. package/dist/wallet/aa/DualNonceManager.js +140 -6
  46. package/dist/wallet/aa/DualNonceManager.js.map +1 -1
  47. package/package.json +3 -6
  48. package/src/ACTPClient.ts +0 -1579
  49. package/src/abi/ACTPKernel.json +0 -1356
  50. package/src/abi/AgentRegistry.json +0 -915
  51. package/src/abi/ERC20.json +0 -40
  52. package/src/abi/EscrowVault.json +0 -134
  53. package/src/abi/IdentityRegistry.json +0 -316
  54. package/src/adapters/AdapterRegistry.ts +0 -173
  55. package/src/adapters/AdapterRouter.ts +0 -416
  56. package/src/adapters/BaseAdapter.ts +0 -498
  57. package/src/adapters/BasicAdapter.ts +0 -514
  58. package/src/adapters/IAdapter.ts +0 -292
  59. package/src/adapters/StandardAdapter.ts +0 -555
  60. package/src/adapters/X402Adapter.ts +0 -731
  61. package/src/adapters/index.ts +0 -60
  62. package/src/builders/DeliveryProofBuilder.ts +0 -327
  63. package/src/builders/QuoteBuilder.ts +0 -483
  64. package/src/builders/index.ts +0 -17
  65. package/src/cli/commands/balance.ts +0 -110
  66. package/src/cli/commands/batch.ts +0 -487
  67. package/src/cli/commands/config.ts +0 -231
  68. package/src/cli/commands/deploy-check.ts +0 -364
  69. package/src/cli/commands/deploy-env.ts +0 -120
  70. package/src/cli/commands/diff.ts +0 -141
  71. package/src/cli/commands/init.ts +0 -469
  72. package/src/cli/commands/mint.ts +0 -116
  73. package/src/cli/commands/pay.ts +0 -113
  74. package/src/cli/commands/publish.ts +0 -475
  75. package/src/cli/commands/pull.ts +0 -124
  76. package/src/cli/commands/register.ts +0 -247
  77. package/src/cli/commands/simulate.ts +0 -345
  78. package/src/cli/commands/time.ts +0 -302
  79. package/src/cli/commands/tx.ts +0 -448
  80. package/src/cli/commands/watch.ts +0 -211
  81. package/src/cli/index.ts +0 -134
  82. package/src/cli/utils/client.ts +0 -252
  83. package/src/cli/utils/config.ts +0 -389
  84. package/src/cli/utils/output.ts +0 -465
  85. package/src/cli/utils/wallet.ts +0 -109
  86. package/src/config/agirailsmd.ts +0 -262
  87. package/src/config/networks.ts +0 -275
  88. package/src/config/pendingPublish.ts +0 -237
  89. package/src/config/publishPipeline.ts +0 -359
  90. package/src/config/syncOperations.ts +0 -279
  91. package/src/erc8004/ERC8004Bridge.ts +0 -462
  92. package/src/erc8004/ReputationReporter.ts +0 -468
  93. package/src/erc8004/index.ts +0 -61
  94. package/src/errors/index.ts +0 -427
  95. package/src/index.ts +0 -364
  96. package/src/level0/Provider.ts +0 -117
  97. package/src/level0/ServiceDirectory.ts +0 -131
  98. package/src/level0/index.ts +0 -10
  99. package/src/level0/provide.ts +0 -132
  100. package/src/level0/request.ts +0 -432
  101. package/src/level1/Agent.ts +0 -1426
  102. package/src/level1/index.ts +0 -10
  103. package/src/level1/pricing/PriceCalculator.ts +0 -255
  104. package/src/level1/pricing/PricingStrategy.ts +0 -198
  105. package/src/level1/types/Job.ts +0 -179
  106. package/src/level1/types/Options.ts +0 -291
  107. package/src/level1/types/index.ts +0 -8
  108. package/src/protocol/ACTPKernel.ts +0 -808
  109. package/src/protocol/AgentRegistry.ts +0 -559
  110. package/src/protocol/DIDManager.ts +0 -629
  111. package/src/protocol/DIDResolver.ts +0 -554
  112. package/src/protocol/EASHelper.ts +0 -378
  113. package/src/protocol/EscrowVault.ts +0 -255
  114. package/src/protocol/EventMonitor.ts +0 -204
  115. package/src/protocol/MessageSigner.ts +0 -510
  116. package/src/protocol/ProofGenerator.ts +0 -339
  117. package/src/protocol/QuoteBuilder.ts +0 -15
  118. package/src/registry/AgentRegistryClient.ts +0 -202
  119. package/src/runtime/BlockchainRuntime.ts +0 -1015
  120. package/src/runtime/IACTPRuntime.ts +0 -306
  121. package/src/runtime/MockRuntime.ts +0 -1298
  122. package/src/runtime/MockStateManager.ts +0 -577
  123. package/src/runtime/index.ts +0 -25
  124. package/src/runtime/types/MockState.ts +0 -237
  125. package/src/storage/ArchiveBundleBuilder.ts +0 -561
  126. package/src/storage/ArweaveClient.ts +0 -946
  127. package/src/storage/FilebaseClient.ts +0 -790
  128. package/src/storage/index.ts +0 -96
  129. package/src/storage/types.ts +0 -348
  130. package/src/types/adapter.ts +0 -310
  131. package/src/types/agent.ts +0 -79
  132. package/src/types/did.ts +0 -223
  133. package/src/types/eip712.ts +0 -175
  134. package/src/types/erc8004.ts +0 -293
  135. package/src/types/escrow.ts +0 -27
  136. package/src/types/index.ts +0 -17
  137. package/src/types/message.ts +0 -145
  138. package/src/types/state.ts +0 -87
  139. package/src/types/transaction.ts +0 -69
  140. package/src/types/x402.ts +0 -251
  141. package/src/utils/ErrorRecoveryGuide.ts +0 -676
  142. package/src/utils/Helpers.ts +0 -688
  143. package/src/utils/IPFSClient.ts +0 -368
  144. package/src/utils/Logger.ts +0 -484
  145. package/src/utils/NonceManager.ts +0 -591
  146. package/src/utils/RateLimiter.ts +0 -534
  147. package/src/utils/ReceivedNonceTracker.ts +0 -567
  148. package/src/utils/SDKLifecycle.ts +0 -416
  149. package/src/utils/SecureNonce.ts +0 -78
  150. package/src/utils/Semaphore.ts +0 -276
  151. package/src/utils/UsedAttestationTracker.ts +0 -385
  152. package/src/utils/canonicalJson.ts +0 -38
  153. package/src/utils/circuitBreaker.ts +0 -324
  154. package/src/utils/computeTypeHash.ts +0 -48
  155. package/src/utils/fsSafe.ts +0 -80
  156. package/src/utils/index.ts +0 -80
  157. package/src/utils/retry.ts +0 -364
  158. package/src/utils/security.ts +0 -418
  159. package/src/utils/validation.ts +0 -540
  160. package/src/wallet/AutoWalletProvider.ts +0 -299
  161. package/src/wallet/EOAWalletProvider.ts +0 -69
  162. package/src/wallet/IWalletProvider.ts +0 -135
  163. package/src/wallet/aa/BundlerClient.ts +0 -274
  164. package/src/wallet/aa/DualNonceManager.ts +0 -173
  165. package/src/wallet/aa/PaymasterClient.ts +0 -174
  166. package/src/wallet/aa/TransactionBatcher.ts +0 -353
  167. package/src/wallet/aa/UserOpBuilder.ts +0 -246
  168. package/src/wallet/aa/constants.ts +0 -60
  169. 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
- }