@arkade-os/sdk 0.3.0-alpha.4 → 0.3.0-alpha.6
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/providers/onchain.js +8 -3
- package/dist/cjs/repositories/contractRepository.js +4 -0
- package/dist/cjs/repositories/walletRepository.js +27 -10
- package/dist/cjs/wallet/serviceWorker/request.js +8 -0
- package/dist/cjs/wallet/serviceWorker/response.js +12 -0
- package/dist/cjs/wallet/serviceWorker/utils.js +9 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +12 -4
- package/dist/cjs/wallet/serviceWorker/worker.js +67 -52
- package/dist/cjs/wallet/wallet.js +3 -1
- package/dist/esm/providers/onchain.js +8 -3
- package/dist/esm/repositories/contractRepository.js +4 -0
- package/dist/esm/repositories/walletRepository.js +27 -10
- package/dist/esm/wallet/serviceWorker/request.js +8 -0
- package/dist/esm/wallet/serviceWorker/response.js +12 -0
- package/dist/esm/wallet/serviceWorker/utils.js +8 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +12 -4
- package/dist/esm/wallet/serviceWorker/worker.js +67 -52
- package/dist/esm/wallet/wallet.js +3 -1
- package/dist/types/providers/onchain.d.ts +1 -0
- package/dist/types/repositories/contractRepository.d.ts +2 -0
- package/dist/types/repositories/walletRepository.d.ts +9 -12
- 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/utils.d.ts +2 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +1 -0
- package/dist/types/wallet/serviceWorker/worker.d.ts +2 -2
- package/dist/types/wallet/wallet.d.ts +3 -2
- package/package.json +1 -1
|
@@ -23,6 +23,7 @@ exports.ESPLORA_URL = {
|
|
|
23
23
|
class EsploraProvider {
|
|
24
24
|
constructor(baseUrl) {
|
|
25
25
|
this.baseUrl = baseUrl;
|
|
26
|
+
this.polling = false;
|
|
26
27
|
}
|
|
27
28
|
async getCoins(address) {
|
|
28
29
|
const response = await fetch(`${this.baseUrl}/address/${address}/utxo`);
|
|
@@ -93,6 +94,9 @@ class EsploraProvider {
|
|
|
93
94
|
let intervalId = null;
|
|
94
95
|
const wsUrl = this.baseUrl.replace(/^http(s)?:/, "ws$1:") + "/v1/ws";
|
|
95
96
|
const poll = async () => {
|
|
97
|
+
if (this.polling)
|
|
98
|
+
return;
|
|
99
|
+
this.polling = true;
|
|
96
100
|
// websocket is not reliable, so we will fallback to polling
|
|
97
101
|
const pollingInterval = 5000; // 5 seconds
|
|
98
102
|
const getAllTxs = () => {
|
|
@@ -102,19 +106,19 @@ class EsploraProvider {
|
|
|
102
106
|
const initialTxs = await getAllTxs();
|
|
103
107
|
// we use block_time in key to also notify when a transaction is confirmed
|
|
104
108
|
const txKey = (tx) => `${tx.txid}_${tx.status.block_time}`;
|
|
109
|
+
// create a set of existing transactions to avoid duplicates
|
|
110
|
+
const existingTxs = new Set(initialTxs.map(txKey));
|
|
105
111
|
// polling for new transactions
|
|
106
112
|
intervalId = setInterval(async () => {
|
|
107
113
|
try {
|
|
108
114
|
// get current transactions
|
|
109
115
|
// we will compare with initialTxs to find new ones
|
|
110
116
|
const currentTxs = await getAllTxs();
|
|
111
|
-
// create a set of existing transactions to avoid duplicates
|
|
112
|
-
const existingTxs = new Set(initialTxs.map(txKey));
|
|
113
117
|
// filter out transactions that are already in initialTxs
|
|
114
118
|
const newTxs = currentTxs.filter((tx) => !existingTxs.has(txKey(tx)));
|
|
115
119
|
if (newTxs.length > 0) {
|
|
116
120
|
// Update the tracking set instead of growing the array
|
|
117
|
-
|
|
121
|
+
newTxs.forEach((tx) => existingTxs.add(txKey(tx)));
|
|
118
122
|
callback(newTxs);
|
|
119
123
|
}
|
|
120
124
|
}
|
|
@@ -175,6 +179,7 @@ class EsploraProvider {
|
|
|
175
179
|
ws.close();
|
|
176
180
|
if (intervalId)
|
|
177
181
|
clearInterval(intervalId);
|
|
182
|
+
this.polling = false;
|
|
178
183
|
};
|
|
179
184
|
return stopFunc;
|
|
180
185
|
}
|
|
@@ -126,5 +126,9 @@ class ContractRepositoryImpl {
|
|
|
126
126
|
throw error; // Rethrow to notify caller of failure
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
async clearContractData() {
|
|
130
|
+
await this.storage.clear();
|
|
131
|
+
this.cache.clear();
|
|
132
|
+
}
|
|
129
133
|
}
|
|
130
134
|
exports.ContractRepositoryImpl = ContractRepositoryImpl;
|
|
@@ -7,8 +7,7 @@ const btc_signer_1 = require("@scure/btc-signer");
|
|
|
7
7
|
const toHex = (b) => (b ? base_1.hex.encode(b) : undefined);
|
|
8
8
|
const fromHex = (h) => h ? base_1.hex.decode(h) : undefined;
|
|
9
9
|
const serializeTapLeaf = ([cb, s]) => ({
|
|
10
|
-
cb: btc_signer_1.TaprootControlBlock.encode(cb)
|
|
11
|
-
base_1.hex.encode(btc_signer_1.TaprootControlBlock.encode(cb)),
|
|
10
|
+
cb: base_1.hex.encode(btc_signer_1.TaprootControlBlock.encode(cb)),
|
|
12
11
|
s: base_1.hex.encode(s),
|
|
13
12
|
});
|
|
14
13
|
const serializeVtxo = (v) => ({
|
|
@@ -53,7 +52,7 @@ class WalletRepositoryImpl {
|
|
|
53
52
|
try {
|
|
54
53
|
const parsed = JSON.parse(stored);
|
|
55
54
|
const vtxos = parsed.map(deserializeVtxo);
|
|
56
|
-
this.cache.vtxos.set(address, vtxos);
|
|
55
|
+
this.cache.vtxos.set(address, vtxos.slice());
|
|
57
56
|
return vtxos.slice();
|
|
58
57
|
}
|
|
59
58
|
catch (error) {
|
|
@@ -71,7 +70,7 @@ class WalletRepositoryImpl {
|
|
|
71
70
|
else {
|
|
72
71
|
vtxos.push(vtxo);
|
|
73
72
|
}
|
|
74
|
-
this.cache.vtxos.set(address, vtxos);
|
|
73
|
+
this.cache.vtxos.set(address, vtxos.slice());
|
|
75
74
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos.map(serializeVtxo)));
|
|
76
75
|
}
|
|
77
76
|
async saveVtxos(address, vtxos) {
|
|
@@ -85,14 +84,14 @@ class WalletRepositoryImpl {
|
|
|
85
84
|
storedVtxos.push(vtxo);
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
|
-
this.cache.vtxos.set(address, storedVtxos);
|
|
87
|
+
this.cache.vtxos.set(address, storedVtxos.slice());
|
|
89
88
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(storedVtxos.map(serializeVtxo)));
|
|
90
89
|
}
|
|
91
90
|
async removeVtxo(address, vtxoId) {
|
|
92
91
|
const vtxos = await this.getVtxos(address);
|
|
93
92
|
const [txid, vout] = vtxoId.split(":");
|
|
94
|
-
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout)));
|
|
95
|
-
this.cache.vtxos.set(address, filtered);
|
|
93
|
+
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
94
|
+
this.cache.vtxos.set(address, filtered.slice());
|
|
96
95
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(filtered.map(serializeVtxo)));
|
|
97
96
|
}
|
|
98
97
|
async clearVtxos(address) {
|
|
@@ -122,18 +121,36 @@ class WalletRepositoryImpl {
|
|
|
122
121
|
}
|
|
123
122
|
async saveTransaction(address, tx) {
|
|
124
123
|
const transactions = await this.getTransactionHistory(address);
|
|
125
|
-
const existing = transactions.findIndex((t) => t.
|
|
124
|
+
const existing = transactions.findIndex((t) => t.key === tx.key);
|
|
126
125
|
if (existing !== -1) {
|
|
127
126
|
transactions[existing] = tx;
|
|
128
127
|
}
|
|
129
128
|
else {
|
|
130
129
|
transactions.push(tx);
|
|
131
|
-
// Sort by timestamp descending
|
|
132
|
-
transactions.sort((a, b) => b.timestamp - a.timestamp);
|
|
133
130
|
}
|
|
131
|
+
// Sort by createdAt descending
|
|
132
|
+
transactions.sort((a, b) => b.createdAt - a.createdAt);
|
|
134
133
|
this.cache.transactions.set(address, transactions);
|
|
135
134
|
await this.storage.setItem(`tx:${address}`, JSON.stringify(transactions));
|
|
136
135
|
}
|
|
136
|
+
async saveTransactions(address, txs) {
|
|
137
|
+
const storedTransactions = await this.getTransactionHistory(address);
|
|
138
|
+
for (const tx of txs) {
|
|
139
|
+
const existing = storedTransactions.findIndex((t) => t.key === tx.key);
|
|
140
|
+
if (existing !== -1) {
|
|
141
|
+
storedTransactions[existing] = tx;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
storedTransactions.push(tx);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
this.cache.transactions.set(address, storedTransactions);
|
|
148
|
+
await this.storage.setItem(`tx:${address}`, JSON.stringify(storedTransactions));
|
|
149
|
+
}
|
|
150
|
+
async clearTransactions(address) {
|
|
151
|
+
this.cache.transactions.set(address, []);
|
|
152
|
+
await this.storage.removeItem(`tx:${address}`);
|
|
153
|
+
}
|
|
137
154
|
async getWalletState() {
|
|
138
155
|
if (this.cache.walletState !== null ||
|
|
139
156
|
this.cache.initialized.has("walletState")) {
|
|
@@ -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 = {}));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.setupServiceWorker = setupServiceWorker;
|
|
4
|
+
exports.extendVirtualCoin = extendVirtualCoin;
|
|
4
5
|
/**
|
|
5
6
|
* setupServiceWorker sets up the service worker.
|
|
6
7
|
* @param path - the path to the service worker script
|
|
@@ -47,3 +48,11 @@ async function setupServiceWorker(path) {
|
|
|
47
48
|
navigator.serviceWorker.addEventListener("error", onError);
|
|
48
49
|
});
|
|
49
50
|
}
|
|
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
|
+
}
|
|
@@ -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() {
|
|
@@ -13,6 +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("./utils");
|
|
16
17
|
/**
|
|
17
18
|
* Worker is a class letting to interact with ServiceWorkerWallet from the client
|
|
18
19
|
* it aims to be run in a service worker context
|
|
@@ -41,7 +42,7 @@ class Worker {
|
|
|
41
42
|
return [];
|
|
42
43
|
const address = await this.wallet.getAddress();
|
|
43
44
|
const allVtxos = await this.walletRepository.getVtxos(address);
|
|
44
|
-
return allVtxos.filter((vtxo) => vtxo.virtualStatus.state === "swept"
|
|
45
|
+
return allVtxos.filter((vtxo) => vtxo.virtualStatus.state === "swept");
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
47
48
|
* Get all vtxos categorized by type
|
|
@@ -72,19 +73,15 @@ class Worker {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
async clear() {
|
|
75
|
-
if (this.
|
|
76
|
-
this.
|
|
77
|
-
}
|
|
76
|
+
if (this.incomingFundsSubscription)
|
|
77
|
+
this.incomingFundsSubscription();
|
|
78
78
|
// Clear storage - this replaces vtxoRepository.close()
|
|
79
79
|
await this.storage.clear();
|
|
80
80
|
this.wallet = undefined;
|
|
81
81
|
this.arkProvider = undefined;
|
|
82
82
|
this.indexerProvider = undefined;
|
|
83
|
-
this.vtxoSubscription = undefined;
|
|
84
83
|
}
|
|
85
84
|
async reload() {
|
|
86
|
-
if (this.vtxoSubscription)
|
|
87
|
-
this.vtxoSubscription.abort();
|
|
88
85
|
await this.onWalletInitialized();
|
|
89
86
|
}
|
|
90
87
|
async onWalletInitialized() {
|
|
@@ -95,58 +92,46 @@ class Worker {
|
|
|
95
92
|
!this.wallet.boardingTapscript) {
|
|
96
93
|
return;
|
|
97
94
|
}
|
|
98
|
-
|
|
99
|
-
const forfeit = this.wallet.offchainTapscript.forfeit();
|
|
100
|
-
const exit = this.wallet.offchainTapscript.exit();
|
|
95
|
+
// Get public key script and set the initial vtxos state
|
|
101
96
|
const script = base_1.hex.encode(this.wallet.offchainTapscript.pkScript);
|
|
102
|
-
// set the initial vtxos state
|
|
103
97
|
const response = await this.indexerProvider.getVtxos({
|
|
104
98
|
scripts: [script],
|
|
105
99
|
});
|
|
106
|
-
const vtxos = response.vtxos.map((vtxo) => (
|
|
107
|
-
...vtxo,
|
|
108
|
-
forfeitTapLeafScript: forfeit,
|
|
109
|
-
intentTapLeafScript: exit,
|
|
110
|
-
tapTree: encodedOffchainTapscript,
|
|
111
|
-
}));
|
|
100
|
+
const vtxos = response.vtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this.wallet, vtxo));
|
|
112
101
|
// Get wallet address and save vtxos using unified repository
|
|
113
102
|
const address = await this.wallet.getAddress();
|
|
114
103
|
await this.walletRepository.saveVtxos(address, vtxos);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
...
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}));
|
|
140
|
-
// Get wallet address and save vtxos using unified repository
|
|
141
|
-
const address = await this.wallet.getAddress();
|
|
142
|
-
await this.walletRepository.saveVtxos(address, extendedVtxos);
|
|
143
|
-
// Notify all clients about the vtxo update
|
|
144
|
-
this.sendMessageToAllClients("VTXO_UPDATE", "");
|
|
104
|
+
// Get transaction history to cache boarding txs
|
|
105
|
+
const txs = await this.wallet.getTransactionHistory();
|
|
106
|
+
if (txs)
|
|
107
|
+
await this.walletRepository.saveTransactions(address, txs);
|
|
108
|
+
// stop previous subscriptions if any
|
|
109
|
+
if (this.incomingFundsSubscription)
|
|
110
|
+
this.incomingFundsSubscription();
|
|
111
|
+
// subscribe for incoming funds and notify all clients when new funds arrive
|
|
112
|
+
this.incomingFundsSubscription = await this.wallet.notifyIncomingFunds(async (funds) => {
|
|
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;
|
|
122
|
+
// save vtxos using unified repository
|
|
123
|
+
await this.walletRepository.saveVtxos(address, [
|
|
124
|
+
...newVtxos,
|
|
125
|
+
...spentVtxos,
|
|
126
|
+
]);
|
|
127
|
+
// notify all clients about the vtxo update
|
|
128
|
+
this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
|
|
145
129
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
130
|
+
if (funds.type === "utxo" && funds.coins.length > 0) {
|
|
131
|
+
// notify all clients about the utxo update
|
|
132
|
+
this.sendMessageToAllClients("UTXO_UPDATE", JSON.stringify(funds.coins));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
150
135
|
}
|
|
151
136
|
async handleClear(event) {
|
|
152
137
|
await this.clear();
|
|
@@ -374,7 +359,11 @@ class Worker {
|
|
|
374
359
|
if (!this.wallet)
|
|
375
360
|
throw new Error("Wallet not initialized");
|
|
376
361
|
// exclude subdust is we don't want recoverable
|
|
377
|
-
|
|
362
|
+
const dustAmount = this.wallet?.dustAmount;
|
|
363
|
+
vtxos =
|
|
364
|
+
dustAmount == null
|
|
365
|
+
? vtxos
|
|
366
|
+
: vtxos.filter((v) => !(0, __1.isSubdust)(v, dustAmount));
|
|
378
367
|
}
|
|
379
368
|
if (message.filter?.withRecoverable) {
|
|
380
369
|
// get also swept and spendable vtxos
|
|
@@ -518,6 +507,10 @@ class Worker {
|
|
|
518
507
|
await this.handleClear(event);
|
|
519
508
|
break;
|
|
520
509
|
}
|
|
510
|
+
case "RELOAD_WALLET": {
|
|
511
|
+
await this.handleReloadWallet(event);
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
521
514
|
default:
|
|
522
515
|
event.source?.postMessage(response_1.Response.error(message.id, "Unknown message type"));
|
|
523
516
|
}
|
|
@@ -534,5 +527,27 @@ class Worker {
|
|
|
534
527
|
});
|
|
535
528
|
});
|
|
536
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
|
+
}
|
|
537
552
|
}
|
|
538
553
|
exports.Worker = Worker;
|
|
@@ -60,6 +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("./serviceWorker/utils");
|
|
63
64
|
/**
|
|
64
65
|
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|
|
65
66
|
* The wallet does not store any data locally and relies on Ark and onchain
|
|
@@ -678,7 +679,8 @@ class Wallet {
|
|
|
678
679
|
if (update.newVtxos?.length > 0) {
|
|
679
680
|
eventCallback({
|
|
680
681
|
type: "vtxo",
|
|
681
|
-
|
|
682
|
+
newVtxos: update.newVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
683
|
+
spentVtxos: update.spentVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
682
684
|
});
|
|
683
685
|
}
|
|
684
686
|
}
|
|
@@ -20,6 +20,7 @@ export const ESPLORA_URL = {
|
|
|
20
20
|
export class EsploraProvider {
|
|
21
21
|
constructor(baseUrl) {
|
|
22
22
|
this.baseUrl = baseUrl;
|
|
23
|
+
this.polling = false;
|
|
23
24
|
}
|
|
24
25
|
async getCoins(address) {
|
|
25
26
|
const response = await fetch(`${this.baseUrl}/address/${address}/utxo`);
|
|
@@ -90,6 +91,9 @@ export class EsploraProvider {
|
|
|
90
91
|
let intervalId = null;
|
|
91
92
|
const wsUrl = this.baseUrl.replace(/^http(s)?:/, "ws$1:") + "/v1/ws";
|
|
92
93
|
const poll = async () => {
|
|
94
|
+
if (this.polling)
|
|
95
|
+
return;
|
|
96
|
+
this.polling = true;
|
|
93
97
|
// websocket is not reliable, so we will fallback to polling
|
|
94
98
|
const pollingInterval = 5000; // 5 seconds
|
|
95
99
|
const getAllTxs = () => {
|
|
@@ -99,19 +103,19 @@ export class EsploraProvider {
|
|
|
99
103
|
const initialTxs = await getAllTxs();
|
|
100
104
|
// we use block_time in key to also notify when a transaction is confirmed
|
|
101
105
|
const txKey = (tx) => `${tx.txid}_${tx.status.block_time}`;
|
|
106
|
+
// create a set of existing transactions to avoid duplicates
|
|
107
|
+
const existingTxs = new Set(initialTxs.map(txKey));
|
|
102
108
|
// polling for new transactions
|
|
103
109
|
intervalId = setInterval(async () => {
|
|
104
110
|
try {
|
|
105
111
|
// get current transactions
|
|
106
112
|
// we will compare with initialTxs to find new ones
|
|
107
113
|
const currentTxs = await getAllTxs();
|
|
108
|
-
// create a set of existing transactions to avoid duplicates
|
|
109
|
-
const existingTxs = new Set(initialTxs.map(txKey));
|
|
110
114
|
// filter out transactions that are already in initialTxs
|
|
111
115
|
const newTxs = currentTxs.filter((tx) => !existingTxs.has(txKey(tx)));
|
|
112
116
|
if (newTxs.length > 0) {
|
|
113
117
|
// Update the tracking set instead of growing the array
|
|
114
|
-
|
|
118
|
+
newTxs.forEach((tx) => existingTxs.add(txKey(tx)));
|
|
115
119
|
callback(newTxs);
|
|
116
120
|
}
|
|
117
121
|
}
|
|
@@ -172,6 +176,7 @@ export class EsploraProvider {
|
|
|
172
176
|
ws.close();
|
|
173
177
|
if (intervalId)
|
|
174
178
|
clearInterval(intervalId);
|
|
179
|
+
this.polling = false;
|
|
175
180
|
};
|
|
176
181
|
return stopFunc;
|
|
177
182
|
}
|
|
@@ -4,8 +4,7 @@ import { TaprootControlBlock } from "@scure/btc-signer";
|
|
|
4
4
|
const toHex = (b) => (b ? hex.encode(b) : undefined);
|
|
5
5
|
const fromHex = (h) => h ? hex.decode(h) : undefined;
|
|
6
6
|
const serializeTapLeaf = ([cb, s]) => ({
|
|
7
|
-
cb: TaprootControlBlock.encode(cb)
|
|
8
|
-
hex.encode(TaprootControlBlock.encode(cb)),
|
|
7
|
+
cb: hex.encode(TaprootControlBlock.encode(cb)),
|
|
9
8
|
s: hex.encode(s),
|
|
10
9
|
});
|
|
11
10
|
const serializeVtxo = (v) => ({
|
|
@@ -50,7 +49,7 @@ export class WalletRepositoryImpl {
|
|
|
50
49
|
try {
|
|
51
50
|
const parsed = JSON.parse(stored);
|
|
52
51
|
const vtxos = parsed.map(deserializeVtxo);
|
|
53
|
-
this.cache.vtxos.set(address, vtxos);
|
|
52
|
+
this.cache.vtxos.set(address, vtxos.slice());
|
|
54
53
|
return vtxos.slice();
|
|
55
54
|
}
|
|
56
55
|
catch (error) {
|
|
@@ -68,7 +67,7 @@ export class WalletRepositoryImpl {
|
|
|
68
67
|
else {
|
|
69
68
|
vtxos.push(vtxo);
|
|
70
69
|
}
|
|
71
|
-
this.cache.vtxos.set(address, vtxos);
|
|
70
|
+
this.cache.vtxos.set(address, vtxos.slice());
|
|
72
71
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos.map(serializeVtxo)));
|
|
73
72
|
}
|
|
74
73
|
async saveVtxos(address, vtxos) {
|
|
@@ -82,14 +81,14 @@ export class WalletRepositoryImpl {
|
|
|
82
81
|
storedVtxos.push(vtxo);
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
|
-
this.cache.vtxos.set(address, storedVtxos);
|
|
84
|
+
this.cache.vtxos.set(address, storedVtxos.slice());
|
|
86
85
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(storedVtxos.map(serializeVtxo)));
|
|
87
86
|
}
|
|
88
87
|
async removeVtxo(address, vtxoId) {
|
|
89
88
|
const vtxos = await this.getVtxos(address);
|
|
90
89
|
const [txid, vout] = vtxoId.split(":");
|
|
91
|
-
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout)));
|
|
92
|
-
this.cache.vtxos.set(address, filtered);
|
|
90
|
+
const filtered = vtxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
91
|
+
this.cache.vtxos.set(address, filtered.slice());
|
|
93
92
|
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(filtered.map(serializeVtxo)));
|
|
94
93
|
}
|
|
95
94
|
async clearVtxos(address) {
|
|
@@ -119,18 +118,36 @@ export class WalletRepositoryImpl {
|
|
|
119
118
|
}
|
|
120
119
|
async saveTransaction(address, tx) {
|
|
121
120
|
const transactions = await this.getTransactionHistory(address);
|
|
122
|
-
const existing = transactions.findIndex((t) => t.
|
|
121
|
+
const existing = transactions.findIndex((t) => t.key === tx.key);
|
|
123
122
|
if (existing !== -1) {
|
|
124
123
|
transactions[existing] = tx;
|
|
125
124
|
}
|
|
126
125
|
else {
|
|
127
126
|
transactions.push(tx);
|
|
128
|
-
// Sort by timestamp descending
|
|
129
|
-
transactions.sort((a, b) => b.timestamp - a.timestamp);
|
|
130
127
|
}
|
|
128
|
+
// Sort by createdAt descending
|
|
129
|
+
transactions.sort((a, b) => b.createdAt - a.createdAt);
|
|
131
130
|
this.cache.transactions.set(address, transactions);
|
|
132
131
|
await this.storage.setItem(`tx:${address}`, JSON.stringify(transactions));
|
|
133
132
|
}
|
|
133
|
+
async saveTransactions(address, txs) {
|
|
134
|
+
const storedTransactions = await this.getTransactionHistory(address);
|
|
135
|
+
for (const tx of txs) {
|
|
136
|
+
const existing = storedTransactions.findIndex((t) => t.key === tx.key);
|
|
137
|
+
if (existing !== -1) {
|
|
138
|
+
storedTransactions[existing] = tx;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
storedTransactions.push(tx);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.cache.transactions.set(address, storedTransactions);
|
|
145
|
+
await this.storage.setItem(`tx:${address}`, JSON.stringify(storedTransactions));
|
|
146
|
+
}
|
|
147
|
+
async clearTransactions(address) {
|
|
148
|
+
this.cache.transactions.set(address, []);
|
|
149
|
+
await this.storage.removeItem(`tx:${address}`);
|
|
150
|
+
}
|
|
134
151
|
async getWalletState() {
|
|
135
152
|
if (this.cache.walletState !== null ||
|
|
136
153
|
this.cache.initialized.has("walletState")) {
|
|
@@ -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 = {}));
|
|
@@ -44,3 +44,11 @@ export async function setupServiceWorker(path) {
|
|
|
44
44
|
navigator.serviceWorker.addEventListener("error", onError);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
+
export function extendVirtualCoin(wallet, vtxo) {
|
|
48
|
+
return {
|
|
49
|
+
...vtxo,
|
|
50
|
+
forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
|
|
51
|
+
intentTapLeafScript: wallet.offchainTapscript.exit(),
|
|
52
|
+
tapTree: wallet.offchainTapscript.encode(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -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));
|
|
@@ -10,6 +10,7 @@ import { RestIndexerProvider } from '../../providers/indexer.js';
|
|
|
10
10
|
import { hex } from "@scure/base";
|
|
11
11
|
import { IndexedDBStorageAdapter } from '../../storage/indexedDB.js';
|
|
12
12
|
import { WalletRepositoryImpl, } from '../../repositories/walletRepository.js';
|
|
13
|
+
import { extendVirtualCoin } from './utils.js';
|
|
13
14
|
/**
|
|
14
15
|
* Worker is a class letting to interact with ServiceWorkerWallet from the client
|
|
15
16
|
* it aims to be run in a service worker context
|
|
@@ -38,7 +39,7 @@ export class Worker {
|
|
|
38
39
|
return [];
|
|
39
40
|
const address = await this.wallet.getAddress();
|
|
40
41
|
const allVtxos = await this.walletRepository.getVtxos(address);
|
|
41
|
-
return allVtxos.filter((vtxo) => vtxo.virtualStatus.state === "swept"
|
|
42
|
+
return allVtxos.filter((vtxo) => vtxo.virtualStatus.state === "swept");
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
44
45
|
* Get all vtxos categorized by type
|
|
@@ -69,19 +70,15 @@ export class Worker {
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
async clear() {
|
|
72
|
-
if (this.
|
|
73
|
-
this.
|
|
74
|
-
}
|
|
73
|
+
if (this.incomingFundsSubscription)
|
|
74
|
+
this.incomingFundsSubscription();
|
|
75
75
|
// Clear storage - this replaces vtxoRepository.close()
|
|
76
76
|
await this.storage.clear();
|
|
77
77
|
this.wallet = undefined;
|
|
78
78
|
this.arkProvider = undefined;
|
|
79
79
|
this.indexerProvider = undefined;
|
|
80
|
-
this.vtxoSubscription = undefined;
|
|
81
80
|
}
|
|
82
81
|
async reload() {
|
|
83
|
-
if (this.vtxoSubscription)
|
|
84
|
-
this.vtxoSubscription.abort();
|
|
85
82
|
await this.onWalletInitialized();
|
|
86
83
|
}
|
|
87
84
|
async onWalletInitialized() {
|
|
@@ -92,58 +89,46 @@ export class Worker {
|
|
|
92
89
|
!this.wallet.boardingTapscript) {
|
|
93
90
|
return;
|
|
94
91
|
}
|
|
95
|
-
|
|
96
|
-
const forfeit = this.wallet.offchainTapscript.forfeit();
|
|
97
|
-
const exit = this.wallet.offchainTapscript.exit();
|
|
92
|
+
// Get public key script and set the initial vtxos state
|
|
98
93
|
const script = hex.encode(this.wallet.offchainTapscript.pkScript);
|
|
99
|
-
// set the initial vtxos state
|
|
100
94
|
const response = await this.indexerProvider.getVtxos({
|
|
101
95
|
scripts: [script],
|
|
102
96
|
});
|
|
103
|
-
const vtxos = response.vtxos.map((vtxo) => (
|
|
104
|
-
...vtxo,
|
|
105
|
-
forfeitTapLeafScript: forfeit,
|
|
106
|
-
intentTapLeafScript: exit,
|
|
107
|
-
tapTree: encodedOffchainTapscript,
|
|
108
|
-
}));
|
|
97
|
+
const vtxos = response.vtxos.map((vtxo) => extendVirtualCoin(this.wallet, vtxo));
|
|
109
98
|
// Get wallet address and save vtxos using unified repository
|
|
110
99
|
const address = await this.wallet.getAddress();
|
|
111
100
|
await this.walletRepository.saveVtxos(address, vtxos);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
...
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}));
|
|
137
|
-
// Get wallet address and save vtxos using unified repository
|
|
138
|
-
const address = await this.wallet.getAddress();
|
|
139
|
-
await this.walletRepository.saveVtxos(address, extendedVtxos);
|
|
140
|
-
// Notify all clients about the vtxo update
|
|
141
|
-
this.sendMessageToAllClients("VTXO_UPDATE", "");
|
|
101
|
+
// Get transaction history to cache boarding txs
|
|
102
|
+
const txs = await this.wallet.getTransactionHistory();
|
|
103
|
+
if (txs)
|
|
104
|
+
await this.walletRepository.saveTransactions(address, txs);
|
|
105
|
+
// stop previous subscriptions if any
|
|
106
|
+
if (this.incomingFundsSubscription)
|
|
107
|
+
this.incomingFundsSubscription();
|
|
108
|
+
// subscribe for incoming funds and notify all clients when new funds arrive
|
|
109
|
+
this.incomingFundsSubscription = await this.wallet.notifyIncomingFunds(async (funds) => {
|
|
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;
|
|
119
|
+
// save vtxos using unified repository
|
|
120
|
+
await this.walletRepository.saveVtxos(address, [
|
|
121
|
+
...newVtxos,
|
|
122
|
+
...spentVtxos,
|
|
123
|
+
]);
|
|
124
|
+
// notify all clients about the vtxo update
|
|
125
|
+
this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
|
|
142
126
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
if (funds.type === "utxo" && funds.coins.length > 0) {
|
|
128
|
+
// notify all clients about the utxo update
|
|
129
|
+
this.sendMessageToAllClients("UTXO_UPDATE", JSON.stringify(funds.coins));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
147
132
|
}
|
|
148
133
|
async handleClear(event) {
|
|
149
134
|
await this.clear();
|
|
@@ -371,7 +356,11 @@ export class Worker {
|
|
|
371
356
|
if (!this.wallet)
|
|
372
357
|
throw new Error("Wallet not initialized");
|
|
373
358
|
// exclude subdust is we don't want recoverable
|
|
374
|
-
|
|
359
|
+
const dustAmount = this.wallet?.dustAmount;
|
|
360
|
+
vtxos =
|
|
361
|
+
dustAmount == null
|
|
362
|
+
? vtxos
|
|
363
|
+
: vtxos.filter((v) => !isSubdust(v, dustAmount));
|
|
375
364
|
}
|
|
376
365
|
if (message.filter?.withRecoverable) {
|
|
377
366
|
// get also swept and spendable vtxos
|
|
@@ -515,6 +504,10 @@ export class Worker {
|
|
|
515
504
|
await this.handleClear(event);
|
|
516
505
|
break;
|
|
517
506
|
}
|
|
507
|
+
case "RELOAD_WALLET": {
|
|
508
|
+
await this.handleReloadWallet(event);
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
518
511
|
default:
|
|
519
512
|
event.source?.postMessage(Response.error(message.id, "Unknown message type"));
|
|
520
513
|
}
|
|
@@ -531,4 +524,26 @@ export class Worker {
|
|
|
531
524
|
});
|
|
532
525
|
});
|
|
533
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
|
+
}
|
|
534
549
|
}
|
|
@@ -23,6 +23,7 @@ import { TxTree } from '../tree/txTree.js';
|
|
|
23
23
|
import { InMemoryStorageAdapter } from '../storage/inMemory.js';
|
|
24
24
|
import { WalletRepositoryImpl, } from '../repositories/walletRepository.js';
|
|
25
25
|
import { ContractRepositoryImpl, } from '../repositories/contractRepository.js';
|
|
26
|
+
import { extendVirtualCoin } from './serviceWorker/utils.js';
|
|
26
27
|
/**
|
|
27
28
|
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|
|
28
29
|
* The wallet does not store any data locally and relies on Ark and onchain
|
|
@@ -641,7 +642,8 @@ export class Wallet {
|
|
|
641
642
|
if (update.newVtxos?.length > 0) {
|
|
642
643
|
eventCallback({
|
|
643
644
|
type: "vtxo",
|
|
644
|
-
|
|
645
|
+
newVtxos: update.newVtxos.map((vtxo) => extendVirtualCoin(this, vtxo)),
|
|
646
|
+
spentVtxos: update.spentVtxos.map((vtxo) => extendVirtualCoin(this, vtxo)),
|
|
645
647
|
});
|
|
646
648
|
}
|
|
647
649
|
}
|
|
@@ -49,6 +49,7 @@ export interface OnchainProvider {
|
|
|
49
49
|
*/
|
|
50
50
|
export declare class EsploraProvider implements OnchainProvider {
|
|
51
51
|
private baseUrl;
|
|
52
|
+
private polling;
|
|
52
53
|
constructor(baseUrl: string);
|
|
53
54
|
getCoins(address: string): Promise<Coin[]>;
|
|
54
55
|
getFeeRate(): Promise<number | undefined>;
|
|
@@ -3,6 +3,7 @@ export interface ContractRepository {
|
|
|
3
3
|
getContractData<T>(contractId: string, key: string): Promise<T | null>;
|
|
4
4
|
setContractData<T>(contractId: string, key: string, data: T): Promise<void>;
|
|
5
5
|
deleteContractData(contractId: string, key: string): Promise<void>;
|
|
6
|
+
clearContractData(): Promise<void>;
|
|
6
7
|
getContractCollection<T>(contractType: string): Promise<ReadonlyArray<T>>;
|
|
7
8
|
saveToContractCollection<T, K extends keyof T>(contractType: string, item: T, idField: K): Promise<void>;
|
|
8
9
|
removeFromContractCollection<T, K extends keyof T>(contractType: string, id: T[K], idField: K): Promise<void>;
|
|
@@ -17,4 +18,5 @@ export declare class ContractRepositoryImpl implements ContractRepository {
|
|
|
17
18
|
getContractCollection<T>(contractType: string): Promise<ReadonlyArray<T>>;
|
|
18
19
|
saveToContractCollection<T, K extends keyof T>(contractType: string, item: T, idField: K): Promise<void>;
|
|
19
20
|
removeFromContractCollection<T, K extends keyof T>(contractType: string, id: T[K], idField: K): Promise<void>;
|
|
21
|
+
clearContractData(): Promise<void>;
|
|
20
22
|
}
|
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import { StorageAdapter } from "../storage";
|
|
2
|
-
import { ExtendedVirtualCoin } from "../wallet";
|
|
2
|
+
import { ArkTransaction, ExtendedVirtualCoin } from "../wallet";
|
|
3
3
|
export interface WalletState {
|
|
4
4
|
lastSyncTime?: number;
|
|
5
5
|
settings?: Record<string, any>;
|
|
6
6
|
}
|
|
7
|
-
export interface Transaction {
|
|
8
|
-
id: string;
|
|
9
|
-
timestamp: number;
|
|
10
|
-
amount: number;
|
|
11
|
-
type: "send" | "receive";
|
|
12
|
-
status: "pending" | "confirmed" | "failed";
|
|
13
|
-
}
|
|
14
7
|
export interface WalletRepository {
|
|
15
8
|
getVtxos(address: string): Promise<ExtendedVirtualCoin[]>;
|
|
16
9
|
saveVtxo(address: string, vtxo: ExtendedVirtualCoin): Promise<void>;
|
|
17
10
|
saveVtxos(address: string, vtxos: ExtendedVirtualCoin[]): Promise<void>;
|
|
18
11
|
removeVtxo(address: string, vtxoId: string): Promise<void>;
|
|
19
12
|
clearVtxos(address: string): Promise<void>;
|
|
20
|
-
getTransactionHistory(address: string): Promise<
|
|
21
|
-
saveTransaction(address: string, tx:
|
|
13
|
+
getTransactionHistory(address: string): Promise<ArkTransaction[]>;
|
|
14
|
+
saveTransaction(address: string, tx: ArkTransaction): Promise<void>;
|
|
15
|
+
saveTransactions(address: string, txs: ArkTransaction[]): Promise<void>;
|
|
16
|
+
clearTransactions(address: string): Promise<void>;
|
|
22
17
|
getWalletState(): Promise<WalletState | null>;
|
|
23
18
|
saveWalletState(state: WalletState): Promise<void>;
|
|
24
19
|
}
|
|
@@ -31,8 +26,10 @@ export declare class WalletRepositoryImpl implements WalletRepository {
|
|
|
31
26
|
saveVtxos(address: string, vtxos: ExtendedVirtualCoin[]): Promise<void>;
|
|
32
27
|
removeVtxo(address: string, vtxoId: string): Promise<void>;
|
|
33
28
|
clearVtxos(address: string): Promise<void>;
|
|
34
|
-
getTransactionHistory(address: string): Promise<
|
|
35
|
-
saveTransaction(address: string, tx:
|
|
29
|
+
getTransactionHistory(address: string): Promise<ArkTransaction[]>;
|
|
30
|
+
saveTransaction(address: string, tx: ArkTransaction): Promise<void>;
|
|
31
|
+
saveTransactions(address: string, txs: ArkTransaction[]): Promise<void>;
|
|
32
|
+
clearTransactions(address: string): Promise<void>;
|
|
36
33
|
getWalletState(): Promise<WalletState | null>;
|
|
37
34
|
saveWalletState(state: WalletState): Promise<void>;
|
|
38
35
|
}
|
|
@@ -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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ExtendedVirtualCoin, VirtualCoin, Wallet } from "../..";
|
|
1
2
|
/**
|
|
2
3
|
* setupServiceWorker sets up the service worker.
|
|
3
4
|
* @param path - the path to the service worker script
|
|
@@ -7,3 +8,4 @@
|
|
|
7
8
|
* ```
|
|
8
9
|
*/
|
|
9
10
|
export declare function setupServiceWorker(path: string): Promise<ServiceWorker>;
|
|
11
|
+
export declare function extendVirtualCoin(wallet: Wallet, vtxo: VirtualCoin): ExtendedVirtualCoin;
|
|
@@ -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 {};
|
|
@@ -7,7 +7,7 @@ export declare class Worker {
|
|
|
7
7
|
private wallet;
|
|
8
8
|
private arkProvider;
|
|
9
9
|
private indexerProvider;
|
|
10
|
-
private
|
|
10
|
+
private incomingFundsSubscription;
|
|
11
11
|
private walletRepository;
|
|
12
12
|
private storage;
|
|
13
13
|
constructor(messageCallback?: (message: ExtendableMessageEvent) => void);
|
|
@@ -27,7 +27,6 @@ export declare class Worker {
|
|
|
27
27
|
clear(): Promise<void>;
|
|
28
28
|
reload(): Promise<void>;
|
|
29
29
|
private onWalletInitialized;
|
|
30
|
-
private processVtxoSubscription;
|
|
31
30
|
private handleClear;
|
|
32
31
|
private handleInitWallet;
|
|
33
32
|
private handleSettle;
|
|
@@ -41,4 +40,5 @@ export declare class Worker {
|
|
|
41
40
|
private handleGetStatus;
|
|
42
41
|
private handleMessage;
|
|
43
42
|
private sendMessageToAllClients;
|
|
43
|
+
private handleReloadWallet;
|
|
44
44
|
}
|
|
@@ -4,7 +4,7 @@ import { Network, NetworkName } from "../networks";
|
|
|
4
4
|
import { OnchainProvider } from "../providers/onchain";
|
|
5
5
|
import { SettlementEvent, ArkProvider } from "../providers/ark";
|
|
6
6
|
import { Identity } from "../identity";
|
|
7
|
-
import { ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, GetVtxosFilter, IWallet, SendBitcoinParams, SettleParams,
|
|
7
|
+
import { ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, GetVtxosFilter, IWallet, SendBitcoinParams, SettleParams, WalletBalance, WalletConfig } from ".";
|
|
8
8
|
import { Bytes } from "@scure/btc-signer/utils.js";
|
|
9
9
|
import { CSVMultisigTapscript } from "../script/tapscript";
|
|
10
10
|
import { IndexerProvider } from "../providers/indexer";
|
|
@@ -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.
|