@bsv/sdk 1.6.8 → 1.6.10

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 (92) hide show
  1. package/README.md +9 -4
  2. package/dist/cjs/package.json +7 -5
  3. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js +11 -3
  4. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  5. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +1 -1
  6. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  7. package/dist/cjs/src/wallet/substrates/utils/toOriginHeader.js +21 -0
  8. package/dist/cjs/src/wallet/substrates/utils/toOriginHeader.js.map +1 -0
  9. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  10. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js +9 -1
  11. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  12. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +1 -1
  13. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  14. package/dist/esm/src/wallet/substrates/utils/toOriginHeader.js +17 -0
  15. package/dist/esm/src/wallet/substrates/utils/toOriginHeader.js.map +1 -0
  16. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  17. package/dist/types/src/wallet/substrates/HTTPWalletJSON.d.ts.map +1 -1
  18. package/dist/types/src/wallet/substrates/utils/toOriginHeader.d.ts +2 -0
  19. package/dist/types/src/wallet/substrates/utils/toOriginHeader.d.ts.map +1 -0
  20. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  21. package/dist/umd/bundle.js +1 -1
  22. package/docs/concepts/beef.md +84 -0
  23. package/docs/concepts/chain-tracking.md +122 -0
  24. package/docs/concepts/decentralized-identity.md +184 -0
  25. package/docs/concepts/fees.md +217 -0
  26. package/docs/concepts/identity-certificates.md +255 -0
  27. package/docs/concepts/index.md +62 -0
  28. package/docs/concepts/key-management.md +176 -0
  29. package/docs/concepts/script-templates.md +163 -0
  30. package/docs/concepts/sdk-philosophy.md +72 -0
  31. package/docs/concepts/signatures.md +179 -0
  32. package/docs/concepts/spv-verification.md +106 -0
  33. package/docs/concepts/transaction-encoding.md +148 -0
  34. package/docs/concepts/transaction-structure.md +63 -0
  35. package/docs/concepts/trust-model.md +123 -0
  36. package/docs/concepts/verification.md +219 -0
  37. package/docs/concepts/wallet-integration.md +95 -0
  38. package/docs/guides/direct-transaction-creation.md +137 -0
  39. package/docs/guides/http-client-configuration.md +414 -0
  40. package/docs/guides/index.md +30 -0
  41. package/docs/guides/transaction-signing-methods.md +268 -0
  42. package/docs/index.md +74 -0
  43. package/docs/reference/arc-config.md +698 -0
  44. package/docs/reference/brc-100.md +33 -0
  45. package/docs/reference/configuration.md +829 -0
  46. package/docs/reference/debugging.md +700 -0
  47. package/docs/reference/errors.md +547 -0
  48. package/docs/reference/index.md +98 -0
  49. package/docs/reference/network-config.md +914 -0
  50. package/docs/reference/op-codes.md +306 -0
  51. package/docs/reference/transaction-signatures.md +94 -0
  52. package/docs/{wallet.md → reference/wallet.md} +9 -0
  53. package/docs/requirements.txt +3 -0
  54. package/docs/tutorials/advanced-transaction.md +575 -0
  55. package/docs/tutorials/aes-encryption.md +947 -0
  56. package/docs/tutorials/authfetch-tutorial.md +957 -0
  57. package/docs/tutorials/ecdh-key-exchange.md +547 -0
  58. package/docs/tutorials/elliptic-curve-fundamentals.md +603 -0
  59. package/docs/tutorials/error-handling.md +1215 -0
  60. package/docs/tutorials/first-transaction-low-level.md +204 -0
  61. package/docs/tutorials/first-transaction.md +278 -0
  62. package/docs/tutorials/hashes-and-hmacs.md +814 -0
  63. package/docs/tutorials/identity-management.md +702 -0
  64. package/docs/tutorials/index.md +182 -0
  65. package/docs/tutorials/key-management.md +536 -0
  66. package/docs/tutorials/protowallet-development.md +716 -0
  67. package/docs/tutorials/script-construction.md +690 -0
  68. package/docs/tutorials/spv-merkle-proofs.md +682 -0
  69. package/docs/tutorials/testnet-transactions-low-level.md +352 -0
  70. package/docs/tutorials/transaction-broadcasting.md +535 -0
  71. package/docs/tutorials/transaction-types.md +419 -0
  72. package/docs/tutorials/type-42.md +582 -0
  73. package/docs/tutorials/uhrp-storage.md +579 -0
  74. package/package.json +7 -5
  75. package/src/transaction/__tests/Transaction.test.ts +1 -1
  76. package/src/wallet/substrates/HTTPWalletJSON.ts +11 -1
  77. package/src/wallet/substrates/WalletWireProcessor.ts +1 -1
  78. package/src/wallet/substrates/__tests/toOriginHeader.test.ts +34 -0
  79. package/src/wallet/substrates/utils/toOriginHeader.ts +15 -0
  80. package/docs/README.md +0 -21
  81. /package/docs/{auth.md → reference/auth.md} +0 -0
  82. /package/docs/{compat.md → reference/compat.md} +0 -0
  83. /package/docs/{identity.md → reference/identity.md} +0 -0
  84. /package/docs/{kvstore.md → reference/kvstore.md} +0 -0
  85. /package/docs/{messages.md → reference/messages.md} +0 -0
  86. /package/docs/{overlay-tools.md → reference/overlay-tools.md} +0 -0
  87. /package/docs/{primitives.md → reference/primitives.md} +0 -0
  88. /package/docs/{registry.md → reference/registry.md} +0 -0
  89. /package/docs/{script.md → reference/script.md} +0 -0
  90. /package/docs/{storage.md → reference/storage.md} +0 -0
  91. /package/docs/{totp.md → reference/totp.md} +0 -0
  92. /package/docs/{transaction.md → reference/transaction.md} +0 -0
@@ -0,0 +1,579 @@
1
+ # Decentralized File Storage with UHRP
2
+
3
+ **Duration**: 75 minutes
4
+ **Prerequisites**: Node.js, basic TypeScript knowledge, understanding of decentralized storage and `WalletClient` usage
5
+ **Learning Goals**:
6
+ - Understand UHRP (Universal Hash Resource Protocol)
7
+ - Upload and download files using StorageUploader/StorageDownloader
8
+ - Implement decentralized file management systems
9
+ - Handle file integrity verification and expiration
10
+
11
+ ## Introduction
12
+
13
+ UHRP (Universal Hash Resource Protocol) is a decentralized file storage system that uses content hashing for addressing and retrieval. The BSV SDK provides `StorageUploader` and `StorageDownloader` classes for seamless integration with UHRP storage networks.
14
+
15
+ ## Prerequisites
16
+
17
+ ### For Upload Operations
18
+ - **BRC-100 compliant wallet** (such as MetaNet Desktop Wallet) must be installed and running
19
+ - **Wallet connection** accessible via JSON API (typically http://localhost:3321)
20
+ - **Sufficient wallet balance** for transaction fees and storage costs
21
+ - **UHRP storage service** - This tutorial uses `https://nanostore.babbage.systems`
22
+
23
+ ### For Download Operations Only
24
+ - **No wallet connection required** - downloads work independently
25
+ - **Network access** to resolve UHRP URLs via lookup services
26
+
27
+ ### Service Availability
28
+ **Important Note**: This tutorial uses `https://nanostore.babbage.systems`, which is a working UHRP storage service. The examples demonstrate correct SDK usage patterns and will work with:
29
+ - A running BRC-100 compliant wallet (such as MetaNet Desktop Wallet)
30
+ - Sufficient wallet balance for storage fees
31
+
32
+ **Performance Note**: UHRP storage operations may take time to complete as they involve blockchain transactions and network propagation. Upload operations can take 10-30 seconds or more depending on network conditions.
33
+
34
+ **Network Propagation**: After uploading, files typically take 30-60 seconds to propagate across the UHRP network before they become available for download. This is normal behavior for decentralized storage systems and ensures content integrity verification.
35
+
36
+ ## Key Features
37
+
38
+ - **Content-Addressed Storage**: Files identified by their hash
39
+ - **Decentralized Retrieval**: Multiple storage providers
40
+ - **Integrity Verification**: Automatic hash validation
41
+ - **Expiration Management**: Time-based file retention
42
+ - **Authenticated Upload**: Wallet-based authentication
43
+
44
+ ## What You'll Build
45
+
46
+ - File upload system with UHRP
47
+ - Decentralized file retrieval
48
+ - File management dashboard
49
+ - Integrity verification system
50
+
51
+ ## Setting Up UHRP Storage
52
+
53
+ ### Basic File Upload
54
+
55
+ ```typescript
56
+ import { StorageUploader, WalletClient } from '@bsv/sdk'
57
+
58
+ async function basicFileUpload() {
59
+ const wallet = new WalletClient('auto', 'localhost')
60
+
61
+ const uploader = new StorageUploader({
62
+ storageURL: 'https://nanostore.babbage.systems',
63
+ wallet
64
+ })
65
+
66
+ // Create sample file
67
+ const fileData = new TextEncoder().encode('Hello, UHRP storage!')
68
+ const file = {
69
+ data: Array.from(fileData),
70
+ type: 'text/plain'
71
+ }
72
+
73
+ try {
74
+ const result = await uploader.publishFile({
75
+ file,
76
+ retentionPeriod: 60 * 24 * 7 // 7 days in minutes
77
+ })
78
+
79
+ console.log('File uploaded successfully!')
80
+ console.log('UHRP URL:', result.uhrpURL)
81
+ console.log('Published:', result.published)
82
+
83
+ return result
84
+ } catch (error) {
85
+ console.error('Upload failed:', error)
86
+ throw error
87
+ }
88
+ }
89
+
90
+ basicFileUpload().catch(console.error)
91
+ ```
92
+
93
+ ### File Download and Verification
94
+
95
+ ```typescript
96
+ import { StorageDownloader } from '@bsv/sdk'
97
+
98
+ async function basicFileDownload(uhrpUrl: string) {
99
+ const downloader = new StorageDownloader({
100
+ networkPreset: 'mainnet'
101
+ })
102
+
103
+ try {
104
+ console.log('Downloading file:', uhrpUrl)
105
+
106
+ const result = await downloader.download(uhrpUrl)
107
+
108
+ console.log('File downloaded successfully!')
109
+ console.log('MIME Type:', result.mimeType)
110
+ console.log('Content length:', result.data.length, 'bytes')
111
+
112
+ // Convert to string if text file
113
+ if (result.mimeType?.startsWith('text/')) {
114
+ const content = new TextDecoder().decode(new Uint8Array(result.data))
115
+ console.log('Content:', content)
116
+ }
117
+
118
+ return result
119
+ } catch (error) {
120
+ console.error('Download failed:', error)
121
+ throw error
122
+ }
123
+ }
124
+
125
+ // Example usage (replace with actual UHRP URL)
126
+ // basicFileDownload('uhrp://abc123...').catch(console.error)
127
+ ```
128
+
129
+ ## Complete File Management System
130
+
131
+ ### File Manager Class
132
+
133
+ ```typescript
134
+ import { StorageUploader, StorageDownloader, WalletClient } from '@bsv/sdk'
135
+
136
+ interface FileMetadata {
137
+ uhrpUrl: string
138
+ originalName: string
139
+ mimeType: string
140
+ size: number
141
+ uploadDate: Date
142
+ expiryDate: Date
143
+ tags: string[]
144
+ }
145
+
146
+ class UHRPFileManager {
147
+ private uploader: StorageUploader
148
+ private downloader: StorageDownloader
149
+ private fileRegistry: Map<string, FileMetadata> = new Map()
150
+
151
+ constructor(storageURL: string, wallet?: WalletClient) {
152
+ this.uploader = new StorageUploader({
153
+ storageURL,
154
+ wallet: wallet || new WalletClient('auto', 'localhost')
155
+ })
156
+
157
+ this.downloader = new StorageDownloader({
158
+ networkPreset: 'mainnet'
159
+ })
160
+ }
161
+
162
+ async uploadFile(
163
+ fileData: Uint8Array,
164
+ fileName: string,
165
+ mimeType: string,
166
+ retentionDays: number = 30,
167
+ tags: string[] = []
168
+ ): Promise<FileMetadata> {
169
+ const file = {
170
+ data: Array.from(fileData),
171
+ type: mimeType
172
+ }
173
+
174
+ const retentionMinutes = retentionDays * 24 * 60
175
+
176
+ try {
177
+ const result = await this.uploader.publishFile({
178
+ file,
179
+ retentionPeriod: retentionMinutes
180
+ })
181
+
182
+ const metadata: FileMetadata = {
183
+ uhrpUrl: result.uhrpURL,
184
+ originalName: fileName,
185
+ mimeType,
186
+ size: fileData.length,
187
+ uploadDate: new Date(),
188
+ expiryDate: new Date(Date.now() + retentionDays * 24 * 60 * 60 * 1000),
189
+ tags
190
+ }
191
+
192
+ this.fileRegistry.set(result.uhrpURL, metadata)
193
+
194
+ console.log(`File "${fileName}" uploaded successfully`)
195
+ console.log('UHRP URL:', result.uhrpURL)
196
+
197
+ return metadata
198
+ } catch (error) {
199
+ console.error(`Failed to upload "${fileName}":`, error)
200
+ throw error
201
+ }
202
+ }
203
+
204
+ async downloadFile(uhrpUrl: string): Promise<{
205
+ data: Uint8Array
206
+ metadata: FileMetadata | null
207
+ }> {
208
+ try {
209
+ const result = await this.downloader.download(uhrpUrl)
210
+ const metadata = this.fileRegistry.get(uhrpUrl) || null
211
+
212
+ console.log('File downloaded:', uhrpUrl)
213
+
214
+ return {
215
+ data: new Uint8Array(result.data),
216
+ metadata
217
+ }
218
+ } catch (error) {
219
+ console.error('Download failed:', error)
220
+ throw error
221
+ }
222
+ }
223
+
224
+ async getFileInfo(uhrpUrl: string): Promise<any> {
225
+ try {
226
+ return await this.uploader.findFile(uhrpUrl)
227
+ } catch (error) {
228
+ console.error('Failed to get file info:', error)
229
+ throw error
230
+ }
231
+ }
232
+
233
+ async renewFile(uhrpUrl: string, additionalDays: number): Promise<any> {
234
+ const additionalMinutes = additionalDays * 24 * 60
235
+
236
+ try {
237
+ const result = await this.uploader.renewFile(uhrpUrl, additionalMinutes)
238
+
239
+ // Update local metadata if exists
240
+ const metadata = this.fileRegistry.get(uhrpUrl)
241
+ if (metadata) {
242
+ metadata.expiryDate = new Date(Date.now() + additionalDays * 24 * 60 * 60 * 1000)
243
+ this.fileRegistry.set(uhrpUrl, metadata)
244
+ }
245
+
246
+ console.log(`File renewed for ${additionalDays} days`)
247
+ return result
248
+ } catch (error) {
249
+ console.error('Failed to renew file:', error)
250
+ throw error
251
+ }
252
+ }
253
+
254
+ listFiles(tag?: string): FileMetadata[] {
255
+ const files = Array.from(this.fileRegistry.values())
256
+
257
+ if (tag) {
258
+ return files.filter(file => file.tags.includes(tag))
259
+ }
260
+
261
+ return files
262
+ }
263
+
264
+ getExpiringFiles(daysAhead: number = 7): FileMetadata[] {
265
+ const cutoffDate = new Date(Date.now() + daysAhead * 24 * 60 * 60 * 1000)
266
+
267
+ return Array.from(this.fileRegistry.values())
268
+ .filter(file => file.expiryDate <= cutoffDate)
269
+ .sort((a, b) => a.expiryDate.getTime() - b.expiryDate.getTime())
270
+ }
271
+ }
272
+
273
+ async function demonstrateFileManager() {
274
+ const fileManager = new UHRPFileManager('https://nanostore.babbage.systems')
275
+
276
+ console.log('=== UHRP File Manager Demo ===')
277
+
278
+ // Upload different types of files
279
+ const textData = new TextEncoder().encode('This is a text document for UHRP storage.')
280
+ const jsonData = new TextEncoder().encode(JSON.stringify({
281
+ message: 'Hello from UHRP',
282
+ timestamp: new Date().toISOString(),
283
+ data: [1, 2, 3, 4, 5]
284
+ }))
285
+
286
+ try {
287
+ // Upload text file
288
+ const textFile = await fileManager.uploadFile(
289
+ textData,
290
+ 'document.txt',
291
+ 'text/plain',
292
+ 30,
293
+ ['document', 'text']
294
+ )
295
+
296
+ // Upload JSON file
297
+ const jsonFile = await fileManager.uploadFile(
298
+ jsonData,
299
+ 'data.json',
300
+ 'application/json',
301
+ 60,
302
+ ['data', 'json']
303
+ )
304
+
305
+ console.log('\n=== File Registry ===')
306
+ const allFiles = fileManager.listFiles()
307
+ allFiles.forEach(file => {
308
+ console.log(`${file.originalName}: ${file.uhrpUrl}`)
309
+ })
310
+
311
+ // Test download
312
+ console.log('\n=== Testing Download ===')
313
+ const downloadResult = await fileManager.downloadFile(textFile.uhrpUrl)
314
+ const content = new TextDecoder().decode(downloadResult.data)
315
+ console.log('Downloaded content:', content)
316
+
317
+ // Check expiring files
318
+ console.log('\n=== Expiring Files ===')
319
+ const expiringFiles = fileManager.getExpiringFiles(365) // Next year
320
+ expiringFiles.forEach(file => {
321
+ console.log(`${file.originalName} expires: ${file.expiryDate.toISOString()}`)
322
+ })
323
+
324
+ return { textFile, jsonFile, allFiles }
325
+ } catch (error) {
326
+ console.error('Demo failed:', error)
327
+ }
328
+ }
329
+
330
+ demonstrateFileManager().catch(console.error)
331
+
332
+ ## Advanced Features
333
+
334
+ ### Batch Operations
335
+
336
+ ```typescript
337
+ import { StorageUploader, StorageDownloader, WalletClient } from '@bsv/sdk'
338
+
339
+ class BatchFileOperations {
340
+ private uploader: StorageUploader
341
+ private downloader: StorageDownloader
342
+
343
+ constructor(storageURL: string, wallet?: WalletClient) {
344
+ this.uploader = new StorageUploader({
345
+ storageURL,
346
+ wallet: wallet || new WalletClient('auto', 'localhost')
347
+ })
348
+
349
+ this.downloader = new StorageDownloader()
350
+ }
351
+
352
+ async batchUpload(files: Array<{
353
+ data: Uint8Array
354
+ name: string
355
+ type: string
356
+ retention?: number
357
+ }>): Promise<Array<{
358
+ success: boolean
359
+ file: string
360
+ uhrpUrl?: string
361
+ error?: string
362
+ }>> {
363
+ console.log(`Starting batch upload of ${files.length} files...`)
364
+
365
+ const results = await Promise.allSettled(
366
+ files.map(async (file) => {
367
+ const fileObj = {
368
+ data: Array.from(file.data),
369
+ type: file.type
370
+ }
371
+
372
+ const result = await this.uploader.publishFile({
373
+ file: fileObj,
374
+ retentionPeriod: (file.retention || 30) * 24 * 60
375
+ })
376
+
377
+ return { file: file.name, result }
378
+ })
379
+ )
380
+
381
+ return results.map((result, index) => {
382
+ const fileName = files[index].name
383
+
384
+ if (result.status === 'fulfilled') {
385
+ return {
386
+ success: true,
387
+ file: fileName,
388
+ uhrpUrl: result.value.result.uhrpURL
389
+ }
390
+ } else {
391
+ return {
392
+ success: false,
393
+ file: fileName,
394
+ error: result.reason.message
395
+ }
396
+ }
397
+ })
398
+ }
399
+
400
+ async batchDownload(uhrpUrls: string[]): Promise<Array<{
401
+ success: boolean
402
+ url: string
403
+ data?: Uint8Array
404
+ error?: string
405
+ }>> {
406
+ console.log(`Starting batch download of ${uhrpUrls.length} files...`)
407
+
408
+ const results = await Promise.allSettled(
409
+ uhrpUrls.map(url => this.downloader.download(url))
410
+ )
411
+
412
+ return results.map((result, index) => {
413
+ const url = uhrpUrls[index]
414
+
415
+ if (result.status === 'fulfilled') {
416
+ return {
417
+ success: true,
418
+ url,
419
+ data: new Uint8Array(result.value.data)
420
+ }
421
+ } else {
422
+ return {
423
+ success: false,
424
+ url,
425
+ error: result.reason.message
426
+ }
427
+ }
428
+ })
429
+ }
430
+ }
431
+
432
+ async function demonstrateBatchOperations() {
433
+ const batchOps = new BatchFileOperations('https://nanostore.babbage.systems')
434
+
435
+ // Prepare test files
436
+ const testFiles = [
437
+ {
438
+ data: new TextEncoder().encode('File 1 content'),
439
+ name: 'file1.txt',
440
+ type: 'text/plain'
441
+ },
442
+ {
443
+ data: new TextEncoder().encode('File 2 content'),
444
+ name: 'file2.txt',
445
+ type: 'text/plain'
446
+ },
447
+ {
448
+ data: new TextEncoder().encode(JSON.stringify({ test: 'data' })),
449
+ name: 'data.json',
450
+ type: 'application/json'
451
+ }
452
+ ]
453
+
454
+ console.log('=== Batch Upload Demo ===')
455
+ const uploadResults = await batchOps.batchUpload(testFiles)
456
+
457
+ uploadResults.forEach(result => {
458
+ if (result.success) {
459
+ console.log(`✅ ${result.file}: ${result.uhrpUrl}`)
460
+ } else {
461
+ console.log(`❌ ${result.file}: ${result.error}`)
462
+ }
463
+ })
464
+
465
+ // Extract successful URLs for download test
466
+ const successfulUrls = uploadResults
467
+ .filter(r => r.success && r.uhrpUrl)
468
+ .map(r => r.uhrpUrl!)
469
+
470
+ if (successfulUrls.length > 0) {
471
+ console.log('\n=== Batch Download Demo ===')
472
+ const downloadResults = await batchOps.batchDownload(successfulUrls)
473
+
474
+ downloadResults.forEach(result => {
475
+ if (result.success) {
476
+ console.log(`✅ Downloaded: ${result.url} (${result.data?.length} bytes)`)
477
+ } else {
478
+ console.log(`❌ Failed: ${result.url} - ${result.error}`)
479
+ }
480
+ })
481
+ }
482
+
483
+ return { uploadResults, downloadResults: [] }
484
+ }
485
+
486
+ demonstrateBatchOperations().catch(console.error)
487
+
488
+ ## Troubleshooting
489
+
490
+ ### Common Issues and Solutions
491
+
492
+ #### "No wallet available" Error
493
+ **Problem**: StorageUploader fails with "No wallet available over any communication substrate"
494
+ **Solution**:
495
+ - Install and run a BRC-100 compliant wallet (e.g., MetaNet Desktop Wallet)
496
+ - Ensure wallet is accessible at http://localhost:3321
497
+ - Verify wallet is fully synced and has sufficient balance
498
+
499
+ #### "401 Unauthorized" Error
500
+ **Problem**: Upload operations fail with HTTP 401 errors
501
+ **Solution**:
502
+ - Verify your wallet is properly authenticated
503
+ - Check that the UHRP storage service is available
504
+ - Ensure your wallet has sufficient balance for storage fees
505
+
506
+ #### "Invalid parameter UHRP url" Error
507
+ **Problem**: Download operations fail with invalid URL error
508
+ **Solution**:
509
+ - Verify the UHRP URL format (should start with `uhrp://`)
510
+ - Check that the file hasn’t expired
511
+ - Ensure network connectivity for UHRP lookup services
512
+
513
+ #### Download Works but Upload Fails
514
+ **Problem**: StorageDownloader works but StorageUploader fails
515
+ **Solution**: This is expected behavior without a wallet connection. StorageDownloader works independently, while StorageUploader requires wallet authentication.
516
+
517
+ #### Service Unavailable
518
+ **Problem**: UHRP storage service returns errors or is unreachable
519
+ **Solution**:
520
+ - Try alternative UHRP storage services
521
+ - Check service status and availability
522
+ - Consider setting up your own UHRP storage infrastructure
523
+
524
+ ## Best Practices
525
+
526
+ ### 1. File Management
527
+ - Use meaningful file names and metadata
528
+ - Implement proper retention policies
529
+ - Tag files for easy organization and retrieval
530
+
531
+ ### 2. Error Handling
532
+ - Always validate file integrity after download
533
+ - Implement retry logic for network failures
534
+ - Handle storage quota and payment requirements
535
+
536
+ ### 3. Performance
537
+ - Use batch operations for multiple files
538
+ - Implement caching for frequently accessed files
539
+ - Monitor file expiration and renewal needs
540
+
541
+ ### 4. Security
542
+ - Encrypt sensitive files before upload
543
+ - Use authenticated storage endpoints
544
+ - Validate file types and sizes
545
+
546
+ ## Summary
547
+
548
+ In this tutorial, you learned how to:
549
+
550
+ ✅ **Upload files to UHRP storage** with StorageUploader
551
+ ✅ **Download and verify files** with StorageDownloader
552
+ ✅ **Build file management systems** with metadata tracking
553
+ ✅ **Implement batch operations** for multiple files
554
+ ✅ **Handle file expiration** and renewal
555
+
556
+ UHRP provides a robust foundation for decentralized file storage with content addressing and integrity verification.
557
+
558
+ ## Next Steps
559
+
560
+ - Learn about [Identity Management and Certificates](./identity-management.md)
561
+ - Explore [AuthFetch for Authenticated HTTP Requests](./authfetch-tutorial.md)
562
+ - Review [Security Best Practices](../guides/security-best-practices.md)
563
+
564
+ UHRP provides a robust foundation for decentralized file storage with content addressing and integrity verification.
565
+
566
+ The `WalletClient` provides the authentication and payment capabilities needed for UHRP operations.
567
+
568
+ ## Setting Up UHRP with `WalletClient`
569
+
570
+ The `WalletClient` handles authentication automatically when you create `StorageUploader` and `StorageDownloader` instances.
571
+
572
+ ### How `WalletClient` Enables UHRP
573
+
574
+ When you use UHRP with `WalletClient`:
575
+
576
+ - You can upload files to decentralized storage networks.
577
+ - You can download files from decentralized storage networks.
578
+ - You can manage file metadata and track file expiration.
579
+ - You can implement batch operations for multiple files.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.6.8",
3
+ "version": "1.6.10",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -219,11 +219,13 @@
219
219
  "build:umd": "webpack --config webpack.config.js",
220
220
  "dev": "tsc -b -w",
221
221
  "prepublish": "npm run build",
222
- "doc": "ts2md"
222
+ "doc": "ts2md",
223
+ "docs:serve": "mkdocs serve",
224
+ "docs:build": "mkdocs build"
223
225
  },
224
226
  "repository": {
225
227
  "type": "git",
226
- "url": "git+https://github.com/bitcoin-sv/ts-sdk.git"
228
+ "url": "git+https://github.com/bsv-blockchain/ts-sdk.git"
227
229
  },
228
230
  "keywords": [
229
231
  "BSV",
@@ -235,9 +237,9 @@
235
237
  "author": "BSV Blockchain Association",
236
238
  "license": "SEE LICENSE IN LICENSE.txt",
237
239
  "bugs": {
238
- "url": "https://github.com/bitcoin-sv/ts-sdk/issues"
240
+ "url": "https://github.com/bsv-blockchain/ts-sdk/issues"
239
241
  },
240
- "homepage": "https://github.com/bitcoin-sv/ts-sdk#readme",
242
+ "homepage": "https://github.com/bsv-blockchain/ts-sdk#readme",
241
243
  "devDependencies": {
242
244
  "@eslint/js": "^9.23.0",
243
245
  "@jest/globals": "^29.7.0",
@@ -806,7 +806,7 @@ describe('Transaction', () => {
806
806
  expect(beef).toEqual(BRC62Hex)
807
807
  })
808
808
  it('Does not double-encode transactions', () => {
809
- // Source: https://github.com/bitcoin-sv/ts-sdk/issues/77
809
+ // Source: https://github.com/bsv-blockchain/ts-sdk/issues/77
810
810
  const incorrect =
811
811
  '0100beef01fe76eb0c000e02c402deff5437203e0b5cb22646cbada24a60349bf45c8b280ffb755868f2955c3111c500f4076b7f48031fc467f87d5e99d9c3c0b59e4dca5e3049f58b735c59b413a8b6016300bad9c2d948e8a2ca647fdb50f2fd36641c4adf937b41134405a3e7f734b8beb201300053604a579558b5f7030e618d5c726a19229e0ff677f6edf109f41c5cfdafc93e0119005f8465c2a8d1558afbfa80c2395f3f8866a2fa5015e54fab778b0149da58376c010d00cd452b4e74f57d199cdb81b8a0e4a62dcdaf89504d6c63a5a65d5b866912b8c0010700d2ae7e2ce76da560509172066f1a1cf81faf81d73f9c0f6fd5af0904973dcfb10102006e5e077bcaa35c0240d61c1f3bba8d789223711ec035ef88b0911fc569d2b95a010000c961038959b9d404297a180c066816562dd2a34986c0960121a87ba91a51262f010100a50e381b4e8812479ea561e5bab7dcaa80078652b1b39ee5410966c515a3442b010100383ce8891ca7bf1ddefa5e0d8a1ba9ab01cb4e18046e9d7d0d438b5aaecc38b2010100c694be322b4e74acca8a5ef7703afedb708281321fd674f1221eebc743b0e01c0101000f3cc61f2b3d762cfecdd977ba768a5cbb0a4b402ad4f0d1bd3a98a582794c35010100094ad56eeea3b47edb2b298775f2efabe48172612cb3419962632251d8cdb78e010100de84bf9dd8873f37decbda1b5188e24ead978b147a63c809691702d19c47e8cb050200000001b67f1b6a6c3e70742a39b82ba45d51c983f598963ebf237101cc372da1144b83020000006b483045022100d14c3eb0c1438747c124f099bc664bf945cd27cbd96915027057e508bbc9e03302203c73f79d4e00f8018783e1008ce0fbb8e8c58bff6bd8042ab7e3966a66c8788c41210356762156ee9347c3e2ceca17d13713028d5446f453e9cbcb0ea170b4ca7fab52ffffffff037c660000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac75330100000000001976a914eb645f9ea7e4e232e54b9651474c100026698c3088acf2458005000000001976a914802737e30c85b6fe86e26fb28e03140058aca65e88ac0000000001000100000001deff5437203e0b5cb22646cbada24a60349bf45c8b280ffb755868f2955c3111000000006a473044022076da9f61380c208f43652587c219b4452a7b803a0407c2c7c0f3bc27612c4e88022021a9eb02da5529873a5986933f9c35965aa78537b9e2aef9382de33cfb1ab4bb41210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff023c330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac3c330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac00000000000200000001b67f1b6a6c3e70742a39b82ba45d51c983f598963ebf237101cc372da1144b83020000006b483045022100d14c3eb0c1438747c124f099bc664bf945cd27cbd96915027057e508bbc9e03302203c73f79d4e00f8018783e1008ce0fbb8e8c58bff6bd8042ab7e3966a66c8788c41210356762156ee9347c3e2ceca17d13713028d5446f453e9cbcb0ea170b4ca7fab52ffffffff037c660000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac75330100000000001976a914eb645f9ea7e4e232e54b9651474c100026698c3088acf2458005000000001976a914802737e30c85b6fe86e26fb28e03140058aca65e88ac0000000001000100000001deff5437203e0b5cb22646cbada24a60349bf45c8b280ffb755868f2955c3111000000006a473044022076da9f61380c208f43652587c219b4452a7b803a0407c2c7c0f3bc27612c4e88022021a9eb02da5529873a5986933f9c35965aa78537b9e2aef9382de33cfb1ab4bb41210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff023c330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac3c330000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac000000000001000000022e7f69f3e1e17e22cfb8818577b3c83a4fbbbc1bab55c70ffcdd994ae30ea48b000000006b483045022100d9a2d1efea4896b36b2eb5af42cf52009982c7c31b446213fe37f26835d9d72202203e4dee0ceb068a4936e79b0bf69f72203906a00a4256cb1a7b30a40764616e8441210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff2e7f69f3e1e17e22cfb8818577b3c83a4fbbbc1bab55c70ffcdd994ae30ea48b010000006b483045022100b57a09145c57b7b5efb4b546f1b0bfb7adbc5e64d35d9d6989345d4c60c483940220280998a210a49a6efaacda6fb73670001bb7269d069be80eb14ea2227a73e82241210314793e1758db3caa7d2bce97b347ae3ced2f8a402b797ed986be63473d4644a0ffffffff0174660000000000001976a91417c85798ff61f7ec8af257f672d973b6ec6d88fd88ac0000000000'
812
812
  const correct =
@@ -36,6 +36,7 @@ import {
36
36
  VersionString7To30Bytes,
37
37
  } from '../Wallet.interfaces.js'
38
38
  import { WERR_REVIEW_ACTIONS } from '../WERR_REVIEW_ACTIONS.js'
39
+ import { toOriginHeader } from './utils/toOriginHeader.js'
39
40
 
40
41
  export default class HTTPWalletJSON implements WalletInterface {
41
42
  baseUrl: string
@@ -52,14 +53,23 @@ export default class HTTPWalletJSON implements WalletInterface {
52
53
  this.originator = originator
53
54
  this.httpClient = httpClient
54
55
 
56
+ // Detect if we're in a browser environment
57
+ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'
58
+
55
59
  this.api = async (call: string, args: object) => {
60
+ // In browser environments, let the browser handle Origin header automatically
61
+ // In Node.js environments, we need to set it manually if originator is provided
62
+ const origin = !isBrowser && this.originator
63
+ ? toOriginHeader(this.originator, 'http')
64
+ : undefined
65
+
56
66
  const res = await (
57
67
  await httpClient(`${this.baseUrl}/${call}`, {
58
68
  method: 'POST',
59
69
  headers: {
60
70
  Accept: 'application/json',
61
71
  'Content-Type': 'application/json',
62
- Originator: this.originator ?? ''
72
+ ...(origin ? { Origin: origin } : {}),
63
73
  },
64
74
  body: JSON.stringify(args)
65
75
  })
@@ -2094,7 +2094,7 @@ export default class WalletWireProcessor implements WalletWire {
2094
2094
  cert.certifier,
2095
2095
  cert.revocationOutpoint,
2096
2096
  cert.fields,
2097
- cert.signaturre
2097
+ cert.signature
2098
2098
  )
2099
2099
  const certBin = certificate.toBinary()
2100
2100