@1sat/wallet-toolbox 0.0.65 → 0.0.68
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/payments/index.js +7 -21
- package/dist/wallet/factory.js +65 -76
- package/package.json +1 -1
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Inscription } from "@bopen-io/templates";
|
|
7
7
|
import { P2PKH, Script, Utils } from "@bsv/sdk";
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Magic constant that tells the wallet to send all available funds minus fees.
|
|
10
|
+
* When an output has this satoshis value, it's adjusted to the maximum fundable amount.
|
|
11
|
+
*/
|
|
12
|
+
const maxPossibleSatoshis = 2099999999999999;
|
|
9
13
|
// ============================================================================
|
|
10
14
|
// Internal helpers
|
|
11
15
|
// ============================================================================
|
|
@@ -153,32 +157,14 @@ export const sendAllBsv = {
|
|
|
153
157
|
if (isPaymail(destination)) {
|
|
154
158
|
return { error: "paymail-not-yet-implemented" };
|
|
155
159
|
}
|
|
156
|
-
const listResult = await ctx.wallet.listOutputs({
|
|
157
|
-
basket: FUNDING_BASKET,
|
|
158
|
-
include: "locking scripts",
|
|
159
|
-
limit: 10000,
|
|
160
|
-
});
|
|
161
|
-
if (!listResult.outputs || listResult.outputs.length === 0) {
|
|
162
|
-
return { error: "no-funds" };
|
|
163
|
-
}
|
|
164
|
-
const totalSats = listResult.outputs.reduce((sum, o) => sum + o.satoshis, 0);
|
|
165
|
-
const estimatedFee = Math.ceil((listResult.outputs.length * 150 + 44) * 1);
|
|
166
|
-
const sendAmount = totalSats - estimatedFee;
|
|
167
|
-
if (sendAmount <= 0) {
|
|
168
|
-
return { error: "insufficient-funds-for-fee" };
|
|
169
|
-
}
|
|
170
|
-
const inputs = listResult.outputs.map((o) => ({
|
|
171
|
-
outpoint: o.outpoint,
|
|
172
|
-
inputDescription: "Sweep funds",
|
|
173
|
-
}));
|
|
174
160
|
const result = await ctx.wallet.createAction({
|
|
175
161
|
description: "Send all BSV",
|
|
176
|
-
inputs,
|
|
177
162
|
outputs: [
|
|
178
163
|
{
|
|
179
164
|
lockingScript: new P2PKH().lock(destination).toHex(),
|
|
180
|
-
satoshis:
|
|
165
|
+
satoshis: maxPossibleSatoshis,
|
|
181
166
|
outputDescription: "Sweep all funds",
|
|
167
|
+
tags: [],
|
|
182
168
|
},
|
|
183
169
|
],
|
|
184
170
|
options: { signAndProcess: true, acceptDelayedBroadcast: false },
|
package/dist/wallet/factory.js
CHANGED
|
@@ -95,76 +95,71 @@ export async function createWebWallet(config) {
|
|
|
95
95
|
// Add remote storage to the existing storage manager using public API
|
|
96
96
|
await storage.addWalletStorageProvider(remoteClient);
|
|
97
97
|
console.log("[createWebWallet] Remote storage connected successfully");
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
console.log(
|
|
98
|
+
// Bidirectional sync: pull first, then push
|
|
99
|
+
// Pull builds idMap via natural key matching (reference for transactions)
|
|
100
|
+
// Push sends local changes to remote
|
|
101
|
+
console.log("[createWebWallet] Pulling from remote...");
|
|
102
|
+
const pullResult = await storage.syncFromReader(identityPubKey, remoteClient);
|
|
103
|
+
console.log(`[createWebWallet] Pulled: ${pullResult.inserts} inserts, ${pullResult.updates} updates`);
|
|
104
|
+
console.log("[createWebWallet] Pushing to remote...");
|
|
105
|
+
await storage.updateBackups(undefined, (msg) => {
|
|
106
|
+
console.log("[createWebWallet] Push:", msg);
|
|
107
|
+
return msg;
|
|
108
|
+
});
|
|
109
|
+
console.log("[createWebWallet] Push complete");
|
|
102
110
|
}
|
|
103
111
|
catch (err) {
|
|
104
112
|
console.log("[createWebWallet] Remote storage connection failed:", err instanceof Error ? err.message : err);
|
|
105
113
|
remoteClient = undefined;
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
|
-
// Log storage state
|
|
109
|
-
const stores = storage.getStores();
|
|
116
|
+
// Log storage state for debugging
|
|
110
117
|
console.log("[createWebWallet] Storage state:", {
|
|
111
118
|
activeKey: storage.getActiveStore(),
|
|
112
|
-
backups: storage.getBackupStores(),
|
|
113
|
-
conflictingActives: storage.getConflictingStores(),
|
|
119
|
+
backups: storage.getBackupStores().length,
|
|
120
|
+
conflictingActives: storage.getConflictingStores().length,
|
|
114
121
|
isActiveEnabled: storage.isActiveEnabled,
|
|
115
|
-
allStores: stores.map((s) => ({
|
|
116
|
-
name: s.storageName,
|
|
117
|
-
key: s.storageIdentityKey.slice(0, 16) + "...",
|
|
118
|
-
})),
|
|
119
122
|
});
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
console.log("[createWebWallet] Resolving conflicting actives...");
|
|
126
|
-
try {
|
|
127
|
-
await storage.setActive(localKey, (msg) => {
|
|
128
|
-
console.log("[createWebWallet] Conflict resolution:", msg);
|
|
123
|
+
// Helper to sync to remote backup using public updateBackups API
|
|
124
|
+
const syncToBackup = async (context) => {
|
|
125
|
+
if (storage.getBackupStores().length > 0) {
|
|
126
|
+
await storage.updateBackups(undefined, (msg) => {
|
|
127
|
+
console.log(`[createWebWallet] ${context}:`, msg);
|
|
129
128
|
return msg;
|
|
130
129
|
});
|
|
131
|
-
console.log("[createWebWallet] Conflict resolution complete");
|
|
132
|
-
}
|
|
133
|
-
catch (err) {
|
|
134
|
-
console.log("[createWebWallet] Conflict resolution failed:", err instanceof Error ? err.message : err);
|
|
135
|
-
// FALLBACK: If conflict resolution fails, operate in local-only mode
|
|
136
|
-
// This allows the wallet to function even when remote sync is broken
|
|
137
|
-
console.log("[createWebWallet] Falling back to local-only mode (remote disabled)");
|
|
138
|
-
// Recreate storage manager with only local storage to clear conflicts
|
|
139
|
-
storage = new WalletStorageManager(identityPubKey, localStorage, []);
|
|
140
|
-
await storage.makeAvailable();
|
|
141
|
-
// Update the wallet's storage reference
|
|
142
|
-
underlyingWallet.storage = storage;
|
|
143
|
-
remoteClient = undefined;
|
|
144
|
-
// Refresh conflict state
|
|
145
|
-
conflictingStores = storage.getConflictingStores();
|
|
146
|
-
backupStores = storage.getBackupStores();
|
|
147
|
-
console.log("[createWebWallet] Local-only mode active, isActiveEnabled:", storage.isActiveEnabled);
|
|
148
130
|
}
|
|
131
|
+
};
|
|
132
|
+
// 8. Intercept createAction/signAction to sync after immediate broadcasts
|
|
133
|
+
// With acceptDelayedBroadcast: false, broadcasts happen synchronously and
|
|
134
|
+
// bypass the monitor's onTransactionBroadcasted callback. We detect broadcasts
|
|
135
|
+
// by checking for txid in the result and sync to backup immediately.
|
|
136
|
+
if (remoteClient) {
|
|
137
|
+
const originalCreateAction = underlyingWallet.createAction.bind(underlyingWallet);
|
|
138
|
+
underlyingWallet.createAction = async (args) => {
|
|
139
|
+
const result = await originalCreateAction(args);
|
|
140
|
+
if (result.txid) {
|
|
141
|
+
console.log("[createWebWallet] Broadcast detected in createAction:", result.txid);
|
|
142
|
+
syncToBackup("Backup after createAction").catch((err) => {
|
|
143
|
+
console.warn("[createWebWallet] Failed to sync after createAction:", err);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
};
|
|
148
|
+
const originalSignAction = underlyingWallet.signAction.bind(underlyingWallet);
|
|
149
|
+
underlyingWallet.signAction = async (args) => {
|
|
150
|
+
const result = await originalSignAction(args);
|
|
151
|
+
if (result.txid) {
|
|
152
|
+
console.log("[createWebWallet] Broadcast detected in signAction:", result.txid);
|
|
153
|
+
syncToBackup("Backup after signAction").catch((err) => {
|
|
154
|
+
console.warn("[createWebWallet] Failed to sync after signAction:", err);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
};
|
|
149
159
|
}
|
|
150
|
-
|
|
151
|
-
// No conflicts - push local state to remote backup (fire-and-forget)
|
|
152
|
-
console.log("[createWebWallet] Pushing local state to remote backup...");
|
|
153
|
-
storage
|
|
154
|
-
.updateBackups(undefined, (msg) => {
|
|
155
|
-
console.log("[createWebWallet] Backup:", msg);
|
|
156
|
-
return msg;
|
|
157
|
-
})
|
|
158
|
-
.then(() => {
|
|
159
|
-
console.log("[createWebWallet] Backup complete");
|
|
160
|
-
})
|
|
161
|
-
.catch((err) => {
|
|
162
|
-
console.log("[createWebWallet] Backup failed:", err instanceof Error ? err.message : err);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
// 8. Wrap with permissions manager
|
|
160
|
+
// 9. Wrap with permissions manager
|
|
166
161
|
const wallet = new WalletPermissionsManager(underlyingWallet, adminOriginator, permissionsConfig);
|
|
167
|
-
//
|
|
162
|
+
// 10. Create monitor (not started - consumer calls startTasks() when ready)
|
|
168
163
|
const monitor = new Monitor({
|
|
169
164
|
chain,
|
|
170
165
|
services: oneSatServices,
|
|
@@ -178,26 +173,20 @@ export async function createWebWallet(config) {
|
|
|
178
173
|
});
|
|
179
174
|
monitor.addDefaultTasks();
|
|
180
175
|
console.log("[createWebWallet] Monitor created with tasks:", monitor["_tasks"].map((t) => t.name));
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
await storage.updateBackups(undefined, (msg) => {
|
|
185
|
-
console.log(`[Monitor] ${context}:`, msg);
|
|
186
|
-
return msg;
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
// 10. Wire up monitor callbacks - sync to remote first, then call user callbacks
|
|
176
|
+
// 11. Wire up monitor callbacks - sync to remote first, then call user callbacks
|
|
177
|
+
// Note: For delayed broadcasts, the monitor triggers these. For immediate broadcasts,
|
|
178
|
+
// the interception in step 8 handles the sync, but these still fire for the user callback.
|
|
191
179
|
monitor.onTransactionBroadcasted = async (result) => {
|
|
192
|
-
console.log("[
|
|
180
|
+
console.log("[createWebWallet] Monitor detected broadcast:", result.txid);
|
|
193
181
|
// Sync to remote backup first (if connected)
|
|
182
|
+
// Note: For immediate broadcasts, step 8 already synced, but this is harmless
|
|
194
183
|
if (remoteClient) {
|
|
195
184
|
try {
|
|
196
|
-
await syncToBackup("Backup after broadcast");
|
|
197
|
-
console.log("[
|
|
185
|
+
await syncToBackup("Backup after monitor broadcast");
|
|
186
|
+
console.log("[createWebWallet] Synced to backup after monitor broadcast");
|
|
198
187
|
}
|
|
199
188
|
catch (err) {
|
|
200
|
-
console.warn("[
|
|
189
|
+
console.warn("[createWebWallet] Failed to sync after monitor broadcast:", err);
|
|
201
190
|
}
|
|
202
191
|
}
|
|
203
192
|
// Then call user callback (if provided)
|
|
@@ -206,20 +195,20 @@ export async function createWebWallet(config) {
|
|
|
206
195
|
config.onTransactionBroadcasted(result.txid);
|
|
207
196
|
}
|
|
208
197
|
catch (err) {
|
|
209
|
-
console.warn("[
|
|
198
|
+
console.warn("[createWebWallet] User callback error after broadcast:", err);
|
|
210
199
|
}
|
|
211
200
|
}
|
|
212
201
|
};
|
|
213
202
|
monitor.onTransactionProven = async (status) => {
|
|
214
|
-
console.log("[
|
|
203
|
+
console.log("[createWebWallet] Transaction proven:", status.txid, "block", status.blockHeight);
|
|
215
204
|
// Sync to remote backup first (if connected)
|
|
216
205
|
if (remoteClient) {
|
|
217
206
|
try {
|
|
218
207
|
await syncToBackup("Backup after confirmation");
|
|
219
|
-
console.log("[
|
|
208
|
+
console.log("[createWebWallet] Synced to backup after confirmation");
|
|
220
209
|
}
|
|
221
210
|
catch (err) {
|
|
222
|
-
console.warn("[
|
|
211
|
+
console.warn("[createWebWallet] Failed to sync after confirmation:", err);
|
|
223
212
|
}
|
|
224
213
|
}
|
|
225
214
|
// Then call user callback (if provided)
|
|
@@ -228,17 +217,17 @@ export async function createWebWallet(config) {
|
|
|
228
217
|
config.onTransactionProven(status.txid, status.blockHeight);
|
|
229
218
|
}
|
|
230
219
|
catch (err) {
|
|
231
|
-
console.warn("[
|
|
220
|
+
console.warn("[createWebWallet] User callback error after proven:", err);
|
|
232
221
|
}
|
|
233
222
|
}
|
|
234
223
|
};
|
|
235
|
-
//
|
|
224
|
+
// 12. Create cleanup function
|
|
236
225
|
const destroy = async () => {
|
|
237
226
|
monitor.stopTasks();
|
|
238
227
|
await monitor.destroy();
|
|
239
228
|
await underlyingWallet.destroy();
|
|
240
229
|
};
|
|
241
|
-
//
|
|
230
|
+
// 13. Create fullSync function if remote storage is connected
|
|
242
231
|
const fullSyncFn = remoteClient
|
|
243
232
|
? async (onProgress) => {
|
|
244
233
|
return fullSync({
|