@1sat/sweep-ui 0.0.95 → 0.0.96

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.
@@ -7,10 +7,9 @@ export interface EnrichedOrdinal extends IndexedOutput {
7
7
  name?: string;
8
8
  contentUrl: string;
9
9
  }
10
- export interface ScannedAssets extends Omit<ScanResult, 'ordinals' | 'opnsNames'> {
10
+ export interface ScannedAssets extends Omit<ScanResult, 'ordinals' | 'opnsNames' | 'listings'> {
11
11
  ordinals: EnrichedOrdinal[];
12
12
  opnsNames: EnrichedOrdinal[];
13
- listings: EnrichedOrdinal[];
14
13
  bsv21Tokens: TokenBalance[];
15
14
  totalBsv: number;
16
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EAEjB,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIhD,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,CAAA;AAEtD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,aAChB,SAAQ,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,CAAC;IAClD,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,SAAS,EAAE,eAAe,EAAE,CAAA;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,WAAW,EAAE,YAAY,EAAE,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AA2CD,wBAAsB,WAAW,CAChC,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GACpC,OAAO,CAAC,aAAa,CAAC,CAGxB;AAED,wBAAsB,aAAa,CAClC,SAAS,EAAE,MAAM,EAAE,EACnB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GACpC,OAAO,CAAC,aAAa,CAAC,CAIxB"}
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EAEjB,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIhD,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,CAAA;AAEtD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,aAChB,SAAQ,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;IAC/D,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,SAAS,EAAE,eAAe,EAAE,CAAA;IAC5B,WAAW,EAAE,YAAY,EAAE,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AA2CD,wBAAsB,WAAW,CAChC,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GACpC,OAAO,CAAC,aAAa,CAAC,CAGxB;AAED,wBAAsB,aAAa,CAClC,SAAS,EAAE,MAAM,EAAE,EACnB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GACpC,OAAO,CAAC,aAAa,CAAC,CAIxB"}
@@ -1,36 +1,33 @@
1
+ import { type Bsv20Balance, SWEEP_BATCH_SIZE } from '@1sat/actions';
1
2
  import type { IndexedOutput } from '@1sat/types';
2
3
  import type { PrivateKey, WalletInterface } from '@bsv/sdk';
3
- import type { TokenBalance } from './scanner';
4
- /** Page size, select-page size, and createAction batch size for ordinal/OpNS sweeps. */
5
- export declare const SWEEP_BATCH_SIZE = 25;
4
+ import type { ScannedAssets, TokenBalance } from './scanner';
5
+ export { SWEEP_BATCH_SIZE };
6
6
  export interface SweepResult {
7
7
  bsvTxid?: string;
8
8
  ordinalTxids: string[];
9
- listingTxids: string[];
9
+ bsv20Txids: string[];
10
10
  bsv21Txids: string[];
11
11
  errors: string[];
12
- /** Outpoints successfully swept (ordinals/OpNS). */
12
+ /** Outpoints successfully swept (ordinals/OpNS, including listed OrdLocks). */
13
13
  sweptOutpoints: string[];
14
- /** Listing outpoints cancelled into the BRC-100 wallet. */
15
- cancelledListings: string[];
16
14
  }
17
15
  /**
18
- * Sweep BSV funding and ordinals into the connected wallet.
19
- * Ordinals are processed in batches of {@link SWEEP_BATCH_SIZE}; stops on first batch error.
16
+ * Sweep BSV funding then ordinals into the connected wallet.
17
+ * Same class order as `1sat sweep import` for these two classes.
18
+ * Listed OrdLocks belong in `ordinals` and cancel in that spend.
20
19
  */
21
20
  export declare function executeSweep(params: {
22
21
  wallet: WalletInterface;
23
22
  keys: Map<string, PrivateKey>;
24
23
  funding: IndexedOutput[];
25
24
  ordinals: IndexedOutput[];
26
- /** OPL-4696: listed OrdLock UTXOs — cancelled into BRC-100 via sweepOrdinals. */
27
- listings?: IndexedOutput[];
28
25
  amount?: number;
29
26
  onProgress: (stage: string) => void;
30
27
  }): Promise<SweepResult>;
31
28
  /**
32
- * Sweep a single BSV-21 token into the connected wallet.
33
- * Each token requires its own transaction since all inputs must share a tokenId.
29
+ * Sweep one BSV-21 token. Listed OrdLocks are each their own tx so one
30
+ * invalid listing cannot sink the rest. Unlisted UTXOs of the token share a spend.
34
31
  */
35
32
  export declare function sweepBsv21Token(params: {
36
33
  wallet: WalletInterface;
@@ -39,6 +36,25 @@ export declare function sweepBsv21Token(params: {
39
36
  onProgress: (stage: string) => void;
40
37
  }): Promise<{
41
38
  txid?: string;
39
+ txids: string[];
42
40
  error?: string;
43
41
  }>;
42
+ /** Sweep one BSV-20 ticker into the connected wallet. */
43
+ export declare function sweepBsv20Token(params: {
44
+ wallet: WalletInterface;
45
+ keys: Map<string, PrivateKey>;
46
+ token: Bsv20Balance;
47
+ onProgress: (stage: string) => void;
48
+ }): Promise<{
49
+ txid?: string;
50
+ error?: string;
51
+ }>;
52
+ /** Same class order as `1sat sweep import`. Continues later classes if one fails. */
53
+ export declare function sweepAllClasses(params: {
54
+ wallet: WalletInterface;
55
+ keys: Map<string, PrivateKey>;
56
+ assets: ScannedAssets;
57
+ amount?: number;
58
+ onProgress: (stage: string) => void;
59
+ }): Promise<SweepResult>;
44
60
  //# sourceMappingURL=sweeper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sweeper.d.ts","sourceRoot":"","sources":["../../src/lib/sweeper.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAG7C,wFAAwF;AACxF,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC,MAAM,WAAW,WAAW;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,oDAAoD;IACpD,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,2DAA2D;IAC3D,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAC3B;AA8BD;;;GAGG;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,iFAAiF;IACjF,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,GAAG,OAAO,CAAC,WAAW,CAAC,CAsGvB;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,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA4B7C"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1sat/sweep-ui",
3
- "version": "0.0.95",
3
+ "version": "0.0.96",
4
4
  "description": "Sweep UI components for migrating legacy BSV assets",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -29,10 +29,10 @@
29
29
  ],
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
- "@1sat/actions": "0.0.216",
32
+ "@1sat/actions": "0.0.217",
33
33
  "@1sat/client": "0.0.53",
34
34
  "@1sat/connect": "0.0.93",
35
- "@1sat/types": "0.0.45",
35
+ "@1sat/types": "0.0.46",
36
36
  "@1sat/utils": "0.0.36",
37
37
  "bitcoin-backup": "^0.0.11",
38
38
  "class-variance-authority": "^0.7.1",
@@ -1,3 +1,4 @@
1
+ import { groupBsv20Tokens } from '@1sat/actions'
1
2
  import { PrivateKey, type WalletInterface } from '@bsv/sdk'
2
3
  import { useCallback, useEffect, useMemo, useState } from 'react'
3
4
  import { Toaster, toast } from 'sonner'
@@ -12,14 +13,18 @@ import {
12
13
  deriveAddress,
13
14
  scanAddresses,
14
15
  } from '../lib/scanner'
15
- import { executeSweep, sweepBsv21Token } from '../lib/sweeper'
16
+ import {
17
+ executeSweep,
18
+ sweepAllClasses,
19
+ sweepBsv20Token,
20
+ sweepBsv21Token,
21
+ } from '../lib/sweeper'
16
22
  import { getWallet } from '../lib/wallet'
17
23
  import type { LegacyKeys } from '../types'
18
24
  import {
19
25
  Bsv20Section,
20
26
  Bsv21Section,
21
27
  FundingSection,
22
- ListingsSection,
23
28
  LockedSection,
24
29
  OrdinalsSection,
25
30
  RunSection,
@@ -28,10 +33,11 @@ import { ConnectWallet } from './connect-wallet'
28
33
  import { OpnsSection } from './opns-section'
29
34
  import { TxHistory, type TxRecord } from './tx-history'
30
35
  import { Badge } from './ui/badge'
36
+ import { Button } from './ui/button'
31
37
  import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'
32
38
  import { WifInput } from './wif-input'
33
39
 
34
- type TabId = 'ordinals' | 'opns' | 'bsv21' | 'bsv20' | 'locks' | 'run'
40
+ type TabId = 'ordinals' | 'opns' | 'bsv20' | 'bsv21' | 'locks' | 'run'
35
41
 
36
42
  export interface SweepAppProps {
37
43
  legacyKeys?: LegacyKeys
@@ -94,6 +100,15 @@ export function SweepApp({
94
100
  ])
95
101
  }, [])
96
102
 
103
+ const groupedBsv20 = useMemo(
104
+ () => (assets ? groupBsv20Tokens(assets.bsv20Tokens) : []),
105
+ [assets],
106
+ )
107
+ const unparsedBsv20 = assets
108
+ ? assets.bsv20Tokens.length -
109
+ groupedBsv20.reduce((n, t) => n + t.outputs.length, 0)
110
+ : 0
111
+
97
112
  const tabs = useMemo(() => {
98
113
  if (!assets) return []
99
114
  const t: { id: TabId; label: string; count: number }[] = []
@@ -105,16 +120,24 @@ export function SweepApp({
105
120
  })
106
121
  if (assets.opnsNames.length > 0)
107
122
  t.push({ id: 'opns', label: 'OpNS', count: assets.opnsNames.length })
123
+ if (groupedBsv20.length > 0 || unparsedBsv20 > 0)
124
+ t.push({
125
+ id: 'bsv20',
126
+ label: 'BSV-20',
127
+ count: groupedBsv20.reduce((n, tok) => n + tok.outputs.length, 0),
128
+ })
108
129
  if (assets.bsv21Tokens.length > 0)
109
- t.push({ id: 'bsv21', label: 'BSV-21', count: assets.bsv21Tokens.length })
110
- if (assets.bsv20Tokens.length > 0)
111
- t.push({ id: 'bsv20', label: 'BSV-20', count: assets.bsv20Tokens.length })
130
+ t.push({
131
+ id: 'bsv21',
132
+ label: 'BSV-21',
133
+ count: assets.bsv21Tokens.reduce((n, tok) => n + tok.outputs.length, 0),
134
+ })
112
135
  if (assets.locked.length > 0)
113
136
  t.push({ id: 'locks', label: 'Locks', count: assets.locked.length })
114
137
  if (assets.run.length > 0)
115
138
  t.push({ id: 'run', label: 'RUN', count: assets.run.length })
116
139
  return t
117
- }, [assets])
140
+ }, [assets, groupedBsv20, unparsedBsv20])
118
141
 
119
142
  const handleToggleOrdinal = useCallback((outpoint: string) => {
120
143
  setSelectedOrdinals((prev) => {
@@ -187,21 +210,21 @@ export function SweepApp({
187
210
  )
188
211
  setAssets(result)
189
212
 
213
+ const grouped = groupBsv20Tokens(result.bsv20Tokens)
190
214
  const total =
191
215
  result.funding.length +
192
216
  result.ordinals.length +
193
217
  result.opnsNames.length +
194
218
  result.bsv21Tokens.reduce((n, t) => n + t.outputs.length, 0) +
195
- result.bsv20Tokens.length +
196
- result.listings.length +
219
+ grouped.reduce((n, t) => n + t.outputs.length, 0) +
197
220
  result.locked.length +
198
221
  result.run.length
199
222
  if (total === 0) toast.info('No assets found at legacy addresses')
200
223
 
201
224
  if (result.ordinals.length > 0) setActiveTab('ordinals')
202
225
  else if (result.opnsNames.length > 0) setActiveTab('opns')
226
+ else if (grouped.length > 0) setActiveTab('bsv20')
203
227
  else if (result.bsv21Tokens.length > 0) setActiveTab('bsv21')
204
- else if (result.bsv20Tokens.length > 0) setActiveTab('bsv20')
205
228
  else if (result.locked.length > 0) setActiveTab('locks')
206
229
  } catch (e) {
207
230
  console.error('Scan failed:', e)
@@ -218,7 +241,7 @@ export function SweepApp({
218
241
  const runOperation = useCallback(
219
242
  async (label: string, op: () => Promise<string>) => {
220
243
  setSweeping(true)
221
- setSweepProgress(label + '...')
244
+ setSweepProgress(`${label}...`)
222
245
  try {
223
246
  const txid = await op()
224
247
  if (txid) addTx(label, txid)
@@ -257,15 +280,10 @@ export function SweepApp({
257
280
  keys: keyMap,
258
281
  funding: getSelectedFunding(),
259
282
  ordinals: [],
260
- listings: assets.listings,
261
283
  amount: sweepAmount ?? undefined,
262
284
  onProgress: setSweepProgress,
263
285
  })
264
- for (const txid of result.listingTxids) addTx('Cancel listings', txid)
265
- if (result.errors.length > 0) {
266
- if (result.cancelledListings.length > 0) await refreshAssets()
267
- throw new Error(result.errors[0])
268
- }
286
+ if (result.errors.length > 0) throw new Error(result.errors[0])
269
287
  return result.bsvTxid ?? ''
270
288
  })
271
289
  }, [
@@ -276,41 +294,6 @@ export function SweepApp({
276
294
  getSelectedFunding,
277
295
  runOperation,
278
296
  keyMap,
279
- addTx,
280
- refreshAssets,
281
- ])
282
-
283
- const handleCancelListings = useCallback(async () => {
284
- const wallet = resolveWallet()
285
- if (!wallet || !legacyKeys || !assets || assets.listings.length === 0)
286
- return
287
- await runOperation('Cancel listings', async () => {
288
- const result = await executeSweep({
289
- wallet,
290
- keys: keyMap,
291
- funding: [],
292
- ordinals: [],
293
- listings: assets.listings,
294
- onProgress: setSweepProgress,
295
- })
296
- if (result.errors.length > 0) {
297
- for (const txid of result.listingTxids) addTx('Cancel listings', txid)
298
- if (result.cancelledListings.length > 0) await refreshAssets()
299
- throw new Error(result.errors[0])
300
- }
301
- for (let i = 0; i < result.listingTxids.length - 1; i++) {
302
- addTx('Cancel listings', result.listingTxids[i])
303
- }
304
- return result.listingTxids[result.listingTxids.length - 1] ?? ''
305
- })
306
- }, [
307
- resolveWallet,
308
- legacyKeys,
309
- assets,
310
- keyMap,
311
- runOperation,
312
- addTx,
313
- refreshAssets,
314
297
  ])
315
298
 
316
299
  const handleSendBsv = useCallback(
@@ -534,14 +517,69 @@ export function SweepApp({
534
517
  token,
535
518
  onProgress: setSweepProgress,
536
519
  })
520
+ for (const txid of result.txids.slice(0, -1)) addTx('BSV-21', txid)
537
521
  if (result.error) throw new Error(result.error)
538
522
  return result.txid ?? ''
539
523
  },
540
524
  )
541
525
  },
542
- [resolveWallet, assets, keyMap, runOperation],
526
+ [resolveWallet, assets, keyMap, runOperation, addTx],
527
+ )
528
+
529
+ const handleSweepBsv20Token = useCallback(
530
+ async (tick: string) => {
531
+ const wallet = resolveWallet()
532
+ if (!wallet || !assets) return
533
+ const token = groupedBsv20.find((t) => t.tick === tick)
534
+ if (!token) return
535
+ await runOperation(`Sweep ${token.tick}`, async () => {
536
+ const result = await sweepBsv20Token({
537
+ wallet,
538
+ keys: keyMap,
539
+ token,
540
+ onProgress: setSweepProgress,
541
+ })
542
+ if (result.error) throw new Error(result.error)
543
+ return result.txid ?? ''
544
+ })
545
+ },
546
+ [resolveWallet, assets, groupedBsv20, keyMap, runOperation],
543
547
  )
544
548
 
549
+ const handleSweepAll = useCallback(async () => {
550
+ const wallet = resolveWallet()
551
+ if (!wallet || !legacyKeys || !assets) return
552
+ await runOperation('Sweep all', async () => {
553
+ const result = await sweepAllClasses({
554
+ wallet,
555
+ keys: keyMap,
556
+ assets,
557
+ amount: sweepAmount ?? undefined,
558
+ onProgress: setSweepProgress,
559
+ })
560
+ if (result.bsvTxid) addTx('BSV', result.bsvTxid)
561
+ for (const txid of result.ordinalTxids) addTx('Ordinals', txid)
562
+ for (const txid of result.bsv20Txids) addTx('BSV-20', txid)
563
+ for (const txid of result.bsv21Txids) addTx('BSV-21', txid)
564
+ if (result.errors.length > 0) throw new Error(result.errors[0])
565
+ return (
566
+ result.bsvTxid ??
567
+ result.ordinalTxids.at(-1) ??
568
+ result.bsv20Txids.at(-1) ??
569
+ result.bsv21Txids.at(-1) ??
570
+ ''
571
+ )
572
+ })
573
+ }, [
574
+ resolveWallet,
575
+ legacyKeys,
576
+ assets,
577
+ keyMap,
578
+ sweepAmount,
579
+ runOperation,
580
+ addTx,
581
+ ])
582
+
545
583
  return (
546
584
  <div className="min-h-screen bg-background text-foreground">
547
585
  <Toaster position="top-right" />
@@ -577,6 +615,16 @@ export function SweepApp({
577
615
 
578
616
  {assets && !sweeping && (
579
617
  <div className="space-y-3">
618
+ <Button
619
+ className="w-full"
620
+ disabled={!walletConnected}
621
+ onClick={handleSweepAll}
622
+ title={
623
+ walletConnected ? undefined : 'Connect BRC-100 wallet to sweep'
624
+ }
625
+ >
626
+ Sweep all to wallet
627
+ </Button>
580
628
  <FundingSection
581
629
  funding={assets.funding}
582
630
  totalBsv={assets.totalBsv}
@@ -586,11 +634,6 @@ export function SweepApp({
586
634
  onSend={sweepOnly ? undefined : handleSendBsv}
587
635
  walletConnected={walletConnected}
588
636
  />
589
- <ListingsSection
590
- listings={assets.listings}
591
- walletConnected={walletConnected}
592
- onCancel={handleCancelListings}
593
- />
594
637
 
595
638
  {tabs.length > 0 && (
596
639
  <Tabs
@@ -642,6 +685,14 @@ export function SweepApp({
642
685
  walletConnected={walletConnected}
643
686
  />
644
687
  </TabsContent>
688
+ <TabsContent value="bsv20">
689
+ <Bsv20Section
690
+ tokens={groupedBsv20}
691
+ unparsedCount={unparsedBsv20}
692
+ onSweep={handleSweepBsv20Token}
693
+ walletConnected={walletConnected}
694
+ />
695
+ </TabsContent>
645
696
  <TabsContent value="bsv21">
646
697
  <Bsv21Section
647
698
  tokens={assets.bsv21Tokens}
@@ -649,9 +700,6 @@ export function SweepApp({
649
700
  walletConnected={walletConnected}
650
701
  />
651
702
  </TabsContent>
652
- <TabsContent value="bsv20">
653
- <Bsv20Section tokens={assets.bsv20Tokens} />
654
- </TabsContent>
655
703
  <TabsContent value="locks">
656
704
  <LockedSection locked={assets.locked} />
657
705
  </TabsContent>
@@ -1,3 +1,4 @@
1
+ import { type Bsv20Balance, isListedOutput } from '@1sat/actions'
1
2
  import type { IndexedOutput } from '@1sat/types'
2
3
  import { useState } from 'react'
3
4
  import type { EnrichedOrdinal, TokenBalance } from '../lib/scanner'
@@ -53,6 +54,13 @@ function OrdinalCard({
53
54
  )}
54
55
  </div>
55
56
  <div className="min-h-[2rem] flex flex-col justify-end">
57
+ {isListedOutput(ordinal) && (
58
+ <div className="mb-1">
59
+ <span className="inline-block px-1 py-0.5 text-[9px] rounded bg-amber-500/20 text-amber-400">
60
+ listed
61
+ </span>
62
+ </div>
63
+ )}
56
64
  {subtype && (
57
65
  <div className="mb-1">
58
66
  <span className="inline-block max-w-full px-1 py-0.5 text-[9px] rounded bg-blue-500/20 text-blue-400 truncate">
@@ -388,6 +396,7 @@ function TokenRow({
388
396
  onSweep?: (tokenId: string) => void
389
397
  walletConnected: boolean
390
398
  }) {
399
+ const listed = tb.outputs.filter(isListedOutput).length
391
400
  return (
392
401
  <div
393
402
  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'}`}
@@ -404,7 +413,7 @@ function TokenRow({
404
413
  <div>
405
414
  <div className="flex items-center gap-2">
406
415
  <span className="font-medium text-foreground">
407
- {tb.symbol || tb.tokenId.slice(0, 8) + '...'}
416
+ {tb.symbol || `${tb.tokenId.slice(0, 8)}...`}
408
417
  </span>
409
418
  {tb.isActive ? (
410
419
  <span className="px-1.5 py-0.5 text-[9px] rounded bg-green-600/20 text-green-700 dark:text-green-400">
@@ -420,12 +429,13 @@ function TokenRow({
420
429
  {formatTokenAmount(tb.totalAmount.toString(), tb.decimals)}{' '}
421
430
  {tb.symbol || ''}
422
431
  <span className="ml-2">
423
- ({tb.outputs.length} output{tb.outputs.length !== 1 ? 's' : ''})
432
+ ({tb.outputs.length} output{tb.outputs.length !== 1 ? 's' : ''}
433
+ {listed ? `, ${listed} listed` : ''})
424
434
  </span>
425
435
  </div>
426
436
  </div>
427
437
  </div>
428
- {tb.isActive && onSweep && (
438
+ {onSweep && (
429
439
  <Button
430
440
  size="sm"
431
441
  onClick={() => onSweep(tb.tokenId)}
@@ -490,74 +500,69 @@ export function Bsv21Section({
490
500
  )
491
501
  }
492
502
 
493
- export function Bsv20Section({ tokens }: { tokens: IndexedOutput[] }) {
494
- if (tokens.length === 0) return null
503
+ export function Bsv20Section({
504
+ tokens,
505
+ unparsedCount = 0,
506
+ onSweep,
507
+ walletConnected,
508
+ }: {
509
+ tokens: Bsv20Balance[]
510
+ unparsedCount?: number
511
+ onSweep?: (tick: string) => void
512
+ walletConnected: boolean
513
+ }) {
514
+ if (tokens.length === 0 && unparsedCount === 0) return null
495
515
  return (
496
- <div className="border border-muted/30 bg-muted/10 p-4 rounded-lg">
497
- <div className="flex items-center gap-2 mb-2">
498
- <span className="h-2 w-2 rounded-full bg-muted-foreground" />
499
- <span className="text-sm font-semibold text-muted-foreground">
516
+ <div className="border border-teal-500/20 bg-teal-500/5 p-4 rounded-lg">
517
+ <div className="flex items-center gap-2 mb-3">
518
+ <span className="h-2 w-2 rounded-full bg-teal-500" />
519
+ <span className="text-sm font-semibold text-teal-500">
500
520
  BSV-20 Tokens
501
521
  </span>
502
522
  </div>
503
- <p className="text-xs text-muted-foreground mb-2">
504
- Cannot be swept automatically.
505
- </p>
506
- <div className="flex flex-wrap gap-2">
507
- {tokens.slice(0, 10).map((o) => {
508
- const tickEvent = o.events?.find((e) => e.startsWith('tick:'))
509
- const tick = tickEvent ? tickEvent.slice(5) : 'Token'
523
+ {unparsedCount > 0 && (
524
+ <p className="text-xs text-muted-foreground mb-3">
525
+ {unparsedCount} UTXO{unparsedCount !== 1 ? 's' : ''} with no parseable
526
+ tick/amt — leftover, not swept.
527
+ </p>
528
+ )}
529
+ <div className="space-y-3">
530
+ {tokens.map((tb) => {
531
+ const listed = tb.outputs.filter(isListedOutput).length
510
532
  return (
511
- <span
512
- key={o.outpoint}
513
- className="px-2 py-1 text-xs rounded bg-muted/30 text-muted-foreground"
533
+ <div
534
+ key={tb.tick}
535
+ className="flex items-center justify-between p-3 rounded-lg border bg-muted/30 border-teal-500/10"
514
536
  >
515
- {tick}
516
- </span>
537
+ <div>
538
+ <div className="font-medium text-foreground">{tb.tick}</div>
539
+ <div className="text-xs text-muted-foreground">
540
+ {tb.totalAmount.toString()}
541
+ <span className="ml-2">
542
+ ({tb.outputs.length} output
543
+ {tb.outputs.length !== 1 ? 's' : ''}
544
+ {listed ? `, ${listed} listed` : ''})
545
+ </span>
546
+ </div>
547
+ </div>
548
+ {onSweep && (
549
+ <Button
550
+ size="sm"
551
+ onClick={() => onSweep(tb.tick)}
552
+ disabled={!walletConnected}
553
+ title={
554
+ walletConnected
555
+ ? undefined
556
+ : 'Connect BRC-100 wallet to sweep'
557
+ }
558
+ >
559
+ Sweep to Wallet
560
+ </Button>
561
+ )}
562
+ </div>
517
563
  )
518
564
  })}
519
- {tokens.length > 10 && (
520
- <span className="text-xs text-muted-foreground">
521
- +{tokens.length - 10} more
522
- </span>
523
- )}
524
- </div>
525
- </div>
526
- )
527
- }
528
-
529
- export function ListingsSection({
530
- listings,
531
- walletConnected,
532
- onCancel,
533
- }: {
534
- listings: EnrichedOrdinal[]
535
- walletConnected?: boolean
536
- onCancel?: () => void
537
- }) {
538
- if (listings.length === 0) return null
539
- return (
540
- <div className="border border-primary/20 bg-primary/5 p-4 rounded-lg space-y-3">
541
- <div className="flex items-center gap-2">
542
- <span className="h-2 w-2 rounded-full bg-primary" />
543
- <span className="text-sm font-semibold text-primary">
544
- OrdLock Listings
545
- </span>
546
565
  </div>
547
- <p className="text-xs text-muted-foreground">
548
- {listings.length} listed UTXO{listings.length !== 1 ? 's' : ''}. These
549
- are cancelled into your BRC-100 wallet on BSV sweep.
550
- </p>
551
- {onCancel && (
552
- <Button
553
- size="sm"
554
- disabled={!walletConnected}
555
- onClick={onCancel}
556
- className="w-full"
557
- >
558
- Cancel listings into wallet
559
- </Button>
560
- )}
561
566
  </div>
562
567
  )
563
568
  }
@@ -1,3 +1,4 @@
1
+ import { isListedOutput } from '@1sat/actions'
1
2
  import { useEffect, useState } from 'react'
2
3
  import type { EnrichedOrdinal } from '../lib/scanner'
3
4
  import { getServices } from '../lib/services'
@@ -162,6 +163,11 @@ export function OpnsSection({
162
163
  <span className="text-sm text-foreground truncate">
163
164
  {displayName}
164
165
  </span>
166
+ {isListedOutput(item) && (
167
+ <span className="shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400">
168
+ listed
169
+ </span>
170
+ )}
165
171
  {!validating &&
166
172
  overlayValid.has(item.outpoint) &&
167
173
  (overlayValid.get(item.outpoint) ? (
package/src/index.ts CHANGED
@@ -9,7 +9,6 @@ export {
9
9
  OrdinalsSection,
10
10
  Bsv21Section,
11
11
  Bsv20Section,
12
- ListingsSection,
13
12
  LockedSection,
14
13
  RunSection,
15
14
  } from './components/asset-preview'
@@ -53,6 +52,7 @@ export {
53
52
  } from './lib/scanner'
54
53
  export {
55
54
  executeSweep,
55
+ sweepAllClasses,
56
56
  SWEEP_BATCH_SIZE,
57
57
  type SweepResult,
58
58
  } from './lib/sweeper'
@@ -18,10 +18,9 @@ export interface EnrichedOrdinal extends IndexedOutput {
18
18
  }
19
19
 
20
20
  export interface ScannedAssets
21
- extends Omit<ScanResult, 'ordinals' | 'opnsNames'> {
21
+ extends Omit<ScanResult, 'ordinals' | 'opnsNames' | 'listings'> {
22
22
  ordinals: EnrichedOrdinal[]
23
23
  opnsNames: EnrichedOrdinal[]
24
- listings: EnrichedOrdinal[]
25
24
  bsv21Tokens: TokenBalance[]
26
25
  totalBsv: number
27
26
  }
@@ -93,7 +92,6 @@ function toScannedAssets(result: ScanResult): ScannedAssets {
93
92
  funding: result.funding,
94
93
  ordinals: result.ordinals.map(enrichOrdinal),
95
94
  opnsNames: result.opnsNames.map(enrichOrdinal),
96
- listings: result.listings.map(enrichOrdinal),
97
95
  bsv21Tokens: enrichTokenBalances(result.bsv21Tokens),
98
96
  bsv20Tokens: result.bsv20Tokens,
99
97
  locked: result.locked,