@arkade-os/sdk 0.0.16 → 0.1.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.
- package/README.md +12 -4
- package/dist/cjs/providers/ark.js +17 -0
- package/dist/cjs/providers/onchain.js +12 -0
- package/dist/cjs/tree/vtxoTree.js +34 -0
- package/dist/cjs/utils/psbt.js +16 -3
- package/dist/cjs/wallet/serviceWorker/request.js +4 -0
- package/dist/cjs/wallet/serviceWorker/response.js +8 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +19 -5
- package/dist/cjs/wallet/serviceWorker/worker.js +39 -1
- package/dist/cjs/wallet/wallet.js +43 -2
- package/dist/esm/providers/ark.js +17 -0
- package/dist/esm/providers/onchain.js +12 -0
- package/dist/esm/tree/vtxoTree.js +35 -1
- package/dist/esm/utils/psbt.js +16 -3
- package/dist/esm/wallet/serviceWorker/request.js +4 -0
- package/dist/esm/wallet/serviceWorker/response.js +8 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +19 -5
- package/dist/esm/wallet/serviceWorker/worker.js +39 -1
- package/dist/esm/wallet/wallet.js +43 -2
- package/dist/types/providers/ark.d.ts +10 -0
- package/dist/types/providers/onchain.d.ts +10 -0
- package/dist/types/tree/vtxoTree.d.ts +1 -0
- package/dist/types/wallet/index.d.ts +1 -0
- package/dist/types/wallet/serviceWorker/request.d.ts +7 -2
- package/dist/types/wallet/serviceWorker/response.d.ts +6 -1
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -1
- package/dist/types/wallet/serviceWorker/worker.d.ts +2 -1
- package/dist/types/wallet/wallet.d.ts +2 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
The
|
|
3
|
-
|
|
4
|
-

|
|
1
|
+
# Arkade TypeScript SDK
|
|
2
|
+
The Arkade SDK is a TypeScript library for building Bitcoin wallets with support for both on-chain and off-chain transactions via the Ark protocol.
|
|
5
3
|
|
|
6
4
|
## Installation
|
|
7
5
|
|
|
@@ -92,6 +90,16 @@ console.log('History:', history)
|
|
|
92
90
|
}
|
|
93
91
|
```
|
|
94
92
|
|
|
93
|
+
### Unilateral Exit
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Unilateral exit all vtxos
|
|
97
|
+
await wallet.exit();
|
|
98
|
+
|
|
99
|
+
// Unilateral exit a specific vtxo
|
|
100
|
+
await wallet.exit([{ txid: vtxo.txid, vout: vtxo.vout }]);
|
|
101
|
+
```
|
|
102
|
+
|
|
95
103
|
### Running the wallet in a service worker
|
|
96
104
|
|
|
97
105
|
1. Create a service worker file
|
|
@@ -40,6 +40,23 @@ class RestArkProvider {
|
|
|
40
40
|
spentVtxos: [...(data.spentVtxos || [])].map(convertVtxo),
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
+
async getRound(txid) {
|
|
44
|
+
const url = `${this.serverUrl}/v1/round/${txid}`;
|
|
45
|
+
const response = await fetch(url);
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`Failed to fetch round: ${response.statusText}`);
|
|
48
|
+
}
|
|
49
|
+
const data = (await response.json());
|
|
50
|
+
const round = data.round;
|
|
51
|
+
return {
|
|
52
|
+
id: round.id,
|
|
53
|
+
start: new Date(Number(round.start) * 1000), // Convert from Unix timestamp to Date
|
|
54
|
+
end: new Date(Number(round.end) * 1000), // Convert from Unix timestamp to Date
|
|
55
|
+
vtxoTree: this.toTxTree(round.vtxoTree),
|
|
56
|
+
forfeitTxs: round.forfeitTxs || [],
|
|
57
|
+
connectors: this.toTxTree(round.connectors),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
43
60
|
async submitVirtualTx(psbtBase64) {
|
|
44
61
|
const url = `${this.serverUrl}/v1/redeem-tx`;
|
|
45
62
|
const response = await fetch(url, {
|
|
@@ -57,5 +57,17 @@ class EsploraProvider {
|
|
|
57
57
|
}
|
|
58
58
|
return response.json();
|
|
59
59
|
}
|
|
60
|
+
async getTxStatus(txid) {
|
|
61
|
+
const response = await fetch(`${this.baseUrl}/tx/${txid}/status`);
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Failed to get transaction status: ${response.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
return {
|
|
67
|
+
confirmed: data.confirmed,
|
|
68
|
+
blockTime: data.block_time,
|
|
69
|
+
blockHeight: data.block_height,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
60
72
|
}
|
|
61
73
|
exports.EsploraProvider = EsploraProvider;
|
|
@@ -111,6 +111,11 @@ class TxTree {
|
|
|
111
111
|
}
|
|
112
112
|
return branch;
|
|
113
113
|
}
|
|
114
|
+
// Returns the remaining transactions to broadcast in order to exit the vtxo
|
|
115
|
+
async exitBranch(vtxoTxid, isTxConfirmed) {
|
|
116
|
+
const offchainPart = await getOffchainPart(this.branch(vtxoTxid), isTxConfirmed);
|
|
117
|
+
return offchainPart.map(getExitTransaction);
|
|
118
|
+
}
|
|
114
119
|
// Helper method to find parent of a node
|
|
115
120
|
findParent(node) {
|
|
116
121
|
for (const level of this.tree) {
|
|
@@ -195,3 +200,32 @@ function getCosignerKeys(tx) {
|
|
|
195
200
|
}
|
|
196
201
|
return keys;
|
|
197
202
|
}
|
|
203
|
+
async function getOffchainPart(branch, isTxConfirmed) {
|
|
204
|
+
let offchainPath = [...branch];
|
|
205
|
+
// Iterate from the end of the branch (leaf) to the beginning (root)
|
|
206
|
+
for (let i = branch.length - 1; i >= 0; i--) {
|
|
207
|
+
const node = branch[i];
|
|
208
|
+
// check if the transaction is confirmed on-chain
|
|
209
|
+
if (await isTxConfirmed(node.txid)) {
|
|
210
|
+
// if this is the leaf node, return empty array as everything is confirmed
|
|
211
|
+
if (i === branch.length - 1) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
// otherwise, return the unconfirmed part of the branch
|
|
215
|
+
return branch.slice(i + 1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// no confirmation: everything is offchain
|
|
219
|
+
return offchainPath;
|
|
220
|
+
}
|
|
221
|
+
// getExitTransaction finalizes the psbt's input using the musig2 tapkey signature
|
|
222
|
+
function getExitTransaction(treeNode) {
|
|
223
|
+
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(treeNode.tx));
|
|
224
|
+
const input = tx.getInput(0);
|
|
225
|
+
if (!input.tapKeySig)
|
|
226
|
+
throw new TxTreeError("missing tapkey signature");
|
|
227
|
+
const rawTx = btc_signer_1.RawTx.decode(tx.unsignedTx);
|
|
228
|
+
rawTx.witnesses = [[input.tapKeySig]];
|
|
229
|
+
rawTx.segwitFlag = true;
|
|
230
|
+
return base_1.hex.encode(btc_signer_1.RawTx.encode(rawTx));
|
|
231
|
+
}
|
package/dist/cjs/utils/psbt.js
CHANGED
|
@@ -43,16 +43,25 @@ function addConditionWitness(inIndex, tx, witness) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
function createVirtualTx(inputs, outputs) {
|
|
46
|
-
let lockTime;
|
|
46
|
+
let lockTime = 0n;
|
|
47
47
|
for (const input of inputs) {
|
|
48
48
|
const tapscript = (0, tapscript_1.decodeTapscript)((0, base_1.scriptFromTapLeafScript)(input.tapLeafScript));
|
|
49
49
|
if (tapscript_1.CLTVMultisigTapscript.is(tapscript)) {
|
|
50
|
-
lockTime
|
|
50
|
+
if (lockTime !== 0n) {
|
|
51
|
+
// if a locktime is already set, check if the new locktime is in the same unit
|
|
52
|
+
if (isSeconds(lockTime) !==
|
|
53
|
+
isSeconds(tapscript.params.absoluteTimelock)) {
|
|
54
|
+
throw new Error("cannot mix seconds and blocks locktime");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (tapscript.params.absoluteTimelock > lockTime) {
|
|
58
|
+
lockTime = tapscript.params.absoluteTimelock;
|
|
59
|
+
}
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
const tx = new btc_signer_1.Transaction({
|
|
54
63
|
allowUnknown: true,
|
|
55
|
-
lockTime,
|
|
64
|
+
lockTime: Number(lockTime),
|
|
56
65
|
});
|
|
57
66
|
for (const [i, input] of inputs.entries()) {
|
|
58
67
|
tx.addInput({
|
|
@@ -122,3 +131,7 @@ function encodeCompactSizeUint(value) {
|
|
|
122
131
|
return buffer;
|
|
123
132
|
}
|
|
124
133
|
}
|
|
134
|
+
const nLocktimeMinSeconds = 500000000n;
|
|
135
|
+
function isSeconds(locktime) {
|
|
136
|
+
return locktime >= nLocktimeMinSeconds;
|
|
137
|
+
}
|
|
@@ -184,4 +184,12 @@ var Response;
|
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
186
|
Response.clearResponse = clearResponse;
|
|
187
|
+
function exitSuccess(id) {
|
|
188
|
+
return {
|
|
189
|
+
type: "EXIT_SUCCESS",
|
|
190
|
+
success: true,
|
|
191
|
+
id,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
Response.exitSuccess = exitSuccess;
|
|
187
195
|
})(Response || (exports.Response = Response = {}));
|
|
@@ -82,20 +82,17 @@ class ServiceWorkerWallet {
|
|
|
82
82
|
registration = await navigator.serviceWorker.register(path);
|
|
83
83
|
// Handle updates
|
|
84
84
|
registration.addEventListener("updatefound", () => {
|
|
85
|
-
console.info("@arklabs/wallet-sdk: Service worker auto-update...");
|
|
86
85
|
const newWorker = registration.installing;
|
|
87
86
|
if (!newWorker)
|
|
88
87
|
return;
|
|
89
88
|
newWorker.addEventListener("statechange", () => {
|
|
90
|
-
if (newWorker.state === "
|
|
89
|
+
if (newWorker.state === "activated" &&
|
|
91
90
|
navigator.serviceWorker.controller) {
|
|
92
|
-
console.info("
|
|
91
|
+
console.info("Service worker activated, reloading...");
|
|
93
92
|
window.location.reload();
|
|
94
93
|
}
|
|
95
94
|
});
|
|
96
95
|
});
|
|
97
|
-
// Check for updates
|
|
98
|
-
await registration.update();
|
|
99
96
|
const sw = registration.active ||
|
|
100
97
|
registration.waiting ||
|
|
101
98
|
registration.installing;
|
|
@@ -324,6 +321,23 @@ class ServiceWorkerWallet {
|
|
|
324
321
|
throw new Error(`Failed to get transaction history: ${error}`);
|
|
325
322
|
}
|
|
326
323
|
}
|
|
324
|
+
async exit(outpoints) {
|
|
325
|
+
const message = {
|
|
326
|
+
type: "EXIT",
|
|
327
|
+
outpoints,
|
|
328
|
+
id: getRandomId(),
|
|
329
|
+
};
|
|
330
|
+
try {
|
|
331
|
+
const response = await this.sendMessage(message);
|
|
332
|
+
if (response.type === "EXIT_SUCCESS") {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
throw new UnexpectedResponseError(response);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
throw new Error(`Failed to exit: ${error}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
327
341
|
}
|
|
328
342
|
exports.ServiceWorkerWallet = ServiceWorkerWallet;
|
|
329
343
|
function getRandomId() {
|
|
@@ -17,10 +17,20 @@ class Worker {
|
|
|
17
17
|
this.vtxoRepository = vtxoRepository;
|
|
18
18
|
this.messageCallback = messageCallback;
|
|
19
19
|
}
|
|
20
|
-
async start() {
|
|
20
|
+
async start(withServiceWorkerUpdate = true) {
|
|
21
21
|
self.addEventListener("message", async (event) => {
|
|
22
22
|
await this.handleMessage(event);
|
|
23
23
|
});
|
|
24
|
+
if (withServiceWorkerUpdate) {
|
|
25
|
+
// activate service worker immediately
|
|
26
|
+
self.addEventListener("install", () => {
|
|
27
|
+
self.skipWaiting();
|
|
28
|
+
});
|
|
29
|
+
// take control of clients immediately
|
|
30
|
+
self.addEventListener("activate", () => {
|
|
31
|
+
self.clients.claim();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
24
34
|
}
|
|
25
35
|
async clear() {
|
|
26
36
|
if (this.vtxoSubscription) {
|
|
@@ -387,6 +397,30 @@ class Worker {
|
|
|
387
397
|
}
|
|
388
398
|
event.source?.postMessage(response_1.Response.walletStatus(message.id, this.wallet !== undefined));
|
|
389
399
|
}
|
|
400
|
+
async handleExit(event) {
|
|
401
|
+
const message = event.data;
|
|
402
|
+
if (!request_1.Request.isExit(message)) {
|
|
403
|
+
console.error("Invalid EXIT message format", message);
|
|
404
|
+
event.source?.postMessage(response_1.Response.error(message.id, "Invalid EXIT message format"));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (!this.wallet) {
|
|
408
|
+
console.error("Wallet not initialized");
|
|
409
|
+
event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
await this.wallet.exit(message.outpoints);
|
|
414
|
+
event.source?.postMessage(response_1.Response.exitSuccess(message.id));
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
console.error("Error exiting:", error);
|
|
418
|
+
const errorMessage = error instanceof Error
|
|
419
|
+
? error.message
|
|
420
|
+
: "Unknown error occurred";
|
|
421
|
+
event.source?.postMessage(response_1.Response.error(message.id, errorMessage));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
390
424
|
async handleMessage(event) {
|
|
391
425
|
this.messageCallback(event);
|
|
392
426
|
const message = event.data;
|
|
@@ -440,6 +474,10 @@ class Worker {
|
|
|
440
474
|
await this.handleGetStatus(event);
|
|
441
475
|
break;
|
|
442
476
|
}
|
|
477
|
+
case "EXIT": {
|
|
478
|
+
await this.handleExit(event);
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
443
481
|
case "CLEAR": {
|
|
444
482
|
await this.handleClear(event);
|
|
445
483
|
break;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Wallet = void 0;
|
|
4
4
|
const base_1 = require("@scure/base");
|
|
5
5
|
const payment_1 = require("@scure/btc-signer/payment");
|
|
6
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
7
|
const psbt_1 = require("@scure/btc-signer/psbt");
|
|
7
8
|
const transactionHistory_1 = require("../utils/transactionHistory");
|
|
8
9
|
const bip21_1 = require("../utils/bip21");
|
|
@@ -19,7 +20,6 @@ const _1 = require(".");
|
|
|
19
20
|
const base_2 = require("../script/base");
|
|
20
21
|
const tapscript_1 = require("../script/tapscript");
|
|
21
22
|
const psbt_2 = require("../utils/psbt");
|
|
22
|
-
const btc_signer_1 = require("@scure/btc-signer");
|
|
23
23
|
const arknote_1 = require("../arknote");
|
|
24
24
|
// Wallet does not store any data and rely on the Ark and onchain providers to fetch utxos and vtxos
|
|
25
25
|
class Wallet {
|
|
@@ -369,7 +369,6 @@ class Wallet {
|
|
|
369
369
|
amount: BigInt(input.value),
|
|
370
370
|
},
|
|
371
371
|
tapInternalKey: this.onchainP2TR.tapInternalKey,
|
|
372
|
-
tapMerkleRoot: this.onchainP2TR.tapMerkleRoot,
|
|
373
372
|
});
|
|
374
373
|
}
|
|
375
374
|
// Add payment output
|
|
@@ -585,6 +584,48 @@ class Wallet {
|
|
|
585
584
|
}
|
|
586
585
|
throw new Error("Settlement failed");
|
|
587
586
|
}
|
|
587
|
+
async exit(outpoints) {
|
|
588
|
+
// TODO store the exit branches in repository
|
|
589
|
+
// exit should not depend on the ark provider
|
|
590
|
+
if (!this.arkProvider) {
|
|
591
|
+
throw new Error("Ark provider not configured");
|
|
592
|
+
}
|
|
593
|
+
let vtxos = await this.getVtxos();
|
|
594
|
+
if (outpoints && outpoints.length > 0) {
|
|
595
|
+
vtxos = vtxos.filter((vtxo) => outpoints.some((outpoint) => vtxo.txid === outpoint.txid &&
|
|
596
|
+
vtxo.vout === outpoint.vout));
|
|
597
|
+
}
|
|
598
|
+
if (vtxos.length === 0) {
|
|
599
|
+
throw new Error("No vtxos to exit");
|
|
600
|
+
}
|
|
601
|
+
const trees = new Map();
|
|
602
|
+
const transactions = [];
|
|
603
|
+
for (const vtxo of vtxos) {
|
|
604
|
+
const batchTxid = vtxo.virtualStatus.batchTxID;
|
|
605
|
+
if (!batchTxid)
|
|
606
|
+
continue;
|
|
607
|
+
if (!trees.has(batchTxid)) {
|
|
608
|
+
const round = await this.arkProvider.getRound(batchTxid);
|
|
609
|
+
trees.set(batchTxid, round.vtxoTree);
|
|
610
|
+
}
|
|
611
|
+
const tree = trees.get(batchTxid);
|
|
612
|
+
if (!tree) {
|
|
613
|
+
throw new Error("Tree not found");
|
|
614
|
+
}
|
|
615
|
+
const exitBranch = await tree.exitBranch(vtxo.txid, async (txid) => {
|
|
616
|
+
const status = await this.onchainProvider.getTxStatus(txid);
|
|
617
|
+
return status.confirmed;
|
|
618
|
+
});
|
|
619
|
+
transactions.push(...exitBranch);
|
|
620
|
+
}
|
|
621
|
+
const broadcastedTxs = new Map();
|
|
622
|
+
for (const tx of transactions) {
|
|
623
|
+
if (broadcastedTxs.has(tx))
|
|
624
|
+
continue;
|
|
625
|
+
const txid = await this.onchainProvider.broadcastTransaction(tx);
|
|
626
|
+
broadcastedTxs.set(txid, true);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
588
629
|
// validates the vtxo tree, creates a signing session and generates the musig2 nonces
|
|
589
630
|
async handleSettlementSigningEvent(event, sweepTapTreeRoot, session) {
|
|
590
631
|
const vtxoTree = event.unsignedVtxoTree;
|
|
@@ -37,6 +37,23 @@ export class RestArkProvider {
|
|
|
37
37
|
spentVtxos: [...(data.spentVtxos || [])].map(convertVtxo),
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
async getRound(txid) {
|
|
41
|
+
const url = `${this.serverUrl}/v1/round/${txid}`;
|
|
42
|
+
const response = await fetch(url);
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new Error(`Failed to fetch round: ${response.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
const data = (await response.json());
|
|
47
|
+
const round = data.round;
|
|
48
|
+
return {
|
|
49
|
+
id: round.id,
|
|
50
|
+
start: new Date(Number(round.start) * 1000), // Convert from Unix timestamp to Date
|
|
51
|
+
end: new Date(Number(round.end) * 1000), // Convert from Unix timestamp to Date
|
|
52
|
+
vtxoTree: this.toTxTree(round.vtxoTree),
|
|
53
|
+
forfeitTxs: round.forfeitTxs || [],
|
|
54
|
+
connectors: this.toTxTree(round.connectors),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
40
57
|
async submitVirtualTx(psbtBase64) {
|
|
41
58
|
const url = `${this.serverUrl}/v1/redeem-tx`;
|
|
42
59
|
const response = await fetch(url, {
|
|
@@ -54,4 +54,16 @@ export class EsploraProvider {
|
|
|
54
54
|
}
|
|
55
55
|
return response.json();
|
|
56
56
|
}
|
|
57
|
+
async getTxStatus(txid) {
|
|
58
|
+
const response = await fetch(`${this.baseUrl}/tx/${txid}/status`);
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(`Failed to get transaction status: ${response.statusText}`);
|
|
61
|
+
}
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
return {
|
|
64
|
+
confirmed: data.confirmed,
|
|
65
|
+
blockTime: data.block_time,
|
|
66
|
+
blockHeight: data.block_height,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
57
69
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as bip68 from "bip68";
|
|
2
|
-
import { ScriptNum, Transaction } from "@scure/btc-signer";
|
|
2
|
+
import { RawTx, ScriptNum, Transaction } from "@scure/btc-signer";
|
|
3
3
|
import { sha256x2 } from "@scure/btc-signer/utils";
|
|
4
4
|
import { base64, hex } from "@scure/base";
|
|
5
5
|
export class TxTreeError extends Error {
|
|
@@ -72,6 +72,11 @@ export class TxTree {
|
|
|
72
72
|
}
|
|
73
73
|
return branch;
|
|
74
74
|
}
|
|
75
|
+
// Returns the remaining transactions to broadcast in order to exit the vtxo
|
|
76
|
+
async exitBranch(vtxoTxid, isTxConfirmed) {
|
|
77
|
+
const offchainPart = await getOffchainPart(this.branch(vtxoTxid), isTxConfirmed);
|
|
78
|
+
return offchainPart.map(getExitTransaction);
|
|
79
|
+
}
|
|
75
80
|
// Helper method to find parent of a node
|
|
76
81
|
findParent(node) {
|
|
77
82
|
for (const level of this.tree) {
|
|
@@ -155,3 +160,32 @@ export function getCosignerKeys(tx) {
|
|
|
155
160
|
}
|
|
156
161
|
return keys;
|
|
157
162
|
}
|
|
163
|
+
async function getOffchainPart(branch, isTxConfirmed) {
|
|
164
|
+
let offchainPath = [...branch];
|
|
165
|
+
// Iterate from the end of the branch (leaf) to the beginning (root)
|
|
166
|
+
for (let i = branch.length - 1; i >= 0; i--) {
|
|
167
|
+
const node = branch[i];
|
|
168
|
+
// check if the transaction is confirmed on-chain
|
|
169
|
+
if (await isTxConfirmed(node.txid)) {
|
|
170
|
+
// if this is the leaf node, return empty array as everything is confirmed
|
|
171
|
+
if (i === branch.length - 1) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
// otherwise, return the unconfirmed part of the branch
|
|
175
|
+
return branch.slice(i + 1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// no confirmation: everything is offchain
|
|
179
|
+
return offchainPath;
|
|
180
|
+
}
|
|
181
|
+
// getExitTransaction finalizes the psbt's input using the musig2 tapkey signature
|
|
182
|
+
function getExitTransaction(treeNode) {
|
|
183
|
+
const tx = Transaction.fromPSBT(base64.decode(treeNode.tx));
|
|
184
|
+
const input = tx.getInput(0);
|
|
185
|
+
if (!input.tapKeySig)
|
|
186
|
+
throw new TxTreeError("missing tapkey signature");
|
|
187
|
+
const rawTx = RawTx.decode(tx.unsignedTx);
|
|
188
|
+
rawTx.witnesses = [[input.tapKeySig]];
|
|
189
|
+
rawTx.segwitFlag = true;
|
|
190
|
+
return hex.encode(RawTx.encode(rawTx));
|
|
191
|
+
}
|
package/dist/esm/utils/psbt.js
CHANGED
|
@@ -37,16 +37,25 @@ export function addConditionWitness(inIndex, tx, witness) {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
export function createVirtualTx(inputs, outputs) {
|
|
40
|
-
let lockTime;
|
|
40
|
+
let lockTime = 0n;
|
|
41
41
|
for (const input of inputs) {
|
|
42
42
|
const tapscript = decodeTapscript(scriptFromTapLeafScript(input.tapLeafScript));
|
|
43
43
|
if (CLTVMultisigTapscript.is(tapscript)) {
|
|
44
|
-
lockTime
|
|
44
|
+
if (lockTime !== 0n) {
|
|
45
|
+
// if a locktime is already set, check if the new locktime is in the same unit
|
|
46
|
+
if (isSeconds(lockTime) !==
|
|
47
|
+
isSeconds(tapscript.params.absoluteTimelock)) {
|
|
48
|
+
throw new Error("cannot mix seconds and blocks locktime");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (tapscript.params.absoluteTimelock > lockTime) {
|
|
52
|
+
lockTime = tapscript.params.absoluteTimelock;
|
|
53
|
+
}
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
const tx = new Transaction({
|
|
48
57
|
allowUnknown: true,
|
|
49
|
-
lockTime,
|
|
58
|
+
lockTime: Number(lockTime),
|
|
50
59
|
});
|
|
51
60
|
for (const [i, input] of inputs.entries()) {
|
|
52
61
|
tx.addInput({
|
|
@@ -116,3 +125,7 @@ function encodeCompactSizeUint(value) {
|
|
|
116
125
|
return buffer;
|
|
117
126
|
}
|
|
118
127
|
}
|
|
128
|
+
const nLocktimeMinSeconds = 500000000n;
|
|
129
|
+
function isSeconds(locktime) {
|
|
130
|
+
return locktime >= nLocktimeMinSeconds;
|
|
131
|
+
}
|
|
@@ -181,4 +181,12 @@ export var Response;
|
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
183
|
Response.clearResponse = clearResponse;
|
|
184
|
+
function exitSuccess(id) {
|
|
185
|
+
return {
|
|
186
|
+
type: "EXIT_SUCCESS",
|
|
187
|
+
success: true,
|
|
188
|
+
id,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
Response.exitSuccess = exitSuccess;
|
|
184
192
|
})(Response || (Response = {}));
|
|
@@ -79,20 +79,17 @@ export class ServiceWorkerWallet {
|
|
|
79
79
|
registration = await navigator.serviceWorker.register(path);
|
|
80
80
|
// Handle updates
|
|
81
81
|
registration.addEventListener("updatefound", () => {
|
|
82
|
-
console.info("@arklabs/wallet-sdk: Service worker auto-update...");
|
|
83
82
|
const newWorker = registration.installing;
|
|
84
83
|
if (!newWorker)
|
|
85
84
|
return;
|
|
86
85
|
newWorker.addEventListener("statechange", () => {
|
|
87
|
-
if (newWorker.state === "
|
|
86
|
+
if (newWorker.state === "activated" &&
|
|
88
87
|
navigator.serviceWorker.controller) {
|
|
89
|
-
console.info("
|
|
88
|
+
console.info("Service worker activated, reloading...");
|
|
90
89
|
window.location.reload();
|
|
91
90
|
}
|
|
92
91
|
});
|
|
93
92
|
});
|
|
94
|
-
// Check for updates
|
|
95
|
-
await registration.update();
|
|
96
93
|
const sw = registration.active ||
|
|
97
94
|
registration.waiting ||
|
|
98
95
|
registration.installing;
|
|
@@ -321,6 +318,23 @@ export class ServiceWorkerWallet {
|
|
|
321
318
|
throw new Error(`Failed to get transaction history: ${error}`);
|
|
322
319
|
}
|
|
323
320
|
}
|
|
321
|
+
async exit(outpoints) {
|
|
322
|
+
const message = {
|
|
323
|
+
type: "EXIT",
|
|
324
|
+
outpoints,
|
|
325
|
+
id: getRandomId(),
|
|
326
|
+
};
|
|
327
|
+
try {
|
|
328
|
+
const response = await this.sendMessage(message);
|
|
329
|
+
if (response.type === "EXIT_SUCCESS") {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
throw new UnexpectedResponseError(response);
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
throw new Error(`Failed to exit: ${error}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
324
338
|
}
|
|
325
339
|
function getRandomId() {
|
|
326
340
|
const randomValue = crypto.getRandomValues(new Uint8Array(16));
|
|
@@ -14,10 +14,20 @@ export class Worker {
|
|
|
14
14
|
this.vtxoRepository = vtxoRepository;
|
|
15
15
|
this.messageCallback = messageCallback;
|
|
16
16
|
}
|
|
17
|
-
async start() {
|
|
17
|
+
async start(withServiceWorkerUpdate = true) {
|
|
18
18
|
self.addEventListener("message", async (event) => {
|
|
19
19
|
await this.handleMessage(event);
|
|
20
20
|
});
|
|
21
|
+
if (withServiceWorkerUpdate) {
|
|
22
|
+
// activate service worker immediately
|
|
23
|
+
self.addEventListener("install", () => {
|
|
24
|
+
self.skipWaiting();
|
|
25
|
+
});
|
|
26
|
+
// take control of clients immediately
|
|
27
|
+
self.addEventListener("activate", () => {
|
|
28
|
+
self.clients.claim();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
21
31
|
}
|
|
22
32
|
async clear() {
|
|
23
33
|
if (this.vtxoSubscription) {
|
|
@@ -384,6 +394,30 @@ export class Worker {
|
|
|
384
394
|
}
|
|
385
395
|
event.source?.postMessage(Response.walletStatus(message.id, this.wallet !== undefined));
|
|
386
396
|
}
|
|
397
|
+
async handleExit(event) {
|
|
398
|
+
const message = event.data;
|
|
399
|
+
if (!Request.isExit(message)) {
|
|
400
|
+
console.error("Invalid EXIT message format", message);
|
|
401
|
+
event.source?.postMessage(Response.error(message.id, "Invalid EXIT message format"));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (!this.wallet) {
|
|
405
|
+
console.error("Wallet not initialized");
|
|
406
|
+
event.source?.postMessage(Response.error(message.id, "Wallet not initialized"));
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
await this.wallet.exit(message.outpoints);
|
|
411
|
+
event.source?.postMessage(Response.exitSuccess(message.id));
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
console.error("Error exiting:", error);
|
|
415
|
+
const errorMessage = error instanceof Error
|
|
416
|
+
? error.message
|
|
417
|
+
: "Unknown error occurred";
|
|
418
|
+
event.source?.postMessage(Response.error(message.id, errorMessage));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
387
421
|
async handleMessage(event) {
|
|
388
422
|
this.messageCallback(event);
|
|
389
423
|
const message = event.data;
|
|
@@ -437,6 +471,10 @@ export class Worker {
|
|
|
437
471
|
await this.handleGetStatus(event);
|
|
438
472
|
break;
|
|
439
473
|
}
|
|
474
|
+
case "EXIT": {
|
|
475
|
+
await this.handleExit(event);
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
440
478
|
case "CLEAR": {
|
|
441
479
|
await this.handleClear(event);
|
|
442
480
|
break;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { base64, hex } from "@scure/base";
|
|
2
2
|
import { Address, OutScript, p2tr, tapLeafHash, } from "@scure/btc-signer/payment";
|
|
3
|
+
import { Transaction } from "@scure/btc-signer";
|
|
3
4
|
import { TaprootControlBlock } from "@scure/btc-signer/psbt";
|
|
4
5
|
import { vtxosToTxs } from '../utils/transactionHistory.js';
|
|
5
6
|
import { BIP21 } from '../utils/bip21.js';
|
|
@@ -16,7 +17,6 @@ import { TxType, } from './index.js';
|
|
|
16
17
|
import { scriptFromTapLeafScript, VtxoScript } from '../script/base.js';
|
|
17
18
|
import { CSVMultisigTapscript, decodeTapscript, } from '../script/tapscript.js';
|
|
18
19
|
import { createVirtualTx } from '../utils/psbt.js';
|
|
19
|
-
import { Transaction } from "@scure/btc-signer";
|
|
20
20
|
import { ArkNote } from '../arknote/index.js';
|
|
21
21
|
// Wallet does not store any data and rely on the Ark and onchain providers to fetch utxos and vtxos
|
|
22
22
|
export class Wallet {
|
|
@@ -366,7 +366,6 @@ export class Wallet {
|
|
|
366
366
|
amount: BigInt(input.value),
|
|
367
367
|
},
|
|
368
368
|
tapInternalKey: this.onchainP2TR.tapInternalKey,
|
|
369
|
-
tapMerkleRoot: this.onchainP2TR.tapMerkleRoot,
|
|
370
369
|
});
|
|
371
370
|
}
|
|
372
371
|
// Add payment output
|
|
@@ -582,6 +581,48 @@ export class Wallet {
|
|
|
582
581
|
}
|
|
583
582
|
throw new Error("Settlement failed");
|
|
584
583
|
}
|
|
584
|
+
async exit(outpoints) {
|
|
585
|
+
// TODO store the exit branches in repository
|
|
586
|
+
// exit should not depend on the ark provider
|
|
587
|
+
if (!this.arkProvider) {
|
|
588
|
+
throw new Error("Ark provider not configured");
|
|
589
|
+
}
|
|
590
|
+
let vtxos = await this.getVtxos();
|
|
591
|
+
if (outpoints && outpoints.length > 0) {
|
|
592
|
+
vtxos = vtxos.filter((vtxo) => outpoints.some((outpoint) => vtxo.txid === outpoint.txid &&
|
|
593
|
+
vtxo.vout === outpoint.vout));
|
|
594
|
+
}
|
|
595
|
+
if (vtxos.length === 0) {
|
|
596
|
+
throw new Error("No vtxos to exit");
|
|
597
|
+
}
|
|
598
|
+
const trees = new Map();
|
|
599
|
+
const transactions = [];
|
|
600
|
+
for (const vtxo of vtxos) {
|
|
601
|
+
const batchTxid = vtxo.virtualStatus.batchTxID;
|
|
602
|
+
if (!batchTxid)
|
|
603
|
+
continue;
|
|
604
|
+
if (!trees.has(batchTxid)) {
|
|
605
|
+
const round = await this.arkProvider.getRound(batchTxid);
|
|
606
|
+
trees.set(batchTxid, round.vtxoTree);
|
|
607
|
+
}
|
|
608
|
+
const tree = trees.get(batchTxid);
|
|
609
|
+
if (!tree) {
|
|
610
|
+
throw new Error("Tree not found");
|
|
611
|
+
}
|
|
612
|
+
const exitBranch = await tree.exitBranch(vtxo.txid, async (txid) => {
|
|
613
|
+
const status = await this.onchainProvider.getTxStatus(txid);
|
|
614
|
+
return status.confirmed;
|
|
615
|
+
});
|
|
616
|
+
transactions.push(...exitBranch);
|
|
617
|
+
}
|
|
618
|
+
const broadcastedTxs = new Map();
|
|
619
|
+
for (const tx of transactions) {
|
|
620
|
+
if (broadcastedTxs.has(tx))
|
|
621
|
+
continue;
|
|
622
|
+
const txid = await this.onchainProvider.broadcastTransaction(tx);
|
|
623
|
+
broadcastedTxs.set(txid, true);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
585
626
|
// validates the vtxo tree, creates a signing session and generates the musig2 nonces
|
|
586
627
|
async handleSettlementSigningEvent(event, sweepTapTreeRoot, session) {
|
|
587
628
|
const vtxoTree = event.unsignedVtxoTree;
|
|
@@ -75,8 +75,17 @@ export interface ArkInfo {
|
|
|
75
75
|
end: number;
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
+
export interface Round {
|
|
79
|
+
id: string;
|
|
80
|
+
start: Date;
|
|
81
|
+
end: Date;
|
|
82
|
+
vtxoTree: TxTree;
|
|
83
|
+
forfeitTxs: string[];
|
|
84
|
+
connectors: TxTree;
|
|
85
|
+
}
|
|
78
86
|
export interface ArkProvider {
|
|
79
87
|
getInfo(): Promise<ArkInfo>;
|
|
88
|
+
getRound(txid: string): Promise<Round>;
|
|
80
89
|
getVirtualCoins(address: string): Promise<{
|
|
81
90
|
spendableVtxos: VirtualCoin[];
|
|
82
91
|
spentVtxos: VirtualCoin[];
|
|
@@ -105,6 +114,7 @@ export declare class RestArkProvider implements ArkProvider {
|
|
|
105
114
|
spendableVtxos: VirtualCoin[];
|
|
106
115
|
spentVtxos: VirtualCoin[];
|
|
107
116
|
}>;
|
|
117
|
+
getRound(txid: string): Promise<Round>;
|
|
108
118
|
submitVirtualTx(psbtBase64: string): Promise<string>;
|
|
109
119
|
subscribeToEvents(callback: (event: ArkEvent) => void): Promise<() => void>;
|
|
110
120
|
registerInputsForNextRound(inputs: Input[]): Promise<{
|
|
@@ -21,6 +21,11 @@ export interface OnchainProvider {
|
|
|
21
21
|
txid: string;
|
|
22
22
|
}[]>;
|
|
23
23
|
getTransactions(address: string): Promise<ExplorerTransaction[]>;
|
|
24
|
+
getTxStatus(txid: string): Promise<{
|
|
25
|
+
confirmed: boolean;
|
|
26
|
+
blockTime?: number;
|
|
27
|
+
blockHeight?: number;
|
|
28
|
+
}>;
|
|
24
29
|
}
|
|
25
30
|
export declare class EsploraProvider implements OnchainProvider {
|
|
26
31
|
private baseUrl;
|
|
@@ -33,4 +38,9 @@ export declare class EsploraProvider implements OnchainProvider {
|
|
|
33
38
|
txid: string;
|
|
34
39
|
}[]>;
|
|
35
40
|
getTransactions(address: string): Promise<ExplorerTransaction[]>;
|
|
41
|
+
getTxStatus(txid: string): Promise<{
|
|
42
|
+
confirmed: boolean;
|
|
43
|
+
blockTime?: number;
|
|
44
|
+
blockHeight?: number;
|
|
45
|
+
}>;
|
|
36
46
|
}
|
|
@@ -20,6 +20,7 @@ export declare class TxTree {
|
|
|
20
20
|
children(nodeTxid: string): TreeNode[];
|
|
21
21
|
numberOfNodes(): number;
|
|
22
22
|
branch(vtxoTxid: string): TreeNode[];
|
|
23
|
+
exitBranch(vtxoTxid: string, isTxConfirmed: (txid: string) => Promise<boolean>): Promise<string[]>;
|
|
23
24
|
private findParent;
|
|
24
25
|
validate(): void;
|
|
25
26
|
}
|
|
@@ -119,4 +119,5 @@ export interface IWallet {
|
|
|
119
119
|
getTransactionHistory(): Promise<ArkTransaction[]>;
|
|
120
120
|
sendBitcoin(params: SendBitcoinParams, zeroFee?: boolean): Promise<string>;
|
|
121
121
|
settle(params?: SettleParams, eventCallback?: (event: SettlementEvent) => void): Promise<string>;
|
|
122
|
+
exit(outpoints?: Outpoint[]): Promise<void>;
|
|
122
123
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NetworkName } from "../../networks";
|
|
2
|
-
import { SettleParams, SendBitcoinParams } from "..";
|
|
2
|
+
import { SettleParams, SendBitcoinParams, Outpoint } from "..";
|
|
3
3
|
export declare namespace Request {
|
|
4
|
-
type Type = "INIT_WALLET" | "SETTLE" | "GET_ADDRESS" | "GET_ADDRESS_INFO" | "GET_BALANCE" | "GET_COINS" | "GET_VTXOS" | "GET_VIRTUAL_COINS" | "GET_BOARDING_UTXOS" | "SEND_BITCOIN" | "GET_TRANSACTION_HISTORY" | "GET_STATUS" | "CLEAR";
|
|
4
|
+
type Type = "INIT_WALLET" | "SETTLE" | "GET_ADDRESS" | "GET_ADDRESS_INFO" | "GET_BALANCE" | "GET_COINS" | "GET_VTXOS" | "GET_VIRTUAL_COINS" | "GET_BOARDING_UTXOS" | "SEND_BITCOIN" | "GET_TRANSACTION_HISTORY" | "GET_STATUS" | "CLEAR" | "EXIT";
|
|
5
5
|
interface Base {
|
|
6
6
|
type: Type;
|
|
7
7
|
id: string;
|
|
@@ -65,4 +65,9 @@ export declare namespace Request {
|
|
|
65
65
|
interface Clear extends Base {
|
|
66
66
|
type: "CLEAR";
|
|
67
67
|
}
|
|
68
|
+
interface Exit extends Base {
|
|
69
|
+
type: "EXIT";
|
|
70
|
+
outpoints?: Outpoint[];
|
|
71
|
+
}
|
|
72
|
+
function isExit(message: Base): message is Exit;
|
|
68
73
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WalletBalance, Coin, VirtualCoin, ArkTransaction, AddressInfo as WalletAddressInfo, IWallet, Addresses } from "..";
|
|
2
2
|
import { SettlementEvent } from "../../providers/ark";
|
|
3
3
|
export declare namespace Response {
|
|
4
|
-
type Type = "WALLET_INITIALIZED" | "SETTLE_EVENT" | "SETTLE_SUCCESS" | "ADDRESS" | "ADDRESS_INFO" | "BALANCE" | "COINS" | "VTXOS" | "VIRTUAL_COINS" | "BOARDING_UTXOS" | "SEND_BITCOIN_SUCCESS" | "TRANSACTION_HISTORY" | "WALLET_STATUS" | "ERROR" | "CLEAR_RESPONSE";
|
|
4
|
+
type Type = "WALLET_INITIALIZED" | "SETTLE_EVENT" | "SETTLE_SUCCESS" | "ADDRESS" | "ADDRESS_INFO" | "BALANCE" | "COINS" | "VTXOS" | "VIRTUAL_COINS" | "BOARDING_UTXOS" | "SEND_BITCOIN_SUCCESS" | "TRANSACTION_HISTORY" | "WALLET_STATUS" | "ERROR" | "CLEAR_RESPONSE" | "EXIT_SUCCESS";
|
|
5
5
|
interface Base {
|
|
6
6
|
type: Type;
|
|
7
7
|
success: boolean;
|
|
@@ -104,4 +104,9 @@ export declare namespace Response {
|
|
|
104
104
|
}
|
|
105
105
|
function isClearResponse(response: Base): response is ClearResponse;
|
|
106
106
|
function clearResponse(id: string, success: boolean): ClearResponse;
|
|
107
|
+
interface ExitSuccess extends Base {
|
|
108
|
+
type: "EXIT_SUCCESS";
|
|
109
|
+
success: true;
|
|
110
|
+
}
|
|
111
|
+
function exitSuccess(id: string): ExitSuccess;
|
|
107
112
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IWallet, WalletBalance, SendBitcoinParams, SettleParams, AddressInfo, Coin, ArkTransaction, WalletConfig, ExtendedCoin, ExtendedVirtualCoin, Addresses } from "..";
|
|
1
|
+
import { IWallet, WalletBalance, SendBitcoinParams, SettleParams, AddressInfo, Coin, ArkTransaction, WalletConfig, ExtendedCoin, ExtendedVirtualCoin, Addresses, Outpoint } from "..";
|
|
2
2
|
import { Response } from "./response";
|
|
3
3
|
import { SettlementEvent } from "../../providers/ark";
|
|
4
4
|
export declare class ServiceWorkerWallet implements IWallet {
|
|
@@ -20,4 +20,5 @@ export declare class ServiceWorkerWallet implements IWallet {
|
|
|
20
20
|
sendBitcoin(params: SendBitcoinParams, zeroFee?: boolean): Promise<string>;
|
|
21
21
|
settle(params?: SettleParams, callback?: (event: SettlementEvent) => void): Promise<string>;
|
|
22
22
|
getTransactionHistory(): Promise<ArkTransaction[]>;
|
|
23
|
+
exit(outpoints?: Outpoint[]): Promise<void>;
|
|
23
24
|
}
|
|
@@ -6,7 +6,7 @@ export declare class Worker {
|
|
|
6
6
|
private arkProvider;
|
|
7
7
|
private vtxoSubscription;
|
|
8
8
|
constructor(vtxoRepository?: VtxoRepository, messageCallback?: (message: ExtendableMessageEvent) => void);
|
|
9
|
-
start(): Promise<void>;
|
|
9
|
+
start(withServiceWorkerUpdate?: boolean): Promise<void>;
|
|
10
10
|
clear(): Promise<void>;
|
|
11
11
|
private onWalletInitialized;
|
|
12
12
|
private processVtxoSubscription;
|
|
@@ -22,5 +22,6 @@ export declare class Worker {
|
|
|
22
22
|
private handleGetBoardingUtxos;
|
|
23
23
|
private handleGetTransactionHistory;
|
|
24
24
|
private handleGetStatus;
|
|
25
|
+
private handleExit;
|
|
25
26
|
private handleMessage;
|
|
26
27
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ArkAddress } from "../script/address";
|
|
2
2
|
import { DefaultVtxo } from "../script/default";
|
|
3
3
|
import { SettlementEvent } from "../providers/ark";
|
|
4
|
-
import { Addresses, AddressInfo, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, IWallet, SendBitcoinParams, SettleParams, WalletBalance, WalletConfig } from ".";
|
|
4
|
+
import { Addresses, AddressInfo, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, IWallet, Outpoint, SendBitcoinParams, SettleParams, WalletBalance, WalletConfig } from ".";
|
|
5
5
|
export declare class Wallet implements IWallet {
|
|
6
6
|
private identity;
|
|
7
7
|
private network;
|
|
@@ -36,6 +36,7 @@ export declare class Wallet implements IWallet {
|
|
|
36
36
|
private sendOnchain;
|
|
37
37
|
private sendOffchain;
|
|
38
38
|
settle(params?: SettleParams, eventCallback?: (event: SettlementEvent) => void): Promise<string>;
|
|
39
|
+
exit(outpoints?: Outpoint[]): Promise<void>;
|
|
39
40
|
private handleSettlementSigningEvent;
|
|
40
41
|
private handleSettlementSigningNoncesGeneratedEvent;
|
|
41
42
|
private handleSettlementFinalizationEvent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkade-os/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Bitcoin wallet SDK with Taproot and Ark integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"registry": "https://registry.npmjs.org/"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@noble/curves": "1.
|
|
27
|
-
"@noble/hashes": "1.
|
|
26
|
+
"@noble/curves": "1.9.1",
|
|
27
|
+
"@noble/hashes": "1.8.0",
|
|
28
28
|
"@noble/secp256k1": "2.2.3",
|
|
29
|
-
"@scure/base": "1.2.
|
|
30
|
-
"@scure/btc-signer": "1.
|
|
29
|
+
"@scure/base": "1.2.6",
|
|
30
|
+
"@scure/btc-signer": "1.8.1",
|
|
31
31
|
"bip68": "1.0.4"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@types/node": "22.10.2",
|
|
37
37
|
"@typescript-eslint/eslint-plugin": "8.18.2",
|
|
38
38
|
"@typescript-eslint/parser": "8.18.2",
|
|
39
|
-
"@vitest/coverage-v8": "2.1.
|
|
39
|
+
"@vitest/coverage-v8": "2.1.9",
|
|
40
40
|
"esbuild": "^0.20.1",
|
|
41
41
|
"eslint": "^9.17.0",
|
|
42
42
|
"glob": "11.0.1",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"lint-staged": "15.3.0",
|
|
45
45
|
"prettier": "3.4.2",
|
|
46
46
|
"typescript": "5.7.2",
|
|
47
|
-
"vitest": "2.1.
|
|
47
|
+
"vitest": "2.1.9"
|
|
48
48
|
},
|
|
49
49
|
"keywords": [
|
|
50
50
|
"bitcoin",
|