@bsv/sdk 1.1.30 → 1.1.32

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 (36) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/transaction/Beef.js +161 -45
  3. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  4. package/dist/cjs/src/transaction/BeefParty.js +16 -0
  5. package/dist/cjs/src/transaction/BeefParty.js.map +1 -1
  6. package/dist/cjs/src/transaction/BeefTx.js +6 -1
  7. package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
  8. package/dist/cjs/src/transaction/Transaction.js +12 -6
  9. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/transaction/Beef.js +160 -44
  12. package/dist/esm/src/transaction/Beef.js.map +1 -1
  13. package/dist/esm/src/transaction/BeefParty.js +16 -0
  14. package/dist/esm/src/transaction/BeefParty.js.map +1 -1
  15. package/dist/esm/src/transaction/BeefTx.js +6 -1
  16. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  17. package/dist/esm/src/transaction/Transaction.js +12 -6
  18. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/transaction/Beef.d.ts +44 -3
  21. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  22. package/dist/types/src/transaction/BeefParty.d.ts +11 -0
  23. package/dist/types/src/transaction/BeefParty.d.ts.map +1 -1
  24. package/dist/types/src/transaction/BeefTx.d.ts +6 -1
  25. package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
  26. package/dist/types/src/transaction/Transaction.d.ts +8 -2
  27. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  28. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  29. package/dist/umd/bundle.js +1 -1
  30. package/docs/transaction.md +141 -9
  31. package/package.json +1 -1
  32. package/src/transaction/Beef.ts +186 -47
  33. package/src/transaction/BeefParty.ts +17 -0
  34. package/src/transaction/BeefTx.ts +6 -1
  35. package/src/transaction/Transaction.ts +12 -6
  36. package/src/transaction/__tests/Beef.test.ts +55 -10
@@ -857,7 +857,7 @@ export default class BeefTx {
857
857
  _rawTx?: number[];
858
858
  _txid?: string;
859
859
  inputTxids: string[] = [];
860
- degree: number = 0;
860
+ isValid?: boolean = undefined;
861
861
  get bumpIndex(): number | undefined
862
862
  set bumpIndex(v: number | undefined)
863
863
  get hasProof(): boolean
@@ -888,6 +888,16 @@ Argument Details
888
888
  + **bumpIndex**
889
889
  + If transaction already has a proof in the beef to which it will be added.
890
890
 
891
+ #### Property isValid
892
+
893
+ true if `hasProof` or all inputs chain to `hasProof`.
894
+
895
+ Typically set by sorting transactions by proven dependency chains.
896
+
897
+ ```ts
898
+ isValid?: boolean = undefined
899
+ ```
900
+
891
901
  </details>
892
902
 
893
903
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
@@ -900,9 +910,13 @@ export class Beef {
900
910
  bumps: MerklePath[] = [];
901
911
  txs: BeefTx[] = [];
902
912
  version: BeefVersion = undefined;
913
+ atomicTxid: string | undefined = undefined;
903
914
  constructor(version?: BeefVersion)
904
915
  get magic(): number
905
916
  findTxid(txid: string): BeefTx | undefined
917
+ findBump(txid: string): MerklePath | undefined
918
+ findTransactionForSigning(txid: string): Transaction | undefined
919
+ findAtomicTransaction(txid: string): Transaction | undefined
906
920
  mergeBump(bump: MerklePath): number
907
921
  mergeRawTx(rawTx: number[], bumpIndex?: number): BeefTx
908
922
  mergeTransaction(tx: Transaction): BeefTx
@@ -917,9 +931,16 @@ export class Beef {
917
931
  static fromReader(br: Reader): Beef
918
932
  static fromBinary(bin: number[]): Beef
919
933
  static fromString(s: string, enc?: "hex" | "utf8" | "base64"): Beef
920
- sortTxs(): string[]
934
+ sortTxs(): {
935
+ missingInputs: string[];
936
+ notValid: string[];
937
+ valid: string[];
938
+ withMissingInputs: string[];
939
+ txidOnly: string[];
940
+ }
921
941
  clone(): Beef
922
942
  trimKnownTxids(knownTxids: string[])
943
+ getValidTxids(): string[]
923
944
  toLogString(): string
924
945
  }
925
946
  ```
@@ -938,6 +959,55 @@ Returns
938
959
 
939
960
  a shallow copy of this beef
940
961
 
962
+ #### Method findAtomicTransaction
963
+
964
+ Builds the proof tree rooted at a specific `Transaction`.
965
+
966
+ To succeed, the Beef must contain all the required transaction and merkle path data.
967
+
968
+ ```ts
969
+ findAtomicTransaction(txid: string): Transaction | undefined
970
+ ```
971
+
972
+ Returns
973
+
974
+ Transaction with input `SourceTransaction` and `MerklePath` populated from this Beef.
975
+
976
+ Argument Details
977
+
978
+ + **txid**
979
+ + The id of the target transaction.
980
+
981
+ #### Method findBump
982
+
983
+ ```ts
984
+ findBump(txid: string): MerklePath | undefined
985
+ ```
986
+
987
+ Returns
988
+
989
+ `MerklePath` with level zero hash equal to txid or undefined.
990
+
991
+ #### Method findTransactionForSigning
992
+
993
+ Finds a Transaction in this `Beef`
994
+ and adds any missing input SourceTransactions from this `Beef`.
995
+
996
+ The result is suitable for signing.
997
+
998
+ ```ts
999
+ findTransactionForSigning(txid: string): Transaction | undefined
1000
+ ```
1001
+
1002
+ Returns
1003
+
1004
+ Transaction with all available input `SourceTransaction`s from this Beef.
1005
+
1006
+ Argument Details
1007
+
1008
+ + **txid**
1009
+ + The id of the target transaction.
1010
+
941
1011
  #### Method findTxid
942
1012
 
943
1013
  ```ts
@@ -989,6 +1059,16 @@ Argument Details
989
1059
  + **enc**
990
1060
  + The encoding of the string value from which BEEF should be constructed
991
1061
 
1062
+ #### Method getValidTxids
1063
+
1064
+ ```ts
1065
+ getValidTxids(): string[]
1066
+ ```
1067
+
1068
+ Returns
1069
+
1070
+ array of transaction txids that either have a proof or whose inputs chain back to a proven transaction.
1071
+
992
1072
  #### Method isValid
993
1073
 
994
1074
  Sorts `txs` and checks structural validity of beef.
@@ -1074,15 +1154,26 @@ Argument Details
1074
1154
 
1075
1155
  #### Method sortTxs
1076
1156
 
1077
- Sort the `txs` by input txid dependency order.
1157
+ Sort the `txs` by input txid dependency order:
1158
+ - Oldest Tx Anchored by Path
1159
+ - Newer Txs depending on Older parents
1160
+ - Newest Tx
1161
+
1162
+ with proof (MerklePath) last, longest chain of dependencies first
1078
1163
 
1079
1164
  ```ts
1080
- sortTxs(): string[]
1165
+ sortTxs(): {
1166
+ missingInputs: string[];
1167
+ notValid: string[];
1168
+ valid: string[];
1169
+ withMissingInputs: string[];
1170
+ txidOnly: string[];
1171
+ }
1081
1172
  ```
1082
1173
 
1083
1174
  Returns
1084
1175
 
1085
- array of input txids of unproven transactions that aren't included in txs.
1176
+ `{ missingInputs, notValid, valid, withMissingInputs }`
1086
1177
 
1087
1178
  #### Method toBinary
1088
1179
 
@@ -1219,8 +1310,8 @@ export default class Transaction {
1219
1310
  id(enc: "hex"): string;
1220
1311
  id(enc?: "hex"): number[] | string
1221
1312
  async verify(chainTracker: ChainTracker | "scripts only" = defaultChainTracker(), feeModel?: FeeModel): Promise<boolean>
1222
- toBEEF(): number[]
1223
- toAtomicBEEF(): number[]
1313
+ toBEEF(allowPartial?: boolean): number[]
1314
+ toAtomicBEEF(allowPartial?: boolean): number[]
1224
1315
  }
1225
1316
  ```
1226
1317
 
@@ -1554,25 +1645,43 @@ and then the BEEF data containing only the subject transaction and its dependenc
1554
1645
  This format ensures that the BEEF structure is atomic and contains no unrelated transactions.
1555
1646
 
1556
1647
  ```ts
1557
- toAtomicBEEF(): number[]
1648
+ toAtomicBEEF(allowPartial?: boolean): number[]
1558
1649
  ```
1559
1650
 
1560
1651
  Returns
1561
1652
 
1562
1653
  - The serialized Atomic BEEF structure.
1563
1654
 
1655
+ Argument Details
1656
+
1657
+ + **allowPartial**
1658
+ + If true, error will not be thrown if there are any missing sourceTransactions.
1659
+
1660
+ Throws
1661
+
1662
+ Error if there are any missing sourceTransactions unless `allowPartial` is true.
1663
+
1564
1664
  #### Method toBEEF
1565
1665
 
1566
1666
  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.
1567
1667
 
1568
1668
  ```ts
1569
- toBEEF(): number[]
1669
+ toBEEF(allowPartial?: boolean): number[]
1570
1670
  ```
1571
1671
 
1572
1672
  Returns
1573
1673
 
1574
1674
  The serialized BEEF structure
1575
1675
 
1676
+ Argument Details
1677
+
1678
+ + **allowPartial**
1679
+ + If true, error will not be thrown if there are any missing sourceTransactions.
1680
+
1681
+ Throws
1682
+
1683
+ Error if there are any missing sourceTransactions unless `allowPartial` is true.
1684
+
1576
1685
  #### Method toBinary
1577
1686
 
1578
1687
  Converts the transaction to a binary array format.
@@ -1716,6 +1825,7 @@ export class BeefParty extends Beef {
1716
1825
  getKnownTxidsForParty(party: string): string[]
1717
1826
  getTrimmedBeefForParty(party: string): Beef
1718
1827
  addKnownTxidsForParty(party: string, knownTxids: string[])
1828
+ mergeBeefFromParty(party: string, beef: number[] | Beef)
1719
1829
  }
1720
1830
  ```
1721
1831
 
@@ -1794,6 +1904,18 @@ Returns
1794
1904
 
1795
1905
  `true` if `party` has already beed added to this `BeefParty`.
1796
1906
 
1907
+ #### Method mergeBeefFromParty
1908
+
1909
+ Merge a `beef` received from a specific `party`.
1910
+
1911
+ Updates this `BeefParty` to track all the txids
1912
+ corresponding to transactions for which `party`
1913
+ has raw transaction and validity proof data.
1914
+
1915
+ ```ts
1916
+ mergeBeefFromParty(party: string, beef: number[] | Beef)
1917
+ ```
1918
+
1797
1919
  </details>
1798
1920
 
1799
1921
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
@@ -1925,6 +2047,7 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
1925
2047
 
1926
2048
  | |
1927
2049
  | --- |
2050
+ | [ATOMIC_BEEF](#variable-atomic_beef) |
1928
2051
  | [BEEF_MAGIC](#variable-beef_magic) |
1929
2052
  | [BEEF_MAGIC_TXID_ONLY_EXTENSION](#variable-beef_magic_txid_only_extension) |
1930
2053
  | [BEEF_MAGIC_V2](#variable-beef_magic_v2) |
@@ -1960,3 +2083,12 @@ BEEF_MAGIC_TXID_ONLY_EXTENSION = 4022206465
1960
2083
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
1961
2084
 
1962
2085
  ---
2086
+ ### Variable: ATOMIC_BEEF
2087
+
2088
+ ```ts
2089
+ ATOMIC_BEEF = 16843009
2090
+ ```
2091
+
2092
+ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
2093
+
2094
+ ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.1.30",
3
+ "version": "1.1.32",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -7,6 +7,7 @@ import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
7
7
  export const BEEF_MAGIC = 4022206465 // 0100BEEF in LE order
8
8
  export const BEEF_MAGIC_V2 = 4022206466 // 0200BEEF in LE order
9
9
  export const BEEF_MAGIC_TXID_ONLY_EXTENSION = 4022206465 // 0100BEEF in LE order
10
+ export const ATOMIC_BEEF = 0x01010101 // Atomic Beef serialization prefix
10
11
 
11
12
  export type BeefVersion = undefined | 'V1' | 'V2'
12
13
 
@@ -16,6 +17,14 @@ export type BeefVersion = undefined | 'V1' | 'V2'
16
17
  *
17
18
  * BUMP standard: BRC-74: BSV Unified Merkle Path (BUMP) Format
18
19
  * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0074.md
20
+ *
21
+ * BRC-95: Atomic BEEF Transactions
22
+ * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0095.md
23
+ *
24
+ * The Atomic BEEF format is supported by the binary deserialization static method `fromBinary`.
25
+ *
26
+ * BRC-96: BEEF V2, Txid Only Extension
27
+ * https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0096.md
19
28
  *
20
29
  * A valid serialized BEEF is the cornerstone of Simplified Payment Validation (SPV)
21
30
  * where they are exchanged between two non-trusting parties to establish the
@@ -48,11 +57,18 @@ export type BeefVersion = undefined | 'V1' | 'V2'
48
57
  *
49
58
  * A valid `Beef` is only required when sent to a party with no shared history,
50
59
  * such as a transaction processor.
60
+ *
61
+ * IMPORTANT NOTE:
62
+ * It is fundamental to the BEEF value proposition that only valid transactions and valid
63
+ * merkle path (BUMP) data be added to it. Merging invalid data breaks the `verify` and `isValid`
64
+ * functions. There is no support for removing invalid data. A `Beef` that becomes invalid
65
+ * must be discarded.
51
66
  */
52
67
  export class Beef {
53
68
  bumps: MerklePath[] = []
54
69
  txs: BeefTx[] = []
55
70
  version: BeefVersion = undefined
71
+ atomicTxid: string | undefined = undefined
56
72
 
57
73
  constructor (version?: BeefVersion) {
58
74
  this.version = version
@@ -82,6 +98,81 @@ export class Beef {
82
98
  return this.txs.find(tx => tx.txid === txid)
83
99
  }
84
100
 
101
+ /**
102
+ * @returns `MerklePath` with level zero hash equal to txid or undefined.
103
+ */
104
+ findBump (txid: string): MerklePath | undefined {
105
+ return this.bumps.find(b => b.path[0].find(leaf => leaf.hash === txid))
106
+ }
107
+
108
+ /**
109
+ * Finds a Transaction in this `Beef`
110
+ * and adds any missing input SourceTransactions from this `Beef`.
111
+ *
112
+ * The result is suitable for signing.
113
+ *
114
+ * @param txid The id of the target transaction.
115
+ * @returns Transaction with all available input `SourceTransaction`s from this Beef.
116
+ */
117
+ findTransactionForSigning (txid: string): Transaction | undefined {
118
+
119
+ const beefTx = this.findTxid(txid)
120
+ if (!beefTx) return undefined
121
+
122
+ for (const i of beefTx.tx.inputs) {
123
+ if (!i.sourceTransaction) {
124
+ const itx = this.findTxid(i.sourceTXID)
125
+ if (itx) {
126
+ i.sourceTransaction = itx.tx
127
+ }
128
+ }
129
+ }
130
+
131
+ return beefTx.tx
132
+ }
133
+ /**
134
+ * Builds the proof tree rooted at a specific `Transaction`.
135
+ *
136
+ * To succeed, the Beef must contain all the required transaction and merkle path data.
137
+ *
138
+ * @param txid The id of the target transaction.
139
+ * @returns Transaction with input `SourceTransaction` and `MerklePath` populated from this Beef.
140
+ */
141
+ findAtomicTransaction (txid: string): Transaction | undefined {
142
+
143
+ const beefTx = this.findTxid(txid)
144
+ if (!beefTx) return undefined
145
+
146
+ const addInputProof = (beef: Beef, tx: Transaction) => {
147
+ const mp = beef.findBump(tx.id('hex'))
148
+ if (mp)
149
+ tx.merklePath = mp
150
+ else {
151
+ for (const i of tx.inputs) {
152
+ if (!i.sourceTransaction) {
153
+ const itx = beef.findTxid(i.sourceTXID)
154
+ if (itx) {
155
+ i.sourceTransaction = itx.tx
156
+ }
157
+ }
158
+ if (i.sourceTransaction) {
159
+ const mp = beef.findBump(i.sourceTransaction.id('hex'))
160
+ if (mp) {
161
+ i.sourceTransaction.merklePath = mp
162
+ }
163
+ else {
164
+ addInputProof(beef, i.sourceTransaction)
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ addInputProof(this, beefTx.tx)
172
+
173
+ return beefTx.tx
174
+ }
175
+
85
176
  /**
86
177
  * Merge a MerklePath that is assumed to be fully valid.
87
178
  * @param bump
@@ -334,7 +425,13 @@ export class Beef {
334
425
  }
335
426
 
336
427
  static fromReader (br: Reader): Beef {
337
- const version = br.readUInt32LE()
428
+ let version = br.readUInt32LE()
429
+ let atomicTxid: string | undefined = undefined
430
+ if (version === ATOMIC_BEEF) {
431
+ // Skip the txid and re-read the BEEF version
432
+ atomicTxid = toHex(br.readReverse(32))
433
+ version = br.readUInt32LE()
434
+ }
338
435
  if (version !== BEEF_MAGIC && version !== BEEF_MAGIC_V2) { throw new Error(`Serialized BEEF must start with ${BEEF_MAGIC} or ${BEEF_MAGIC_V2} but starts with ${version}`) }
339
436
  const beef = new Beef(version === BEEF_MAGIC_V2 ? 'V2' : undefined)
340
437
  const bumpsLength = br.readVarIntNum()
@@ -347,6 +444,7 @@ export class Beef {
347
444
  const beefTx = BeefTx.fromReader(br, version)
348
445
  beef.txs.push(beefTx)
349
446
  }
447
+ beef.atomicTxid = atomicTxid
350
448
  return beef
351
449
  }
352
450
 
@@ -395,71 +493,104 @@ export class Beef {
395
493
  }
396
494
 
397
495
  /**
398
- * Sort the `txs` by input txid dependency order.
399
- * @returns array of input txids of unproven transactions that aren't included in txs.
496
+ * Sort the `txs` by input txid dependency order:
497
+ * - Oldest Tx Anchored by Path
498
+ * - Newer Txs depending on Older parents
499
+ * - Newest Tx
500
+ *
501
+ * with proof (MerklePath) last, longest chain of dependencies first
502
+ *
503
+ * @returns `{ missingInputs, notValid, valid, withMissingInputs }`
400
504
  */
401
- sortTxs (): string[] {
402
- const missingInputs: Record<string, boolean> = {}
403
-
505
+ sortTxs ()
506
+ : {
507
+ missingInputs: string[],
508
+ notValid: string[],
509
+ valid: string[],
510
+ withMissingInputs: string[],
511
+ txidOnly: string[]
512
+ } {
513
+ // Hashtable of valid txids (with proof or all inputs chain to proof)
514
+ const validTxids: Record<string, boolean> = {}
515
+
516
+ // Hashtable of all transaction txids to transaction
404
517
  const txidToTx: Record<string, BeefTx> = {}
405
518
 
519
+ // queue of unsorted transactions ...
520
+ let queue: BeefTx[] = []
521
+
522
+ // sorted transactions: hasProof to with longest dependency chain
523
+ const result: BeefTx[] = []
524
+
525
+ const txidOnly: BeefTx[] = []
526
+
406
527
  for (const tx of this.txs) {
407
528
  txidToTx[tx.txid] = tx
408
- // All transactions in this beef start at degree zero.
409
- tx.degree = 0
529
+ tx.isValid = tx.hasProof
530
+ if (tx.isValid) {
531
+ validTxids[tx.txid] = true
532
+ result.push(tx)
533
+ } else if (tx.isTxidOnly)
534
+ txidOnly.push(tx)
535
+ else
536
+ queue.push(tx)
410
537
  }
411
538
 
412
- for (const tx of this.txs) {
413
- if (tx.bumpIndex === undefined) {
539
+ // Hashtable of unknown input txids used to fund transactions without their own proof.
540
+ const missingInputs: Record<string, boolean> = {}
541
+ // transactions with one or more missing inputs
542
+ const txsMissingInputs: BeefTx[] = []
543
+
544
+ const possiblyMissingInputs = queue
545
+ queue = []
546
+
547
+ for (const tx of possiblyMissingInputs) {
548
+ let hasMissingInput = false
549
+ if (!tx.isValid) {
414
550
  // For all the unproven transactions,
415
551
  // link their inputs that exist in this beef,
416
552
  // make a note of missing inputs.
417
553
  for (const inputTxid of tx.inputTxids) {
418
- if (!txidToTx[inputTxid]) { missingInputs[inputTxid] = true }
554
+ if (!txidToTx[inputTxid]) {
555
+ missingInputs[inputTxid] = true
556
+ hasMissingInput = true
557
+ }
419
558
  }
420
559
  }
421
- }
422
-
423
- // queue of transactions that no unsorted transactions depend upon...
424
- const queue: BeefTx[] = []
425
- // sorted transactions
426
- const result: BeefTx[] = []
427
-
428
- // Increment each txid's degree for every input reference to it by another txid
429
- for (const tx of this.txs) {
430
- for (const inputTxid of tx.inputTxids) {
431
- const tx = txidToTx[inputTxid]
432
- if (tx) { tx.degree++ }
433
- }
434
- }
435
- // Since circular dependencies aren't possible, start with the txids no one depends on.
436
- // These are the transactions that should be sent last...
437
- for (const tx of this.txs) {
438
- if (tx.degree === 0) {
560
+ if (hasMissingInput)
561
+ txsMissingInputs.push(tx)
562
+ else
439
563
  queue.push(tx)
440
- }
441
564
  }
442
- // As long as we have transactions to send...
565
+
566
+ // As long as we have unsorted transactions...
443
567
  while (queue.length > 0) {
444
- const tx = queue.shift()!
445
- // Add it as new first to send
446
- result.unshift(tx)
447
- // And remove its impact on degree
448
- // noting that any tx achieving a
449
- // value of zero can be sent...
450
- for (const inputTxid of tx.inputTxids) {
451
- const inputTx = txidToTx[inputTxid]
452
- if (inputTx) {
453
- inputTx.degree--
454
- if (inputTx.degree === 0) {
455
- queue.push(inputTx)
456
- }
457
- }
568
+ const oldQueue = queue
569
+ queue = []
570
+ for (const tx of oldQueue) {
571
+ if (tx.inputTxids.every(txid => validTxids[txid])) {
572
+ validTxids[tx.txid] = true
573
+ result.push(tx)
574
+ } else
575
+ queue.push(tx)
458
576
  }
577
+ if (oldQueue.length === queue.length)
578
+ break;
459
579
  }
460
- this.txs = result
461
580
 
462
- return Object.keys(missingInputs)
581
+ // transactions that don't have proofs and don't chain to proofs
582
+ const txsNotValid = queue
583
+
584
+ // New order of txs is sorted, unsortable, txidOnly (no raw transaction)
585
+ this.txs = result.concat(queue).concat(txidOnly).concat(txsMissingInputs)
586
+
587
+ return {
588
+ missingInputs: Object.keys(missingInputs),
589
+ notValid: txsNotValid.map(tx => tx.txid),
590
+ valid: Object.keys(validTxids),
591
+ withMissingInputs: txsMissingInputs.map(tx => tx.txid),
592
+ txidOnly: txidOnly.map(tx => tx.txid)
593
+ }
463
594
  }
464
595
 
465
596
  /**
@@ -488,6 +619,14 @@ export class Beef {
488
619
  // TODO: bumps could be trimmed to eliminate unreferenced proofs.
489
620
  }
490
621
 
622
+ /**
623
+ * @returns array of transaction txids that either have a proof or whose inputs chain back to a proven transaction.
624
+ */
625
+ getValidTxids() : string[] {
626
+ const r = this.sortTxs()
627
+ return r.valid
628
+ }
629
+
491
630
  /**
492
631
  * @returns Summary of `Beef` contents as multi-line string.
493
632
  */
@@ -91,6 +91,23 @@ export class BeefParty extends Beef {
91
91
  this.mergeTxidOnly(txid)
92
92
  }
93
93
  }
94
+
95
+ /**
96
+ * Merge a `beef` received from a specific `party`.
97
+ *
98
+ * Updates this `BeefParty` to track all the txids
99
+ * corresponding to transactions for which `party`
100
+ * has raw transaction and validity proof data.
101
+ *
102
+ * @param party
103
+ * @param beef
104
+ */
105
+ mergeBeefFromParty (party: string, beef: number[] | Beef) {
106
+ const b: Beef = Array.isArray(beef) ? Beef.fromBinary(beef) : beef
107
+ const knownTxids = b.getValidTxids()
108
+ this.mergeBeef(b)
109
+ this.addKnownTxidsForParty(party, knownTxids)
110
+ }
94
111
  }
95
112
 
96
113
  export default BeefParty
@@ -18,7 +18,12 @@ export default class BeefTx {
18
18
  _rawTx?: number[]
19
19
  _txid?: string
20
20
  inputTxids: string[] = []
21
- degree: number = 0
21
+ /**
22
+ * true if `hasProof` or all inputs chain to `hasProof`.
23
+ *
24
+ * Typically set by sorting transactions by proven dependency chains.
25
+ */
26
+ isValid?: boolean = undefined
22
27
 
23
28
  get bumpIndex (): number | undefined { return this._bumpIndex }
24
29
 
@@ -859,10 +859,13 @@ export default class Transaction {
859
859
 
860
860
  /**
861
861
  * 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.
862
+ *
863
+ * @param allowPartial If true, error will not be thrown if there are any missing sourceTransactions.
862
864
  *
863
865
  * @returns The serialized BEEF structure
866
+ * @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
864
867
  */
865
- toBEEF (): number[] {
868
+ toBEEF (allowPartial?: boolean): number[] {
866
869
  const writer = new Writer()
867
870
  writer.writeUInt32LE(4022206465)
868
871
  const BUMPs: MerklePath[] = []
@@ -907,10 +910,10 @@ export default class Transaction {
907
910
  if (!hasProof) {
908
911
  for (let i = 0; i < tx.inputs.length; i++) {
909
912
  const input = tx.inputs[i]
910
- if (typeof input.sourceTransaction !== 'object') {
913
+ if (typeof input.sourceTransaction === 'object')
914
+ addPathsAndInputs(input.sourceTransaction)
915
+ else if (!allowPartial)
911
916
  throw new Error('A required source transaction is missing!')
912
- }
913
- addPathsAndInputs(input.sourceTransaction)
914
917
  }
915
918
  }
916
919
  }
@@ -940,16 +943,19 @@ export default class Transaction {
940
943
  * and then the BEEF data containing only the subject transaction and its dependencies.
941
944
  * This format ensures that the BEEF structure is atomic and contains no unrelated transactions.
942
945
  *
946
+ * @param allowPartial If true, error will not be thrown if there are any missing sourceTransactions.
947
+ *
943
948
  * @returns {number[]} - The serialized Atomic BEEF structure.
949
+ * @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
944
950
  */
945
- toAtomicBEEF (): number[] {
951
+ toAtomicBEEF (allowPartial?: boolean): number[] {
946
952
  const writer = new Writer()
947
953
  // Write the Atomic BEEF prefix
948
954
  writer.writeUInt32LE(0x01010101)
949
955
  // Write the subject TXID (big-endian)
950
956
  writer.write(this.id())
951
957
  // Append the BEEF data
952
- const beefData = this.toBEEF()
958
+ const beefData = this.toBEEF(allowPartial)
953
959
  writer.write(beefData)
954
960
  return writer.toArray()
955
961
  }