@bsv/sdk 1.8.0 → 1.8.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 (69) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/kvstore/GlobalKVStore.js +420 -0
  3. package/dist/cjs/src/kvstore/GlobalKVStore.js.map +1 -0
  4. package/dist/cjs/src/kvstore/LocalKVStore.js +6 -6
  5. package/dist/cjs/src/kvstore/LocalKVStore.js.map +1 -1
  6. package/dist/cjs/src/kvstore/index.js +3 -1
  7. package/dist/cjs/src/kvstore/index.js.map +1 -1
  8. package/dist/cjs/src/kvstore/kvStoreInterpreter.js +74 -0
  9. package/dist/cjs/src/kvstore/kvStoreInterpreter.js.map +1 -0
  10. package/dist/cjs/src/kvstore/types.js +11 -0
  11. package/dist/cjs/src/kvstore/types.js.map +1 -0
  12. package/dist/cjs/src/overlay-tools/Historian.js +153 -0
  13. package/dist/cjs/src/overlay-tools/Historian.js.map +1 -0
  14. package/dist/cjs/src/script/templates/PushDrop.js +2 -2
  15. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -1
  16. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  17. package/dist/esm/src/kvstore/GlobalKVStore.js +416 -0
  18. package/dist/esm/src/kvstore/GlobalKVStore.js.map +1 -0
  19. package/dist/esm/src/kvstore/LocalKVStore.js +6 -6
  20. package/dist/esm/src/kvstore/LocalKVStore.js.map +1 -1
  21. package/dist/esm/src/kvstore/index.js +1 -0
  22. package/dist/esm/src/kvstore/index.js.map +1 -1
  23. package/dist/esm/src/kvstore/kvStoreInterpreter.js +47 -0
  24. package/dist/esm/src/kvstore/kvStoreInterpreter.js.map +1 -0
  25. package/dist/esm/src/kvstore/types.js +8 -0
  26. package/dist/esm/src/kvstore/types.js.map +1 -0
  27. package/dist/esm/src/overlay-tools/Historian.js +155 -0
  28. package/dist/esm/src/overlay-tools/Historian.js.map +1 -0
  29. package/dist/esm/src/script/templates/PushDrop.js +2 -2
  30. package/dist/esm/src/script/templates/PushDrop.js.map +1 -1
  31. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  32. package/dist/types/src/kvstore/GlobalKVStore.d.ts +129 -0
  33. package/dist/types/src/kvstore/GlobalKVStore.d.ts.map +1 -0
  34. package/dist/types/src/kvstore/index.d.ts +1 -0
  35. package/dist/types/src/kvstore/index.d.ts.map +1 -1
  36. package/dist/types/src/kvstore/kvStoreInterpreter.d.ts +22 -0
  37. package/dist/types/src/kvstore/kvStoreInterpreter.d.ts.map +1 -0
  38. package/dist/types/src/kvstore/types.d.ts +106 -0
  39. package/dist/types/src/kvstore/types.d.ts.map +1 -0
  40. package/dist/types/src/overlay-tools/Historian.d.ts +92 -0
  41. package/dist/types/src/overlay-tools/Historian.d.ts.map +1 -0
  42. package/dist/types/src/script/templates/PushDrop.d.ts +6 -5
  43. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -1
  44. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  45. package/dist/umd/bundle.js +3 -3
  46. package/dist/umd/bundle.js.map +1 -1
  47. package/docs/reference/compat.md +15 -27
  48. package/docs/reference/identity.md +12 -16
  49. package/docs/reference/kvstore.md +471 -4
  50. package/docs/reference/messages.md +0 -8
  51. package/docs/reference/overlay-tools.md +15 -22
  52. package/docs/reference/primitives.md +168 -168
  53. package/docs/reference/registry.md +9 -19
  54. package/docs/reference/script.md +35 -48
  55. package/docs/reference/storage.md +10 -14
  56. package/docs/reference/totp.md +5 -5
  57. package/docs/reference/transaction.md +117 -69
  58. package/docs/reference/wallet.md +131 -135
  59. package/package.json +1 -1
  60. package/src/kvstore/GlobalKVStore.ts +478 -0
  61. package/src/kvstore/LocalKVStore.ts +7 -7
  62. package/src/kvstore/__tests/GlobalKVStore.test.ts +965 -0
  63. package/src/kvstore/__tests/LocalKVStore.test.ts +72 -0
  64. package/src/kvstore/index.ts +1 -0
  65. package/src/kvstore/kvStoreInterpreter.ts +49 -0
  66. package/src/kvstore/types.ts +114 -0
  67. package/src/overlay-tools/Historian.ts +195 -0
  68. package/src/overlay-tools/__tests/Historian.test.ts +690 -0
  69. package/src/script/templates/PushDrop.ts +6 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -0,0 +1,478 @@
1
+ import Transaction from '../transaction/Transaction.js'
2
+ import * as Utils from '../primitives/utils.js'
3
+ import { TopicBroadcaster, LookupResolver } from '../overlay-tools/index.js'
4
+ import { BroadcastResponse, BroadcastFailure } from '../transaction/Broadcaster.js'
5
+ import { WalletInterface, WalletProtocol, CreateActionInput, OutpointString, PubKeyHex, CreateActionOutput, HexString } from '../wallet/Wallet.interfaces.js'
6
+ import { PushDrop } from '../script/index.js'
7
+ import WalletClient from '../wallet/WalletClient.js'
8
+ import { Beef } from '../transaction/Beef.js'
9
+ import { Historian } from '../overlay-tools/Historian.js'
10
+ import { KVContext, kvStoreInterpreter } from './kvStoreInterpreter.js'
11
+ import { ProtoWallet } from '../wallet/ProtoWallet.js'
12
+ import { kvProtocol, KVStoreConfig, KVStoreQuery, KVStoreEntry, KVStoreGetOptions, KVStoreSetOptions, KVStoreRemoveOptions } from './types.js'
13
+
14
+ /**
15
+ * Default configuration values for GlobalKVStore operations.
16
+ * Provides sensible defaults for overlay connection and protocol settings.
17
+ */
18
+ const DEFAULT_CONFIG: KVStoreConfig = {
19
+ protocolID: [1, 'kvstore'],
20
+ serviceName: 'ls_kvstore',
21
+ tokenAmount: 1,
22
+ topics: ['tm_kvstore'],
23
+ networkPreset: 'mainnet',
24
+ acceptDelayedBroadcast: false,
25
+ tokenSetDescription: '', // Will be set dynamically
26
+ tokenUpdateDescription: '', // Will be set dynamically
27
+ tokenRemovalDescription: '' // Will be set dynamically
28
+ }
29
+
30
+ /**
31
+ * Implements a global key-value storage system which uses an overlay service to track key-value pairs.
32
+ * Each key-value pair is represented by a PushDrop token output.
33
+ * Allows getting, setting, and removing key-value pairs with optional fetching by protocolID and history tracking.
34
+ */
35
+ export class GlobalKVStore {
36
+ /**
37
+ * The wallet interface used to create transactions and perform cryptographic operations.
38
+ * @readonly
39
+ */
40
+ private readonly wallet: WalletInterface
41
+
42
+ /**
43
+ * Configuration for the KVStore instance containing all runtime options.
44
+ * @private
45
+ * @readonly
46
+ */
47
+ private readonly config: KVStoreConfig
48
+
49
+ /**
50
+ * Historian instance used to extract history from transaction outputs.
51
+ * @private
52
+ */
53
+ private readonly historian: Historian<string, KVContext>
54
+
55
+ /**
56
+ * Lookup resolver used to query the overlay for transaction outputs.
57
+ * @private
58
+ */
59
+ private readonly lookupResolver: LookupResolver
60
+
61
+ /**
62
+ * Topic broadcaster used to broadcast transactions to the overlay.
63
+ * @private
64
+ */
65
+ private readonly topicBroadcaster: TopicBroadcaster
66
+
67
+ /**
68
+ * A map to store locks for each key to ensure atomic updates.
69
+ * @private
70
+ */
71
+ private readonly keyLocks: Map<string, Array<(value: void | PromiseLike<void>) => void>> = new Map()
72
+
73
+ /**
74
+ * Cached user identity key
75
+ * @private
76
+ */
77
+ private cachedIdentityKey: PubKeyHex | null = null
78
+
79
+ /**
80
+ * Creates an instance of the GlobalKVStore.
81
+ *
82
+ * @param {KVStoreConfig} [config={}] - Configuration options for the KVStore. Defaults to empty object.
83
+ * @param {WalletInterface} [config.wallet] - Wallet to use for operations. Defaults to WalletClient.
84
+ * @throws {Error} If the configuration contains invalid parameters.
85
+ */
86
+ constructor (config: KVStoreConfig = {}) {
87
+ // Merge with defaults to create a fully resolved config
88
+ this.config = { ...DEFAULT_CONFIG, ...config }
89
+ this.wallet = config.wallet ?? new WalletClient()
90
+ this.historian = new Historian<string, KVContext>(kvStoreInterpreter)
91
+ this.lookupResolver = new LookupResolver({
92
+ networkPreset: this.config.networkPreset
93
+ })
94
+ this.topicBroadcaster = new TopicBroadcaster(this.config.topics as string[], {
95
+ networkPreset: this.config.networkPreset
96
+ })
97
+ }
98
+
99
+ /**
100
+ * Retrieves data from the KVStore.
101
+ * Can query by key+controller (single result), protocolID, controller, or key (multiple results).
102
+ *
103
+ * @param {KVStoreQuery} query - Query parameters sent to overlay
104
+ * @param {KVStoreGetOptions} [options={}] - Configuration options for the get operation
105
+ * @returns {Promise<KVStoreEntry | KVStoreEntry[] | undefined>} Single entry for key+controller queries, array for all other queries
106
+ */
107
+ async get (query: KVStoreQuery, options: KVStoreGetOptions = {}): Promise<KVStoreEntry | KVStoreEntry[] | undefined> {
108
+ if (Object.keys(query).length === 0) {
109
+ throw new Error('Must specify either key, controller, or protocolID')
110
+ }
111
+ if (query.key != null && query.controller != null) {
112
+ // Specific key+controller query - return single entry
113
+ const entries = await this.queryOverlay(query, options)
114
+ return entries.length > 0 ? entries[0] : undefined
115
+ }
116
+ return await this.queryOverlay(query, options)
117
+ }
118
+
119
+ /**
120
+ * Sets a key-value pair. The current user (wallet identity) becomes the controller.
121
+ *
122
+ * @param {string} key - The key to set (user computes this however they want)
123
+ * @param {string} value - The value to store
124
+ * @param {KVStoreSetOptions} [options={}] - Configuration options for the set operation
125
+ * @returns {Promise<OutpointString>} The outpoint of the created token
126
+ */
127
+ async set (key: string, value: string, options: KVStoreSetOptions = {}): Promise<OutpointString> {
128
+ if (typeof key !== 'string' || key.length === 0) {
129
+ throw new Error('Key must be a non-empty string.')
130
+ }
131
+ if (typeof value !== 'string') {
132
+ throw new Error('Value must be a string.')
133
+ }
134
+
135
+ const controller = await this.getIdentityKey()
136
+ const lockQueue = await this.queueOperationOnKey(key)
137
+ const protocolID = options.protocolID ?? this.config.protocolID
138
+ const tokenSetDescription = (options.tokenSetDescription != null && options.tokenSetDescription !== '') ? options.tokenSetDescription : `Create KVStore value for ${key}`
139
+ const tokenUpdateDescription = (options.tokenUpdateDescription != null && options.tokenUpdateDescription !== '') ? options.tokenUpdateDescription : `Update KVStore value for ${key}`
140
+ const tokenAmount = options.tokenAmount ?? this.config.tokenAmount
141
+
142
+ try {
143
+ // Check for existing token to spend
144
+ const existingEntries = await this.queryOverlay({ key, controller }, { includeToken: true })
145
+ const existingToken = existingEntries.length > 0 ? existingEntries[0].token : undefined
146
+
147
+ // Create PushDrop locking script
148
+ const pushdrop = new PushDrop(this.wallet, this.config.originator)
149
+ const lockingScript = await pushdrop.lock(
150
+ [
151
+ Utils.toArray(JSON.stringify(protocolID), 'utf8'),
152
+ Utils.toArray(key, 'utf8'),
153
+ Utils.toArray(value, 'utf8'),
154
+ Utils.toArray(controller, 'hex')
155
+ ],
156
+ protocolID ?? this.config.protocolID as WalletProtocol,
157
+ Utils.toUTF8(Utils.toArray(key, 'utf8')),
158
+ 'anyone',
159
+ true
160
+ )
161
+
162
+ let inputs: CreateActionInput[] = []
163
+ let inputBEEF: Beef | undefined
164
+
165
+ if (existingToken != null) {
166
+ inputs = [{
167
+ outpoint: `${existingToken.txid}.${existingToken.outputIndex}`,
168
+ unlockingScriptLength: 74,
169
+ inputDescription: 'Previous KVStore token'
170
+ }]
171
+ inputBEEF = existingToken.beef
172
+ }
173
+
174
+ if (inputs.length > 0) {
175
+ // Update existing token
176
+ const { signableTransaction } = await this.wallet.createAction({
177
+ description: tokenUpdateDescription,
178
+ inputBEEF: inputBEEF?.toBinary(),
179
+ inputs,
180
+ outputs: [{
181
+ satoshis: tokenAmount ?? this.config.tokenAmount as number,
182
+ lockingScript: lockingScript.toHex(),
183
+ outputDescription: 'KVStore token'
184
+ }],
185
+ options: {
186
+ acceptDelayedBroadcast: this.config.acceptDelayedBroadcast,
187
+ randomizeOutputs: false
188
+ }
189
+ }, this.config.originator)
190
+
191
+ if (signableTransaction == null) {
192
+ throw new Error('Unable to create update transaction')
193
+ }
194
+
195
+ const tx = Transaction.fromAtomicBEEF(signableTransaction.tx)
196
+ const unlocker = pushdrop.unlock(
197
+ this.config.protocolID as WalletProtocol,
198
+ key,
199
+ 'anyone'
200
+ )
201
+ const unlockingScript = await unlocker.sign(tx, 0)
202
+
203
+ const { tx: finalTx } = await this.wallet.signAction({
204
+ reference: signableTransaction.reference,
205
+ spends: { 0: { unlockingScript: unlockingScript.toHex() } }
206
+ }, this.config.originator)
207
+
208
+ if (finalTx == null) {
209
+ throw new Error('Unable to finalize update transaction')
210
+ }
211
+
212
+ const transaction = Transaction.fromAtomicBEEF(finalTx)
213
+ await this.submitToOverlay(transaction)
214
+ return `${transaction.id('hex')}.0`
215
+ } else {
216
+ // Create new token
217
+ const { tx } = await this.wallet.createAction({
218
+ description: tokenSetDescription,
219
+ outputs: [{
220
+ satoshis: tokenAmount ?? this.config.tokenAmount as number,
221
+ lockingScript: lockingScript.toHex(),
222
+ outputDescription: 'KVStore token'
223
+ }],
224
+ options: {
225
+ acceptDelayedBroadcast: this.config.acceptDelayedBroadcast,
226
+ randomizeOutputs: false
227
+ }
228
+ }, this.config.originator)
229
+
230
+ if (tx == null) {
231
+ throw new Error('Failed to create transaction')
232
+ }
233
+
234
+ const transaction = Transaction.fromAtomicBEEF(tx)
235
+ await this.submitToOverlay(transaction)
236
+ return `${transaction.id('hex')}.0`
237
+ }
238
+ } finally {
239
+ if (lockQueue.length > 0) {
240
+ this.finishOperationOnKey(key, lockQueue)
241
+ }
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Removes the key-value pair associated with the given key from the overlay service.
247
+ *
248
+ * @param {string} key - The key to remove.
249
+ * @param {CreateActionOutput[] | undefined} [outputs=undefined] - Additional outputs to include in the removal transaction.
250
+ * @param {KVStoreRemoveOptions} [options=undefined] - Optional parameters for the removal operation.
251
+ * @returns {Promise<HexString>} A promise that resolves to the txid of the removal transaction if successful.
252
+ * @throws {Error} If the key is invalid.
253
+ * @throws {Error} If the key does not exist in the store.
254
+ * @throws {Error} If the overlay service is unreachable or the transaction fails.
255
+ * @throws {Error} If there are existing tokens that cannot be unlocked.
256
+ */
257
+ async remove (key: string, outputs?: CreateActionOutput[], options: KVStoreRemoveOptions = {}): Promise<HexString> {
258
+ if (typeof key !== 'string' || key.length === 0) {
259
+ throw new Error('Key must be a non-empty string.')
260
+ }
261
+
262
+ const controller = await this.getIdentityKey()
263
+ const lockQueue = await this.queueOperationOnKey(key)
264
+
265
+ const protocolID = options.protocolID ?? this.config.protocolID
266
+ const tokenRemovalDescription = (options.tokenRemovalDescription != null && options.tokenRemovalDescription !== '') ? options.tokenRemovalDescription : `Remove KVStore value for ${key}`
267
+
268
+ try {
269
+ const existingEntries = await this.queryOverlay({ key, controller }, { includeToken: true })
270
+
271
+ if (existingEntries.length === 0 || existingEntries[0].token == null) {
272
+ throw new Error('The item did not exist, no item was deleted.')
273
+ }
274
+
275
+ const existingToken = existingEntries[0].token
276
+ const inputs: CreateActionInput[] = [{
277
+ outpoint: `${existingToken.txid}.${existingToken.outputIndex}`,
278
+ unlockingScriptLength: 74,
279
+ inputDescription: 'KVStore token to remove'
280
+ }]
281
+
282
+ const pushdrop = new PushDrop(this.wallet, this.config.originator)
283
+ const { signableTransaction } = await this.wallet.createAction({
284
+ description: tokenRemovalDescription,
285
+ inputBEEF: existingToken.beef.toBinary(),
286
+ inputs,
287
+ outputs,
288
+ options: {
289
+ acceptDelayedBroadcast: this.config.acceptDelayedBroadcast
290
+ }
291
+ }, this.config.originator)
292
+
293
+ if (signableTransaction == null) {
294
+ throw new Error('Unable to create removal transaction')
295
+ }
296
+
297
+ const tx = Transaction.fromAtomicBEEF(signableTransaction.tx)
298
+ const unlocker = pushdrop.unlock(
299
+ protocolID ?? this.config.protocolID as WalletProtocol,
300
+ key,
301
+ 'anyone'
302
+ )
303
+ const unlockingScript = await unlocker.sign(tx, 0)
304
+
305
+ const { tx: finalTx } = await this.wallet.signAction({
306
+ reference: signableTransaction.reference,
307
+ spends: { 0: { unlockingScript: unlockingScript.toHex() } }
308
+ }, this.config.originator)
309
+
310
+ if (finalTx == null) {
311
+ throw new Error('Unable to finalize removal transaction')
312
+ }
313
+
314
+ const transaction = Transaction.fromAtomicBEEF(finalTx)
315
+ await this.submitToOverlay(transaction)
316
+ return transaction.id('hex')
317
+ } finally {
318
+ if (lockQueue.length > 0) {
319
+ this.finishOperationOnKey(key, lockQueue)
320
+ }
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Queues an operation on a specific key to ensure atomic updates.
326
+ * Prevents concurrent operations on the same key from interfering with each other.
327
+ *
328
+ * @param {string} key - The key to queue an operation for.
329
+ * @returns {Promise<Array<(value: void | PromiseLike<void>) => void>>} The lock queue for cleanup.
330
+ * @private
331
+ */
332
+ private async queueOperationOnKey (key: string): Promise<Array<(value: void | PromiseLike<void>) => void>> {
333
+ // Check if a lock exists for this key and wait for it to resolve
334
+ let lockQueue = this.keyLocks.get(key)
335
+ if (lockQueue == null) {
336
+ lockQueue = []
337
+ this.keyLocks.set(key, lockQueue)
338
+ }
339
+
340
+ let resolveNewLock: () => void = () => { }
341
+ const newLock = new Promise<void>((resolve) => {
342
+ resolveNewLock = resolve
343
+ if (lockQueue != null) { lockQueue.push(resolve) }
344
+ })
345
+
346
+ // If we are the only request, resolve the lock immediately, queue remains at 1 item until request ends.
347
+ if (lockQueue.length === 1) {
348
+ resolveNewLock()
349
+ }
350
+
351
+ await newLock
352
+ return lockQueue
353
+ }
354
+
355
+ /**
356
+ * Finishes an operation on a key and resolves the next waiting operation.
357
+ *
358
+ * @param {string} key - The key to finish the operation for.
359
+ * @param {Array<(value: void | PromiseLike<void>) => void>} lockQueue - The lock queue from queueOperationOnKey.
360
+ * @private
361
+ */
362
+ private finishOperationOnKey (key: string, lockQueue: Array<(value: void | PromiseLike<void>) => void>): void {
363
+ lockQueue.shift() // Remove the current lock from the queue
364
+ if (lockQueue.length > 0) {
365
+ // If there are more locks waiting, resolve the next one
366
+ lockQueue[0]()
367
+ } else {
368
+ // Clean up empty queue to prevent memory leak
369
+ this.keyLocks.delete(key)
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Helper function to fetch and cache user identity key
375
+ *
376
+ * @returns {Promise<PubKeyHex>} The identity key of the current user
377
+ * @private
378
+ */
379
+ private async getIdentityKey (): Promise<PubKeyHex> {
380
+ if (this.cachedIdentityKey == null) {
381
+ this.cachedIdentityKey = (await this.wallet.getPublicKey({ identityKey: true }, this.config.originator)).publicKey
382
+ }
383
+ return this.cachedIdentityKey
384
+ }
385
+
386
+ /**
387
+ * Queries the overlay service for KV entries.
388
+ *
389
+ * @param {KVStoreQuery} query - Query parameters sent to overlay
390
+ * @param {KVStoreGetOptions} options - Configuration options for the query
391
+ * @returns {Promise<KVStoreEntry[]>} Array of matching KV entries
392
+ * @private
393
+ */
394
+ private async queryOverlay (query: KVStoreQuery, options: KVStoreGetOptions = {}): Promise<KVStoreEntry[]> {
395
+ const answer = await this.lookupResolver.query({
396
+ service: options.serviceName ?? this.config.serviceName as string,
397
+ query
398
+ })
399
+
400
+ if (answer.type !== 'output-list' || answer.outputs.length === 0) {
401
+ return []
402
+ }
403
+
404
+ const entries: KVStoreEntry[] = []
405
+
406
+ for (const result of answer.outputs) {
407
+ try {
408
+ const tx = Transaction.fromBEEF(result.beef)
409
+ const output = tx.outputs[result.outputIndex]
410
+ const decoded = PushDrop.decode(output.lockingScript)
411
+
412
+ if (decoded.fields.length !== 5) {
413
+ continue
414
+ }
415
+
416
+ // Verify signature
417
+ const anyoneWallet = new ProtoWallet('anyone')
418
+ const signature = decoded.fields.pop() as number[]
419
+ try {
420
+ await anyoneWallet.verifySignature({
421
+ data: decoded.fields.reduce((a, e) => [...a, ...e], []),
422
+ signature,
423
+ counterparty: Utils.toHex(decoded.fields[kvProtocol.controller]),
424
+ protocolID: JSON.parse(Utils.toUTF8(decoded.fields[kvProtocol.protocolID])),
425
+ keyID: Utils.toUTF8(decoded.fields[kvProtocol.key])
426
+ })
427
+ } catch (error) {
428
+ // Skip all outputs that fail signature verification
429
+ continue
430
+ }
431
+
432
+ const entry: KVStoreEntry = {
433
+ key: Utils.toUTF8(decoded.fields[kvProtocol.key]),
434
+ value: Utils.toUTF8(decoded.fields[kvProtocol.value]),
435
+ controller: Utils.toHex(decoded.fields[kvProtocol.controller]),
436
+ protocolID: JSON.parse(Utils.toUTF8(decoded.fields[kvProtocol.protocolID]))
437
+ }
438
+
439
+ if (options.includeToken === true) {
440
+ entry.token = {
441
+ txid: tx.id('hex'),
442
+ outputIndex: result.outputIndex,
443
+ beef: Beef.fromBinary(result.beef),
444
+ satoshis: output.satoshis ?? 1
445
+ }
446
+ }
447
+
448
+ if (options.history === true) {
449
+ entry.history = await this.historian.buildHistory(tx, {
450
+ key: entry.key,
451
+ protocolID: entry.protocolID
452
+ })
453
+ }
454
+
455
+ entries.push(entry)
456
+ } catch (error) {
457
+ continue
458
+ }
459
+ }
460
+
461
+ return entries
462
+ }
463
+
464
+ /**
465
+ * Submits a transaction to an overlay service using TopicBroadcaster.
466
+ * Broadcasts the transaction to the configured topics for network propagation.
467
+ *
468
+ * @param {Transaction} transaction - The transaction to broadcast.
469
+ * @returns {Promise<BroadcastResponse | BroadcastFailure>} The broadcast result.
470
+ * @throws {Error} If the broadcast fails or the network is unreachable.
471
+ * @private
472
+ */
473
+ private async submitToOverlay (transaction: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
474
+ return await this.topicBroadcaster.broadcast(transaction)
475
+ }
476
+ }
477
+
478
+ export default GlobalKVStore
@@ -80,7 +80,7 @@ export default class LocalKVStore {
80
80
  this.keyLocks.set(key, lockQueue)
81
81
  }
82
82
 
83
- let resolveNewLock: () => void = () => {}
83
+ let resolveNewLock: () => void = () => { }
84
84
  const newLock = new Promise<void>((resolve) => {
85
85
  resolveNewLock = resolve
86
86
  if (lockQueue != null) { lockQueue.push(resolve) }
@@ -166,8 +166,8 @@ export default class LocalKVStore {
166
166
  throw new Error('Invalid token.')
167
167
  }
168
168
  field = decoded.fields[0]
169
- } catch (_) {
170
- throw new Error(`Invalid value found. You need to call set to collapse the corrupted state (or relinquish the corrupted ${outputs[0].outpoint} output from the ${this.context} basket) before you can get this value again.`)
169
+ } catch (error) {
170
+ throw new Error(`Invalid value found. You need to call set to collapse the corrupted state (or relinquish the corrupted ${outputs[0].outpoint} output from the ${this.context} basket) before you can get this value again. Original error: ${error instanceof Error ? error.message : String(error)}`)
171
171
  }
172
172
  if (!this.encrypt) {
173
173
  r.value = Utils.toUTF8(field)
@@ -288,8 +288,8 @@ export default class LocalKVStore {
288
288
  })
289
289
  outpoint = `${txid as string}.0`
290
290
  }
291
- } catch (_) {
292
- throw new Error(`There are ${outputs.length} outputs with tag ${key} that cannot be unlocked.`)
291
+ } catch (error) {
292
+ throw new Error(`There are ${outputs.length} outputs with tag ${key} that cannot be unlocked. Original error: ${error instanceof Error ? error.message : String(error)}`)
293
293
  }
294
294
 
295
295
  return outpoint
@@ -337,8 +337,8 @@ export default class LocalKVStore {
337
337
  })
338
338
  if (txid === undefined) { throw new Error('signAction must return a valid txid') }
339
339
  txids.push(txid)
340
- } catch (_) {
341
- throw new Error(`There are ${totalOutputs} outputs with tag ${key} that cannot be unlocked.`)
340
+ } catch (error) {
341
+ throw new Error(`There are ${totalOutputs} outputs with tag ${key} that cannot be unlocked. Original error: ${error instanceof Error ? error.message : String(error)}`)
342
342
  }
343
343
  }
344
344
  if (outputs.length === totalOutputs) { break }