@1sat/sweep-ui 0.0.96 → 0.0.98

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/index.js CHANGED
@@ -277,26 +277,46 @@ async function legacyBurnOrdinals(params) {
277
277
  // src/lib/sweeper.ts
278
278
  import {
279
279
  SWEEP_BATCH_SIZE,
280
+ bsv20SweepBatches,
280
281
  bsv21SweepBatches,
281
282
  createContext,
282
283
  groupBsv20Tokens,
284
+ isListedOutput,
283
285
  prepareSweepInputs,
284
286
  sweepBsv,
285
287
  sweepBsv20,
286
288
  sweepBsv21,
287
289
  sweepOrdinals
288
290
  } from "@1sat/actions";
289
- function getOwner(output) {
290
- return output.events?.find((e) => e.startsWith("own:"))?.slice(4);
291
+ import { formatOutpoint, parseOutpoint as parseOutpoint2 } from "@1sat/utils";
292
+ function selectAllSweepClasses(assets) {
293
+ return {
294
+ sweepBsv: assets.funding.length > 0,
295
+ ordinalOutpoints: new Set(assets.ordinals.map((o) => o.outpoint)),
296
+ opnsOutpoints: new Set(assets.opnsNames.map((o) => o.outpoint)),
297
+ bsv20Ticks: new Set(groupBsv20Tokens(assets.bsv20Tokens).map((t) => t.tick)),
298
+ bsv21TokenIds: new Set(assets.bsv21Tokens.filter((t) => t.outputs.length > 0).map((t) => t.tokenId))
299
+ };
300
+ }
301
+ function isSweepAllDisabled(available, skipped) {
302
+ return available.every((id) => skipped.has(id));
303
+ }
304
+ function showClassSkips(available, skipped) {
305
+ return available.length > 1 || skipped.size > 0;
306
+ }
307
+ function normalizeOutpoint(outpoint) {
308
+ const { txid, vout } = parseOutpoint2(outpoint);
309
+ return formatOutpoint(txid, vout);
291
310
  }
292
311
  function buildKeys(outputs, keyMap, inputs = outputs) {
293
- const owners = new Map(outputs.map((output) => [output.outpoint, getOwner(output)]));
312
+ const byOutpoint = new Map(outputs.map((output) => [normalizeOutpoint(output.outpoint), output]));
294
313
  return inputs.map(({ outpoint }) => {
295
- const owner = owners.get(outpoint);
296
- const key = owner ? keyMap.get(owner) : undefined;
297
- if (!key)
298
- throw new Error(`No key for output ${outpoint} (owner: ${owner})`);
299
- return key;
314
+ const output = byOutpoint.get(normalizeOutpoint(outpoint));
315
+ const owners = new Set((output?.events ?? []).filter((e) => e.startsWith("own:")).map((e) => e.slice(4)));
316
+ const matches = [...owners].filter((owner) => keyMap.has(owner));
317
+ if (matches.length !== 1)
318
+ throw new Error(`No key for output ${outpoint} (owner: ${[...owners].join(",") || "unknown"})`);
319
+ return keyMap.get(matches[0]);
300
320
  });
301
321
  }
302
322
  function chunk(items, size) {
@@ -314,63 +334,147 @@ async function executeSweep(params) {
314
334
  bsv20Txids: [],
315
335
  bsv21Txids: [],
316
336
  errors: [],
317
- sweptOutpoints: []
337
+ sweptOutpoints: [],
338
+ cancelledListings: []
318
339
  };
319
- if (funding.length > 0) {
320
- onProgress(`Sweeping ${funding.length} BSV UTXOs...`);
340
+ const fundingStep = await sweepFundingStep({
341
+ ctx,
342
+ keys,
343
+ funding,
344
+ amount,
345
+ completed: params.completed,
346
+ signal: params.signal,
347
+ onProgress,
348
+ onResult: params.onResult
349
+ });
350
+ if (fundingStep.bsvTxid)
351
+ result.bsvTxid = fundingStep.bsvTxid;
352
+ if (fundingStep.error)
353
+ result.errors.push(fundingStep.error);
354
+ const ordinalBatches = await sweepOrdinalBatches({
355
+ ctx,
356
+ keys,
357
+ ordinals,
358
+ sweepClass: "ordinals",
359
+ completed: params.completed,
360
+ signal: params.signal,
361
+ onProgress,
362
+ onResult: params.onResult
363
+ });
364
+ result.ordinalTxids.push(...ordinalBatches.txids);
365
+ result.sweptOutpoints.push(...ordinalBatches.swept);
366
+ result.errors.push(...ordinalBatches.errors);
367
+ result.cancelledListings.push(...cancelledIn(funding, fundingStep.swept), ...cancelledIn(ordinals, ordinalBatches.swept));
368
+ onProgress(result.errors.length > 0 ? "Sweep stopped with errors" : "Sweep complete");
369
+ return result;
370
+ }
371
+ function isCompleted(completed, outpoint) {
372
+ if (!completed)
373
+ return false;
374
+ return completed.has(normalizeOutpoint(outpoint));
375
+ }
376
+ function cancelledIn(outputs, swept) {
377
+ const done = new Set(swept.map(normalizeOutpoint));
378
+ return outputs.filter((o) => done.has(normalizeOutpoint(o.outpoint)) && isListedOutput(o)).map((o) => o.outpoint);
379
+ }
380
+ function markCompleted(completed, outputs) {
381
+ if (!completed)
382
+ return;
383
+ for (const output of outputs)
384
+ completed.add(normalizeOutpoint(output.outpoint));
385
+ }
386
+ async function sweepFundingStep(options) {
387
+ const { ctx, keys, amount, completed, signal, onProgress, onResult } = options;
388
+ const pending = options.funding.filter((o) => !isCompleted(completed, o.outpoint));
389
+ if (pending.length === 0)
390
+ return { swept: [] };
391
+ signal?.throwIfAborted();
392
+ onProgress(`Sweeping ${pending.length} BSV UTXOs...`);
393
+ try {
394
+ const inputs = await prepareSweepInputs(ctx, pending);
395
+ const bsvResult = await sweepBsv.execute(ctx, {
396
+ inputs,
397
+ keys: buildKeys(pending, keys, inputs),
398
+ amount
399
+ });
400
+ if (bsvResult.error)
401
+ throw new Error(bsvResult.error);
402
+ const txid = bsvResult.txid?.trim();
403
+ if (!txid)
404
+ throw new Error("Sweep returned no transaction ID");
405
+ markCompleted(completed, pending);
406
+ onResult?.({ sweepClass: "bsv", label: "BSV", txid });
407
+ return { bsvTxid: txid, swept: pending.map((o) => o.outpoint) };
408
+ } catch (e) {
409
+ if (signal?.aborted)
410
+ throw e;
411
+ const error = e instanceof Error ? e.message : String(e);
412
+ onResult?.({ sweepClass: "bsv", label: "BSV", error });
413
+ return { error: `BSV: ${error}`, swept: [] };
414
+ }
415
+ }
416
+ var ORDINAL_CLASS_LABEL = {
417
+ ordinals: "Ordinals",
418
+ opns: "OpNS"
419
+ };
420
+ async function sweepOrdinalBatches(options) {
421
+ const { ctx, keys, sweepClass, completed, signal, onProgress, onResult } = options;
422
+ const label = ORDINAL_CLASS_LABEL[sweepClass];
423
+ const singular = sweepClass === "opns" ? "OpNS name" : "ordinal";
424
+ const txids = [];
425
+ const swept = [];
426
+ const errors = [];
427
+ const pending = options.ordinals.filter((o) => !isCompleted(completed, o.outpoint));
428
+ if (pending.length === 0)
429
+ return { txids, swept, errors };
430
+ const batches = chunk(pending, SWEEP_BATCH_SIZE);
431
+ for (let b = 0;b < batches.length; b++) {
432
+ signal?.throwIfAborted();
433
+ const batch = batches[b];
434
+ const from = b * SWEEP_BATCH_SIZE + 1;
435
+ const to = b * SWEEP_BATCH_SIZE + batch.length;
436
+ const batchLabel = batches.length === 1 ? label : `${label} (${from}–${to} of ${pending.length})`;
437
+ onProgress(batches.length === 1 ? `Sweeping ${batch.length} ${singular}${batch.length !== 1 ? "s" : ""}...` : `Sweeping ${singular}s ${from}–${to} of ${pending.length} (batch ${b + 1}/${batches.length})...`);
321
438
  try {
322
- const inputs = await prepareSweepInputs(ctx, funding);
323
- const bsvResult = await sweepBsv.execute(ctx, {
439
+ const inputs = await prepareSweepInputs(ctx, batch);
440
+ const ordResult = await sweepOrdinals.execute(ctx, {
324
441
  inputs,
325
- keys: buildKeys(funding, keys, inputs),
326
- amount
442
+ keys: buildKeys(batch, keys, inputs)
327
443
  });
328
- if (bsvResult.error)
329
- throw new Error(bsvResult.error);
330
- const txid = bsvResult.txid?.trim();
444
+ if (ordResult.error)
445
+ throw new Error(ordResult.error);
446
+ const txid = ordResult.txid?.trim();
331
447
  if (!txid)
332
448
  throw new Error("Sweep returned no transaction ID");
333
- result.bsvTxid = txid;
449
+ txids.push(txid);
450
+ swept.push(...batch.map((o) => o.outpoint));
451
+ markCompleted(completed, batch);
452
+ onResult?.({ sweepClass, label: batchLabel, txid });
334
453
  } catch (e) {
335
- result.errors.push(`BSV: ${e instanceof Error ? e.message : String(e)}`);
336
- }
337
- }
338
- if (ordinals.length > 0) {
339
- const batches = chunk(ordinals, SWEEP_BATCH_SIZE);
340
- for (let b = 0;b < batches.length; b++) {
341
- const batch = batches[b];
342
- const from = b * SWEEP_BATCH_SIZE + 1;
343
- const to = b * SWEEP_BATCH_SIZE + batch.length;
344
- onProgress(batches.length === 1 ? `Sweeping ${batch.length} ordinal${batch.length !== 1 ? "s" : ""}...` : `Sweeping ordinals ${from}–${to} of ${ordinals.length} (batch ${b + 1}/${batches.length})...`);
345
- try {
346
- const inputs = await prepareSweepInputs(ctx, batch);
347
- const ordResult = await sweepOrdinals.execute(ctx, {
348
- inputs,
349
- keys: buildKeys(batch, keys, inputs)
350
- });
351
- if (ordResult.error)
352
- throw new Error(ordResult.error);
353
- const txid = ordResult.txid?.trim();
354
- if (!txid)
355
- throw new Error("Sweep returned no transaction ID");
356
- result.ordinalTxids.push(txid);
357
- result.sweptOutpoints.push(...batch.map((o) => o.outpoint));
358
- } catch (e) {
359
- result.errors.push(`Ordinals batch ${b + 1}/${batches.length}: ${e instanceof Error ? e.message : String(e)}`);
360
- break;
361
- }
454
+ if (signal?.aborted)
455
+ throw e;
456
+ const error = e instanceof Error ? e.message : String(e);
457
+ errors.push(`${label} batch ${b + 1}/${batches.length}: ${error}`);
458
+ onResult?.({ sweepClass, label: batchLabel, error });
459
+ break;
362
460
  }
363
461
  }
364
- onProgress(result.errors.length > 0 ? "Sweep stopped with errors" : "Sweep complete");
365
- return result;
462
+ return { txids, swept, errors };
366
463
  }
367
464
  async function sweepBsv21Token(params) {
368
- const { wallet, keys, token, onProgress } = params;
465
+ const { wallet, keys, token, onProgress, signal, completed, onResult } = params;
369
466
  const ctx = createContext(wallet, { services: getServices(), chain: "main" });
370
467
  const txids = [];
468
+ const sweptOutpoints = [];
371
469
  const errors = [];
372
470
  const name = token.symbol ?? token.tokenId.slice(0, 8);
373
- for (const batch of bsv21SweepBatches(token.outputs)) {
471
+ const amounts = new Map([...token.amounts].map(([outpoint, amount]) => [
472
+ normalizeOutpoint(outpoint),
473
+ amount
474
+ ]));
475
+ const outputs = token.outputs.filter((o) => !isCompleted(completed, o.outpoint));
476
+ for (const batch of bsv21SweepBatches(outputs)) {
477
+ signal?.throwIfAborted();
374
478
  onProgress(`Sweeping ${name}...`);
375
479
  try {
376
480
  const sweepInputs = await prepareSweepInputs(ctx, batch);
@@ -382,7 +486,7 @@ async function sweepBsv21Token(params) {
382
486
  return {
383
487
  ...base,
384
488
  tokenId: token.tokenId,
385
- amount: token.amounts.get(out.outpoint) ?? "0"
489
+ amount: amounts.get(normalizeOutpoint(out.outpoint)) ?? "0"
386
490
  };
387
491
  });
388
492
  const result = await sweepBsv21.execute(ctx, {
@@ -391,103 +495,197 @@ async function sweepBsv21Token(params) {
391
495
  });
392
496
  if (result.error)
393
497
  errors.push(result.error);
394
- else if (result.txid)
498
+ else if (result.txid) {
395
499
  txids.push(result.txid);
500
+ sweptOutpoints.push(...batch.map((o) => o.outpoint));
501
+ markCompleted(completed, batch);
502
+ onResult?.({ sweepClass: "bsv21", label: name, txid: result.txid });
503
+ }
396
504
  } catch (e) {
505
+ if (signal?.aborted)
506
+ throw e;
397
507
  errors.push(e instanceof Error ? e.message : String(e));
398
508
  }
399
509
  }
400
510
  return {
401
511
  txid: txids.at(-1),
402
512
  txids,
513
+ sweptOutpoints,
514
+ cancelledListings: cancelledIn(outputs, sweptOutpoints),
403
515
  error: errors[0]
404
516
  };
405
517
  }
406
518
  async function sweepBsv20Token(params) {
407
- const { wallet, keys, token, onProgress } = params;
519
+ const {
520
+ wallet,
521
+ keys,
522
+ token,
523
+ onProgress,
524
+ signal,
525
+ splitListed,
526
+ completed,
527
+ onResult
528
+ } = params;
408
529
  const ctx = createContext(wallet, { services: getServices(), chain: "main" });
409
- onProgress(`Sweeping ${token.tick}...`);
410
- try {
411
- const sweepInputs = await prepareSweepInputs(ctx, token.outputs);
412
- const sweepInputMap = new Map(sweepInputs.map((s) => [s.outpoint, s]));
413
- const inputs = token.outputs.map((out) => {
414
- const base = sweepInputMap.get(out.outpoint);
415
- if (!base)
416
- throw new Error(`Missing sweep input for ${out.outpoint}`);
417
- return {
418
- ...base,
419
- tick: token.tick,
420
- amount: token.amounts.get(out.outpoint) ?? "0"
421
- };
422
- });
423
- const result = await sweepBsv20.execute(ctx, {
424
- inputs,
425
- keys: buildKeys(token.outputs, keys)
426
- });
427
- if (result.error)
428
- return { error: result.error };
429
- return { txid: result.txid };
430
- } catch (e) {
431
- return { error: e instanceof Error ? e.message : String(e) };
530
+ const txids = [];
531
+ const sweptOutpoints = [];
532
+ const errors = [];
533
+ const amounts = new Map([...token.amounts].map(([outpoint, amount]) => [
534
+ normalizeOutpoint(outpoint),
535
+ amount
536
+ ]));
537
+ const outputs = token.outputs.filter((o) => !isCompleted(completed, o.outpoint));
538
+ if (outputs.length === 0)
539
+ return { txids: [], sweptOutpoints: [], cancelledListings: [] };
540
+ const batches = splitListed ? bsv20SweepBatches(outputs) : [outputs];
541
+ for (const batch of batches) {
542
+ signal?.throwIfAborted();
543
+ onProgress(`Sweeping ${token.tick}...`);
544
+ try {
545
+ const sweepInputs = await prepareSweepInputs(ctx, batch);
546
+ const sweepInputMap = new Map(sweepInputs.map((s) => [s.outpoint, s]));
547
+ const inputs = batch.map((out) => {
548
+ const base = sweepInputMap.get(out.outpoint);
549
+ if (!base)
550
+ throw new Error(`Missing sweep input for ${out.outpoint}`);
551
+ return {
552
+ ...base,
553
+ tick: token.tick,
554
+ amount: amounts.get(normalizeOutpoint(out.outpoint)) ?? "0"
555
+ };
556
+ });
557
+ const result = await sweepBsv20.execute(ctx, {
558
+ inputs,
559
+ keys: buildKeys(batch, keys)
560
+ });
561
+ if (result.error)
562
+ errors.push(result.error);
563
+ else if (result.txid) {
564
+ txids.push(result.txid);
565
+ sweptOutpoints.push(...batch.map((o) => o.outpoint));
566
+ markCompleted(completed, batch);
567
+ onResult?.({
568
+ sweepClass: "bsv20",
569
+ label: token.tick,
570
+ txid: result.txid
571
+ });
572
+ }
573
+ } catch (e) {
574
+ if (signal?.aborted)
575
+ throw e;
576
+ errors.push(e instanceof Error ? e.message : String(e));
577
+ }
432
578
  }
579
+ return {
580
+ txid: txids.at(-1),
581
+ txids,
582
+ sweptOutpoints,
583
+ cancelledListings: cancelledIn(outputs, sweptOutpoints),
584
+ error: errors[0]
585
+ };
433
586
  }
434
587
  async function sweepAllClasses(params) {
435
588
  const { wallet, keys, assets, amount, onProgress } = params;
589
+ const selection = params.selection ?? selectAllSweepClasses(assets);
590
+ const completed = params.completed;
591
+ const signal = params.signal;
592
+ const onResult = params.onResult;
593
+ const ctx = createContext(wallet, { services: getServices(), chain: "main" });
436
594
  const result = {
437
595
  ordinalTxids: [],
438
596
  bsv20Txids: [],
439
597
  bsv21Txids: [],
440
598
  errors: [],
441
- sweptOutpoints: []
599
+ sweptOutpoints: [],
600
+ cancelledListings: []
442
601
  };
443
- const first = await executeSweep({
444
- wallet,
445
- keys,
446
- funding: assets.funding,
447
- ordinals: assets.ordinals,
448
- amount,
449
- onProgress
450
- });
451
- result.bsvTxid = first.bsvTxid;
452
- result.ordinalTxids.push(...first.ordinalTxids);
453
- result.sweptOutpoints.push(...first.sweptOutpoints);
454
- result.errors.push(...first.errors);
455
- if (assets.opnsNames.length > 0) {
456
- const opns = await executeSweep({
457
- wallet,
602
+ signal?.throwIfAborted();
603
+ if (selection.sweepBsv) {
604
+ const fundingStep = await sweepFundingStep({
605
+ ctx,
458
606
  keys,
459
- funding: [],
460
- ordinals: assets.opnsNames,
461
- onProgress
607
+ funding: assets.funding,
608
+ amount,
609
+ completed,
610
+ signal,
611
+ onProgress,
612
+ onResult
462
613
  });
463
- result.ordinalTxids.push(...opns.ordinalTxids);
464
- result.sweptOutpoints.push(...opns.sweptOutpoints);
465
- result.errors.push(...opns.errors);
614
+ if (fundingStep.bsvTxid)
615
+ result.bsvTxid = fundingStep.bsvTxid;
616
+ if (fundingStep.error)
617
+ result.errors.push(fundingStep.error);
618
+ result.cancelledListings.push(...cancelledIn(assets.funding, fundingStep.swept));
619
+ }
620
+ const inSelection = (outpoints) => (o) => [...outpoints].some((selected) => normalizeOutpoint(selected) === normalizeOutpoint(o.outpoint));
621
+ for (const [sweepClass, outputs] of [
622
+ [
623
+ "ordinals",
624
+ assets.ordinals.filter(inSelection(selection.ordinalOutpoints))
625
+ ],
626
+ ["opns", assets.opnsNames.filter(inSelection(selection.opnsOutpoints))]
627
+ ]) {
628
+ signal?.throwIfAborted();
629
+ if (outputs.length === 0)
630
+ continue;
631
+ const batches = await sweepOrdinalBatches({
632
+ ctx,
633
+ keys,
634
+ ordinals: outputs,
635
+ sweepClass,
636
+ completed,
637
+ signal,
638
+ onProgress,
639
+ onResult
640
+ });
641
+ result.ordinalTxids.push(...batches.txids);
642
+ result.sweptOutpoints.push(...batches.swept);
643
+ result.errors.push(...batches.errors);
644
+ result.cancelledListings.push(...cancelledIn(outputs, batches.swept));
466
645
  }
467
646
  for (const token of groupBsv20Tokens(assets.bsv20Tokens)) {
647
+ signal?.throwIfAborted();
648
+ if (!selection.bsv20Ticks.has(token.tick))
649
+ continue;
468
650
  const swept = await sweepBsv20Token({
469
651
  wallet,
470
652
  keys,
471
653
  token,
472
- onProgress
654
+ onProgress,
655
+ signal,
656
+ splitListed: params.splitListedBsv20,
657
+ completed,
658
+ onResult
473
659
  });
474
- if (swept.error)
660
+ result.bsv20Txids.push(...swept.txids);
661
+ result.cancelledListings.push(...swept.cancelledListings);
662
+ if (swept.error) {
475
663
  result.errors.push(`BSV-20 ${token.tick}: ${swept.error}`);
476
- else if (swept.txid)
477
- result.bsv20Txids.push(swept.txid);
664
+ onResult?.({ sweepClass: "bsv20", label: token.tick, error: swept.error });
665
+ }
478
666
  }
479
667
  for (const token of assets.bsv21Tokens) {
480
- if (token.outputs.length === 0)
668
+ signal?.throwIfAborted();
669
+ if (!selection.bsv21TokenIds.has(token.tokenId))
481
670
  continue;
482
671
  const swept = await sweepBsv21Token({
483
672
  wallet,
484
673
  keys,
485
674
  token,
486
- onProgress
675
+ onProgress,
676
+ signal,
677
+ completed,
678
+ onResult
487
679
  });
488
680
  result.bsv21Txids.push(...swept.txids);
681
+ result.cancelledListings.push(...swept.cancelledListings);
489
682
  if (swept.error) {
490
683
  result.errors.push(`BSV-21 ${token.symbol ?? token.tokenId.slice(0, 8)}: ${swept.error}`);
684
+ onResult?.({
685
+ sweepClass: "bsv21",
686
+ label: token.symbol ?? token.tokenId,
687
+ error: swept.error
688
+ });
491
689
  }
492
690
  }
493
691
  onProgress(result.errors.length > 0 ? "Sweep stopped with errors" : "Sweep complete");
@@ -524,7 +722,7 @@ function isConnected() {
524
722
  }
525
723
 
526
724
  // src/components/asset-preview.tsx
527
- import { isListedOutput } from "@1sat/actions";
725
+ import { isListedOutput as isListedOutput2 } from "@1sat/actions";
528
726
  import { useState } from "react";
529
727
 
530
728
  // src/lib/utils.ts
@@ -681,7 +879,7 @@ function OrdinalCard({
681
879
  /* @__PURE__ */ jsxs("div", {
682
880
  className: "min-h-[2rem] flex flex-col justify-end",
683
881
  children: [
684
- isListedOutput(ordinal) && /* @__PURE__ */ jsx4("div", {
882
+ isListedOutput2(ordinal) && /* @__PURE__ */ jsx4("div", {
685
883
  className: "mb-1",
686
884
  children: /* @__PURE__ */ jsx4("span", {
687
885
  className: "inline-block px-1 py-0.5 text-[9px] rounded bg-amber-500/20 text-amber-400",
@@ -1046,7 +1244,7 @@ function TokenRow({
1046
1244
  onSweep,
1047
1245
  walletConnected
1048
1246
  }) {
1049
- const listed = tb.outputs.filter(isListedOutput).length;
1247
+ const listed = tb.outputs.filter(isListedOutput2).length;
1050
1248
  return /* @__PURE__ */ jsxs("div", {
1051
1249
  className: `flex items-center justify-between p-3 rounded-lg border ${tb.isActive ? "bg-muted/30 border-purple-500/10" : "bg-muted/20 border-muted/20 opacity-60"}`,
1052
1250
  children: [
@@ -1199,7 +1397,7 @@ function Bsv20Section({
1199
1397
  /* @__PURE__ */ jsx4("div", {
1200
1398
  className: "space-y-3",
1201
1399
  children: tokens.map((tb) => {
1202
- const listed = tb.outputs.filter(isListedOutput).length;
1400
+ const listed = tb.outputs.filter(isListedOutput2).length;
1203
1401
  return /* @__PURE__ */ jsxs("div", {
1204
1402
  className: "flex items-center justify-between p-3 rounded-lg border bg-muted/30 border-teal-500/10",
1205
1403
  children: [
@@ -1394,7 +1592,7 @@ function ConnectWallet({
1394
1592
  }
1395
1593
 
1396
1594
  // src/components/opns-section.tsx
1397
- import { isListedOutput as isListedOutput2 } from "@1sat/actions";
1595
+ import { isListedOutput as isListedOutput3 } from "@1sat/actions";
1398
1596
  import { useEffect, useState as useState3 } from "react";
1399
1597
  import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1400
1598
  function OpnsSection({
@@ -1556,7 +1754,7 @@ function OpnsSection({
1556
1754
  className: "text-sm text-foreground truncate",
1557
1755
  children: displayName
1558
1756
  }),
1559
- isListedOutput2(item) && /* @__PURE__ */ jsx6("span", {
1757
+ isListedOutput3(item) && /* @__PURE__ */ jsx6("span", {
1560
1758
  className: "shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400",
1561
1759
  children: "listed"
1562
1760
  }),
@@ -1670,7 +1868,12 @@ function OpnsSection({
1670
1868
  import { ExternalLink, Loader2 as Loader22 } from "lucide-react";
1671
1869
  import { jsx as jsx7, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
1672
1870
  var EXPLORER_BASE = "https://bananablocks.com/tx/";
1673
- function TxHistory({ sweeping, progress, history }) {
1871
+ function TxHistory({
1872
+ sweeping,
1873
+ progress,
1874
+ history,
1875
+ cancelledListings = 0
1876
+ }) {
1674
1877
  return /* @__PURE__ */ jsxs4(Fragment, {
1675
1878
  children: [
1676
1879
  sweeping && /* @__PURE__ */ jsxs4("div", {
@@ -1697,7 +1900,17 @@ function TxHistory({ sweeping, progress, history }) {
1697
1900
  children: [
1698
1901
  "Transactions (",
1699
1902
  history.length,
1700
- ")"
1903
+ ")",
1904
+ cancelledListings > 0 && /* @__PURE__ */ jsxs4("span", {
1905
+ children: [
1906
+ " ",
1907
+ "· ",
1908
+ cancelledListings,
1909
+ " listing",
1910
+ cancelledListings !== 1 ? "s" : "",
1911
+ " canceled"
1912
+ ]
1913
+ })
1701
1914
  ]
1702
1915
  }),
1703
1916
  /* @__PURE__ */ jsx7("div", {
@@ -2368,10 +2581,12 @@ function SweepApp({
2368
2581
  const [sweeping, setSweeping] = useState5(false);
2369
2582
  const [sweepProgress, setSweepProgress] = useState5("");
2370
2583
  const [txHistory, setTxHistory] = useState5([]);
2584
+ const [cancelledListings, setCancelledListings] = useState5(0);
2371
2585
  const [selectedOrdinals, setSelectedOrdinals] = useState5(new Set);
2372
2586
  const [selectedOpns, setSelectedOpns] = useState5(new Set);
2373
2587
  const [sweepAmount, setSweepAmount] = useState5(null);
2374
2588
  const [activeTab, setActiveTab] = useState5("ordinals");
2589
+ const [skippedClasses, setSkippedClasses] = useState5(new Set);
2375
2590
  useEffect2(() => {
2376
2591
  setWalletConnected(!!externalWallet);
2377
2592
  }, [externalWallet]);
@@ -2395,6 +2610,10 @@ function SweepApp({
2395
2610
  { label, txid, timestamp: new Date, error }
2396
2611
  ]);
2397
2612
  }, []);
2613
+ const addCancelled = useCallback2((count) => {
2614
+ if (count > 0)
2615
+ setCancelledListings((prev) => prev + count);
2616
+ }, []);
2398
2617
  const groupedBsv20 = useMemo(() => assets ? groupBsv20Tokens2(assets.bsv20Tokens) : [], [assets]);
2399
2618
  const unparsedBsv20 = assets ? assets.bsv20Tokens.length - groupedBsv20.reduce((n, t) => n + t.outputs.length, 0) : 0;
2400
2619
  const tabs = useMemo(() => {
@@ -2476,6 +2695,7 @@ function SweepApp({
2476
2695
  setAssets(null);
2477
2696
  setSelectedOrdinals(new Set);
2478
2697
  setSelectedOpns(new Set);
2698
+ setSkippedClasses(new Set);
2479
2699
  setSweepAmount(null);
2480
2700
  setLegacyKeys(keys);
2481
2701
  try {
@@ -2560,6 +2780,7 @@ function SweepApp({
2560
2780
  });
2561
2781
  if (result.errors.length > 0)
2562
2782
  throw new Error(result.errors[0]);
2783
+ addCancelled(result.cancelledListings.length);
2563
2784
  return result.bsvTxid ?? "";
2564
2785
  });
2565
2786
  }, [
@@ -2569,7 +2790,8 @@ function SweepApp({
2569
2790
  sweepAmount,
2570
2791
  getSelectedFunding,
2571
2792
  runOperation,
2572
- keyMap
2793
+ keyMap,
2794
+ addCancelled
2573
2795
  ]);
2574
2796
  const handleSendBsv = useCallback2(async (destination) => {
2575
2797
  if (!legacyKeys || !assets)
@@ -2611,12 +2833,21 @@ function SweepApp({
2611
2833
  await refreshAssets();
2612
2834
  throw new Error(result.errors[0]);
2613
2835
  }
2836
+ addCancelled(result.cancelledListings.length);
2614
2837
  for (let i = 0;i < result.ordinalTxids.length - 1; i++) {
2615
2838
  addTx(label, result.ordinalTxids[i]);
2616
2839
  }
2617
2840
  return result.ordinalTxids[result.ordinalTxids.length - 1] ?? "";
2618
2841
  });
2619
- }, [resolveWallet, legacyKeys, keyMap, runOperation, addTx, refreshAssets]);
2842
+ }, [
2843
+ resolveWallet,
2844
+ legacyKeys,
2845
+ keyMap,
2846
+ runOperation,
2847
+ addTx,
2848
+ addCancelled,
2849
+ refreshAssets
2850
+ ]);
2620
2851
  const handleSweepOrdinals = useCallback2(async () => {
2621
2852
  if (!assets)
2622
2853
  return;
@@ -2688,12 +2919,21 @@ function SweepApp({
2688
2919
  await refreshAssets();
2689
2920
  throw new Error(result.errors[0]);
2690
2921
  }
2922
+ addCancelled(result.cancelledListings.length);
2691
2923
  for (let i = 0;i < result.ordinalTxids.length - 1; i++) {
2692
2924
  addTx(label, result.ordinalTxids[i]);
2693
2925
  }
2694
2926
  return result.ordinalTxids[result.ordinalTxids.length - 1] ?? "";
2695
2927
  });
2696
- }, [resolveWallet, legacyKeys, keyMap, runOperation, addTx, refreshAssets]);
2928
+ }, [
2929
+ resolveWallet,
2930
+ legacyKeys,
2931
+ keyMap,
2932
+ runOperation,
2933
+ addTx,
2934
+ addCancelled,
2935
+ refreshAssets
2936
+ ]);
2697
2937
  const handleSweepOpns = useCallback2(async () => {
2698
2938
  if (!assets)
2699
2939
  return;
@@ -2756,9 +2996,10 @@ function SweepApp({
2756
2996
  addTx("BSV-21", txid);
2757
2997
  if (result.error)
2758
2998
  throw new Error(result.error);
2999
+ addCancelled(result.cancelledListings.length);
2759
3000
  return result.txid ?? "";
2760
3001
  });
2761
- }, [resolveWallet, assets, keyMap, runOperation, addTx]);
3002
+ }, [resolveWallet, assets, keyMap, runOperation, addTx, addCancelled]);
2762
3003
  const handleSweepBsv20Token = useCallback2(async (tick) => {
2763
3004
  const wallet = resolveWallet();
2764
3005
  if (!wallet || !assets)
@@ -2775,20 +3016,71 @@ function SweepApp({
2775
3016
  });
2776
3017
  if (result.error)
2777
3018
  throw new Error(result.error);
3019
+ addCancelled(result.cancelledListings.length);
2778
3020
  return result.txid ?? "";
2779
3021
  });
2780
- }, [resolveWallet, assets, groupedBsv20, keyMap, runOperation]);
3022
+ }, [resolveWallet, assets, groupedBsv20, keyMap, runOperation, addCancelled]);
3023
+ const toggleSweepClass = useCallback2((sweepClass) => {
3024
+ setSkippedClasses((prev) => {
3025
+ const next = new Set(prev);
3026
+ if (next.has(sweepClass))
3027
+ next.delete(sweepClass);
3028
+ else
3029
+ next.add(sweepClass);
3030
+ return next;
3031
+ });
3032
+ }, []);
3033
+ const sweepClasses = useMemo(() => {
3034
+ if (!assets)
3035
+ return [];
3036
+ const classes = [];
3037
+ if (assets.funding.length > 0)
3038
+ classes.push({ id: "bsv", label: "BSV", count: assets.funding.length });
3039
+ if (assets.ordinals.length > 0)
3040
+ classes.push({
3041
+ id: "ordinals",
3042
+ label: "Ordinals",
3043
+ count: assets.ordinals.length
3044
+ });
3045
+ if (assets.opnsNames.length > 0)
3046
+ classes.push({
3047
+ id: "opns",
3048
+ label: "OpNS",
3049
+ count: assets.opnsNames.length
3050
+ });
3051
+ if (groupedBsv20.length > 0)
3052
+ classes.push({
3053
+ id: "bsv20",
3054
+ label: "BSV-20",
3055
+ count: groupedBsv20.reduce((n, t) => n + t.outputs.length, 0)
3056
+ });
3057
+ if (assets.bsv21Tokens.some((t) => t.outputs.length > 0))
3058
+ classes.push({
3059
+ id: "bsv21",
3060
+ label: "BSV-21",
3061
+ count: assets.bsv21Tokens.reduce((n, t) => n + t.outputs.length, 0)
3062
+ });
3063
+ return classes;
3064
+ }, [assets, groupedBsv20]);
2781
3065
  const handleSweepAll = useCallback2(async () => {
2782
3066
  const wallet = resolveWallet();
2783
3067
  if (!wallet || !legacyKeys || !assets)
2784
3068
  return;
2785
- await runOperation("Sweep all", async () => {
3069
+ await runOperation("Sweep selected", async () => {
3070
+ const all = selectAllSweepClasses(assets);
2786
3071
  const result = await sweepAllClasses({
2787
3072
  wallet,
2788
3073
  keys: keyMap,
2789
3074
  assets,
2790
3075
  amount: sweepAmount ?? undefined,
2791
- onProgress: setSweepProgress
3076
+ onProgress: setSweepProgress,
3077
+ selection: {
3078
+ sweepBsv: !skippedClasses.has("bsv") && all.sweepBsv,
3079
+ ordinalOutpoints: skippedClasses.has("ordinals") ? new Set : selectedOrdinals.size > 0 ? selectedOrdinals : all.ordinalOutpoints,
3080
+ opnsOutpoints: skippedClasses.has("opns") ? new Set : selectedOpns.size > 0 ? selectedOpns : all.opnsOutpoints,
3081
+ bsv20Ticks: skippedClasses.has("bsv20") ? new Set : all.bsv20Ticks,
3082
+ bsv21TokenIds: skippedClasses.has("bsv21") ? new Set : all.bsv21TokenIds
3083
+ }
2792
3084
  });
2793
3085
  if (result.bsvTxid)
2794
3086
  addTx("BSV", result.bsvTxid);
@@ -2798,6 +3090,7 @@ function SweepApp({
2798
3090
  addTx("BSV-20", txid);
2799
3091
  for (const txid of result.bsv21Txids)
2800
3092
  addTx("BSV-21", txid);
3093
+ addCancelled(result.cancelledListings.length);
2801
3094
  if (result.errors.length > 0)
2802
3095
  throw new Error(result.errors[0]);
2803
3096
  return result.bsvTxid ?? result.ordinalTxids.at(-1) ?? result.bsv20Txids.at(-1) ?? result.bsv21Txids.at(-1) ?? "";
@@ -2809,7 +3102,11 @@ function SweepApp({
2809
3102
  keyMap,
2810
3103
  sweepAmount,
2811
3104
  runOperation,
2812
- addTx
3105
+ addTx,
3106
+ addCancelled,
3107
+ skippedClasses,
3108
+ selectedOrdinals,
3109
+ selectedOpns
2813
3110
  ]);
2814
3111
  return /* @__PURE__ */ jsxs6("div", {
2815
3112
  className: "min-h-screen bg-background text-foreground",
@@ -2850,12 +3147,33 @@ function SweepApp({
2850
3147
  assets && !sweeping && /* @__PURE__ */ jsxs6("div", {
2851
3148
  className: "space-y-3",
2852
3149
  children: [
3150
+ showClassSkips(sweepClasses.map((c) => c.id), skippedClasses) && /* @__PURE__ */ jsx11("div", {
3151
+ className: "flex flex-wrap gap-1.5",
3152
+ children: sweepClasses.map((sweepClass) => {
3153
+ const skipped = skippedClasses.has(sweepClass.id);
3154
+ return /* @__PURE__ */ jsxs6(Button, {
3155
+ variant: skipped ? "outline" : "secondary",
3156
+ size: "sm",
3157
+ className: "gap-1.5",
3158
+ onClick: () => toggleSweepClass(sweepClass.id),
3159
+ title: skipped ? `Include ${sweepClass.label} in Sweep-selected` : `Skip ${sweepClass.label} in Sweep-selected`,
3160
+ children: [
3161
+ sweepClass.label,
3162
+ /* @__PURE__ */ jsx11(Badge, {
3163
+ variant: "secondary",
3164
+ className: "text-[10px] px-1.5 py-0",
3165
+ children: sweepClass.count
3166
+ })
3167
+ ]
3168
+ }, sweepClass.id);
3169
+ })
3170
+ }),
2853
3171
  /* @__PURE__ */ jsx11(Button, {
2854
3172
  className: "w-full",
2855
- disabled: !walletConnected,
3173
+ disabled: !walletConnected || isSweepAllDisabled(sweepClasses.map((c) => c.id), skippedClasses),
2856
3174
  onClick: handleSweepAll,
2857
3175
  title: walletConnected ? undefined : "Connect BRC-100 wallet to sweep",
2858
- children: "Sweep all to wallet"
3176
+ children: skippedClasses.size > 0 ? "Sweep selected to wallet" : "Sweep all to wallet"
2859
3177
  }),
2860
3178
  /* @__PURE__ */ jsx11(FundingSection, {
2861
3179
  funding: assets.funding,
@@ -2951,7 +3269,8 @@ function SweepApp({
2951
3269
  /* @__PURE__ */ jsx11(TxHistory, {
2952
3270
  sweeping,
2953
3271
  progress: sweepProgress,
2954
- history: txHistory
3272
+ history: txHistory,
3273
+ cancelledListings
2955
3274
  })
2956
3275
  ]
2957
3276
  })
@@ -3104,11 +3423,16 @@ export {
3104
3423
  getServices,
3105
3424
  getWallet,
3106
3425
  isConnected,
3426
+ isSweepAllDisabled,
3107
3427
  legacyBurnOrdinals,
3108
3428
  legacySendBsv,
3109
3429
  legacySendOrdinals,
3110
3430
  scanAddress,
3111
3431
  scanAddresses,
3432
+ selectAllSweepClasses,
3433
+ showClassSkips,
3112
3434
  sweepAllClasses,
3435
+ sweepBsv20Token,
3436
+ sweepBsv21Token,
3113
3437
  truncate
3114
3438
  };