@arkade-os/sdk 0.3.0-alpha.6 → 0.3.0-alpha.8
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 +51 -0
- package/dist/cjs/adapters/expo.js +8 -0
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/providers/expoArk.js +237 -0
- package/dist/cjs/providers/expoIndexer.js +194 -0
- package/dist/cjs/providers/indexer.js +3 -1
- package/dist/cjs/script/base.js +16 -95
- package/dist/cjs/utils/arkTransaction.js +13 -0
- package/dist/cjs/wallet/index.js +1 -1
- package/dist/cjs/wallet/serviceWorker/utils.js +0 -9
- package/dist/cjs/wallet/serviceWorker/worker.js +14 -17
- package/dist/cjs/wallet/utils.js +11 -0
- package/dist/cjs/wallet/wallet.js +69 -51
- package/dist/esm/adapters/expo.js +3 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/providers/expoArk.js +200 -0
- package/dist/esm/providers/expoIndexer.js +157 -0
- package/dist/esm/providers/indexer.js +3 -1
- package/dist/esm/script/base.js +13 -92
- package/dist/esm/utils/arkTransaction.js +13 -1
- package/dist/esm/wallet/index.js +1 -1
- package/dist/esm/wallet/serviceWorker/utils.js +0 -8
- package/dist/esm/wallet/serviceWorker/worker.js +15 -18
- package/dist/esm/wallet/utils.js +8 -0
- package/dist/esm/wallet/wallet.js +70 -52
- package/dist/types/adapters/expo.d.ts +4 -0
- package/dist/types/index.d.ts +5 -5
- package/dist/types/providers/ark.d.ts +136 -2
- package/dist/types/providers/expoArk.d.ts +22 -0
- package/dist/types/providers/expoIndexer.d.ts +26 -0
- package/dist/types/providers/indexer.d.ts +8 -0
- package/dist/types/utils/arkTransaction.d.ts +3 -1
- package/dist/types/wallet/index.d.ts +44 -6
- package/dist/types/wallet/serviceWorker/utils.d.ts +0 -2
- package/dist/types/wallet/utils.d.ts +2 -0
- package/dist/types/wallet/wallet.d.ts +9 -1
- package/package.json +11 -2
package/dist/cjs/script/base.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VtxoScript = void 0;
|
|
4
4
|
exports.scriptFromTapLeafScript = scriptFromTapLeafScript;
|
|
5
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
5
6
|
const payment_js_1 = require("@scure/btc-signer/payment.js");
|
|
7
|
+
const psbt_js_1 = require("@scure/btc-signer/psbt.js");
|
|
6
8
|
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
7
|
-
const address_1 = require("./address");
|
|
8
|
-
const script_js_1 = require("@scure/btc-signer/script.js");
|
|
9
9
|
const base_1 = require("@scure/base");
|
|
10
|
+
const address_1 = require("./address");
|
|
10
11
|
const tapscript_1 = require("./tapscript");
|
|
12
|
+
const TapTreeCoder = psbt_js_1.PSBTOutput.tapTree[2];
|
|
11
13
|
function scriptFromTapLeafScript(leaf) {
|
|
12
14
|
return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte
|
|
13
15
|
}
|
|
@@ -21,13 +23,14 @@ function scriptFromTapLeafScript(leaf) {
|
|
|
21
23
|
*/
|
|
22
24
|
class VtxoScript {
|
|
23
25
|
static decode(tapTree) {
|
|
24
|
-
const leaves =
|
|
25
|
-
|
|
26
|
+
const leaves = TapTreeCoder.decode(tapTree);
|
|
27
|
+
const scripts = leaves.map((leaf) => leaf.script);
|
|
28
|
+
return new VtxoScript(scripts);
|
|
26
29
|
}
|
|
27
30
|
constructor(scripts) {
|
|
28
31
|
this.scripts = scripts;
|
|
29
|
-
const tapTree = (0,
|
|
30
|
-
const payment = (0,
|
|
32
|
+
const tapTree = (0, btc_signer_1.taprootListToTree)(scripts.map((script) => ({ script, leafVersion: payment_js_1.TAP_LEAF_VERSION })));
|
|
33
|
+
const payment = (0, btc_signer_1.p2tr)(utils_js_1.TAPROOT_UNSPENDABLE_KEY, tapTree, undefined, true);
|
|
31
34
|
if (!payment.tapLeafScript ||
|
|
32
35
|
payment.tapLeafScript.length !== scripts.length) {
|
|
33
36
|
throw new Error("invalid scripts");
|
|
@@ -36,17 +39,21 @@ class VtxoScript {
|
|
|
36
39
|
this.tweakedPublicKey = payment.tweakedPubkey;
|
|
37
40
|
}
|
|
38
41
|
encode() {
|
|
39
|
-
const tapTree =
|
|
42
|
+
const tapTree = TapTreeCoder.encode(this.scripts.map((script) => ({
|
|
43
|
+
depth: 1,
|
|
44
|
+
version: payment_js_1.TAP_LEAF_VERSION,
|
|
45
|
+
script,
|
|
46
|
+
})));
|
|
40
47
|
return tapTree;
|
|
41
48
|
}
|
|
42
49
|
address(prefix, serverPubKey) {
|
|
43
50
|
return new address_1.ArkAddress(serverPubKey, this.tweakedPublicKey, prefix);
|
|
44
51
|
}
|
|
45
52
|
get pkScript() {
|
|
46
|
-
return
|
|
53
|
+
return btc_signer_1.Script.encode(["OP_1", this.tweakedPublicKey]);
|
|
47
54
|
}
|
|
48
55
|
onchainAddress(network) {
|
|
49
|
-
return (0,
|
|
56
|
+
return (0, btc_signer_1.Address)(network).encode({
|
|
50
57
|
type: "tr",
|
|
51
58
|
pubkey: this.tweakedPublicKey,
|
|
52
59
|
});
|
|
@@ -80,89 +87,3 @@ class VtxoScript {
|
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
89
|
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
|
-
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildOffchainTx = buildOffchainTx;
|
|
4
|
+
exports.hasBoardingTxExpired = hasBoardingTxExpired;
|
|
4
5
|
const transaction_js_1 = require("@scure/btc-signer/transaction.js");
|
|
5
6
|
const tapscript_1 = require("../script/tapscript");
|
|
6
7
|
const base_1 = require("../script/base");
|
|
@@ -106,3 +107,15 @@ const nLocktimeMinSeconds = 500000000n;
|
|
|
106
107
|
function isSeconds(locktime) {
|
|
107
108
|
return locktime >= nLocktimeMinSeconds;
|
|
108
109
|
}
|
|
110
|
+
function hasBoardingTxExpired(coin, boardingTimelock) {
|
|
111
|
+
if (!coin.status.block_time)
|
|
112
|
+
return false;
|
|
113
|
+
if (boardingTimelock.value === 0n)
|
|
114
|
+
return true;
|
|
115
|
+
if (boardingTimelock.type !== "blocks")
|
|
116
|
+
return false; // TODO: handle get chain tip
|
|
117
|
+
// validate expiry in terms of seconds
|
|
118
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
119
|
+
const blockTime = BigInt(Math.floor(coin.status.block_time));
|
|
120
|
+
return blockTime + boardingTimelock.value <= now;
|
|
121
|
+
}
|
package/dist/cjs/wallet/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var TxType;
|
|
|
10
10
|
TxType["TxReceived"] = "RECEIVED";
|
|
11
11
|
})(TxType || (exports.TxType = TxType = {}));
|
|
12
12
|
function isSpendable(vtxo) {
|
|
13
|
-
return vtxo.
|
|
13
|
+
return !vtxo.isSpent;
|
|
14
14
|
}
|
|
15
15
|
function isRecoverable(vtxo) {
|
|
16
16
|
return vtxo.virtualStatus.state === "swept" && isSpendable(vtxo);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.setupServiceWorker = setupServiceWorker;
|
|
4
|
-
exports.extendVirtualCoin = extendVirtualCoin;
|
|
5
4
|
/**
|
|
6
5
|
* setupServiceWorker sets up the service worker.
|
|
7
6
|
* @param path - the path to the service worker script
|
|
@@ -48,11 +47,3 @@ async function setupServiceWorker(path) {
|
|
|
48
47
|
navigator.serviceWorker.addEventListener("error", onError);
|
|
49
48
|
});
|
|
50
49
|
}
|
|
51
|
-
function extendVirtualCoin(wallet, vtxo) {
|
|
52
|
-
return {
|
|
53
|
-
...vtxo,
|
|
54
|
-
forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
|
|
55
|
-
intentTapLeafScript: wallet.offchainTapscript.exit(),
|
|
56
|
-
tapTree: wallet.offchainTapscript.encode(),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
@@ -13,7 +13,7 @@ const indexer_1 = require("../../providers/indexer");
|
|
|
13
13
|
const base_1 = require("@scure/base");
|
|
14
14
|
const indexedDB_1 = require("../../storage/indexedDB");
|
|
15
15
|
const walletRepository_1 = require("../../repositories/walletRepository");
|
|
16
|
-
const utils_1 = require("
|
|
16
|
+
const utils_1 = require("../utils");
|
|
17
17
|
/**
|
|
18
18
|
* Worker is a class letting to interact with ServiceWorkerWallet from the client
|
|
19
19
|
* it aims to be run in a service worker context
|
|
@@ -77,6 +77,8 @@ class Worker {
|
|
|
77
77
|
this.incomingFundsSubscription();
|
|
78
78
|
// Clear storage - this replaces vtxoRepository.close()
|
|
79
79
|
await this.storage.clear();
|
|
80
|
+
// Reset in-memory caches by recreating the repository
|
|
81
|
+
this.walletRepository = new walletRepository_1.WalletRepositoryImpl(this.storage);
|
|
80
82
|
this.wallet = undefined;
|
|
81
83
|
this.arkProvider = undefined;
|
|
82
84
|
this.indexerProvider = undefined;
|
|
@@ -105,9 +107,6 @@ class Worker {
|
|
|
105
107
|
const txs = await this.wallet.getTransactionHistory();
|
|
106
108
|
if (txs)
|
|
107
109
|
await this.walletRepository.saveTransactions(address, txs);
|
|
108
|
-
// stop previous subscriptions if any
|
|
109
|
-
if (this.incomingFundsSubscription)
|
|
110
|
-
this.incomingFundsSubscription();
|
|
111
110
|
// subscribe for incoming funds and notify all clients when new funds arrive
|
|
112
111
|
this.incomingFundsSubscription = await this.wallet.notifyIncomingFunds(async (funds) => {
|
|
113
112
|
if (funds.type === "vtxo") {
|
|
@@ -127,7 +126,7 @@ class Worker {
|
|
|
127
126
|
// notify all clients about the vtxo update
|
|
128
127
|
this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
|
|
129
128
|
}
|
|
130
|
-
if (funds.type === "utxo"
|
|
129
|
+
if (funds.type === "utxo") {
|
|
131
130
|
// notify all clients about the utxo update
|
|
132
131
|
this.sendMessageToAllClients("UTXO_UPDATE", JSON.stringify(funds.coins));
|
|
133
132
|
}
|
|
@@ -358,17 +357,16 @@ class Worker {
|
|
|
358
357
|
if (!message.filter?.withRecoverable) {
|
|
359
358
|
if (!this.wallet)
|
|
360
359
|
throw new Error("Wallet not initialized");
|
|
361
|
-
// exclude subdust
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
dustAmount == null
|
|
365
|
-
?
|
|
366
|
-
:
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
vtxos.push(...sweptVtxos.filter(__1.isSpendable));
|
|
360
|
+
// exclude subdust and recoverable if we don't want recoverable
|
|
361
|
+
const notSubdust = (v) => {
|
|
362
|
+
const dustAmount = this.wallet?.dustAmount;
|
|
363
|
+
return dustAmount == null
|
|
364
|
+
? true
|
|
365
|
+
: !(0, __1.isSubdust)(v, dustAmount);
|
|
366
|
+
};
|
|
367
|
+
vtxos = vtxos
|
|
368
|
+
.filter(notSubdust)
|
|
369
|
+
.filter((v) => !(0, __1.isRecoverable)(v));
|
|
372
370
|
}
|
|
373
371
|
event.source?.postMessage(response_1.Response.vtxos(message.id, vtxos));
|
|
374
372
|
}
|
|
@@ -529,7 +527,6 @@ class Worker {
|
|
|
529
527
|
}
|
|
530
528
|
async handleReloadWallet(event) {
|
|
531
529
|
const message = event.data;
|
|
532
|
-
console.log("RELOAD_WALLET message received", message);
|
|
533
530
|
if (!request_1.Request.isReloadWallet(message)) {
|
|
534
531
|
console.error("Invalid RELOAD_WALLET message format", message);
|
|
535
532
|
event.source?.postMessage(response_1.Response.error(message.id, "Invalid RELOAD_WALLET message format"));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extendVirtualCoin = extendVirtualCoin;
|
|
4
|
+
function extendVirtualCoin(wallet, vtxo) {
|
|
5
|
+
return {
|
|
6
|
+
...vtxo,
|
|
7
|
+
forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
|
|
8
|
+
intentTapLeafScript: wallet.offchainTapscript.exit(),
|
|
9
|
+
tapTree: wallet.offchainTapscript.encode(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -60,7 +60,7 @@ const txTree_1 = require("../tree/txTree");
|
|
|
60
60
|
const inMemory_1 = require("../storage/inMemory");
|
|
61
61
|
const walletRepository_1 = require("../repositories/walletRepository");
|
|
62
62
|
const contractRepository_1 = require("../repositories/contractRepository");
|
|
63
|
-
const utils_1 = require("./
|
|
63
|
+
const utils_1 = require("./utils");
|
|
64
64
|
/**
|
|
65
65
|
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|
|
66
66
|
* The wallet does not store any data locally and relies on Ark and onchain
|
|
@@ -68,13 +68,21 @@ const utils_1 = require("./serviceWorker/utils");
|
|
|
68
68
|
*
|
|
69
69
|
* @example
|
|
70
70
|
* ```typescript
|
|
71
|
-
* // Create a wallet
|
|
71
|
+
* // Create a wallet with URL configuration
|
|
72
72
|
* const wallet = await Wallet.create({
|
|
73
73
|
* identity: SingleKey.fromHex('your_private_key'),
|
|
74
74
|
* arkServerUrl: 'https://ark.example.com',
|
|
75
75
|
* esploraUrl: 'https://mempool.space/api'
|
|
76
76
|
* });
|
|
77
77
|
*
|
|
78
|
+
* // Or with custom provider instances (e.g., for Expo/React Native)
|
|
79
|
+
* const wallet = await Wallet.create({
|
|
80
|
+
* identity: SingleKey.fromHex('your_private_key'),
|
|
81
|
+
* arkProvider: new ExpoArkProvider('https://ark.example.com'),
|
|
82
|
+
* indexerProvider: new ExpoIndexerProvider('https://ark.example.com'),
|
|
83
|
+
* esploraUrl: 'https://mempool.space/api'
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
78
86
|
* // Get addresses
|
|
79
87
|
* const arkAddress = await wallet.getAddress();
|
|
80
88
|
* const boardingAddress = await wallet.getBoardingAddress();
|
|
@@ -108,11 +116,29 @@ class Wallet {
|
|
|
108
116
|
if (!pubkey) {
|
|
109
117
|
throw new Error("Invalid configured public key");
|
|
110
118
|
}
|
|
111
|
-
|
|
112
|
-
const
|
|
119
|
+
// Use provided arkProvider instance or create a new one from arkServerUrl
|
|
120
|
+
const arkProvider = config.arkProvider ||
|
|
121
|
+
(() => {
|
|
122
|
+
if (!config.arkServerUrl) {
|
|
123
|
+
throw new Error("Either arkProvider or arkServerUrl must be provided");
|
|
124
|
+
}
|
|
125
|
+
return new ark_1.RestArkProvider(config.arkServerUrl);
|
|
126
|
+
})();
|
|
127
|
+
// Extract arkServerUrl from provider if not explicitly provided
|
|
128
|
+
const arkServerUrl = config.arkServerUrl || arkProvider.serverUrl;
|
|
129
|
+
if (!arkServerUrl) {
|
|
130
|
+
throw new Error("Could not determine arkServerUrl from provider");
|
|
131
|
+
}
|
|
132
|
+
// Use provided indexerProvider instance or create a new one
|
|
133
|
+
// indexerUrl defaults to arkServerUrl if not provided
|
|
134
|
+
const indexerUrl = config.indexerUrl || arkServerUrl;
|
|
135
|
+
const indexerProvider = config.indexerProvider || new indexer_1.RestIndexerProvider(indexerUrl);
|
|
113
136
|
const info = await arkProvider.getInfo();
|
|
114
137
|
const network = (0, networks_1.getNetwork)(info.network);
|
|
115
|
-
|
|
138
|
+
// Extract esploraUrl from provider if not explicitly provided
|
|
139
|
+
const esploraUrl = config.esploraUrl || onchain_1.ESPLORA_URL[info.network];
|
|
140
|
+
// Use provided onchainProvider instance or create a new one
|
|
141
|
+
const onchainProvider = config.onchainProvider || new onchain_1.EsploraProvider(esploraUrl);
|
|
116
142
|
const exitTimelock = {
|
|
117
143
|
value: info.unilateralExitDelay,
|
|
118
144
|
type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
|
|
@@ -136,8 +162,14 @@ class Wallet {
|
|
|
136
162
|
// Save tapscripts
|
|
137
163
|
const offchainTapscript = bareVtxoTapscript;
|
|
138
164
|
// the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
|
|
139
|
-
|
|
140
|
-
|
|
165
|
+
let serverUnrollScript;
|
|
166
|
+
try {
|
|
167
|
+
const raw = base_1.hex.decode(info.checkpointExitClosure);
|
|
168
|
+
serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(raw);
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
throw new Error("Invalid checkpointExitClosure from server");
|
|
172
|
+
}
|
|
141
173
|
// parse the server forfeit address
|
|
142
174
|
// server is expecting funds to be sent to this address
|
|
143
175
|
const forfeitAddress = (0, payment_js_1.Address)(network).decode(info.forfeitAddress);
|
|
@@ -208,40 +240,24 @@ class Wallet {
|
|
|
208
240
|
// if (cachedVtxos.length) return cachedVtxos;
|
|
209
241
|
// For now, always fetch fresh data from provider and update cache
|
|
210
242
|
// In future, we can add cache invalidation logic based on timestamps
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
const forfeit = this.offchainTapscript.forfeit();
|
|
214
|
-
const exit = this.offchainTapscript.exit();
|
|
215
|
-
const extendedVtxos = spendableVtxos.map((vtxo) => ({
|
|
216
|
-
...vtxo,
|
|
217
|
-
forfeitTapLeafScript: forfeit,
|
|
218
|
-
intentTapLeafScript: exit,
|
|
219
|
-
tapTree: encodedOffchainTapscript,
|
|
220
|
-
}));
|
|
243
|
+
const vtxos = await this.getVirtualCoins(filter);
|
|
244
|
+
const extendedVtxos = vtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo));
|
|
221
245
|
// Update cache with fresh data
|
|
222
246
|
await this.walletRepository.saveVtxos(address, extendedVtxos);
|
|
223
247
|
return extendedVtxos;
|
|
224
248
|
}
|
|
225
249
|
async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
|
|
226
250
|
const scripts = [base_1.hex.encode(this.offchainTapscript.pkScript)];
|
|
227
|
-
const response = await this.indexerProvider.getVtxos({
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const response = await this.indexerProvider.getVtxos({
|
|
234
|
-
scripts,
|
|
235
|
-
recoverableOnly: true,
|
|
236
|
-
});
|
|
237
|
-
vtxos.push(...response.vtxos);
|
|
251
|
+
const response = await this.indexerProvider.getVtxos({ scripts });
|
|
252
|
+
const allVtxos = response.vtxos;
|
|
253
|
+
let vtxos = allVtxos.filter(_1.isSpendable);
|
|
254
|
+
// all recoverable vtxos are spendable by definition
|
|
255
|
+
if (!filter.withRecoverable) {
|
|
256
|
+
vtxos = vtxos.filter((vtxo) => !(0, _1.isRecoverable)(vtxo));
|
|
238
257
|
}
|
|
239
258
|
if (filter.withUnrolled) {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
spentOnly: true,
|
|
243
|
-
});
|
|
244
|
-
vtxos.push(...response.vtxos.filter((vtxo) => vtxo.isUnrolled));
|
|
259
|
+
const spentVtxos = allVtxos.filter((vtxo) => !(0, _1.isSpendable)(vtxo));
|
|
260
|
+
vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
|
|
245
261
|
}
|
|
246
262
|
return vtxos;
|
|
247
263
|
}
|
|
@@ -279,10 +295,10 @@ class Wallet {
|
|
|
279
295
|
return txs;
|
|
280
296
|
}
|
|
281
297
|
async getBoardingTxs() {
|
|
282
|
-
const boardingAddress = await this.getBoardingAddress();
|
|
283
|
-
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
284
298
|
const utxos = [];
|
|
285
299
|
const commitmentsToIgnore = new Set();
|
|
300
|
+
const boardingAddress = await this.getBoardingAddress();
|
|
301
|
+
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
286
302
|
for (const tx of txs) {
|
|
287
303
|
for (let i = 0; i < tx.vout.length; i++) {
|
|
288
304
|
const vout = tx.vout[i];
|
|
@@ -423,13 +439,15 @@ class Wallet {
|
|
|
423
439
|
}
|
|
424
440
|
}
|
|
425
441
|
}
|
|
426
|
-
// if no params are provided, use all boarding and offchain
|
|
442
|
+
// if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
|
|
427
443
|
// and send all to the offchain address
|
|
428
444
|
if (!params) {
|
|
429
445
|
let amount = 0;
|
|
430
|
-
const
|
|
446
|
+
const exitScript = tapscript_1.CSVMultisigTapscript.decode(base_1.hex.decode(this.boardingTapscript.exitScript));
|
|
447
|
+
const boardingTimelock = exitScript.params.timelock;
|
|
448
|
+
const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !(0, arkTransaction_1.hasBoardingTxExpired)(utxo, boardingTimelock));
|
|
431
449
|
amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
|
|
432
|
-
const vtxos = await this.getVtxos();
|
|
450
|
+
const vtxos = await this.getVtxos({ withRecoverable: true });
|
|
433
451
|
amount += vtxos.reduce((sum, input) => sum + input.value, 0);
|
|
434
452
|
const inputs = [...boardingUtxos, ...vtxos];
|
|
435
453
|
if (inputs.length === 0) {
|
|
@@ -639,22 +657,22 @@ class Wallet {
|
|
|
639
657
|
let onchainStopFunc;
|
|
640
658
|
let indexerStopFunc;
|
|
641
659
|
if (this.onchainProvider && boardingAddress) {
|
|
660
|
+
const findVoutOnTx = (tx) => {
|
|
661
|
+
return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
|
|
662
|
+
};
|
|
642
663
|
onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
|
|
664
|
+
// find all utxos belonging to our boarding address
|
|
643
665
|
const coins = txs
|
|
666
|
+
// filter txs where address is in output
|
|
667
|
+
.filter((tx) => findVoutOnTx(tx) !== -1)
|
|
668
|
+
// return utxo as Coin
|
|
644
669
|
.map((tx) => {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
txid: tx.txid,
|
|
652
|
-
vout,
|
|
653
|
-
value: Number(tx.vout[vout].value),
|
|
654
|
-
status: tx.status,
|
|
655
|
-
};
|
|
656
|
-
})
|
|
657
|
-
.filter((coin) => coin !== null);
|
|
670
|
+
const { txid, status } = tx;
|
|
671
|
+
const vout = findVoutOnTx(tx);
|
|
672
|
+
const value = Number(tx.vout[vout].value);
|
|
673
|
+
return { txid, vout, value, status };
|
|
674
|
+
});
|
|
675
|
+
// and notify via callback
|
|
658
676
|
eventCallback({
|
|
659
677
|
type: "utxo",
|
|
660
678
|
coins,
|
package/dist/esm/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { Response } from './wallet/serviceWorker/response.js';
|
|
|
17
17
|
import { ESPLORA_URL, EsploraProvider, } from './providers/onchain.js';
|
|
18
18
|
import { RestArkProvider, SettlementEventType, } from './providers/ark.js';
|
|
19
19
|
import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, } from './script/tapscript.js';
|
|
20
|
-
import { buildOffchainTx, } from './utils/arkTransaction.js';
|
|
20
|
+
import { hasBoardingTxExpired, buildOffchainTx, } from './utils/arkTransaction.js';
|
|
21
21
|
import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry, } from './utils/unknownFields.js';
|
|
22
22
|
import { BIP322 } from './bip322/index.js';
|
|
23
23
|
import { ArkNote } from './arknote/index.js';
|
|
@@ -43,7 +43,7 @@ decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTa
|
|
|
43
43
|
// Ark PSBT fields
|
|
44
44
|
ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness,
|
|
45
45
|
// Utils
|
|
46
|
-
buildOffchainTx, waitForIncomingFunds,
|
|
46
|
+
buildOffchainTx, waitForIncomingFunds, hasBoardingTxExpired,
|
|
47
47
|
// Arknote
|
|
48
48
|
ArkNote,
|
|
49
49
|
// Network
|