@1sat/sweep-ui 0.0.75 → 0.0.78

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/package.json CHANGED
@@ -1,54 +1,54 @@
1
1
  {
2
- "name": "@1sat/sweep-ui",
3
- "version": "0.0.75",
4
- "description": "Sweep UI components for migrating legacy BSV assets",
5
- "type": "module",
6
- "main": "./src/index.ts",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./src/index.ts"
12
- }
13
- },
14
- "files": [
15
- "dist",
16
- "src"
17
- ],
18
- "scripts": {
19
- "build": "bun run build.ts && tsc --emitDeclarationOnly",
20
- "dev": "tsc --watch"
21
- },
22
- "keywords": [
23
- "1sat",
24
- "bsv",
25
- "ordinals",
26
- "sweep",
27
- "migration",
28
- "react"
29
- ],
30
- "license": "MIT",
31
- "dependencies": {
32
- "@1sat/actions": "0.0.187",
33
- "@1sat/client": "0.0.43",
34
- "@1sat/connect": "0.0.79",
35
- "@1sat/types": "0.0.34",
36
- "@1sat/utils": "0.0.27",
37
- "bitcoin-backup": "^0.0.11",
38
- "class-variance-authority": "^0.7.1",
39
- "clsx": "^2.1.1",
40
- "lucide-react": "^0.577.0",
41
- "sonner": "^2.0.7",
42
- "tailwind-merge": "^3.5.0"
43
- },
44
- "peerDependencies": {
45
- "@bsv/sdk": "^2.1.6",
46
- "react": "^18.0.0 || ^19.0.0"
47
- },
48
- "devDependencies": {
49
- "@bsv/sdk": "^2.1.6",
50
- "@types/react": "^19.0.0",
51
- "react": "^19.0.0",
52
- "typescript": "~5.7.0"
53
- }
2
+ "name": "@1sat/sweep-ui",
3
+ "version": "0.0.78",
4
+ "description": "Sweep UI components for migrating legacy BSV assets",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./src/index.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "scripts": {
19
+ "build": "bun run build.ts && tsc --emitDeclarationOnly",
20
+ "dev": "tsc --watch"
21
+ },
22
+ "keywords": [
23
+ "1sat",
24
+ "bsv",
25
+ "ordinals",
26
+ "sweep",
27
+ "migration",
28
+ "react"
29
+ ],
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "@1sat/actions": "0.0.191",
33
+ "@1sat/client": "0.0.46",
34
+ "@1sat/connect": "0.0.82",
35
+ "@1sat/types": "0.0.37",
36
+ "@1sat/utils": "0.0.30",
37
+ "bitcoin-backup": "^0.0.11",
38
+ "class-variance-authority": "^0.7.1",
39
+ "clsx": "^2.1.1",
40
+ "lucide-react": "^0.577.0",
41
+ "sonner": "^2.0.7",
42
+ "tailwind-merge": "^3.5.0"
43
+ },
44
+ "peerDependencies": {
45
+ "@bsv/sdk": "^2.1.9",
46
+ "react": "^18.0.0 || ^19.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@bsv/sdk": "^2.1.9",
50
+ "@types/react": "^19.0.0",
51
+ "react": "^19.0.0",
52
+ "typescript": "~5.7.0"
53
+ }
54
54
  }
@@ -7,6 +7,7 @@ import {
7
7
  legacySendOrdinals,
8
8
  } from '../lib/legacy-send'
9
9
  import {
10
+ type EnrichedOrdinal,
10
11
  type ScannedAssets,
11
12
  deriveAddress,
12
13
  scanAddresses,
@@ -122,10 +123,9 @@ export function SweepApp({
122
123
  return next
123
124
  })
124
125
  }, [])
125
- const handleSelectAllOrdinals = useCallback(() => {
126
- if (assets)
127
- setSelectedOrdinals(new Set(assets.ordinals.map((o) => o.outpoint)))
128
- }, [assets])
126
+ const handleSelectOrdinalPage = useCallback((outpoints: string[]) => {
127
+ setSelectedOrdinals(new Set(outpoints))
128
+ }, [])
129
129
  const handleDeselectAllOrdinals = useCallback(
130
130
  () => setSelectedOrdinals(new Set()),
131
131
  [],
@@ -139,10 +139,9 @@ export function SweepApp({
139
139
  return next
140
140
  })
141
141
  }, [])
142
- const handleSelectAllOpns = useCallback(() => {
143
- if (assets)
144
- setSelectedOpns(new Set(assets.opnsNames.map((o) => o.outpoint)))
145
- }, [assets])
142
+ const handleSelectOpnsPage = useCallback((outpoints: string[]) => {
143
+ setSelectedOpns(new Set(outpoints))
144
+ }, [])
146
145
  const handleDeselectAllOpns = useCallback(
147
146
  () => setSelectedOpns(new Set()),
148
147
  [],
@@ -287,28 +286,58 @@ export function SweepApp({
287
286
  [legacyKeys, assets, sweepAmount, getSelectedFunding, runOperation],
288
287
  )
289
288
 
290
- const handleSweepOrdinals = useCallback(async () => {
291
- const wallet = resolveWallet()
292
- if (!wallet || !legacyKeys || !assets) return
293
- const selected = assets.ordinals.filter((o) =>
294
- selectedOrdinals.has(o.outpoint),
295
- )
296
- if (selected.length === 0) return
297
- await runOperation(
298
- `Sweep ${selected.length} ordinal${selected.length !== 1 ? 's' : ''}`,
299
- async () => {
289
+ const sweepOrdinalList = useCallback(
290
+ async (list: EnrichedOrdinal[], label: string) => {
291
+ const wallet = resolveWallet()
292
+ if (!wallet || !legacyKeys || list.length === 0) return
293
+ await runOperation(label, async () => {
300
294
  const result = await executeSweep({
301
295
  wallet,
302
296
  keys: keyMap,
303
297
  funding: [],
304
- ordinals: selected,
298
+ ordinals: list,
305
299
  onProgress: setSweepProgress,
306
300
  })
307
- if (result.errors.length > 0) throw new Error(result.errors[0])
308
- return result.ordinalTxids[0] ?? ''
309
- },
301
+ if (result.sweptOutpoints.length > 0) {
302
+ setSelectedOrdinals((prev) => {
303
+ const next = new Set(prev)
304
+ for (const op of result.sweptOutpoints) next.delete(op)
305
+ return next
306
+ })
307
+ }
308
+ if (result.errors.length > 0) {
309
+ for (const txid of result.ordinalTxids) addTx(label, txid)
310
+ if (result.sweptOutpoints.length > 0) await refreshAssets()
311
+ throw new Error(result.errors[0])
312
+ }
313
+ for (let i = 0; i < result.ordinalTxids.length - 1; i++) {
314
+ addTx(label, result.ordinalTxids[i])
315
+ }
316
+ return result.ordinalTxids[result.ordinalTxids.length - 1] ?? ''
317
+ })
318
+ },
319
+ [resolveWallet, legacyKeys, keyMap, runOperation, addTx, refreshAssets],
320
+ )
321
+
322
+ const handleSweepOrdinals = useCallback(async () => {
323
+ if (!assets) return
324
+ const selected = assets.ordinals.filter((o) =>
325
+ selectedOrdinals.has(o.outpoint),
326
+ )
327
+ if (selected.length === 0) return
328
+ await sweepOrdinalList(
329
+ selected,
330
+ `Sweep ${selected.length} ordinal${selected.length !== 1 ? 's' : ''}`,
310
331
  )
311
- }, [resolveWallet, legacyKeys, assets, selectedOrdinals, runOperation])
332
+ }, [assets, selectedOrdinals, sweepOrdinalList])
333
+
334
+ const handleSweepAllOrdinals = useCallback(async () => {
335
+ if (!assets || assets.ordinals.length === 0) return
336
+ await sweepOrdinalList(
337
+ assets.ordinals,
338
+ `Sweep all ${assets.ordinals.length} ordinal${assets.ordinals.length !== 1 ? 's' : ''}`,
339
+ )
340
+ }, [assets, sweepOrdinalList])
312
341
 
313
342
  const handleSendOrdinals = useCallback(
314
343
  async (destination: string) => {
@@ -352,28 +381,58 @@ export function SweepApp({
352
381
  )
353
382
  }, [legacyKeys, assets, selectedOrdinals, runOperation])
354
383
 
355
- const handleSweepOpns = useCallback(async () => {
356
- const wallet = resolveWallet()
357
- if (!wallet || !legacyKeys || !assets) return
358
- const selected = assets.opnsNames.filter((o) =>
359
- selectedOpns.has(o.outpoint),
360
- )
361
- if (selected.length === 0) return
362
- await runOperation(
363
- `Sweep ${selected.length} domain${selected.length !== 1 ? 's' : ''}`,
364
- async () => {
384
+ const sweepOpnsList = useCallback(
385
+ async (list: EnrichedOrdinal[], label: string) => {
386
+ const wallet = resolveWallet()
387
+ if (!wallet || !legacyKeys || list.length === 0) return
388
+ await runOperation(label, async () => {
365
389
  const result = await executeSweep({
366
390
  wallet,
367
391
  keys: keyMap,
368
392
  funding: [],
369
- ordinals: selected,
393
+ ordinals: list,
370
394
  onProgress: setSweepProgress,
371
395
  })
372
- if (result.errors.length > 0) throw new Error(result.errors[0])
373
- return result.ordinalTxids[0] ?? ''
374
- },
396
+ if (result.sweptOutpoints.length > 0) {
397
+ setSelectedOpns((prev) => {
398
+ const next = new Set(prev)
399
+ for (const op of result.sweptOutpoints) next.delete(op)
400
+ return next
401
+ })
402
+ }
403
+ if (result.errors.length > 0) {
404
+ for (const txid of result.ordinalTxids) addTx(label, txid)
405
+ if (result.sweptOutpoints.length > 0) await refreshAssets()
406
+ throw new Error(result.errors[0])
407
+ }
408
+ for (let i = 0; i < result.ordinalTxids.length - 1; i++) {
409
+ addTx(label, result.ordinalTxids[i])
410
+ }
411
+ return result.ordinalTxids[result.ordinalTxids.length - 1] ?? ''
412
+ })
413
+ },
414
+ [resolveWallet, legacyKeys, keyMap, runOperation, addTx, refreshAssets],
415
+ )
416
+
417
+ const handleSweepOpns = useCallback(async () => {
418
+ if (!assets) return
419
+ const selected = assets.opnsNames.filter((o) =>
420
+ selectedOpns.has(o.outpoint),
421
+ )
422
+ if (selected.length === 0) return
423
+ await sweepOpnsList(
424
+ selected,
425
+ `Sweep ${selected.length} domain${selected.length !== 1 ? 's' : ''}`,
426
+ )
427
+ }, [assets, selectedOpns, sweepOpnsList])
428
+
429
+ const handleSweepAllOpns = useCallback(async () => {
430
+ if (!assets || assets.opnsNames.length === 0) return
431
+ await sweepOpnsList(
432
+ assets.opnsNames,
433
+ `Sweep all ${assets.opnsNames.length} domain${assets.opnsNames.length !== 1 ? 's' : ''}`,
375
434
  )
376
- }, [resolveWallet, legacyKeys, assets, selectedOpns, runOperation])
435
+ }, [assets, sweepOpnsList])
377
436
 
378
437
  const handleSendOpns = useCallback(
379
438
  async (destination: string) => {
@@ -512,9 +571,10 @@ export function SweepApp({
512
571
  ordinals={assets.ordinals}
513
572
  selectedOrdinals={selectedOrdinals}
514
573
  onToggle={handleToggleOrdinal}
515
- onSelectAll={handleSelectAllOrdinals}
574
+ onSelectPage={handleSelectOrdinalPage}
516
575
  onDeselectAll={handleDeselectAllOrdinals}
517
576
  onSweep={handleSweepOrdinals}
577
+ onSweepAll={handleSweepAllOrdinals}
518
578
  onSend={sweepOnly ? undefined : handleSendOrdinals}
519
579
  onBurn={sweepOnly ? undefined : handleBurnOrdinals}
520
580
  walletConnected={walletConnected}
@@ -525,9 +585,10 @@ export function SweepApp({
525
585
  opnsNames={assets.opnsNames}
526
586
  selectedOpns={selectedOpns}
527
587
  onToggle={handleToggleOpns}
528
- onSelectAll={handleSelectAllOpns}
588
+ onSelectPage={handleSelectOpnsPage}
529
589
  onDeselectAll={handleDeselectAllOpns}
530
590
  onSweep={handleSweepOpns}
591
+ onSweepAll={handleSweepAllOpns}
531
592
  onSend={sweepOnly ? undefined : handleSendOpns}
532
593
  onBurn={sweepOnly ? undefined : handleBurnOpns}
533
594
  walletConnected={walletConnected}
@@ -1,13 +1,12 @@
1
1
  import type { IndexedOutput } from '@1sat/types'
2
2
  import { useState } from 'react'
3
3
  import type { EnrichedOrdinal, TokenBalance } from '../lib/scanner'
4
+ import { SWEEP_BATCH_SIZE } from '../lib/sweeper'
4
5
  import { formatSats, formatTokenAmount } from '../lib/utils'
5
6
  import { Badge } from './ui/badge'
6
7
  import { Button } from './ui/button'
7
8
  import { Input } from './ui/input'
8
9
 
9
- const ORDINALS_PER_PAGE = 20
10
-
11
10
  function isImageType(ct: string): boolean {
12
11
  return ct.startsWith('image/') && ct !== 'image/svg+xml'
13
12
  }
@@ -200,9 +199,10 @@ export function OrdinalsSection({
200
199
  ordinals,
201
200
  selectedOrdinals,
202
201
  onToggle,
203
- onSelectAll,
202
+ onSelectPage,
204
203
  onDeselectAll,
205
204
  onSweep,
205
+ onSweepAll,
206
206
  onSend,
207
207
  onBurn,
208
208
  walletConnected,
@@ -210,9 +210,10 @@ export function OrdinalsSection({
210
210
  ordinals: EnrichedOrdinal[]
211
211
  selectedOrdinals: Set<string>
212
212
  onToggle: (outpoint: string) => void
213
- onSelectAll: () => void
213
+ onSelectPage: (outpoints: string[]) => void
214
214
  onDeselectAll: () => void
215
215
  onSweep: () => void
216
+ onSweepAll: () => void
216
217
  onSend?: (destination: string) => void
217
218
  onBurn?: () => void
218
219
  walletConnected: boolean
@@ -220,9 +221,13 @@ export function OrdinalsSection({
220
221
  const [page, setPage] = useState(0)
221
222
  const [address, setAddress] = useState('')
222
223
  if (ordinals.length === 0) return null
223
- const totalPages = Math.ceil(ordinals.length / ORDINALS_PER_PAGE)
224
- const start = page * ORDINALS_PER_PAGE
225
- const pageItems = ordinals.slice(start, start + ORDINALS_PER_PAGE)
224
+ const totalPages = Math.ceil(ordinals.length / SWEEP_BATCH_SIZE)
225
+ const safePage = Math.min(page, totalPages - 1)
226
+ const start = safePage * SWEEP_BATCH_SIZE
227
+ const pageItems = ordinals.slice(start, start + SWEEP_BATCH_SIZE)
228
+ const pageOutpoints = pageItems.map((o) => o.outpoint)
229
+ const selectedCount = selectedOrdinals.size
230
+ const batchCount = Math.ceil(ordinals.length / SWEEP_BATCH_SIZE)
226
231
 
227
232
  return (
228
233
  <div className="border border-blue-500/20 bg-blue-500/5 p-4 rounded-lg">
@@ -236,11 +241,14 @@ export function OrdinalsSection({
236
241
  </div>
237
242
  <div className="text-xs text-muted-foreground">
238
243
  {ordinals.length} inscription{ordinals.length !== 1 ? 's' : ''}
239
- {selectedOrdinals.size > 0 && (
244
+ {selectedCount > 0 && (
240
245
  <span className="text-blue-400 ml-1">
241
- ({selectedOrdinals.size} selected)
246
+ ({selectedCount} selected)
242
247
  </span>
243
248
  )}
249
+ <span className="ml-1 text-muted-foreground/80">
250
+ · {SWEEP_BATCH_SIZE}/page
251
+ </span>
244
252
  </div>
245
253
  </div>
246
254
  <div className="flex gap-2">
@@ -248,16 +256,16 @@ export function OrdinalsSection({
248
256
  variant="outline"
249
257
  size="sm"
250
258
  className="h-7 text-[11px]"
251
- onClick={onSelectAll}
259
+ onClick={() => onSelectPage(pageOutpoints)}
252
260
  >
253
- Select All
261
+ Select page
254
262
  </Button>
255
263
  <Button
256
264
  variant="outline"
257
265
  size="sm"
258
266
  className="h-7 text-[11px]"
259
267
  onClick={onDeselectAll}
260
- disabled={selectedOrdinals.size === 0}
268
+ disabled={selectedCount === 0}
261
269
  >
262
270
  Deselect
263
271
  </Button>
@@ -279,79 +287,94 @@ export function OrdinalsSection({
279
287
  variant="ghost"
280
288
  size="sm"
281
289
  className="text-xs"
282
- onClick={() => setPage(page - 1)}
283
- disabled={page === 0}
290
+ onClick={() => setPage(safePage - 1)}
291
+ disabled={safePage === 0}
284
292
  >
285
293
  Prev
286
294
  </Button>
287
295
  <span className="text-xs text-muted-foreground">
288
- Page {page + 1} of {totalPages}
296
+ Page {safePage + 1} of {totalPages}
289
297
  </span>
290
298
  <Button
291
299
  variant="ghost"
292
300
  size="sm"
293
301
  className="text-xs"
294
- onClick={() => setPage(page + 1)}
295
- disabled={page >= totalPages - 1}
302
+ onClick={() => setPage(safePage + 1)}
303
+ disabled={safePage >= totalPages - 1}
296
304
  >
297
305
  Next
298
306
  </Button>
299
307
  </div>
300
308
  )}
301
- {selectedOrdinals.size > 0 && (
302
- <div className="mt-3 space-y-2">
303
- {onSend && (
304
- <Input
305
- type="text"
306
- placeholder="Destination address..."
307
- value={address}
308
- onChange={(e) => setAddress(e.target.value)}
309
- className="font-mono text-xs"
310
- />
309
+ <div className="mt-3 space-y-2">
310
+ {selectedCount > 0 && onSend && (
311
+ <Input
312
+ type="text"
313
+ placeholder="Destination address..."
314
+ value={address}
315
+ onChange={(e) => setAddress(e.target.value)}
316
+ className="font-mono text-xs"
317
+ />
318
+ )}
319
+ <div className="flex flex-wrap gap-2">
320
+ {selectedCount > 0 && onSend && (
321
+ <Button
322
+ variant="outline"
323
+ size="sm"
324
+ className="flex-1 min-w-[6rem]"
325
+ disabled={!address.trim()}
326
+ onClick={() => onSend(address.trim())}
327
+ >
328
+ Send {selectedCount}
329
+ </Button>
311
330
  )}
312
- <div className="flex gap-2">
313
- {onSend && (
314
- <Button
315
- variant="outline"
316
- size="sm"
317
- className="flex-1"
318
- disabled={!address.trim()}
319
- onClick={() => onSend(address.trim())}
320
- >
321
- Send {selectedOrdinals.size} Ordinal
322
- {selectedOrdinals.size !== 1 ? 's' : ''}
323
- </Button>
324
- )}
331
+ {selectedCount > 0 && (
325
332
  <Button
326
333
  size="sm"
327
- className="flex-1"
334
+ className="flex-1 min-w-[6rem]"
328
335
  onClick={onSweep}
329
336
  disabled={!walletConnected}
330
337
  title={
331
338
  walletConnected ? undefined : 'Connect BRC-100 wallet to sweep'
332
339
  }
333
340
  >
334
- Sweep to Wallet
341
+ Sweep {selectedCount}
342
+ {selectedCount > SWEEP_BATCH_SIZE
343
+ ? ` (${Math.ceil(selectedCount / SWEEP_BATCH_SIZE)} batches)`
344
+ : ''}
335
345
  </Button>
336
- {onBurn && (
337
- <Button
338
- size="sm"
339
- className="bg-red-600 hover:bg-red-700 text-white"
340
- onClick={() => {
341
- if (
342
- window.confirm(
343
- `Permanently burn ${selectedOrdinals.size} ordinal${selectedOrdinals.size !== 1 ? 's' : ''}? This cannot be undone.`,
344
- )
346
+ )}
347
+ <Button
348
+ variant={selectedCount > 0 ? 'outline' : 'default'}
349
+ size="sm"
350
+ className="flex-1 min-w-[6rem]"
351
+ onClick={onSweepAll}
352
+ disabled={!walletConnected}
353
+ title={
354
+ walletConnected ? undefined : 'Connect BRC-100 wallet to sweep'
355
+ }
356
+ >
357
+ Sweep all
358
+ {batchCount > 1 ? ` (${batchCount} batches)` : ''}
359
+ </Button>
360
+ {selectedCount > 0 && onBurn && (
361
+ <Button
362
+ size="sm"
363
+ className="bg-red-600 hover:bg-red-700 text-white"
364
+ onClick={() => {
365
+ if (
366
+ window.confirm(
367
+ `Permanently burn ${selectedCount} ordinal${selectedCount !== 1 ? 's' : ''}? This cannot be undone.`,
345
368
  )
346
- onBurn()
347
- }}
348
- >
349
- Burn
350
- </Button>
351
- )}
352
- </div>
369
+ )
370
+ onBurn()
371
+ }}
372
+ >
373
+ Burn
374
+ </Button>
375
+ )}
353
376
  </div>
354
- )}
377
+ </div>
355
378
  </div>
356
379
  )
357
380
  }