@agirails/sdk 2.5.3 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ACTPClient.d.ts +18 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +72 -23
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +15 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +33 -4
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/StandardAdapter.d.ts +20 -3
- package/dist/adapters/StandardAdapter.d.ts.map +1 -1
- package/dist/adapters/StandardAdapter.js +90 -12
- package/dist/adapters/StandardAdapter.js.map +1 -1
- package/dist/cli/commands/publish.js +16 -4
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/register.js +16 -4
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/tx.js +31 -3
- package/dist/cli/commands/tx.js.map +1 -1
- package/dist/config/networks.d.ts +10 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +31 -22
- package/dist/config/networks.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +2 -1
- package/dist/level0/request.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +11 -5
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/utils/IPFSClient.d.ts +3 -1
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +27 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts +11 -1
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +84 -19
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/IWalletProvider.d.ts +34 -0
- package/dist/wallet/IWalletProvider.d.ts.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +128 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +248 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +26 -1
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +140 -6
- package/dist/wallet/aa/DualNonceManager.js.map +1 -1
- package/package.json +3 -6
- package/src/ACTPClient.ts +0 -1579
- package/src/abi/ACTPKernel.json +0 -1356
- package/src/abi/AgentRegistry.json +0 -915
- package/src/abi/ERC20.json +0 -40
- package/src/abi/EscrowVault.json +0 -134
- package/src/abi/IdentityRegistry.json +0 -316
- package/src/adapters/AdapterRegistry.ts +0 -173
- package/src/adapters/AdapterRouter.ts +0 -416
- package/src/adapters/BaseAdapter.ts +0 -498
- package/src/adapters/BasicAdapter.ts +0 -514
- package/src/adapters/IAdapter.ts +0 -292
- package/src/adapters/StandardAdapter.ts +0 -555
- package/src/adapters/X402Adapter.ts +0 -731
- package/src/adapters/index.ts +0 -60
- package/src/builders/DeliveryProofBuilder.ts +0 -327
- package/src/builders/QuoteBuilder.ts +0 -483
- package/src/builders/index.ts +0 -17
- package/src/cli/commands/balance.ts +0 -110
- package/src/cli/commands/batch.ts +0 -487
- package/src/cli/commands/config.ts +0 -231
- package/src/cli/commands/deploy-check.ts +0 -364
- package/src/cli/commands/deploy-env.ts +0 -120
- package/src/cli/commands/diff.ts +0 -141
- package/src/cli/commands/init.ts +0 -469
- package/src/cli/commands/mint.ts +0 -116
- package/src/cli/commands/pay.ts +0 -113
- package/src/cli/commands/publish.ts +0 -475
- package/src/cli/commands/pull.ts +0 -124
- package/src/cli/commands/register.ts +0 -247
- package/src/cli/commands/simulate.ts +0 -345
- package/src/cli/commands/time.ts +0 -302
- package/src/cli/commands/tx.ts +0 -448
- package/src/cli/commands/watch.ts +0 -211
- package/src/cli/index.ts +0 -134
- package/src/cli/utils/client.ts +0 -252
- package/src/cli/utils/config.ts +0 -389
- package/src/cli/utils/output.ts +0 -465
- package/src/cli/utils/wallet.ts +0 -109
- package/src/config/agirailsmd.ts +0 -262
- package/src/config/networks.ts +0 -275
- package/src/config/pendingPublish.ts +0 -237
- package/src/config/publishPipeline.ts +0 -359
- package/src/config/syncOperations.ts +0 -279
- package/src/erc8004/ERC8004Bridge.ts +0 -462
- package/src/erc8004/ReputationReporter.ts +0 -468
- package/src/erc8004/index.ts +0 -61
- package/src/errors/index.ts +0 -427
- package/src/index.ts +0 -364
- package/src/level0/Provider.ts +0 -117
- package/src/level0/ServiceDirectory.ts +0 -131
- package/src/level0/index.ts +0 -10
- package/src/level0/provide.ts +0 -132
- package/src/level0/request.ts +0 -432
- package/src/level1/Agent.ts +0 -1426
- package/src/level1/index.ts +0 -10
- package/src/level1/pricing/PriceCalculator.ts +0 -255
- package/src/level1/pricing/PricingStrategy.ts +0 -198
- package/src/level1/types/Job.ts +0 -179
- package/src/level1/types/Options.ts +0 -291
- package/src/level1/types/index.ts +0 -8
- package/src/protocol/ACTPKernel.ts +0 -808
- package/src/protocol/AgentRegistry.ts +0 -559
- package/src/protocol/DIDManager.ts +0 -629
- package/src/protocol/DIDResolver.ts +0 -554
- package/src/protocol/EASHelper.ts +0 -378
- package/src/protocol/EscrowVault.ts +0 -255
- package/src/protocol/EventMonitor.ts +0 -204
- package/src/protocol/MessageSigner.ts +0 -510
- package/src/protocol/ProofGenerator.ts +0 -339
- package/src/protocol/QuoteBuilder.ts +0 -15
- package/src/registry/AgentRegistryClient.ts +0 -202
- package/src/runtime/BlockchainRuntime.ts +0 -1015
- package/src/runtime/IACTPRuntime.ts +0 -306
- package/src/runtime/MockRuntime.ts +0 -1298
- package/src/runtime/MockStateManager.ts +0 -577
- package/src/runtime/index.ts +0 -25
- package/src/runtime/types/MockState.ts +0 -237
- package/src/storage/ArchiveBundleBuilder.ts +0 -561
- package/src/storage/ArweaveClient.ts +0 -946
- package/src/storage/FilebaseClient.ts +0 -790
- package/src/storage/index.ts +0 -96
- package/src/storage/types.ts +0 -348
- package/src/types/adapter.ts +0 -310
- package/src/types/agent.ts +0 -79
- package/src/types/did.ts +0 -223
- package/src/types/eip712.ts +0 -175
- package/src/types/erc8004.ts +0 -293
- package/src/types/escrow.ts +0 -27
- package/src/types/index.ts +0 -17
- package/src/types/message.ts +0 -145
- package/src/types/state.ts +0 -87
- package/src/types/transaction.ts +0 -69
- package/src/types/x402.ts +0 -251
- package/src/utils/ErrorRecoveryGuide.ts +0 -676
- package/src/utils/Helpers.ts +0 -688
- package/src/utils/IPFSClient.ts +0 -368
- package/src/utils/Logger.ts +0 -484
- package/src/utils/NonceManager.ts +0 -591
- package/src/utils/RateLimiter.ts +0 -534
- package/src/utils/ReceivedNonceTracker.ts +0 -567
- package/src/utils/SDKLifecycle.ts +0 -416
- package/src/utils/SecureNonce.ts +0 -78
- package/src/utils/Semaphore.ts +0 -276
- package/src/utils/UsedAttestationTracker.ts +0 -385
- package/src/utils/canonicalJson.ts +0 -38
- package/src/utils/circuitBreaker.ts +0 -324
- package/src/utils/computeTypeHash.ts +0 -48
- package/src/utils/fsSafe.ts +0 -80
- package/src/utils/index.ts +0 -80
- package/src/utils/retry.ts +0 -364
- package/src/utils/security.ts +0 -418
- package/src/utils/validation.ts +0 -540
- package/src/wallet/AutoWalletProvider.ts +0 -299
- package/src/wallet/EOAWalletProvider.ts +0 -69
- package/src/wallet/IWalletProvider.ts +0 -135
- package/src/wallet/aa/BundlerClient.ts +0 -274
- package/src/wallet/aa/DualNonceManager.ts +0 -173
- package/src/wallet/aa/PaymasterClient.ts +0 -174
- package/src/wallet/aa/TransactionBatcher.ts +0 -353
- package/src/wallet/aa/UserOpBuilder.ts +0 -246
- package/src/wallet/aa/constants.ts +0 -60
- package/src/wallet/keystore.ts +0 -240
|
@@ -1,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
|
-
}
|