@arkade-os/sdk 0.3.0-alpha.5 → 0.3.0-alpha.7
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/dist/cjs/repositories/walletRepository.js +4 -4
- package/dist/cjs/script/base.js +16 -95
- package/dist/cjs/wallet/serviceWorker/request.js +8 -0
- package/dist/cjs/wallet/serviceWorker/response.js +12 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +12 -4
- package/dist/cjs/wallet/serviceWorker/worker.js +40 -5
- package/dist/cjs/wallet/wallet.js +2 -1
- package/dist/esm/repositories/walletRepository.js +4 -4
- package/dist/esm/script/base.js +13 -92
- package/dist/esm/wallet/serviceWorker/request.js +8 -0
- package/dist/esm/wallet/serviceWorker/response.js +12 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +12 -4
- package/dist/esm/wallet/serviceWorker/worker.js +40 -5
- package/dist/esm/wallet/wallet.js +2 -1
- package/dist/types/wallet/serviceWorker/request.d.ts +6 -1
- package/dist/types/wallet/serviceWorker/response.d.ts +6 -1
- package/dist/types/wallet/serviceWorker/wallet.d.ts +1 -0
- package/dist/types/wallet/serviceWorker/worker.d.ts +1 -0
- package/dist/types/wallet/wallet.d.ts +2 -1
- package/package.json +1 -1
|
@@ -52,7 +52,7 @@ class WalletRepositoryImpl {
|
|
|
52
52
|
try {
|
|
53
53
|
const parsed = JSON.parse(stored);
|
|
54
54
|
const vtxos = parsed.map(deserializeVtxo);
|
|
55
|
-
this.cache.vtxos.set(address, vtxos);
|
|
55
|
+
this.cache.vtxos.set(address, vtxos.slice());
|
|
56
56
|
return vtxos.slice();
|
|
57
57
|
}
|
|
58
58
|
catch (error) {
|
|
@@ -70,7 +70,7 @@ class WalletRepositoryImpl {
|
|
|
70
70
|
else {
|
|
71
71
|
vtxos.push(vtxo);
|
|
72
72
|
}
|
|
73
|
-
this.cache.vtxos.set(address, vtxos);
|
|
73
|
+
this.cache.vtxos.set(address, vtxos.slice());
|
|
74
74
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos.map(serializeVtxo)));
|
|
75
75
|
}
|
|
76
76
|
async saveVtxos(address, vtxos) {
|
|
@@ -84,14 +84,14 @@ class WalletRepositoryImpl {
|
|
|
84
84
|
storedVtxos.push(vtxo);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
this.cache.vtxos.set(address, storedVtxos);
|
|
87
|
+
this.cache.vtxos.set(address, storedVtxos.slice());
|
|
88
88
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(storedVtxos.map(serializeVtxo)));
|
|
89
89
|
}
|
|
90
90
|
async removeVtxo(address, vtxoId) {
|
|
91
91
|
const vtxos = await this.getVtxos(address);
|
|
92
92
|
const [txid, vout] = vtxoId.split(":");
|
|
93
93
|
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
94
|
-
this.cache.vtxos.set(address, filtered);
|
|
94
|
+
this.cache.vtxos.set(address, filtered.slice());
|
|
95
95
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(filtered.map(serializeVtxo)));
|
|
96
96
|
}
|
|
97
97
|
async clearVtxos(address) {
|
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
|
-
}
|
|
@@ -69,4 +69,12 @@ var Request;
|
|
|
69
69
|
return message.type === "GET_STATUS";
|
|
70
70
|
}
|
|
71
71
|
Request.isGetStatus = isGetStatus;
|
|
72
|
+
function isClear(message) {
|
|
73
|
+
return message.type === "CLEAR";
|
|
74
|
+
}
|
|
75
|
+
Request.isClear = isClear;
|
|
76
|
+
function isReloadWallet(message) {
|
|
77
|
+
return message.type === "RELOAD_WALLET";
|
|
78
|
+
}
|
|
79
|
+
Request.isReloadWallet = isReloadWallet;
|
|
72
80
|
})(Request || (exports.Request = Request = {}));
|
|
@@ -175,4 +175,16 @@ var Response;
|
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
177
|
Response.clearResponse = clearResponse;
|
|
178
|
+
function isWalletReloaded(response) {
|
|
179
|
+
return response.type === "WALLET_RELOADED";
|
|
180
|
+
}
|
|
181
|
+
Response.isWalletReloaded = isWalletReloaded;
|
|
182
|
+
function walletReloaded(id, success) {
|
|
183
|
+
return {
|
|
184
|
+
type: "WALLET_RELOADED",
|
|
185
|
+
success,
|
|
186
|
+
id,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
Response.walletReloaded = walletReloaded;
|
|
178
190
|
})(Response || (exports.Response = Response = {}));
|
|
@@ -8,10 +8,7 @@ const walletRepository_1 = require("../../repositories/walletRepository");
|
|
|
8
8
|
const contractRepository_1 = require("../../repositories/contractRepository");
|
|
9
9
|
const utils_1 = require("./utils");
|
|
10
10
|
const isPrivateKeyIdentity = (identity) => {
|
|
11
|
-
return
|
|
12
|
-
typeof identity.toHex === "function" &&
|
|
13
|
-
typeof identity.toHex() === "string" &&
|
|
14
|
-
identity.toHex().length > 0);
|
|
11
|
+
return typeof identity.toHex === "function";
|
|
15
12
|
};
|
|
16
13
|
class UnexpectedResponseError extends Error {
|
|
17
14
|
constructor(response) {
|
|
@@ -290,6 +287,17 @@ class ServiceWorkerWallet {
|
|
|
290
287
|
throw new Error(`Settlement failed: ${error}`);
|
|
291
288
|
}
|
|
292
289
|
}
|
|
290
|
+
async reload() {
|
|
291
|
+
const message = {
|
|
292
|
+
type: "RELOAD_WALLET",
|
|
293
|
+
id: getRandomId(),
|
|
294
|
+
};
|
|
295
|
+
const response = await this.sendMessage(message);
|
|
296
|
+
if (response_1.Response.isWalletReloaded(response)) {
|
|
297
|
+
return response.success;
|
|
298
|
+
}
|
|
299
|
+
throw new UnexpectedResponseError(response);
|
|
300
|
+
}
|
|
293
301
|
}
|
|
294
302
|
exports.ServiceWorkerWallet = ServiceWorkerWallet;
|
|
295
303
|
function getRandomId() {
|
|
@@ -110,13 +110,22 @@ class Worker {
|
|
|
110
110
|
this.incomingFundsSubscription();
|
|
111
111
|
// subscribe for incoming funds and notify all clients when new funds arrive
|
|
112
112
|
this.incomingFundsSubscription = await this.wallet.notifyIncomingFunds(async (funds) => {
|
|
113
|
-
if (funds.type === "vtxo"
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
if (funds.type === "vtxo") {
|
|
114
|
+
const newVtxos = funds.newVtxos.length > 0
|
|
115
|
+
? funds.newVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.wallet, vtxo))
|
|
116
|
+
: [];
|
|
117
|
+
const spentVtxos = funds.spentVtxos.length > 0
|
|
118
|
+
? funds.spentVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.wallet, vtxo))
|
|
119
|
+
: [];
|
|
120
|
+
if ([...newVtxos, ...spentVtxos].length === 0)
|
|
121
|
+
return;
|
|
116
122
|
// save vtxos using unified repository
|
|
117
|
-
await this.walletRepository.saveVtxos(address,
|
|
123
|
+
await this.walletRepository.saveVtxos(address, [
|
|
124
|
+
...newVtxos,
|
|
125
|
+
...spentVtxos,
|
|
126
|
+
]);
|
|
118
127
|
// notify all clients about the vtxo update
|
|
119
|
-
this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify(
|
|
128
|
+
this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
|
|
120
129
|
}
|
|
121
130
|
if (funds.type === "utxo" && funds.coins.length > 0) {
|
|
122
131
|
// notify all clients about the utxo update
|
|
@@ -498,6 +507,10 @@ class Worker {
|
|
|
498
507
|
await this.handleClear(event);
|
|
499
508
|
break;
|
|
500
509
|
}
|
|
510
|
+
case "RELOAD_WALLET": {
|
|
511
|
+
await this.handleReloadWallet(event);
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
501
514
|
default:
|
|
502
515
|
event.source?.postMessage(response_1.Response.error(message.id, "Unknown message type"));
|
|
503
516
|
}
|
|
@@ -514,5 +527,27 @@ class Worker {
|
|
|
514
527
|
});
|
|
515
528
|
});
|
|
516
529
|
}
|
|
530
|
+
async handleReloadWallet(event) {
|
|
531
|
+
const message = event.data;
|
|
532
|
+
console.log("RELOAD_WALLET message received", message);
|
|
533
|
+
if (!request_1.Request.isReloadWallet(message)) {
|
|
534
|
+
console.error("Invalid RELOAD_WALLET message format", message);
|
|
535
|
+
event.source?.postMessage(response_1.Response.error(message.id, "Invalid RELOAD_WALLET message format"));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (!this.wallet) {
|
|
539
|
+
console.error("Wallet not initialized");
|
|
540
|
+
event.source?.postMessage(response_1.Response.walletReloaded(message.id, false));
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
await this.onWalletInitialized();
|
|
545
|
+
event.source?.postMessage(response_1.Response.walletReloaded(message.id, true));
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
console.error("Error reloading wallet:", error);
|
|
549
|
+
event.source?.postMessage(response_1.Response.walletReloaded(message.id, false));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
517
552
|
}
|
|
518
553
|
exports.Worker = Worker;
|
|
@@ -679,7 +679,8 @@ class Wallet {
|
|
|
679
679
|
if (update.newVtxos?.length > 0) {
|
|
680
680
|
eventCallback({
|
|
681
681
|
type: "vtxo",
|
|
682
|
-
|
|
682
|
+
newVtxos: update.newVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
683
|
+
spentVtxos: update.spentVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
683
684
|
});
|
|
684
685
|
}
|
|
685
686
|
}
|
|
@@ -49,7 +49,7 @@ export class WalletRepositoryImpl {
|
|
|
49
49
|
try {
|
|
50
50
|
const parsed = JSON.parse(stored);
|
|
51
51
|
const vtxos = parsed.map(deserializeVtxo);
|
|
52
|
-
this.cache.vtxos.set(address, vtxos);
|
|
52
|
+
this.cache.vtxos.set(address, vtxos.slice());
|
|
53
53
|
return vtxos.slice();
|
|
54
54
|
}
|
|
55
55
|
catch (error) {
|
|
@@ -67,7 +67,7 @@ export class WalletRepositoryImpl {
|
|
|
67
67
|
else {
|
|
68
68
|
vtxos.push(vtxo);
|
|
69
69
|
}
|
|
70
|
-
this.cache.vtxos.set(address, vtxos);
|
|
70
|
+
this.cache.vtxos.set(address, vtxos.slice());
|
|
71
71
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos.map(serializeVtxo)));
|
|
72
72
|
}
|
|
73
73
|
async saveVtxos(address, vtxos) {
|
|
@@ -81,14 +81,14 @@ export class WalletRepositoryImpl {
|
|
|
81
81
|
storedVtxos.push(vtxo);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
this.cache.vtxos.set(address, storedVtxos);
|
|
84
|
+
this.cache.vtxos.set(address, storedVtxos.slice());
|
|
85
85
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(storedVtxos.map(serializeVtxo)));
|
|
86
86
|
}
|
|
87
87
|
async removeVtxo(address, vtxoId) {
|
|
88
88
|
const vtxos = await this.getVtxos(address);
|
|
89
89
|
const [txid, vout] = vtxoId.split(":");
|
|
90
90
|
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
91
|
-
this.cache.vtxos.set(address, filtered);
|
|
91
|
+
this.cache.vtxos.set(address, filtered.slice());
|
|
92
92
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(filtered.map(serializeVtxo)));
|
|
93
93
|
}
|
|
94
94
|
async clearVtxos(address) {
|
package/dist/esm/script/base.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Address, p2tr,
|
|
1
|
+
import { Script, Address, p2tr, taprootListToTree } from "@scure/btc-signer";
|
|
2
|
+
import { TAP_LEAF_VERSION } from "@scure/btc-signer/payment.js";
|
|
3
|
+
import { PSBTOutput } from "@scure/btc-signer/psbt.js";
|
|
2
4
|
import { TAPROOT_UNSPENDABLE_KEY, } from "@scure/btc-signer/utils.js";
|
|
3
|
-
import { ArkAddress } from './address.js';
|
|
4
|
-
import { Script } from "@scure/btc-signer/script.js";
|
|
5
5
|
import { hex } from "@scure/base";
|
|
6
|
+
import { ArkAddress } from './address.js';
|
|
6
7
|
import { ConditionCSVMultisigTapscript, CSVMultisigTapscript, } from './tapscript.js';
|
|
8
|
+
const TapTreeCoder = PSBTOutput.tapTree[2];
|
|
7
9
|
export function scriptFromTapLeafScript(leaf) {
|
|
8
10
|
return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte
|
|
9
11
|
}
|
|
@@ -17,8 +19,9 @@ export function scriptFromTapLeafScript(leaf) {
|
|
|
17
19
|
*/
|
|
18
20
|
export class VtxoScript {
|
|
19
21
|
static decode(tapTree) {
|
|
20
|
-
const leaves =
|
|
21
|
-
|
|
22
|
+
const leaves = TapTreeCoder.decode(tapTree);
|
|
23
|
+
const scripts = leaves.map((leaf) => leaf.script);
|
|
24
|
+
return new VtxoScript(scripts);
|
|
22
25
|
}
|
|
23
26
|
constructor(scripts) {
|
|
24
27
|
this.scripts = scripts;
|
|
@@ -32,7 +35,11 @@ export class VtxoScript {
|
|
|
32
35
|
this.tweakedPublicKey = payment.tweakedPubkey;
|
|
33
36
|
}
|
|
34
37
|
encode() {
|
|
35
|
-
const tapTree =
|
|
38
|
+
const tapTree = TapTreeCoder.encode(this.scripts.map((script) => ({
|
|
39
|
+
depth: 1,
|
|
40
|
+
version: TAP_LEAF_VERSION,
|
|
41
|
+
script,
|
|
42
|
+
})));
|
|
36
43
|
return tapTree;
|
|
37
44
|
}
|
|
38
45
|
address(prefix, serverPubKey) {
|
|
@@ -75,89 +82,3 @@ export class VtxoScript {
|
|
|
75
82
|
return paths;
|
|
76
83
|
}
|
|
77
84
|
}
|
|
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
|
-
}
|
|
163
|
-
}
|
|
@@ -66,4 +66,12 @@ export var Request;
|
|
|
66
66
|
return message.type === "GET_STATUS";
|
|
67
67
|
}
|
|
68
68
|
Request.isGetStatus = isGetStatus;
|
|
69
|
+
function isClear(message) {
|
|
70
|
+
return message.type === "CLEAR";
|
|
71
|
+
}
|
|
72
|
+
Request.isClear = isClear;
|
|
73
|
+
function isReloadWallet(message) {
|
|
74
|
+
return message.type === "RELOAD_WALLET";
|
|
75
|
+
}
|
|
76
|
+
Request.isReloadWallet = isReloadWallet;
|
|
69
77
|
})(Request || (Request = {}));
|
|
@@ -172,4 +172,16 @@ export var Response;
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
Response.clearResponse = clearResponse;
|
|
175
|
+
function isWalletReloaded(response) {
|
|
176
|
+
return response.type === "WALLET_RELOADED";
|
|
177
|
+
}
|
|
178
|
+
Response.isWalletReloaded = isWalletReloaded;
|
|
179
|
+
function walletReloaded(id, success) {
|
|
180
|
+
return {
|
|
181
|
+
type: "WALLET_RELOADED",
|
|
182
|
+
success,
|
|
183
|
+
id,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
Response.walletReloaded = walletReloaded;
|
|
175
187
|
})(Response || (Response = {}));
|
|
@@ -5,10 +5,7 @@ import { WalletRepositoryImpl } from '../../repositories/walletRepository.js';
|
|
|
5
5
|
import { ContractRepositoryImpl } from '../../repositories/contractRepository.js';
|
|
6
6
|
import { setupServiceWorker } from './utils.js';
|
|
7
7
|
const isPrivateKeyIdentity = (identity) => {
|
|
8
|
-
return
|
|
9
|
-
typeof identity.toHex === "function" &&
|
|
10
|
-
typeof identity.toHex() === "string" &&
|
|
11
|
-
identity.toHex().length > 0);
|
|
8
|
+
return typeof identity.toHex === "function";
|
|
12
9
|
};
|
|
13
10
|
class UnexpectedResponseError extends Error {
|
|
14
11
|
constructor(response) {
|
|
@@ -287,6 +284,17 @@ export class ServiceWorkerWallet {
|
|
|
287
284
|
throw new Error(`Settlement failed: ${error}`);
|
|
288
285
|
}
|
|
289
286
|
}
|
|
287
|
+
async reload() {
|
|
288
|
+
const message = {
|
|
289
|
+
type: "RELOAD_WALLET",
|
|
290
|
+
id: getRandomId(),
|
|
291
|
+
};
|
|
292
|
+
const response = await this.sendMessage(message);
|
|
293
|
+
if (Response.isWalletReloaded(response)) {
|
|
294
|
+
return response.success;
|
|
295
|
+
}
|
|
296
|
+
throw new UnexpectedResponseError(response);
|
|
297
|
+
}
|
|
290
298
|
}
|
|
291
299
|
function getRandomId() {
|
|
292
300
|
const randomValue = crypto.getRandomValues(new Uint8Array(16));
|
|
@@ -107,13 +107,22 @@ export class Worker {
|
|
|
107
107
|
this.incomingFundsSubscription();
|
|
108
108
|
// subscribe for incoming funds and notify all clients when new funds arrive
|
|
109
109
|
this.incomingFundsSubscription = await this.wallet.notifyIncomingFunds(async (funds) => {
|
|
110
|
-
if (funds.type === "vtxo"
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
if (funds.type === "vtxo") {
|
|
111
|
+
const newVtxos = funds.newVtxos.length > 0
|
|
112
|
+
? funds.newVtxos.map((vtxo) => extendVirtualCoin(this.wallet, vtxo))
|
|
113
|
+
: [];
|
|
114
|
+
const spentVtxos = funds.spentVtxos.length > 0
|
|
115
|
+
? funds.spentVtxos.map((vtxo) => extendVirtualCoin(this.wallet, vtxo))
|
|
116
|
+
: [];
|
|
117
|
+
if ([...newVtxos, ...spentVtxos].length === 0)
|
|
118
|
+
return;
|
|
113
119
|
// save vtxos using unified repository
|
|
114
|
-
await this.walletRepository.saveVtxos(address,
|
|
120
|
+
await this.walletRepository.saveVtxos(address, [
|
|
121
|
+
...newVtxos,
|
|
122
|
+
...spentVtxos,
|
|
123
|
+
]);
|
|
115
124
|
// notify all clients about the vtxo update
|
|
116
|
-
this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify(
|
|
125
|
+
this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
|
|
117
126
|
}
|
|
118
127
|
if (funds.type === "utxo" && funds.coins.length > 0) {
|
|
119
128
|
// notify all clients about the utxo update
|
|
@@ -495,6 +504,10 @@ export class Worker {
|
|
|
495
504
|
await this.handleClear(event);
|
|
496
505
|
break;
|
|
497
506
|
}
|
|
507
|
+
case "RELOAD_WALLET": {
|
|
508
|
+
await this.handleReloadWallet(event);
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
498
511
|
default:
|
|
499
512
|
event.source?.postMessage(Response.error(message.id, "Unknown message type"));
|
|
500
513
|
}
|
|
@@ -511,4 +524,26 @@ export class Worker {
|
|
|
511
524
|
});
|
|
512
525
|
});
|
|
513
526
|
}
|
|
527
|
+
async handleReloadWallet(event) {
|
|
528
|
+
const message = event.data;
|
|
529
|
+
console.log("RELOAD_WALLET message received", message);
|
|
530
|
+
if (!Request.isReloadWallet(message)) {
|
|
531
|
+
console.error("Invalid RELOAD_WALLET message format", message);
|
|
532
|
+
event.source?.postMessage(Response.error(message.id, "Invalid RELOAD_WALLET message format"));
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (!this.wallet) {
|
|
536
|
+
console.error("Wallet not initialized");
|
|
537
|
+
event.source?.postMessage(Response.walletReloaded(message.id, false));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
await this.onWalletInitialized();
|
|
542
|
+
event.source?.postMessage(Response.walletReloaded(message.id, true));
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
console.error("Error reloading wallet:", error);
|
|
546
|
+
event.source?.postMessage(Response.walletReloaded(message.id, false));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
514
549
|
}
|
|
@@ -642,7 +642,8 @@ export class Wallet {
|
|
|
642
642
|
if (update.newVtxos?.length > 0) {
|
|
643
643
|
eventCallback({
|
|
644
644
|
type: "vtxo",
|
|
645
|
-
|
|
645
|
+
newVtxos: update.newVtxos.map((vtxo) => extendVirtualCoin(this, vtxo)),
|
|
646
|
+
spentVtxos: update.spentVtxos.map((vtxo) => extendVirtualCoin(this, vtxo)),
|
|
646
647
|
});
|
|
647
648
|
}
|
|
648
649
|
}
|
|
@@ -3,7 +3,7 @@ import { SettleParams, SendBitcoinParams, GetVtxosFilter } from "..";
|
|
|
3
3
|
* Request is the namespace that contains the request types for the service worker.
|
|
4
4
|
*/
|
|
5
5
|
export declare namespace Request {
|
|
6
|
-
type Type = "INIT_WALLET" | "SETTLE" | "GET_ADDRESS" | "GET_BOARDING_ADDRESS" | "GET_BALANCE" | "GET_VTXOS" | "GET_VIRTUAL_COINS" | "GET_BOARDING_UTXOS" | "SEND_BITCOIN" | "GET_TRANSACTION_HISTORY" | "GET_STATUS" | "CLEAR";
|
|
6
|
+
type Type = "INIT_WALLET" | "RELOAD_WALLET" | "SETTLE" | "GET_ADDRESS" | "GET_BOARDING_ADDRESS" | "GET_BALANCE" | "GET_VTXOS" | "GET_VIRTUAL_COINS" | "GET_BOARDING_UTXOS" | "SEND_BITCOIN" | "GET_TRANSACTION_HISTORY" | "GET_STATUS" | "CLEAR";
|
|
7
7
|
interface Base {
|
|
8
8
|
type: Type;
|
|
9
9
|
id: string;
|
|
@@ -62,4 +62,9 @@ export declare namespace Request {
|
|
|
62
62
|
interface Clear extends Base {
|
|
63
63
|
type: "CLEAR";
|
|
64
64
|
}
|
|
65
|
+
function isClear(message: Base): message is Clear;
|
|
66
|
+
interface ReloadWallet extends Base {
|
|
67
|
+
type: "RELOAD_WALLET";
|
|
68
|
+
}
|
|
69
|
+
function isReloadWallet(message: Base): message is ReloadWallet;
|
|
65
70
|
}
|
|
@@ -4,7 +4,7 @@ import { SettlementEvent } from "../../providers/ark";
|
|
|
4
4
|
* Response is the namespace that contains the response types for the service worker.
|
|
5
5
|
*/
|
|
6
6
|
export declare namespace Response {
|
|
7
|
-
type Type = "WALLET_INITIALIZED" | "SETTLE_EVENT" | "SETTLE_SUCCESS" | "ADDRESS" | "BOARDING_ADDRESS" | "BALANCE" | "VTXOS" | "VIRTUAL_COINS" | "BOARDING_UTXOS" | "SEND_BITCOIN_SUCCESS" | "TRANSACTION_HISTORY" | "WALLET_STATUS" | "ERROR" | "CLEAR_RESPONSE";
|
|
7
|
+
type Type = "WALLET_INITIALIZED" | "WALLET_RELOADED" | "SETTLE_EVENT" | "SETTLE_SUCCESS" | "ADDRESS" | "BOARDING_ADDRESS" | "BALANCE" | "VTXOS" | "VIRTUAL_COINS" | "BOARDING_UTXOS" | "SEND_BITCOIN_SUCCESS" | "TRANSACTION_HISTORY" | "WALLET_STATUS" | "ERROR" | "CLEAR_RESPONSE";
|
|
8
8
|
interface Base {
|
|
9
9
|
type: Type;
|
|
10
10
|
success: boolean;
|
|
@@ -101,4 +101,9 @@ export declare namespace Response {
|
|
|
101
101
|
}
|
|
102
102
|
function isClearResponse(response: Base): response is ClearResponse;
|
|
103
103
|
function clearResponse(id: string, success: boolean): ClearResponse;
|
|
104
|
+
interface WalletReloaded extends Base {
|
|
105
|
+
type: "WALLET_RELOADED";
|
|
106
|
+
}
|
|
107
|
+
function isWalletReloaded(response: Base): response is WalletReloaded;
|
|
108
|
+
function walletReloaded(id: string, success: boolean): WalletReloaded;
|
|
104
109
|
}
|
|
@@ -93,5 +93,6 @@ export declare class ServiceWorkerWallet implements IWallet {
|
|
|
93
93
|
getVtxos(filter?: GetVtxosFilter): Promise<ExtendedVirtualCoin[]>;
|
|
94
94
|
sendBitcoin(params: SendBitcoinParams): Promise<string>;
|
|
95
95
|
settle(params?: SettleParams, callback?: (event: SettlementEvent) => void): Promise<string>;
|
|
96
|
+
reload(): Promise<boolean>;
|
|
96
97
|
}
|
|
97
98
|
export {};
|
|
@@ -15,7 +15,8 @@ export type IncomingFunds = {
|
|
|
15
15
|
coins: Coin[];
|
|
16
16
|
} | {
|
|
17
17
|
type: "vtxo";
|
|
18
|
-
|
|
18
|
+
newVtxos: ExtendedVirtualCoin[];
|
|
19
|
+
spentVtxos: ExtendedVirtualCoin[];
|
|
19
20
|
};
|
|
20
21
|
/**
|
|
21
22
|
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|