@bsv/sdk 1.1.25 → 1.1.27

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 (32) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/transaction/Beef.js +17 -10
  3. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  4. package/dist/cjs/src/transaction/BeefTx.js +67 -24
  5. package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
  6. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  7. package/dist/cjs/src/transaction/Transaction.js +154 -34
  8. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  9. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  10. package/dist/esm/src/transaction/Beef.js +17 -10
  11. package/dist/esm/src/transaction/Beef.js.map +1 -1
  12. package/dist/esm/src/transaction/BeefTx.js +68 -25
  13. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  14. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  15. package/dist/esm/src/transaction/Transaction.js +154 -34
  16. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  17. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  18. package/dist/types/src/transaction/Beef.d.ts +5 -3
  19. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  20. package/dist/types/src/transaction/BeefTx.d.ts +2 -2
  21. package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
  22. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  23. package/dist/types/src/transaction/Transaction.d.ts +44 -4
  24. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  25. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  26. package/docs/transaction.md +80 -9
  27. package/package.json +1 -1
  28. package/src/transaction/Beef.ts +23 -10
  29. package/src/transaction/BeefTx.ts +65 -24
  30. package/src/transaction/MerklePath.ts +11 -11
  31. package/src/transaction/Transaction.ts +195 -59
  32. package/src/transaction/__tests/Transaction.test.ts +222 -3
@@ -1,6 +1,6 @@
1
1
  import BigNumber from '../../../dist/cjs/src/primitives/BigNumber'
2
2
  import TransactionSignature from '../../../dist/cjs/src/primitives/TransactionSignature'
3
- import { toHex, toArray } from '../../../dist/cjs/src/primitives/utils'
3
+ import { toHex, toArray, Writer } from '../../../dist/cjs/src/primitives/utils'
4
4
  import Script from '../../../dist/cjs/src/script/Script'
5
5
  import UnlockingScript from '../../../dist/cjs/src/script/UnlockingScript'
6
6
  import LockingScript from '../../../dist/cjs/src/script/LockingScript'
@@ -10,6 +10,8 @@ import PrivateKey from '../../../dist/cjs/src/primitives/PrivateKey'
10
10
  import Curve from '../../../dist/cjs/src/primitives/Curve'
11
11
  import P2PKH from '../../../dist/cjs/src/script/templates/P2PKH'
12
12
  import fromUtxo from '../../../dist/cjs/src/compat/Utxo'
13
+ import MerklePath from '../../../dist/cjs/src/transaction/MerklePath'
14
+ import { BEEF_MAGIC } from '../../../dist/cjs/src/transaction/Beef'
13
15
 
14
16
  import sighashVectors from '../../primitives/__tests/sighash.vectors'
15
17
  import invalidTransactions from './tx.invalid.vectors'
@@ -422,7 +424,7 @@ describe('Transaction', () => {
422
424
  status: 200,
423
425
  statusText: 'OK',
424
426
  headers: {
425
- get (key: string) {
427
+ get(key: string) {
426
428
  if (key === 'Content-Type') {
427
429
  return 'application/json'
428
430
  }
@@ -501,7 +503,7 @@ describe('Transaction', () => {
501
503
  status: 200,
502
504
  statusText: 'OK',
503
505
  headers: {
504
- get (key: string) {
506
+ get(key: string) {
505
507
  if (key === 'Content-Type') {
506
508
  return 'application/json'
507
509
  }
@@ -616,4 +618,221 @@ describe('Transaction', () => {
616
618
  })
617
619
  })
618
620
  })
621
+
622
+ describe('Atomic BEEF', () => {
623
+ it('should serialize a transaction to Atomic BEEF format correctly', async () => {
624
+ const privateKey = new PrivateKey(1)
625
+ const publicKey = new Curve().g.mul(privateKey)
626
+ const publicKeyHash = hash160(publicKey.encode(true)) as number[]
627
+ const p2pkh = new P2PKH()
628
+
629
+ // Create a simple transaction
630
+ const sourceTx = new Transaction(1, [], [{
631
+ lockingScript: p2pkh.lock(publicKeyHash),
632
+ satoshis: 10000
633
+ }], 0)
634
+
635
+ const spendTx = new Transaction(1, [{
636
+ sourceTransaction: sourceTx,
637
+ sourceOutputIndex: 0,
638
+ unlockingScriptTemplate: p2pkh.unlock(privateKey),
639
+ sequence: 0xffffffff
640
+ }], [{
641
+ satoshis: 9000,
642
+ lockingScript: p2pkh.lock(publicKeyHash)
643
+ }], 0)
644
+
645
+ // Sign the transaction
646
+ await spendTx.fee()
647
+ await spendTx.sign()
648
+
649
+ // Assign a MerklePath to the source transaction to simulate mined transaction
650
+ const sourceTxID = sourceTx.id('hex')
651
+ const merklePath = new MerklePath(1000, [
652
+ [
653
+ { offset: 0, hash: sourceTxID, txid: true },
654
+ { offset: 1, duplicate: true }
655
+ ]
656
+ ])
657
+ sourceTx.merklePath = merklePath
658
+
659
+ // Serialize to Atomic BEEF
660
+ const atomicBEEF = spendTx.toAtomicBEEF()
661
+ expect(atomicBEEF).toBeDefined()
662
+ // Verify that the Atomic BEEF starts with the correct prefix and TXID
663
+ const expectedPrefix = [0x01, 0x01, 0x01, 0x01]
664
+ expect(atomicBEEF.slice(0, 4)).toEqual(expectedPrefix)
665
+ const txid = spendTx.id()
666
+ expect(atomicBEEF.slice(4, 36)).toEqual(txid)
667
+
668
+ // Deserialize from Atomic BEEF
669
+ const deserializedTx = Transaction.fromAtomicBEEF(atomicBEEF)
670
+ expect(deserializedTx.toHex()).toEqual(spendTx.toHex())
671
+ })
672
+
673
+ it('should throw an error when deserializing Atomic BEEF if subject transaction is missing', () => {
674
+ // Create Atomic BEEF data with missing subject transaction
675
+ const writer = new Writer()
676
+ // Write Atomic BEEF prefix
677
+ writer.writeUInt32LE(0x01010101)
678
+ // Write subject TXID
679
+ const fakeTXID = toArray('00'.repeat(32), 'hex')
680
+ writer.write(fakeTXID)
681
+ // Write empty BEEF data
682
+ writer.writeUInt32LE(BEEF_MAGIC) // BEEF version
683
+ writer.writeVarIntNum(0) // No BUMPs
684
+ writer.writeVarIntNum(0) // No transactions
685
+
686
+ const atomicBEEFData = writer.toArray()
687
+
688
+ expect(() => {
689
+ Transaction.fromAtomicBEEF(atomicBEEFData)
690
+ }).toThrowError('Subject transaction with TXID 0000000000000000000000000000000000000000000000000000000000000000 not found in Atomic BEEF data.')
691
+ })
692
+
693
+ it('should throw an error when deserializing Atomic BEEF if there are unrelated transactions', async () => {
694
+ // Create two unrelated transactions
695
+ const privateKey1 = new PrivateKey(1)
696
+ const publicKey1 = new Curve().g.mul(privateKey1)
697
+ const publicKeyHash1 = hash160(publicKey1.encode(true)) as number[]
698
+ const p2pkh = new P2PKH()
699
+
700
+ const sourceTx1 = new Transaction(1, [], [{
701
+ lockingScript: p2pkh.lock(publicKeyHash1),
702
+ satoshis: 10000
703
+ }], 0)
704
+
705
+ const spendTx1 = new Transaction(1, [{
706
+ sourceTransaction: sourceTx1,
707
+ sourceOutputIndex: 0,
708
+ unlockingScriptTemplate: p2pkh.unlock(privateKey1),
709
+ sequence: 0xffffffff
710
+ }], [{
711
+ satoshis: 9000,
712
+ lockingScript: p2pkh.lock(publicKeyHash1)
713
+ }], 0)
714
+
715
+ // Another unrelated transaction
716
+ const privateKey2 = new PrivateKey(2)
717
+ const publicKey2 = new Curve().g.mul(privateKey2)
718
+ const publicKeyHash2 = hash160(publicKey2.encode(true)) as number[]
719
+
720
+ const sourceTx2 = new Transaction(1, [], [{
721
+ lockingScript: p2pkh.lock(publicKeyHash2),
722
+ satoshis: 10000
723
+ }], 0)
724
+
725
+ // Assign merkle paths to source transactions to simulate mined transactions
726
+ const sourceTx1ID = sourceTx1.id('hex')
727
+ const sourceTx2ID = sourceTx2.id('hex')
728
+ sourceTx1.merklePath = new MerklePath(1000, [
729
+ [
730
+ { offset: 0, hash: sourceTx1ID, txid: true },
731
+ { offset: 1, duplicate: true }
732
+ ]
733
+ ])
734
+ sourceTx2.merklePath = new MerklePath(1001, [
735
+ [
736
+ { offset: 0, hash: sourceTx2ID, txid: true },
737
+ { offset: 1, duplicate: true }
738
+ ]
739
+ ])
740
+
741
+ // Sign the transaction
742
+ await spendTx1.fee()
743
+ await spendTx1.sign()
744
+
745
+ // Construct the Atomic BEEF data manually, including the unrelated transaction
746
+ const writer = new Writer()
747
+ // Write Atomic BEEF prefix
748
+ writer.writeUInt32LE(0x01010101)
749
+ // Write subject TXID (spendTx1)
750
+ const spendTx1ID = spendTx1.id()
751
+ writer.write(spendTx1ID)
752
+
753
+ // Build BEEF data
754
+ const beefWriter = new Writer()
755
+ beefWriter.writeUInt32LE(BEEF_MAGIC) // BEEF version
756
+ // BUMPs
757
+ beefWriter.writeVarIntNum(2)
758
+ beefWriter.write(sourceTx1.merklePath.toBinary())
759
+ beefWriter.write(sourceTx2.merklePath.toBinary())
760
+ // Transactions
761
+ beefWriter.writeVarIntNum(3)
762
+ // Transaction 1 (sourceTx1)
763
+ beefWriter.write(sourceTx1.toBinary())
764
+ beefWriter.writeUInt8(1) // hasBump
765
+ beefWriter.writeVarIntNum(0) // BUMP index
766
+ // Transaction 2 (sourceTx2) - unrelated
767
+ beefWriter.write(sourceTx2.toBinary())
768
+ beefWriter.writeUInt8(1) // hasBump
769
+ beefWriter.writeVarIntNum(1) // BUMP index
770
+ // Transaction 3 (spendTx1)
771
+ beefWriter.write(spendTx1.toBinary())
772
+ beefWriter.writeUInt8(0) // no BUMP
773
+
774
+ // Combine Atomic BEEF data
775
+ writer.write(beefWriter.toArray())
776
+ const atomicBEEFData = writer.toArray()
777
+
778
+ // Attempt to deserialize
779
+ expect(() => {
780
+ Transaction.fromAtomicBEEF(atomicBEEFData)
781
+ }).toThrowError(/Unrelated transaction with TXID .+ found in Atomic BEEF data./)
782
+ })
783
+
784
+ it('should allow selecting a specific TXID from BEEF data', async () => {
785
+ // Create two transactions, one depending on the other
786
+ const privateKey = new PrivateKey(1)
787
+ const publicKey = new Curve().g.mul(privateKey)
788
+ const publicKeyHash = hash160(publicKey.encode(true)) as number[]
789
+ const p2pkh = new P2PKH()
790
+
791
+ const sourceTx = new Transaction(1, [], [{
792
+ lockingScript: p2pkh.lock(publicKeyHash),
793
+ satoshis: 10000
794
+ }], 0)
795
+
796
+ const spendTx = new Transaction(1, [{
797
+ sourceTransaction: sourceTx,
798
+ sourceOutputIndex: 0,
799
+ unlockingScriptTemplate: p2pkh.unlock(privateKey),
800
+ sequence: 0xffffffff
801
+ }], [{
802
+ satoshis: 9000,
803
+ lockingScript: p2pkh.lock(publicKeyHash)
804
+ }], 0)
805
+
806
+ // Sign transactions
807
+ await spendTx.fee()
808
+ await spendTx.sign()
809
+
810
+ // Assign merkle path to source transaction
811
+ const sourceTxID = sourceTx.id('hex')
812
+ sourceTx.merklePath = new MerklePath(1000, [
813
+ [
814
+ { offset: 0, hash: sourceTxID, txid: true },
815
+ { offset: 1, duplicate: true }
816
+ ]
817
+ ])
818
+
819
+ // Serialize to BEEF
820
+ const beefData = spendTx.toBEEF()
821
+ // Get TXIDs
822
+ const spendTxID = spendTx.id('hex')
823
+
824
+ // Deserialize the source transaction from BEEF data
825
+ const deserializedSourceTx = Transaction.fromBEEF(beefData, sourceTxID)
826
+ expect(deserializedSourceTx.id('hex')).toEqual(sourceTxID)
827
+
828
+ // Deserialize the spend transaction from BEEF data
829
+ const deserializedSpendTx = Transaction.fromBEEF(beefData, spendTxID)
830
+ expect(deserializedSpendTx.id('hex')).toEqual(spendTxID)
831
+
832
+ // Attempt to deserialize a non-existent transaction
833
+ expect(() => {
834
+ Transaction.fromBEEF(beefData, '00'.repeat(32))
835
+ }).toThrowError('Transaction with TXID 0000000000000000000000000000000000000000000000000000000000000000 not found in BEEF data.')
836
+ })
837
+ })
619
838
  })