@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.
@@ -3,6 +3,40 @@ import type { IndexedOutput } from '@1sat/types';
3
3
  import type { PrivateKey, WalletInterface } from '@bsv/sdk';
4
4
  import type { ScannedAssets, TokenBalance } from './scanner';
5
5
  export { SWEEP_BATCH_SIZE };
6
+ /** Transferable sweep classes, same order and names as `1sat sweep import`. */
7
+ export type SweepClass = 'bsv' | 'ordinals' | 'opns' | 'bsv20' | 'bsv21';
8
+ /**
9
+ * Per-class sweep selection. Empty sets sweep nothing in that class;
10
+ * omit `selection` entirely to sweep every class (previous behavior).
11
+ */
12
+ export interface SweepClassSelection {
13
+ sweepBsv: boolean;
14
+ /** Ordinals-class outpoints (`ordinals`, listed OrdLocks included). */
15
+ ordinalOutpoints: Set<string>;
16
+ /** OpNS-class outpoints. */
17
+ opnsOutpoints: Set<string>;
18
+ /** BSV-20 ticks. */
19
+ bsv20Ticks: Set<string>;
20
+ /** BSV-21 tokenIds. */
21
+ bsv21TokenIds: Set<string>;
22
+ }
23
+ /** Select every scanned asset, the default when no selection is passed. */
24
+ export declare function selectAllSweepClasses(assets: ScannedAssets): SweepClassSelection;
25
+ /**
26
+ * Sweep-all is disabled only when every *currently available* class is
27
+ * skipped. Comparing counts instead strands the button on stale skip ids
28
+ * after a refresh drops a class.
29
+ */
30
+ export declare function isSweepAllDisabled(available: SweepClass[], skipped: Set<SweepClass>): boolean;
31
+ /** Show class skip chips when there is a choice, or a skip to clear. */
32
+ export declare function showClassSkips(available: SweepClass[], skipped: Set<SweepClass>): boolean;
33
+ /** Per-step receipt, emitted via `onResult` as each class/batch completes. */
34
+ export interface SweepStepResult {
35
+ sweepClass: SweepClass;
36
+ label: string;
37
+ txid?: string;
38
+ error?: string;
39
+ }
6
40
  export interface SweepResult {
7
41
  bsvTxid?: string;
8
42
  ordinalTxids: string[];
@@ -11,6 +45,8 @@ export interface SweepResult {
11
45
  errors: string[];
12
46
  /** Outpoints successfully swept (ordinals/OpNS, including listed OrdLocks). */
13
47
  sweptOutpoints: string[];
48
+ /** Swept outpoints that were marketplace listings canceled into the wallet. */
49
+ cancelledListings: string[];
14
50
  }
15
51
  /**
16
52
  * Sweep BSV funding then ordinals into the connected wallet.
@@ -24,6 +60,10 @@ export declare function executeSweep(params: {
24
60
  ordinals: IndexedOutput[];
25
61
  amount?: number;
26
62
  onProgress: (stage: string) => void;
63
+ /** Outpoints to skip; successes are added (canonical `txid.vout` form). */
64
+ completed?: Set<string>;
65
+ signal?: AbortSignal;
66
+ onResult?: (result: SweepStepResult) => void;
27
67
  }): Promise<SweepResult>;
28
68
  /**
29
69
  * Sweep one BSV-21 token. Listed OrdLocks are each their own tx so one
@@ -34,27 +74,58 @@ export declare function sweepBsv21Token(params: {
34
74
  keys: Map<string, PrivateKey>;
35
75
  token: TokenBalance;
36
76
  onProgress: (stage: string) => void;
77
+ signal?: AbortSignal;
78
+ /** Outpoints to skip; successes are added (canonical `txid.vout` form). */
79
+ completed?: Set<string>;
80
+ onResult?: (result: SweepStepResult) => void;
37
81
  }): Promise<{
38
82
  txid?: string;
39
83
  txids: string[];
84
+ sweptOutpoints: string[];
85
+ cancelledListings: string[];
40
86
  error?: string;
41
87
  }>;
42
- /** Sweep one BSV-20 ticker into the connected wallet. */
88
+ /**
89
+ * Sweep one BSV-20 ticker into the connected wallet. BSV-20 needs no per-tx
90
+ * overlay funding, so splitting listed outputs 1-per-tx is cheap — pass
91
+ * `splitListed` so one invalid listing cannot sink the rest of the tick.
92
+ */
43
93
  export declare function sweepBsv20Token(params: {
44
94
  wallet: WalletInterface;
45
95
  keys: Map<string, PrivateKey>;
46
96
  token: Bsv20Balance;
47
97
  onProgress: (stage: string) => void;
98
+ signal?: AbortSignal;
99
+ splitListed?: boolean;
100
+ /** Outpoints to skip; successes are added (canonical `txid.vout` form). */
101
+ completed?: Set<string>;
102
+ onResult?: (result: SweepStepResult) => void;
48
103
  }): Promise<{
49
104
  txid?: string;
105
+ txids: string[];
106
+ sweptOutpoints: string[];
107
+ cancelledListings: string[];
50
108
  error?: string;
51
109
  }>;
52
- /** Same class order as `1sat sweep import`. Continues later classes if one fails. */
110
+ /**
111
+ * Same class order as `1sat sweep import`: BSV, ordinals, OpNS, BSV-20,
112
+ * BSV-21. Continues later classes if one fails. Pass `selection` to sweep
113
+ * only chosen classes/items (like `1sat sweep --only/--skip`); pass
114
+ * `completed` to skip already-swept outpoints and record new successes for
115
+ * retry; pass `signal` to abort between steps. Listed OrdLocks stay in their
116
+ * class and cancel in that spend.
117
+ */
53
118
  export declare function sweepAllClasses(params: {
54
119
  wallet: WalletInterface;
55
120
  keys: Map<string, PrivateKey>;
56
121
  assets: ScannedAssets;
57
122
  amount?: number;
58
123
  onProgress: (stage: string) => void;
124
+ selection?: SweepClassSelection;
125
+ completed?: Set<string>;
126
+ signal?: AbortSignal;
127
+ onResult?: (result: SweepStepResult) => void;
128
+ /** Split listed BSV-20 outputs 1-per-tx so one bad listing cannot sink the tick. */
129
+ splitListedBsv20?: boolean;
59
130
  }): Promise<SweepResult>;
60
131
  //# sourceMappingURL=sweeper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sweeper.d.ts","sourceRoot":"","sources":["../../src/lib/sweeper.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,YAAY,EACjB,gBAAgB,EAShB,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAG5D,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAE3B,MAAM,WAAW,WAAW;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,EAAE,CAAA;CACxB;AA8BD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE;IAC1C,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC7B,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,GAAG,OAAO,CAAC,WAAW,CAAC,CAiEvB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC7B,KAAK,EAAE,YAAY,CAAA;IACnB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,GAAG,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAqC9D;AAED,yDAAyD;AACzD,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC7B,KAAK,EAAE,YAAY,CAAA;IACnB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,GAAG,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6B7C;AAED,qFAAqF;AACrF,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC7B,MAAM,EAAE,aAAa,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,GAAG,OAAO,CAAC,WAAW,CAAC,CAmEvB"}
1
+ {"version":3,"file":"sweeper.d.ts","sourceRoot":"","sources":["../../src/lib/sweeper.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1sat/sweep-ui",
3
- "version": "0.0.96",
3
+ "version": "0.0.98",
4
4
  "description": "Sweep UI components for migrating legacy BSV assets",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -29,11 +29,11 @@
29
29
  ],
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
- "@1sat/actions": "0.0.217",
33
- "@1sat/client": "0.0.53",
34
- "@1sat/connect": "0.0.93",
35
- "@1sat/types": "0.0.46",
36
- "@1sat/utils": "0.0.36",
32
+ "@1sat/actions": "0.0.220",
33
+ "@1sat/client": "0.0.54",
34
+ "@1sat/connect": "0.0.94",
35
+ "@1sat/types": "0.0.47",
36
+ "@1sat/utils": "0.0.37",
37
37
  "bitcoin-backup": "^0.0.11",
38
38
  "class-variance-authority": "^0.7.1",
39
39
  "clsx": "^2.1.1",
@@ -14,7 +14,11 @@ import {
14
14
  scanAddresses,
15
15
  } from '../lib/scanner'
16
16
  import {
17
+ type SweepClass,
17
18
  executeSweep,
19
+ isSweepAllDisabled,
20
+ selectAllSweepClasses,
21
+ showClassSkips,
18
22
  sweepAllClasses,
19
23
  sweepBsv20Token,
20
24
  sweepBsv21Token,
@@ -58,12 +62,17 @@ export function SweepApp({
58
62
  const [sweeping, setSweeping] = useState(false)
59
63
  const [sweepProgress, setSweepProgress] = useState('')
60
64
  const [txHistory, setTxHistory] = useState<TxRecord[]>([])
65
+ const [cancelledListings, setCancelledListings] = useState(0)
61
66
  const [selectedOrdinals, setSelectedOrdinals] = useState<Set<string>>(
62
67
  new Set(),
63
68
  )
64
69
  const [selectedOpns, setSelectedOpns] = useState<Set<string>>(new Set())
65
70
  const [sweepAmount, setSweepAmount] = useState<number | null>(null)
66
71
  const [activeTab, setActiveTab] = useState<TabId>('ordinals')
72
+ /** Classes excluded from Sweep-all (CLI `--skip` equivalent). Empty = all. */
73
+ const [skippedClasses, setSkippedClasses] = useState<Set<SweepClass>>(
74
+ new Set(),
75
+ )
67
76
 
68
77
  useEffect(() => {
69
78
  setWalletConnected(!!externalWallet)
@@ -100,6 +109,10 @@ export function SweepApp({
100
109
  ])
101
110
  }, [])
102
111
 
112
+ const addCancelled = useCallback((count: number) => {
113
+ if (count > 0) setCancelledListings((prev) => prev + count)
114
+ }, [])
115
+
103
116
  const groupedBsv20 = useMemo(
104
117
  () => (assets ? groupBsv20Tokens(assets.bsv20Tokens) : []),
105
118
  [assets],
@@ -194,6 +207,7 @@ export function SweepApp({
194
207
  setAssets(null)
195
208
  setSelectedOrdinals(new Set())
196
209
  setSelectedOpns(new Set())
210
+ setSkippedClasses(new Set())
197
211
  setSweepAmount(null)
198
212
  setLegacyKeys(keys)
199
213
 
@@ -284,6 +298,7 @@ export function SweepApp({
284
298
  onProgress: setSweepProgress,
285
299
  })
286
300
  if (result.errors.length > 0) throw new Error(result.errors[0])
301
+ addCancelled(result.cancelledListings.length)
287
302
  return result.bsvTxid ?? ''
288
303
  })
289
304
  }, [
@@ -294,6 +309,7 @@ export function SweepApp({
294
309
  getSelectedFunding,
295
310
  runOperation,
296
311
  keyMap,
312
+ addCancelled,
297
313
  ])
298
314
 
299
315
  const handleSendBsv = useCallback(
@@ -336,13 +352,22 @@ export function SweepApp({
336
352
  if (result.sweptOutpoints.length > 0) await refreshAssets()
337
353
  throw new Error(result.errors[0])
338
354
  }
355
+ addCancelled(result.cancelledListings.length)
339
356
  for (let i = 0; i < result.ordinalTxids.length - 1; i++) {
340
357
  addTx(label, result.ordinalTxids[i])
341
358
  }
342
359
  return result.ordinalTxids[result.ordinalTxids.length - 1] ?? ''
343
360
  })
344
361
  },
345
- [resolveWallet, legacyKeys, keyMap, runOperation, addTx, refreshAssets],
362
+ [
363
+ resolveWallet,
364
+ legacyKeys,
365
+ keyMap,
366
+ runOperation,
367
+ addTx,
368
+ addCancelled,
369
+ refreshAssets,
370
+ ],
346
371
  )
347
372
 
348
373
  const handleSweepOrdinals = useCallback(async () => {
@@ -431,13 +456,22 @@ export function SweepApp({
431
456
  if (result.sweptOutpoints.length > 0) await refreshAssets()
432
457
  throw new Error(result.errors[0])
433
458
  }
459
+ addCancelled(result.cancelledListings.length)
434
460
  for (let i = 0; i < result.ordinalTxids.length - 1; i++) {
435
461
  addTx(label, result.ordinalTxids[i])
436
462
  }
437
463
  return result.ordinalTxids[result.ordinalTxids.length - 1] ?? ''
438
464
  })
439
465
  },
440
- [resolveWallet, legacyKeys, keyMap, runOperation, addTx, refreshAssets],
466
+ [
467
+ resolveWallet,
468
+ legacyKeys,
469
+ keyMap,
470
+ runOperation,
471
+ addTx,
472
+ addCancelled,
473
+ refreshAssets,
474
+ ],
441
475
  )
442
476
 
443
477
  const handleSweepOpns = useCallback(async () => {
@@ -519,11 +553,12 @@ export function SweepApp({
519
553
  })
520
554
  for (const txid of result.txids.slice(0, -1)) addTx('BSV-21', txid)
521
555
  if (result.error) throw new Error(result.error)
556
+ addCancelled(result.cancelledListings.length)
522
557
  return result.txid ?? ''
523
558
  },
524
559
  )
525
560
  },
526
- [resolveWallet, assets, keyMap, runOperation, addTx],
561
+ [resolveWallet, assets, keyMap, runOperation, addTx, addCancelled],
527
562
  )
528
563
 
529
564
  const handleSweepBsv20Token = useCallback(
@@ -540,27 +575,90 @@ export function SweepApp({
540
575
  onProgress: setSweepProgress,
541
576
  })
542
577
  if (result.error) throw new Error(result.error)
578
+ addCancelled(result.cancelledListings.length)
543
579
  return result.txid ?? ''
544
580
  })
545
581
  },
546
- [resolveWallet, assets, groupedBsv20, keyMap, runOperation],
582
+ [resolveWallet, assets, groupedBsv20, keyMap, runOperation, addCancelled],
547
583
  )
548
584
 
585
+ const toggleSweepClass = useCallback((sweepClass: SweepClass) => {
586
+ setSkippedClasses((prev) => {
587
+ const next = new Set(prev)
588
+ if (next.has(sweepClass)) next.delete(sweepClass)
589
+ else next.add(sweepClass)
590
+ return next
591
+ })
592
+ }, [])
593
+
594
+ const sweepClasses = useMemo(() => {
595
+ if (!assets) return []
596
+ const classes: { id: SweepClass; label: string; count: number }[] = []
597
+ if (assets.funding.length > 0)
598
+ classes.push({ id: 'bsv', label: 'BSV', count: assets.funding.length })
599
+ if (assets.ordinals.length > 0)
600
+ classes.push({
601
+ id: 'ordinals',
602
+ label: 'Ordinals',
603
+ count: assets.ordinals.length,
604
+ })
605
+ if (assets.opnsNames.length > 0)
606
+ classes.push({
607
+ id: 'opns',
608
+ label: 'OpNS',
609
+ count: assets.opnsNames.length,
610
+ })
611
+ if (groupedBsv20.length > 0)
612
+ classes.push({
613
+ id: 'bsv20',
614
+ label: 'BSV-20',
615
+ count: groupedBsv20.reduce((n, t) => n + t.outputs.length, 0),
616
+ })
617
+ if (assets.bsv21Tokens.some((t) => t.outputs.length > 0))
618
+ classes.push({
619
+ id: 'bsv21',
620
+ label: 'BSV-21',
621
+ count: assets.bsv21Tokens.reduce((n, t) => n + t.outputs.length, 0),
622
+ })
623
+ return classes
624
+ }, [assets, groupedBsv20])
625
+
549
626
  const handleSweepAll = useCallback(async () => {
550
627
  const wallet = resolveWallet()
551
628
  if (!wallet || !legacyKeys || !assets) return
552
- await runOperation('Sweep all', async () => {
629
+ await runOperation('Sweep selected', async () => {
630
+ const all = selectAllSweepClasses(assets)
553
631
  const result = await sweepAllClasses({
554
632
  wallet,
555
633
  keys: keyMap,
556
634
  assets,
557
635
  amount: sweepAmount ?? undefined,
558
636
  onProgress: setSweepProgress,
637
+ selection: {
638
+ sweepBsv: !skippedClasses.has('bsv') && all.sweepBsv,
639
+ ordinalOutpoints: skippedClasses.has('ordinals')
640
+ ? new Set<string>()
641
+ : selectedOrdinals.size > 0
642
+ ? selectedOrdinals
643
+ : all.ordinalOutpoints,
644
+ opnsOutpoints: skippedClasses.has('opns')
645
+ ? new Set<string>()
646
+ : selectedOpns.size > 0
647
+ ? selectedOpns
648
+ : all.opnsOutpoints,
649
+ bsv20Ticks: skippedClasses.has('bsv20')
650
+ ? new Set<string>()
651
+ : all.bsv20Ticks,
652
+ bsv21TokenIds: skippedClasses.has('bsv21')
653
+ ? new Set<string>()
654
+ : all.bsv21TokenIds,
655
+ },
559
656
  })
560
657
  if (result.bsvTxid) addTx('BSV', result.bsvTxid)
561
658
  for (const txid of result.ordinalTxids) addTx('Ordinals', txid)
562
659
  for (const txid of result.bsv20Txids) addTx('BSV-20', txid)
563
660
  for (const txid of result.bsv21Txids) addTx('BSV-21', txid)
661
+ addCancelled(result.cancelledListings.length)
564
662
  if (result.errors.length > 0) throw new Error(result.errors[0])
565
663
  return (
566
664
  result.bsvTxid ??
@@ -578,6 +676,10 @@ export function SweepApp({
578
676
  sweepAmount,
579
677
  runOperation,
580
678
  addTx,
679
+ addCancelled,
680
+ skippedClasses,
681
+ selectedOrdinals,
682
+ selectedOpns,
581
683
  ])
582
684
 
583
685
  return (
@@ -615,15 +717,55 @@ export function SweepApp({
615
717
 
616
718
  {assets && !sweeping && (
617
719
  <div className="space-y-3">
720
+ {showClassSkips(
721
+ sweepClasses.map((c) => c.id),
722
+ skippedClasses,
723
+ ) && (
724
+ <div className="flex flex-wrap gap-1.5">
725
+ {sweepClasses.map((sweepClass) => {
726
+ const skipped = skippedClasses.has(sweepClass.id)
727
+ return (
728
+ <Button
729
+ key={sweepClass.id}
730
+ variant={skipped ? 'outline' : 'secondary'}
731
+ size="sm"
732
+ className="gap-1.5"
733
+ onClick={() => toggleSweepClass(sweepClass.id)}
734
+ title={
735
+ skipped
736
+ ? `Include ${sweepClass.label} in Sweep-selected`
737
+ : `Skip ${sweepClass.label} in Sweep-selected`
738
+ }
739
+ >
740
+ {sweepClass.label}
741
+ <Badge
742
+ variant="secondary"
743
+ className="text-[10px] px-1.5 py-0"
744
+ >
745
+ {sweepClass.count}
746
+ </Badge>
747
+ </Button>
748
+ )
749
+ })}
750
+ </div>
751
+ )}
618
752
  <Button
619
753
  className="w-full"
620
- disabled={!walletConnected}
754
+ disabled={
755
+ !walletConnected ||
756
+ isSweepAllDisabled(
757
+ sweepClasses.map((c) => c.id),
758
+ skippedClasses,
759
+ )
760
+ }
621
761
  onClick={handleSweepAll}
622
762
  title={
623
763
  walletConnected ? undefined : 'Connect BRC-100 wallet to sweep'
624
764
  }
625
765
  >
626
- Sweep all to wallet
766
+ {skippedClasses.size > 0
767
+ ? 'Sweep selected to wallet'
768
+ : 'Sweep all to wallet'}
627
769
  </Button>
628
770
  <FundingSection
629
771
  funding={assets.funding}
@@ -715,6 +857,7 @@ export function SweepApp({
715
857
  sweeping={sweeping}
716
858
  progress={sweepProgress}
717
859
  history={txHistory}
860
+ cancelledListings={cancelledListings}
718
861
  />
719
862
  </div>
720
863
  </div>
@@ -13,9 +13,16 @@ interface Props {
13
13
  sweeping: boolean
14
14
  progress: string
15
15
  history: TxRecord[]
16
+ /** Marketplace listings canceled into the wallet across this session. */
17
+ cancelledListings?: number
16
18
  }
17
19
 
18
- export function TxHistory({ sweeping, progress, history }: Props) {
20
+ export function TxHistory({
21
+ sweeping,
22
+ progress,
23
+ history,
24
+ cancelledListings = 0,
25
+ }: Props) {
19
26
  return (
20
27
  <>
21
28
  {sweeping && (
@@ -32,6 +39,13 @@ export function TxHistory({ sweeping, progress, history }: Props) {
32
39
  <div className="border border-border/50 rounded-lg p-3 space-y-2">
33
40
  <div className="text-xs font-medium text-muted-foreground">
34
41
  Transactions ({history.length})
42
+ {cancelledListings > 0 && (
43
+ <span>
44
+ {' '}
45
+ · {cancelledListings} listing
46
+ {cancelledListings !== 1 ? 's' : ''} canceled
47
+ </span>
48
+ )}
35
49
  </div>
36
50
  <div className="space-y-2 max-h-48 overflow-y-auto">
37
51
  {[...history].reverse().map((tx, i) => (
package/src/index.ts CHANGED
@@ -53,8 +53,16 @@ export {
53
53
  export {
54
54
  executeSweep,
55
55
  sweepAllClasses,
56
+ sweepBsv20Token,
57
+ sweepBsv21Token,
58
+ selectAllSweepClasses,
59
+ isSweepAllDisabled,
60
+ showClassSkips,
56
61
  SWEEP_BATCH_SIZE,
62
+ type SweepClass,
63
+ type SweepClassSelection,
57
64
  type SweepResult,
65
+ type SweepStepResult,
58
66
  } from './lib/sweeper'
59
67
  export {
60
68
  legacySendBsv,