@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.
- package/README.md +156 -174
- package/dist/cjs/arknote/index.js +61 -58
- package/dist/cjs/bip322/errors.js +13 -0
- package/dist/cjs/bip322/index.js +178 -0
- package/dist/cjs/forfeit.js +14 -25
- package/dist/cjs/identity/singleKey.js +68 -0
- package/dist/cjs/index.js +41 -17
- package/dist/cjs/providers/ark.js +253 -317
- package/dist/cjs/providers/indexer.js +525 -0
- package/dist/cjs/providers/onchain.js +193 -15
- package/dist/cjs/script/address.js +48 -17
- package/dist/cjs/script/base.js +120 -3
- package/dist/cjs/script/default.js +18 -4
- package/dist/cjs/script/tapscript.js +46 -14
- package/dist/cjs/script/vhtlc.js +27 -7
- package/dist/cjs/tree/signingSession.js +63 -106
- package/dist/cjs/tree/txTree.js +193 -0
- package/dist/cjs/tree/validation.js +79 -155
- package/dist/cjs/utils/anchor.js +35 -0
- package/dist/cjs/utils/arkTransaction.js +108 -0
- package/dist/cjs/utils/transactionHistory.js +84 -72
- package/dist/cjs/utils/txSizeEstimator.js +12 -0
- package/dist/cjs/utils/unknownFields.js +211 -0
- package/dist/cjs/wallet/index.js +12 -0
- package/dist/cjs/wallet/onchain.js +201 -0
- package/dist/cjs/wallet/ramps.js +95 -0
- package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +32 -0
- package/dist/cjs/wallet/serviceWorker/request.js +15 -12
- package/dist/cjs/wallet/serviceWorker/response.js +22 -27
- package/dist/cjs/wallet/serviceWorker/utils.js +8 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +58 -34
- package/dist/cjs/wallet/serviceWorker/worker.js +117 -108
- package/dist/cjs/wallet/unroll.js +270 -0
- package/dist/cjs/wallet/wallet.js +701 -459
- package/dist/esm/arknote/index.js +61 -57
- package/dist/esm/bip322/errors.js +9 -0
- package/dist/esm/bip322/index.js +174 -0
- package/dist/esm/forfeit.js +15 -26
- package/dist/esm/identity/singleKey.js +64 -0
- package/dist/esm/index.js +30 -12
- package/dist/esm/providers/ark.js +252 -317
- package/dist/esm/providers/indexer.js +521 -0
- package/dist/esm/providers/onchain.js +193 -15
- package/dist/esm/script/address.js +48 -17
- package/dist/esm/script/base.js +120 -3
- package/dist/esm/script/default.js +18 -4
- package/dist/esm/script/tapscript.js +46 -14
- package/dist/esm/script/vhtlc.js +27 -7
- package/dist/esm/tree/signingSession.js +65 -108
- package/dist/esm/tree/txTree.js +189 -0
- package/dist/esm/tree/validation.js +75 -152
- package/dist/esm/utils/anchor.js +31 -0
- package/dist/esm/utils/arkTransaction.js +105 -0
- package/dist/esm/utils/transactionHistory.js +84 -72
- package/dist/esm/utils/txSizeEstimator.js +12 -0
- package/dist/esm/utils/unknownFields.js +173 -0
- package/dist/esm/wallet/index.js +9 -0
- package/dist/esm/wallet/onchain.js +196 -0
- package/dist/esm/wallet/ramps.js +91 -0
- package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +32 -0
- package/dist/esm/wallet/serviceWorker/request.js +15 -12
- package/dist/esm/wallet/serviceWorker/response.js +22 -27
- package/dist/esm/wallet/serviceWorker/utils.js +8 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +59 -35
- package/dist/esm/wallet/serviceWorker/worker.js +117 -108
- package/dist/esm/wallet/unroll.js +267 -0
- package/dist/esm/wallet/wallet.js +674 -466
- package/dist/types/arknote/index.d.ts +40 -13
- package/dist/types/bip322/errors.d.ts +6 -0
- package/dist/types/bip322/index.d.ts +57 -0
- package/dist/types/forfeit.d.ts +2 -14
- package/dist/types/identity/singleKey.d.ts +27 -0
- package/dist/types/index.d.ts +23 -12
- package/dist/types/providers/ark.d.ts +114 -95
- package/dist/types/providers/indexer.d.ts +186 -0
- package/dist/types/providers/onchain.d.ts +41 -11
- package/dist/types/script/address.d.ts +26 -2
- package/dist/types/script/base.d.ts +13 -3
- package/dist/types/script/default.d.ts +22 -0
- package/dist/types/script/tapscript.d.ts +61 -5
- package/dist/types/script/vhtlc.d.ts +27 -0
- package/dist/types/tree/signingSession.d.ts +5 -5
- package/dist/types/tree/txTree.d.ts +28 -0
- package/dist/types/tree/validation.d.ts +15 -22
- package/dist/types/utils/anchor.d.ts +19 -0
- package/dist/types/utils/arkTransaction.d.ts +27 -0
- package/dist/types/utils/transactionHistory.d.ts +7 -1
- package/dist/types/utils/txSizeEstimator.d.ts +3 -0
- package/dist/types/utils/unknownFields.d.ts +83 -0
- package/dist/types/wallet/index.d.ts +51 -50
- package/dist/types/wallet/onchain.d.ts +49 -0
- package/dist/types/wallet/ramps.d.ts +32 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +2 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +2 -0
- package/dist/types/wallet/serviceWorker/request.d.ts +14 -16
- package/dist/types/wallet/serviceWorker/response.d.ts +17 -19
- package/dist/types/wallet/serviceWorker/utils.d.ts +8 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +36 -8
- package/dist/types/wallet/serviceWorker/worker.d.ts +7 -3
- package/dist/types/wallet/unroll.d.ts +102 -0
- package/dist/types/wallet/wallet.d.ts +71 -26
- package/package.json +14 -15
- package/dist/cjs/identity/inMemoryKey.js +0 -40
- package/dist/cjs/tree/vtxoTree.js +0 -231
- package/dist/cjs/utils/coinselect.js +0 -73
- package/dist/cjs/utils/psbt.js +0 -137
- package/dist/esm/identity/inMemoryKey.js +0 -36
- package/dist/esm/tree/vtxoTree.js +0 -191
- package/dist/esm/utils/coinselect.js +0 -69
- package/dist/esm/utils/psbt.js +0 -131
- package/dist/types/identity/inMemoryKey.d.ts +0 -12
- package/dist/types/tree/vtxoTree.d.ts +0 -33
- package/dist/types/utils/coinselect.d.ts +0 -21
- 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}/
|
|
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
|
|
39
|
+
const fees = (await response.json());
|
|
40
|
+
return fees["1"] ?? undefined;
|
|
29
41
|
}
|
|
30
|
-
async broadcastTransaction(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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,
|
|
29
|
+
constructor(serverPubKey, vtxoTaprootKey, hrp, version = 0) {
|
|
9
30
|
this.serverPubKey = serverPubKey;
|
|
10
|
-
this.
|
|
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 (
|
|
16
|
-
throw new Error("Invalid
|
|
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
|
|
26
|
-
if (data.length !==
|
|
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
|
|
30
|
-
const
|
|
31
|
-
|
|
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
|
|
35
|
-
const data = new Uint8Array(
|
|
36
|
-
data
|
|
37
|
-
data.set(this.
|
|
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.
|
|
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;
|
package/dist/cjs/script/base.js
CHANGED
|
@@ -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(
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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;
|