@arkade-os/sdk 0.3.0-alpha.8 → 0.3.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 +48 -14
- package/dist/cjs/arknote/index.js +3 -3
- package/dist/cjs/forfeit.js +2 -2
- package/dist/cjs/identity/singleKey.js +8 -8
- package/dist/cjs/index.js +13 -5
- package/dist/cjs/{bip322 → intent}/index.js +38 -61
- package/dist/cjs/musig2/index.js +2 -1
- package/dist/cjs/musig2/nonces.js +4 -0
- package/dist/cjs/providers/ark.js +76 -45
- package/dist/cjs/providers/errors.js +59 -0
- package/dist/cjs/providers/expoArk.js +15 -170
- package/dist/cjs/providers/expoIndexer.js +22 -111
- package/dist/cjs/providers/expoUtils.js +124 -0
- package/dist/cjs/providers/onchain.js +19 -20
- package/dist/cjs/repositories/walletRepository.js +64 -28
- package/dist/cjs/script/base.js +15 -7
- package/dist/cjs/script/tapscript.js +20 -21
- package/dist/cjs/script/vhtlc.js +2 -2
- package/dist/cjs/tree/signingSession.js +44 -11
- package/dist/cjs/tree/txTree.js +3 -4
- package/dist/cjs/tree/validation.js +2 -3
- package/dist/cjs/utils/arkTransaction.js +105 -15
- package/dist/cjs/utils/transaction.js +28 -0
- package/dist/cjs/utils/unknownFields.js +7 -7
- package/dist/cjs/wallet/onchain.js +6 -7
- package/dist/cjs/wallet/serviceWorker/response.js +32 -0
- package/dist/cjs/wallet/serviceWorker/utils.js +2 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +46 -27
- package/dist/cjs/wallet/unroll.js +7 -9
- package/dist/cjs/wallet/utils.js +9 -0
- package/dist/cjs/wallet/vtxo-manager.js +323 -0
- package/dist/cjs/wallet/wallet.js +98 -125
- package/dist/esm/arknote/index.js +2 -2
- package/dist/esm/forfeit.js +1 -1
- package/dist/esm/identity/singleKey.js +9 -9
- package/dist/esm/index.js +14 -10
- package/dist/esm/{bip322 → intent}/index.js +32 -54
- package/dist/esm/musig2/index.js +1 -1
- package/dist/esm/musig2/nonces.js +3 -0
- package/dist/esm/providers/ark.js +76 -45
- package/dist/esm/providers/errors.js +54 -0
- package/dist/esm/providers/expoArk.js +15 -137
- package/dist/esm/providers/expoIndexer.js +22 -78
- package/dist/esm/providers/expoUtils.js +87 -0
- package/dist/esm/providers/onchain.js +19 -20
- package/dist/esm/repositories/walletRepository.js +64 -28
- package/dist/esm/script/base.js +12 -4
- package/dist/esm/script/tapscript.js +1 -2
- package/dist/esm/script/vhtlc.js +1 -1
- package/dist/esm/tree/signingSession.js +45 -12
- package/dist/esm/tree/txTree.js +3 -4
- package/dist/esm/tree/validation.js +2 -3
- package/dist/esm/utils/arkTransaction.js +97 -8
- package/dist/esm/utils/transaction.js +24 -0
- package/dist/esm/utils/unknownFields.js +3 -3
- package/dist/esm/wallet/onchain.js +3 -4
- package/dist/esm/wallet/serviceWorker/response.js +32 -0
- package/dist/esm/wallet/serviceWorker/utils.js +1 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
- package/dist/esm/wallet/serviceWorker/worker.js +48 -29
- package/dist/esm/wallet/unroll.js +5 -7
- package/dist/esm/wallet/utils.js +8 -0
- package/dist/esm/wallet/vtxo-manager.js +317 -0
- package/dist/esm/wallet/wallet.js +92 -119
- package/dist/types/arknote/index.d.ts +1 -1
- package/dist/types/forfeit.d.ts +2 -2
- package/dist/types/identity/index.d.ts +2 -2
- package/dist/types/identity/singleKey.d.ts +2 -2
- package/dist/types/index.d.ts +9 -7
- package/dist/types/intent/index.d.ts +41 -0
- package/dist/types/musig2/index.d.ts +1 -1
- package/dist/types/musig2/nonces.d.ts +1 -0
- package/dist/types/providers/ark.d.ts +62 -26
- package/dist/types/providers/errors.d.ts +13 -0
- package/dist/types/providers/expoIndexer.d.ts +2 -10
- package/dist/types/providers/expoUtils.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +1 -9
- package/dist/types/providers/onchain.d.ts +6 -2
- package/dist/types/repositories/walletRepository.d.ts +9 -5
- package/dist/types/script/base.d.ts +5 -2
- package/dist/types/tree/signingSession.d.ts +16 -11
- package/dist/types/utils/anchor.d.ts +2 -2
- package/dist/types/utils/arkTransaction.d.ts +12 -4
- package/dist/types/utils/transaction.d.ts +13 -0
- package/dist/types/utils/unknownFields.d.ts +4 -4
- package/dist/types/wallet/index.d.ts +6 -4
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/response.d.ts +16 -2
- package/dist/types/wallet/serviceWorker/utils.d.ts +1 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
- package/dist/types/wallet/serviceWorker/worker.d.ts +7 -1
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/utils.d.ts +2 -1
- package/dist/types/wallet/vtxo-manager.d.ts +179 -0
- package/dist/types/wallet/wallet.d.ts +8 -4
- package/package.json +1 -2
- package/dist/cjs/bip322/errors.js +0 -13
- package/dist/esm/bip322/errors.js +0 -9
- package/dist/types/bip322/errors.d.ts +0 -6
- package/dist/types/bip322/index.d.ts +0 -57
|
@@ -1,41 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.ExpoIndexerProvider = void 0;
|
|
37
4
|
const indexer_1 = require("./indexer");
|
|
38
5
|
const ark_1 = require("./ark");
|
|
6
|
+
const expoUtils_1 = require("./expoUtils");
|
|
39
7
|
// Helper function to convert Vtxo to VirtualCoin (same as in indexer.ts)
|
|
40
8
|
function convertVtxo(vtxo) {
|
|
41
9
|
return {
|
|
@@ -85,95 +53,38 @@ class ExpoIndexerProvider extends indexer_1.RestIndexerProvider {
|
|
|
85
53
|
// Detect if we're running in React Native/Expo environment
|
|
86
54
|
const isReactNative = typeof navigator !== "undefined" &&
|
|
87
55
|
navigator.product === "ReactNative";
|
|
88
|
-
|
|
89
|
-
let expoFetch = fetch; // Default to standard fetch
|
|
90
|
-
try {
|
|
91
|
-
const expoFetchModule = await Promise.resolve().then(() => __importStar(require("expo/fetch")));
|
|
92
|
-
// expo/fetch returns a compatible fetch function but with different types
|
|
93
|
-
expoFetch = expoFetchModule.fetch;
|
|
94
|
-
console.debug("Using expo/fetch for indexer subscription");
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
56
|
+
const expoFetch = await (0, expoUtils_1.getExpoFetch)().catch((error) => {
|
|
97
57
|
// In React Native/Expo, expo/fetch is required for proper streaming support
|
|
98
58
|
if (isReactNative) {
|
|
99
59
|
throw new Error("expo/fetch is unavailable in React Native environment. " +
|
|
100
60
|
"Please ensure expo/fetch is installed and properly configured. " +
|
|
101
61
|
"Streaming support may not work with standard fetch in React Native.");
|
|
102
62
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"Streaming may not be fully supported in some environments.", error);
|
|
106
|
-
}
|
|
63
|
+
throw error;
|
|
64
|
+
});
|
|
107
65
|
const url = `${this.serverUrl}/v1/indexer/script/subscription/${subscriptionId}`;
|
|
108
66
|
while (!abortSignal.aborted) {
|
|
109
67
|
try {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
signal: abortSignal,
|
|
116
|
-
});
|
|
117
|
-
if (!res.ok) {
|
|
118
|
-
throw new Error(`Unexpected status ${res.status} when subscribing to address updates`);
|
|
119
|
-
}
|
|
120
|
-
// Check if response is the expected content type
|
|
121
|
-
const contentType = res.headers.get("content-type");
|
|
122
|
-
if (contentType &&
|
|
123
|
-
!contentType.includes("text/event-stream") &&
|
|
124
|
-
!contentType.includes("application/json")) {
|
|
125
|
-
throw new Error(`Unexpected content-type: ${contentType}. Expected text/event-stream or application/json`);
|
|
126
|
-
}
|
|
127
|
-
if (!res.body) {
|
|
128
|
-
throw new Error("Response body is null");
|
|
129
|
-
}
|
|
130
|
-
const reader = res.body.getReader();
|
|
131
|
-
const decoder = new TextDecoder();
|
|
132
|
-
let buffer = "";
|
|
133
|
-
while (!abortSignal.aborted) {
|
|
134
|
-
const { done, value } = await reader.read();
|
|
135
|
-
if (done) {
|
|
136
|
-
break;
|
|
68
|
+
yield* (0, expoUtils_1.sseStreamIterator)(url, abortSignal, expoFetch, { "Content-Type": "application/json" }, (data) => {
|
|
69
|
+
// Handle new v8 proto format with heartbeat or event
|
|
70
|
+
if (data.heartbeat !== undefined) {
|
|
71
|
+
// Skip heartbeat messages
|
|
72
|
+
return null;
|
|
137
73
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
continue;
|
|
150
|
-
const data = JSON.parse(jsonStr);
|
|
151
|
-
// Handle new v8 proto format with heartbeat or event
|
|
152
|
-
if (data.heartbeat !== undefined) {
|
|
153
|
-
// Skip heartbeat messages
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
// Process event messages
|
|
157
|
-
if (data.event) {
|
|
158
|
-
yield {
|
|
159
|
-
txid: data.event.txid,
|
|
160
|
-
scripts: data.event.scripts || [],
|
|
161
|
-
newVtxos: (data.event.newVtxos || []).map(convertVtxo),
|
|
162
|
-
spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
|
|
163
|
-
sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
|
|
164
|
-
tx: data.event.tx,
|
|
165
|
-
checkpointTxs: data.event.checkpointTxs,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch (parseError) {
|
|
171
|
-
console.error("Failed to parse subscription response:", parseError);
|
|
172
|
-
throw parseError;
|
|
173
|
-
}
|
|
74
|
+
// Process event messages
|
|
75
|
+
if (data.event) {
|
|
76
|
+
return {
|
|
77
|
+
txid: data.event.txid,
|
|
78
|
+
scripts: data.event.scripts || [],
|
|
79
|
+
newVtxos: (data.event.newVtxos || []).map(convertVtxo),
|
|
80
|
+
spentVtxos: (data.event.spentVtxos || []).map(convertVtxo),
|
|
81
|
+
sweptVtxos: (data.event.sweptVtxos || []).map(convertVtxo),
|
|
82
|
+
tx: data.event.tx,
|
|
83
|
+
checkpointTxs: data.event.checkpointTxs,
|
|
84
|
+
};
|
|
174
85
|
}
|
|
175
|
-
|
|
176
|
-
}
|
|
86
|
+
return null;
|
|
87
|
+
});
|
|
177
88
|
}
|
|
178
89
|
catch (error) {
|
|
179
90
|
if (error instanceof Error && error.name === "AbortError") {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getExpoFetch = getExpoFetch;
|
|
37
|
+
exports.sseStreamIterator = sseStreamIterator;
|
|
38
|
+
/**
|
|
39
|
+
* Dynamically imports expo/fetch with fallback to standard fetch.
|
|
40
|
+
* @returns A fetch function suitable for SSE streaming
|
|
41
|
+
*/
|
|
42
|
+
async function getExpoFetch(options) {
|
|
43
|
+
const requireExpo = options?.requireExpo ?? false;
|
|
44
|
+
try {
|
|
45
|
+
const expoFetchModule = await Promise.resolve().then(() => __importStar(require("expo/fetch")));
|
|
46
|
+
console.debug("Using expo/fetch for streaming");
|
|
47
|
+
return expoFetchModule.fetch;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (requireExpo) {
|
|
51
|
+
throw new Error("expo/fetch is unavailable in this environment. " +
|
|
52
|
+
"Please ensure expo/fetch is installed and properly configured.");
|
|
53
|
+
}
|
|
54
|
+
console.warn("Using standard fetch instead of expo/fetch. " +
|
|
55
|
+
"Streaming may not be fully supported in some environments.", error);
|
|
56
|
+
return fetch;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Generic SSE stream processor using fetch API with ReadableStream.
|
|
61
|
+
* Handles SSE format parsing, buffer management, and abort signals.
|
|
62
|
+
*
|
|
63
|
+
* @param url - The SSE endpoint URL
|
|
64
|
+
* @param abortSignal - Signal to abort the stream
|
|
65
|
+
* @param fetchFn - Fetch function to use (defaults to standard fetch)
|
|
66
|
+
* @param headers - Additional headers to send
|
|
67
|
+
* @param parseData - Function to parse and yield data from SSE events
|
|
68
|
+
*/
|
|
69
|
+
async function* sseStreamIterator(url, abortSignal, fetchFn, headers, parseData) {
|
|
70
|
+
const fetchController = new AbortController();
|
|
71
|
+
const cleanup = () => fetchController.abort();
|
|
72
|
+
abortSignal?.addEventListener("abort", cleanup, { once: true });
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetchFn(url, {
|
|
75
|
+
headers: {
|
|
76
|
+
Accept: "text/event-stream",
|
|
77
|
+
...headers,
|
|
78
|
+
},
|
|
79
|
+
signal: fetchController.signal,
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`Unexpected status ${response.status} when fetching SSE stream`);
|
|
83
|
+
}
|
|
84
|
+
if (!response.body) {
|
|
85
|
+
throw new Error("Response body is null");
|
|
86
|
+
}
|
|
87
|
+
const reader = response.body.getReader();
|
|
88
|
+
const decoder = new TextDecoder();
|
|
89
|
+
let buffer = "";
|
|
90
|
+
while (!abortSignal?.aborted) {
|
|
91
|
+
const { done, value } = await reader.read();
|
|
92
|
+
if (done) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
buffer += decoder.decode(value, { stream: true });
|
|
96
|
+
const lines = buffer.split("\n");
|
|
97
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
98
|
+
const line = lines[i].trim();
|
|
99
|
+
if (!line)
|
|
100
|
+
continue;
|
|
101
|
+
if (line.startsWith("data:")) {
|
|
102
|
+
const jsonStr = line.substring(5).trim();
|
|
103
|
+
if (!jsonStr)
|
|
104
|
+
continue;
|
|
105
|
+
try {
|
|
106
|
+
const data = JSON.parse(jsonStr);
|
|
107
|
+
const parsed = parseData(data);
|
|
108
|
+
if (parsed !== null) {
|
|
109
|
+
yield parsed;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (parseError) {
|
|
113
|
+
console.error("Failed to parse SSE data:", parseError);
|
|
114
|
+
throw parseError;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
buffer = lines[lines.length - 1];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
abortSignal?.removeEventListener("abort", cleanup);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -21,9 +21,10 @@ exports.ESPLORA_URL = {
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
class EsploraProvider {
|
|
24
|
-
constructor(baseUrl) {
|
|
24
|
+
constructor(baseUrl, opts) {
|
|
25
25
|
this.baseUrl = baseUrl;
|
|
26
|
-
this.
|
|
26
|
+
this.pollingInterval = opts?.pollingInterval ?? 15000;
|
|
27
|
+
this.forcePolling = opts?.forcePolling ?? false;
|
|
27
28
|
}
|
|
28
29
|
async getCoins(address) {
|
|
29
30
|
const response = await fetch(`${this.baseUrl}/address/${address}/utxo`);
|
|
@@ -94,13 +95,9 @@ class EsploraProvider {
|
|
|
94
95
|
let intervalId = null;
|
|
95
96
|
const wsUrl = this.baseUrl.replace(/^http(s)?:/, "ws$1:") + "/v1/ws";
|
|
96
97
|
const poll = async () => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// websocket is not reliable, so we will fallback to polling
|
|
101
|
-
const pollingInterval = 5000; // 5 seconds
|
|
102
|
-
const getAllTxs = () => {
|
|
103
|
-
return Promise.all(addresses.map((address) => this.getTransactions(address))).then((txArrays) => txArrays.flat());
|
|
98
|
+
const getAllTxs = async () => {
|
|
99
|
+
const txArrays = await Promise.all(addresses.map((address) => this.getTransactions(address)));
|
|
100
|
+
return txArrays.flat();
|
|
104
101
|
};
|
|
105
102
|
// initial fetch to get existing transactions
|
|
106
103
|
const initialTxs = await getAllTxs();
|
|
@@ -125,9 +122,19 @@ class EsploraProvider {
|
|
|
125
122
|
catch (error) {
|
|
126
123
|
console.error("Error in polling mechanism:", error);
|
|
127
124
|
}
|
|
128
|
-
}, pollingInterval);
|
|
125
|
+
}, this.pollingInterval);
|
|
129
126
|
};
|
|
130
127
|
let ws = null;
|
|
128
|
+
const stopFunc = () => {
|
|
129
|
+
if (ws)
|
|
130
|
+
ws.close();
|
|
131
|
+
if (intervalId)
|
|
132
|
+
clearInterval(intervalId);
|
|
133
|
+
};
|
|
134
|
+
if (this.forcePolling) {
|
|
135
|
+
await poll();
|
|
136
|
+
return stopFunc;
|
|
137
|
+
}
|
|
131
138
|
try {
|
|
132
139
|
ws = new WebSocket(wsUrl);
|
|
133
140
|
ws.addEventListener("open", () => {
|
|
@@ -174,13 +181,6 @@ class EsploraProvider {
|
|
|
174
181
|
// if websocket is not available, fallback to polling
|
|
175
182
|
await poll();
|
|
176
183
|
}
|
|
177
|
-
const stopFunc = () => {
|
|
178
|
-
if (ws && ws.readyState === WebSocket.OPEN)
|
|
179
|
-
ws.close();
|
|
180
|
-
if (intervalId)
|
|
181
|
-
clearInterval(intervalId);
|
|
182
|
-
this.polling = false;
|
|
183
|
-
};
|
|
184
184
|
return stopFunc;
|
|
185
185
|
}
|
|
186
186
|
async getChainTip() {
|
|
@@ -249,8 +249,7 @@ const isExplorerTransaction = (tx) => {
|
|
|
249
249
|
return (typeof tx.txid === "string" &&
|
|
250
250
|
Array.isArray(tx.vout) &&
|
|
251
251
|
tx.vout.every((vout) => typeof vout.scriptpubkey_address === "string" &&
|
|
252
|
-
typeof vout.value === "
|
|
252
|
+
typeof vout.value === "number") &&
|
|
253
253
|
typeof tx.status === "object" &&
|
|
254
|
-
typeof tx.status.confirmed === "boolean"
|
|
255
|
-
typeof tx.status.block_time === "number");
|
|
254
|
+
typeof tx.status.confirmed === "boolean");
|
|
256
255
|
};
|
|
@@ -15,7 +15,14 @@ const serializeVtxo = (v) => ({
|
|
|
15
15
|
tapTree: toHex(v.tapTree),
|
|
16
16
|
forfeitTapLeafScript: serializeTapLeaf(v.forfeitTapLeafScript),
|
|
17
17
|
intentTapLeafScript: serializeTapLeaf(v.intentTapLeafScript),
|
|
18
|
-
extraWitness: v.extraWitness?.map(
|
|
18
|
+
extraWitness: v.extraWitness?.map(toHex),
|
|
19
|
+
});
|
|
20
|
+
const serializeUtxo = (u) => ({
|
|
21
|
+
...u,
|
|
22
|
+
tapTree: toHex(u.tapTree),
|
|
23
|
+
forfeitTapLeafScript: serializeTapLeaf(u.forfeitTapLeafScript),
|
|
24
|
+
intentTapLeafScript: serializeTapLeaf(u.intentTapLeafScript),
|
|
25
|
+
extraWitness: u.extraWitness?.map(toHex),
|
|
19
26
|
});
|
|
20
27
|
const deserializeTapLeaf = (t) => {
|
|
21
28
|
const cb = btc_signer_1.TaprootControlBlock.decode(fromHex(t.cb));
|
|
@@ -27,13 +34,21 @@ const deserializeVtxo = (o) => ({
|
|
|
27
34
|
tapTree: fromHex(o.tapTree),
|
|
28
35
|
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
|
|
29
36
|
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
|
|
30
|
-
extraWitness: o.extraWitness?.map(
|
|
37
|
+
extraWitness: o.extraWitness?.map(fromHex),
|
|
38
|
+
});
|
|
39
|
+
const deserializeUtxo = (o) => ({
|
|
40
|
+
...o,
|
|
41
|
+
tapTree: fromHex(o.tapTree),
|
|
42
|
+
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
|
|
43
|
+
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
|
|
44
|
+
extraWitness: o.extraWitness?.map(fromHex),
|
|
31
45
|
});
|
|
32
46
|
class WalletRepositoryImpl {
|
|
33
47
|
constructor(storage) {
|
|
34
48
|
this.storage = storage;
|
|
35
49
|
this.cache = {
|
|
36
50
|
vtxos: new Map(),
|
|
51
|
+
utxos: new Map(),
|
|
37
52
|
transactions: new Map(),
|
|
38
53
|
walletState: null,
|
|
39
54
|
initialized: new Set(),
|
|
@@ -61,18 +76,6 @@ class WalletRepositoryImpl {
|
|
|
61
76
|
return [];
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
|
-
async saveVtxo(address, vtxo) {
|
|
65
|
-
const vtxos = await this.getVtxos(address);
|
|
66
|
-
const existing = vtxos.findIndex((v) => v.txid === vtxo.txid && v.vout === vtxo.vout);
|
|
67
|
-
if (existing !== -1) {
|
|
68
|
-
vtxos[existing] = vtxo;
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
vtxos.push(vtxo);
|
|
72
|
-
}
|
|
73
|
-
this.cache.vtxos.set(address, vtxos.slice());
|
|
74
|
-
await this.storage.setItem(`vtxos:${address}`, JSON.stringify(vtxos.map(serializeVtxo)));
|
|
75
|
-
}
|
|
76
79
|
async saveVtxos(address, vtxos) {
|
|
77
80
|
const storedVtxos = await this.getVtxos(address);
|
|
78
81
|
for (const vtxo of vtxos) {
|
|
@@ -98,6 +101,53 @@ class WalletRepositoryImpl {
|
|
|
98
101
|
this.cache.vtxos.set(address, []);
|
|
99
102
|
await this.storage.removeItem(`vtxos:${address}`);
|
|
100
103
|
}
|
|
104
|
+
async getUtxos(address) {
|
|
105
|
+
const cacheKey = `utxos:${address}`;
|
|
106
|
+
if (this.cache.utxos.has(address)) {
|
|
107
|
+
return this.cache.utxos.get(address);
|
|
108
|
+
}
|
|
109
|
+
const stored = await this.storage.getItem(cacheKey);
|
|
110
|
+
if (!stored) {
|
|
111
|
+
this.cache.utxos.set(address, []);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(stored);
|
|
116
|
+
const utxos = parsed.map(deserializeUtxo);
|
|
117
|
+
this.cache.utxos.set(address, utxos.slice());
|
|
118
|
+
return utxos.slice();
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`Failed to parse UTXOs for address ${address}:`, error);
|
|
122
|
+
this.cache.utxos.set(address, []);
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async saveUtxos(address, utxos) {
|
|
127
|
+
const storedUtxos = await this.getUtxos(address);
|
|
128
|
+
utxos.forEach((utxo) => {
|
|
129
|
+
const existing = storedUtxos.findIndex((u) => u.txid === utxo.txid && u.vout === utxo.vout);
|
|
130
|
+
if (existing !== -1) {
|
|
131
|
+
storedUtxos[existing] = utxo;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
storedUtxos.push(utxo);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.cache.utxos.set(address, storedUtxos.slice());
|
|
138
|
+
await this.storage.setItem(`utxos:${address}`, JSON.stringify(storedUtxos.map(serializeUtxo)));
|
|
139
|
+
}
|
|
140
|
+
async removeUtxo(address, utxoId) {
|
|
141
|
+
const utxos = await this.getUtxos(address);
|
|
142
|
+
const [txid, vout] = utxoId.split(":");
|
|
143
|
+
const filtered = utxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
|
|
144
|
+
this.cache.utxos.set(address, filtered.slice());
|
|
145
|
+
await this.storage.setItem(`utxos:${address}`, JSON.stringify(filtered.map(serializeUtxo)));
|
|
146
|
+
}
|
|
147
|
+
async clearUtxos(address) {
|
|
148
|
+
this.cache.utxos.set(address, []);
|
|
149
|
+
await this.storage.removeItem(`utxos:${address}`);
|
|
150
|
+
}
|
|
101
151
|
async getTransactionHistory(address) {
|
|
102
152
|
const cacheKey = `tx:${address}`;
|
|
103
153
|
if (this.cache.transactions.has(address)) {
|
|
@@ -119,20 +169,6 @@ class WalletRepositoryImpl {
|
|
|
119
169
|
return [];
|
|
120
170
|
}
|
|
121
171
|
}
|
|
122
|
-
async saveTransaction(address, tx) {
|
|
123
|
-
const transactions = await this.getTransactionHistory(address);
|
|
124
|
-
const existing = transactions.findIndex((t) => t.key === tx.key);
|
|
125
|
-
if (existing !== -1) {
|
|
126
|
-
transactions[existing] = tx;
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
transactions.push(tx);
|
|
130
|
-
}
|
|
131
|
-
// Sort by createdAt descending
|
|
132
|
-
transactions.sort((a, b) => b.createdAt - a.createdAt);
|
|
133
|
-
this.cache.transactions.set(address, transactions);
|
|
134
|
-
await this.storage.setItem(`tx:${address}`, JSON.stringify(transactions));
|
|
135
|
-
}
|
|
136
172
|
async saveTransactions(address, txs) {
|
|
137
173
|
const storedTransactions = await this.getTransactionHistory(address);
|
|
138
174
|
for (const tx of txs) {
|
package/dist/cjs/script/base.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VtxoScript = void 0;
|
|
3
|
+
exports.VtxoScript = exports.TapTreeCoder = void 0;
|
|
4
4
|
exports.scriptFromTapLeafScript = scriptFromTapLeafScript;
|
|
5
5
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
6
|
const payment_js_1 = require("@scure/btc-signer/payment.js");
|
|
7
7
|
const psbt_js_1 = require("@scure/btc-signer/psbt.js");
|
|
8
|
-
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
9
8
|
const base_1 = require("@scure/base");
|
|
10
9
|
const address_1 = require("./address");
|
|
11
10
|
const tapscript_1 = require("./tapscript");
|
|
12
|
-
|
|
11
|
+
exports.TapTreeCoder = psbt_js_1.PSBTOutput.tapTree[2];
|
|
13
12
|
function scriptFromTapLeafScript(leaf) {
|
|
14
13
|
return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte
|
|
15
14
|
}
|
|
@@ -23,14 +22,23 @@ function scriptFromTapLeafScript(leaf) {
|
|
|
23
22
|
*/
|
|
24
23
|
class VtxoScript {
|
|
25
24
|
static decode(tapTree) {
|
|
26
|
-
const leaves = TapTreeCoder.decode(tapTree);
|
|
25
|
+
const leaves = exports.TapTreeCoder.decode(tapTree);
|
|
27
26
|
const scripts = leaves.map((leaf) => leaf.script);
|
|
28
27
|
return new VtxoScript(scripts);
|
|
29
28
|
}
|
|
30
29
|
constructor(scripts) {
|
|
31
30
|
this.scripts = scripts;
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
// reverse the scripts if the number of scripts is odd
|
|
32
|
+
// this is to be compatible with arkd algorithm computing taproot tree from list of tapscripts
|
|
33
|
+
// the scripts must be reversed only HERE while we compute the tweaked public key
|
|
34
|
+
// but the original order should be preserved while encoding as taptree
|
|
35
|
+
// note: .slice().reverse() is used instead of .reverse() to avoid mutating the original array
|
|
36
|
+
const list = scripts.length % 2 !== 0 ? scripts.slice().reverse() : scripts;
|
|
37
|
+
const tapTree = (0, btc_signer_1.taprootListToTree)(list.map((script) => ({
|
|
38
|
+
script,
|
|
39
|
+
leafVersion: payment_js_1.TAP_LEAF_VERSION,
|
|
40
|
+
})));
|
|
41
|
+
const payment = (0, btc_signer_1.p2tr)(btc_signer_1.TAPROOT_UNSPENDABLE_KEY, tapTree, undefined, true);
|
|
34
42
|
if (!payment.tapLeafScript ||
|
|
35
43
|
payment.tapLeafScript.length !== scripts.length) {
|
|
36
44
|
throw new Error("invalid scripts");
|
|
@@ -39,7 +47,7 @@ class VtxoScript {
|
|
|
39
47
|
this.tweakedPublicKey = payment.tweakedPubkey;
|
|
40
48
|
}
|
|
41
49
|
encode() {
|
|
42
|
-
const tapTree = TapTreeCoder.encode(this.scripts.map((script) => ({
|
|
50
|
+
const tapTree = exports.TapTreeCoder.encode(this.scripts.map((script) => ({
|
|
43
51
|
depth: 1,
|
|
44
52
|
version: payment_js_1.TAP_LEAF_VERSION,
|
|
45
53
|
script,
|