@agirails/sdk 2.5.3 → 2.5.5

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