@arkade-os/sdk 0.2.2 → 0.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -43
- package/dist/cjs/adapters/asyncStorage.js +5 -0
- package/dist/cjs/adapters/fileSystem.js +5 -0
- package/dist/cjs/adapters/indexedDB.js +5 -0
- package/dist/cjs/adapters/localStorage.js +5 -0
- package/dist/cjs/bip322/index.js +2 -2
- package/dist/cjs/identity/index.js +15 -0
- package/dist/cjs/identity/singleKey.js +20 -1
- package/dist/cjs/index.js +5 -3
- package/dist/cjs/musig2/keys.js +6 -6
- package/dist/cjs/musig2/sign.js +5 -5
- package/dist/cjs/repositories/contractRepository.js +130 -0
- package/dist/cjs/repositories/index.js +18 -0
- package/dist/cjs/repositories/walletRepository.js +136 -0
- package/dist/cjs/storage/asyncStorage.js +47 -0
- package/dist/cjs/storage/fileSystem.js +138 -0
- package/dist/cjs/storage/inMemory.js +21 -0
- package/dist/cjs/storage/indexedDB.js +97 -0
- package/dist/cjs/storage/localStorage.js +48 -0
- package/dist/cjs/tree/signingSession.js +4 -4
- package/dist/cjs/wallet/onchain.js +12 -6
- package/dist/cjs/wallet/serviceWorker/request.js +4 -14
- package/dist/cjs/wallet/serviceWorker/response.js +0 -13
- package/dist/cjs/wallet/serviceWorker/wallet.js +124 -130
- package/dist/cjs/wallet/serviceWorker/worker.js +89 -53
- package/dist/cjs/wallet/wallet.js +21 -4
- package/dist/esm/adapters/asyncStorage.js +1 -0
- package/dist/esm/adapters/fileSystem.js +1 -0
- package/dist/esm/adapters/indexedDB.js +1 -0
- package/dist/esm/adapters/localStorage.js +1 -0
- package/dist/esm/bip322/index.js +1 -1
- package/dist/esm/identity/index.js +1 -1
- package/dist/esm/identity/singleKey.js +21 -2
- package/dist/esm/index.js +4 -3
- package/dist/esm/musig2/keys.js +6 -6
- package/dist/esm/musig2/sign.js +4 -4
- package/dist/esm/repositories/contractRepository.js +126 -0
- package/dist/esm/repositories/index.js +2 -0
- package/dist/esm/repositories/walletRepository.js +132 -0
- package/dist/esm/storage/asyncStorage.js +43 -0
- package/dist/esm/storage/fileSystem.js +101 -0
- package/dist/esm/storage/inMemory.js +17 -0
- package/dist/esm/storage/indexedDB.js +93 -0
- package/dist/esm/storage/localStorage.js +44 -0
- package/dist/esm/tree/signingSession.js +1 -1
- package/dist/esm/wallet/onchain.js +12 -6
- package/dist/esm/wallet/serviceWorker/request.js +4 -14
- package/dist/esm/wallet/serviceWorker/response.js +0 -13
- package/dist/esm/wallet/serviceWorker/wallet.js +125 -131
- package/dist/esm/wallet/serviceWorker/worker.js +90 -54
- package/dist/esm/wallet/wallet.js +21 -4
- package/dist/types/adapters/asyncStorage.d.ts +2 -0
- package/dist/types/adapters/fileSystem.d.ts +2 -0
- package/dist/types/adapters/indexedDB.d.ts +2 -0
- package/dist/types/adapters/localStorage.d.ts +2 -0
- package/dist/types/identity/index.d.ts +3 -1
- package/dist/types/identity/singleKey.d.ts +12 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/repositories/contractRepository.d.ts +20 -0
- package/dist/types/repositories/index.d.ts +2 -0
- package/dist/types/repositories/walletRepository.d.ts +38 -0
- package/dist/types/storage/asyncStorage.d.ts +9 -0
- package/dist/types/storage/fileSystem.d.ts +11 -0
- package/dist/types/storage/inMemory.d.ts +8 -0
- package/dist/types/storage/index.d.ts +6 -0
- package/dist/types/storage/indexedDB.d.ts +12 -0
- package/dist/types/storage/localStorage.d.ts +8 -0
- package/dist/types/wallet/index.d.ts +3 -0
- package/dist/types/wallet/onchain.d.ts +3 -2
- package/dist/types/wallet/serviceWorker/request.d.ts +1 -7
- package/dist/types/wallet/serviceWorker/response.d.ts +1 -8
- package/dist/types/wallet/serviceWorker/wallet.d.ts +67 -21
- package/dist/types/wallet/serviceWorker/worker.d.ts +17 -4
- package/dist/types/wallet/wallet.d.ts +4 -0
- package/package.json +38 -14
- package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +0 -185
- package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +0 -181
- package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +0 -20
- package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +0 -14
- /package/dist/cjs/{wallet/serviceWorker/db/vtxo → storage}/index.js +0 -0
- /package/dist/esm/{wallet/serviceWorker/db/vtxo → storage}/index.js +0 -0
|
@@ -3,93 +3,91 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ServiceWorkerWallet = void 0;
|
|
4
4
|
const response_1 = require("./response");
|
|
5
5
|
const base_1 = require("@scure/base");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
const indexedDB_1 = require("../../storage/indexedDB");
|
|
7
|
+
const walletRepository_1 = require("../../repositories/walletRepository");
|
|
8
|
+
const contractRepository_1 = require("../../repositories/contractRepository");
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
const isPrivateKeyIdentity = (identity) => {
|
|
11
|
+
return (identity.toHex !== undefined &&
|
|
12
|
+
typeof identity.toHex === "function" &&
|
|
13
|
+
typeof identity.toHex() === "string" &&
|
|
14
|
+
identity.toHex().length > 0);
|
|
15
|
+
};
|
|
9
16
|
class UnexpectedResponseError extends Error {
|
|
10
17
|
constructor(response) {
|
|
11
18
|
super(`Unexpected response type. Got: ${JSON.stringify(response, null, 2)}`);
|
|
12
19
|
this.name = "UnexpectedResponseError";
|
|
13
20
|
}
|
|
14
21
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Service Worker-based wallet implementation for browser environments.
|
|
17
|
-
*
|
|
18
|
-
* This wallet uses a service worker as a backend to handle wallet logic,
|
|
19
|
-
* providing secure key storage and transaction signing in web applications.
|
|
20
|
-
* The service worker runs in a separate thread and can persist data between
|
|
21
|
-
* browser sessions.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```typescript
|
|
25
|
-
* // Create and initialize the service worker wallet
|
|
26
|
-
* const serviceWorker = await setupServiceWorker("/service-worker.js");
|
|
27
|
-
* const wallet = new ServiceWorkerWallet(serviceWorker);
|
|
28
|
-
* await wallet.init({
|
|
29
|
-
* privateKey: 'your_private_key_hex',
|
|
30
|
-
* arkServerUrl: 'https://ark.example.com'
|
|
31
|
-
* });
|
|
32
|
-
*
|
|
33
|
-
* // Use like any other wallet
|
|
34
|
-
* const address = await wallet.getAddress();
|
|
35
|
-
* const balance = await wallet.getBalance();
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
22
|
class ServiceWorkerWallet {
|
|
39
|
-
constructor(serviceWorker) {
|
|
23
|
+
constructor(serviceWorker, identity, walletRepository, contractRepository) {
|
|
40
24
|
this.serviceWorker = serviceWorker;
|
|
25
|
+
this.identity = identity;
|
|
26
|
+
this.walletRepository = walletRepository;
|
|
27
|
+
this.contractRepository = contractRepository;
|
|
41
28
|
}
|
|
42
|
-
async
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
throw new UnexpectedResponseError(response);
|
|
55
|
-
}
|
|
56
|
-
async init(config, failIfInitialized = false) {
|
|
57
|
-
// Check if wallet is already initialized
|
|
58
|
-
const statusMessage = {
|
|
59
|
-
type: "GET_STATUS",
|
|
60
|
-
id: getRandomId(),
|
|
61
|
-
};
|
|
62
|
-
const response = await this.sendMessage(statusMessage);
|
|
63
|
-
if (response_1.Response.isWalletStatus(response) &&
|
|
64
|
-
response.status.walletInitialized) {
|
|
65
|
-
if (failIfInitialized) {
|
|
66
|
-
throw new Error("Wallet already initialized");
|
|
67
|
-
}
|
|
68
|
-
this.cachedXOnlyPublicKey = response.status.xOnlyPublicKey;
|
|
69
|
-
return;
|
|
29
|
+
static async create(options) {
|
|
30
|
+
// Default to IndexedDB for service worker context
|
|
31
|
+
const storage = options.storage || new indexedDB_1.IndexedDBStorageAdapter("wallet-db");
|
|
32
|
+
// Create repositories
|
|
33
|
+
const walletRepo = new walletRepository_1.WalletRepositoryImpl(storage);
|
|
34
|
+
const contractRepo = new contractRepository_1.ContractRepositoryImpl(storage);
|
|
35
|
+
// Extract identity and check if it can expose private key
|
|
36
|
+
const identity = isPrivateKeyIdentity(options.identity)
|
|
37
|
+
? options.identity
|
|
38
|
+
: null;
|
|
39
|
+
if (!identity) {
|
|
40
|
+
throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose its private key");
|
|
70
41
|
}
|
|
71
|
-
//
|
|
72
|
-
const
|
|
42
|
+
// Extract private key for service worker initialization
|
|
43
|
+
const privateKey = identity.toHex();
|
|
44
|
+
// Create the wallet instance
|
|
45
|
+
const wallet = new ServiceWorkerWallet(options.serviceWorker, identity, walletRepo, contractRepo);
|
|
46
|
+
// Initialize the service worker with the config
|
|
47
|
+
const initMessage = {
|
|
73
48
|
type: "INIT_WALLET",
|
|
74
49
|
id: getRandomId(),
|
|
75
|
-
privateKey
|
|
76
|
-
arkServerUrl:
|
|
77
|
-
arkServerPublicKey:
|
|
50
|
+
privateKey,
|
|
51
|
+
arkServerUrl: options.arkServerUrl,
|
|
52
|
+
arkServerPublicKey: options.arkServerPublicKey,
|
|
78
53
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.cachedXOnlyPublicKey =
|
|
83
|
-
singleKey_1.SingleKey.fromPrivateKey(privKeyBytes).xOnlyPublicKey();
|
|
54
|
+
// Initialize the service worker
|
|
55
|
+
await wallet.sendMessage(initMessage);
|
|
56
|
+
return wallet;
|
|
84
57
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Simplified setup method that handles service worker registration,
|
|
60
|
+
* identity creation, and wallet initialization automatically.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // One-liner setup - handles everything automatically!
|
|
65
|
+
* const wallet = await ServiceWorkerWallet.setup({
|
|
66
|
+
* serviceWorkerPath: '/service-worker.js',
|
|
67
|
+
* arkServerUrl: 'https://mutinynet.arkade.sh'
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* // With custom identity
|
|
71
|
+
* const identity = SingleKey.fromHex('your_private_key_hex');
|
|
72
|
+
* const wallet = await ServiceWorkerWallet.setup({
|
|
73
|
+
* serviceWorkerPath: '/service-worker.js',
|
|
74
|
+
* arkServerUrl: 'https://mutinynet.arkade.sh',
|
|
75
|
+
* identity
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
static async setup(options) {
|
|
80
|
+
// Register and setup the service worker
|
|
81
|
+
const serviceWorker = await (0, utils_1.setupServiceWorker)(options.serviceWorkerPath);
|
|
82
|
+
// Use the existing create method
|
|
83
|
+
return await ServiceWorkerWallet.create({
|
|
84
|
+
arkServerPublicKey: options.arkServerPublicKey,
|
|
85
|
+
arkServerUrl: options.arkServerUrl,
|
|
86
|
+
esploraUrl: options.esploraUrl,
|
|
87
|
+
identity: options.identity,
|
|
88
|
+
serviceWorker,
|
|
89
|
+
storage: options.storage,
|
|
90
|
+
});
|
|
93
91
|
}
|
|
94
92
|
// send a message and wait for a response
|
|
95
93
|
async sendMessage(message) {
|
|
@@ -115,6 +113,21 @@ class ServiceWorkerWallet {
|
|
|
115
113
|
this.serviceWorker.postMessage(message);
|
|
116
114
|
});
|
|
117
115
|
}
|
|
116
|
+
async clear() {
|
|
117
|
+
const message = {
|
|
118
|
+
type: "CLEAR",
|
|
119
|
+
id: getRandomId(),
|
|
120
|
+
};
|
|
121
|
+
// Clear page-side storage to maintain parity with SW
|
|
122
|
+
try {
|
|
123
|
+
const address = await this.getAddress();
|
|
124
|
+
await this.walletRepository.clearVtxos(address);
|
|
125
|
+
}
|
|
126
|
+
catch (_) {
|
|
127
|
+
console.warn("Failed to clear vtxos from wallet repository");
|
|
128
|
+
}
|
|
129
|
+
await this.sendMessage(message);
|
|
130
|
+
}
|
|
118
131
|
async getAddress() {
|
|
119
132
|
const message = {
|
|
120
133
|
type: "GET_ADDRESS",
|
|
@@ -163,37 +176,64 @@ class ServiceWorkerWallet {
|
|
|
163
176
|
throw new Error(`Failed to get balance: ${error}`);
|
|
164
177
|
}
|
|
165
178
|
}
|
|
166
|
-
async
|
|
179
|
+
async getBoardingUtxos() {
|
|
167
180
|
const message = {
|
|
168
|
-
type: "
|
|
181
|
+
type: "GET_BOARDING_UTXOS",
|
|
169
182
|
id: getRandomId(),
|
|
170
|
-
filter,
|
|
171
183
|
};
|
|
172
184
|
try {
|
|
173
185
|
const response = await this.sendMessage(message);
|
|
174
|
-
if (response_1.Response.
|
|
175
|
-
return response.
|
|
186
|
+
if (response_1.Response.isBoardingUtxos(response)) {
|
|
187
|
+
return response.boardingUtxos;
|
|
176
188
|
}
|
|
177
189
|
throw new UnexpectedResponseError(response);
|
|
178
190
|
}
|
|
179
191
|
catch (error) {
|
|
180
|
-
throw new Error(`Failed to get
|
|
192
|
+
throw new Error(`Failed to get boarding UTXOs: ${error}`);
|
|
181
193
|
}
|
|
182
194
|
}
|
|
183
|
-
async
|
|
195
|
+
async getStatus() {
|
|
184
196
|
const message = {
|
|
185
|
-
type: "
|
|
197
|
+
type: "GET_STATUS",
|
|
198
|
+
id: getRandomId(),
|
|
199
|
+
};
|
|
200
|
+
const response = await this.sendMessage(message);
|
|
201
|
+
if (response_1.Response.isWalletStatus(response)) {
|
|
202
|
+
return response.status;
|
|
203
|
+
}
|
|
204
|
+
throw new UnexpectedResponseError(response);
|
|
205
|
+
}
|
|
206
|
+
async getTransactionHistory() {
|
|
207
|
+
const message = {
|
|
208
|
+
type: "GET_TRANSACTION_HISTORY",
|
|
186
209
|
id: getRandomId(),
|
|
187
210
|
};
|
|
188
211
|
try {
|
|
189
212
|
const response = await this.sendMessage(message);
|
|
190
|
-
if (response_1.Response.
|
|
191
|
-
return response.
|
|
213
|
+
if (response_1.Response.isTransactionHistory(response)) {
|
|
214
|
+
return response.transactions;
|
|
192
215
|
}
|
|
193
216
|
throw new UnexpectedResponseError(response);
|
|
194
217
|
}
|
|
195
218
|
catch (error) {
|
|
196
|
-
throw new Error(`Failed to get
|
|
219
|
+
throw new Error(`Failed to get transaction history: ${error}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async getVtxos(filter) {
|
|
223
|
+
const message = {
|
|
224
|
+
type: "GET_VTXOS",
|
|
225
|
+
id: getRandomId(),
|
|
226
|
+
filter,
|
|
227
|
+
};
|
|
228
|
+
try {
|
|
229
|
+
const response = await this.sendMessage(message);
|
|
230
|
+
if (response_1.Response.isVtxos(response)) {
|
|
231
|
+
return response.vtxos;
|
|
232
|
+
}
|
|
233
|
+
throw new UnexpectedResponseError(response);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
throw new Error(`Failed to get vtxos: ${error}`);
|
|
197
237
|
}
|
|
198
238
|
}
|
|
199
239
|
async sendBitcoin(params) {
|
|
@@ -250,52 +290,6 @@ class ServiceWorkerWallet {
|
|
|
250
290
|
throw new Error(`Settlement failed: ${error}`);
|
|
251
291
|
}
|
|
252
292
|
}
|
|
253
|
-
async getTransactionHistory() {
|
|
254
|
-
const message = {
|
|
255
|
-
type: "GET_TRANSACTION_HISTORY",
|
|
256
|
-
id: getRandomId(),
|
|
257
|
-
};
|
|
258
|
-
try {
|
|
259
|
-
const response = await this.sendMessage(message);
|
|
260
|
-
if (response_1.Response.isTransactionHistory(response)) {
|
|
261
|
-
return response.transactions;
|
|
262
|
-
}
|
|
263
|
-
throw new UnexpectedResponseError(response);
|
|
264
|
-
}
|
|
265
|
-
catch (error) {
|
|
266
|
-
throw new Error(`Failed to get transaction history: ${error}`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
xOnlyPublicKey() {
|
|
270
|
-
if (!this.cachedXOnlyPublicKey) {
|
|
271
|
-
throw new Error("Wallet not initialized");
|
|
272
|
-
}
|
|
273
|
-
return this.cachedXOnlyPublicKey;
|
|
274
|
-
}
|
|
275
|
-
signerSession() {
|
|
276
|
-
return signingSession_1.TreeSignerSession.random();
|
|
277
|
-
}
|
|
278
|
-
async sign(tx, inputIndexes) {
|
|
279
|
-
const message = {
|
|
280
|
-
type: "SIGN",
|
|
281
|
-
tx: base_1.base64.encode(tx.toPSBT()),
|
|
282
|
-
inputIndexes,
|
|
283
|
-
id: getRandomId(),
|
|
284
|
-
};
|
|
285
|
-
try {
|
|
286
|
-
const response = await this.sendMessage(message);
|
|
287
|
-
if (response_1.Response.isSignSuccess(response)) {
|
|
288
|
-
return btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(response.tx), {
|
|
289
|
-
allowUnknown: true,
|
|
290
|
-
allowUnknownInputs: true,
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
throw new UnexpectedResponseError(response);
|
|
294
|
-
}
|
|
295
|
-
catch (error) {
|
|
296
|
-
throw new Error(`Failed to sign: ${error}`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
293
|
}
|
|
300
294
|
exports.ServiceWorkerWallet = ServiceWorkerWallet;
|
|
301
295
|
function getRandomId() {
|
|
@@ -8,19 +8,53 @@ const wallet_1 = require("../wallet");
|
|
|
8
8
|
const request_1 = require("./request");
|
|
9
9
|
const response_1 = require("./response");
|
|
10
10
|
const ark_1 = require("../../providers/ark");
|
|
11
|
-
const idb_1 = require("./db/vtxo/idb");
|
|
12
11
|
const transactionHistory_1 = require("../../utils/transactionHistory");
|
|
13
12
|
const indexer_1 = require("../../providers/indexer");
|
|
14
13
|
const base_1 = require("@scure/base");
|
|
15
|
-
const
|
|
14
|
+
const indexedDB_1 = require("../../storage/indexedDB");
|
|
15
|
+
const walletRepository_1 = require("../../repositories/walletRepository");
|
|
16
16
|
/**
|
|
17
17
|
* Worker is a class letting to interact with ServiceWorkerWallet from the client
|
|
18
18
|
* it aims to be run in a service worker context
|
|
19
19
|
*/
|
|
20
20
|
class Worker {
|
|
21
|
-
constructor(
|
|
22
|
-
this.vtxoRepository = vtxoRepository;
|
|
21
|
+
constructor(messageCallback = () => { }) {
|
|
23
22
|
this.messageCallback = messageCallback;
|
|
23
|
+
this.storage = new indexedDB_1.IndexedDBStorageAdapter("arkade-service-worker", 1);
|
|
24
|
+
this.walletRepository = new walletRepository_1.WalletRepositoryImpl(this.storage);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get spendable vtxos for the current wallet address
|
|
28
|
+
*/
|
|
29
|
+
async getSpendableVtxos() {
|
|
30
|
+
if (!this.wallet)
|
|
31
|
+
return [];
|
|
32
|
+
const address = await this.wallet.getAddress();
|
|
33
|
+
const allVtxos = await this.walletRepository.getVtxos(address);
|
|
34
|
+
return allVtxos.filter(__1.isSpendable);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get swept vtxos for the current wallet address
|
|
38
|
+
*/
|
|
39
|
+
async getSweptVtxos() {
|
|
40
|
+
if (!this.wallet)
|
|
41
|
+
return [];
|
|
42
|
+
const address = await this.wallet.getAddress();
|
|
43
|
+
const allVtxos = await this.walletRepository.getVtxos(address);
|
|
44
|
+
return allVtxos.filter((vtxo) => vtxo.virtualStatus.state === "swept" && (0, __1.isSpendable)(vtxo));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get all vtxos categorized by type
|
|
48
|
+
*/
|
|
49
|
+
async getAllVtxos() {
|
|
50
|
+
if (!this.wallet)
|
|
51
|
+
return { spendable: [], spent: [] };
|
|
52
|
+
const address = await this.wallet.getAddress();
|
|
53
|
+
const allVtxos = await this.walletRepository.getVtxos(address);
|
|
54
|
+
return {
|
|
55
|
+
spendable: allVtxos.filter(__1.isSpendable),
|
|
56
|
+
spent: allVtxos.filter((vtxo) => !(0, __1.isSpendable)(vtxo)),
|
|
57
|
+
};
|
|
24
58
|
}
|
|
25
59
|
async start(withServiceWorkerUpdate = true) {
|
|
26
60
|
self.addEventListener("message", async (event) => {
|
|
@@ -41,12 +75,18 @@ class Worker {
|
|
|
41
75
|
if (this.vtxoSubscription) {
|
|
42
76
|
this.vtxoSubscription.abort();
|
|
43
77
|
}
|
|
44
|
-
|
|
78
|
+
// Clear storage - this replaces vtxoRepository.close()
|
|
79
|
+
await this.storage.clear();
|
|
45
80
|
this.wallet = undefined;
|
|
46
81
|
this.arkProvider = undefined;
|
|
47
82
|
this.indexerProvider = undefined;
|
|
48
83
|
this.vtxoSubscription = undefined;
|
|
49
84
|
}
|
|
85
|
+
async reload() {
|
|
86
|
+
if (this.vtxoSubscription)
|
|
87
|
+
this.vtxoSubscription.abort();
|
|
88
|
+
await this.onWalletInitialized();
|
|
89
|
+
}
|
|
50
90
|
async onWalletInitialized() {
|
|
51
91
|
if (!this.wallet ||
|
|
52
92
|
!this.arkProvider ||
|
|
@@ -55,8 +95,6 @@ class Worker {
|
|
|
55
95
|
!this.wallet.boardingTapscript) {
|
|
56
96
|
return;
|
|
57
97
|
}
|
|
58
|
-
// subscribe to address updates
|
|
59
|
-
await this.vtxoRepository.open();
|
|
60
98
|
const encodedOffchainTapscript = this.wallet.offchainTapscript.encode();
|
|
61
99
|
const forfeit = this.wallet.offchainTapscript.forfeit();
|
|
62
100
|
const exit = this.wallet.offchainTapscript.exit();
|
|
@@ -71,7 +109,9 @@ class Worker {
|
|
|
71
109
|
intentTapLeafScript: exit,
|
|
72
110
|
tapTree: encodedOffchainTapscript,
|
|
73
111
|
}));
|
|
74
|
-
|
|
112
|
+
// Get wallet address and save vtxos using unified repository
|
|
113
|
+
const address = await this.wallet.getAddress();
|
|
114
|
+
await this.walletRepository.saveVtxos(address, vtxos);
|
|
75
115
|
this.processVtxoSubscription({
|
|
76
116
|
script,
|
|
77
117
|
vtxoScript: this.wallet.offchainTapscript,
|
|
@@ -97,7 +137,11 @@ class Worker {
|
|
|
97
137
|
intentTapLeafScript,
|
|
98
138
|
tapTree,
|
|
99
139
|
}));
|
|
100
|
-
|
|
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", "");
|
|
101
145
|
}
|
|
102
146
|
}
|
|
103
147
|
catch (error) {
|
|
@@ -105,7 +149,7 @@ class Worker {
|
|
|
105
149
|
}
|
|
106
150
|
}
|
|
107
151
|
async handleClear(event) {
|
|
108
|
-
this.clear();
|
|
152
|
+
await this.clear();
|
|
109
153
|
if (request_1.Request.isBase(event.data)) {
|
|
110
154
|
event.source?.postMessage(response_1.Response.clearResponse(event.data.id, true));
|
|
111
155
|
}
|
|
@@ -117,13 +161,22 @@ class Worker {
|
|
|
117
161
|
event.source?.postMessage(response_1.Response.error(message.id, "Invalid INIT_WALLET message format"));
|
|
118
162
|
return;
|
|
119
163
|
}
|
|
164
|
+
if (!message.privateKey) {
|
|
165
|
+
const err = "Missing privateKey";
|
|
166
|
+
event.source?.postMessage(response_1.Response.error(message.id, err));
|
|
167
|
+
console.error(err);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
120
170
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
171
|
+
const { arkServerPublicKey, arkServerUrl, privateKey } = message;
|
|
172
|
+
const identity = singleKey_1.SingleKey.fromHex(privateKey);
|
|
173
|
+
this.arkProvider = new ark_1.RestArkProvider(arkServerUrl);
|
|
174
|
+
this.indexerProvider = new indexer_1.RestIndexerProvider(arkServerUrl);
|
|
123
175
|
this.wallet = await wallet_1.Wallet.create({
|
|
124
|
-
identity
|
|
125
|
-
arkServerUrl
|
|
126
|
-
arkServerPublicKey
|
|
176
|
+
identity,
|
|
177
|
+
arkServerUrl,
|
|
178
|
+
arkServerPublicKey,
|
|
179
|
+
storage: this.storage, // Use unified storage for wallet too
|
|
127
180
|
});
|
|
128
181
|
event.source?.postMessage(response_1.Response.walletInitialized(message.id));
|
|
129
182
|
await this.onWalletInitialized();
|
|
@@ -249,8 +302,8 @@ class Worker {
|
|
|
249
302
|
try {
|
|
250
303
|
const [boardingUtxos, spendableVtxos, sweptVtxos] = await Promise.all([
|
|
251
304
|
this.wallet.getBoardingUtxos(),
|
|
252
|
-
this.
|
|
253
|
-
this.
|
|
305
|
+
this.getSpendableVtxos(),
|
|
306
|
+
this.getSweptVtxos(),
|
|
254
307
|
]);
|
|
255
308
|
// boarding
|
|
256
309
|
let confirmed = 0;
|
|
@@ -316,7 +369,7 @@ class Worker {
|
|
|
316
369
|
return;
|
|
317
370
|
}
|
|
318
371
|
try {
|
|
319
|
-
let vtxos = await this.
|
|
372
|
+
let vtxos = await this.getSpendableVtxos();
|
|
320
373
|
if (!message.filter?.withRecoverable) {
|
|
321
374
|
if (!this.wallet)
|
|
322
375
|
throw new Error("Wallet not initialized");
|
|
@@ -325,7 +378,7 @@ class Worker {
|
|
|
325
378
|
}
|
|
326
379
|
if (message.filter?.withRecoverable) {
|
|
327
380
|
// get also swept and spendable vtxos
|
|
328
|
-
const sweptVtxos = await this.
|
|
381
|
+
const sweptVtxos = await this.getSweptVtxos();
|
|
329
382
|
vtxos.push(...sweptVtxos.filter(__1.isSpendable));
|
|
330
383
|
}
|
|
331
384
|
event.source?.postMessage(response_1.Response.vtxos(message.id, vtxos));
|
|
@@ -376,7 +429,7 @@ class Worker {
|
|
|
376
429
|
}
|
|
377
430
|
try {
|
|
378
431
|
const { boardingTxs, commitmentsToIgnore: roundsToIgnore } = await this.wallet.getBoardingTxs();
|
|
379
|
-
const { spendable, spent } = await this.
|
|
432
|
+
const { spendable, spent } = await this.getAllVtxos();
|
|
380
433
|
// convert VTXOs to offchain transactions
|
|
381
434
|
const offchainTxs = (0, transactionHistory_1.vtxosToTxs)(spendable, spent, roundsToIgnore);
|
|
382
435
|
const txs = [...boardingTxs, ...offchainTxs];
|
|
@@ -407,35 +460,10 @@ class Worker {
|
|
|
407
460
|
event.source?.postMessage(response_1.Response.error(message.id, "Invalid GET_STATUS message format"));
|
|
408
461
|
return;
|
|
409
462
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (!request_1.Request.isSign(message)) {
|
|
415
|
-
console.error("Invalid SIGN message format", message);
|
|
416
|
-
event.source?.postMessage(response_1.Response.error(message.id, "Invalid SIGN message format"));
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
if (!this.wallet) {
|
|
420
|
-
console.error("Wallet not initialized");
|
|
421
|
-
event.source?.postMessage(response_1.Response.error(message.id, "Wallet not initialized"));
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
try {
|
|
425
|
-
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(message.tx), {
|
|
426
|
-
allowUnknown: true,
|
|
427
|
-
allowUnknownInputs: true,
|
|
428
|
-
});
|
|
429
|
-
const signedTx = await this.wallet.identity.sign(tx, message.inputIndexes);
|
|
430
|
-
event.source?.postMessage(response_1.Response.signSuccess(message.id, base_1.base64.encode(signedTx.toPSBT())));
|
|
431
|
-
}
|
|
432
|
-
catch (error) {
|
|
433
|
-
console.error("Error signing:", error);
|
|
434
|
-
const errorMessage = error instanceof Error
|
|
435
|
-
? error.message
|
|
436
|
-
: "Unknown error occurred";
|
|
437
|
-
event.source?.postMessage(response_1.Response.error(message.id, errorMessage));
|
|
438
|
-
}
|
|
463
|
+
const pubKey = this.wallet
|
|
464
|
+
? await this.wallet.identity.xOnlyPublicKey()
|
|
465
|
+
: undefined;
|
|
466
|
+
event.source?.postMessage(response_1.Response.walletStatus(message.id, this.wallet !== undefined, pubKey));
|
|
439
467
|
}
|
|
440
468
|
async handleMessage(event) {
|
|
441
469
|
this.messageCallback(event);
|
|
@@ -490,13 +518,21 @@ class Worker {
|
|
|
490
518
|
await this.handleClear(event);
|
|
491
519
|
break;
|
|
492
520
|
}
|
|
493
|
-
case "SIGN": {
|
|
494
|
-
await this.handleSign(event);
|
|
495
|
-
break;
|
|
496
|
-
}
|
|
497
521
|
default:
|
|
498
522
|
event.source?.postMessage(response_1.Response.error(message.id, "Unknown message type"));
|
|
499
523
|
}
|
|
500
524
|
}
|
|
525
|
+
async sendMessageToAllClients(type, message) {
|
|
526
|
+
self.clients
|
|
527
|
+
.matchAll({ includeUncontrolled: true, type: "window" })
|
|
528
|
+
.then((clients) => {
|
|
529
|
+
clients.forEach((client) => {
|
|
530
|
+
client.postMessage({
|
|
531
|
+
type,
|
|
532
|
+
message,
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
}
|
|
501
537
|
}
|
|
502
538
|
exports.Worker = Worker;
|
|
@@ -57,6 +57,9 @@ const arknote_1 = require("../arknote");
|
|
|
57
57
|
const bip322_1 = require("../bip322");
|
|
58
58
|
const indexer_1 = require("../providers/indexer");
|
|
59
59
|
const txTree_1 = require("../tree/txTree");
|
|
60
|
+
const inMemory_1 = require("../storage/inMemory");
|
|
61
|
+
const walletRepository_1 = require("../repositories/walletRepository");
|
|
62
|
+
const contractRepository_1 = require("../repositories/contractRepository");
|
|
60
63
|
/**
|
|
61
64
|
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|
|
62
65
|
* The wallet does not store any data locally and relies on Ark and onchain
|
|
@@ -83,7 +86,7 @@ const txTree_1 = require("../tree/txTree");
|
|
|
83
86
|
* ```
|
|
84
87
|
*/
|
|
85
88
|
class Wallet {
|
|
86
|
-
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, dustAmount) {
|
|
89
|
+
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, dustAmount, walletRepository, contractRepository) {
|
|
87
90
|
this.identity = identity;
|
|
88
91
|
this.network = network;
|
|
89
92
|
this.networkName = networkName;
|
|
@@ -96,9 +99,11 @@ class Wallet {
|
|
|
96
99
|
this.serverUnrollScript = serverUnrollScript;
|
|
97
100
|
this.forfeitOutputScript = forfeitOutputScript;
|
|
98
101
|
this.dustAmount = dustAmount;
|
|
102
|
+
this.walletRepository = walletRepository;
|
|
103
|
+
this.contractRepository = contractRepository;
|
|
99
104
|
}
|
|
100
105
|
static async create(config) {
|
|
101
|
-
const pubkey = config.identity.xOnlyPublicKey();
|
|
106
|
+
const pubkey = await config.identity.xOnlyPublicKey();
|
|
102
107
|
if (!pubkey) {
|
|
103
108
|
throw new Error("Invalid configured public key");
|
|
104
109
|
}
|
|
@@ -138,7 +143,11 @@ class Wallet {
|
|
|
138
143
|
// server is expecting funds to be sent to this address
|
|
139
144
|
const forfeitAddress = (0, payment_1.Address)(network).decode(info.forfeitAddress);
|
|
140
145
|
const forfeitOutputScript = payment_1.OutScript.encode(forfeitAddress);
|
|
141
|
-
|
|
146
|
+
// Set up storage and repositories
|
|
147
|
+
const storage = config.storage || new inMemory_1.InMemoryStorageAdapter();
|
|
148
|
+
const walletRepository = new walletRepository_1.WalletRepositoryImpl(storage);
|
|
149
|
+
const contractRepository = new contractRepository_1.ContractRepositoryImpl(storage);
|
|
150
|
+
return new Wallet(config.identity, network, info.network, onchainProvider, arkProvider, indexerProvider, serverPubKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, info.dust, walletRepository, contractRepository);
|
|
142
151
|
}
|
|
143
152
|
get arkAddress() {
|
|
144
153
|
return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
|
|
@@ -194,16 +203,24 @@ class Wallet {
|
|
|
194
203
|
};
|
|
195
204
|
}
|
|
196
205
|
async getVtxos(filter) {
|
|
206
|
+
const address = await this.getAddress();
|
|
207
|
+
// Try to get from cache first
|
|
208
|
+
const cachedVtxos = await this.walletRepository.getVtxos(address);
|
|
209
|
+
// For now, always fetch fresh data from provider and update cache
|
|
210
|
+
// In future, we can add cache invalidation logic based on timestamps
|
|
197
211
|
const spendableVtxos = await this.getVirtualCoins(filter);
|
|
198
212
|
const encodedOffchainTapscript = this.offchainTapscript.encode();
|
|
199
213
|
const forfeit = this.offchainTapscript.forfeit();
|
|
200
214
|
const exit = this.offchainTapscript.exit();
|
|
201
|
-
|
|
215
|
+
const extendedVtxos = spendableVtxos.map((vtxo) => ({
|
|
202
216
|
...vtxo,
|
|
203
217
|
forfeitTapLeafScript: forfeit,
|
|
204
218
|
intentTapLeafScript: exit,
|
|
205
219
|
tapTree: encodedOffchainTapscript,
|
|
206
220
|
}));
|
|
221
|
+
// Update cache with fresh data
|
|
222
|
+
await this.walletRepository.saveVtxos(address, extendedVtxos);
|
|
223
|
+
return extendedVtxos;
|
|
207
224
|
}
|
|
208
225
|
async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
|
|
209
226
|
const scripts = [base_1.hex.encode(this.offchainTapscript.pkScript)];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AsyncStorageAdapter } from '../storage/asyncStorage.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileSystemStorageAdapter } from '../storage/fileSystem.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { IndexedDBStorageAdapter } from '../storage/indexedDB.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LocalStorageAdapter } from '../storage/localStorage.js';
|
package/dist/esm/bip322/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OP, Transaction, Script, SigHash } from "@scure/btc-signer";
|
|
2
2
|
import { ErrMissingData, ErrMissingInputs, ErrMissingWitnessUtxo, } from './errors.js';
|
|
3
|
-
import { schnorr } from "@noble/curves/secp256k1";
|
|
3
|
+
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
4
4
|
import { base64 } from "@scure/base";
|
|
5
5
|
/**
|
|
6
6
|
* BIP-322 signature implementation for Bitcoin message signing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './singleKey.js';
|