@arkade-os/sdk 0.1.0 → 0.1.2
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 +20 -3
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/providers/ark.js +16 -0
- package/dist/cjs/utils/psbt.js +16 -3
- package/dist/cjs/wallet/serviceWorker/utils.js +48 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +4 -80
- package/dist/cjs/wallet/serviceWorker/worker.js +11 -1
- package/dist/cjs/wallet/wallet.js +0 -1
- package/dist/esm/index.js +3 -4
- package/dist/esm/providers/ark.js +16 -0
- package/dist/esm/utils/psbt.js +16 -3
- package/dist/esm/wallet/serviceWorker/utils.js +45 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +4 -80
- package/dist/esm/wallet/serviceWorker/worker.js +11 -1
- package/dist/esm/wallet/wallet.js +0 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/wallet/serviceWorker/utils.d.ts +1 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -3
- package/dist/types/wallet/serviceWorker/worker.d.ts +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -117,18 +117,26 @@ new Worker().start()
|
|
|
117
117
|
```typescript
|
|
118
118
|
// specify the path to the service worker file
|
|
119
119
|
// this will automatically register the service worker
|
|
120
|
-
const
|
|
120
|
+
const serviceWorker = await setupServiceWorker('/service-worker.js')
|
|
121
|
+
const wallet = new ServiceWorkerWallet(serviceWorker)
|
|
121
122
|
|
|
122
123
|
// initialize the wallet
|
|
123
124
|
await wallet.init({
|
|
124
125
|
network: 'mutinynet', // 'bitcoin', 'testnet', 'regtest', 'signet' or 'mutinynet'
|
|
125
|
-
|
|
126
|
+
privateKey: 'your_private_key_hex',
|
|
126
127
|
// Esplora API, can be left empty mempool.space API will be used
|
|
127
128
|
esploraUrl: 'https://mutinynet.com/api',
|
|
128
129
|
// OPTIONAL Ark Server connection information
|
|
129
130
|
arkServerUrl: 'https://mutinynet.arkade.sh',
|
|
130
131
|
arkServerPublicKey: 'fa73c6e4876ffb2dfc961d763cca9abc73d4b88efcb8f5e7ff92dc55e9aa553d'
|
|
131
132
|
})
|
|
133
|
+
|
|
134
|
+
// check service worker status
|
|
135
|
+
const status = await wallet.getStatus()
|
|
136
|
+
console.log('Service worker status:', status.walletInitialized)
|
|
137
|
+
|
|
138
|
+
// clear wallet data stored in the service worker memory
|
|
139
|
+
await wallet.clear()
|
|
132
140
|
```
|
|
133
141
|
|
|
134
142
|
## API Reference
|
|
@@ -149,6 +157,10 @@ interface WalletConfig {
|
|
|
149
157
|
arkServerUrl?: string;
|
|
150
158
|
/** Ark server public key (optional) */
|
|
151
159
|
arkServerPublicKey?: string;
|
|
160
|
+
/** Optional boarding timelock configuration */
|
|
161
|
+
boardingTimelock?: RelativeTimelock;
|
|
162
|
+
/** Optional exit timelock configuration */
|
|
163
|
+
exitTimelock?: RelativeTimelock;
|
|
152
164
|
}
|
|
153
165
|
```
|
|
154
166
|
|
|
@@ -175,7 +187,9 @@ interface IWallet {
|
|
|
175
187
|
total: number;
|
|
176
188
|
settled: number;
|
|
177
189
|
pending: number;
|
|
190
|
+
swept: number;
|
|
178
191
|
};
|
|
192
|
+
total: number;
|
|
179
193
|
}>;
|
|
180
194
|
|
|
181
195
|
/** Send bitcoin (on-chain or off-chain) */
|
|
@@ -183,7 +197,8 @@ interface IWallet {
|
|
|
183
197
|
address: string;
|
|
184
198
|
amount: number;
|
|
185
199
|
feeRate?: number;
|
|
186
|
-
|
|
200
|
+
memo?: string;
|
|
201
|
+
}, zeroFee?: boolean): Promise<string>;
|
|
187
202
|
|
|
188
203
|
/** Get virtual UTXOs */
|
|
189
204
|
getVtxos(): Promise<VirtualCoin[]>;
|
|
@@ -228,6 +243,8 @@ interface VirtualCoin {
|
|
|
228
243
|
virtualStatus: {
|
|
229
244
|
state: 'pending' | 'settled';
|
|
230
245
|
};
|
|
246
|
+
spentBy?: string;
|
|
247
|
+
createdAt: Date;
|
|
231
248
|
}
|
|
232
249
|
|
|
233
250
|
/** Boarding UTXO */
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.IndexedDBVtxoRepository = exports.networks = exports.ArkNoteData = exports.ArkNote = exports.createVirtualTx = exports.CONDITION_WITNESS_KEY_PREFIX = exports.addConditionWitness = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.Worker = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.InMemoryKey = exports.
|
|
3
|
+
exports.IndexedDBVtxoRepository = exports.networks = exports.ArkNoteData = exports.ArkNote = exports.createVirtualTx = exports.CONDITION_WITNESS_KEY_PREFIX = exports.addConditionWitness = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.InMemoryKey = exports.Wallet = void 0;
|
|
4
4
|
const inMemoryKey_1 = require("./identity/inMemoryKey");
|
|
5
5
|
Object.defineProperty(exports, "InMemoryKey", { enumerable: true, get: function () { return inMemoryKey_1.InMemoryKey; } });
|
|
6
6
|
const address_1 = require("./script/address");
|
|
@@ -17,6 +17,8 @@ const wallet_1 = require("./wallet/wallet");
|
|
|
17
17
|
Object.defineProperty(exports, "Wallet", { enumerable: true, get: function () { return wallet_1.Wallet; } });
|
|
18
18
|
const wallet_2 = require("./wallet/serviceWorker/wallet");
|
|
19
19
|
Object.defineProperty(exports, "ServiceWorkerWallet", { enumerable: true, get: function () { return wallet_2.ServiceWorkerWallet; } });
|
|
20
|
+
const utils_1 = require("./wallet/serviceWorker/utils");
|
|
21
|
+
Object.defineProperty(exports, "setupServiceWorker", { enumerable: true, get: function () { return utils_1.setupServiceWorker; } });
|
|
20
22
|
const worker_1 = require("./wallet/serviceWorker/worker");
|
|
21
23
|
Object.defineProperty(exports, "Worker", { enumerable: true, get: function () { return worker_1.Worker; } });
|
|
22
24
|
const request_1 = require("./wallet/serviceWorker/request");
|
|
@@ -368,6 +368,12 @@ class RestArkProvider {
|
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
370
|
catch (error) {
|
|
371
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
372
|
+
// these timeouts are set by builtin fetch function
|
|
373
|
+
if (isFetchTimeoutError(error)) {
|
|
374
|
+
console.debug("Timeout error ignored");
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
371
377
|
console.error("Address subscription error:", error);
|
|
372
378
|
throw error;
|
|
373
379
|
}
|
|
@@ -545,3 +551,13 @@ function convertVtxo(vtxo) {
|
|
|
545
551
|
createdAt: new Date(vtxo.createdAt * 1000),
|
|
546
552
|
};
|
|
547
553
|
}
|
|
554
|
+
function isFetchTimeoutError(err) {
|
|
555
|
+
const checkError = (error) => {
|
|
556
|
+
return (error instanceof Error &&
|
|
557
|
+
(error.name === "HeadersTimeoutError" ||
|
|
558
|
+
error.name === "BodyTimeoutError" ||
|
|
559
|
+
error.code === "UND_ERR_HEADERS_TIMEOUT" ||
|
|
560
|
+
error.code === "UND_ERR_BODY_TIMEOUT"));
|
|
561
|
+
};
|
|
562
|
+
return checkError(err) || checkError(err.cause);
|
|
563
|
+
}
|
package/dist/cjs/utils/psbt.js
CHANGED
|
@@ -43,16 +43,25 @@ function addConditionWitness(inIndex, tx, witness) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
function createVirtualTx(inputs, outputs) {
|
|
46
|
-
let lockTime;
|
|
46
|
+
let lockTime = 0n;
|
|
47
47
|
for (const input of inputs) {
|
|
48
48
|
const tapscript = (0, tapscript_1.decodeTapscript)((0, base_1.scriptFromTapLeafScript)(input.tapLeafScript));
|
|
49
49
|
if (tapscript_1.CLTVMultisigTapscript.is(tapscript)) {
|
|
50
|
-
lockTime
|
|
50
|
+
if (lockTime !== 0n) {
|
|
51
|
+
// if a locktime is already set, check if the new locktime is in the same unit
|
|
52
|
+
if (isSeconds(lockTime) !==
|
|
53
|
+
isSeconds(tapscript.params.absoluteTimelock)) {
|
|
54
|
+
throw new Error("cannot mix seconds and blocks locktime");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (tapscript.params.absoluteTimelock > lockTime) {
|
|
58
|
+
lockTime = tapscript.params.absoluteTimelock;
|
|
59
|
+
}
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
const tx = new btc_signer_1.Transaction({
|
|
54
63
|
allowUnknown: true,
|
|
55
|
-
lockTime,
|
|
64
|
+
lockTime: Number(lockTime),
|
|
56
65
|
});
|
|
57
66
|
for (const [i, input] of inputs.entries()) {
|
|
58
67
|
tx.addInput({
|
|
@@ -122,3 +131,7 @@ function encodeCompactSizeUint(value) {
|
|
|
122
131
|
return buffer;
|
|
123
132
|
}
|
|
124
133
|
}
|
|
134
|
+
const nLocktimeMinSeconds = 500000000n;
|
|
135
|
+
function isSeconds(locktime) {
|
|
136
|
+
return locktime >= nLocktimeMinSeconds;
|
|
137
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupServiceWorker = setupServiceWorker;
|
|
4
|
+
async function setupServiceWorker(path) {
|
|
5
|
+
// check if service workers are supported
|
|
6
|
+
if (!("serviceWorker" in navigator)) {
|
|
7
|
+
throw new Error("Service workers are not supported in this browser");
|
|
8
|
+
}
|
|
9
|
+
// check for existing registration
|
|
10
|
+
const existingRegistration = await navigator.serviceWorker.getRegistration(path);
|
|
11
|
+
let registration;
|
|
12
|
+
if (existingRegistration) {
|
|
13
|
+
registration = existingRegistration;
|
|
14
|
+
// Force unregister and re-register to ensure we get the latest version
|
|
15
|
+
await existingRegistration.unregister();
|
|
16
|
+
}
|
|
17
|
+
registration = await navigator.serviceWorker.register(path);
|
|
18
|
+
// Handle updates
|
|
19
|
+
registration.addEventListener("updatefound", () => {
|
|
20
|
+
const newWorker = registration.installing;
|
|
21
|
+
if (!newWorker)
|
|
22
|
+
return;
|
|
23
|
+
newWorker.addEventListener("statechange", () => {
|
|
24
|
+
if (newWorker.state === "activated" &&
|
|
25
|
+
navigator.serviceWorker.controller) {
|
|
26
|
+
console.info("Service worker activated, reloading...");
|
|
27
|
+
window.location.reload();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
const serviceWorker = registration.active || registration.waiting || registration.installing;
|
|
32
|
+
if (!serviceWorker) {
|
|
33
|
+
throw new Error("Failed to get service worker instance");
|
|
34
|
+
}
|
|
35
|
+
// wait for the service worker to be ready
|
|
36
|
+
if (serviceWorker.state !== "activated") {
|
|
37
|
+
await new Promise((resolve) => {
|
|
38
|
+
if (!serviceWorker)
|
|
39
|
+
return resolve();
|
|
40
|
+
serviceWorker.addEventListener("statechange", () => {
|
|
41
|
+
if (serviceWorker.state === "activated") {
|
|
42
|
+
resolve();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return serviceWorker;
|
|
48
|
+
}
|
|
@@ -11,15 +11,8 @@ class UnexpectedResponseError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
// ServiceWorkerWallet is a wallet that uses a service worker as "backend" to handle the wallet logic
|
|
13
13
|
class ServiceWorkerWallet {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const wallet = new ServiceWorkerWallet();
|
|
17
|
-
await wallet.setupServiceWorker(svcWorkerPath);
|
|
18
|
-
return wallet;
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
throw new Error(`Failed to initialize service worker wallet: ${error}`);
|
|
22
|
-
}
|
|
14
|
+
constructor(serviceWorker) {
|
|
15
|
+
this.serviceWorker = serviceWorker;
|
|
23
16
|
}
|
|
24
17
|
async getStatus() {
|
|
25
18
|
const message = {
|
|
@@ -64,67 +57,8 @@ class ServiceWorkerWallet {
|
|
|
64
57
|
};
|
|
65
58
|
await this.sendMessage(message);
|
|
66
59
|
}
|
|
67
|
-
// register the service worker
|
|
68
|
-
async setupServiceWorker(path) {
|
|
69
|
-
// check if service workers are supported
|
|
70
|
-
if (!("serviceWorker" in navigator)) {
|
|
71
|
-
throw new Error("Service workers are not supported in this browser");
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
// check for existing registration
|
|
75
|
-
const existingRegistration = await navigator.serviceWorker.getRegistration(path);
|
|
76
|
-
let registration;
|
|
77
|
-
if (existingRegistration) {
|
|
78
|
-
registration = existingRegistration;
|
|
79
|
-
// Force unregister and re-register to ensure we get the latest version
|
|
80
|
-
await existingRegistration.unregister();
|
|
81
|
-
}
|
|
82
|
-
registration = await navigator.serviceWorker.register(path);
|
|
83
|
-
// Handle updates
|
|
84
|
-
registration.addEventListener("updatefound", () => {
|
|
85
|
-
console.info("@arklabs/wallet-sdk: Service worker auto-update...");
|
|
86
|
-
const newWorker = registration.installing;
|
|
87
|
-
if (!newWorker)
|
|
88
|
-
return;
|
|
89
|
-
newWorker.addEventListener("statechange", () => {
|
|
90
|
-
if (newWorker.state === "installed" &&
|
|
91
|
-
navigator.serviceWorker.controller) {
|
|
92
|
-
console.info("@arklabs/wallet-sdk: Service worker updated, reloading...");
|
|
93
|
-
window.location.reload();
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
// Check for updates
|
|
98
|
-
await registration.update();
|
|
99
|
-
const sw = registration.active ||
|
|
100
|
-
registration.waiting ||
|
|
101
|
-
registration.installing;
|
|
102
|
-
if (!sw) {
|
|
103
|
-
throw new Error("Failed to get service worker instance");
|
|
104
|
-
}
|
|
105
|
-
this.serviceWorker = sw;
|
|
106
|
-
// wait for the service worker to be ready
|
|
107
|
-
if (this.serviceWorker?.state !== "activated") {
|
|
108
|
-
await new Promise((resolve) => {
|
|
109
|
-
if (!this.serviceWorker)
|
|
110
|
-
return resolve();
|
|
111
|
-
this.serviceWorker.addEventListener("statechange", () => {
|
|
112
|
-
if (this.serviceWorker?.state === "activated") {
|
|
113
|
-
resolve();
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
throw new Error(`Failed to setup service worker: ${error}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
60
|
// send a message and wait for a response
|
|
124
61
|
async sendMessage(message) {
|
|
125
|
-
if (!this.serviceWorker) {
|
|
126
|
-
throw new Error("Service worker not initialized");
|
|
127
|
-
}
|
|
128
62
|
return new Promise((resolve, reject) => {
|
|
129
63
|
const messageHandler = (event) => {
|
|
130
64
|
const response = event.data;
|
|
@@ -144,12 +78,7 @@ class ServiceWorkerWallet {
|
|
|
144
78
|
}
|
|
145
79
|
};
|
|
146
80
|
navigator.serviceWorker.addEventListener("message", messageHandler);
|
|
147
|
-
|
|
148
|
-
this.serviceWorker.postMessage(message);
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
reject(new Error("Service worker not initialized"));
|
|
152
|
-
}
|
|
81
|
+
this.serviceWorker.postMessage(message);
|
|
153
82
|
});
|
|
154
83
|
}
|
|
155
84
|
async getAddress() {
|
|
@@ -296,12 +225,7 @@ class ServiceWorkerWallet {
|
|
|
296
225
|
}
|
|
297
226
|
};
|
|
298
227
|
navigator.serviceWorker.addEventListener("message", messageHandler);
|
|
299
|
-
|
|
300
|
-
this.serviceWorker.postMessage(message);
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
reject(new Error("Service worker not initialized"));
|
|
304
|
-
}
|
|
228
|
+
this.serviceWorker.postMessage(message);
|
|
305
229
|
});
|
|
306
230
|
}
|
|
307
231
|
catch (error) {
|
|
@@ -17,10 +17,20 @@ class Worker {
|
|
|
17
17
|
this.vtxoRepository = vtxoRepository;
|
|
18
18
|
this.messageCallback = messageCallback;
|
|
19
19
|
}
|
|
20
|
-
async start() {
|
|
20
|
+
async start(withServiceWorkerUpdate = true) {
|
|
21
21
|
self.addEventListener("message", async (event) => {
|
|
22
22
|
await this.handleMessage(event);
|
|
23
23
|
});
|
|
24
|
+
if (withServiceWorkerUpdate) {
|
|
25
|
+
// activate service worker immediately
|
|
26
|
+
self.addEventListener("install", () => {
|
|
27
|
+
self.skipWaiting();
|
|
28
|
+
});
|
|
29
|
+
// take control of clients immediately
|
|
30
|
+
self.addEventListener("activate", () => {
|
|
31
|
+
self.clients.claim();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
24
34
|
}
|
|
25
35
|
async clear() {
|
|
26
36
|
if (this.vtxoSubscription) {
|
package/dist/esm/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { VtxoScript } from './script/base.js';
|
|
|
6
6
|
import { TxType, } from './wallet/index.js';
|
|
7
7
|
import { Wallet } from './wallet/wallet.js';
|
|
8
8
|
import { ServiceWorkerWallet } from './wallet/serviceWorker/wallet.js';
|
|
9
|
+
import { setupServiceWorker } from './wallet/serviceWorker/utils.js';
|
|
9
10
|
import { Worker } from './wallet/serviceWorker/worker.js';
|
|
10
11
|
import { Request } from './wallet/serviceWorker/request.js';
|
|
11
12
|
import { Response } from './wallet/serviceWorker/response.js';
|
|
@@ -16,9 +17,7 @@ import { addConditionWitness, CONDITION_WITNESS_KEY_PREFIX, createVirtualTx, } f
|
|
|
16
17
|
import { ArkNote, ArkNoteData } from './arknote/index.js';
|
|
17
18
|
import { IndexedDBVtxoRepository } from './wallet/serviceWorker/db/vtxo/idb.js';
|
|
18
19
|
import { networks } from './networks.js';
|
|
19
|
-
export {
|
|
20
|
-
// Classes
|
|
21
|
-
Wallet, ServiceWorkerWallet, InMemoryKey,
|
|
20
|
+
export { Wallet, InMemoryKey,
|
|
22
21
|
// Providers
|
|
23
22
|
ESPLORA_URL, EsploraProvider, RestArkProvider,
|
|
24
23
|
// Script-related
|
|
@@ -26,7 +25,7 @@ ArkAddress, DefaultVtxo, VtxoScript, VHTLC,
|
|
|
26
25
|
// Enums
|
|
27
26
|
TxType,
|
|
28
27
|
// Service Worker
|
|
29
|
-
Worker, Request, Response,
|
|
28
|
+
setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response,
|
|
30
29
|
// Tapscript
|
|
31
30
|
decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript,
|
|
32
31
|
// Utils
|
|
@@ -365,6 +365,12 @@ export class RestArkProvider {
|
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
367
|
catch (error) {
|
|
368
|
+
// ignore timeout errors, they're expected when the server is not sending anything for 5 min
|
|
369
|
+
// these timeouts are set by builtin fetch function
|
|
370
|
+
if (isFetchTimeoutError(error)) {
|
|
371
|
+
console.debug("Timeout error ignored");
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
368
374
|
console.error("Address subscription error:", error);
|
|
369
375
|
throw error;
|
|
370
376
|
}
|
|
@@ -541,3 +547,13 @@ function convertVtxo(vtxo) {
|
|
|
541
547
|
createdAt: new Date(vtxo.createdAt * 1000),
|
|
542
548
|
};
|
|
543
549
|
}
|
|
550
|
+
function isFetchTimeoutError(err) {
|
|
551
|
+
const checkError = (error) => {
|
|
552
|
+
return (error instanceof Error &&
|
|
553
|
+
(error.name === "HeadersTimeoutError" ||
|
|
554
|
+
error.name === "BodyTimeoutError" ||
|
|
555
|
+
error.code === "UND_ERR_HEADERS_TIMEOUT" ||
|
|
556
|
+
error.code === "UND_ERR_BODY_TIMEOUT"));
|
|
557
|
+
};
|
|
558
|
+
return checkError(err) || checkError(err.cause);
|
|
559
|
+
}
|
package/dist/esm/utils/psbt.js
CHANGED
|
@@ -37,16 +37,25 @@ export function addConditionWitness(inIndex, tx, witness) {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
export function createVirtualTx(inputs, outputs) {
|
|
40
|
-
let lockTime;
|
|
40
|
+
let lockTime = 0n;
|
|
41
41
|
for (const input of inputs) {
|
|
42
42
|
const tapscript = decodeTapscript(scriptFromTapLeafScript(input.tapLeafScript));
|
|
43
43
|
if (CLTVMultisigTapscript.is(tapscript)) {
|
|
44
|
-
lockTime
|
|
44
|
+
if (lockTime !== 0n) {
|
|
45
|
+
// if a locktime is already set, check if the new locktime is in the same unit
|
|
46
|
+
if (isSeconds(lockTime) !==
|
|
47
|
+
isSeconds(tapscript.params.absoluteTimelock)) {
|
|
48
|
+
throw new Error("cannot mix seconds and blocks locktime");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (tapscript.params.absoluteTimelock > lockTime) {
|
|
52
|
+
lockTime = tapscript.params.absoluteTimelock;
|
|
53
|
+
}
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
const tx = new Transaction({
|
|
48
57
|
allowUnknown: true,
|
|
49
|
-
lockTime,
|
|
58
|
+
lockTime: Number(lockTime),
|
|
50
59
|
});
|
|
51
60
|
for (const [i, input] of inputs.entries()) {
|
|
52
61
|
tx.addInput({
|
|
@@ -116,3 +125,7 @@ function encodeCompactSizeUint(value) {
|
|
|
116
125
|
return buffer;
|
|
117
126
|
}
|
|
118
127
|
}
|
|
128
|
+
const nLocktimeMinSeconds = 500000000n;
|
|
129
|
+
function isSeconds(locktime) {
|
|
130
|
+
return locktime >= nLocktimeMinSeconds;
|
|
131
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export async function setupServiceWorker(path) {
|
|
2
|
+
// check if service workers are supported
|
|
3
|
+
if (!("serviceWorker" in navigator)) {
|
|
4
|
+
throw new Error("Service workers are not supported in this browser");
|
|
5
|
+
}
|
|
6
|
+
// check for existing registration
|
|
7
|
+
const existingRegistration = await navigator.serviceWorker.getRegistration(path);
|
|
8
|
+
let registration;
|
|
9
|
+
if (existingRegistration) {
|
|
10
|
+
registration = existingRegistration;
|
|
11
|
+
// Force unregister and re-register to ensure we get the latest version
|
|
12
|
+
await existingRegistration.unregister();
|
|
13
|
+
}
|
|
14
|
+
registration = await navigator.serviceWorker.register(path);
|
|
15
|
+
// Handle updates
|
|
16
|
+
registration.addEventListener("updatefound", () => {
|
|
17
|
+
const newWorker = registration.installing;
|
|
18
|
+
if (!newWorker)
|
|
19
|
+
return;
|
|
20
|
+
newWorker.addEventListener("statechange", () => {
|
|
21
|
+
if (newWorker.state === "activated" &&
|
|
22
|
+
navigator.serviceWorker.controller) {
|
|
23
|
+
console.info("Service worker activated, reloading...");
|
|
24
|
+
window.location.reload();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
const serviceWorker = registration.active || registration.waiting || registration.installing;
|
|
29
|
+
if (!serviceWorker) {
|
|
30
|
+
throw new Error("Failed to get service worker instance");
|
|
31
|
+
}
|
|
32
|
+
// wait for the service worker to be ready
|
|
33
|
+
if (serviceWorker.state !== "activated") {
|
|
34
|
+
await new Promise((resolve) => {
|
|
35
|
+
if (!serviceWorker)
|
|
36
|
+
return resolve();
|
|
37
|
+
serviceWorker.addEventListener("statechange", () => {
|
|
38
|
+
if (serviceWorker.state === "activated") {
|
|
39
|
+
resolve();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return serviceWorker;
|
|
45
|
+
}
|
|
@@ -8,15 +8,8 @@ class UnexpectedResponseError extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
// ServiceWorkerWallet is a wallet that uses a service worker as "backend" to handle the wallet logic
|
|
10
10
|
export class ServiceWorkerWallet {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const wallet = new ServiceWorkerWallet();
|
|
14
|
-
await wallet.setupServiceWorker(svcWorkerPath);
|
|
15
|
-
return wallet;
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
throw new Error(`Failed to initialize service worker wallet: ${error}`);
|
|
19
|
-
}
|
|
11
|
+
constructor(serviceWorker) {
|
|
12
|
+
this.serviceWorker = serviceWorker;
|
|
20
13
|
}
|
|
21
14
|
async getStatus() {
|
|
22
15
|
const message = {
|
|
@@ -61,67 +54,8 @@ export class ServiceWorkerWallet {
|
|
|
61
54
|
};
|
|
62
55
|
await this.sendMessage(message);
|
|
63
56
|
}
|
|
64
|
-
// register the service worker
|
|
65
|
-
async setupServiceWorker(path) {
|
|
66
|
-
// check if service workers are supported
|
|
67
|
-
if (!("serviceWorker" in navigator)) {
|
|
68
|
-
throw new Error("Service workers are not supported in this browser");
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
// check for existing registration
|
|
72
|
-
const existingRegistration = await navigator.serviceWorker.getRegistration(path);
|
|
73
|
-
let registration;
|
|
74
|
-
if (existingRegistration) {
|
|
75
|
-
registration = existingRegistration;
|
|
76
|
-
// Force unregister and re-register to ensure we get the latest version
|
|
77
|
-
await existingRegistration.unregister();
|
|
78
|
-
}
|
|
79
|
-
registration = await navigator.serviceWorker.register(path);
|
|
80
|
-
// Handle updates
|
|
81
|
-
registration.addEventListener("updatefound", () => {
|
|
82
|
-
console.info("@arklabs/wallet-sdk: Service worker auto-update...");
|
|
83
|
-
const newWorker = registration.installing;
|
|
84
|
-
if (!newWorker)
|
|
85
|
-
return;
|
|
86
|
-
newWorker.addEventListener("statechange", () => {
|
|
87
|
-
if (newWorker.state === "installed" &&
|
|
88
|
-
navigator.serviceWorker.controller) {
|
|
89
|
-
console.info("@arklabs/wallet-sdk: Service worker updated, reloading...");
|
|
90
|
-
window.location.reload();
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
// Check for updates
|
|
95
|
-
await registration.update();
|
|
96
|
-
const sw = registration.active ||
|
|
97
|
-
registration.waiting ||
|
|
98
|
-
registration.installing;
|
|
99
|
-
if (!sw) {
|
|
100
|
-
throw new Error("Failed to get service worker instance");
|
|
101
|
-
}
|
|
102
|
-
this.serviceWorker = sw;
|
|
103
|
-
// wait for the service worker to be ready
|
|
104
|
-
if (this.serviceWorker?.state !== "activated") {
|
|
105
|
-
await new Promise((resolve) => {
|
|
106
|
-
if (!this.serviceWorker)
|
|
107
|
-
return resolve();
|
|
108
|
-
this.serviceWorker.addEventListener("statechange", () => {
|
|
109
|
-
if (this.serviceWorker?.state === "activated") {
|
|
110
|
-
resolve();
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
throw new Error(`Failed to setup service worker: ${error}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
57
|
// send a message and wait for a response
|
|
121
58
|
async sendMessage(message) {
|
|
122
|
-
if (!this.serviceWorker) {
|
|
123
|
-
throw new Error("Service worker not initialized");
|
|
124
|
-
}
|
|
125
59
|
return new Promise((resolve, reject) => {
|
|
126
60
|
const messageHandler = (event) => {
|
|
127
61
|
const response = event.data;
|
|
@@ -141,12 +75,7 @@ export class ServiceWorkerWallet {
|
|
|
141
75
|
}
|
|
142
76
|
};
|
|
143
77
|
navigator.serviceWorker.addEventListener("message", messageHandler);
|
|
144
|
-
|
|
145
|
-
this.serviceWorker.postMessage(message);
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
reject(new Error("Service worker not initialized"));
|
|
149
|
-
}
|
|
78
|
+
this.serviceWorker.postMessage(message);
|
|
150
79
|
});
|
|
151
80
|
}
|
|
152
81
|
async getAddress() {
|
|
@@ -293,12 +222,7 @@ export class ServiceWorkerWallet {
|
|
|
293
222
|
}
|
|
294
223
|
};
|
|
295
224
|
navigator.serviceWorker.addEventListener("message", messageHandler);
|
|
296
|
-
|
|
297
|
-
this.serviceWorker.postMessage(message);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
reject(new Error("Service worker not initialized"));
|
|
301
|
-
}
|
|
225
|
+
this.serviceWorker.postMessage(message);
|
|
302
226
|
});
|
|
303
227
|
}
|
|
304
228
|
catch (error) {
|
|
@@ -14,10 +14,20 @@ export class Worker {
|
|
|
14
14
|
this.vtxoRepository = vtxoRepository;
|
|
15
15
|
this.messageCallback = messageCallback;
|
|
16
16
|
}
|
|
17
|
-
async start() {
|
|
17
|
+
async start(withServiceWorkerUpdate = true) {
|
|
18
18
|
self.addEventListener("message", async (event) => {
|
|
19
19
|
await this.handleMessage(event);
|
|
20
20
|
});
|
|
21
|
+
if (withServiceWorkerUpdate) {
|
|
22
|
+
// activate service worker immediately
|
|
23
|
+
self.addEventListener("install", () => {
|
|
24
|
+
self.skipWaiting();
|
|
25
|
+
});
|
|
26
|
+
// take control of clients immediately
|
|
27
|
+
self.addEventListener("activate", () => {
|
|
28
|
+
self.clients.claim();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
21
31
|
}
|
|
22
32
|
async clear() {
|
|
23
33
|
if (this.vtxoSubscription) {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { VtxoScript } from "./script/base";
|
|
|
7
7
|
import { TxType, IWallet, WalletConfig, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, VtxoTaprootAddress, AddressInfo, TapscriptInfo, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, Addresses } from "./wallet/index";
|
|
8
8
|
import { Wallet } from "./wallet/wallet";
|
|
9
9
|
import { ServiceWorkerWallet } from "./wallet/serviceWorker/wallet";
|
|
10
|
+
import { setupServiceWorker } from "./wallet/serviceWorker/utils";
|
|
10
11
|
import { Worker } from "./wallet/serviceWorker/worker";
|
|
11
12
|
import { Request } from "./wallet/serviceWorker/request";
|
|
12
13
|
import { Response } from "./wallet/serviceWorker/response";
|
|
@@ -18,5 +19,5 @@ import { ArkNote, ArkNoteData } from "./arknote";
|
|
|
18
19
|
import { IndexedDBVtxoRepository } from "./wallet/serviceWorker/db/vtxo/idb";
|
|
19
20
|
import { VtxoRepository } from "./wallet/serviceWorker/db/vtxo";
|
|
20
21
|
import { networks } from "./networks";
|
|
21
|
-
export { Wallet,
|
|
22
|
+
export { Wallet, InMemoryKey, ESPLORA_URL, EsploraProvider, RestArkProvider, ArkAddress, DefaultVtxo, VtxoScript, VHTLC, TxType, setupServiceWorker, Worker, ServiceWorkerWallet, Request, Response, decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CLTVMultisigTapscript, addConditionWitness, CONDITION_WITNESS_KEY_PREFIX, createVirtualTx, ArkNote, ArkNoteData, networks, IndexedDBVtxoRepository, };
|
|
22
23
|
export type { Identity, IWallet, WalletConfig, ArkTransaction, Coin, ExtendedCoin, ExtendedVirtualCoin, WalletBalance, SendBitcoinParams, Recipient, SettleParams, VtxoTaprootAddress, AddressInfo, Addresses, TapscriptInfo, Status, VirtualStatus, Outpoint, VirtualCoin, TxKey, TapscriptType, VtxoRepository, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setupServiceWorker(path: string): Promise<ServiceWorker>;
|
|
@@ -2,14 +2,13 @@ import { IWallet, WalletBalance, SendBitcoinParams, SettleParams, AddressInfo, C
|
|
|
2
2
|
import { Response } from "./response";
|
|
3
3
|
import { SettlementEvent } from "../../providers/ark";
|
|
4
4
|
export declare class ServiceWorkerWallet implements IWallet {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
readonly serviceWorker: ServiceWorker;
|
|
6
|
+
constructor(serviceWorker: ServiceWorker);
|
|
7
7
|
getStatus(): Promise<Response.WalletStatus["status"]>;
|
|
8
8
|
init(config: Omit<WalletConfig, "identity"> & {
|
|
9
9
|
privateKey: string;
|
|
10
10
|
}, failIfInitialized?: boolean): Promise<void>;
|
|
11
11
|
clear(): Promise<void>;
|
|
12
|
-
private setupServiceWorker;
|
|
13
12
|
private sendMessage;
|
|
14
13
|
getAddress(): Promise<Addresses>;
|
|
15
14
|
getAddressInfo(): Promise<AddressInfo>;
|
|
@@ -6,7 +6,7 @@ export declare class Worker {
|
|
|
6
6
|
private arkProvider;
|
|
7
7
|
private vtxoSubscription;
|
|
8
8
|
constructor(vtxoRepository?: VtxoRepository, messageCallback?: (message: ExtendableMessageEvent) => void);
|
|
9
|
-
start(): Promise<void>;
|
|
9
|
+
start(withServiceWorkerUpdate?: boolean): Promise<void>;
|
|
10
10
|
clear(): Promise<void>;
|
|
11
11
|
private onWalletInitialized;
|
|
12
12
|
private processVtxoSubscription;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkade-os/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Bitcoin wallet SDK with Taproot and Ark integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"registry": "https://registry.npmjs.org/"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@noble/curves": "1.
|
|
27
|
-
"@noble/hashes": "1.
|
|
26
|
+
"@noble/curves": "1.9.1",
|
|
27
|
+
"@noble/hashes": "1.8.0",
|
|
28
28
|
"@noble/secp256k1": "2.2.3",
|
|
29
|
-
"@scure/base": "1.2.
|
|
30
|
-
"@scure/btc-signer": "1.
|
|
29
|
+
"@scure/base": "1.2.6",
|
|
30
|
+
"@scure/btc-signer": "1.8.1",
|
|
31
31
|
"bip68": "1.0.4"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@types/node": "22.10.2",
|
|
37
37
|
"@typescript-eslint/eslint-plugin": "8.18.2",
|
|
38
38
|
"@typescript-eslint/parser": "8.18.2",
|
|
39
|
-
"@vitest/coverage-v8": "2.1.
|
|
39
|
+
"@vitest/coverage-v8": "2.1.9",
|
|
40
40
|
"esbuild": "^0.20.1",
|
|
41
41
|
"eslint": "^9.17.0",
|
|
42
42
|
"glob": "11.0.1",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"lint-staged": "15.3.0",
|
|
45
45
|
"prettier": "3.4.2",
|
|
46
46
|
"typescript": "5.7.2",
|
|
47
|
-
"vitest": "2.1.
|
|
47
|
+
"vitest": "2.1.9"
|
|
48
48
|
},
|
|
49
49
|
"keywords": [
|
|
50
50
|
"bitcoin",
|