@bsv/sdk 1.4.0 → 1.4.2

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 (80) hide show
  1. package/dist/cjs/mod.js +2 -0
  2. package/dist/cjs/mod.js.map +1 -1
  3. package/dist/cjs/package.json +1 -1
  4. package/dist/cjs/src/identity/IdentityClient.js +258 -0
  5. package/dist/cjs/src/identity/IdentityClient.js.map +1 -0
  6. package/dist/cjs/src/identity/index.js +19 -0
  7. package/dist/cjs/src/identity/index.js.map +1 -0
  8. package/dist/cjs/src/identity/types/index.js +30 -0
  9. package/dist/cjs/src/identity/types/index.js.map +1 -0
  10. package/dist/cjs/src/registry/RegistryClient.js +392 -0
  11. package/dist/cjs/src/registry/RegistryClient.js.map +1 -0
  12. package/dist/cjs/src/registry/index.js +19 -0
  13. package/dist/cjs/src/registry/index.js.map +1 -0
  14. package/dist/cjs/src/registry/types/index.js +3 -0
  15. package/dist/cjs/src/registry/types/index.js.map +1 -0
  16. package/dist/cjs/src/storage/StorageDownloader.js +82 -0
  17. package/dist/cjs/src/storage/StorageDownloader.js.map +1 -0
  18. package/dist/cjs/src/storage/__test/StorageDownloader.test.js +144 -0
  19. package/dist/cjs/src/storage/__test/StorageDownloader.test.js.map +1 -0
  20. package/dist/cjs/src/storage/index.js +3 -1
  21. package/dist/cjs/src/storage/index.js.map +1 -1
  22. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  23. package/dist/esm/mod.js +2 -0
  24. package/dist/esm/mod.js.map +1 -1
  25. package/dist/esm/src/identity/IdentityClient.js +255 -0
  26. package/dist/esm/src/identity/IdentityClient.js.map +1 -0
  27. package/dist/esm/src/identity/index.js +3 -0
  28. package/dist/esm/src/identity/index.js.map +1 -0
  29. package/dist/esm/src/identity/types/index.js +27 -0
  30. package/dist/esm/src/identity/types/index.js.map +1 -0
  31. package/dist/esm/src/registry/RegistryClient.js +388 -0
  32. package/dist/esm/src/registry/RegistryClient.js.map +1 -0
  33. package/dist/esm/src/registry/index.js +3 -0
  34. package/dist/esm/src/registry/index.js.map +1 -0
  35. package/dist/esm/src/registry/types/index.js +2 -0
  36. package/dist/esm/src/registry/types/index.js.map +1 -0
  37. package/dist/esm/src/storage/StorageDownloader.js +75 -0
  38. package/dist/esm/src/storage/StorageDownloader.js.map +1 -0
  39. package/dist/esm/src/storage/__test/StorageDownloader.test.js +139 -0
  40. package/dist/esm/src/storage/__test/StorageDownloader.test.js.map +1 -0
  41. package/dist/esm/src/storage/index.js +1 -0
  42. package/dist/esm/src/storage/index.js.map +1 -1
  43. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  44. package/dist/types/mod.d.ts +2 -0
  45. package/dist/types/mod.d.ts.map +1 -1
  46. package/dist/types/src/identity/IdentityClient.d.ts +50 -0
  47. package/dist/types/src/identity/IdentityClient.d.ts.map +1 -0
  48. package/dist/types/src/identity/index.d.ts +3 -0
  49. package/dist/types/src/identity/index.d.ts.map +1 -0
  50. package/dist/types/src/identity/types/index.d.ts +30 -0
  51. package/dist/types/src/identity/types/index.d.ts.map +1 -0
  52. package/dist/types/src/registry/RegistryClient.d.ts +94 -0
  53. package/dist/types/src/registry/RegistryClient.d.ts.map +1 -0
  54. package/dist/types/src/registry/index.d.ts +3 -0
  55. package/dist/types/src/registry/index.d.ts.map +1 -0
  56. package/dist/types/src/registry/types/index.d.ts +86 -0
  57. package/dist/types/src/registry/types/index.d.ts.map +1 -0
  58. package/dist/types/src/storage/StorageDownloader.d.ts +25 -0
  59. package/dist/types/src/storage/StorageDownloader.d.ts.map +1 -0
  60. package/dist/types/src/storage/__test/StorageDownloader.test.d.ts +2 -0
  61. package/dist/types/src/storage/__test/StorageDownloader.test.d.ts.map +1 -0
  62. package/dist/types/src/storage/index.d.ts +1 -0
  63. package/dist/types/src/storage/index.d.ts.map +1 -1
  64. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  65. package/dist/umd/bundle.js +1 -1
  66. package/docs/storage.md +51 -0
  67. package/mod.ts +3 -1
  68. package/package.json +2 -2
  69. package/src/identity/IdentityClient.ts +305 -0
  70. package/src/identity/README.md +93 -0
  71. package/src/identity/__tests/IdentityClient.test.ts +278 -0
  72. package/src/identity/index.ts +2 -0
  73. package/src/identity/types/index.ts +46 -0
  74. package/src/registry/RegistryClient.ts +493 -0
  75. package/src/registry/__tests/RegistryClient.test.ts +444 -0
  76. package/src/registry/index.ts +2 -0
  77. package/src/registry/types/index.ts +101 -0
  78. package/src/storage/StorageDownloader.ts +91 -0
  79. package/src/storage/__test/StorageDownloader.test.ts +170 -0
  80. package/src/storage/index.ts +3 -0
@@ -0,0 +1,388 @@
1
+ import { WalletClient } from '../wallet/index.js';
2
+ import { Utils } from '../primitives/index.js';
3
+ import { Transaction } from '../transaction/index.js';
4
+ import { LookupResolver, TopicBroadcaster } from '../overlay-tools/index.js';
5
+ import { PushDrop, LockingScript } from '../script/index.js';
6
+ const REGISTRANT_TOKEN_AMOUNT = 1;
7
+ /**
8
+ * RegistryClient manages on-chain registry definitions for three types:
9
+ * - BasketMap (basket-based items)
10
+ * - ProtoMap (protocol-based items)
11
+ * - CertMap (certificate-based items)
12
+ *
13
+ * It provides methods to:
14
+ * - Register new definitions using pushdrop-based UTXOs.
15
+ * - Resolve existing definitions using a overlay lookup service.
16
+ * - List registry entries associated with the operator's wallet.
17
+ * - Revoke an existing registry entry by spending its UTXO.
18
+ *
19
+ * Registry operators use this client to establish and manage
20
+ * canonical references for baskets, protocols, and certificates types.
21
+ */
22
+ export class RegistryClient {
23
+ wallet;
24
+ network;
25
+ constructor(wallet = new WalletClient()) {
26
+ this.wallet = wallet;
27
+ }
28
+ /**
29
+ * Publishes a new on-chain definition for baskets, protocols, or certificates.
30
+ * The definition data is encoded in a pushdrop-based UTXO.
31
+ *
32
+ * Registry operators (i.e., identity key owners) can create these definitions
33
+ * to establish canonical references for basket IDs, protocol specs, or certificate schemas.
34
+ *
35
+ * @param data - The structured information needed to register an item of kind 'basket', 'protocol', or 'certificate'.
36
+ * @returns A promise with the broadcast result or failure.
37
+ */
38
+ async registerDefinition(data) {
39
+ const registryOperator = (await this.wallet.getPublicKey({ identityKey: true })).publicKey;
40
+ const pushdrop = new PushDrop(this.wallet);
41
+ // Build the array of fields, depending on the definition type
42
+ const fields = this.buildPushDropFields(data, registryOperator);
43
+ const protocol = this.getWalletProtocol(data.definitionType);
44
+ // Lock the fields
45
+ const lockingScript = await pushdrop.lock(fields, protocol, '1', 'self');
46
+ // Create a transaction
47
+ const { tx } = await this.wallet.createAction({
48
+ description: `Register a new ${data.definitionType} item`,
49
+ outputs: [
50
+ {
51
+ satoshis: REGISTRANT_TOKEN_AMOUNT,
52
+ lockingScript: lockingScript.toHex(),
53
+ outputDescription: `New ${data.definitionType} registration token`
54
+ }
55
+ ]
56
+ });
57
+ if (tx === undefined) {
58
+ throw new Error(`Failed to create ${data.definitionType} registration transaction!`);
59
+ }
60
+ // Broadcast
61
+ const broadcaster = new TopicBroadcaster([this.getBroadcastTopic(data.definitionType)], {
62
+ networkPreset: this.network ??= (await (this.wallet.getNetwork({}))).network
63
+ });
64
+ return await broadcaster.broadcast(Transaction.fromAtomicBEEF(tx));
65
+ }
66
+ /**
67
+ * Resolves registrant tokens of a particular type using a lookup service.
68
+ *
69
+ * The query object shape depends on the registry type:
70
+ * - For "basket", the query is of type BasketMapQuery:
71
+ * { basketID?: string; name?: string; registryOperators?: string[]; }
72
+ * - For "protocol", the query is of type ProtoMapQuery:
73
+ * { name?: string; registryOperators?: string[]; protocolID?: string; securityLevel?: number; }
74
+ * - For "certificate", the query is of type CertMapQuery:
75
+ * { type?: string; name?: string; registryOperators?: string[]; }
76
+ *
77
+ * @param definitionType - The registry type, which can be 'basket', 'protocol', or 'certificate'.
78
+ * @param query - The query object used to filter registry records, whose shape is determined by the registry type.
79
+ * @returns A promise that resolves to an array of matching registry records.
80
+ */
81
+ async resolve(definitionType, query) {
82
+ const resolver = new LookupResolver();
83
+ // The service name depends on the kind
84
+ const serviceName = this.getServiceName(definitionType);
85
+ const result = await resolver.query({
86
+ service: serviceName,
87
+ query
88
+ });
89
+ if (result.type !== 'output-list') {
90
+ return [];
91
+ }
92
+ const parsedRegistryRecords = [];
93
+ for (const output of result.outputs) {
94
+ try {
95
+ const parsedTx = Transaction.fromAtomicBEEF(output.beef);
96
+ const record = await this.parseLockingScript(definitionType, parsedTx.outputs[output.outputIndex].lockingScript);
97
+ parsedRegistryRecords.push(record);
98
+ }
99
+ catch {
100
+ // skip
101
+ }
102
+ }
103
+ return parsedRegistryRecords;
104
+ }
105
+ /**
106
+ * Lists the registry operator's published definitions for the given type.
107
+ *
108
+ * Returns parsed registry records including transaction details such as txid, outputIndex, satoshis, and the locking script.
109
+ *
110
+ * @param definitionType - The type of registry definition to list ('basket', 'protocol', or 'certificate').
111
+ * @returns A promise that resolves to an array of RegistryRecord objects.
112
+ */
113
+ async listOwnRegistryEntries(definitionType) {
114
+ const relevantBasketName = this.getBasketName(definitionType);
115
+ const { outputs } = await this.wallet.listOutputs({
116
+ basket: relevantBasketName,
117
+ include: 'locking scripts'
118
+ });
119
+ const results = [];
120
+ for (const output of outputs) {
121
+ if (output.spendable) {
122
+ continue;
123
+ }
124
+ try {
125
+ const record = await this.parseLockingScript(definitionType, LockingScript.fromHex(output.lockingScript));
126
+ const [txid, outputIndex] = output.outpoint.split('.');
127
+ results.push({
128
+ ...record,
129
+ txid,
130
+ outputIndex: Number(outputIndex),
131
+ satoshis: output.satoshis,
132
+ lockingScript: output.lockingScript
133
+ });
134
+ }
135
+ catch {
136
+ // ignore parse errors
137
+ }
138
+ }
139
+ return results;
140
+ }
141
+ /**
142
+ * Revokes a registry record by spending its associated UTXO.
143
+ *
144
+ * This function creates a transaction that spends the UTXO corresponding to the provided registry record,
145
+ * revoking the registry entry. It prepares an unlocker using the appropriate wallet protocol,
146
+ * builds a signable transaction, signs it, and then broadcasts the finalized transaction.
147
+ *
148
+ * @param registryRecord - The registry record to revoke. It must include a valid txid, outputIndex, and lockingScript.
149
+ * @returns A promise that resolves with either a BroadcastResponse upon success or a BroadcastFailure on error.
150
+ * @throws If required fields are missing or if transaction creation/signing fails.
151
+ */
152
+ async revokeOwnRegistryEntry(registryRecord) {
153
+ if (registryRecord.txid === undefined || typeof registryRecord.outputIndex === 'undefined' || registryRecord.lockingScript === undefined) {
154
+ throw new Error('Invalid record. Missing txid, outputIndex, or lockingScript.');
155
+ }
156
+ // Prepare the unlocker
157
+ const pushdrop = new PushDrop(this.wallet);
158
+ const unlocker = await pushdrop.unlock(this.getWalletProtocol(registryRecord.definitionType), '1', 'anyone');
159
+ const itemIdentifier = registryRecord.definitionType === 'basket'
160
+ ? registryRecord.basketID
161
+ : registryRecord.definitionType === 'protocol'
162
+ ? registryRecord.name
163
+ : registryRecord.definitionType === 'certificate'
164
+ ? (registryRecord.name !== undefined ? registryRecord.name : registryRecord.type)
165
+ : 'unknown';
166
+ const description = `Revoke ${registryRecord.definitionType} item: ${String(itemIdentifier)}`;
167
+ // Create a new transaction that spends the UTXO
168
+ const outpoint = `${registryRecord.txid}.${registryRecord.outputIndex}`;
169
+ const { signableTransaction } = await this.wallet.createAction({
170
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
171
+ description,
172
+ inputs: [
173
+ {
174
+ outpoint,
175
+ unlockingScriptLength: 73,
176
+ inputDescription: `Revoking ${registryRecord.definitionType} token`
177
+ }
178
+ ]
179
+ });
180
+ if (signableTransaction === undefined) {
181
+ throw new Error('Failed to create signable transaction.');
182
+ }
183
+ // Build a transaction object, sign with the unlock script
184
+ const tx = Transaction.fromBEEF(signableTransaction.tx);
185
+ const finalUnlockScript = await unlocker.sign(tx, registryRecord.outputIndex);
186
+ // Complete the signing
187
+ const { tx: signedTx } = await this.wallet.signAction({
188
+ reference: signableTransaction.reference,
189
+ spends: {
190
+ [registryRecord.outputIndex]: {
191
+ unlockingScript: finalUnlockScript.toHex()
192
+ }
193
+ }
194
+ });
195
+ if (signedTx === undefined) {
196
+ throw new Error('Failed to finalize the transaction signature.');
197
+ }
198
+ // Broadcast
199
+ const broadcaster = new TopicBroadcaster([this.getBroadcastTopic(registryRecord.definitionType)], {
200
+ networkPreset: this.network ??= (await (this.wallet.getNetwork({}))).network
201
+ });
202
+ return await broadcaster.broadcast(Transaction.fromAtomicBEEF(signedTx));
203
+ }
204
+ // --------------------------------------------------------------------------
205
+ // INTERNAL HELPER METHODS
206
+ // --------------------------------------------------------------------------
207
+ buildPushDropFields(data, registryOperator) {
208
+ let fields;
209
+ switch (data.definitionType) {
210
+ case 'basket':
211
+ fields = [
212
+ data.basketID,
213
+ data.name,
214
+ data.iconURL,
215
+ data.description,
216
+ data.documentationURL
217
+ ];
218
+ break;
219
+ case 'protocol':
220
+ fields = [
221
+ data.securityLevel.toString(),
222
+ data.protocolID,
223
+ data.name,
224
+ data.iconURL,
225
+ data.description,
226
+ data.documentationURL
227
+ ];
228
+ break;
229
+ case 'certificate':
230
+ fields = [
231
+ data.type,
232
+ data.name,
233
+ data.iconURL,
234
+ data.description,
235
+ data.documentationURL,
236
+ JSON.stringify(data.fields)
237
+ ];
238
+ break;
239
+ default:
240
+ throw new Error('Invalid registry kind specified');
241
+ }
242
+ // Append the registry operator to all cases.
243
+ fields.push(registryOperator);
244
+ return fields.map(field => Utils.toArray(field));
245
+ }
246
+ /**
247
+ * Decodes a pushdrop locking script for a given registry kind,
248
+ * returning a typed record with the appropriate fields.
249
+ */
250
+ async parseLockingScript(definitionType, lockingScript) {
251
+ const decoded = PushDrop.decode(lockingScript);
252
+ if (decoded.fields.length === 0) {
253
+ throw new Error('Not a valid registry pushdrop script.');
254
+ }
255
+ let registryOperator;
256
+ let data;
257
+ switch (definitionType) {
258
+ case 'basket': {
259
+ if (decoded.fields.length !== 6) {
260
+ throw new Error('Unexpected field count for basket type.');
261
+ }
262
+ const [basketID, name, iconURL, description, docURL, operator] = decoded.fields;
263
+ registryOperator = Utils.toUTF8(operator);
264
+ data = {
265
+ definitionType: 'basket',
266
+ basketID: Utils.toUTF8(basketID),
267
+ name: Utils.toUTF8(name),
268
+ iconURL: Utils.toUTF8(iconURL),
269
+ description: Utils.toUTF8(description),
270
+ documentationURL: Utils.toUTF8(docURL)
271
+ };
272
+ break;
273
+ }
274
+ case 'protocol': {
275
+ if (decoded.fields.length !== 7) {
276
+ throw new Error('Unexpected field count for proto type.');
277
+ }
278
+ const [securityLevel, protocolID, name, iconURL, description, docURL, operator] = decoded.fields;
279
+ registryOperator = Utils.toUTF8(operator);
280
+ data = {
281
+ definitionType: 'protocol',
282
+ securityLevel: parseInt(Utils.toUTF8(securityLevel), 10),
283
+ protocolID: Utils.toUTF8(protocolID),
284
+ name: Utils.toUTF8(name),
285
+ iconURL: Utils.toUTF8(iconURL),
286
+ description: Utils.toUTF8(description),
287
+ documentationURL: Utils.toUTF8(docURL)
288
+ };
289
+ break;
290
+ }
291
+ case 'certificate': {
292
+ if (decoded.fields.length !== 7) {
293
+ throw new Error('Unexpected field count for certificate type.');
294
+ }
295
+ const [certType, name, iconURL, description, docURL, fieldsJSON, operator] = decoded.fields;
296
+ registryOperator = Utils.toUTF8(operator);
297
+ let parsedFields;
298
+ try {
299
+ parsedFields = JSON.parse(Utils.toUTF8(fieldsJSON));
300
+ }
301
+ catch {
302
+ parsedFields = {};
303
+ }
304
+ data = {
305
+ definitionType: 'certificate',
306
+ type: Utils.toUTF8(certType),
307
+ name: Utils.toUTF8(name),
308
+ iconURL: Utils.toUTF8(iconURL),
309
+ description: Utils.toUTF8(description),
310
+ documentationURL: Utils.toUTF8(docURL),
311
+ fields: parsedFields
312
+ };
313
+ break;
314
+ }
315
+ default:
316
+ throw new Error('Invalid registry kind for parsing.');
317
+ }
318
+ const currentIdentityKey = (await this.wallet.getPublicKey({ identityKey: true })).publicKey;
319
+ if (registryOperator !== currentIdentityKey) {
320
+ throw new Error('This registry token does not belong to the current wallet.');
321
+ }
322
+ return {
323
+ ...data,
324
+ registryOperator
325
+ };
326
+ }
327
+ /**
328
+ * Returns the (protocolID, keyID) used for pushdrop based on the registry kind.
329
+ */
330
+ getWalletProtocol(definitionType) {
331
+ switch (definitionType) {
332
+ case 'basket':
333
+ return [1, 'basketmap'];
334
+ case 'protocol':
335
+ return [1, 'protomap'];
336
+ case 'certificate':
337
+ return [1, 'certmap'];
338
+ default:
339
+ throw new Error(`Unknown registry type: ${definitionType}`);
340
+ }
341
+ }
342
+ /**
343
+ * Returns the name of the basket used by the wallet
344
+ */
345
+ getBasketName(definitionType) {
346
+ switch (definitionType) {
347
+ case 'basket':
348
+ return 'basketmap';
349
+ case 'protocol':
350
+ return 'protomap';
351
+ case 'certificate':
352
+ return 'certmap';
353
+ default:
354
+ throw new Error(`Unknown basket type: ${definitionType}`);
355
+ }
356
+ }
357
+ /**
358
+ * Returns the broadcast topic to be used with SHIPBroadcaster.
359
+ */
360
+ getBroadcastTopic(definitionType) {
361
+ switch (definitionType) {
362
+ case 'basket':
363
+ return 'tm_basketmap';
364
+ case 'protocol':
365
+ return 'tm_protomap';
366
+ case 'certificate':
367
+ return 'tm_certmap';
368
+ default:
369
+ throw new Error(`Unknown topic type: ${definitionType}`);
370
+ }
371
+ }
372
+ /**
373
+ * Returns the lookup service name to use.
374
+ */
375
+ getServiceName(definitionType) {
376
+ switch (definitionType) {
377
+ case 'basket':
378
+ return 'ls_basketmap';
379
+ case 'protocol':
380
+ return 'ls_protomap';
381
+ case 'certificate':
382
+ return 'ls_certmap';
383
+ default:
384
+ throw new Error(`Unknown service type: ${definitionType}`);
385
+ }
386
+ }
387
+ }
388
+ //# sourceMappingURL=RegistryClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegistryClient.js","sourceRoot":"","sources":["../../../../src/registry/RegistryClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from './RegistryClient.js';
2
+ export * from './types/index.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/registry/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/registry/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,75 @@
1
+ import { LookupResolver } from '../overlay-tools/index.js';
2
+ import { StorageUtils } from './index.js';
3
+ import PushDrop from '../script/templates/PushDrop.js';
4
+ import Transaction from '../transaction/Transaction.js';
5
+ import { Hash, Utils } from '../primitives/index.js';
6
+ /**
7
+ * Locates HTTP URLs where content can be downloaded. It uses the passed or the default one.
8
+ *
9
+ * @param {Object} obj All parameters are passed in an object.
10
+ * @param {String} obj.uhrpUrl The UHRP url to resolve.
11
+ * @param {string} obj.confederacyHost HTTPS URL for for the with default setting.
12
+ *
13
+ * @return {Array<String>} An array of HTTP URLs where content can be downloaded.
14
+ * @throws {Error} If UHRP url parameter invalid or is not an array
15
+ * or there is an error retrieving url(s) stored in the UHRP token.
16
+ */
17
+ export class StorageDownloader {
18
+ networkPreset = 'mainnet';
19
+ constructor(config) {
20
+ this.networkPreset = config?.networkPreset;
21
+ }
22
+ async resolve(uhrpUrl) {
23
+ // Use UHRP lookup service
24
+ const lookupResolver = new LookupResolver({ networkPreset: this.networkPreset });
25
+ const response = await lookupResolver.query({ service: 'ls_uhrp', query: { uhrpUrl } });
26
+ if (response.type !== 'output-list') {
27
+ throw new Error('Lookup answer must be an output list');
28
+ }
29
+ const decodedResults = [];
30
+ for (let i = 0; i < response.outputs.length; i++) {
31
+ const tx = Transaction.fromBEEF(response.outputs[i].beef);
32
+ const { fields } = PushDrop.decode(tx.outputs[response.outputs[i].outputIndex].lockingScript);
33
+ decodedResults.push(Utils.toUTF8(fields[2]));
34
+ }
35
+ return decodedResults;
36
+ }
37
+ async download(uhrpUrl) {
38
+ if (!StorageUtils.isValidURL(uhrpUrl)) {
39
+ throw new Error('Invalid parameter UHRP url');
40
+ }
41
+ const hash = StorageUtils.getHashFromURL(uhrpUrl);
42
+ const downloadURLs = await this.resolve(uhrpUrl);
43
+ if (!Array.isArray(downloadURLs) || downloadURLs.length === 0) {
44
+ throw new Error('No one currently hosts this file!');
45
+ }
46
+ for (let i = 0; i < downloadURLs.length; i++) {
47
+ try {
48
+ // The url is fetched
49
+ const result = await fetch(downloadURLs[i], { method: 'GET' });
50
+ // If the request fails, continue to the next url
51
+ if (!result.ok || result.status >= 400) {
52
+ continue;
53
+ }
54
+ const body = await result.arrayBuffer();
55
+ // The body is loaded into a number array
56
+ const content = [...new Uint8Array(body)];
57
+ const contentHash = Hash.sha256(content);
58
+ for (let i = 0; i < contentHash.length; ++i) {
59
+ if (contentHash[i] !== hash[i]) {
60
+ throw new Error('Value of content does not match hash of the url given');
61
+ }
62
+ }
63
+ return {
64
+ data: content,
65
+ mimeType: result.headers.get('Content-Type')
66
+ };
67
+ }
68
+ catch (error) {
69
+ continue;
70
+ }
71
+ }
72
+ throw new Error(`Unable to download content from ${uhrpUrl}`);
73
+ }
74
+ }
75
+ //# sourceMappingURL=StorageDownloader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageDownloader.js","sourceRoot":"","sources":["../../../../src/storage/StorageDownloader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ import { StorageDownloader } from '../StorageDownloader.js';
2
+ import { StorageUtils } from '../index.js';
3
+ import { LookupResolver } from '../../overlay-tools/index.js';
4
+ import Transaction from '../../transaction/Transaction.js';
5
+ import PushDrop from '../../script/templates/PushDrop.js';
6
+ import { Utils } from '../../primitives/index.js';
7
+ beforeEach(() => {
8
+ jest.restoreAllMocks();
9
+ });
10
+ describe('StorageDownloader', () => {
11
+ let downloader;
12
+ beforeEach(() => {
13
+ // Create a fresh instance
14
+ downloader = new StorageDownloader();
15
+ });
16
+ describe('resolve()', () => {
17
+ it('throws if the lookup response is not "output-list"', async () => {
18
+ // Mock the LookupResolver to return something invalid
19
+ jest.spyOn(LookupResolver.prototype, 'query').mockResolvedValue({
20
+ type: 'something-else',
21
+ outputs: []
22
+ });
23
+ await expect(downloader.resolve('fakeUhrpUrl'))
24
+ .rejects
25
+ .toThrow('Lookup answer must be an output list');
26
+ });
27
+ it('decodes each output with Transaction.fromBEEF and PushDrop.decode', async () => {
28
+ // 1) Mock lookup response
29
+ jest.spyOn(LookupResolver.prototype, 'query').mockResolvedValue({
30
+ type: 'output-list',
31
+ outputs: [
32
+ { beef: 'fake-beef-a', outputIndex: 0 },
33
+ { beef: 'fake-beef-b', outputIndex: 1 }
34
+ ]
35
+ });
36
+ // 2) Mock Transaction.fromBEEF -> returns a dummy transaction
37
+ jest.spyOn(Transaction, 'fromBEEF').mockImplementation(() => {
38
+ // Each transaction might have multiple outputs; we only care about `outputIndex`
39
+ return {
40
+ outputs: [
41
+ { lockingScript: {} },
42
+ { lockingScript: {} } // index 1
43
+ ]
44
+ };
45
+ });
46
+ // 3) Mock PushDrop.decode -> returns { fields: number[][] }
47
+ jest.spyOn(PushDrop, 'decode').mockImplementation(() => {
48
+ // The decode function returns an object with `fields`,
49
+ return {
50
+ lockingPublicKey: {},
51
+ fields: [
52
+ [11],
53
+ [22],
54
+ [104, 116, 116, 112, 58, 47, 47, 97, 46, 99, 111, 109]
55
+ ]
56
+ };
57
+ });
58
+ // 4) Mock Utils.toUTF8 to convert that number[] to a string
59
+ jest.spyOn(Utils, 'toUTF8').mockReturnValue('http://a.com');
60
+ const resolved = await downloader.resolve('fakeUhrpUrl');
61
+ expect(resolved).toEqual(['http://a.com', 'http://a.com']);
62
+ });
63
+ });
64
+ describe('download()', () => {
65
+ it('throws if UHRP URL is invalid', async () => {
66
+ jest.spyOn(StorageUtils, 'isValidURL').mockReturnValue(false);
67
+ await expect(downloader.download('invalidUrl'))
68
+ .rejects
69
+ .toThrow('Invalid parameter UHRP url');
70
+ });
71
+ it('throws if no hosts are found', async () => {
72
+ // Valid UHRP URL
73
+ jest.spyOn(StorageUtils, 'isValidURL').mockReturnValue(true);
74
+ // Return some random 32-byte hash so we can pass the check
75
+ jest.spyOn(StorageUtils, 'getHashFromURL').mockReturnValue(new Array(32).fill(0));
76
+ // Force resolve() to return an empty array
77
+ jest.spyOn(downloader, 'resolve').mockResolvedValue([]);
78
+ await expect(downloader.download('validButUnhostedUrl'))
79
+ .rejects
80
+ .toThrow('No one currently hosts this file!');
81
+ });
82
+ it('downloads successfully from the first working host', async () => {
83
+ jest.spyOn(StorageUtils, 'isValidURL').mockReturnValue(true);
84
+ const knownHash = [
85
+ 102, 104, 122, 173, 248, 98, 189, 119, 108, 143,
86
+ 193, 139, 142, 159, 142, 32, 8, 151, 20, 133,
87
+ 110, 226, 51, 179, 144, 42, 89, 29, 13, 95,
88
+ 41, 37
89
+ ];
90
+ jest.spyOn(StorageUtils, 'getHashFromURL').mockReturnValue(knownHash);
91
+ // Suppose two possible download URLs
92
+ jest.spyOn(downloader, 'resolve').mockResolvedValue([
93
+ 'http://host1/404',
94
+ 'http://host2/ok'
95
+ ]);
96
+ // The first fetch -> 404, second fetch -> success
97
+ const fetchSpy = jest.spyOn(global, 'fetch')
98
+ .mockResolvedValueOnce(new Response(null, { status: 404 }))
99
+ .mockResolvedValueOnce(new Response(new Uint8Array(32).fill(0), {
100
+ status: 200,
101
+ headers: { 'Content-Type': 'application/test' }
102
+ }));
103
+ const result = await downloader.download('validUrl');
104
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
105
+ expect(result).toEqual({
106
+ data: new Array(32).fill(0),
107
+ mimeType: 'application/test'
108
+ });
109
+ });
110
+ it('throws if content hash mismatches the UHRP hash', async () => {
111
+ jest.spyOn(StorageUtils, 'isValidURL').mockReturnValue(true);
112
+ // The expected hash is all zeros
113
+ jest.spyOn(StorageUtils, 'getHashFromURL').mockReturnValue(new Array(32).fill(0));
114
+ // One potential host
115
+ jest.spyOn(downloader, 'resolve').mockResolvedValue([
116
+ 'http://bad-content.test'
117
+ ]);
118
+ // The fetch returns 32 bytes of all 1's => hash mismatch
119
+ jest.spyOn(global, 'fetch').mockResolvedValue(new Response(new Uint8Array(32).fill(1), { status: 200 }));
120
+ await expect(downloader.download('validButBadHashUrl'))
121
+ .rejects
122
+ .toThrow();
123
+ });
124
+ it('throws if all hosts fail or mismatch', async () => {
125
+ jest.spyOn(StorageUtils, 'isValidURL').mockReturnValue(true);
126
+ jest.spyOn(StorageUtils, 'getHashFromURL').mockReturnValue(new Array(32).fill(0));
127
+ jest.spyOn(downloader, 'resolve').mockResolvedValue([
128
+ 'http://host1.test',
129
+ 'http://host2.test'
130
+ ]);
131
+ // Both fetches fail with 500 or something >=400
132
+ jest.spyOn(global, 'fetch').mockResolvedValue(new Response(null, { status: 500 }));
133
+ await expect(downloader.download('validButNoGoodHostUrl'))
134
+ .rejects
135
+ .toThrow('Unable to download content from validButNoGoodHostUrl');
136
+ });
137
+ });
138
+ });
139
+ //# sourceMappingURL=StorageDownloader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageDownloader.test.js","sourceRoot":"","sources":["../../../../../src/storage/__test/StorageDownloader.test.ts"],"names":[],"mappings":"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"}