@agirails/sdk 2.5.3 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/dist/ACTPClient.d.ts +18 -0
  2. package/dist/ACTPClient.d.ts.map +1 -1
  3. package/dist/ACTPClient.js +67 -22
  4. package/dist/ACTPClient.js.map +1 -1
  5. package/dist/adapters/BasicAdapter.d.ts +12 -0
  6. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  7. package/dist/adapters/BasicAdapter.js +30 -4
  8. package/dist/adapters/BasicAdapter.js.map +1 -1
  9. package/dist/adapters/StandardAdapter.d.ts +20 -3
  10. package/dist/adapters/StandardAdapter.d.ts.map +1 -1
  11. package/dist/adapters/StandardAdapter.js +45 -11
  12. package/dist/adapters/StandardAdapter.js.map +1 -1
  13. package/dist/cli/commands/publish.js +16 -4
  14. package/dist/cli/commands/publish.js.map +1 -1
  15. package/dist/cli/commands/register.js +16 -4
  16. package/dist/cli/commands/register.js.map +1 -1
  17. package/dist/cli/commands/tx.js +31 -3
  18. package/dist/cli/commands/tx.js.map +1 -1
  19. package/dist/config/networks.d.ts +2 -2
  20. package/dist/config/networks.d.ts.map +1 -1
  21. package/dist/config/networks.js +27 -22
  22. package/dist/config/networks.js.map +1 -1
  23. package/dist/level0/request.d.ts.map +1 -1
  24. package/dist/level0/request.js +2 -1
  25. package/dist/level0/request.js.map +1 -1
  26. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
  27. package/dist/runtime/BlockchainRuntime.js +11 -5
  28. package/dist/runtime/BlockchainRuntime.js.map +1 -1
  29. package/dist/utils/IPFSClient.d.ts +3 -1
  30. package/dist/utils/IPFSClient.d.ts.map +1 -1
  31. package/dist/utils/IPFSClient.js +27 -7
  32. package/dist/utils/IPFSClient.js.map +1 -1
  33. package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
  34. package/dist/wallet/AutoWalletProvider.js +52 -18
  35. package/dist/wallet/AutoWalletProvider.js.map +1 -1
  36. package/dist/wallet/SmartWalletRouter.d.ts +116 -0
  37. package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
  38. package/dist/wallet/SmartWalletRouter.js +212 -0
  39. package/dist/wallet/SmartWalletRouter.js.map +1 -0
  40. package/dist/wallet/aa/DualNonceManager.d.ts +19 -0
  41. package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
  42. package/dist/wallet/aa/DualNonceManager.js +100 -5
  43. package/dist/wallet/aa/DualNonceManager.js.map +1 -1
  44. package/package.json +3 -6
  45. package/src/ACTPClient.ts +0 -1579
  46. package/src/abi/ACTPKernel.json +0 -1356
  47. package/src/abi/AgentRegistry.json +0 -915
  48. package/src/abi/ERC20.json +0 -40
  49. package/src/abi/EscrowVault.json +0 -134
  50. package/src/abi/IdentityRegistry.json +0 -316
  51. package/src/adapters/AdapterRegistry.ts +0 -173
  52. package/src/adapters/AdapterRouter.ts +0 -416
  53. package/src/adapters/BaseAdapter.ts +0 -498
  54. package/src/adapters/BasicAdapter.ts +0 -514
  55. package/src/adapters/IAdapter.ts +0 -292
  56. package/src/adapters/StandardAdapter.ts +0 -555
  57. package/src/adapters/X402Adapter.ts +0 -731
  58. package/src/adapters/index.ts +0 -60
  59. package/src/builders/DeliveryProofBuilder.ts +0 -327
  60. package/src/builders/QuoteBuilder.ts +0 -483
  61. package/src/builders/index.ts +0 -17
  62. package/src/cli/commands/balance.ts +0 -110
  63. package/src/cli/commands/batch.ts +0 -487
  64. package/src/cli/commands/config.ts +0 -231
  65. package/src/cli/commands/deploy-check.ts +0 -364
  66. package/src/cli/commands/deploy-env.ts +0 -120
  67. package/src/cli/commands/diff.ts +0 -141
  68. package/src/cli/commands/init.ts +0 -469
  69. package/src/cli/commands/mint.ts +0 -116
  70. package/src/cli/commands/pay.ts +0 -113
  71. package/src/cli/commands/publish.ts +0 -475
  72. package/src/cli/commands/pull.ts +0 -124
  73. package/src/cli/commands/register.ts +0 -247
  74. package/src/cli/commands/simulate.ts +0 -345
  75. package/src/cli/commands/time.ts +0 -302
  76. package/src/cli/commands/tx.ts +0 -448
  77. package/src/cli/commands/watch.ts +0 -211
  78. package/src/cli/index.ts +0 -134
  79. package/src/cli/utils/client.ts +0 -252
  80. package/src/cli/utils/config.ts +0 -389
  81. package/src/cli/utils/output.ts +0 -465
  82. package/src/cli/utils/wallet.ts +0 -109
  83. package/src/config/agirailsmd.ts +0 -262
  84. package/src/config/networks.ts +0 -275
  85. package/src/config/pendingPublish.ts +0 -237
  86. package/src/config/publishPipeline.ts +0 -359
  87. package/src/config/syncOperations.ts +0 -279
  88. package/src/erc8004/ERC8004Bridge.ts +0 -462
  89. package/src/erc8004/ReputationReporter.ts +0 -468
  90. package/src/erc8004/index.ts +0 -61
  91. package/src/errors/index.ts +0 -427
  92. package/src/index.ts +0 -364
  93. package/src/level0/Provider.ts +0 -117
  94. package/src/level0/ServiceDirectory.ts +0 -131
  95. package/src/level0/index.ts +0 -10
  96. package/src/level0/provide.ts +0 -132
  97. package/src/level0/request.ts +0 -432
  98. package/src/level1/Agent.ts +0 -1426
  99. package/src/level1/index.ts +0 -10
  100. package/src/level1/pricing/PriceCalculator.ts +0 -255
  101. package/src/level1/pricing/PricingStrategy.ts +0 -198
  102. package/src/level1/types/Job.ts +0 -179
  103. package/src/level1/types/Options.ts +0 -291
  104. package/src/level1/types/index.ts +0 -8
  105. package/src/protocol/ACTPKernel.ts +0 -808
  106. package/src/protocol/AgentRegistry.ts +0 -559
  107. package/src/protocol/DIDManager.ts +0 -629
  108. package/src/protocol/DIDResolver.ts +0 -554
  109. package/src/protocol/EASHelper.ts +0 -378
  110. package/src/protocol/EscrowVault.ts +0 -255
  111. package/src/protocol/EventMonitor.ts +0 -204
  112. package/src/protocol/MessageSigner.ts +0 -510
  113. package/src/protocol/ProofGenerator.ts +0 -339
  114. package/src/protocol/QuoteBuilder.ts +0 -15
  115. package/src/registry/AgentRegistryClient.ts +0 -202
  116. package/src/runtime/BlockchainRuntime.ts +0 -1015
  117. package/src/runtime/IACTPRuntime.ts +0 -306
  118. package/src/runtime/MockRuntime.ts +0 -1298
  119. package/src/runtime/MockStateManager.ts +0 -577
  120. package/src/runtime/index.ts +0 -25
  121. package/src/runtime/types/MockState.ts +0 -237
  122. package/src/storage/ArchiveBundleBuilder.ts +0 -561
  123. package/src/storage/ArweaveClient.ts +0 -946
  124. package/src/storage/FilebaseClient.ts +0 -790
  125. package/src/storage/index.ts +0 -96
  126. package/src/storage/types.ts +0 -348
  127. package/src/types/adapter.ts +0 -310
  128. package/src/types/agent.ts +0 -79
  129. package/src/types/did.ts +0 -223
  130. package/src/types/eip712.ts +0 -175
  131. package/src/types/erc8004.ts +0 -293
  132. package/src/types/escrow.ts +0 -27
  133. package/src/types/index.ts +0 -17
  134. package/src/types/message.ts +0 -145
  135. package/src/types/state.ts +0 -87
  136. package/src/types/transaction.ts +0 -69
  137. package/src/types/x402.ts +0 -251
  138. package/src/utils/ErrorRecoveryGuide.ts +0 -676
  139. package/src/utils/Helpers.ts +0 -688
  140. package/src/utils/IPFSClient.ts +0 -368
  141. package/src/utils/Logger.ts +0 -484
  142. package/src/utils/NonceManager.ts +0 -591
  143. package/src/utils/RateLimiter.ts +0 -534
  144. package/src/utils/ReceivedNonceTracker.ts +0 -567
  145. package/src/utils/SDKLifecycle.ts +0 -416
  146. package/src/utils/SecureNonce.ts +0 -78
  147. package/src/utils/Semaphore.ts +0 -276
  148. package/src/utils/UsedAttestationTracker.ts +0 -385
  149. package/src/utils/canonicalJson.ts +0 -38
  150. package/src/utils/circuitBreaker.ts +0 -324
  151. package/src/utils/computeTypeHash.ts +0 -48
  152. package/src/utils/fsSafe.ts +0 -80
  153. package/src/utils/index.ts +0 -80
  154. package/src/utils/retry.ts +0 -364
  155. package/src/utils/security.ts +0 -418
  156. package/src/utils/validation.ts +0 -540
  157. package/src/wallet/AutoWalletProvider.ts +0 -299
  158. package/src/wallet/EOAWalletProvider.ts +0 -69
  159. package/src/wallet/IWalletProvider.ts +0 -135
  160. package/src/wallet/aa/BundlerClient.ts +0 -274
  161. package/src/wallet/aa/DualNonceManager.ts +0 -173
  162. package/src/wallet/aa/PaymasterClient.ts +0 -174
  163. package/src/wallet/aa/TransactionBatcher.ts +0 -353
  164. package/src/wallet/aa/UserOpBuilder.ts +0 -246
  165. package/src/wallet/aa/constants.ts +0 -60
  166. package/src/wallet/keystore.ts +0 -240
@@ -1,946 +0,0 @@
1
- /**
2
- * ArweaveClient - Permanent Storage via Irys (AIP-7 §4.3)
3
- *
4
- * Provides permanent storage on Arweave via Irys (formerly Bundlr).
5
- * Used for archiving settled transaction bundles for compliance.
6
- *
7
- * Security Features (Post-Audit):
8
- * - Gateway URL whitelist (SSRF protection)
9
- * - Download size limits (DoS protection)
10
- * - Credential sanitization in errors
11
- * - Retry with exponential backoff
12
- * - Circuit breaker for gateway health tracking
13
- *
14
- * @module storage/ArweaveClient
15
- *
16
- * Key Principle: Arweave-First Write Order
17
- * 1. Write to Arweave FIRST -> Get Arweave TX ID
18
- * 2. THEN anchor TX ID on-chain
19
- *
20
- * @see AIP-7 §4.1 for invariant explanation
21
- */
22
-
23
- import Irys from '@irys/sdk';
24
- import {
25
- ArweaveUploadError,
26
- ArweaveDownloadError,
27
- ArweaveTimeoutError,
28
- InsufficientBalanceError,
29
- ValidationError,
30
- StorageError,
31
- FileSizeLimitExceededError
32
- } from '../errors';
33
- import {
34
- ArweaveConfig,
35
- IrysCurrency,
36
- IrysNetwork,
37
- ArchiveBundle,
38
- ArweaveUploadResult,
39
- DownloadResult,
40
- ARCHIVE_BUNDLE_TYPE
41
- } from './types';
42
- import {
43
- validateArweaveTxId,
44
- validateGatewayURL,
45
- sanitizeErrorMessage,
46
- ALLOWED_ARWEAVE_GATEWAYS
47
- } from '../utils/validation';
48
- import { withRetry, RetryOptions } from '../utils/retry';
49
- import {
50
- GatewayCircuitBreaker,
51
- } from '../utils/circuitBreaker';
52
-
53
- // ============================================================================
54
- // Constants
55
- // ============================================================================
56
-
57
- const DEFAULT_CURRENCY: IrysCurrency = 'base-eth';
58
- const DEFAULT_NETWORK: IrysNetwork = 'mainnet';
59
- const DEFAULT_TIMEOUT = 60000; // 60 seconds
60
- const DEFAULT_MAX_DOWNLOAD_SIZE = 10 * 1024 * 1024; // 10MB for archive bundles
61
- const ARWEAVE_GATEWAY = 'https://arweave.net/';
62
-
63
- // Minimum funding amount (to avoid dust)
64
- const MIN_FUNDING_AMOUNT = 1000n; // 1000 wei minimum
65
-
66
- // ============================================================================
67
- // ArweaveClient Class
68
- // ============================================================================
69
-
70
- /**
71
- * ArweaveClient - Permanent storage on Arweave via Irys
72
- *
73
- * Used for:
74
- * - Archiving settled transaction bundles (compliance)
75
- * - 7-year retention requirement (Arweave guarantees 200+ years)
76
- *
77
- * IMPORTANT: Uses Base ETH for payments (no bridging required)
78
- *
79
- * @example
80
- * ```typescript
81
- * const client = await ArweaveClient.create({
82
- * privateKey: process.env.ARCHIVE_UPLOADER_KEY!,
83
- * rpcUrl: process.env.BASE_RPC_URL!
84
- * });
85
- *
86
- * // Check balance
87
- * const balance = await client.getBalance();
88
- * console.log('Irys balance:', balance);
89
- *
90
- * // Fund if needed
91
- * if (balance < 10000n) {
92
- * await client.fund(100000n);
93
- * }
94
- *
95
- * // Upload archive bundle
96
- * const result = await client.uploadBundle(archiveBundle);
97
- * console.log('Arweave TX ID:', result.txId);
98
- *
99
- * // Download archive bundle
100
- * const downloaded = await client.downloadBundle(result.txId);
101
- * ```
102
- */
103
- export class ArweaveClient {
104
- private _irys: Irys | null = null;
105
- private readonly config: Required<Omit<ArweaveConfig, 'circuitBreaker'>>;
106
- private readonly maxDownloadSize: number;
107
- private readonly retryOptions: RetryOptions;
108
- private readonly circuitBreaker: GatewayCircuitBreaker | null;
109
- private readonly circuitBreakerEnabled: boolean;
110
- private initialized = false;
111
-
112
- /**
113
- * Get initialized Irys instance (throws if not initialized)
114
- */
115
- private get irys(): Irys {
116
- if (!this.initialized || !this._irys) {
117
- throw new StorageError('operation', 'ArweaveClient not initialized. Call create() first.');
118
- }
119
- return this._irys;
120
- }
121
-
122
- /**
123
- * Private constructor - use ArweaveClient.create() factory
124
- */
125
- private constructor(config: ArweaveConfig) {
126
- // Validate required config
127
- if (!config.privateKey) {
128
- throw new ValidationError('privateKey', 'Private key is required for Arweave uploads');
129
- }
130
- if (!config.rpcUrl) {
131
- throw new ValidationError('rpcUrl', 'RPC URL is required for payment chain');
132
- }
133
-
134
- // P0-1: Validate default gateway URL against whitelist
135
- validateGatewayURL(ARWEAVE_GATEWAY, ALLOWED_ARWEAVE_GATEWAYS, 'gatewayUrl');
136
-
137
- this.config = {
138
- privateKey: config.privateKey,
139
- rpcUrl: config.rpcUrl,
140
- currency: config.currency || DEFAULT_CURRENCY,
141
- network: config.network || DEFAULT_NETWORK,
142
- timeout: config.timeout || DEFAULT_TIMEOUT
143
- };
144
-
145
- // P1-1: DoS protection
146
- this.maxDownloadSize = DEFAULT_MAX_DOWNLOAD_SIZE;
147
-
148
- // P1-2: Default retry options
149
- this.retryOptions = {
150
- maxAttempts: 3,
151
- initialDelayMs: 2000,
152
- maxDelayMs: 30000,
153
- backoffMultiplier: 2
154
- };
155
-
156
- // Circuit breaker for gateway health tracking
157
- this.circuitBreakerEnabled = config.circuitBreaker?.enabled !== false;
158
- if (this.circuitBreakerEnabled) {
159
- this.circuitBreaker = new GatewayCircuitBreaker({
160
- failureThreshold: config.circuitBreaker?.failureThreshold,
161
- resetTimeoutMs: config.circuitBreaker?.resetTimeoutMs,
162
- failureWindowMs: config.circuitBreaker?.failureWindowMs,
163
- successThreshold: config.circuitBreaker?.successThreshold
164
- });
165
- } else {
166
- this.circuitBreaker = null;
167
- }
168
- }
169
-
170
- /**
171
- * Create and initialize ArweaveClient
172
- *
173
- * @param config - Arweave configuration
174
- * @returns Initialized ArweaveClient
175
- *
176
- * @example
177
- * ```typescript
178
- * const client = await ArweaveClient.create({
179
- * privateKey: process.env.ARCHIVE_UPLOADER_KEY!,
180
- * rpcUrl: 'https://mainnet.base.org'
181
- * });
182
- * ```
183
- */
184
- static async create(config: ArweaveConfig): Promise<ArweaveClient> {
185
- const client = new ArweaveClient(config);
186
- await client.initialize();
187
- return client;
188
- }
189
-
190
- /**
191
- * Initialize Irys SDK connection
192
- */
193
- private async initialize(): Promise<void> {
194
- if (this.initialized) return;
195
-
196
- try {
197
- this._irys = new Irys({
198
- network: this.config.network,
199
- token: this.config.currency,
200
- key: this.config.privateKey,
201
- config: {
202
- providerUrl: this.config.rpcUrl
203
- }
204
- });
205
-
206
- // Connect to Irys node
207
- await this._irys.ready();
208
-
209
- this.initialized = true;
210
- } catch (error: any) {
211
- // P0-2: Sanitize error message (may contain credentials)
212
- throw new StorageError(
213
- 'initialization',
214
- `Failed to initialize Irys client: ${sanitizeErrorMessage(error)}`,
215
- { currency: this.config.currency, network: this.config.network }
216
- );
217
- }
218
- }
219
-
220
- // ==========================================================================
221
- // Funding Methods
222
- // ==========================================================================
223
-
224
- /**
225
- * Fund the Irys node (required before uploading)
226
- *
227
- * @param amount - Amount to fund in payment currency (wei for ETH)
228
- * @throws {ValidationError} If amount is invalid
229
- * @throws {StorageError} If funding fails
230
- *
231
- * @example
232
- * ```typescript
233
- * // Fund with 0.001 ETH (1e15 wei)
234
- * await client.fund(1000000000000000n);
235
- * ```
236
- */
237
- async fund(amount: bigint): Promise<void> {
238
-
239
-
240
- if (amount < MIN_FUNDING_AMOUNT) {
241
- throw new ValidationError(
242
- 'amount',
243
- `Funding amount too small. Minimum: ${MIN_FUNDING_AMOUNT} wei`
244
- );
245
- }
246
-
247
- try {
248
- await this.withTimeout(
249
- this.irys.fund(amount),
250
- this.config.timeout,
251
- 'fund'
252
- );
253
- } catch (error: any) {
254
- if (error instanceof ArweaveTimeoutError) throw error;
255
-
256
- // P0-2: Sanitize error message (may contain credentials)
257
- throw new StorageError('funding', `Failed to fund Irys: ${sanitizeErrorMessage(error)}`, {
258
- amount: amount.toString(),
259
- currency: this.config.currency
260
- });
261
- }
262
- }
263
-
264
- /**
265
- * Get current Irys balance
266
- *
267
- * @returns Balance in payment currency (wei for ETH)
268
- *
269
- * @example
270
- * ```typescript
271
- * const balance = await client.getBalance();
272
- * console.log('Balance:', balance, 'wei');
273
- * ```
274
- */
275
- async getBalance(): Promise<bigint> {
276
-
277
-
278
- try {
279
- const balance = await this.irys.getLoadedBalance();
280
- return BigInt(balance.toString());
281
- } catch (error: any) {
282
- // P0-2: Sanitize error message
283
- throw new StorageError('balance', `Failed to get Irys balance: ${sanitizeErrorMessage(error)}`);
284
- }
285
- }
286
-
287
- /**
288
- * Estimate cost of uploading data
289
- *
290
- * @param sizeBytes - Size of data to upload in bytes
291
- * @returns Cost in payment currency (wei for ETH)
292
- *
293
- * @example
294
- * ```typescript
295
- * const cost = await client.estimateCost(50 * 1024); // 50KB
296
- * console.log('Upload cost:', cost, 'wei');
297
- * ```
298
- */
299
- async estimateCost(sizeBytes: number): Promise<bigint> {
300
-
301
-
302
- if (sizeBytes <= 0) {
303
- throw new ValidationError('sizeBytes', 'Size must be positive');
304
- }
305
-
306
- try {
307
- const price = await this.irys.getPrice(sizeBytes);
308
- return BigInt(price.toString());
309
- } catch (error: any) {
310
- // P0-2: Sanitize error message
311
- throw new StorageError('estimate', `Failed to estimate cost: ${sanitizeErrorMessage(error)}`, {
312
- sizeBytes
313
- });
314
- }
315
- }
316
-
317
- // ==========================================================================
318
- // Upload Methods
319
- // ==========================================================================
320
-
321
- /**
322
- * Upload archive bundle to Arweave (permanent storage)
323
- *
324
- * IMPORTANT: This is the first step in the archive flow.
325
- * After getting the TX ID, anchor it on-chain via ArchiveTreasury.anchorArchive()
326
- *
327
- * @param bundle - Archive bundle to upload
328
- * @returns Upload result with Arweave TX ID
329
- * @throws {InsufficientBalanceError} If Irys balance is too low
330
- * @throws {ArweaveUploadError} If upload fails
331
- *
332
- * @example
333
- * ```typescript
334
- * const result = await client.uploadBundle(bundle);
335
- * console.log('Arweave TX ID:', result.txId);
336
- *
337
- * // Then anchor on-chain
338
- * await archiveTreasury.anchorArchive(bundle.txId, result.txId);
339
- * ```
340
- */
341
- async uploadBundle(bundle: ArchiveBundle): Promise<ArweaveUploadResult> {
342
-
343
-
344
- // Validate bundle
345
- this.validateBundle(bundle);
346
-
347
- // Serialize bundle
348
- const jsonString = JSON.stringify(bundle);
349
- const buffer = Buffer.from(jsonString, 'utf-8');
350
-
351
- // Check balance
352
- const cost = await this.estimateCost(buffer.length);
353
- const balance = await this.getBalance();
354
-
355
- if (balance < cost) {
356
- throw new InsufficientBalanceError(
357
- cost.toString(),
358
- balance.toString(),
359
- this.config.currency.toUpperCase()
360
- );
361
- }
362
-
363
- try {
364
- // Upload with tags for discoverability
365
- const tx = await this.withTimeout(
366
- this.irys.upload(buffer, {
367
- tags: [
368
- { name: 'Content-Type', value: 'application/json' },
369
- { name: 'Protocol', value: 'AGIRAILS' },
370
- { name: 'Version', value: bundle.protocolVersion },
371
- { name: 'Schema', value: bundle.archiveSchemaVersion },
372
- { name: 'Type', value: bundle.type },
373
- { name: 'ChainId', value: bundle.chainId.toString() },
374
- { name: 'TxId', value: bundle.txId }
375
- ]
376
- }),
377
- this.config.timeout,
378
- 'upload'
379
- );
380
-
381
- return {
382
- txId: tx.id,
383
- size: buffer.length,
384
- uploadedAt: new Date(),
385
- cost: cost.toString()
386
- };
387
- } catch (error: any) {
388
- if (error instanceof ArweaveTimeoutError) throw error;
389
- if (error instanceof InsufficientBalanceError) throw error;
390
-
391
- throw new ArweaveUploadError(error.message || 'Upload failed', {
392
- bundleTxId: bundle.txId,
393
- size: buffer.length
394
- });
395
- }
396
- }
397
-
398
- /**
399
- * Upload arbitrary JSON to Arweave
400
- *
401
- * For general-purpose permanent storage (not archive bundles).
402
- *
403
- * @param data - JSON data to upload
404
- * @param tags - Optional tags for discoverability
405
- * @returns Upload result with Arweave TX ID
406
- */
407
- async uploadJSON(
408
- data: unknown,
409
- tags?: Array<{ name: string; value: string }>
410
- ): Promise<ArweaveUploadResult> {
411
-
412
-
413
- const jsonString = JSON.stringify(data);
414
- const buffer = Buffer.from(jsonString, 'utf-8');
415
-
416
- // Check balance
417
- const cost = await this.estimateCost(buffer.length);
418
- const balance = await this.getBalance();
419
-
420
- if (balance < cost) {
421
- throw new InsufficientBalanceError(
422
- cost.toString(),
423
- balance.toString(),
424
- this.config.currency.toUpperCase()
425
- );
426
- }
427
-
428
- try {
429
- const defaultTags = [
430
- { name: 'Content-Type', value: 'application/json' },
431
- { name: 'Protocol', value: 'AGIRAILS' }
432
- ];
433
-
434
- const tx = await this.withTimeout(
435
- this.irys.upload(buffer, {
436
- tags: tags ? [...defaultTags, ...tags] : defaultTags
437
- }),
438
- this.config.timeout,
439
- 'upload'
440
- );
441
-
442
- return {
443
- txId: tx.id,
444
- size: buffer.length,
445
- uploadedAt: new Date(),
446
- cost: cost.toString()
447
- };
448
- } catch (error: any) {
449
- if (error instanceof ArweaveTimeoutError) throw error;
450
- if (error instanceof InsufficientBalanceError) throw error;
451
-
452
- throw new ArweaveUploadError(error.message || 'JSON upload failed', {
453
- size: buffer.length
454
- });
455
- }
456
- }
457
-
458
- // ==========================================================================
459
- // Download Methods
460
- // ==========================================================================
461
-
462
- /**
463
- * Download archive bundle from Arweave
464
- *
465
- * Security Features:
466
- * - Size limit enforcement (P1-1: DoS protection)
467
- * - Retry with exponential backoff (P1-2)
468
- * - Centralized TX ID validation (P1-3)
469
- * - Credential sanitization in errors (P0-2)
470
- *
471
- * @param txId - Arweave transaction ID
472
- * @returns Downloaded archive bundle
473
- * @throws {InvalidArweaveTxIdError} If TX ID format is invalid
474
- * @throws {FileSizeLimitExceededError} If content exceeds size limit
475
- * @throws {ArweaveDownloadError} If download fails
476
- *
477
- * @example
478
- * ```typescript
479
- * const result = await client.downloadBundle('h7Xk2...');
480
- * console.log('Bundle:', result.data);
481
- * ```
482
- */
483
- async downloadBundle(txId: string): Promise<DownloadResult<ArchiveBundle>> {
484
- // P1-3: Use centralized TX ID validation
485
- validateArweaveTxId(txId);
486
-
487
- const url = `${ARWEAVE_GATEWAY}${txId}`;
488
-
489
- // Circuit breaker: check gateway health before attempting download
490
- if (this.circuitBreaker && !this.circuitBreaker.isHealthy(ARWEAVE_GATEWAY)) {
491
- const state = this.circuitBreaker.getState(ARWEAVE_GATEWAY);
492
- const failures = this.circuitBreaker.getFailureCount(ARWEAVE_GATEWAY);
493
- throw new ArweaveDownloadError(
494
- txId,
495
- `Gateway circuit breaker OPEN for ${ARWEAVE_GATEWAY}. ` +
496
- `State: ${state}, Failures: ${failures}. ` +
497
- `Please wait for cooldown period before retrying.`
498
- );
499
- }
500
-
501
- // P1-2: Wrap in retry logic
502
- return withRetry(
503
- async () => {
504
- const controller = new AbortController();
505
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
506
-
507
- try {
508
- const response = await fetch(url, {
509
- signal: controller.signal,
510
- headers: { 'Accept': 'application/json' }
511
- });
512
-
513
- clearTimeout(timeoutId);
514
-
515
- if (!response.ok) {
516
- throw new ArweaveDownloadError(
517
- txId,
518
- `HTTP ${response.status}: ${response.statusText}`
519
- );
520
- }
521
-
522
- // P1-1: Check Content-Length header before downloading
523
- const contentLength = response.headers.get('content-length');
524
- if (contentLength && parseInt(contentLength, 10) > this.maxDownloadSize) {
525
- throw new FileSizeLimitExceededError(
526
- parseInt(contentLength, 10),
527
- this.maxDownloadSize
528
- );
529
- }
530
-
531
- // P1-1: Stream response with size limit enforcement
532
- const reader = response.body?.getReader();
533
- if (!reader) {
534
- throw new ArweaveDownloadError(txId, 'No response body');
535
- }
536
-
537
- const chunks: Uint8Array[] = [];
538
- let totalSize = 0;
539
-
540
- // eslint-disable-next-line no-constant-condition
541
- while (true) {
542
- const { done, value } = await reader.read();
543
- if (done) break;
544
-
545
- totalSize += value.length;
546
-
547
- // P1-1: Enforce size limit during streaming
548
- if (totalSize > this.maxDownloadSize) {
549
- reader.cancel();
550
- throw new FileSizeLimitExceededError(totalSize, this.maxDownloadSize);
551
- }
552
-
553
- chunks.push(value);
554
- }
555
-
556
- // Combine chunks into text
557
- const decoder = new TextDecoder();
558
- const text = chunks.map(chunk => decoder.decode(chunk, { stream: true })).join('') +
559
- decoder.decode();
560
-
561
- const data = JSON.parse(text) as ArchiveBundle;
562
-
563
- // Validate it's actually an archive bundle
564
- if (data.type !== ARCHIVE_BUNDLE_TYPE) {
565
- throw new ArweaveDownloadError(
566
- txId,
567
- `Invalid bundle type: ${data.type}. Expected: ${ARCHIVE_BUNDLE_TYPE}`
568
- );
569
- }
570
-
571
- // Circuit breaker: record success
572
- this.circuitBreaker?.recordSuccess(ARWEAVE_GATEWAY);
573
-
574
- return {
575
- data,
576
- size: totalSize,
577
- downloadedAt: new Date()
578
- };
579
- } catch (error: any) {
580
- clearTimeout(timeoutId);
581
-
582
- // Circuit breaker: record failure only for gateway issues (not content errors)
583
- if (this.isGatewayFailure(error)) {
584
- this.circuitBreaker?.recordFailure(ARWEAVE_GATEWAY);
585
- }
586
-
587
- if (error instanceof ArweaveDownloadError) throw error;
588
- if (error instanceof FileSizeLimitExceededError) throw error;
589
-
590
- if (error.name === 'AbortError') {
591
- throw new ArweaveTimeoutError('download', this.config.timeout);
592
- }
593
-
594
- if (error instanceof SyntaxError) {
595
- throw new ArweaveDownloadError(txId, 'Invalid JSON content');
596
- }
597
-
598
- // P0-2: Sanitize error message
599
- throw new ArweaveDownloadError(txId, sanitizeErrorMessage(error));
600
- }
601
- },
602
- this.retryOptions
603
- );
604
- }
605
-
606
- /**
607
- * Download arbitrary JSON from Arweave
608
- *
609
- * Security Features:
610
- * - Size limit enforcement (P1-1: DoS protection)
611
- * - Retry with exponential backoff (P1-2)
612
- * - Centralized TX ID validation (P1-3)
613
- *
614
- * @param txId - Arweave transaction ID
615
- * @returns Downloaded JSON data
616
- */
617
- async downloadJSON<T = unknown>(txId: string): Promise<DownloadResult<T>> {
618
- // P1-3: Use centralized TX ID validation
619
- validateArweaveTxId(txId);
620
-
621
- const url = `${ARWEAVE_GATEWAY}${txId}`;
622
-
623
- // Circuit breaker: check gateway health before attempting download
624
- if (this.circuitBreaker && !this.circuitBreaker.isHealthy(ARWEAVE_GATEWAY)) {
625
- const state = this.circuitBreaker.getState(ARWEAVE_GATEWAY);
626
- const failures = this.circuitBreaker.getFailureCount(ARWEAVE_GATEWAY);
627
- throw new ArweaveDownloadError(
628
- txId,
629
- `Gateway circuit breaker OPEN for ${ARWEAVE_GATEWAY}. ` +
630
- `State: ${state}, Failures: ${failures}. ` +
631
- `Please wait for cooldown period before retrying.`
632
- );
633
- }
634
-
635
- // P1-2: Wrap in retry logic
636
- return withRetry(
637
- async () => {
638
- const controller = new AbortController();
639
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
640
-
641
- try {
642
- const response = await fetch(url, {
643
- signal: controller.signal,
644
- headers: { 'Accept': 'application/json' }
645
- });
646
-
647
- clearTimeout(timeoutId);
648
-
649
- if (!response.ok) {
650
- throw new ArweaveDownloadError(
651
- txId,
652
- `HTTP ${response.status}: ${response.statusText}`
653
- );
654
- }
655
-
656
- // P1-1: Check Content-Length header before downloading
657
- const contentLength = response.headers.get('content-length');
658
- if (contentLength && parseInt(contentLength, 10) > this.maxDownloadSize) {
659
- throw new FileSizeLimitExceededError(
660
- parseInt(contentLength, 10),
661
- this.maxDownloadSize
662
- );
663
- }
664
-
665
- // P1-1: Stream response with size limit enforcement
666
- const reader = response.body?.getReader();
667
- if (!reader) {
668
- throw new ArweaveDownloadError(txId, 'No response body');
669
- }
670
-
671
- const chunks: Uint8Array[] = [];
672
- let totalSize = 0;
673
-
674
- // eslint-disable-next-line no-constant-condition
675
- while (true) {
676
- const { done, value } = await reader.read();
677
- if (done) break;
678
-
679
- totalSize += value.length;
680
-
681
- // P1-1: Enforce size limit during streaming
682
- if (totalSize > this.maxDownloadSize) {
683
- reader.cancel();
684
- throw new FileSizeLimitExceededError(totalSize, this.maxDownloadSize);
685
- }
686
-
687
- chunks.push(value);
688
- }
689
-
690
- // Combine chunks into text
691
- const decoder = new TextDecoder();
692
- const text = chunks.map(chunk => decoder.decode(chunk, { stream: true })).join('') +
693
- decoder.decode();
694
-
695
- const data = JSON.parse(text) as T;
696
-
697
- // Circuit breaker: record success
698
- this.circuitBreaker?.recordSuccess(ARWEAVE_GATEWAY);
699
-
700
- return {
701
- data,
702
- size: totalSize,
703
- downloadedAt: new Date()
704
- };
705
- } catch (error: any) {
706
- clearTimeout(timeoutId);
707
-
708
- // Circuit breaker: record failure only for gateway issues (not content errors)
709
- if (this.isGatewayFailure(error)) {
710
- this.circuitBreaker?.recordFailure(ARWEAVE_GATEWAY);
711
- }
712
-
713
- if (error instanceof ArweaveDownloadError) throw error;
714
- if (error instanceof FileSizeLimitExceededError) throw error;
715
-
716
- if (error.name === 'AbortError') {
717
- throw new ArweaveTimeoutError('download', this.config.timeout);
718
- }
719
-
720
- if (error instanceof SyntaxError) {
721
- throw new ArweaveDownloadError(txId, 'Invalid JSON content');
722
- }
723
-
724
- // P0-2: Sanitize error message
725
- throw new ArweaveDownloadError(txId, sanitizeErrorMessage(error));
726
- }
727
- },
728
- this.retryOptions
729
- );
730
- }
731
-
732
- /**
733
- * Check if content exists on Arweave
734
- *
735
- * @param txId - Arweave transaction ID
736
- * @returns True if content exists
737
- */
738
- async exists(txId: string): Promise<boolean> {
739
- // P1-3: Use centralized TX ID validation
740
- validateArweaveTxId(txId);
741
-
742
- const url = `${ARWEAVE_GATEWAY}${txId}`;
743
-
744
- try {
745
- const controller = new AbortController();
746
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
747
-
748
- const response = await fetch(url, {
749
- method: 'HEAD',
750
- signal: controller.signal
751
- });
752
-
753
- clearTimeout(timeoutId);
754
-
755
- return response.ok;
756
- } catch {
757
- return false;
758
- }
759
- }
760
-
761
- // ==========================================================================
762
- // Getters
763
- // ==========================================================================
764
-
765
- /**
766
- * Get configured currency
767
- */
768
- getCurrency(): IrysCurrency {
769
- return this.config.currency;
770
- }
771
-
772
- /**
773
- * Get configured network
774
- */
775
- getNetwork(): IrysNetwork {
776
- return this.config.network;
777
- }
778
-
779
- /**
780
- * Get wallet address
781
- */
782
- async getAddress(): Promise<string> {
783
-
784
- return this.irys.address;
785
- }
786
-
787
- // ==========================================================================
788
- // Private Methods
789
- // ==========================================================================
790
-
791
-
792
- /**
793
- * Validate archive bundle structure
794
- */
795
- private validateBundle(bundle: ArchiveBundle): void {
796
- if (!bundle) {
797
- throw new ValidationError('bundle', 'Archive bundle is required');
798
- }
799
-
800
- if (bundle.type !== ARCHIVE_BUNDLE_TYPE) {
801
- throw new ValidationError(
802
- 'bundle.type',
803
- `Invalid bundle type: ${bundle.type}. Expected: ${ARCHIVE_BUNDLE_TYPE}`
804
- );
805
- }
806
-
807
- if (!bundle.txId || !/^0x[a-fA-F0-9]{64}$/.test(bundle.txId)) {
808
- throw new ValidationError('bundle.txId', 'Invalid transaction ID format');
809
- }
810
-
811
- if (![8453, 84532].includes(bundle.chainId)) {
812
- throw new ValidationError(
813
- 'bundle.chainId',
814
- `Invalid chain ID: ${bundle.chainId}. Expected 8453 or 84532`
815
- );
816
- }
817
-
818
- if (!bundle.participants?.requester || !bundle.participants?.provider) {
819
- throw new ValidationError('bundle.participants', 'Both requester and provider required');
820
- }
821
-
822
- if (!bundle.references?.requestCID || !bundle.references?.deliveryCID) {
823
- throw new ValidationError('bundle.references', 'Both requestCID and deliveryCID required');
824
- }
825
-
826
- if (!bundle.hashes?.requestHash || !bundle.hashes?.deliveryHash || !bundle.hashes?.serviceHash) {
827
- throw new ValidationError('bundle.hashes', 'All hashes required');
828
- }
829
-
830
- if (!bundle.settlement) {
831
- throw new ValidationError('bundle.settlement', 'Settlement info required');
832
- }
833
- }
834
-
835
- /**
836
- * Wrap promise with timeout
837
- */
838
- private async withTimeout<T>(
839
- promise: Promise<T>,
840
- timeoutMs: number,
841
- operation: string
842
- ): Promise<T> {
843
- let timeoutId: NodeJS.Timeout;
844
-
845
- const timeoutPromise = new Promise<never>((_, reject) => {
846
- timeoutId = setTimeout(() => {
847
- reject(new ArweaveTimeoutError(operation, timeoutMs));
848
- }, timeoutMs);
849
- });
850
-
851
- try {
852
- return await Promise.race([promise, timeoutPromise]);
853
- } finally {
854
- clearTimeout(timeoutId!);
855
- }
856
- }
857
-
858
- /**
859
- * Determine if an error represents a gateway failure vs content-specific error
860
- *
861
- * Gateway failures (should trigger circuit breaker):
862
- * - 5xx HTTP errors (server errors)
863
- * - Network timeouts
864
- * - Connection refused
865
- * - DNS resolution failures
866
- *
867
- * Content-specific errors (should NOT trigger circuit breaker):
868
- * - 404 (content not found - not gateway's fault)
869
- * - 400 (bad request - client's fault)
870
- * - SyntaxError (invalid JSON - content issue)
871
- * - Invalid bundle type (content validation failed)
872
- */
873
- private isGatewayFailure(error: any): boolean {
874
- // Network-level errors are always gateway failures
875
- if (error.name === 'AbortError') return true; // Timeout
876
- if (error.code === 'ECONNREFUSED') return true;
877
- if (error.code === 'ENOTFOUND') return true;
878
- if (error.code === 'ETIMEDOUT') return true;
879
-
880
- // ArweaveTimeoutError is a gateway failure
881
- if (error instanceof ArweaveTimeoutError) return true;
882
-
883
- // Content-specific errors are NOT gateway failures
884
- if (error instanceof SyntaxError) return false; // Invalid JSON
885
- if (error instanceof FileSizeLimitExceededError) return false;
886
-
887
- // Check for HTTP status codes in ArweaveDownloadError
888
- if (error instanceof ArweaveDownloadError) {
889
- const message = error.message || '';
890
- // 5xx errors are gateway failures
891
- if (/HTTP 5\d{2}/.test(message)) return true;
892
- // 4xx errors are NOT gateway failures (content/client issues)
893
- if (/HTTP 4\d{2}/.test(message)) return false;
894
- // "Invalid bundle type" is content validation failure
895
- if (message.includes('Invalid bundle type')) return false;
896
- // "Invalid JSON content" is content error
897
- if (message.includes('Invalid JSON')) return false;
898
- // Default: treat unknown ArweaveDownloadError as potential gateway issue
899
- return true;
900
- }
901
-
902
- // Generic errors (TypeError, etc.) could be network issues
903
- return true;
904
- }
905
-
906
- // ==========================================================================
907
- // Circuit Breaker Methods
908
- // ==========================================================================
909
-
910
- /**
911
- * Get circuit breaker status for Arweave gateway
912
- *
913
- * @returns Circuit breaker status or null if disabled
914
- *
915
- * @example
916
- * ```typescript
917
- * const status = client.getCircuitBreakerStatus();
918
- * if (status && status.state === 'OPEN') {
919
- * console.log('Gateway unhealthy, failures:', status.failures);
920
- * }
921
- * ```
922
- */
923
- getCircuitBreakerStatus(): { state: string; failures: number } | null {
924
- if (!this.circuitBreaker) return null;
925
-
926
- return {
927
- state: this.circuitBreaker.getState(ARWEAVE_GATEWAY),
928
- failures: this.circuitBreaker.getFailureCount(ARWEAVE_GATEWAY)
929
- };
930
- }
931
-
932
- /**
933
- * Reset circuit breaker for Arweave gateway
934
- *
935
- * Use with caution - only reset when you're confident the gateway is healthy.
936
- * Useful for testing or after manual verification.
937
- *
938
- * @example
939
- * ```typescript
940
- * client.resetCircuitBreaker();
941
- * ```
942
- */
943
- resetCircuitBreaker(): void {
944
- this.circuitBreaker?.reset(ARWEAVE_GATEWAY);
945
- }
946
- }