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