@bsv/sdk 1.10.2 → 1.10.3

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 (63) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/ReaderUint8Array.js +180 -0
  3. package/dist/cjs/src/primitives/ReaderUint8Array.js.map +1 -0
  4. package/dist/cjs/src/primitives/WriterUint8Array.js +173 -0
  5. package/dist/cjs/src/primitives/WriterUint8Array.js.map +1 -0
  6. package/dist/cjs/src/primitives/utils.js +20 -2
  7. package/dist/cjs/src/primitives/utils.js.map +1 -1
  8. package/dist/cjs/src/transaction/Beef.js +85 -27
  9. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  10. package/dist/cjs/src/transaction/BeefTx.js +32 -14
  11. package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
  12. package/dist/cjs/src/transaction/MerklePath.js +25 -6
  13. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  14. package/dist/cjs/src/transaction/Transaction.js +77 -26
  15. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  16. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  17. package/dist/esm/src/primitives/ReaderUint8Array.js +176 -0
  18. package/dist/esm/src/primitives/ReaderUint8Array.js.map +1 -0
  19. package/dist/esm/src/primitives/WriterUint8Array.js +169 -0
  20. package/dist/esm/src/primitives/WriterUint8Array.js.map +1 -0
  21. package/dist/esm/src/primitives/utils.js +18 -1
  22. package/dist/esm/src/primitives/utils.js.map +1 -1
  23. package/dist/esm/src/transaction/Beef.js +86 -28
  24. package/dist/esm/src/transaction/Beef.js.map +1 -1
  25. package/dist/esm/src/transaction/BeefTx.js +33 -15
  26. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  27. package/dist/esm/src/transaction/MerklePath.js +26 -7
  28. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  29. package/dist/esm/src/transaction/Transaction.js +78 -27
  30. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  31. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  32. package/dist/types/src/primitives/ReaderUint8Array.d.ts +32 -0
  33. package/dist/types/src/primitives/ReaderUint8Array.d.ts.map +1 -0
  34. package/dist/types/src/primitives/WriterUint8Array.d.ts +54 -0
  35. package/dist/types/src/primitives/WriterUint8Array.d.ts.map +1 -0
  36. package/dist/types/src/primitives/utils.d.ts +15 -3
  37. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  38. package/dist/types/src/transaction/Beef.d.ts +24 -7
  39. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  40. package/dist/types/src/transaction/BeefTx.d.ts +13 -6
  41. package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
  42. package/dist/types/src/transaction/MerklePath.d.ts +16 -3
  43. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  44. package/dist/types/src/transaction/Transaction.d.ts +44 -7
  45. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  46. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  47. package/dist/umd/bundle.js +3 -3
  48. package/dist/umd/bundle.js.map +1 -1
  49. package/docs/reference/primitives.md +167 -29
  50. package/docs/reference/script.md +1 -1
  51. package/docs/reference/transaction.md +177 -34
  52. package/package.json +1 -1
  53. package/src/overlay-tools/__tests/SHIPBroadcaster.test.ts +9 -0
  54. package/src/primitives/ReaderUint8Array.ts +196 -0
  55. package/src/primitives/WriterUint8Array.ts +195 -0
  56. package/src/primitives/__tests/ReaderUint8Array.test.ts +317 -0
  57. package/src/primitives/__tests/WriterUint8Array.test.ts +208 -0
  58. package/src/primitives/utils.ts +20 -2
  59. package/src/transaction/Beef.ts +103 -40
  60. package/src/transaction/BeefTx.ts +38 -19
  61. package/src/transaction/MerklePath.ts +30 -9
  62. package/src/transaction/Transaction.ts +91 -38
  63. package/src/transaction/__tests/Beef.test.ts +75 -0
@@ -3,7 +3,7 @@ import TransactionInput from './TransactionInput.js'
3
3
  import TransactionOutput from './TransactionOutput.js'
4
4
  import UnlockingScript from '../script/UnlockingScript.js'
5
5
  import LockingScript from '../script/LockingScript.js'
6
- import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
6
+ import { Reader, Writer, toHex, toArray, ReaderUint8Array, toUint8Array, WriterUint8Array } from '../primitives/utils.js'
7
7
  import { hash256 } from '../primitives/Hash.js'
8
8
  import FeeModel from './FeeModel.js'
9
9
  import LivePolicy from './fee-models/LivePolicy.js'
@@ -16,9 +16,6 @@ import { defaultChainTracker } from './chaintrackers/DefaultChainTracker.js'
16
16
  import { Beef, BEEF_V1 } from './Beef.js'
17
17
  import P2PKH from '../script/templates/P2PKH.js'
18
18
 
19
- const BufferCtor =
20
- typeof globalThis !== 'undefined' ? (globalThis as any).Buffer : undefined
21
-
22
19
  /**
23
20
  * Represents a complete Bitcoin transaction. This class encapsulates all the details
24
21
  * required for creating, signing, and processing a Bitcoin transaction, including
@@ -109,7 +106,7 @@ export default class Transaction {
109
106
  * @param txid Optional TXID of the transaction to retrieve from the BEEF data.
110
107
  * @returns An anchored transaction, linked to its associated inputs populated with merkle paths.
111
108
  */
112
- static fromBEEF (beef: number[], txid?: string): Transaction {
109
+ static fromBEEF (beef: number[] | Uint8Array, txid?: string): Transaction {
113
110
  const { tx } = Transaction.fromAnyBeef(beef, txid)
114
111
  return tx
115
112
  }
@@ -121,7 +118,7 @@ export default class Transaction {
121
118
  * @param beef A binary representation of an Atomic BEEF structure.
122
119
  * @returns The subject transaction, linked to its associated inputs populated with merkle paths.
123
120
  */
124
- static fromAtomicBEEF (beef: number[]): Transaction {
121
+ static fromAtomicBEEF (beef: number[] | Uint8Array): Transaction {
125
122
  const { tx, txid, beef: b } = Transaction.fromAnyBeef(beef)
126
123
  if (txid !== b.atomicTxid) {
127
124
  if (b.atomicTxid != null) {
@@ -133,7 +130,7 @@ export default class Transaction {
133
130
  return tx
134
131
  }
135
132
 
136
- private static fromAnyBeef (beef: number[], txid?: string): { tx: Transaction, beef: Beef, txid: string } {
133
+ private static fromAnyBeef (beef: number[] | Uint8Array, txid?: string): { tx: Transaction, beef: Beef, txid: string } {
137
134
  const b = Beef.fromBinary(beef)
138
135
  if (b.txs.length < 1) {
139
136
  throw new Error('beef must include at least one transaction.')
@@ -155,8 +152,8 @@ export default class Transaction {
155
152
  * @param ef A binary representation of a transaction in EF format.
156
153
  * @returns An extended transaction, linked to its associated inputs by locking script and satoshis amounts only.
157
154
  */
158
- static fromEF (ef: number[]): Transaction {
159
- const br = new Reader(ef)
155
+ static fromEF (ef: number[] | Uint8Array): Transaction {
156
+ const br = ReaderUint8Array.makeReader(ef)
160
157
  const version = br.readUInt32LE()
161
158
  if (toHex(br.read(6)) !== '0000000000ef') { throw new Error('Invalid EF marker') }
162
159
  const inputsLength = br.readVarIntNum()
@@ -217,11 +214,11 @@ export default class Transaction {
217
214
  * outputs: { vout: number, offset: number, length: number }[]
218
215
  * }
219
216
  */
220
- static parseScriptOffsets (bin: number[]): {
217
+ static parseScriptOffsets (bin: number[] | Uint8Array): {
221
218
  inputs: Array<{ vin: number, offset: number, length: number }>
222
219
  outputs: Array<{ vout: number, offset: number, length: number }>
223
220
  } {
224
- const br = new Reader(bin)
221
+ const br = ReaderUint8Array.makeReader(bin)
225
222
  const inputs: Array<{ vin: number, offset: number, length: number }> = []
226
223
  const outputs: Array<{ vout: number, offset: number, length: number }> = []
227
224
 
@@ -243,7 +240,7 @@ export default class Transaction {
243
240
  return { inputs, outputs }
244
241
  }
245
242
 
246
- static fromReader (br: Reader): Transaction {
243
+ static fromReader (br: Reader | ReaderUint8Array): Transaction {
247
244
  const version = br.readUInt32LE()
248
245
  const inputsLength = br.readVarIntNum()
249
246
  const inputs: TransactionInput[] = []
@@ -284,10 +281,10 @@ export default class Transaction {
284
281
  * @param {number[]} bin - The binary array representation of the transaction.
285
282
  * @returns {Transaction} - A new Transaction instance.
286
283
  */
287
- static fromBinary (bin: number[]): Transaction {
284
+ static fromBinary (bin: number[] | Uint8Array): Transaction {
288
285
  const copy = bin.slice()
289
286
  const rawBytes = Uint8Array.from(copy)
290
- const br = new Reader(copy)
287
+ const br = new ReaderUint8Array(rawBytes)
291
288
  const tx = Transaction.fromReader(br)
292
289
  tx.rawBytesCache = rawBytes
293
290
  return tx
@@ -301,15 +298,11 @@ export default class Transaction {
301
298
  * @returns {Transaction} - A new Transaction instance.
302
299
  */
303
300
  static fromHex (hex: string): Transaction {
304
- const bin = toArray(hex, 'hex')
305
- const rawBytes = Uint8Array.from(bin)
306
- const br = new Reader(bin)
301
+ const rawBytes = toUint8Array(hex, 'hex')
302
+ const br = new ReaderUint8Array(rawBytes)
307
303
  const tx = Transaction.fromReader(br)
308
304
  tx.rawBytesCache = rawBytes
309
- tx.hexCache =
310
- BufferCtor != null
311
- ? BufferCtor.from(rawBytes).toString('hex')
312
- : toHex(bin)
305
+ tx.hexCache = toHex(rawBytes)
313
306
  return tx
314
307
  }
315
308
 
@@ -321,7 +314,7 @@ export default class Transaction {
321
314
  * @returns {Transaction} - A new Transaction instance.
322
315
  */
323
316
  static fromHexEF (hex: string): Transaction {
324
- return Transaction.fromEF(toArray(hex, 'hex'))
317
+ return Transaction.fromEF(toUint8Array(hex, 'hex'))
325
318
  }
326
319
 
327
320
  /**
@@ -616,7 +609,7 @@ export default class Transaction {
616
609
  return await broadcaster.broadcast(this)
617
610
  }
618
611
 
619
- private writeTransactionBody (writer: Writer): void {
612
+ private writeTransactionBody (writer: Writer | WriterUint8Array): void {
620
613
  writer.writeUInt32LE(this.version)
621
614
  writer.writeVarIntNum(this.inputs.length)
622
615
  for (const i of this.inputs) {
@@ -649,7 +642,7 @@ export default class Transaction {
649
642
  }
650
643
 
651
644
  private buildSerializedBytes (): Uint8Array {
652
- const writer = new Writer()
645
+ const writer = new WriterUint8Array()
653
646
  this.writeTransactionBody(writer)
654
647
  return writer.toUint8Array()
655
648
  }
@@ -674,13 +667,7 @@ export default class Transaction {
674
667
  return this.getSerializedBytes()
675
668
  }
676
669
 
677
- /**
678
- * Converts the transaction to a BRC-30 EF format.
679
- *
680
- * @returns {number[]} - The BRC-30 EF representation of the transaction.
681
- */
682
- toEF (): number[] {
683
- const writer = new Writer()
670
+ private writeEF (writer: Writer | WriterUint8Array): void {
684
671
  writer.writeUInt32LE(this.version)
685
672
  writer.write([0, 0, 0, 0, 0, 0xef])
686
673
  writer.writeVarIntNum(this.inputs.length)
@@ -721,16 +708,37 @@ export default class Transaction {
721
708
  writer.write(scriptBin)
722
709
  }
723
710
  writer.writeUInt32LE(this.lockTime)
711
+ }
712
+
713
+ /**
714
+ * Converts the transaction to a BRC-30 EF format.
715
+ *
716
+ * @returns {number[]} - The BRC-30 EF representation of the transaction.
717
+ */
718
+ toEF (): number[] {
719
+ const writer = new Writer()
720
+ this.writeEF(writer)
724
721
  return writer.toArray()
725
722
  }
726
723
 
724
+ /**
725
+ * Converts the transaction to a BRC-30 EF format.
726
+ *
727
+ * @returns {Uint8Array} - The BRC-30 EF representation of the transaction.
728
+ */
729
+ toEFUint8Array (): Uint8Array {
730
+ const writer = new WriterUint8Array()
731
+ this.writeEF(writer)
732
+ return writer.toUint8Array()
733
+ }
734
+
727
735
  /**
728
736
  * Converts the transaction to a hexadecimal string EF.
729
737
  *
730
738
  * @returns {string} - The hexadecimal string representation of the transaction EF.
731
739
  */
732
740
  toHexEF (): string {
733
- return toHex(this.toEF())
741
+ return toHex(this.toEFUint8Array())
734
742
  }
735
743
 
736
744
  /**
@@ -743,10 +751,7 @@ export default class Transaction {
743
751
  return this.hexCache
744
752
  }
745
753
  const bytes = this.getSerializedBytes()
746
- const hex =
747
- BufferCtor != null
748
- ? BufferCtor.from(bytes).toString('hex')
749
- : toHex(Array.from(bytes))
754
+ const hex = toHex(bytes)
750
755
  this.hexCache = hex
751
756
  return hex
752
757
  }
@@ -956,8 +961,7 @@ export default class Transaction {
956
961
  * @returns The serialized BEEF structure
957
962
  * @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
958
963
  */
959
- toBEEF (allowPartial?: boolean): number[] {
960
- const writer = new Writer()
964
+ writeSerializedBEEF (writer: Writer | WriterUint8Array, allowPartial?: boolean): void {
961
965
  writer.writeUInt32LE(BEEF_V1)
962
966
  const BUMPs: MerklePath[] = []
963
967
  const bumpIndexByInstance = new Map<MerklePath, number>()
@@ -1035,6 +1039,34 @@ export default class Transaction {
1035
1039
  return writer.toArray()
1036
1040
  }
1037
1041
 
1042
+ /**
1043
+ * Serializes this transaction, together with its inputs and the respective merkle proofs, into the BEEF (BRC-62) format. This enables efficient verification of its compliance with the rules of SPV.
1044
+ *
1045
+ * @param allowPartial If true, error will not be thrown if there are any missing sourceTransactions.
1046
+ *
1047
+ * @returns {number[]} The serialized BEEF structure
1048
+ * @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
1049
+ */
1050
+ toBEEF (allowPartial?: boolean): number[] {
1051
+ const writer = new Writer()
1052
+ this.writeSerializedBEEF(writer, allowPartial)
1053
+ return writer.toArray()
1054
+ }
1055
+
1056
+ /**
1057
+ * Serializes this transaction, together with its inputs and the respective merkle proofs, into the BEEF (BRC-62) format. This enables efficient verification of its compliance with the rules of SPV.
1058
+ *
1059
+ * @param allowPartial If true, error will not be thrown if there are any missing sourceTransactions.
1060
+ *
1061
+ * @returns {number[]} The serialized BEEF structure
1062
+ * @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
1063
+ */
1064
+ toBEEFUint8Array (allowPartial?: boolean): Uint8Array {
1065
+ const writer = new WriterUint8Array()
1066
+ this.writeSerializedBEEF(writer, allowPartial)
1067
+ return writer.toArray()
1068
+ }
1069
+
1038
1070
  /**
1039
1071
  * Serializes this transaction and its inputs into the Atomic BEEF (BRC-95) format.
1040
1072
  * The Atomic BEEF format starts with a 4-byte prefix `0x01010101`, followed by the TXID of the subject transaction,
@@ -1052,4 +1084,25 @@ export default class Transaction {
1052
1084
  const beefData = this.toBEEF(allowPartial)
1053
1085
  return prefix.concat(txHash, beefData)
1054
1086
  }
1087
+
1088
+ /**
1089
+ * Serializes this transaction and its inputs into the Atomic BEEF (BRC-95) format.
1090
+ * The Atomic BEEF format starts with a 4-byte prefix `0x01010101`, followed by the TXID of the subject transaction,
1091
+ * and then the BEEF data containing only the subject transaction and its dependencies.
1092
+ * This format ensures that the BEEF structure is atomic and contains no unrelated transactions.
1093
+ *
1094
+ * @param allowPartial If true, error will not be thrown if there are any missing sourceTransactions.
1095
+ *
1096
+ * @returns {number[]} - The serialized Atomic BEEF structure.
1097
+ * @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
1098
+ */
1099
+ toAtomicBEEFUint8Array (allowPartial?: boolean): Uint8Array {
1100
+ const writer = new WriterUint8Array()
1101
+ const prefix = [1, 1, 1, 1]
1102
+ writer.write(prefix)
1103
+ const txHash = this.hash() as number[]
1104
+ writer.write(txHash)
1105
+ this.writeSerializedBEEF(writer, allowPartial)
1106
+ return writer.toUint8Array()
1107
+ }
1055
1108
  }
@@ -324,6 +324,81 @@ describe('Beef tests', () => {
324
324
  }
325
325
  })
326
326
 
327
+ test('6b_trimKnownTxids_removes_unreferenced_bumps', async () => {
328
+ // Create a beef with multiple transactions and bumps
329
+ const beef = Beef.fromString(beefs[0])
330
+
331
+ // Verify initial state
332
+ const initialBumpCount = beef.bumps.length
333
+ const initialTxCount = beef.txs.length
334
+ expect(initialBumpCount).toBe(1)
335
+ expect(initialTxCount).toBe(1)
336
+
337
+ // Add some txid-only transactions that don't have bumps
338
+ beef.mergeTxidOnly('txid1')
339
+ beef.mergeTxidOnly('txid2')
340
+ beef.mergeTxidOnly('txid3')
341
+
342
+ expect(beef.txs.length).toBe(4)
343
+ expect(beef.bumps.length).toBe(1)
344
+
345
+ // Get the txid of the transaction with a bump
346
+ const txWithBump = beef.txs.find(tx => tx.bumpIndex !== undefined)
347
+ expect(txWithBump).toBeTruthy()
348
+
349
+ // Trim all txid-only transactions
350
+ beef.trimKnownTxids(['txid1', 'txid2', 'txid3'])
351
+
352
+ // Verify txids were removed
353
+ expect(beef.txs.length).toBe(1)
354
+ expect(beef.bumps.length).toBe(1) // Bump should still be there because it's referenced
355
+
356
+ // Now test removing the transaction that has the bump
357
+ // First, make it txidOnly
358
+ const originalTxid = txWithBump!.txid
359
+ beef.makeTxidOnly(originalTxid)
360
+
361
+ // Now trim it
362
+ beef.trimKnownTxids([originalTxid])
363
+
364
+ // The bump should be removed since no transactions reference it anymore
365
+ expect(beef.txs.length).toBe(0)
366
+ expect(beef.bumps.length).toBe(0)
367
+ })
368
+
369
+ test('6c_trimKnownTxids_updates_bump_indices', async () => {
370
+ // Use existing beef with bumps
371
+ const beef = Beef.fromString(beefs[0])
372
+
373
+ const initialBumpCount = beef.bumps.length
374
+ const initialTxCount = beef.txs.length
375
+
376
+ // Find transaction with bump
377
+ const txWithBump = beef.txs.find(tx => tx.bumpIndex !== undefined)
378
+ expect(txWithBump).toBeTruthy()
379
+ const originalBumpIndex = txWithBump!.bumpIndex
380
+
381
+ // Add some txid-only transactions
382
+ beef.mergeTxidOnly('known1')
383
+ beef.mergeTxidOnly('known2')
384
+
385
+ expect(beef.txs.length).toBe(initialTxCount + 2)
386
+ expect(beef.bumps.length).toBe(initialBumpCount)
387
+
388
+ // Trim the known txids
389
+ beef.trimKnownTxids(['known1', 'known2'])
390
+
391
+ // Verify bump count didn't change (all bumps are still referenced)
392
+ expect(beef.bumps.length).toBe(initialBumpCount)
393
+
394
+ // Verify transaction count decreased
395
+ expect(beef.txs.length).toBe(initialTxCount)
396
+
397
+ // Verify bumpIndex is still correct
398
+ const txAfterTrim = beef.txs.find(tx => tx.txid === txWithBump!.txid)
399
+ expect(txAfterTrim?.bumpIndex).toBe(originalBumpIndex)
400
+ })
401
+
327
402
  test('7_AtomicBeef', async () => {
328
403
  {
329
404
  const beef = Beef.fromString(beefs[0])