@1sat/wallet-toolbox 0.0.10 → 0.0.11

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.
@@ -33,4 +33,5 @@ export * from "./inscriptions";
33
33
  export * from "./locks";
34
34
  export * from "./signing";
35
35
  export * from "./broadcast";
36
+ export * from "./sweep";
36
37
  export type { WalletInterface, WalletOutput, CreateActionArgs, CreateActionResult, CreateActionOutput, CreateActionInput, ListOutputsResult, ListOutputsArgs, } from "@bsv/sdk";
package/dist/api/index.js CHANGED
@@ -37,6 +37,8 @@ export * from "./inscriptions";
37
37
  export * from "./locks";
38
38
  export * from "./signing";
39
39
  export * from "./broadcast";
40
+ // Export sweep module (uses external signing, not skill-based)
41
+ export * from "./sweep";
40
42
  // Register all skills with the global registry
41
43
  import { skillRegistry } from "./skills/registry";
42
44
  import { balanceSkills } from "./balance";
@@ -47,6 +49,7 @@ import { inscriptionsSkills } from "./inscriptions";
47
49
  import { locksSkills } from "./locks";
48
50
  import { signingSkills } from "./signing";
49
51
  import { broadcastSkills } from "./broadcast";
52
+ import { sweepSkills } from "./sweep";
50
53
  skillRegistry.registerAll([
51
54
  ...balanceSkills,
52
55
  ...paymentsSkills,
@@ -56,4 +59,5 @@ skillRegistry.registerAll([
56
59
  ...locksSkills,
57
60
  ...signingSkills,
58
61
  ...broadcastSkills,
62
+ ...sweepSkills,
59
63
  ]);
@@ -32,7 +32,7 @@ export interface JsonSchemaProperty {
32
32
  /**
33
33
  * Skill category for grouping related operations.
34
34
  */
35
- export type SkillCategory = "balance" | "payments" | "ordinals" | "tokens" | "inscriptions" | "locks" | "signing" | "broadcast";
35
+ export type SkillCategory = "balance" | "payments" | "ordinals" | "tokens" | "inscriptions" | "locks" | "signing" | "broadcast" | "sweep";
36
36
  /**
37
37
  * Metadata describing a skill for AI agents and tooling.
38
38
  */
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Sweep Module
3
+ *
4
+ * Functions for sweeping assets from external wallets into a BRC-100 wallet.
5
+ */
6
+ import type { OneSatContext, Skill } from "../skills/types";
7
+ import type { IndexedOutput } from "../../services/types";
8
+ import type { SweepBsvRequest, SweepBsvResponse, SweepInput } from "./types";
9
+ export * from "./types";
10
+ /**
11
+ * Prepare sweep inputs from IndexedOutput objects by fetching locking scripts.
12
+ * This extracts locking scripts from the raw transactions in BEEF format.
13
+ */
14
+ export declare function prepareSweepInputs(ctx: OneSatContext, utxos: IndexedOutput[]): Promise<SweepInput[]>;
15
+ /**
16
+ * Sweep BSV from external inputs into the destination wallet.
17
+ *
18
+ * If amount is specified, only that amount is swept and the remainder
19
+ * is returned to the source address. If amount is omitted, all input
20
+ * value is swept (minus fees).
21
+ */
22
+ export declare const sweepBsv: Skill<SweepBsvRequest, SweepBsvResponse>;
23
+ export declare const sweepSkills: Skill<SweepBsvRequest, SweepBsvResponse>[];
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Sweep Module
3
+ *
4
+ * Functions for sweeping assets from external wallets into a BRC-100 wallet.
5
+ */
6
+ import { P2PKH, PrivateKey, Transaction, } from "@bsv/sdk";
7
+ export * from "./types";
8
+ /**
9
+ * Prepare sweep inputs from IndexedOutput objects by fetching locking scripts.
10
+ * This extracts locking scripts from the raw transactions in BEEF format.
11
+ */
12
+ export async function prepareSweepInputs(ctx, utxos) {
13
+ if (!ctx.services) {
14
+ throw new Error("Services required for prepareSweepInputs");
15
+ }
16
+ // Group UTXOs by txid to minimize BEEF fetches
17
+ const byTxid = new Map();
18
+ for (const utxo of utxos) {
19
+ const [txid, voutStr] = utxo.outpoint.split("_");
20
+ const vout = Number.parseInt(voutStr, 10);
21
+ const existing = byTxid.get(txid) ?? [];
22
+ existing.push({ vout, utxo });
23
+ byTxid.set(txid, existing);
24
+ }
25
+ const results = [];
26
+ // Fetch BEEF for each txid and extract locking scripts
27
+ for (const [txid, outputs] of byTxid) {
28
+ const beef = await ctx.services.getBeefForTxid(txid);
29
+ const beefTx = beef.findTxid(txid);
30
+ if (!beefTx?.tx) {
31
+ throw new Error(`Transaction ${txid} not found in BEEF`);
32
+ }
33
+ for (const { vout, utxo } of outputs) {
34
+ const output = beefTx.tx.outputs[vout];
35
+ if (!output) {
36
+ throw new Error(`Output ${vout} not found in transaction ${txid}`);
37
+ }
38
+ results.push({
39
+ outpoint: utxo.outpoint,
40
+ satoshis: utxo.satoshis ?? output.satoshis ?? 0,
41
+ lockingScript: output.lockingScript?.toHex() ?? "",
42
+ });
43
+ }
44
+ }
45
+ return results;
46
+ }
47
+ /**
48
+ * Sweep BSV from external inputs into the destination wallet.
49
+ *
50
+ * If amount is specified, only that amount is swept and the remainder
51
+ * is returned to the source address. If amount is omitted, all input
52
+ * value is swept (minus fees).
53
+ */
54
+ export const sweepBsv = {
55
+ meta: {
56
+ name: "sweepBsv",
57
+ description: "Sweep BSV from external wallet (via WIF) into the connected wallet",
58
+ category: "sweep",
59
+ requiresServices: true,
60
+ inputSchema: {
61
+ type: "object",
62
+ properties: {
63
+ inputs: {
64
+ type: "array",
65
+ description: "UTXOs to sweep (use prepareSweepInputs to build these)",
66
+ items: {
67
+ type: "object",
68
+ properties: {
69
+ outpoint: { type: "string", description: "Outpoint (txid_vout)" },
70
+ satoshis: { type: "integer", description: "Satoshis in output" },
71
+ lockingScript: {
72
+ type: "string",
73
+ description: "Locking script hex",
74
+ },
75
+ },
76
+ required: ["outpoint", "satoshis", "lockingScript"],
77
+ },
78
+ },
79
+ wif: {
80
+ type: "string",
81
+ description: "WIF private key controlling the inputs",
82
+ },
83
+ amount: {
84
+ type: "integer",
85
+ description: "Amount to sweep (satoshis). If omitted, sweeps all input value.",
86
+ },
87
+ },
88
+ required: ["inputs", "wif"],
89
+ },
90
+ },
91
+ async execute(ctx, request) {
92
+ if (!ctx.services) {
93
+ return { error: "services-required" };
94
+ }
95
+ try {
96
+ const { inputs, wif, amount } = request;
97
+ if (!inputs || inputs.length === 0) {
98
+ return { error: "no-inputs" };
99
+ }
100
+ // Parse WIF and derive source address
101
+ const privateKey = PrivateKey.fromWif(wif);
102
+ const sourceAddress = privateKey.toPublicKey().toAddress();
103
+ // Calculate totals
104
+ const inputTotal = inputs.reduce((sum, i) => sum + i.satoshis, 0);
105
+ // Validate amount if specified
106
+ if (amount !== undefined) {
107
+ if (amount <= 0) {
108
+ return { error: "invalid-amount" };
109
+ }
110
+ if (amount > inputTotal) {
111
+ return { error: "insufficient-funds" };
112
+ }
113
+ }
114
+ // Fetch BEEF for all input transactions and merge them
115
+ const txids = [
116
+ ...new Set(inputs.map((i) => i.outpoint.split("_")[0])),
117
+ ];
118
+ console.log(`[sweep] Fetching BEEF for ${txids.length} transactions`);
119
+ // Get first BEEF, then merge others into it
120
+ const firstBeef = await ctx.services.getBeefForTxid(txids[0]);
121
+ for (let i = 1; i < txids.length; i++) {
122
+ const additionalBeef = await ctx.services.getBeefForTxid(txids[i]);
123
+ firstBeef.mergeBeef(additionalBeef);
124
+ }
125
+ console.log(`[sweep] Merged BEEF valid=${firstBeef.isValid()}, txs=${firstBeef.txs.length}`);
126
+ // Build input descriptors (we'll sign after getting the final transaction)
127
+ const inputDescriptors = inputs.map((input) => {
128
+ const [txid, voutStr] = input.outpoint.split("_");
129
+ // Convert outpoint format: our format uses "_" but SDK expects "."
130
+ return {
131
+ outpoint: `${txid}.${voutStr}`,
132
+ inputDescription: "Sweep input",
133
+ unlockingScriptLength: 108, // P2PKH unlocking script length
134
+ sequenceNumber: 0xffffffff,
135
+ };
136
+ });
137
+ const beefData = firstBeef.toBinary();
138
+ // Build outputs array
139
+ const outputs = [];
140
+ // If amount specified, create return output for the difference
141
+ if (amount !== undefined) {
142
+ const returnAmount = inputTotal - amount;
143
+ if (returnAmount > 0) {
144
+ outputs.push({
145
+ lockingScript: new P2PKH().lock(sourceAddress).toHex(),
146
+ satoshis: returnAmount,
147
+ outputDescription: "Return to source",
148
+ });
149
+ }
150
+ }
151
+ // If no amount specified, no outputs - wallet creates change for everything
152
+ // Step 1: Create action to get the signable transaction
153
+ const createResult = await ctx.wallet.createAction({
154
+ description: amount
155
+ ? `Sweep ${amount} sats`
156
+ : `Sweep ${inputTotal} sats`,
157
+ inputBEEF: beefData,
158
+ inputs: inputDescriptors,
159
+ outputs,
160
+ options: { signAndProcess: false },
161
+ });
162
+ if ("error" in createResult && createResult.error) {
163
+ return { error: String(createResult.error) };
164
+ }
165
+ if (!createResult.signableTransaction) {
166
+ return { error: "no-signable-transaction" };
167
+ }
168
+ // Step 2: Sign each input with our external key
169
+ const tx = Transaction.fromBEEF(createResult.signableTransaction.tx);
170
+ console.log(`[sweep] Transaction has ${tx.inputs.length} inputs, ${tx.outputs.length} outputs`);
171
+ // Build a set of outpoints we control (using SDK format with ".")
172
+ const ourOutpoints = new Set(inputs.map((input) => {
173
+ const [txid, vout] = input.outpoint.split("_");
174
+ return `${txid}.${vout}`;
175
+ }));
176
+ // Find and set up P2PKH unlocker on each input we control
177
+ for (let i = 0; i < tx.inputs.length; i++) {
178
+ const txInput = tx.inputs[i];
179
+ const inputOutpoint = `${txInput.sourceTXID}.${txInput.sourceOutputIndex}`;
180
+ const hasSourceTx = !!txInput.sourceTransaction;
181
+ const sourceSatoshis = txInput.sourceTransaction?.outputs[txInput.sourceOutputIndex]?.satoshis;
182
+ console.log(`[sweep] Input ${i}: ${inputOutpoint}, hasSourceTx=${hasSourceTx}, satoshis=${sourceSatoshis}, isOurs=${ourOutpoints.has(inputOutpoint)}`);
183
+ if (ourOutpoints.has(inputOutpoint)) {
184
+ const p2pkh = new P2PKH();
185
+ txInput.unlockingScriptTemplate = p2pkh.unlock(privateKey, "all", // SIGHASH_ALL - commit to outputs (we know them now)
186
+ true);
187
+ }
188
+ }
189
+ // Sign all inputs
190
+ await tx.sign();
191
+ // Extract unlocking scripts for signAction (only for our inputs)
192
+ const spends = {};
193
+ for (let i = 0; i < tx.inputs.length; i++) {
194
+ const txInput = tx.inputs[i];
195
+ const inputOutpoint = `${txInput.sourceTXID}.${txInput.sourceOutputIndex}`;
196
+ if (ourOutpoints.has(inputOutpoint)) {
197
+ spends[i] = { unlockingScript: txInput.unlockingScript?.toHex() ?? "" };
198
+ }
199
+ }
200
+ // Step 3: Complete the action with our signatures
201
+ const signResult = await ctx.wallet.signAction({
202
+ reference: createResult.signableTransaction.reference,
203
+ spends,
204
+ });
205
+ if ("error" in signResult) {
206
+ return { error: String(signResult.error) };
207
+ }
208
+ return {
209
+ txid: signResult.txid,
210
+ beef: signResult.tx ? Array.from(signResult.tx) : undefined,
211
+ };
212
+ }
213
+ catch (error) {
214
+ return {
215
+ error: error instanceof Error ? error.message : "unknown-error",
216
+ };
217
+ }
218
+ },
219
+ };
220
+ // Export skills array for registry
221
+ export const sweepSkills = [sweepBsv];
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Sweep Module Types
3
+ */
4
+ /** Input for sweep operations - a UTXO to be swept */
5
+ export interface SweepInput {
6
+ /** Outpoint in format "txid_vout" */
7
+ outpoint: string;
8
+ /** Satoshis in this output */
9
+ satoshis: number;
10
+ /** Locking script hex */
11
+ lockingScript: string;
12
+ }
13
+ /** Request to sweep BSV funds */
14
+ export interface SweepBsvRequest {
15
+ /** UTXOs to spend from source wallet */
16
+ inputs: SweepInput[];
17
+ /** WIF private key controlling the inputs */
18
+ wif: string;
19
+ /** Amount to sweep (in satoshis). If omitted, sweeps all input value. */
20
+ amount?: number;
21
+ }
22
+ /** Response from sweep operation */
23
+ export interface SweepBsvResponse {
24
+ /** Transaction ID if successful */
25
+ txid?: string;
26
+ /** BEEF (transaction with validity proof) */
27
+ beef?: number[];
28
+ /** Error message if failed */
29
+ error?: string;
30
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Sweep Module Types
3
+ */
4
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./api";
2
2
  export { OneSatServices, type SyncOutput } from "./services/OneSatServices";
3
- export type { OrdfsMetadata, OrdfsContentOptions, OrdfsContentResponse, OrdfsResponseHeaders, Capability, } from "./services/types";
3
+ export type { IndexedOutput, OrdfsMetadata, OrdfsContentOptions, OrdfsContentResponse, OrdfsResponseHeaders, Capability, } from "./services/types";
4
4
  export * from "./services/client";
5
5
  export { ReadOnlySigner } from "./signers/ReadOnlySigner";
6
6
  export * from "./indexers";
@@ -22,6 +22,10 @@ export class OwnerClient extends BaseClient {
22
22
  limit: opts?.limit,
23
23
  rev: opts?.rev,
24
24
  unspent: opts?.unspent,
25
+ sats: opts?.sats,
26
+ spend: opts?.spend,
27
+ events: opts?.events,
28
+ block: opts?.block,
25
29
  refresh: opts?.refresh,
26
30
  });
27
31
  return this.request(`/${owner}/txos${qs}`);
@@ -83,6 +83,7 @@ export async function createWebWallet(config) {
83
83
  });
84
84
  // 6. Attempt remote storage connection if URL provided
85
85
  if (config.remoteStorageUrl) {
86
+ console.log(`[createWebWallet] Attempting remote storage connection to ${config.remoteStorageUrl}`);
86
87
  try {
87
88
  const remoteClient = new StorageClient(underlyingWallet, config.remoteStorageUrl);
88
89
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Remote storage connection timeout")), DEFAULT_REMOTE_STORAGE_TIMEOUT));
@@ -92,10 +93,43 @@ export async function createWebWallet(config) {
92
93
  remoteClient,
93
94
  ]);
94
95
  await storage.makeAvailable();
96
+ // Check for conflicting actives and resolve if needed
97
+ const storageAny = storage;
98
+ console.log("[createWebWallet] Storage state:", {
99
+ activeKey: storageAny._active?.settings?.storageIdentityKey,
100
+ backups: storageAny._backups?.map(b => b.settings?.storageIdentityKey),
101
+ conflictingActives: storageAny._conflictingActives?.map(c => c.settings?.storageIdentityKey),
102
+ });
103
+ // If there are conflicting actives, resolve by setting local as active (merges remote data)
104
+ if (storageAny._conflictingActives && storageAny._conflictingActives.length > 0) {
105
+ const localKey = storageAny._active?.settings?.storageIdentityKey;
106
+ if (localKey && storageAny.setActive) {
107
+ console.log("[createWebWallet] Resolving conflicts by merging into local storage...");
108
+ await storageAny.setActive(localKey, (msg) => {
109
+ console.log("[createWebWallet] Sync:", msg);
110
+ return msg;
111
+ });
112
+ console.log("[createWebWallet] Conflict resolution complete");
113
+ }
114
+ }
115
+ else if (storageAny._backups && storageAny._backups.length > 0 && storageAny.updateBackups) {
116
+ // No conflicts - push local state to remote backup (non-blocking to avoid IDB timeout)
117
+ console.log("[createWebWallet] Starting background backup to remote...");
118
+ storageAny.updateBackups(undefined, (msg) => {
119
+ console.log("[createWebWallet] Backup:", msg);
120
+ return msg;
121
+ }).then(() => {
122
+ console.log("[createWebWallet] Background backup complete");
123
+ }).catch((err) => {
124
+ console.log("[createWebWallet] Background backup failed:", err instanceof Error ? err.message : err);
125
+ });
126
+ }
95
127
  // Update wallet's storage reference
96
128
  underlyingWallet._storage = storage;
129
+ console.log("[createWebWallet] Remote storage connected successfully");
97
130
  }
98
- catch {
131
+ catch (err) {
132
+ console.log("[createWebWallet] Remote storage connection failed:", err instanceof Error ? err.message : err);
99
133
  // Graceful degradation - continue with local only
100
134
  }
101
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1sat/wallet-toolbox",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "BSV wallet library extending @bsv/wallet-toolbox with 1Sat Ordinals protocol support",
5
5
  "author": "1Sat Team",
6
6
  "license": "MIT",
@@ -37,12 +37,12 @@
37
37
  "tester": "bun run build && bun run tester:build && bun ./tester/server.ts"
38
38
  },
39
39
  "dependencies": {
40
- "@bopen-io/templates": "^1.1.4",
41
- "@bsv/sdk": "^1.9.31",
40
+ "@bopen-io/templates": "^1.1.6",
41
+ "@bsv/sdk": "^1.10.1",
42
42
  "buffer": "^6.0.3"
43
43
  },
44
44
  "peerDependencies": {
45
- "@bsv/wallet-toolbox-mobile": "npm:@bopen-io/wallet-toolbox-mobile@^1.7.19"
45
+ "@bsv/wallet-toolbox-mobile": "npm:@bopen-io/wallet-toolbox-mobile@^1.7.20-idb-fix.1"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@bsv/wallet-toolbox-mobile": {
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@biomejs/biome": "^1.9.4",
54
- "@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@^1.7.19",
55
- "@bsv/wallet-toolbox-mobile": "npm:@bopen-io/wallet-toolbox-mobile@^1.7.19",
54
+ "@bsv/wallet-toolbox": "npm:@bopen-io/wallet-toolbox@^1.7.20-idb-fix.1",
55
+ "@bsv/wallet-toolbox-mobile": "npm:@bopen-io/wallet-toolbox-mobile@^1.7.20-idb-fix.1",
56
56
  "@types/bun": "^1.3.4",
57
57
  "@types/chrome": "^0.1.32",
58
58
  "typescript": "^5.9.3"