@1sat/wallet-toolbox 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/balance/index.d.ts +50 -0
- package/dist/api/balance/index.js +135 -0
- package/dist/api/broadcast/index.d.ts +24 -0
- package/dist/api/broadcast/index.js +73 -0
- package/dist/api/constants.d.ts +21 -0
- package/dist/api/constants.js +29 -0
- package/dist/api/index.d.ts +36 -0
- package/dist/api/index.js +59 -0
- package/dist/api/inscriptions/index.d.ts +25 -0
- package/dist/api/inscriptions/index.js +98 -0
- package/dist/api/locks/index.d.ts +47 -0
- package/dist/api/locks/index.js +291 -0
- package/dist/api/ordinals/index.d.ts +102 -0
- package/dist/api/ordinals/index.js +566 -0
- package/dist/api/payments/index.d.ts +48 -0
- package/dist/api/payments/index.js +185 -0
- package/dist/api/signing/index.d.ts +35 -0
- package/dist/api/signing/index.js +78 -0
- package/dist/api/skills/registry.d.ts +61 -0
- package/dist/api/skills/registry.js +74 -0
- package/dist/api/skills/types.d.ts +71 -0
- package/dist/api/skills/types.js +14 -0
- package/dist/api/tokens/index.d.ts +87 -0
- package/dist/api/tokens/index.js +457 -0
- package/dist/cwi/chrome.d.ts +11 -0
- package/dist/cwi/chrome.js +39 -0
- package/dist/cwi/event.d.ts +11 -0
- package/dist/cwi/event.js +38 -0
- package/dist/cwi/factory.d.ts +14 -0
- package/dist/cwi/factory.js +44 -0
- package/dist/cwi/index.d.ts +11 -0
- package/dist/cwi/index.js +11 -0
- package/dist/cwi/types.d.ts +39 -0
- package/dist/cwi/types.js +39 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +7 -1
- package/dist/indexers/CosignIndexer.js +1 -0
- package/dist/indexers/InscriptionIndexer.js +3 -4
- package/dist/indexers/LockIndexer.js +1 -0
- package/dist/indexers/OrdLockIndexer.js +1 -0
- package/dist/indexers/OriginIndexer.js +1 -1
- package/dist/indexers/index.d.ts +1 -1
- package/dist/indexers/types.d.ts +18 -0
- package/dist/services/OneSatServices.d.ts +19 -10
- package/dist/services/OneSatServices.js +201 -39
- package/dist/services/client/ChaintracksClient.d.ts +55 -13
- package/dist/services/client/ChaintracksClient.js +123 -28
- package/dist/services/client/OrdfsClient.d.ts +2 -2
- package/dist/services/client/OrdfsClient.js +4 -3
- package/dist/services/client/TxoClient.js +9 -0
- package/dist/sync/AddressManager.d.ts +85 -0
- package/dist/sync/AddressManager.js +107 -0
- package/dist/sync/SyncManager.d.ts +207 -0
- package/dist/sync/SyncManager.js +507 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.js +2 -0
- package/dist/wallet/factory.d.ts +64 -0
- package/dist/wallet/factory.js +129 -0
- package/dist/wallet/index.d.ts +1 -0
- package/dist/wallet/index.js +1 -0
- package/package.json +14 -4
- package/dist/OneSatWallet.d.ts +0 -316
- package/dist/OneSatWallet.js +0 -956
- package/dist/indexers/TransactionParser.d.ts +0 -53
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncManager - Syncs external transactions to the BRC-100 wallet.
|
|
3
|
+
*
|
|
4
|
+
* Split into two components for Chrome extension compatibility:
|
|
5
|
+
* - SyncFetcher: Calls SSE endpoint and enqueues txids (runs in UI context)
|
|
6
|
+
* - SyncProcessor: Processes queue and internalizes outputs (runs in service worker)
|
|
7
|
+
*
|
|
8
|
+
* This separation is necessary because EventSource (SSE) doesn't work reliably
|
|
9
|
+
* in Chrome extension service workers due to their ephemeral nature.
|
|
10
|
+
*/
|
|
11
|
+
import { Beef } from "@bsv/sdk";
|
|
12
|
+
import { Outpoint } from "../indexers/Outpoint";
|
|
13
|
+
import { FundIndexer } from "../indexers/FundIndexer";
|
|
14
|
+
import { InscriptionIndexer } from "../indexers/InscriptionIndexer";
|
|
15
|
+
import { Bsv21Indexer } from "../indexers/Bsv21Indexer";
|
|
16
|
+
import { OriginIndexer } from "../indexers/OriginIndexer";
|
|
17
|
+
import { OpNSIndexer } from "../indexers/OpNSIndexer";
|
|
18
|
+
import { SigmaIndexer } from "../indexers/SigmaIndexer";
|
|
19
|
+
import { MapIndexer } from "../indexers/MapIndexer";
|
|
20
|
+
/** Reorg-safe depth - only update lastQueuedScore for outputs this many blocks deep */
|
|
21
|
+
const REORG_SAFE_DEPTH = 6;
|
|
22
|
+
/** Default batch size for queue processing */
|
|
23
|
+
const DEFAULT_BATCH_SIZE = 20;
|
|
24
|
+
/**
|
|
25
|
+
* SyncFetcher - Fetches outputs via SSE and enqueues them.
|
|
26
|
+
*
|
|
27
|
+
* Run this from the UI context (popup) when the wallet opens.
|
|
28
|
+
* It will fetch all new outputs since the last sync and add them to the queue.
|
|
29
|
+
*/
|
|
30
|
+
export class SyncFetcher {
|
|
31
|
+
services;
|
|
32
|
+
syncQueue;
|
|
33
|
+
addressManager;
|
|
34
|
+
unsubscribeStream = null;
|
|
35
|
+
listeners = new Map();
|
|
36
|
+
constructor(options) {
|
|
37
|
+
this.services = options.services;
|
|
38
|
+
this.syncQueue = options.syncQueue;
|
|
39
|
+
this.addressManager = options.addressManager;
|
|
40
|
+
}
|
|
41
|
+
on(event, listener) {
|
|
42
|
+
if (!this.listeners.has(event)) {
|
|
43
|
+
this.listeners.set(event, new Set());
|
|
44
|
+
}
|
|
45
|
+
this.listeners.get(event).add(listener);
|
|
46
|
+
}
|
|
47
|
+
off(event, listener) {
|
|
48
|
+
this.listeners.get(event)?.delete(listener);
|
|
49
|
+
}
|
|
50
|
+
emit(event, data) {
|
|
51
|
+
this.listeners.get(event)?.forEach((listener) => listener(data));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Fetch new outputs via SSE and enqueue them.
|
|
55
|
+
* Returns when the stream completes (all historical data fetched).
|
|
56
|
+
*/
|
|
57
|
+
async fetch(currentHeight) {
|
|
58
|
+
if (this.unsubscribeStream) {
|
|
59
|
+
console.warn("Fetch already running");
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
const addresses = this.addressManager.getAddresses();
|
|
63
|
+
if (addresses.length === 0) {
|
|
64
|
+
console.warn("No addresses to fetch");
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
this.emit("fetch:start", { addresses });
|
|
68
|
+
// Get last score for resumption
|
|
69
|
+
const state = await this.syncQueue.getState();
|
|
70
|
+
const fromScore = state.lastQueuedScore;
|
|
71
|
+
let queuedCount = 0;
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
this.unsubscribeStream = this.services.owner.sync(addresses, async (output) => {
|
|
74
|
+
await this.handleSyncOutput(output, currentHeight);
|
|
75
|
+
queuedCount++;
|
|
76
|
+
this.emit("fetch:queued", { outpoint: output.outpoint, score: output.score });
|
|
77
|
+
}, fromScore, () => {
|
|
78
|
+
// Stream complete
|
|
79
|
+
this.unsubscribeStream = null;
|
|
80
|
+
this.emit("fetch:complete", { queuedCount });
|
|
81
|
+
resolve(queuedCount);
|
|
82
|
+
}, (error) => {
|
|
83
|
+
this.unsubscribeStream = null;
|
|
84
|
+
this.emit("fetch:error", { message: error.message });
|
|
85
|
+
reject(error);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Stop the SSE stream if running.
|
|
91
|
+
*/
|
|
92
|
+
stop() {
|
|
93
|
+
if (this.unsubscribeStream) {
|
|
94
|
+
this.unsubscribeStream();
|
|
95
|
+
this.unsubscribeStream = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async handleSyncOutput(output, currentHeight) {
|
|
99
|
+
// Enqueue the output
|
|
100
|
+
await this.syncQueue.enqueue([
|
|
101
|
+
{
|
|
102
|
+
outpoint: output.outpoint,
|
|
103
|
+
score: output.score,
|
|
104
|
+
spendTxid: output.spendTxid,
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
// Update lastQueuedScore only for reorg-safe outputs
|
|
108
|
+
const outputHeight = Math.floor(output.score / 1e9);
|
|
109
|
+
if (currentHeight - outputHeight >= REORG_SAFE_DEPTH) {
|
|
110
|
+
await this.syncQueue.setState({ lastQueuedScore: output.score });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* SyncProcessor - Processes the sync queue and internalizes outputs.
|
|
116
|
+
*
|
|
117
|
+
* Run this from the service worker. It will process items from the queue
|
|
118
|
+
* until the queue is empty or stop() is called.
|
|
119
|
+
*/
|
|
120
|
+
export class SyncProcessor {
|
|
121
|
+
wallet;
|
|
122
|
+
services;
|
|
123
|
+
syncQueue;
|
|
124
|
+
addressManager;
|
|
125
|
+
network;
|
|
126
|
+
batchSize;
|
|
127
|
+
indexers;
|
|
128
|
+
processorRunning = false;
|
|
129
|
+
stopRequested = false;
|
|
130
|
+
listeners = new Map();
|
|
131
|
+
constructor(options) {
|
|
132
|
+
this.wallet = options.wallet;
|
|
133
|
+
this.services = options.services;
|
|
134
|
+
this.syncQueue = options.syncQueue;
|
|
135
|
+
this.addressManager = options.addressManager;
|
|
136
|
+
this.network = options.network;
|
|
137
|
+
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
138
|
+
// Build owners set from address manager
|
|
139
|
+
const owners = new Set(this.addressManager.getAddresses());
|
|
140
|
+
// Initialize indexers for sync (P2PKH-based outputs only)
|
|
141
|
+
this.indexers = options.indexers ?? [
|
|
142
|
+
new FundIndexer(owners, this.network),
|
|
143
|
+
new InscriptionIndexer(owners, this.network),
|
|
144
|
+
new Bsv21Indexer(owners, this.network, this.services),
|
|
145
|
+
new OriginIndexer(owners, this.network, this.services),
|
|
146
|
+
new OpNSIndexer(owners, this.network),
|
|
147
|
+
new SigmaIndexer(owners, this.network),
|
|
148
|
+
new MapIndexer(owners, this.network),
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
on(event, listener) {
|
|
152
|
+
if (!this.listeners.has(event)) {
|
|
153
|
+
this.listeners.set(event, new Set());
|
|
154
|
+
}
|
|
155
|
+
this.listeners.get(event).add(listener);
|
|
156
|
+
}
|
|
157
|
+
off(event, listener) {
|
|
158
|
+
this.listeners.get(event)?.delete(listener);
|
|
159
|
+
}
|
|
160
|
+
emit(event, data) {
|
|
161
|
+
this.listeners.get(event)?.forEach((listener) => listener(data));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Start processing the queue.
|
|
165
|
+
* Processes until queue is empty or stop() is called.
|
|
166
|
+
*/
|
|
167
|
+
async start() {
|
|
168
|
+
if (this.processorRunning) {
|
|
169
|
+
console.warn("Processor already running");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Reset any stuck processing items
|
|
173
|
+
await this.syncQueue.resetProcessing();
|
|
174
|
+
this.stopRequested = false;
|
|
175
|
+
this.emit("process:start", {});
|
|
176
|
+
await this.processQueueLoop();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Stop processing the queue.
|
|
180
|
+
*/
|
|
181
|
+
stop() {
|
|
182
|
+
this.stopRequested = true;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if processor is currently running.
|
|
186
|
+
*/
|
|
187
|
+
isRunning() {
|
|
188
|
+
return this.processorRunning;
|
|
189
|
+
}
|
|
190
|
+
async processQueueLoop() {
|
|
191
|
+
this.processorRunning = true;
|
|
192
|
+
try {
|
|
193
|
+
while (!this.stopRequested) {
|
|
194
|
+
// Claim batch of pending items grouped by txid
|
|
195
|
+
const byTxid = await this.syncQueue.claim(this.batchSize);
|
|
196
|
+
if (byTxid.size === 0) {
|
|
197
|
+
// Queue empty - wait and poll again
|
|
198
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
// Process each txid in parallel
|
|
202
|
+
await Promise.all(Array.from(byTxid.entries()).map(([txid, items]) => this.processTxid(txid, items)));
|
|
203
|
+
// Emit progress
|
|
204
|
+
const stats = await this.syncQueue.getStats();
|
|
205
|
+
this.emit("process:progress", {
|
|
206
|
+
pending: stats.pending,
|
|
207
|
+
done: stats.done,
|
|
208
|
+
failed: stats.failed,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
this.emit("process:complete", {});
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
this.emit("process:error", {
|
|
215
|
+
message: error instanceof Error ? error.message : String(error),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
this.processorRunning = false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async processTxid(txid, items) {
|
|
223
|
+
try {
|
|
224
|
+
// Build spend map: vout → spendTxid
|
|
225
|
+
const spendMap = new Map();
|
|
226
|
+
for (const item of items) {
|
|
227
|
+
if (item.spendTxid) {
|
|
228
|
+
const vout = parseInt(item.outpoint.split("_")[1], 10);
|
|
229
|
+
spendMap.set(vout, item.spendTxid);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// If all items are spend-only (no new outputs), just mark outputs as spent
|
|
233
|
+
const hasNewOutputs = items.some((item) => !item.spendTxid);
|
|
234
|
+
if (!hasNewOutputs) {
|
|
235
|
+
// TODO: Mark outputs as spent in wallet storage
|
|
236
|
+
// For now, just complete the items
|
|
237
|
+
await this.syncQueue.completeMany(items.map((i) => i.id));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// Load transaction as BEEF
|
|
241
|
+
const beef = await this.services.beef.getBeef(txid);
|
|
242
|
+
if (!beef) {
|
|
243
|
+
throw new Error(`Failed to load transaction ${txid}`);
|
|
244
|
+
}
|
|
245
|
+
// Parse transaction with indexers
|
|
246
|
+
const beefObj = Beef.fromBinary(Array.from(beef));
|
|
247
|
+
const btx = beefObj.findTxid(txid);
|
|
248
|
+
if (!btx?.tx) {
|
|
249
|
+
throw new Error(`Transaction ${txid} not found in BEEF`);
|
|
250
|
+
}
|
|
251
|
+
const ctx = await this.parseTransaction(btx.tx);
|
|
252
|
+
// Build InternalizeOutput entries for owned outputs
|
|
253
|
+
const outputs = [];
|
|
254
|
+
let internalizedCount = 0;
|
|
255
|
+
for (const txo of ctx.txos) {
|
|
256
|
+
if (!txo.owner)
|
|
257
|
+
continue;
|
|
258
|
+
// Check if this output is at one of our receive addresses
|
|
259
|
+
const derivation = this.addressManager.getDerivation(txo.owner);
|
|
260
|
+
if (!derivation)
|
|
261
|
+
continue;
|
|
262
|
+
// Build internalize output using protocol from indexer
|
|
263
|
+
const internalizeOutput = this.buildInternalizeOutput(txo, derivation);
|
|
264
|
+
if (internalizeOutput) {
|
|
265
|
+
outputs.push(internalizeOutput);
|
|
266
|
+
internalizedCount++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Internalize if we have owned outputs
|
|
270
|
+
if (outputs.length > 0) {
|
|
271
|
+
const args = {
|
|
272
|
+
tx: Array.from(beef),
|
|
273
|
+
outputs,
|
|
274
|
+
description: "1sat sync",
|
|
275
|
+
};
|
|
276
|
+
console.log(`[SyncProcessor] Internalizing ${outputs.length} outputs for txid ${txid}`, outputs.map((o) => ({ vout: o.outputIndex, protocol: o.protocol })));
|
|
277
|
+
await this.wallet.internalizeAction(args);
|
|
278
|
+
console.log(`[SyncProcessor] Internalization complete for txid ${txid}`);
|
|
279
|
+
this.emit("process:parsed", { internalizedCount });
|
|
280
|
+
}
|
|
281
|
+
// Complete all items for this txid
|
|
282
|
+
await this.syncQueue.completeMany(items.map((i) => i.id));
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
// Fail all items for this txid
|
|
286
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
287
|
+
console.error(`[SyncProcessor] Failed to process txid ${txid}:`, errorMsg);
|
|
288
|
+
for (const item of items) {
|
|
289
|
+
await this.syncQueue.fail(item.id, errorMsg);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async parseTransaction(tx) {
|
|
294
|
+
const txid = tx.id("hex");
|
|
295
|
+
// Initialize parse context
|
|
296
|
+
const ctx = {
|
|
297
|
+
tx,
|
|
298
|
+
txid,
|
|
299
|
+
txos: [],
|
|
300
|
+
spends: [],
|
|
301
|
+
summary: {},
|
|
302
|
+
indexers: this.indexers,
|
|
303
|
+
};
|
|
304
|
+
// Parse each output
|
|
305
|
+
for (let vout = 0; vout < tx.outputs.length; vout++) {
|
|
306
|
+
const output = tx.outputs[vout];
|
|
307
|
+
const txo = {
|
|
308
|
+
output,
|
|
309
|
+
outpoint: new Outpoint(txid, vout),
|
|
310
|
+
data: {},
|
|
311
|
+
};
|
|
312
|
+
// Run through all indexers
|
|
313
|
+
for (const indexer of this.indexers) {
|
|
314
|
+
const result = await indexer.parse(txo);
|
|
315
|
+
if (result) {
|
|
316
|
+
txo.data[indexer.tag] = {
|
|
317
|
+
data: result.data,
|
|
318
|
+
tags: result.tags,
|
|
319
|
+
content: result.content,
|
|
320
|
+
};
|
|
321
|
+
// First indexer to set owner wins
|
|
322
|
+
if (result.owner && !txo.owner) {
|
|
323
|
+
txo.owner = result.owner;
|
|
324
|
+
}
|
|
325
|
+
// First indexer to set basket wins
|
|
326
|
+
if (result.basket && !txo.basket) {
|
|
327
|
+
txo.basket = result.basket;
|
|
328
|
+
}
|
|
329
|
+
// First indexer to set protocol wins (custom scripts override P2PKH)
|
|
330
|
+
if (result.protocol && !txo.protocol) {
|
|
331
|
+
txo.protocol = result.protocol;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
ctx.txos.push(txo);
|
|
336
|
+
}
|
|
337
|
+
// Run summarize phase for all indexers
|
|
338
|
+
for (const indexer of this.indexers) {
|
|
339
|
+
const summary = await indexer.summarize(ctx, true);
|
|
340
|
+
if (summary) {
|
|
341
|
+
ctx.summary[indexer.tag] = summary;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return ctx;
|
|
345
|
+
}
|
|
346
|
+
buildInternalizeOutput(txo, derivation) {
|
|
347
|
+
const vout = txo.outpoint.vout;
|
|
348
|
+
// Use protocol from indexer, default to "wallet payment" for P2PKH
|
|
349
|
+
const protocol = txo.protocol || "wallet payment";
|
|
350
|
+
if (protocol === "basket insertion") {
|
|
351
|
+
// Custom script - use basket insertion
|
|
352
|
+
// These outputs need custom unlock scripts when spent
|
|
353
|
+
const basket = txo.basket || "custom";
|
|
354
|
+
const tags = this.collectTags(txo);
|
|
355
|
+
return {
|
|
356
|
+
outputIndex: vout,
|
|
357
|
+
protocol: "basket insertion",
|
|
358
|
+
insertionRemittance: {
|
|
359
|
+
basket,
|
|
360
|
+
tags,
|
|
361
|
+
// Store derivation info for future signing
|
|
362
|
+
customInstructions: JSON.stringify({
|
|
363
|
+
derivationPrefix: derivation.derivationPrefix,
|
|
364
|
+
derivationSuffix: derivation.derivationSuffix,
|
|
365
|
+
senderIdentityKey: derivation.senderIdentityKey,
|
|
366
|
+
}),
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
// P2PKH-based output - use wallet payment for auto-signing
|
|
372
|
+
return {
|
|
373
|
+
outputIndex: vout,
|
|
374
|
+
protocol: "wallet payment",
|
|
375
|
+
paymentRemittance: {
|
|
376
|
+
derivationPrefix: derivation.derivationPrefix,
|
|
377
|
+
derivationSuffix: derivation.derivationSuffix,
|
|
378
|
+
senderIdentityKey: derivation.senderIdentityKey,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
collectTags(txo) {
|
|
384
|
+
const tags = [];
|
|
385
|
+
for (const indexData of Object.values(txo.data)) {
|
|
386
|
+
tags.push(...indexData.tags);
|
|
387
|
+
}
|
|
388
|
+
return tags;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* SyncManager - Legacy class that combines SSE fetching and queue processing.
|
|
393
|
+
*
|
|
394
|
+
* @deprecated For Chrome extensions, use SyncFetcher (in UI) and SyncProcessor (in service worker)
|
|
395
|
+
* separately since EventSource doesn't work reliably in service workers.
|
|
396
|
+
*
|
|
397
|
+
* This class is kept for backwards compatibility and for environments where
|
|
398
|
+
* SSE works (e.g., Node.js, React Native).
|
|
399
|
+
*/
|
|
400
|
+
export class SyncManager {
|
|
401
|
+
fetcher;
|
|
402
|
+
processor;
|
|
403
|
+
wallet;
|
|
404
|
+
addressManager;
|
|
405
|
+
listeners = new Map();
|
|
406
|
+
fetchPromise = null;
|
|
407
|
+
constructor(options) {
|
|
408
|
+
this.wallet = options.wallet;
|
|
409
|
+
this.addressManager = options.addressManager;
|
|
410
|
+
// Create fetcher for SSE stream
|
|
411
|
+
this.fetcher = new SyncFetcher({
|
|
412
|
+
services: options.services,
|
|
413
|
+
syncQueue: options.syncQueue,
|
|
414
|
+
addressManager: options.addressManager,
|
|
415
|
+
});
|
|
416
|
+
// Create processor for queue processing
|
|
417
|
+
this.processor = new SyncProcessor({
|
|
418
|
+
wallet: options.wallet,
|
|
419
|
+
services: options.services,
|
|
420
|
+
syncQueue: options.syncQueue,
|
|
421
|
+
addressManager: options.addressManager,
|
|
422
|
+
network: options.network,
|
|
423
|
+
batchSize: options.batchSize,
|
|
424
|
+
indexers: options.indexers,
|
|
425
|
+
});
|
|
426
|
+
// Wire up event forwarding
|
|
427
|
+
this.setupEventForwarding();
|
|
428
|
+
}
|
|
429
|
+
setupEventForwarding() {
|
|
430
|
+
// Forward fetcher events
|
|
431
|
+
this.fetcher.on("fetch:start", (data) => {
|
|
432
|
+
this.emit("sync:start", { addresses: data.addresses });
|
|
433
|
+
});
|
|
434
|
+
this.fetcher.on("fetch:error", (data) => {
|
|
435
|
+
this.emit("sync:error", { message: data.message });
|
|
436
|
+
});
|
|
437
|
+
// Forward processor events
|
|
438
|
+
this.processor.on("process:progress", (data) => {
|
|
439
|
+
this.emit("sync:progress", data);
|
|
440
|
+
});
|
|
441
|
+
this.processor.on("process:complete", () => {
|
|
442
|
+
this.emit("sync:complete", {});
|
|
443
|
+
});
|
|
444
|
+
this.processor.on("process:error", (data) => {
|
|
445
|
+
this.emit("sync:error", { message: data.message });
|
|
446
|
+
});
|
|
447
|
+
this.processor.on("process:parsed", (data) => {
|
|
448
|
+
this.emit("sync:parsed", data);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
// ===== Event Emitter =====
|
|
452
|
+
on(event, listener) {
|
|
453
|
+
if (!this.listeners.has(event)) {
|
|
454
|
+
this.listeners.set(event, new Set());
|
|
455
|
+
}
|
|
456
|
+
this.listeners.get(event).add(listener);
|
|
457
|
+
}
|
|
458
|
+
off(event, listener) {
|
|
459
|
+
this.listeners.get(event)?.delete(listener);
|
|
460
|
+
}
|
|
461
|
+
emit(event, data) {
|
|
462
|
+
this.listeners.get(event)?.forEach((listener) => listener(data));
|
|
463
|
+
}
|
|
464
|
+
// ===== Sync Control =====
|
|
465
|
+
/**
|
|
466
|
+
* Start syncing - opens SSE stream and starts queue processor.
|
|
467
|
+
*/
|
|
468
|
+
async sync() {
|
|
469
|
+
if (this.fetchPromise || this.processor.isRunning()) {
|
|
470
|
+
console.warn("Sync already running");
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const addresses = this.addressManager.getAddresses();
|
|
474
|
+
if (addresses.length === 0) {
|
|
475
|
+
console.warn("No addresses to sync");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// Get current height for reorg protection
|
|
479
|
+
const { height: currentHeight } = await this.wallet.getHeight({});
|
|
480
|
+
// Start fetcher (SSE stream) - this runs until stream completes
|
|
481
|
+
this.fetchPromise = this.fetcher.fetch(currentHeight);
|
|
482
|
+
// Start processor in parallel - this processes items as they come in
|
|
483
|
+
// and waits for more until fetch completes
|
|
484
|
+
this.processor.start();
|
|
485
|
+
// Wait for fetch to complete, then let processor finish
|
|
486
|
+
try {
|
|
487
|
+
await this.fetchPromise;
|
|
488
|
+
}
|
|
489
|
+
finally {
|
|
490
|
+
this.fetchPromise = null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Stop syncing - closes SSE stream and stops queue processor.
|
|
495
|
+
*/
|
|
496
|
+
stop() {
|
|
497
|
+
this.fetcher.stop();
|
|
498
|
+
this.processor.stop();
|
|
499
|
+
this.fetchPromise = null;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Check if sync is currently running.
|
|
503
|
+
*/
|
|
504
|
+
isSyncing() {
|
|
505
|
+
return this.fetchPromise !== null || this.processor.isRunning();
|
|
506
|
+
}
|
|
507
|
+
}
|
package/dist/sync/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
2
|
export { IndexedDbSyncQueue } from "./IndexedDbSyncQueue";
|
|
3
3
|
export { SqliteSyncQueue } from "./SqliteSyncQueue";
|
|
4
|
+
export { AddressManager, YOURS_PREFIX, BRC29_PROTOCOL_ID } from "./AddressManager";
|
|
5
|
+
export type { AddressDerivation } from "./AddressManager";
|
|
6
|
+
export { SyncFetcher, SyncProcessor, SyncManager } from "./SyncManager";
|
|
7
|
+
export type { SyncFetcherOptions, SyncFetcherEvents, SyncProcessorOptions, SyncProcessorEvents, SyncManagerOptions, SyncEvents, } from "./SyncManager";
|
package/dist/sync/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
2
|
export { IndexedDbSyncQueue } from "./IndexedDbSyncQueue";
|
|
3
3
|
export { SqliteSyncQueue } from "./SqliteSyncQueue";
|
|
4
|
+
export { AddressManager, YOURS_PREFIX, BRC29_PROTOCOL_ID } from "./AddressManager";
|
|
5
|
+
export { SyncFetcher, SyncProcessor, SyncManager } from "./SyncManager";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating web wallets.
|
|
3
|
+
*
|
|
4
|
+
* This consolidates the common wallet setup used by both yours-wallet
|
|
5
|
+
* (browser extension) and 1sat-website (React app).
|
|
6
|
+
*/
|
|
7
|
+
import { PrivateKey } from "@bsv/sdk";
|
|
8
|
+
import { WalletPermissionsManager, Monitor, type PermissionsManagerConfig } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
|
|
9
|
+
import { OneSatServices } from "../services/OneSatServices";
|
|
10
|
+
type Chain = "main" | "test";
|
|
11
|
+
/**
|
|
12
|
+
* Configuration for creating a web wallet.
|
|
13
|
+
*/
|
|
14
|
+
export interface WebWalletConfig {
|
|
15
|
+
/** Private key - can be PrivateKey instance, WIF string, or hex string */
|
|
16
|
+
privateKey: PrivateKey | string;
|
|
17
|
+
/** Network: 'main' or 'test' */
|
|
18
|
+
chain: Chain;
|
|
19
|
+
/** Admin originator that bypasses permission checks (e.g., chrome-extension://id or https://wallet.example.com) */
|
|
20
|
+
adminOriginator: string;
|
|
21
|
+
/** Permission configuration for WalletPermissionsManager */
|
|
22
|
+
permissionsConfig: PermissionsManagerConfig;
|
|
23
|
+
/** Fee model. Default: { model: 'sat/kb', value: 1 } */
|
|
24
|
+
feeModel?: {
|
|
25
|
+
model: "sat/kb";
|
|
26
|
+
value: number;
|
|
27
|
+
};
|
|
28
|
+
/** Remote storage URL. If provided, attempts to connect for cloud backup. */
|
|
29
|
+
remoteStorageUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Result of wallet creation.
|
|
33
|
+
*/
|
|
34
|
+
export interface WebWalletResult {
|
|
35
|
+
/** Wallet instance with permission management */
|
|
36
|
+
wallet: WalletPermissionsManager;
|
|
37
|
+
/** 1Sat services for API access */
|
|
38
|
+
services: OneSatServices;
|
|
39
|
+
/** Monitor for transaction lifecycle (not started - call monitor.startTasks() when ready) */
|
|
40
|
+
monitor: Monitor;
|
|
41
|
+
/** Cleanup function - stops monitor, destroys wallet */
|
|
42
|
+
destroy: () => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a web wallet with storage, services, permissions, and monitor.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const { wallet, services, monitor, destroy } = await createWebWallet({
|
|
50
|
+
* privateKey: identityWif,
|
|
51
|
+
* chain: 'main',
|
|
52
|
+
* adminOriginator: 'https://wallet.example.com',
|
|
53
|
+
* permissionsConfig: DEFAULT_PERMISSIONS_CONFIG,
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* // Wire up monitor callbacks
|
|
57
|
+
* monitor.onTransactionProven = async (status) => console.log('Proven:', status.txid);
|
|
58
|
+
*
|
|
59
|
+
* // Start monitor when ready
|
|
60
|
+
* monitor.startTasks();
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function createWebWallet(config: WebWalletConfig): Promise<WebWalletResult>;
|
|
64
|
+
export {};
|