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