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