@arkade-os/sdk 0.1.4 → 0.2.0

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 (114) hide show
  1. package/README.md +156 -174
  2. package/dist/cjs/arknote/index.js +61 -58
  3. package/dist/cjs/bip322/errors.js +13 -0
  4. package/dist/cjs/bip322/index.js +178 -0
  5. package/dist/cjs/forfeit.js +14 -25
  6. package/dist/cjs/identity/singleKey.js +68 -0
  7. package/dist/cjs/index.js +41 -17
  8. package/dist/cjs/providers/ark.js +253 -317
  9. package/dist/cjs/providers/indexer.js +525 -0
  10. package/dist/cjs/providers/onchain.js +193 -15
  11. package/dist/cjs/script/address.js +48 -17
  12. package/dist/cjs/script/base.js +120 -3
  13. package/dist/cjs/script/default.js +18 -4
  14. package/dist/cjs/script/tapscript.js +46 -14
  15. package/dist/cjs/script/vhtlc.js +27 -7
  16. package/dist/cjs/tree/signingSession.js +63 -106
  17. package/dist/cjs/tree/txTree.js +193 -0
  18. package/dist/cjs/tree/validation.js +79 -155
  19. package/dist/cjs/utils/anchor.js +35 -0
  20. package/dist/cjs/utils/arkTransaction.js +108 -0
  21. package/dist/cjs/utils/transactionHistory.js +84 -72
  22. package/dist/cjs/utils/txSizeEstimator.js +12 -0
  23. package/dist/cjs/utils/unknownFields.js +211 -0
  24. package/dist/cjs/wallet/index.js +12 -0
  25. package/dist/cjs/wallet/onchain.js +201 -0
  26. package/dist/cjs/wallet/ramps.js +95 -0
  27. package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +32 -0
  28. package/dist/cjs/wallet/serviceWorker/request.js +15 -12
  29. package/dist/cjs/wallet/serviceWorker/response.js +22 -27
  30. package/dist/cjs/wallet/serviceWorker/utils.js +8 -0
  31. package/dist/cjs/wallet/serviceWorker/wallet.js +58 -34
  32. package/dist/cjs/wallet/serviceWorker/worker.js +117 -108
  33. package/dist/cjs/wallet/unroll.js +270 -0
  34. package/dist/cjs/wallet/wallet.js +701 -454
  35. package/dist/esm/arknote/index.js +61 -57
  36. package/dist/esm/bip322/errors.js +9 -0
  37. package/dist/esm/bip322/index.js +174 -0
  38. package/dist/esm/forfeit.js +15 -26
  39. package/dist/esm/identity/singleKey.js +64 -0
  40. package/dist/esm/index.js +30 -12
  41. package/dist/esm/providers/ark.js +252 -317
  42. package/dist/esm/providers/indexer.js +521 -0
  43. package/dist/esm/providers/onchain.js +193 -15
  44. package/dist/esm/script/address.js +48 -17
  45. package/dist/esm/script/base.js +120 -3
  46. package/dist/esm/script/default.js +18 -4
  47. package/dist/esm/script/tapscript.js +46 -14
  48. package/dist/esm/script/vhtlc.js +27 -7
  49. package/dist/esm/tree/signingSession.js +65 -108
  50. package/dist/esm/tree/txTree.js +189 -0
  51. package/dist/esm/tree/validation.js +75 -152
  52. package/dist/esm/utils/anchor.js +31 -0
  53. package/dist/esm/utils/arkTransaction.js +105 -0
  54. package/dist/esm/utils/transactionHistory.js +84 -72
  55. package/dist/esm/utils/txSizeEstimator.js +12 -0
  56. package/dist/esm/utils/unknownFields.js +173 -0
  57. package/dist/esm/wallet/index.js +9 -0
  58. package/dist/esm/wallet/onchain.js +196 -0
  59. package/dist/esm/wallet/ramps.js +91 -0
  60. package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +32 -0
  61. package/dist/esm/wallet/serviceWorker/request.js +15 -12
  62. package/dist/esm/wallet/serviceWorker/response.js +22 -27
  63. package/dist/esm/wallet/serviceWorker/utils.js +8 -0
  64. package/dist/esm/wallet/serviceWorker/wallet.js +59 -35
  65. package/dist/esm/wallet/serviceWorker/worker.js +117 -108
  66. package/dist/esm/wallet/unroll.js +267 -0
  67. package/dist/esm/wallet/wallet.js +674 -461
  68. package/dist/types/arknote/index.d.ts +40 -13
  69. package/dist/types/bip322/errors.d.ts +6 -0
  70. package/dist/types/bip322/index.d.ts +57 -0
  71. package/dist/types/forfeit.d.ts +2 -14
  72. package/dist/types/identity/singleKey.d.ts +27 -0
  73. package/dist/types/index.d.ts +23 -12
  74. package/dist/types/providers/ark.d.ts +114 -95
  75. package/dist/types/providers/indexer.d.ts +186 -0
  76. package/dist/types/providers/onchain.d.ts +41 -11
  77. package/dist/types/script/address.d.ts +26 -2
  78. package/dist/types/script/base.d.ts +13 -3
  79. package/dist/types/script/default.d.ts +22 -0
  80. package/dist/types/script/tapscript.d.ts +61 -5
  81. package/dist/types/script/vhtlc.d.ts +27 -0
  82. package/dist/types/tree/signingSession.d.ts +5 -5
  83. package/dist/types/tree/txTree.d.ts +28 -0
  84. package/dist/types/tree/validation.d.ts +15 -22
  85. package/dist/types/utils/anchor.d.ts +19 -0
  86. package/dist/types/utils/arkTransaction.d.ts +27 -0
  87. package/dist/types/utils/transactionHistory.d.ts +7 -1
  88. package/dist/types/utils/txSizeEstimator.d.ts +3 -0
  89. package/dist/types/utils/unknownFields.d.ts +83 -0
  90. package/dist/types/wallet/index.d.ts +51 -50
  91. package/dist/types/wallet/onchain.d.ts +49 -0
  92. package/dist/types/wallet/ramps.d.ts +32 -0
  93. package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +2 -0
  94. package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +2 -0
  95. package/dist/types/wallet/serviceWorker/request.d.ts +14 -16
  96. package/dist/types/wallet/serviceWorker/response.d.ts +17 -19
  97. package/dist/types/wallet/serviceWorker/utils.d.ts +8 -0
  98. package/dist/types/wallet/serviceWorker/wallet.d.ts +36 -8
  99. package/dist/types/wallet/serviceWorker/worker.d.ts +7 -3
  100. package/dist/types/wallet/unroll.d.ts +102 -0
  101. package/dist/types/wallet/wallet.d.ts +71 -25
  102. package/package.json +14 -15
  103. package/dist/cjs/identity/inMemoryKey.js +0 -40
  104. package/dist/cjs/tree/vtxoTree.js +0 -231
  105. package/dist/cjs/utils/coinselect.js +0 -73
  106. package/dist/cjs/utils/psbt.js +0 -137
  107. package/dist/esm/identity/inMemoryKey.js +0 -36
  108. package/dist/esm/tree/vtxoTree.js +0 -191
  109. package/dist/esm/utils/coinselect.js +0 -69
  110. package/dist/esm/utils/psbt.js +0 -131
  111. package/dist/types/identity/inMemoryKey.d.ts +0 -12
  112. package/dist/types/tree/vtxoTree.d.ts +0 -33
  113. package/dist/types/utils/coinselect.d.ts +0 -21
  114. package/dist/types/utils/psbt.d.ts +0 -11
@@ -2,13 +2,33 @@ import { Script } from "@scure/btc-signer";
2
2
  import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, MultisigTapscript, } from './tapscript.js';
3
3
  import { hex } from "@scure/base";
4
4
  import { VtxoScript } from './base.js';
5
- // VHTLC is an Hashed Timelock Contract VtxoScript implementation
6
- // - claim (preimage + receiver)
7
- // - refund (sender + receiver + server)
8
- // - refundWithoutReceiver (at refundLocktime, sender + receiver + server)
9
- // - unilateralClaim (preimage + receiver after unilateralClaimDelay)
10
- // - unilateralRefund (sender + receiver after unilateralRefundDelay)
11
- // - unilateralRefundWithoutReceiver (sender after unilateralRefundWithoutReceiverDelay)
5
+ /**
6
+ * Virtual Hash Time Lock Contract (VHTLC) implementation.
7
+ *
8
+ * VHTLC is a contract that enables atomic swaps and conditional payments
9
+ * in the Ark protocol. It provides multiple spending paths:
10
+ *
11
+ * - **claim**: Receiver can claim funds by revealing the preimage
12
+ * - **refund**: Sender and receiver can collaboratively refund
13
+ * - **refundWithoutReceiver**: Sender can refund after locktime expires
14
+ * - **unilateralClaim**: Receiver can claim unilaterally after delay
15
+ * - **unilateralRefund**: Sender and receiver can refund unilaterally after delay
16
+ * - **unilateralRefundWithoutReceiver**: Sender can refund unilaterally after delay
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const vhtlc = new VHTLC.Script({
21
+ * sender: alicePubKey,
22
+ * receiver: bobPubKey,
23
+ * server: serverPubKey,
24
+ * preimageHash: hash160(secret),
25
+ * refundLocktime: BigInt(chainTip + 10),
26
+ * unilateralClaimDelay: { type: 'blocks', value: 100n },
27
+ * unilateralRefundDelay: { type: 'blocks', value: 102n },
28
+ * unilateralRefundWithoutReceiverDelay: { type: 'blocks', value: 103n }
29
+ * });
30
+ * ```
31
+ */
12
32
  export var VHTLC;
13
33
  (function (VHTLC) {
14
34
  class Script extends VtxoScript {
@@ -1,17 +1,17 @@
1
1
  import * as musig2 from '../musig2/index.js';
2
- import { getCosignerKeys } from './vtxoTree.js';
3
- import { Script, SigHash, Transaction } from "@scure/btc-signer";
4
- import { base64, hex } from "@scure/base";
2
+ import { Script, SigHash } from "@scure/btc-signer";
3
+ import { hex } from "@scure/base";
5
4
  import { schnorr, secp256k1 } from "@noble/curves/secp256k1";
6
- import { randomPrivateKeyBytes } from "@scure/btc-signer/utils";
7
- export const ErrMissingVtxoTree = new Error("missing vtxo tree");
5
+ import { randomPrivateKeyBytes, sha256x2 } from "@scure/btc-signer/utils";
6
+ import { CosignerPublicKey, getArkPsbtFields } from '../utils/unknownFields.js';
7
+ export const ErrMissingVtxoGraph = new Error("missing vtxo graph");
8
8
  export const ErrMissingAggregateKey = new Error("missing aggregate key");
9
9
  export class TreeSignerSession {
10
10
  constructor(secretKey) {
11
11
  this.secretKey = secretKey;
12
12
  this.myNonces = null;
13
13
  this.aggregateNonces = null;
14
- this.tree = null;
14
+ this.graph = null;
15
15
  this.scriptRoot = null;
16
16
  this.rootSharedOutputAmount = null;
17
17
  }
@@ -20,7 +20,7 @@ export class TreeSignerSession {
20
20
  return new TreeSignerSession(secretKey);
21
21
  }
22
22
  init(tree, scriptRoot, rootInputAmount) {
23
- this.tree = tree;
23
+ this.graph = tree;
24
24
  this.scriptRoot = scriptRoot;
25
25
  this.rootSharedOutputAmount = rootInputAmount;
26
26
  }
@@ -28,24 +28,16 @@ export class TreeSignerSession {
28
28
  return secp256k1.getPublicKey(this.secretKey);
29
29
  }
30
30
  getNonces() {
31
- if (!this.tree)
32
- throw ErrMissingVtxoTree;
31
+ if (!this.graph)
32
+ throw ErrMissingVtxoGraph;
33
33
  if (!this.myNonces) {
34
34
  this.myNonces = this.generateNonces();
35
35
  }
36
- const nonces = [];
37
- for (const levelNonces of this.myNonces) {
38
- const levelPubNonces = [];
39
- for (const nonce of levelNonces) {
40
- if (!nonce) {
41
- levelPubNonces.push(null);
42
- continue;
43
- }
44
- levelPubNonces.push({ pubNonce: nonce.pubNonce });
45
- }
46
- nonces.push(levelPubNonces);
36
+ const publicNonces = new Map();
37
+ for (const [txid, nonces] of this.myNonces) {
38
+ publicNonces.set(txid, { pubNonce: nonces.pubNonce });
47
39
  }
48
- return nonces;
40
+ return publicNonces;
49
41
  }
50
42
  setAggregatedNonces(nonces) {
51
43
  if (this.aggregateNonces)
@@ -53,71 +45,55 @@ export class TreeSignerSession {
53
45
  this.aggregateNonces = nonces;
54
46
  }
55
47
  sign() {
56
- if (!this.tree)
57
- throw ErrMissingVtxoTree;
48
+ if (!this.graph)
49
+ throw ErrMissingVtxoGraph;
58
50
  if (!this.aggregateNonces)
59
51
  throw new Error("nonces not set");
60
52
  if (!this.myNonces)
61
53
  throw new Error("nonces not generated");
62
- const sigs = [];
63
- for (let levelIndex = 0; levelIndex < this.tree.levels.length; levelIndex++) {
64
- const levelSigs = [];
65
- const level = this.tree.levels[levelIndex];
66
- for (let nodeIndex = 0; nodeIndex < level.length; nodeIndex++) {
67
- const node = level[nodeIndex];
68
- const tx = Transaction.fromPSBT(base64.decode(node.tx));
69
- const sig = this.signPartial(tx, levelIndex, nodeIndex);
70
- if (sig) {
71
- levelSigs.push(sig);
72
- }
73
- else {
74
- levelSigs.push(null);
75
- }
76
- }
77
- sigs.push(levelSigs);
54
+ const sigs = new Map();
55
+ for (const g of this.graph) {
56
+ const sig = this.signPartial(g);
57
+ sigs.set(g.txid, sig);
78
58
  }
79
59
  return sigs;
80
60
  }
81
61
  generateNonces() {
82
- if (!this.tree)
83
- throw ErrMissingVtxoTree;
84
- const myNonces = [];
62
+ if (!this.graph)
63
+ throw ErrMissingVtxoGraph;
64
+ const myNonces = new Map();
85
65
  const publicKey = secp256k1.getPublicKey(this.secretKey);
86
- for (const level of this.tree.levels) {
87
- const levelNonces = [];
88
- for (let i = 0; i < level.length; i++) {
89
- const nonces = musig2.generateNonces(publicKey);
90
- levelNonces.push(nonces);
91
- }
92
- myNonces.push(levelNonces);
66
+ for (const g of this.graph) {
67
+ const nonces = musig2.generateNonces(publicKey);
68
+ myNonces.set(g.txid, nonces);
93
69
  }
94
70
  return myNonces;
95
71
  }
96
- signPartial(tx, levelIndex, nodeIndex) {
97
- if (!this.tree || !this.scriptRoot || !this.rootSharedOutputAmount) {
72
+ signPartial(g) {
73
+ if (!this.graph || !this.scriptRoot || !this.rootSharedOutputAmount) {
98
74
  throw TreeSignerSession.NOT_INITIALIZED;
99
75
  }
100
76
  if (!this.myNonces || !this.aggregateNonces) {
101
77
  throw new Error("session not properly initialized");
102
78
  }
103
- const myNonce = this.myNonces[levelIndex][nodeIndex];
79
+ const myNonce = this.myNonces.get(g.txid);
104
80
  if (!myNonce)
105
- return null;
106
- const aggNonce = this.aggregateNonces[levelIndex][nodeIndex];
81
+ throw new Error("missing private nonce");
82
+ const aggNonce = this.aggregateNonces.get(g.txid);
107
83
  if (!aggNonce)
108
84
  throw new Error("missing aggregate nonce");
109
85
  const prevoutAmounts = [];
110
86
  const prevoutScripts = [];
111
- const cosigners = getCosignerKeys(tx);
87
+ const cosigners = getArkPsbtFields(g.root, 0, CosignerPublicKey).map((c) => c.key);
112
88
  const { finalKey } = musig2.aggregateKeys(cosigners, true, {
113
89
  taprootTweak: this.scriptRoot,
114
90
  });
115
- for (let inputIndex = 0; inputIndex < tx.inputsLength; inputIndex++) {
116
- const prevout = getPrevOutput(finalKey, this.tree, this.rootSharedOutputAmount, tx);
91
+ for (let inputIndex = 0; inputIndex < g.root.inputsLength; inputIndex++) {
92
+ const prevout = getPrevOutput(finalKey, this.graph, this.rootSharedOutputAmount, g.root);
117
93
  prevoutAmounts.push(prevout.amount);
118
94
  prevoutScripts.push(prevout.script);
119
95
  }
120
- const message = tx.preimageWitnessV1(0, // always first input
96
+ const message = g.root.preimageWitnessV1(0, // always first input
121
97
  prevoutScripts, SigHash.DEFAULT, prevoutAmounts);
122
98
  return musig2.sign(myNonce.secNonce, this.secretKey, aggNonce.pubNonce, cosigners, message, {
123
99
  taprootTweak: this.scriptRoot,
@@ -129,66 +105,47 @@ TreeSignerSession.NOT_INITIALIZED = new Error("session not initialized, call ini
129
105
  // Helper function to validate tree signatures
130
106
  export async function validateTreeSigs(finalAggregatedKey, sharedOutputAmount, vtxoTree) {
131
107
  // Iterate through each level of the tree
132
- for (const level of vtxoTree.levels) {
133
- for (const node of level) {
134
- // Parse the transaction
135
- const tx = Transaction.fromPSBT(base64.decode(node.tx));
136
- const input = tx.getInput(0);
137
- // Check if input has signature
138
- if (!input.tapKeySig) {
139
- throw new Error("unsigned tree input");
140
- }
141
- // Get the previous output information
142
- const prevout = getPrevOutput(finalAggregatedKey, vtxoTree, sharedOutputAmount, tx);
143
- // Calculate the message that was signed
144
- const message = tx.preimageWitnessV1(0, // always first input
145
- [prevout.script], SigHash.DEFAULT, [prevout.amount]);
146
- // Verify the signature
147
- const isValid = schnorr.verify(input.tapKeySig, message, finalAggregatedKey);
148
- if (!isValid) {
149
- throw new Error("invalid signature");
150
- }
108
+ for (const g of vtxoTree) {
109
+ // Parse the transaction
110
+ const input = g.root.getInput(0);
111
+ // Check if input has signature
112
+ if (!input.tapKeySig) {
113
+ throw new Error("unsigned tree input");
114
+ }
115
+ // Get the previous output information
116
+ const prevout = getPrevOutput(finalAggregatedKey, vtxoTree, sharedOutputAmount, g.root);
117
+ // Calculate the message that was signed
118
+ const message = g.root.preimageWitnessV1(0, // always first input
119
+ [prevout.script], SigHash.DEFAULT, [prevout.amount]);
120
+ // Verify the signature
121
+ const isValid = schnorr.verify(input.tapKeySig, message, finalAggregatedKey);
122
+ if (!isValid) {
123
+ throw new Error("invalid signature");
151
124
  }
152
125
  }
153
126
  }
154
- function getPrevOutput(finalKey, vtxoTree, sharedOutputAmount, partial) {
155
- // Generate P2TR script
127
+ function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
128
+ // generate P2TR script from musig2 final key
156
129
  const pkScript = Script.encode(["OP_1", finalKey.slice(1)]);
157
- // Get root node
158
- const rootNode = vtxoTree.levels[0][0];
159
- if (!rootNode)
160
- throw new Error("empty vtxo tree");
161
- const input = partial.getInput(0);
162
- if (!input.txid)
163
- throw new Error("missing input txid");
164
- const parentTxID = hex.encode(input.txid);
165
- // Check if parent is root
166
- if (rootNode.parentTxid === parentTxID) {
130
+ const txid = hex.encode(sha256x2(tx.toBytes(true)).reverse());
131
+ // if the input is the root input, return the shared output amount
132
+ if (txid === graph.txid) {
167
133
  return {
168
134
  amount: sharedOutputAmount,
169
135
  script: pkScript,
170
136
  };
171
137
  }
172
- // Search for parent in tree
173
- let parent = null;
174
- for (const level of vtxoTree.levels) {
175
- for (const node of level) {
176
- if (node.txid === parentTxID) {
177
- parent = node;
178
- break;
179
- }
180
- }
181
- if (parent)
182
- break;
183
- }
184
- if (!parent) {
185
- throw new Error("parent tx not found");
186
- }
187
- // Parse parent tx
188
- const parentTx = Transaction.fromPSBT(base64.decode(parent.tx));
189
- if (!input.index)
138
+ // find the parent transaction
139
+ const parentInput = tx.getInput(0);
140
+ if (!parentInput.txid)
141
+ throw new Error("missing parent input txid");
142
+ const parentTxid = hex.encode(new Uint8Array(parentInput.txid));
143
+ const parent = graph.find(parentTxid);
144
+ if (!parent)
145
+ throw new Error("parent tx not found");
146
+ if (parentInput.index === undefined)
190
147
  throw new Error("missing input index");
191
- const parentOutput = parentTx.getOutput(input.index);
148
+ const parentOutput = parent.root.getOutput(parentInput.index);
192
149
  if (!parentOutput)
193
150
  throw new Error("parent output not found");
194
151
  if (!parentOutput.amount)
@@ -0,0 +1,189 @@
1
+ import { Transaction } from "@scure/btc-signer";
2
+ import { base64 } from "@scure/base";
3
+ import { hex } from "@scure/base";
4
+ import { sha256x2 } from "@scure/btc-signer/utils";
5
+ /**
6
+ * TxTree is a graph of bitcoin transactions.
7
+ * It is used to represent batch tree created during settlement session
8
+ */
9
+ export class TxTree {
10
+ constructor(root, children = new Map()) {
11
+ this.root = root;
12
+ this.children = children;
13
+ }
14
+ static create(chunks) {
15
+ if (chunks.length === 0) {
16
+ throw new Error("empty chunks");
17
+ }
18
+ // Create a map to store all chunks by their txid for easy lookup
19
+ const chunksByTxid = new Map();
20
+ for (const chunk of chunks) {
21
+ const decodedChunk = decodeNode(chunk);
22
+ const txid = hex.encode(sha256x2(decodedChunk.tx.toBytes(true)).reverse());
23
+ chunksByTxid.set(txid, decodedChunk);
24
+ }
25
+ // Find the root chunks (the ones that aren't referenced as a child)
26
+ const rootTxids = [];
27
+ for (const [txid] of chunksByTxid) {
28
+ let isChild = false;
29
+ for (const [otherTxid, otherChunk] of chunksByTxid) {
30
+ if (otherTxid === txid) {
31
+ // skip self
32
+ continue;
33
+ }
34
+ // check if the current chunk is a child of the other chunk
35
+ isChild = hasChild(otherChunk, txid);
36
+ if (isChild) {
37
+ break;
38
+ }
39
+ }
40
+ // if the chunk is not a child of any other chunk, it is a root
41
+ if (!isChild) {
42
+ rootTxids.push(txid);
43
+ continue;
44
+ }
45
+ }
46
+ if (rootTxids.length === 0) {
47
+ throw new Error("no root chunk found");
48
+ }
49
+ if (rootTxids.length > 1) {
50
+ throw new Error(`multiple root chunks found: ${rootTxids.join(", ")}`);
51
+ }
52
+ const graph = buildGraph(rootTxids[0], chunksByTxid);
53
+ if (!graph) {
54
+ throw new Error(`chunk not found for root txid: ${rootTxids[0]}`);
55
+ }
56
+ // verify that the number of chunks is equal to the number node in the graph
57
+ if (graph.nbOfNodes() !== chunks.length) {
58
+ throw new Error(`number of chunks (${chunks.length}) is not equal to the number of nodes in the graph (${graph.nbOfNodes()})`);
59
+ }
60
+ return graph;
61
+ }
62
+ nbOfNodes() {
63
+ let count = 1; // count this node
64
+ for (const child of this.children.values()) {
65
+ count += child.nbOfNodes();
66
+ }
67
+ return count;
68
+ }
69
+ validate() {
70
+ if (!this.root) {
71
+ throw new Error("unexpected nil root");
72
+ }
73
+ const nbOfOutputs = this.root.outputsLength;
74
+ const nbOfInputs = this.root.inputsLength;
75
+ if (nbOfInputs !== 1) {
76
+ throw new Error(`unexpected number of inputs: ${nbOfInputs}, expected 1`);
77
+ }
78
+ // the children map can't be bigger than the number of outputs (excluding the P2A)
79
+ // a graph can be "partial" and specify only some of the outputs as children,
80
+ // that's why we allow len(g.Children) to be less than nbOfOutputs-1
81
+ if (this.children.size > nbOfOutputs - 1) {
82
+ throw new Error(`unexpected number of children: ${this.children.size}, expected maximum ${nbOfOutputs - 1}`);
83
+ }
84
+ // validate each child
85
+ for (const [outputIndex, child] of this.children) {
86
+ if (outputIndex >= nbOfOutputs) {
87
+ throw new Error(`output index ${outputIndex} is out of bounds (nb of outputs: ${nbOfOutputs})`);
88
+ }
89
+ child.validate();
90
+ const childInput = child.root.getInput(0);
91
+ const parentTxid = hex.encode(sha256x2(this.root.toBytes(true)).reverse());
92
+ // verify the input of the child is the output of the parent
93
+ if (!childInput.txid ||
94
+ hex.encode(childInput.txid) !== parentTxid ||
95
+ childInput.index !== outputIndex) {
96
+ throw new Error(`input of child ${outputIndex} is not the output of the parent`);
97
+ }
98
+ // verify the sum of the child's outputs is equal to the output of the parent
99
+ let childOutputsSum = 0n;
100
+ for (let i = 0; i < child.root.outputsLength; i++) {
101
+ const output = child.root.getOutput(i);
102
+ if (output?.amount) {
103
+ childOutputsSum += output.amount;
104
+ }
105
+ }
106
+ const parentOutput = this.root.getOutput(outputIndex);
107
+ if (!parentOutput?.amount) {
108
+ throw new Error(`parent output ${outputIndex} has no amount`);
109
+ }
110
+ if (childOutputsSum !== parentOutput.amount) {
111
+ throw new Error(`sum of child's outputs is not equal to the output of the parent: ${childOutputsSum} != ${parentOutput.amount}`);
112
+ }
113
+ }
114
+ }
115
+ leaves() {
116
+ if (this.children.size === 0) {
117
+ return [this.root];
118
+ }
119
+ const leaves = [];
120
+ for (const child of this.children.values()) {
121
+ leaves.push(...child.leaves());
122
+ }
123
+ return leaves;
124
+ }
125
+ get txid() {
126
+ return hex.encode(sha256x2(this.root.toBytes(true)).reverse());
127
+ }
128
+ find(txid) {
129
+ if (txid === this.txid) {
130
+ return this;
131
+ }
132
+ for (const child of this.children.values()) {
133
+ const found = child.find(txid);
134
+ if (found) {
135
+ return found;
136
+ }
137
+ }
138
+ return null;
139
+ }
140
+ update(txid, fn) {
141
+ if (txid === this.txid) {
142
+ fn(this.root);
143
+ return;
144
+ }
145
+ for (const child of this.children.values()) {
146
+ try {
147
+ child.update(txid, fn);
148
+ return;
149
+ }
150
+ catch (error) {
151
+ // Continue searching in other children if not found
152
+ continue;
153
+ }
154
+ }
155
+ throw new Error(`tx not found: ${txid}`);
156
+ }
157
+ *[Symbol.iterator]() {
158
+ yield this;
159
+ for (const child of this.children.values()) {
160
+ yield* child;
161
+ }
162
+ }
163
+ }
164
+ // Helper function to check if a chunk has a specific child
165
+ function hasChild(chunk, childTxid) {
166
+ return Object.values(chunk.children).includes(childTxid);
167
+ }
168
+ // buildGraph recursively builds the TxGraph starting from the given txid
169
+ function buildGraph(rootTxid, chunksByTxid) {
170
+ const chunk = chunksByTxid.get(rootTxid);
171
+ if (!chunk) {
172
+ return null;
173
+ }
174
+ const rootTx = chunk.tx;
175
+ const children = new Map();
176
+ // Recursively build children graphs
177
+ for (const [outputIndexStr, childTxid] of Object.entries(chunk.children)) {
178
+ const outputIndex = parseInt(outputIndexStr);
179
+ const childGraph = buildGraph(childTxid, chunksByTxid);
180
+ if (childGraph) {
181
+ children.set(outputIndex, childGraph);
182
+ }
183
+ }
184
+ return new TxTree(rootTx, children);
185
+ }
186
+ function decodeNode(chunk) {
187
+ const tx = Transaction.fromPSBT(base64.decode(chunk.tx));
188
+ return { tx, children: chunk.children };
189
+ }