@bsv/sdk 1.6.6 → 1.6.8

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.
@@ -3229,7 +3229,8 @@ export class Reader {
3229
3229
  public readInt32LE(): number
3230
3230
  public readUInt64BEBn(): BigNumber
3231
3231
  public readUInt64LEBn(): BigNumber
3232
- public readVarIntNum(): number
3232
+ public readInt64LEBn(): BigNumber
3233
+ public readVarIntNum(signed: boolean = true): number
3233
3234
  public readVarInt(): number[]
3234
3235
  public readVarIntBn(): BigNumber
3235
3236
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.6.6",
3
+ "version": "1.6.8",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -44,7 +44,7 @@ export default class LocalKVStore {
44
44
  * A map to store locks for each key to ensure atomic updates.
45
45
  * @private
46
46
  */
47
- private readonly keyLocks: Map<string, Promise<void>> = new Map()
47
+ private readonly keyLocks: Map<string, Array<(value: void | PromiseLike<void>) => void>> = new Map()
48
48
 
49
49
  /**
50
50
  * Creates an instance of the localKVStore.
@@ -72,6 +72,38 @@ export default class LocalKVStore {
72
72
  this.acceptDelayedBroadcast = acceptDelayedBroadcast
73
73
  }
74
74
 
75
+ private async queueOperationOnKey (key: string): Promise<Array<(value: void | PromiseLike<void>) => void>> {
76
+ // Check if a lock exists for this key and wait for it to resolve
77
+ let lockQueue = this.keyLocks.get(key)
78
+ if (lockQueue == null) {
79
+ lockQueue = []
80
+ this.keyLocks.set(key, lockQueue)
81
+ }
82
+
83
+ let resolveNewLock: () => void = () => {}
84
+ const newLock = new Promise<void>((resolve) => {
85
+ resolveNewLock = resolve
86
+ if (lockQueue != null) { lockQueue.push(resolve) }
87
+ })
88
+
89
+ // If we are the only request, resolve the lock immediately, queue remains at 1 item until request ends.
90
+ if (lockQueue.length === 1) {
91
+ resolveNewLock()
92
+ }
93
+
94
+ await newLock
95
+
96
+ return lockQueue
97
+ }
98
+
99
+ private finishOperationOnKey (key: string, lockQueue: Array<(value: void | PromiseLike<void>) => void>): void {
100
+ lockQueue.shift() // Remove the current lock from the queue
101
+ if (lockQueue.length > 0) {
102
+ // If there are more locks waiting, resolve the next one
103
+ lockQueue[0]()
104
+ }
105
+ }
106
+
75
107
  private getProtocol (key: string): { protocolID: WalletProtocol, keyID: string } {
76
108
  return { protocolID: [2, this.context], keyID: key }
77
109
  }
@@ -98,8 +130,14 @@ export default class LocalKVStore {
98
130
  * @throws {Error} If the found output's locking script cannot be decoded or represents an invalid token format.
99
131
  */
100
132
  async get (key: string, defaultValue: string | undefined = undefined): Promise<string | undefined> {
101
- const r = await this.lookupValue(key, defaultValue, 5)
102
- return r.value
133
+ const lockQueue = await this.queueOperationOnKey(key)
134
+
135
+ try {
136
+ const r = await this.lookupValue(key, defaultValue, 5)
137
+ return r.value
138
+ } finally {
139
+ this.finishOperationOnKey(key, lockQueue)
140
+ }
103
141
  }
104
142
 
105
143
  private getLockingScript (output: WalletOutput, beef: Beef): LockingScript {
@@ -185,17 +223,7 @@ export default class LocalKVStore {
185
223
  * @returns {Promise<OutpointString>} A promise that resolves to the outpoint string (txid.vout) of the new or updated token output.
186
224
  */
187
225
  async set (key: string, value: string): Promise<OutpointString> {
188
- // Check if a lock exists for this key and wait for it to resolve
189
- const existingLock = this.keyLocks.get(key)
190
- if (existingLock != null) {
191
- await existingLock
192
- }
193
-
194
- let resolveNewLock: () => void = () => {}
195
- const newLock = new Promise<void>((resolve) => {
196
- resolveNewLock = resolve
197
- })
198
- this.keyLocks.set(key, newLock)
226
+ const lockQueue = await this.queueOperationOnKey(key)
199
227
 
200
228
  try {
201
229
  const current = await this.lookupValue(key, undefined, 10)
@@ -266,9 +294,7 @@ export default class LocalKVStore {
266
294
 
267
295
  return outpoint
268
296
  } finally {
269
- // Release the lock by resolving the promise and removing it from the map
270
- this.keyLocks.delete(key)
271
- resolveNewLock()
297
+ this.finishOperationOnKey(key, lockQueue)
272
298
  }
273
299
  }
274
300
 
@@ -283,38 +309,44 @@ export default class LocalKVStore {
283
309
  * @returns {Promise<string[]>} A promise that resolves to the txids of the removal transactions if successful.
284
310
  */
285
311
  async remove (key: string): Promise<string[]> {
286
- const txids: string[] = []
287
- for (; ;) {
288
- const { outputs, BEEF: inputBEEF, totalOutputs } = await this.getOutputs(key)
289
- if (outputs.length > 0) {
290
- const pushdrop = new PushDrop(this.wallet, this.originator)
291
- try {
292
- const inputs = this.getInputs(outputs)
293
- const { signableTransaction } = await this.wallet.createAction({
294
- description: `Remove ${key} in ${this.context}`,
295
- inputBEEF,
296
- inputs,
297
- options: {
298
- acceptDelayedBroadcast: this.acceptDelayedBroadcast
312
+ const lockQueue = await this.queueOperationOnKey(key)
313
+
314
+ try {
315
+ const txids: string[] = []
316
+ for (; ;) {
317
+ const { outputs, BEEF: inputBEEF, totalOutputs } = await this.getOutputs(key)
318
+ if (outputs.length > 0) {
319
+ const pushdrop = new PushDrop(this.wallet, this.originator)
320
+ try {
321
+ const inputs = this.getInputs(outputs)
322
+ const { signableTransaction } = await this.wallet.createAction({
323
+ description: `Remove ${key} in ${this.context}`,
324
+ inputBEEF,
325
+ inputs,
326
+ options: {
327
+ acceptDelayedBroadcast: this.acceptDelayedBroadcast
328
+ }
329
+ })
330
+ if (typeof signableTransaction !== 'object') {
331
+ throw new Error('Wallet did not return a signable transaction when expected.')
299
332
  }
300
- })
301
- if (typeof signableTransaction !== 'object') {
302
- throw new Error('Wallet did not return a signable transaction when expected.')
333
+ const spends = await this.getSpends(key, outputs, pushdrop, signableTransaction.tx)
334
+ const { txid } = await this.wallet.signAction({
335
+ reference: signableTransaction.reference,
336
+ spends
337
+ })
338
+ if (txid === undefined) { throw new Error('signAction must return a valid txid') }
339
+ txids.push(txid)
340
+ } catch (_) {
341
+ throw new Error(`There are ${totalOutputs} outputs with tag ${key} that cannot be unlocked.`)
303
342
  }
304
- const spends = await this.getSpends(key, outputs, pushdrop, signableTransaction.tx)
305
- const { txid } = await this.wallet.signAction({
306
- reference: signableTransaction.reference,
307
- spends
308
- })
309
- if (txid === undefined) { throw new Error('signAction must return a valid txid') }
310
- txids.push(txid)
311
- } catch (_) {
312
- throw new Error(`There are ${totalOutputs} outputs with tag ${key} that cannot be unlocked.`)
313
343
  }
344
+ if (outputs.length === totalOutputs) { break }
314
345
  }
315
- if (outputs.length === totalOutputs) { break }
346
+ return txids
347
+ } finally {
348
+ this.finishOperationOnKey(key, lockQueue)
316
349
  }
317
- return txids
318
350
  }
319
351
  }
320
352
 
@@ -525,6 +525,9 @@ export class Writer {
525
525
 
526
526
  static varIntNum (n: number): number[] {
527
527
  let buf: number[]
528
+ if (n < 0) {
529
+ return this.varIntBn(new BigNumber(n))
530
+ }
528
531
  if (n < 253) {
529
532
  buf = [n] // 1 byte
530
533
  } else if (n < 0x10000) {
@@ -565,6 +568,9 @@ export class Writer {
565
568
 
566
569
  static varIntBn (bn: BigNumber): number[] {
567
570
  let buf: number[]
571
+ if (bn.isNeg()) {
572
+ bn = bn.add(OverflowUint64) // Adjust for negative numbers
573
+ }
568
574
  if (bn.ltn(253)) {
569
575
  const n = bn.toNumber()
570
576
  // No need for bitwise operation as the value is within a byte's range
@@ -708,7 +714,16 @@ export class Reader {
708
714
  return bn
709
715
  }
710
716
 
711
- public readVarIntNum (): number {
717
+ public readInt64LEBn (): BigNumber {
718
+ const bin = this.readReverse(8)
719
+ let bn = new BigNumber(bin)
720
+ if (bn.gte(OverflowInt64)) {
721
+ bn = bn.sub(OverflowUint64) // Adjust for negative numbers
722
+ }
723
+ return bn
724
+ }
725
+
726
+ public readVarIntNum (signed: boolean = true): number {
712
727
  const first = this.readUInt8()
713
728
  let bn: BigNumber
714
729
  switch (first) {
@@ -717,7 +732,7 @@ export class Reader {
717
732
  case 0xfe:
718
733
  return this.readUInt32LE()
719
734
  case 0xff:
720
- bn = this.readUInt64LEBn()
735
+ bn = signed ? this.readInt64LEBn() : this.readUInt64LEBn()
721
736
  if (bn.lte(new BigNumber(2).pow(new BigNumber(53)))) {
722
737
  return bn.toNumber()
723
738
  } else {
@@ -801,3 +816,6 @@ export const minimallyEncode = (buf: number[]): number[] => {
801
816
  // If we found the whole thing is zeros, then we have a zero.
802
817
  return []
803
818
  }
819
+
820
+ const OverflowInt64 = new BigNumber(2).pow(new BigNumber(63))
821
+ const OverflowUint64 = new BigNumber(2).pow(new BigNumber(64))