@arkade-os/sdk 0.1.3 → 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 -459
  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 -466
  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 -26
  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
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EsploraProvider = exports.ESPLORA_URL = void 0;
4
+ /**
5
+ * The default base URLs for esplora API providers.
6
+ */
4
7
  exports.ESPLORA_URL = {
5
8
  bitcoin: "https://mempool.space/api",
6
9
  testnet: "https://mempool.space/testnet/api",
@@ -8,6 +11,15 @@ exports.ESPLORA_URL = {
8
11
  mutinynet: "https://mutinynet.com/api",
9
12
  regtest: "http://localhost:3000",
10
13
  };
14
+ /**
15
+ * Implementation of the onchain provider interface for esplora REST API.
16
+ * @see https://mempool.space/docs/api/rest
17
+ * @example
18
+ * ```typescript
19
+ * const provider = new EsploraProvider("https://mempool.space/api");
20
+ * const utxos = await provider.getCoins("bcrt1q679zsd45msawvr7782r0twvmukns3drlstjt77");
21
+ * ```
22
+ */
11
23
  class EsploraProvider {
12
24
  constructor(baseUrl) {
13
25
  this.baseUrl = baseUrl;
@@ -20,26 +32,22 @@ class EsploraProvider {
20
32
  return response.json();
21
33
  }
22
34
  async getFeeRate() {
23
- const response = await fetch(`${this.baseUrl}/v1/fees/recommended`);
35
+ const response = await fetch(`${this.baseUrl}/fee-estimates`);
24
36
  if (!response.ok) {
25
37
  throw new Error(`Failed to fetch fee rate: ${response.statusText}`);
26
38
  }
27
- const fees = await response.json();
28
- return fees.halfHourFee; // Return the "medium" priority fee rate
39
+ const fees = (await response.json());
40
+ return fees["1"] ?? undefined;
29
41
  }
30
- async broadcastTransaction(txHex) {
31
- const response = await fetch(`${this.baseUrl}/tx`, {
32
- method: "POST",
33
- headers: {
34
- "Content-Type": "text/plain",
35
- },
36
- body: txHex,
37
- });
38
- if (!response.ok) {
39
- const error = await response.text();
40
- throw new Error(`Failed to broadcast transaction: ${error}`);
42
+ async broadcastTransaction(...txs) {
43
+ switch (txs.length) {
44
+ case 1:
45
+ return this.broadcastTx(txs[0]);
46
+ case 2:
47
+ return this.broadcastPackage(txs[0], txs[1]);
48
+ default:
49
+ throw new Error("Only 1 or 1C1P package can be broadcast");
41
50
  }
42
- return response.text(); // Returns the txid
43
51
  }
44
52
  async getTxOutspends(txid) {
45
53
  const response = await fetch(`${this.baseUrl}/tx/${txid}/outspends`);
@@ -58,16 +66,186 @@ class EsploraProvider {
58
66
  return response.json();
59
67
  }
60
68
  async getTxStatus(txid) {
69
+ // make sure tx exists in mempool or in block
70
+ const txresponse = await fetch(`${this.baseUrl}/tx/${txid}`);
71
+ if (!txresponse.ok) {
72
+ throw new Error(txresponse.statusText);
73
+ }
74
+ const tx = await txresponse.json();
75
+ if (!tx.status.confirmed) {
76
+ return { confirmed: false };
77
+ }
61
78
  const response = await fetch(`${this.baseUrl}/tx/${txid}/status`);
62
79
  if (!response.ok) {
63
80
  throw new Error(`Failed to get transaction status: ${response.statusText}`);
64
81
  }
65
82
  const data = await response.json();
83
+ if (!data.confirmed) {
84
+ return { confirmed: false };
85
+ }
66
86
  return {
67
87
  confirmed: data.confirmed,
68
88
  blockTime: data.block_time,
69
89
  blockHeight: data.block_height,
70
90
  };
71
91
  }
92
+ async watchAddresses(addresses, callback) {
93
+ let intervalId = null;
94
+ const wsUrl = this.baseUrl.replace(/^http(s)?:/, "ws$1:") + "/v1/ws";
95
+ const poll = async () => {
96
+ // websocket is not reliable, so we will fallback to polling
97
+ const pollingInterval = 5000; // 5 seconds
98
+ const getAllTxs = () => {
99
+ return Promise.all(addresses.map((address) => this.getTransactions(address))).then((txArrays) => txArrays.flat());
100
+ };
101
+ // initial fetch to get existing transactions
102
+ const initialTxs = await getAllTxs();
103
+ // we use block_time in key to also notify when a transaction is confirmed
104
+ const txKey = (tx) => `${tx.txid}_${tx.status.block_time}`;
105
+ // polling for new transactions
106
+ intervalId = setInterval(async () => {
107
+ try {
108
+ // get current transactions
109
+ // we will compare with initialTxs to find new ones
110
+ const currentTxs = await getAllTxs();
111
+ // create a set of existing transactions to avoid duplicates
112
+ const existingTxs = new Set(initialTxs.map(txKey));
113
+ // filter out transactions that are already in initialTxs
114
+ const newTxs = currentTxs.filter((tx) => !existingTxs.has(txKey(tx)));
115
+ if (newTxs.length > 0) {
116
+ // Update the tracking set instead of growing the array
117
+ initialTxs.push(...newTxs);
118
+ callback(newTxs);
119
+ }
120
+ }
121
+ catch (error) {
122
+ console.error("Error in polling mechanism:", error);
123
+ }
124
+ }, pollingInterval);
125
+ };
126
+ let ws = null;
127
+ try {
128
+ ws = new WebSocket(wsUrl);
129
+ ws.addEventListener("open", () => {
130
+ // subscribe to address updates
131
+ const subscribeMsg = {
132
+ "track-addresses": addresses,
133
+ };
134
+ ws.send(JSON.stringify(subscribeMsg));
135
+ });
136
+ ws.addEventListener("message", (event) => {
137
+ try {
138
+ const newTxs = [];
139
+ const message = JSON.parse(event.data.toString());
140
+ if (!message["multi-address-transactions"])
141
+ return;
142
+ const aux = message["multi-address-transactions"];
143
+ for (const address in aux) {
144
+ for (const type of [
145
+ "mempool",
146
+ "confirmed",
147
+ "removed",
148
+ ]) {
149
+ if (!aux[address][type])
150
+ continue;
151
+ newTxs.push(...aux[address][type].filter(isExplorerTransaction));
152
+ }
153
+ }
154
+ // callback with new transactions
155
+ if (newTxs.length > 0)
156
+ callback(newTxs);
157
+ }
158
+ catch (error) {
159
+ console.error("Failed to process WebSocket message:", error);
160
+ }
161
+ });
162
+ ws.addEventListener("error", async () => {
163
+ // if websocket is not available, fallback to polling
164
+ await poll();
165
+ });
166
+ }
167
+ catch {
168
+ if (intervalId)
169
+ clearInterval(intervalId);
170
+ // if websocket is not available, fallback to polling
171
+ await poll();
172
+ }
173
+ const stopFunc = () => {
174
+ if (ws && ws.readyState === WebSocket.OPEN)
175
+ ws.close();
176
+ if (intervalId)
177
+ clearInterval(intervalId);
178
+ };
179
+ return stopFunc;
180
+ }
181
+ async getChainTip() {
182
+ const tipBlocks = await fetch(`${this.baseUrl}/blocks/tip`);
183
+ if (!tipBlocks.ok) {
184
+ throw new Error(`Failed to get chain tip: ${tipBlocks.statusText}`);
185
+ }
186
+ const tip = await tipBlocks.json();
187
+ if (!isValidBlocksTip(tip)) {
188
+ throw new Error(`Invalid chain tip: ${JSON.stringify(tip)}`);
189
+ }
190
+ if (tip.length === 0) {
191
+ throw new Error("No chain tip found");
192
+ }
193
+ const hash = tip[0].id;
194
+ return {
195
+ height: tip[0].height,
196
+ time: tip[0].mediantime,
197
+ hash,
198
+ };
199
+ }
200
+ async broadcastPackage(parent, child) {
201
+ const response = await fetch(`${this.baseUrl}/txs/package`, {
202
+ method: "POST",
203
+ headers: {
204
+ "Content-Type": "application/json",
205
+ },
206
+ body: JSON.stringify([parent, child]),
207
+ });
208
+ if (!response.ok) {
209
+ const error = await response.text();
210
+ throw new Error(`Failed to broadcast package: ${error}`);
211
+ }
212
+ return response.json();
213
+ }
214
+ async broadcastTx(tx) {
215
+ const response = await fetch(`${this.baseUrl}/tx`, {
216
+ method: "POST",
217
+ headers: {
218
+ "Content-Type": "text/plain",
219
+ },
220
+ body: tx,
221
+ });
222
+ if (!response.ok) {
223
+ const error = await response.text();
224
+ throw new Error(`Failed to broadcast transaction: ${error}`);
225
+ }
226
+ return response.text();
227
+ }
72
228
  }
73
229
  exports.EsploraProvider = EsploraProvider;
230
+ function isValidBlocksTip(tip) {
231
+ return (Array.isArray(tip) &&
232
+ tip.every((t) => {
233
+ t &&
234
+ typeof t === "object" &&
235
+ typeof t.id === "string" &&
236
+ t.id.length > 0 &&
237
+ typeof t.height === "number" &&
238
+ t.height >= 0 &&
239
+ typeof t.mediantime === "number" &&
240
+ t.mediantime > 0;
241
+ }));
242
+ }
243
+ const isExplorerTransaction = (tx) => {
244
+ return (typeof tx.txid === "string" &&
245
+ Array.isArray(tx.vout) &&
246
+ tx.vout.every((vout) => typeof vout.scriptpubkey_address === "string" &&
247
+ typeof vout.value === "string") &&
248
+ typeof tx.status === "object" &&
249
+ typeof tx.status.confirmed === "boolean" &&
250
+ typeof tx.status.block_time === "number");
251
+ };
@@ -3,17 +3,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ArkAddress = void 0;
4
4
  const base_1 = require("@scure/base");
5
5
  const btc_signer_1 = require("@scure/btc-signer");
6
- // ArkAddress is a bech32m encoded address with a custom HRP (ark/tark)
6
+ /**
7
+ * ArkAddress allows to create and decode bech32m encoded ark address.
8
+ * An ark address is composed of:
9
+ * - a human readable prefix (hrp)
10
+ * - a version byte (1 byte)
11
+ * - a server public key (32 bytes)
12
+ * - a vtxo taproot public key (32 bytes)
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const address = new ArkAddress(
17
+ * new Uint8Array(32), // server public key
18
+ * new Uint8Array(32), // vtxo taproot public key
19
+ * "ark"
20
+ * );
21
+ *
22
+ * const encoded = address.encode();
23
+ * console.log("address: ", encoded);
24
+ *
25
+ * const decoded = ArkAddress.decode(encoded);
26
+ * ```
27
+ */
7
28
  class ArkAddress {
8
- constructor(serverPubKey, tweakedPubKey, hrp) {
29
+ constructor(serverPubKey, vtxoTaprootKey, hrp, version = 0) {
9
30
  this.serverPubKey = serverPubKey;
10
- this.tweakedPubKey = tweakedPubKey;
31
+ this.vtxoTaprootKey = vtxoTaprootKey;
11
32
  this.hrp = hrp;
33
+ this.version = version;
12
34
  if (serverPubKey.length !== 32) {
13
- throw new Error("Invalid server public key length");
35
+ throw new Error("Invalid server public key length, expected 32 bytes, got " +
36
+ serverPubKey.length);
14
37
  }
15
- if (tweakedPubKey.length !== 32) {
16
- throw new Error("Invalid tweaked public key length");
38
+ if (vtxoTaprootKey.length !== 32) {
39
+ throw new Error("Invalid vtxo taproot public key length, expected 32 bytes, got " +
40
+ vtxoTaprootKey.length);
17
41
  }
18
42
  }
19
43
  static decode(address) {
@@ -22,24 +46,31 @@ class ArkAddress {
22
46
  throw new Error("Invalid address");
23
47
  }
24
48
  const data = new Uint8Array(base_1.bech32m.fromWords(decoded.words));
25
- // First 32 bytes are server pubkey, next 32 bytes are tweaked pubkey
26
- if (data.length !== 64) {
27
- throw new Error("Invalid data length");
49
+ // First the version byte, then 32 bytes server pubkey, then 32 bytes vtxo taproot pubkey
50
+ if (data.length !== 1 + 32 + 32) {
51
+ throw new Error("Invalid data length, expected 65 bytes, got " + data.length);
28
52
  }
29
- const serverPubKey = data.slice(0, 32);
30
- const tweakedPubKey = data.slice(32, 64);
31
- return new ArkAddress(serverPubKey, tweakedPubKey, decoded.prefix);
53
+ const version = data[0];
54
+ const serverPubKey = data.slice(1, 33);
55
+ const vtxoTaprootPubKey = data.slice(33, 65);
56
+ return new ArkAddress(serverPubKey, vtxoTaprootPubKey, decoded.prefix, version);
32
57
  }
33
58
  encode() {
34
- // Combine server pubkey and tweaked pubkey
35
- const data = new Uint8Array(64);
36
- data.set(this.serverPubKey, 0);
37
- data.set(this.tweakedPubKey, 32);
59
+ // Combine version byte, server pubkey, and vtxo taproot pubkey
60
+ const data = new Uint8Array(1 + 32 + 32);
61
+ data[0] = this.version;
62
+ data.set(this.serverPubKey, 1);
63
+ data.set(this.vtxoTaprootKey, 33);
38
64
  const words = base_1.bech32m.toWords(data);
39
65
  return base_1.bech32m.encode(this.hrp, words, 1023);
40
66
  }
67
+ // pkScript is the script that should be used to send non-dust funds to the address
41
68
  get pkScript() {
42
- return btc_signer_1.Script.encode(["OP_1", this.tweakedPubKey]);
69
+ return btc_signer_1.Script.encode(["OP_1", this.vtxoTaprootKey]);
70
+ }
71
+ // subdustPkScript is the script that should be used to send sub-dust funds to the address
72
+ get subdustPkScript() {
73
+ return btc_signer_1.Script.encode(["RETURN", this.vtxoTaprootKey]);
43
74
  }
44
75
  }
45
76
  exports.ArkAddress = ArkAddress;
@@ -7,12 +7,22 @@ const utils_1 = require("@scure/btc-signer/utils");
7
7
  const address_1 = require("./address");
8
8
  const btc_signer_1 = require("@scure/btc-signer");
9
9
  const base_1 = require("@scure/base");
10
+ const tapscript_1 = require("./tapscript");
10
11
  function scriptFromTapLeafScript(leaf) {
11
12
  return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte
12
13
  }
14
+ /**
15
+ * VtxoScript is a script that contains a list of tapleaf scripts.
16
+ * It is used to create vtxo scripts.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const vtxoScript = new VtxoScript([new Uint8Array(32), new Uint8Array(32)]);
21
+ */
13
22
  class VtxoScript {
14
- static decode(scripts) {
15
- return new VtxoScript(scripts.map(base_1.hex.decode));
23
+ static decode(tapTree) {
24
+ const leaves = decodeTaprootTree(tapTree);
25
+ return new VtxoScript(leaves);
16
26
  }
17
27
  constructor(scripts) {
18
28
  this.scripts = scripts;
@@ -26,7 +36,8 @@ class VtxoScript {
26
36
  this.tweakedPublicKey = payment.tweakedPubkey;
27
37
  }
28
38
  encode() {
29
- return this.scripts.map(base_1.hex.encode);
39
+ const tapTree = encodeTaprootTree(this.scripts);
40
+ return tapTree;
30
41
  }
31
42
  address(prefix, serverPubKey) {
32
43
  return new address_1.ArkAddress(serverPubKey, this.tweakedPublicKey, prefix);
@@ -47,5 +58,111 @@ class VtxoScript {
47
58
  }
48
59
  return leaf;
49
60
  }
61
+ exitPaths() {
62
+ const paths = [];
63
+ for (const leaf of this.leaves) {
64
+ try {
65
+ const tapscript = tapscript_1.CSVMultisigTapscript.decode(scriptFromTapLeafScript(leaf));
66
+ paths.push(tapscript);
67
+ continue;
68
+ }
69
+ catch (e) {
70
+ try {
71
+ const tapscript = tapscript_1.ConditionCSVMultisigTapscript.decode(scriptFromTapLeafScript(leaf));
72
+ paths.push(tapscript);
73
+ }
74
+ catch (e) {
75
+ continue;
76
+ }
77
+ }
78
+ }
79
+ return paths;
80
+ }
50
81
  }
51
82
  exports.VtxoScript = VtxoScript;
83
+ function decodeTaprootTree(tapTree) {
84
+ let offset = 0;
85
+ const scripts = [];
86
+ // Read number of leaves
87
+ const [numLeaves, numLeavesSize] = decodeCompactSizeUint(tapTree, offset);
88
+ offset += numLeavesSize;
89
+ // Read each leaf
90
+ for (let i = 0; i < numLeaves; i++) {
91
+ // Skip depth (1 byte)
92
+ offset += 1;
93
+ // Skip leaf version (1 byte)
94
+ offset += 1;
95
+ // Read script length
96
+ const [scriptLength, scriptLengthSize] = decodeCompactSizeUint(tapTree, offset);
97
+ offset += scriptLengthSize;
98
+ // Read script content
99
+ const script = tapTree.slice(offset, offset + scriptLength);
100
+ scripts.push(script);
101
+ offset += scriptLength;
102
+ }
103
+ return scripts;
104
+ }
105
+ function decodeCompactSizeUint(data, offset) {
106
+ const firstByte = data[offset];
107
+ if (firstByte < 0xfd) {
108
+ return [firstByte, 1];
109
+ }
110
+ else if (firstByte === 0xfd) {
111
+ const value = new DataView(data.buffer).getUint16(offset + 1, true);
112
+ return [value, 3];
113
+ }
114
+ else if (firstByte === 0xfe) {
115
+ const value = new DataView(data.buffer).getUint32(offset + 1, true);
116
+ return [value, 5];
117
+ }
118
+ else {
119
+ const value = Number(new DataView(data.buffer).getBigUint64(offset + 1, true));
120
+ return [value, 9];
121
+ }
122
+ }
123
+ function encodeTaprootTree(leaves) {
124
+ const chunks = [];
125
+ // Write number of leaves as compact size uint
126
+ chunks.push(encodeCompactSizeUint(leaves.length));
127
+ for (const tapscript of leaves) {
128
+ // Write depth (always 1 for now)
129
+ chunks.push(new Uint8Array([1]));
130
+ // Write leaf version (0xc0 for tapscript)
131
+ chunks.push(new Uint8Array([0xc0]));
132
+ // Write script length and script
133
+ chunks.push(encodeCompactSizeUint(tapscript.length));
134
+ chunks.push(tapscript);
135
+ }
136
+ // Concatenate all chunks
137
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
138
+ const result = new Uint8Array(totalLength);
139
+ let offset = 0;
140
+ for (const chunk of chunks) {
141
+ result.set(chunk, offset);
142
+ offset += chunk.length;
143
+ }
144
+ return result;
145
+ }
146
+ function encodeCompactSizeUint(value) {
147
+ if (value < 0xfd) {
148
+ return new Uint8Array([value]);
149
+ }
150
+ else if (value <= 0xffff) {
151
+ const buffer = new Uint8Array(3);
152
+ buffer[0] = 0xfd;
153
+ new DataView(buffer.buffer).setUint16(1, value, true);
154
+ return buffer;
155
+ }
156
+ else if (value <= 0xffffffff) {
157
+ const buffer = new Uint8Array(5);
158
+ buffer[0] = 0xfe;
159
+ new DataView(buffer.buffer).setUint32(1, value, true);
160
+ return buffer;
161
+ }
162
+ else {
163
+ const buffer = new Uint8Array(9);
164
+ buffer[0] = 0xff;
165
+ new DataView(buffer.buffer).setBigUint64(1, BigInt(value), true);
166
+ return buffer;
167
+ }
168
+ }
@@ -4,12 +4,26 @@ exports.DefaultVtxo = void 0;
4
4
  const base_1 = require("./base");
5
5
  const tapscript_1 = require("./tapscript");
6
6
  const base_2 = require("@scure/base");
7
- // DefaultVtxo is the default implementation of a VtxoScript.
8
- // it contains 1 forfeit path and 1 exit path.
9
- // forfeit = (Alice + Server)
10
- // exit = (Alice) after csvTimelock
7
+ /**
8
+ * DefaultVtxo is the default implementation of a VtxoScript.
9
+ * It contains 1 forfeit path and 1 exit path.
10
+ * - forfeit = (Alice + Server)
11
+ * - exit = (Alice) after csvTimelock
12
+ */
11
13
  var DefaultVtxo;
12
14
  (function (DefaultVtxo) {
15
+ /**
16
+ * DefaultVtxo.Script is the class letting to create the vtxo script.
17
+ * @example
18
+ * ```typescript
19
+ * const vtxoScript = new DefaultVtxo.Script({
20
+ * pubKey: new Uint8Array(32),
21
+ * serverPubKey: new Uint8Array(32),
22
+ * });
23
+ *
24
+ * console.log("script pub key:", vtxoScript.pkScript)
25
+ * ```
26
+ */
13
27
  class Script extends base_1.VtxoScript {
14
28
  constructor(options) {
15
29
  const { pubKey, serverPubKey, csvTimelock = Script.DEFAULT_TIMELOCK, } = options;