@arkade-os/sdk 0.1.4 → 0.2.1

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