@1sat/wallet-toolbox 0.0.74 → 0.0.76

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.
@@ -107,8 +107,4 @@ export const getExchangeRate = {
107
107
  // Module exports
108
108
  // ============================================================================
109
109
  /** All balance skills for registry */
110
- export const balanceSkills = [
111
- getBalance,
112
- getPaymentUtxos,
113
- getExchangeRate,
114
- ];
110
+ export const balanceSkills = [getBalance, getPaymentUtxos, getExchangeRate];
@@ -65,8 +65,9 @@ export const broadcast = {
65
65
  return { txid };
66
66
  }
67
67
  catch (error) {
68
+ console.error("[broadcast]", error);
68
69
  return {
69
- error: error instanceof Error ? error.message : "Unknown error",
70
+ error: error instanceof Error ? error.message : "unknown-error",
70
71
  };
71
72
  }
72
73
  },
@@ -101,6 +101,7 @@ export const inscribe = {
101
101
  };
102
102
  }
103
103
  catch (error) {
104
+ console.error("[inscribe]", error);
104
105
  return {
105
106
  error: error instanceof Error ? error.message : "unknown-error",
106
107
  };
@@ -143,6 +143,7 @@ export const lockBsv = {
143
143
  };
144
144
  }
145
145
  catch (error) {
146
+ console.error("[lockBsv]", error);
146
147
  return {
147
148
  error: error instanceof Error ? error.message : "unknown-error",
148
149
  };
@@ -228,6 +229,9 @@ export const unlockBsv = {
228
229
  for (let i = 0; i < maturedLocks.length; i++) {
229
230
  const lock = maturedLocks[i];
230
231
  const input = tx.inputs[i];
232
+ if (!lock.output.lockingScript || !input.sourceTXID) {
233
+ return { error: "missing-lock-data" };
234
+ }
231
235
  const lockingScript = Script.fromHex(lock.output.lockingScript);
232
236
  const preimage = TransactionSignature.format({
233
237
  sourceTXID: input.sourceTXID,
@@ -277,6 +281,7 @@ export const unlockBsv = {
277
281
  };
278
282
  }
279
283
  catch (error) {
284
+ console.error("[unlockBsv]", error);
280
285
  return {
281
286
  error: error instanceof Error ? error.message : "unknown-error",
282
287
  };
@@ -175,9 +175,12 @@ export async function buildTransferOrdinals(ctx, request) {
175
175
  });
176
176
  recipientAddress = PublicKey.fromString(publicKey).toAddress();
177
177
  }
178
- else {
178
+ else if (address) {
179
179
  recipientAddress = address;
180
180
  }
181
+ else {
182
+ return { error: "must-provide-counterparty-or-address" };
183
+ }
181
184
  // Preserve important tags from source output
182
185
  const tags = [];
183
186
  for (const tag of ordinal.tags ?? []) {
@@ -188,7 +191,7 @@ export async function buildTransferOrdinals(ctx, request) {
188
191
  }
189
192
  }
190
193
  const sourceName = extractName(ordinal.customInstructions);
191
- inputs.push({
194
+ inputs?.push({
192
195
  outpoint,
193
196
  inputDescription: "Ordinal to transfer",
194
197
  unlockingScriptLength: 108,
@@ -196,7 +199,7 @@ export async function buildTransferOrdinals(ctx, request) {
196
199
  // Only track output in wallet when transferring to a counterparty (wallet can derive keys to spend it)
197
200
  // External address transfers are NOT tracked since the wallet cannot spend them
198
201
  if (counterparty) {
199
- outputs.push({
202
+ outputs?.push({
200
203
  lockingScript: new P2PKH().lock(recipientAddress).toHex(),
201
204
  satoshis: 1,
202
205
  outputDescription: "Ordinal transfer",
@@ -211,7 +214,7 @@ export async function buildTransferOrdinals(ctx, request) {
211
214
  }
212
215
  else {
213
216
  // External address - output is not tracked in wallet
214
- outputs.push({
217
+ outputs?.push({
215
218
  lockingScript: new P2PKH().lock(recipientAddress).toHex(),
216
219
  satoshis: 1,
217
220
  outputDescription: "Ordinal transfer to external address",
@@ -396,7 +399,7 @@ export const transferOrdinals = {
396
399
  inputs: params.inputs,
397
400
  outputs: params.outputs?.map((o) => ({
398
401
  ...o,
399
- lockingScript: o.lockingScript?.slice(0, 20) + "...",
402
+ lockingScript: `${o.lockingScript?.slice(0, 20)}...`,
400
403
  })),
401
404
  }, null, 2));
402
405
  // Debug: Check if BEEF contains the source transactions
@@ -451,6 +454,7 @@ export const transferOrdinals = {
451
454
  };
452
455
  }
453
456
  catch (error) {
457
+ console.error("[transferOrdinals]", error);
454
458
  return {
455
459
  error: error instanceof Error ? error.message : "unknown-error",
456
460
  };
@@ -520,6 +524,7 @@ export const listOrdinal = {
520
524
  };
521
525
  }
522
526
  catch (error) {
527
+ console.error("[listOrdinal]", error);
523
528
  return {
524
529
  error: error instanceof Error ? error.message : "unknown-error",
525
530
  };
@@ -658,6 +663,7 @@ export const cancelListing = {
658
663
  };
659
664
  }
660
665
  catch (error) {
666
+ console.error("[cancelListing]", error);
661
667
  return {
662
668
  error: error instanceof Error ? error.message : "unknown-error",
663
669
  };
@@ -824,6 +830,7 @@ export const purchaseOrdinal = {
824
830
  };
825
831
  }
826
832
  catch (error) {
833
+ console.error("[purchaseOrdinal]", error);
827
834
  return {
828
835
  error: error instanceof Error ? error.message : "unknown-error",
829
836
  };
@@ -126,6 +126,7 @@ export const sendBsv = {
126
126
  };
127
127
  }
128
128
  catch (error) {
129
+ console.error("[sendBsv]", error);
129
130
  return {
130
131
  error: error instanceof Error ? error.message : "unknown-error",
131
132
  };
@@ -178,6 +179,7 @@ export const sendAllBsv = {
178
179
  };
179
180
  }
180
181
  catch (error) {
182
+ console.error("[sendAllBsv]", error);
181
183
  return {
182
184
  error: error instanceof Error ? error.message : "unknown-error",
183
185
  };
@@ -67,6 +67,7 @@ export const signMessage = {
67
67
  };
68
68
  }
69
69
  catch (error) {
70
+ console.error("[signMessage]", error);
70
71
  return {
71
72
  error: error instanceof Error ? error.message : "unknown-error",
72
73
  };
@@ -5,8 +5,7 @@
5
5
  */
6
6
  import { BSV21 } from "@bopen-io/templates";
7
7
  import { P2PKH, PrivateKey, PublicKey, Transaction, Utils, } from "@bsv/sdk";
8
- import { deriveFundAddress } from "../../indexers";
9
- import { BSV21_BASKET, BSV21_FEE_SATS, BSV21_PROTOCOL, ONESAT_PROTOCOL, } from "../constants";
8
+ import { BSV21_BASKET, BSV21_PROTOCOL, ONESAT_PROTOCOL } from "../constants";
10
9
  export * from "./types";
11
10
  /**
12
11
  * Prepare sweep inputs from IndexedOutput objects by fetching locking scripts.
@@ -217,10 +216,11 @@ export const sweepBsv = {
217
216
  };
218
217
  }
219
218
  catch (error) {
219
+ console.error("[sweepBsv]", error);
220
220
  // Log detailed error info for WERR_REVIEW_ACTIONS
221
221
  if (error && typeof error === "object" && "sendWithResults" in error) {
222
222
  const werr = error;
223
- console.error("[sweep] WERR_REVIEW_ACTIONS details:", {
223
+ console.error("[sweepBsv] WERR_REVIEW_ACTIONS details:", {
224
224
  message: werr.message,
225
225
  txid: werr.txid,
226
226
  sendWithResults: JSON.stringify(werr.sendWithResults, null, 2),
@@ -359,19 +359,19 @@ export const sweepOrdinals = {
359
359
  outputs,
360
360
  options: { signAndProcess: false, randomizeOutputs: false },
361
361
  };
362
- console.log(`[sweepOrdinals] === CREATE ACTION ARGS ===`);
362
+ console.log("[sweepOrdinals] === CREATE ACTION ARGS ===");
363
363
  console.log(`[sweepOrdinals] description: ${createActionArgs.description}`);
364
364
  console.log(`[sweepOrdinals] inputBEEF length: ${beefData.length} bytes`);
365
365
  console.log(`[sweepOrdinals] inputs count: ${inputDescriptors.length}`);
366
366
  console.log(`[sweepOrdinals] outputs count: ${outputs.length}`);
367
- console.log(`[sweepOrdinals] inputs:`, JSON.stringify(inputDescriptors, null, 2));
368
- console.log(`[sweepOrdinals] outputs:`, JSON.stringify(outputs, null, 2));
369
- console.log(`[sweepOrdinals] options:`, JSON.stringify(createActionArgs.options));
370
- console.log(`[sweepOrdinals] Calling createAction...`);
367
+ console.log("[sweepOrdinals] inputs:", JSON.stringify(inputDescriptors, null, 2));
368
+ console.log("[sweepOrdinals] outputs:", JSON.stringify(outputs, null, 2));
369
+ console.log("[sweepOrdinals] options:", JSON.stringify(createActionArgs.options));
370
+ console.log("[sweepOrdinals] Calling createAction...");
371
371
  let createResult;
372
372
  try {
373
373
  createResult = await ctx.wallet.createAction(createActionArgs);
374
- console.log(`[sweepOrdinals] createAction returned:`, JSON.stringify(createResult, (key, value) => {
374
+ console.log("[sweepOrdinals] createAction returned:", JSON.stringify(createResult, (key, value) => {
375
375
  // Don't stringify large binary data
376
376
  if (key === "tx" && value instanceof Uint8Array)
377
377
  return `<Uint8Array ${value.length} bytes>`;
@@ -381,12 +381,12 @@ export const sweepOrdinals = {
381
381
  }, 2));
382
382
  }
383
383
  catch (createError) {
384
- console.error(`[sweepOrdinals] createAction threw:`, createError);
384
+ console.error("[sweepOrdinals] createAction threw:", createError);
385
385
  const errorMsg = createError instanceof Error
386
386
  ? createError.message
387
387
  : String(createError);
388
388
  const errorStack = createError instanceof Error ? createError.stack : undefined;
389
- console.error(`[sweepOrdinals] Stack:`, errorStack);
389
+ console.error("[sweepOrdinals] Stack:", errorStack);
390
390
  return { error: `createAction failed: ${errorMsg}` };
391
391
  }
392
392
  if ("error" in createResult && createResult.error) {
@@ -398,7 +398,7 @@ export const sweepOrdinals = {
398
398
  // Sign each input with our external key
399
399
  const tx = Transaction.fromBEEF(createResult.signableTransaction.tx);
400
400
  // Log transaction structure for debugging
401
- console.log(`[sweepOrdinals] === Transaction Structure ===`);
401
+ console.log("[sweepOrdinals] === Transaction Structure ===");
402
402
  console.log(`[sweepOrdinals] Inputs (${tx.inputs.length}):`);
403
403
  let totalInputSats = 0;
404
404
  for (let i = 0; i < tx.inputs.length; i++) {
@@ -417,7 +417,7 @@ export const sweepOrdinals = {
417
417
  }
418
418
  console.log(`[sweepOrdinals] Total in: ${totalInputSats}, Total out: ${totalOutputSats}, Fee: ${totalInputSats - totalOutputSats}`);
419
419
  console.log(`[sweepOrdinals] Signable tx hex: ${Utils.toHex(createResult.signableTransaction.tx)}`);
420
- console.log(`[sweepOrdinals] ==============================`);
420
+ console.log("[sweepOrdinals] ==============================");
421
421
  // Build a set of outpoints we control
422
422
  const ourOutpoints = new Set(inputs.map((input) => {
423
423
  const [txid, vout] = input.outpoint.split("_");
@@ -435,12 +435,12 @@ export const sweepOrdinals = {
435
435
  await tx.sign();
436
436
  // Log signed transaction details for debugging
437
437
  const localTxid = tx.id("hex");
438
- console.log(`[sweepOrdinals] === LOCAL SIGNED TX ===`);
438
+ console.log("[sweepOrdinals] === LOCAL SIGNED TX ===");
439
439
  console.log(`[sweepOrdinals] Local txid: ${localTxid}`);
440
440
  console.log(`[sweepOrdinals] Signed tx hex: ${tx.toHex()}`);
441
441
  // Extract unlocking scripts for signAction
442
442
  const spends = {};
443
- console.log(`[sweepOrdinals] === UNLOCKING SCRIPTS FOR SIGNACTION ===`);
443
+ console.log("[sweepOrdinals] === UNLOCKING SCRIPTS FOR SIGNACTION ===");
444
444
  for (let i = 0; i < tx.inputs.length; i++) {
445
445
  const txInput = tx.inputs[i];
446
446
  const inputOutpoint = `${txInput.sourceTXID}.${txInput.sourceOutputIndex}`;
@@ -463,18 +463,18 @@ export const sweepOrdinals = {
463
463
  return { error: String(signResult.error) };
464
464
  }
465
465
  // Debug: compare local vs signAction result
466
- console.log(`[sweepOrdinals] === SIGN ACTION RESULT ===`);
466
+ console.log("[sweepOrdinals] === SIGN ACTION RESULT ===");
467
467
  console.log(`[sweepOrdinals] signAction txid: ${signResult.txid}`);
468
468
  // Log broadcast results if available
469
469
  if ("sendWithResults" in signResult) {
470
- console.log(`[sweepOrdinals] sendWithResults:`, JSON.stringify(signResult.sendWithResults));
470
+ console.log("[sweepOrdinals] sendWithResults:", JSON.stringify(signResult.sendWithResults));
471
471
  }
472
472
  console.log(`[sweepOrdinals] Local txid (partial): ${localTxid}`);
473
- console.log(`[sweepOrdinals] Note: TXIDs differ because local is partial (wallet input unsigned)`);
473
+ console.log("[sweepOrdinals] Note: TXIDs differ because local is partial (wallet input unsigned)");
474
474
  if (signResult.tx) {
475
475
  // Parse returned BEEF to show final transaction structure
476
476
  const returnedTx = Transaction.fromBEEF(signResult.tx);
477
- console.log(`[sweepOrdinals] === FINAL TX STRUCTURE (broadcast) ===`);
477
+ console.log("[sweepOrdinals] === FINAL TX STRUCTURE (broadcast) ===");
478
478
  console.log(`[sweepOrdinals] Final inputs (${returnedTx.inputs.length}):`);
479
479
  let returnedInputSats = 0;
480
480
  for (let i = 0; i < returnedTx.inputs.length; i++) {
@@ -501,7 +501,7 @@ export const sweepOrdinals = {
501
501
  const satPerByte = finalFee / txSize;
502
502
  console.log(`[sweepOrdinals] Tx size: ${txSize} bytes, Fee rate: ${satPerByte.toFixed(2)} sat/byte`);
503
503
  if (satPerByte < 0.5) {
504
- console.warn(`[sweepOrdinals] WARNING: Fee rate seems very low!`);
504
+ console.warn("[sweepOrdinals] WARNING: Fee rate seems very low!");
505
505
  }
506
506
  }
507
507
  return {
@@ -510,6 +510,7 @@ export const sweepOrdinals = {
510
510
  };
511
511
  }
512
512
  catch (error) {
513
+ console.error("[sweepOrdinals]", error);
513
514
  return {
514
515
  error: error instanceof Error ? error.message : "unknown-error",
515
516
  };
@@ -583,6 +584,22 @@ export const sweepBsv21 = {
583
584
  if (!inputs.every((i) => i.tokenId === tokenId)) {
584
585
  return { error: "mixed-token-ids" };
585
586
  }
587
+ // Lookup token details to verify it's active and get fee info
588
+ const tokenDetails = await ctx.services.bsv21.getTokenDetails(tokenId);
589
+ if (!tokenDetails.status.is_active) {
590
+ return { error: "token-not-active" };
591
+ }
592
+ const { fee_address, fee_per_output } = tokenDetails.status;
593
+ // Validate all input outpoints exist in the overlay
594
+ const candidateOutpoints = inputs.map((i) => i.outpoint);
595
+ const validated = await ctx.services.bsv21.validateOutputs(tokenId, candidateOutpoints, { unspent: true });
596
+ const validSet = new Set(validated.map((v) => v.outpoint));
597
+ const invalidInputs = inputs.filter((i) => !validSet.has(i.outpoint));
598
+ if (invalidInputs.length > 0) {
599
+ return {
600
+ error: `unvalidated-inputs: ${invalidInputs.map((i) => i.outpoint).join(", ")}`,
601
+ };
602
+ }
586
603
  // Parse WIF
587
604
  const privateKey = PrivateKey.fromWif(wif);
588
605
  // Sum all input amounts
@@ -630,17 +647,24 @@ export const sweepBsv21 = {
630
647
  satoshis: 1,
631
648
  outputDescription: `Sweep ${totalAmount} tokens`,
632
649
  basket: BSV21_BASKET,
633
- tags: [`id:${tokenId}`, `amt:${totalAmount}`],
650
+ tags: [
651
+ `id:${tokenId}`,
652
+ `amt:${totalAmount}`,
653
+ `dec:${tokenDetails.token.dec}`,
654
+ ...(tokenDetails.token.sym ? [`sym:${tokenDetails.token.sym}`] : []),
655
+ ...(tokenDetails.token.icon
656
+ ? [`icon:${tokenDetails.token.icon}`]
657
+ : []),
658
+ ],
634
659
  customInstructions: JSON.stringify({
635
660
  protocolID: BSV21_PROTOCOL,
636
661
  keyID,
637
662
  }),
638
663
  });
639
- // 2. Fee output (1000 sats) to overlay fund address
640
- const fundAddress = deriveFundAddress(tokenId);
664
+ // 2. Fee output to overlay fund address
641
665
  outputs.push({
642
- lockingScript: p2pkh.lock(fundAddress).toHex(),
643
- satoshis: BSV21_FEE_SATS,
666
+ lockingScript: p2pkh.lock(fee_address).toHex(),
667
+ satoshis: fee_per_output,
644
668
  outputDescription: "Overlay processing fee",
645
669
  tags: [],
646
670
  });
@@ -698,13 +722,12 @@ export const sweepBsv21 = {
698
722
  // Submit to overlay service for indexing
699
723
  if (signResult.tx) {
700
724
  try {
701
- const services = ctx.services;
702
- const overlayResult = await services.overlay.submitBsv21(signResult.tx, tokenId);
703
- console.log(`[sweepBsv21] Overlay submission result:`, overlayResult);
725
+ const overlayResult = await ctx.services.overlay.submitBsv21(signResult.tx, tokenId);
726
+ console.log("[sweepBsv21] Overlay submission result:", overlayResult);
704
727
  }
705
728
  catch (overlayError) {
706
729
  // Log but don't fail the sweep - tx is already broadcast
707
- console.warn(`[sweepBsv21] Overlay submission failed:`, overlayError);
730
+ console.warn("[sweepBsv21] Overlay submission failed:", overlayError);
708
731
  }
709
732
  }
710
733
  return {
@@ -713,6 +736,7 @@ export const sweepBsv21 = {
713
736
  };
714
737
  }
715
738
  catch (error) {
739
+ console.error("[sweepBsv21]", error);
716
740
  return {
717
741
  error: error instanceof Error ? error.message : "unknown-error",
718
742
  };
@@ -5,8 +5,7 @@
5
5
  */
6
6
  import { BSV21, OrdLock } from "@bopen-io/templates";
7
7
  import { BigNumber, LockingScript, OP, P2PKH, PublicKey, Transaction, TransactionSignature, UnlockingScript, Utils, } from "@bsv/sdk";
8
- import { deriveFundAddress } from "../../indexers";
9
- import { BSV21_BASKET, BSV21_FEE_SATS, BSV21_PROTOCOL } from "../constants";
8
+ import { BSV21_BASKET, BSV21_PROTOCOL } from "../constants";
10
9
  // ============================================================================
11
10
  // Internal helpers
12
11
  // ============================================================================
@@ -105,31 +104,19 @@ export const getBsv21Balances = {
105
104
  const amtTag = o.tags?.find((t) => t.startsWith("amt:"))?.slice(4);
106
105
  if (!idTag || !amtTag)
107
106
  continue;
108
- const idContent = idTag.slice(3);
109
- const lastColonIdx = idContent.lastIndexOf(":");
110
- if (lastColonIdx === -1)
111
- continue;
112
- const tokenId = idContent.slice(0, lastColonIdx);
113
- const status = idContent.slice(lastColonIdx + 1);
114
- if (status === "invalid")
115
- continue;
116
- const isConfirmed = status === "valid";
107
+ const tokenId = idTag.slice(3);
117
108
  const amt = BigInt(amtTag);
118
109
  const dec = Number.parseInt(o.tags?.find((t) => t.startsWith("dec:"))?.slice(4) || "0", 10);
119
110
  const symTag = o.tags?.find((t) => t.startsWith("sym:"))?.slice(4);
120
111
  const iconTag = o.tags?.find((t) => t.startsWith("icon:"))?.slice(5);
121
112
  const existing = balanceMap.get(tokenId);
122
113
  if (existing) {
123
- if (isConfirmed)
124
- existing.confirmed += amt;
125
- else
126
- existing.pending += amt;
114
+ existing.amt += amt;
127
115
  }
128
116
  else {
129
117
  balanceMap.set(tokenId, {
130
118
  id: tokenId,
131
- confirmed: isConfirmed ? amt : 0n,
132
- pending: isConfirmed ? 0n : amt,
119
+ amt,
133
120
  sym: symTag,
134
121
  icon: iconTag,
135
122
  dec,
@@ -140,11 +127,11 @@ export const getBsv21Balances = {
140
127
  p: "bsv-20",
141
128
  op: "transfer",
142
129
  dec: b.dec,
143
- amt: (b.confirmed + b.pending).toString(),
130
+ amt: b.amt.toString(),
144
131
  id: b.id,
145
132
  sym: b.sym,
146
133
  icon: b.icon,
147
- all: { confirmed: b.confirmed, pending: b.pending },
134
+ all: { confirmed: b.amt, pending: 0n },
148
135
  listed: { confirmed: 0n, pending: 0n },
149
136
  }));
150
137
  },
@@ -191,6 +178,14 @@ export const sendBsv21 = {
191
178
  !/^\d+$/.test(parts[1])) {
192
179
  return { error: "invalid-token-id-format" };
193
180
  }
181
+ if (!ctx.services) {
182
+ return { error: "services-required" };
183
+ }
184
+ const tokenDetails = await ctx.services.bsv21.getTokenDetails(tokenId);
185
+ if (!tokenDetails.status.is_active) {
186
+ return { error: "token-not-active" };
187
+ }
188
+ const { fee_address, fee_per_output } = tokenDetails.status;
194
189
  const result = await ctx.wallet.listOutputs({
195
190
  basket: BSV21_BASKET,
196
191
  includeTags: true,
@@ -201,16 +196,24 @@ export const sendBsv21 = {
201
196
  const idTag = o.tags?.find((t) => t.startsWith("id:"));
202
197
  if (!idTag)
203
198
  return false;
204
- const idContent = idTag.slice(3);
205
- const lastColonIdx = idContent.lastIndexOf(":");
206
- if (lastColonIdx === -1)
207
- return false;
208
- const id = idContent.slice(0, lastColonIdx);
209
- const status = idContent.slice(lastColonIdx + 1);
210
- return id === tokenId && status !== "invalid";
199
+ return idTag.slice(3) === tokenId;
211
200
  });
212
- if (tokenUtxos.length === 0) {
213
- return { error: "no-token-utxos-found" };
201
+ // Batch-validate all candidate outpoints against the overlay
202
+ const validOutpoints = new Set();
203
+ let overlayValidated = false;
204
+ if (ctx.services?.bsv21) {
205
+ const candidateOutpoints = tokenUtxos.map((o) => o.outpoint);
206
+ try {
207
+ const validated = await ctx.services.bsv21.validateOutputs(tokenId, candidateOutpoints, { unspent: true });
208
+ overlayValidated = true;
209
+ for (const v of validated) {
210
+ validOutpoints.add(v.outpoint);
211
+ }
212
+ }
213
+ catch (e) {
214
+ console.error("[sendBsv21] overlay validation error:", e);
215
+ return { error: "overlay-validation-failed" };
216
+ }
214
217
  }
215
218
  const selected = [];
216
219
  let totalIn = 0n;
@@ -221,23 +224,15 @@ export const sendBsv21 = {
221
224
  if (!amtTag)
222
225
  continue;
223
226
  const utxoAmount = BigInt(amtTag.slice(4));
224
- if (ctx.services?.bsv21) {
225
- try {
226
- const [txid] = utxo.outpoint.split("_");
227
- const validation = await ctx.services.bsv21.getTokenByTxid(tokenId, txid);
228
- const outputData = validation.outputs.find((o) => `${validation.txid}_${o.vout}` === utxo.outpoint);
229
- if (!outputData)
230
- continue;
231
- }
232
- catch {
233
- continue;
234
- }
227
+ // Skip UTXOs not confirmed in the overlay
228
+ if (overlayValidated && !validOutpoints.has(utxo.outpoint)) {
229
+ continue;
235
230
  }
236
231
  selected.push(utxo);
237
232
  totalIn += utxoAmount;
238
233
  }
239
234
  if (totalIn < amount) {
240
- return { error: "insufficient-validated-tokens" };
235
+ return { error: "insufficient-tokens" };
241
236
  }
242
237
  let recipientAddress;
243
238
  if (counterparty) {
@@ -252,9 +247,12 @@ export const sendBsv21 = {
252
247
  else if (paymail) {
253
248
  return { error: "paymail-not-yet-implemented" };
254
249
  }
255
- else {
250
+ else if (address) {
256
251
  recipientAddress = address;
257
252
  }
253
+ else {
254
+ return { error: "must-provide-counterparty-or-address" };
255
+ }
258
256
  const outputs = [];
259
257
  const p2pkh = new P2PKH();
260
258
  const destinationLockingScript = p2pkh.lock(recipientAddress);
@@ -264,16 +262,10 @@ export const sendBsv21 = {
264
262
  satoshis: 1,
265
263
  outputDescription: `Send ${amount} tokens`,
266
264
  });
267
- // Fee output to overlay fund address
268
- const fundAddress = deriveFundAddress(tokenId);
269
- outputs.push({
270
- lockingScript: p2pkh.lock(fundAddress).toHex(),
271
- satoshis: BSV21_FEE_SATS,
272
- outputDescription: "Overlay processing fee",
273
- tags: [],
274
- });
275
265
  const change = totalIn - amount;
266
+ let tokenOutputCount = 1;
276
267
  if (change > 0n) {
268
+ tokenOutputCount = 2;
277
269
  const changeKeyID = `${tokenId}-${Date.now()}`;
278
270
  const { publicKey } = await ctx.wallet.getPublicKey({
279
271
  protocolID: BSV21_PROTOCOL,
@@ -289,15 +281,31 @@ export const sendBsv21 = {
289
281
  satoshis: 1,
290
282
  outputDescription: "Token change",
291
283
  basket: BSV21_BASKET,
292
- tags: [`id:${tokenId}`, `amt:${change}`],
284
+ tags: [
285
+ `id:${tokenId}`,
286
+ `amt:${change}`,
287
+ `dec:${tokenDetails.token.dec}`,
288
+ ...(tokenDetails.token.sym
289
+ ? [`sym:${tokenDetails.token.sym}`]
290
+ : []),
291
+ ...(tokenDetails.token.icon
292
+ ? [`icon:${tokenDetails.token.icon}`]
293
+ : []),
294
+ ],
293
295
  customInstructions: JSON.stringify({
294
296
  protocolID: BSV21_PROTOCOL,
295
297
  keyID: changeKeyID,
296
298
  }),
297
299
  });
298
300
  }
299
- const symTag = tokenUtxos[0]?.tags?.find((t) => t.startsWith("sym:"));
300
- const symbol = symTag ? symTag.slice(4) : tokenId.slice(0, 8);
301
+ // Fee output to overlay fund address (per token output)
302
+ outputs.push({
303
+ lockingScript: p2pkh.lock(fee_address).toHex(),
304
+ satoshis: fee_per_output * tokenOutputCount,
305
+ outputDescription: "Overlay processing fee",
306
+ tags: [],
307
+ });
308
+ const symbol = tokenDetails.token.sym || tokenId.slice(0, 8);
301
309
  const createResult = await ctx.wallet.createAction({
302
310
  description: `Send ${amount} ${symbol}`,
303
311
  inputs: selected.map((o) => ({
@@ -324,12 +332,11 @@ export const sendBsv21 = {
324
332
  // Submit to overlay service for indexing
325
333
  if (signResult.tx && ctx.services) {
326
334
  try {
327
- const services = ctx.services;
328
- const overlayResult = await services.overlay.submitBsv21(signResult.tx, tokenId);
329
- console.log(`[sendBsv21] Overlay submission result:`, overlayResult);
335
+ const overlayResult = await ctx.services.overlay.submitBsv21(signResult.tx, tokenId);
336
+ console.log("[sendBsv21] Overlay submission result:", overlayResult);
330
337
  }
331
338
  catch (overlayError) {
332
- console.warn(`[sendBsv21] Overlay submission failed:`, overlayError);
339
+ console.warn("[sendBsv21] Overlay submission failed:", overlayError);
333
340
  }
334
341
  }
335
342
  return {
@@ -338,6 +345,7 @@ export const sendBsv21 = {
338
345
  };
339
346
  }
340
347
  catch (error) {
348
+ console.error("[sendBsv21]", error);
341
349
  return {
342
350
  error: error instanceof Error ? error.message : "unknown-error",
343
351
  };
@@ -391,11 +399,13 @@ export const purchaseBsv21 = {
391
399
  const [txid, voutStr] = parts;
392
400
  const vout = Number.parseInt(voutStr, 10);
393
401
  try {
394
- await ctx.services.bsv21.getTokenByTxid(tokenId, txid);
402
+ await ctx.services.bsv21.validateOutput(tokenId, outpoint);
395
403
  }
396
- catch {
404
+ catch (e) {
405
+ console.error("[purchaseBsv21] overlay validation error:", e);
397
406
  return { error: "listing-not-found-in-overlay" };
398
407
  }
408
+ const tokenDetails = await ctx.services.bsv21.getTokenDetails(tokenId);
399
409
  const beef = await ctx.services.getBeefForTxid(txid);
400
410
  const listingBeefTx = beef.findTxid(txid);
401
411
  if (!listingBeefTx?.tx) {
@@ -426,7 +436,15 @@ export const purchaseBsv21 = {
426
436
  satoshis: 1,
427
437
  outputDescription: "Purchased tokens",
428
438
  basket: BSV21_BASKET,
429
- tags: [`id:${tokenId}`, `amt:${tokenAmount}`],
439
+ tags: [
440
+ `id:${tokenId}`,
441
+ `amt:${tokenAmount}`,
442
+ `dec:${tokenDetails.token.dec}`,
443
+ ...(tokenDetails.token.sym ? [`sym:${tokenDetails.token.sym}`] : []),
444
+ ...(tokenDetails.token.icon
445
+ ? [`icon:${tokenDetails.token.icon}`]
446
+ : []),
447
+ ],
430
448
  customInstructions: JSON.stringify({
431
449
  protocolID: BSV21_PROTOCOL,
432
450
  keyID: bsv21KeyID,
@@ -454,6 +472,15 @@ export const purchaseBsv21 = {
454
472
  });
455
473
  }
456
474
  }
475
+ // Fee output to overlay fund address
476
+ if (tokenDetails.status.is_active) {
477
+ outputs.push({
478
+ lockingScript: p2pkh.lock(tokenDetails.status.fee_address).toHex(),
479
+ satoshis: tokenDetails.status.fee_per_output,
480
+ outputDescription: "Overlay processing fee",
481
+ tags: [],
482
+ });
483
+ }
457
484
  const createResult = await ctx.wallet.createAction({
458
485
  description: `Purchase ${tokenAmount} tokens for ${payoutSatoshis} sats`,
459
486
  inputBEEF: beef.toBinary(),
@@ -488,12 +515,11 @@ export const purchaseBsv21 = {
488
515
  // Submit to overlay service for indexing
489
516
  if (signResult.tx && ctx.services) {
490
517
  try {
491
- const services = ctx.services;
492
- const overlayResult = await services.overlay.submitBsv21(signResult.tx, tokenId);
493
- console.log(`[purchaseBsv21] Overlay submission result:`, overlayResult);
518
+ const overlayResult = await ctx.services.overlay.submitBsv21(signResult.tx, tokenId);
519
+ console.log("[purchaseBsv21] Overlay submission result:", overlayResult);
494
520
  }
495
521
  catch (overlayError) {
496
- console.warn(`[purchaseBsv21] Overlay submission failed:`, overlayError);
522
+ console.warn("[purchaseBsv21] Overlay submission failed:", overlayError);
497
523
  }
498
524
  }
499
525
  return {
@@ -502,6 +528,7 @@ export const purchaseBsv21 = {
502
528
  };
503
529
  }
504
530
  catch (error) {
531
+ console.error("[purchaseBsv21]", error);
505
532
  return {
506
533
  error: error instanceof Error ? error.message : "unknown-error",
507
534
  };
@@ -77,8 +77,7 @@ export class Bsv21Indexer extends Indexer {
77
77
  return;
78
78
  const tags = [];
79
79
  if (txo.owner && this.owners.has(txo.owner)) {
80
- // Use id:tokenId:status format for querying by token and status
81
- tags.push(`id:${bsv21.id}:${bsv21.status}`);
80
+ tags.push(`id:${bsv21.id}`);
82
81
  tags.push(`amt:${bsv21.amt.toString()}`);
83
82
  // Add metadata tags for efficient querying
84
83
  if (bsv21.sym)
@@ -139,7 +139,7 @@ export class OneSatServices {
139
139
  }
140
140
  async postBeef(beef, txids) {
141
141
  console.log("[OneSatServices] postBeef called with txids:", txids);
142
- console.log("[OneSatServices] BEEF structure:\n" + beef.toLogString());
142
+ console.log(`[OneSatServices] BEEF structure:\n${beef.toLogString()}`);
143
143
  const results = [];
144
144
  for (const txid of txids) {
145
145
  try {
@@ -149,7 +149,7 @@ export class OneSatServices {
149
149
  console.log("[OneSatServices] AtomicBEEF length:", atomicBeef.length, "bytes");
150
150
  // Parse back to verify structure
151
151
  const verifyBeef = Beef.fromBinary(atomicBeef);
152
- console.log("[OneSatServices] AtomicBEEF parsed back:\n" + verifyBeef.toLogString());
152
+ console.log(`[OneSatServices] AtomicBEEF parsed back:\n${verifyBeef.toLogString()}`);
153
153
  // TODO: Remove hardcoded callback headers after server testing
154
154
  const status = await this.arcade.submitTransaction(atomicBeef, {
155
155
  callbackUrl: `${this.baseUrl}/1sat/arc/callback`,
@@ -292,10 +292,8 @@ export class OneSatServices {
292
292
  const depth = currentHeight - tx.merklePath.blockHeight + 1;
293
293
  return { txid, status: "mined", depth };
294
294
  }
295
- else {
296
- // No merkle path = known but not yet mined
297
- return { txid, status: "known", depth: 0 };
298
- }
295
+ // No merkle path = known but not yet mined
296
+ return { txid, status: "known", depth: 0 };
299
297
  }
300
298
  catch {
301
299
  // 404 or error from Beef = unknown
@@ -1,5 +1,23 @@
1
1
  import type { Bsv21TransactionData, ClientOptions, IndexedOutput, TokenDetailResponse } from "../types";
2
2
  import { BaseClient } from "./BaseClient";
3
+ /**
4
+ * Query options for /outputs validation endpoints.
5
+ * All flags default to false on the server.
6
+ */
7
+ export interface OutputQueryOptions {
8
+ /** Filter for unspent outputs only */
9
+ unspent?: boolean;
10
+ /** Include spend txid */
11
+ spend?: boolean;
12
+ /** Include satoshis */
13
+ sats?: boolean;
14
+ /** Include events array */
15
+ events?: boolean;
16
+ /** Include block info */
17
+ block?: boolean;
18
+ /** Comma-separated data tags to include (e.g. 'bsv21') */
19
+ tags?: string;
20
+ }
3
21
  /**
4
22
  * Client for /1sat/bsv21/* routes.
5
23
  * Provides BSV21 token queries.
@@ -66,6 +84,24 @@ export declare class Bsv21Client extends BaseClient {
66
84
  * @param addresses - Array of addresses (max 100)
67
85
  */
68
86
  getHistoryMulti(tokenId: string, lockType: string, addresses: string[]): Promise<IndexedOutput[]>;
87
+ /**
88
+ * Validate specific outpoints against the token's overlay topic.
89
+ * Returns only those found in the overlay. By default returns minimal data
90
+ * (outpoint + score). Use opts to include additional fields.
91
+ * @param tokenId - Token ID (txid_vout format)
92
+ * @param outpoints - Array of outpoints to validate (max 1000)
93
+ * @param opts - Optional query flags for additional data
94
+ */
95
+ validateOutputs(tokenId: string, outpoints: string[], opts?: OutputQueryOptions): Promise<IndexedOutput[]>;
96
+ /**
97
+ * Validate a single outpoint against the token's overlay topic.
98
+ * Returns 404 if not found. By default returns minimal data (outpoint + score).
99
+ * @param tokenId - Token ID (txid_vout format)
100
+ * @param outpoint - Outpoint to validate (txid_vout or txid:vout)
101
+ * @param opts - Optional query flags for additional data
102
+ */
103
+ validateOutput(tokenId: string, outpoint: string, opts?: OutputQueryOptions): Promise<IndexedOutput>;
104
+ private buildOutputQuery;
69
105
  /**
70
106
  * Clear the token details cache
71
107
  */
@@ -100,6 +100,51 @@ export class Bsv21Client extends BaseClient {
100
100
  body: JSON.stringify(addresses),
101
101
  });
102
102
  }
103
+ /**
104
+ * Validate specific outpoints against the token's overlay topic.
105
+ * Returns only those found in the overlay. By default returns minimal data
106
+ * (outpoint + score). Use opts to include additional fields.
107
+ * @param tokenId - Token ID (txid_vout format)
108
+ * @param outpoints - Array of outpoints to validate (max 1000)
109
+ * @param opts - Optional query flags for additional data
110
+ */
111
+ async validateOutputs(tokenId, outpoints, opts) {
112
+ const params = this.buildOutputQuery(opts);
113
+ return this.request(`/${tokenId}/outputs${params}`, {
114
+ method: "POST",
115
+ headers: { "Content-Type": "application/json" },
116
+ body: JSON.stringify(outpoints),
117
+ });
118
+ }
119
+ /**
120
+ * Validate a single outpoint against the token's overlay topic.
121
+ * Returns 404 if not found. By default returns minimal data (outpoint + score).
122
+ * @param tokenId - Token ID (txid_vout format)
123
+ * @param outpoint - Outpoint to validate (txid_vout or txid:vout)
124
+ * @param opts - Optional query flags for additional data
125
+ */
126
+ async validateOutput(tokenId, outpoint, opts) {
127
+ const params = this.buildOutputQuery(opts);
128
+ return this.request(`/${tokenId}/outputs/${outpoint}${params}`);
129
+ }
130
+ buildOutputQuery(opts) {
131
+ if (!opts)
132
+ return "";
133
+ const parts = [];
134
+ if (opts.unspent)
135
+ parts.push("unspent=true");
136
+ if (opts.spend)
137
+ parts.push("spend=true");
138
+ if (opts.sats)
139
+ parts.push("sats=true");
140
+ if (opts.events)
141
+ parts.push("events=true");
142
+ if (opts.block)
143
+ parts.push("block=true");
144
+ if (opts.tags)
145
+ parts.push(`tags=${encodeURIComponent(opts.tags)}`);
146
+ return parts.length > 0 ? `?${parts.join("&")}` : "";
147
+ }
103
148
  /**
104
149
  * Clear the token details cache
105
150
  */
@@ -5,5 +5,5 @@ export { ArcadeClient } from "./ArcadeClient";
5
5
  export { TxoClient } from "./TxoClient";
6
6
  export { OwnerClient } from "./OwnerClient";
7
7
  export { OrdfsClient } from "./OrdfsClient";
8
- export { Bsv21Client } from "./Bsv21Client";
8
+ export { Bsv21Client, type OutputQueryOptions } from "./Bsv21Client";
9
9
  export { OverlayClient } from "./OverlayClient";
@@ -81,13 +81,13 @@ export interface SyncProcessorOptions {
81
81
  indexers?: Indexer[];
82
82
  }
83
83
  export interface SyncProcessorEvents {
84
- "process:start": {};
84
+ "process:start": Record<string, never>;
85
85
  "process:progress": {
86
86
  pending: number;
87
87
  done: number;
88
88
  failed: number;
89
89
  };
90
- "process:complete": {};
90
+ "process:complete": Record<string, never>;
91
91
  "process:error": {
92
92
  message: string;
93
93
  };
@@ -161,7 +161,7 @@ export interface SyncEvents {
161
161
  done: number;
162
162
  failed: number;
163
163
  };
164
- "sync:complete": {};
164
+ "sync:complete": Record<string, never>;
165
165
  "sync:error": {
166
166
  message: string;
167
167
  };
@@ -44,7 +44,7 @@ export class SyncFetcher {
44
44
  }
45
45
  this.listeners
46
46
  .get(event)
47
- .add(listener);
47
+ ?.add(listener);
48
48
  }
49
49
  off(event, listener) {
50
50
  this.listeners
@@ -52,7 +52,9 @@ export class SyncFetcher {
52
52
  ?.delete(listener);
53
53
  }
54
54
  emit(event, data) {
55
- this.listeners.get(event)?.forEach((listener) => listener(data));
55
+ for (const listener of this.listeners.get(event) ?? []) {
56
+ listener(data);
57
+ }
56
58
  }
57
59
  /**
58
60
  * Fetch new outputs via SSE and enqueue them.
@@ -161,7 +163,7 @@ export class SyncProcessor {
161
163
  }
162
164
  this.listeners
163
165
  .get(event)
164
- .add(listener);
166
+ ?.add(listener);
165
167
  }
166
168
  off(event, listener) {
167
169
  this.listeners
@@ -169,7 +171,9 @@ export class SyncProcessor {
169
171
  ?.delete(listener);
170
172
  }
171
173
  emit(event, data) {
172
- this.listeners.get(event)?.forEach((listener) => listener(data));
174
+ for (const listener of this.listeners.get(event) ?? []) {
175
+ listener(data);
176
+ }
173
177
  }
174
178
  /**
175
179
  * Start processing the queue.
@@ -379,18 +383,16 @@ export class SyncProcessor {
379
383
  },
380
384
  };
381
385
  }
382
- else {
383
- // P2PKH-based output - use wallet payment for auto-signing
384
- return {
385
- outputIndex: vout,
386
- protocol: "wallet payment",
387
- paymentRemittance: {
388
- derivationPrefix: derivation.derivationPrefix,
389
- derivationSuffix: derivation.derivationSuffix,
390
- senderIdentityKey: derivation.senderIdentityKey,
391
- },
392
- };
393
- }
386
+ // P2PKH-based output - use wallet payment for auto-signing
387
+ return {
388
+ outputIndex: vout,
389
+ protocol: "wallet payment",
390
+ paymentRemittance: {
391
+ derivationPrefix: derivation.derivationPrefix,
392
+ derivationSuffix: derivation.derivationSuffix,
393
+ senderIdentityKey: derivation.senderIdentityKey,
394
+ },
395
+ };
394
396
  }
395
397
  collectTags(txo) {
396
398
  const tags = [];
@@ -467,7 +469,7 @@ export class SyncManager {
467
469
  }
468
470
  this.listeners
469
471
  .get(event)
470
- .add(listener);
472
+ ?.add(listener);
471
473
  }
472
474
  off(event, listener) {
473
475
  this.listeners
@@ -475,7 +477,9 @@ export class SyncManager {
475
477
  ?.delete(listener);
476
478
  }
477
479
  emit(event, data) {
478
- this.listeners.get(event)?.forEach((listener) => listener(data));
480
+ for (const listener of this.listeners.get(event) ?? []) {
481
+ listener(data);
482
+ }
479
483
  }
480
484
  // ===== Sync Control =====
481
485
  /**
@@ -5,7 +5,7 @@
5
5
  * (browser extension) and 1sat-website (React app).
6
6
  */
7
7
  import { PrivateKey } from "@bsv/sdk";
8
- import { Monitor, type PermissionsManagerConfig, StorageClient, Wallet, WalletPermissionsManager, WalletStorageManager } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
8
+ import { Monitor, StorageClient, Wallet, WalletStorageManager } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
9
9
  import { OneSatServices } from "../services/OneSatServices";
10
10
  import { type FullSyncResult, type FullSyncStage } from "./fullSync";
11
11
  type Chain = "main" | "test";
@@ -17,10 +17,6 @@ export interface WebWalletConfig {
17
17
  privateKey: PrivateKey | string;
18
18
  /** Network: 'main' or 'test' */
19
19
  chain: Chain;
20
- /** Admin originator that bypasses permission checks (e.g., chrome-extension://id or https://wallet.example.com) */
21
- adminOriginator: string;
22
- /** Permission configuration for WalletPermissionsManager */
23
- permissionsConfig: PermissionsManagerConfig;
24
20
  /** Fee model. Default: { model: 'sat/kb', value: 100 } */
25
21
  feeModel?: {
26
22
  model: "sat/kb";
@@ -39,10 +35,8 @@ export interface WebWalletConfig {
39
35
  * Result of wallet creation.
40
36
  */
41
37
  export interface WebWalletResult {
42
- /** Wallet instance with permission management */
43
- wallet: WalletPermissionsManager;
44
- /** Underlying wallet without permission checks (for trusted contexts like sweep-ui) */
45
- rawWallet: Wallet;
38
+ /** Wallet instance */
39
+ wallet: Wallet;
46
40
  /** 1Sat services for API access */
47
41
  services: OneSatServices;
48
42
  /** Monitor for transaction lifecycle (not started - call monitor.startTasks() when ready) */
@@ -57,15 +51,14 @@ export interface WebWalletResult {
57
51
  remoteStorage?: StorageClient;
58
52
  }
59
53
  /**
60
- * Create a web wallet with storage, services, permissions, and monitor.
54
+ * Create a web wallet with storage, services, and monitor.
61
55
  *
62
56
  * @example
63
57
  * ```typescript
64
58
  * const { wallet, services, monitor, destroy } = await createWebWallet({
65
59
  * privateKey: identityWif,
66
60
  * chain: 'main',
67
- * adminOriginator: 'https://wallet.example.com',
68
- * permissionsConfig: DEFAULT_PERMISSIONS_CONFIG,
61
+ * storageIdentityKey: 'device-unique-id',
69
62
  * });
70
63
  *
71
64
  * // Wire up monitor callbacks
@@ -5,7 +5,7 @@
5
5
  * (browser extension) and 1sat-website (React app).
6
6
  */
7
7
  import { KeyDeriver, PrivateKey } from "@bsv/sdk";
8
- import { Monitor, Services, StorageClient, StorageIdb, StorageProvider, Wallet, WalletPermissionsManager, WalletStorageManager, } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
8
+ import { Monitor, Services, StorageClient, StorageIdb, StorageProvider, Wallet, WalletStorageManager, } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
9
9
  import { OneSatServices } from "../services/OneSatServices";
10
10
  import { fullSync } from "./fullSync";
11
11
  // Default database name for IndexedDB storage
@@ -39,15 +39,14 @@ function parsePrivateKey(input) {
39
39
  }
40
40
  }
41
41
  /**
42
- * Create a web wallet with storage, services, permissions, and monitor.
42
+ * Create a web wallet with storage, services, and monitor.
43
43
  *
44
44
  * @example
45
45
  * ```typescript
46
46
  * const { wallet, services, monitor, destroy } = await createWebWallet({
47
47
  * privateKey: identityWif,
48
48
  * chain: 'main',
49
- * adminOriginator: 'https://wallet.example.com',
50
- * permissionsConfig: DEFAULT_PERMISSIONS_CONFIG,
49
+ * storageIdentityKey: 'device-unique-id',
51
50
  * });
52
51
  *
53
52
  * // Wire up monitor callbacks
@@ -58,7 +57,7 @@ function parsePrivateKey(input) {
58
57
  * ```
59
58
  */
60
59
  export async function createWebWallet(config) {
61
- const { chain, adminOriginator, permissionsConfig } = config;
60
+ const { chain } = config;
62
61
  const feeModel = config.feeModel ?? DEFAULT_FEE_MODEL;
63
62
  // 1. Parse private key and create KeyDeriver
64
63
  const privateKey = parsePrivateKey(config.privateKey);
@@ -73,10 +72,10 @@ export async function createWebWallet(config) {
73
72
  const localStorage = new StorageIdb(storageOptions);
74
73
  await localStorage.migrate(DEFAULT_DATABASE_NAME, config.storageIdentityKey);
75
74
  // 4. Create storage manager with local-only storage initially (empty backups)
76
- let storage = new WalletStorageManager(identityPubKey, localStorage, []);
75
+ const storage = new WalletStorageManager(identityPubKey, localStorage, []);
77
76
  await storage.makeAvailable();
78
- // 5. Create the underlying Wallet FIRST (needed for StorageClient signing)
79
- const underlyingWallet = new Wallet({
77
+ // 5. Create wallet (needed before StorageClient for signing)
78
+ const wallet = new Wallet({
80
79
  chain,
81
80
  keyDeriver,
82
81
  storage,
@@ -89,7 +88,7 @@ export async function createWebWallet(config) {
89
88
  if (config.remoteStorageUrl) {
90
89
  console.log(`[createWebWallet] Attempting remote storage connection to ${config.remoteStorageUrl}`);
91
90
  try {
92
- remoteClient = new StorageClient(underlyingWallet, config.remoteStorageUrl);
91
+ remoteClient = new StorageClient(wallet, config.remoteStorageUrl);
93
92
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Remote storage connection timeout")), DEFAULT_REMOTE_STORAGE_TIMEOUT));
94
93
  await Promise.race([remoteClient.makeAvailable(), timeoutPromise]);
95
94
  // Add remote to storage manager - it will partition as conflicting if another device is active
@@ -137,8 +136,8 @@ export async function createWebWallet(config) {
137
136
  // bypass the monitor's onTransactionBroadcasted callback. We detect broadcasts
138
137
  // by checking for txid in the result and sync to backup immediately.
139
138
  if (remoteClient) {
140
- const originalCreateAction = underlyingWallet.createAction.bind(underlyingWallet);
141
- underlyingWallet.createAction = async (args) => {
139
+ const originalCreateAction = wallet.createAction.bind(wallet);
140
+ wallet.createAction = async (args) => {
142
141
  const result = await originalCreateAction(args);
143
142
  if (result.txid) {
144
143
  console.log("[createWebWallet] Broadcast detected in createAction:", result.txid);
@@ -148,8 +147,8 @@ export async function createWebWallet(config) {
148
147
  }
149
148
  return result;
150
149
  };
151
- const originalSignAction = underlyingWallet.signAction.bind(underlyingWallet);
152
- underlyingWallet.signAction = async (args) => {
150
+ const originalSignAction = wallet.signAction.bind(wallet);
151
+ wallet.signAction = async (args) => {
153
152
  const result = await originalSignAction(args);
154
153
  if (result.txid) {
155
154
  console.log("[createWebWallet] Broadcast detected in signAction:", result.txid);
@@ -160,9 +159,7 @@ export async function createWebWallet(config) {
160
159
  return result;
161
160
  };
162
161
  }
163
- // 9. Wrap with permissions manager
164
- const wallet = new WalletPermissionsManager(underlyingWallet, adminOriginator, permissionsConfig);
165
- // 10. Create monitor (not started - consumer calls startTasks() when ready)
162
+ // 9. Create monitor (not started - consumer calls startTasks() when ready)
166
163
  const monitor = new Monitor({
167
164
  chain,
168
165
  services: oneSatServices,
@@ -175,7 +172,7 @@ export async function createWebWallet(config) {
175
172
  unprovenAttemptsLimitMain: 144,
176
173
  });
177
174
  monitor.addDefaultTasks();
178
- console.log("[createWebWallet] Monitor created with tasks:", monitor["_tasks"].map((t) => t.name));
175
+ console.log("[createWebWallet] Monitor created with tasks:", monitor._tasks.map((t) => t.name));
179
176
  // 11. Wire up monitor callbacks - sync to remote first, then call user callbacks
180
177
  // Note: For delayed broadcasts, the monitor triggers these. For immediate broadcasts,
181
178
  // the interception in step 8 handles the sync, but these still fire for the user callback.
@@ -228,7 +225,7 @@ export async function createWebWallet(config) {
228
225
  const destroy = async () => {
229
226
  monitor.stopTasks();
230
227
  await monitor.destroy();
231
- await underlyingWallet.destroy();
228
+ await wallet.destroy();
232
229
  };
233
230
  // 13. Create fullSync function if remote storage is connected
234
231
  const fullSyncFn = remoteClient
@@ -243,7 +240,6 @@ export async function createWebWallet(config) {
243
240
  : undefined;
244
241
  return {
245
242
  wallet,
246
- rawWallet: underlyingWallet,
247
243
  services: oneSatServices,
248
244
  monitor,
249
245
  destroy,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1sat/wallet-toolbox",
3
- "version": "0.0.74",
3
+ "version": "0.0.76",
4
4
  "description": "BSV wallet library extending @bsv/wallet-toolbox with 1Sat Ordinals protocol support",
5
5
  "author": "1Sat Team",
6
6
  "license": "MIT",