@1sat/sweep-ui 0.0.19 → 0.0.20

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.
Files changed (60) hide show
  1. package/dist/components/SweepApp.d.ts +3 -3
  2. package/dist/components/SweepApp.d.ts.map +1 -1
  3. package/dist/components/asset-preview.d.ts +5 -5
  4. package/dist/components/asset-preview.d.ts.map +1 -1
  5. package/dist/components/connect-wallet.d.ts +1 -1
  6. package/dist/components/connect-wallet.d.ts.map +1 -1
  7. package/dist/components/opns-section.d.ts +2 -2
  8. package/dist/components/opns-section.d.ts.map +1 -1
  9. package/dist/components/sweep-progress.d.ts +1 -1
  10. package/dist/components/sweep-progress.d.ts.map +1 -1
  11. package/dist/components/tx-history.d.ts.map +1 -1
  12. package/dist/components/ui/badge.d.ts +3 -3
  13. package/dist/components/ui/badge.d.ts.map +1 -1
  14. package/dist/components/ui/button.d.ts +3 -3
  15. package/dist/components/ui/button.d.ts.map +1 -1
  16. package/dist/components/ui/card.d.ts +9 -9
  17. package/dist/components/ui/card.d.ts.map +1 -1
  18. package/dist/components/ui/input.d.ts +2 -2
  19. package/dist/components/ui/input.d.ts.map +1 -1
  20. package/dist/components/ui/tabs.d.ts +5 -5
  21. package/dist/components/ui/tabs.d.ts.map +1 -1
  22. package/dist/components/wif-input.d.ts +1 -1
  23. package/dist/components/wif-input.d.ts.map +1 -1
  24. package/dist/index.d.ts +19 -19
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1911 -1757
  27. package/dist/lib/legacy-send.d.ts +2 -2
  28. package/dist/lib/legacy-send.d.ts.map +1 -1
  29. package/dist/lib/scanner.d.ts +3 -3
  30. package/dist/lib/scanner.d.ts.map +1 -1
  31. package/dist/lib/services.d.ts +1 -1
  32. package/dist/lib/services.d.ts.map +1 -1
  33. package/dist/lib/sweeper.d.ts +3 -3
  34. package/dist/lib/sweeper.d.ts.map +1 -1
  35. package/dist/lib/utils.d.ts +1 -1
  36. package/dist/lib/utils.d.ts.map +1 -1
  37. package/dist/lib/wallet.d.ts +2 -2
  38. package/dist/lib/wallet.d.ts.map +1 -1
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +53 -44
  41. package/src/components/SweepApp.tsx +480 -222
  42. package/src/components/asset-preview.tsx +380 -97
  43. package/src/components/connect-wallet.tsx +50 -25
  44. package/src/components/opns-section.tsx +167 -60
  45. package/src/components/sweep-progress.tsx +40 -17
  46. package/src/components/tx-history.tsx +30 -17
  47. package/src/components/ui/badge.tsx +17 -14
  48. package/src/components/ui/button.tsx +26 -22
  49. package/src/components/ui/card.tsx +76 -17
  50. package/src/components/ui/input.tsx +7 -7
  51. package/src/components/ui/tabs.tsx +51 -12
  52. package/src/components/wif-input.tsx +243 -135
  53. package/src/index.ts +54 -19
  54. package/src/lib/legacy-send.ts +110 -106
  55. package/src/lib/scanner.ts +45 -40
  56. package/src/lib/services.ts +11 -9
  57. package/src/lib/sweeper.ts +67 -54
  58. package/src/lib/utils.ts +11 -11
  59. package/src/lib/wallet.ts +16 -13
  60. package/src/types.ts +3 -3
@@ -1,105 +1,224 @@
1
- import { useState } from "react";
2
- import { Badge } from "./ui/badge";
3
- import { Button } from "./ui/button";
4
- import { Input } from "./ui/input";
5
- import { formatSats, formatTokenAmount } from "../lib/utils";
6
- import type { EnrichedOrdinal, TokenBalance } from "../lib/scanner";
7
- import type { IndexedOutput } from "@1sat/types";
1
+ import type { IndexedOutput } from '@1sat/types'
2
+ import { useState } from 'react'
3
+ import type { EnrichedOrdinal, TokenBalance } from '../lib/scanner'
4
+ import { formatSats, formatTokenAmount } from '../lib/utils'
5
+ import { Badge } from './ui/badge'
6
+ import { Button } from './ui/button'
7
+ import { Input } from './ui/input'
8
8
 
9
- const ORDINALS_PER_PAGE = 20;
9
+ const ORDINALS_PER_PAGE = 20
10
10
 
11
11
  function isImageType(ct: string): boolean {
12
- return ct.startsWith("image/") && ct !== "image/svg+xml";
12
+ return ct.startsWith('image/') && ct !== 'image/svg+xml'
13
13
  }
14
14
 
15
- function OrdinalCard({ ordinal, isSelected, onToggle }: { ordinal: EnrichedOrdinal; isSelected: boolean; onToggle: () => void }) {
16
- const ct = ordinal.contentType ?? "";
17
- const isImage = isImageType(ct);
18
- const subtype = ct.includes("/") ? ct.split("/")[1] : ct;
15
+ function OrdinalCard({
16
+ ordinal,
17
+ isSelected,
18
+ onToggle,
19
+ }: { ordinal: EnrichedOrdinal; isSelected: boolean; onToggle: () => void }) {
20
+ const ct = ordinal.contentType ?? ''
21
+ const isImage = isImageType(ct)
22
+ const subtype = ct.includes('/') ? ct.split('/')[1] : ct
19
23
 
20
24
  return (
21
25
  <div
22
- className={`relative p-2 rounded-lg border cursor-pointer transition-all ${isSelected ? "border-blue-500 bg-blue-500/10 ring-1 ring-blue-500/30" : "border-border/50 hover:border-border bg-black/20"}`}
26
+ className={`relative p-2 rounded-lg border cursor-pointer transition-all ${isSelected ? 'border-blue-500 bg-blue-500/10 ring-1 ring-blue-500/30' : 'border-border/50 hover:border-border bg-black/20'}`}
23
27
  onClick={onToggle}
24
28
  >
25
29
  <div className="absolute top-1.5 right-1.5 z-10">
26
- <div className={`w-4 h-4 rounded border-2 flex items-center justify-center text-[10px] ${isSelected ? "bg-blue-500 border-blue-500 text-white" : "border-muted-foreground/40"}`}>
27
- {isSelected && "\u2713"}
30
+ <div
31
+ className={`w-4 h-4 rounded border-2 flex items-center justify-center text-[10px] ${isSelected ? 'bg-blue-500 border-blue-500 text-white' : 'border-muted-foreground/40'}`}
32
+ >
33
+ {isSelected && '\u2713'}
28
34
  </div>
29
35
  </div>
30
36
  <div className="w-full aspect-square mb-1.5 rounded overflow-hidden bg-black/30 flex items-center justify-center">
31
37
  {!ordinal.contentUrl ? (
32
- <span className="text-muted-foreground text-lg">{"\u25C6"}</span>
38
+ <span className="text-muted-foreground text-lg">{'\u25C6'}</span>
33
39
  ) : isImage ? (
34
- <img src={ordinal.contentUrl} alt={ordinal.name || "Ordinal"} className="w-full h-full object-cover" loading="lazy" />
40
+ <img
41
+ src={ordinal.contentUrl}
42
+ alt={ordinal.name || 'Ordinal'}
43
+ className="w-full h-full object-cover"
44
+ loading="lazy"
45
+ />
35
46
  ) : (
36
- <iframe src={ordinal.contentUrl} title={ordinal.name || "Ordinal"} className="w-full h-full border-0 pointer-events-none" sandbox="allow-scripts" loading="lazy" />
47
+ <iframe
48
+ src={ordinal.contentUrl}
49
+ title={ordinal.name || 'Ordinal'}
50
+ className="w-full h-full border-0 pointer-events-none"
51
+ sandbox="allow-scripts"
52
+ loading="lazy"
53
+ />
37
54
  )}
38
55
  </div>
39
56
  {subtype && (
40
57
  <div className="mb-1">
41
- <span className="px-1 py-0.5 text-[9px] rounded bg-blue-500/20 text-blue-400 truncate">{subtype}</span>
58
+ <span className="px-1 py-0.5 text-[9px] rounded bg-blue-500/20 text-blue-400 truncate">
59
+ {subtype}
60
+ </span>
42
61
  </div>
43
62
  )}
44
63
  {ordinal.name ? (
45
- <a href={ordinal.contentUrl} target="_blank" rel="noopener noreferrer" className="text-[10px] text-foreground truncate font-medium hover:text-blue-400" title={ordinal.name}>{ordinal.name}</a>
64
+ <a
65
+ href={ordinal.contentUrl}
66
+ target="_blank"
67
+ rel="noopener noreferrer"
68
+ className="text-[10px] text-foreground truncate font-medium hover:text-blue-400"
69
+ title={ordinal.name}
70
+ >
71
+ {ordinal.name}
72
+ </a>
46
73
  ) : (
47
- <a href={ordinal.contentUrl} target="_blank" rel="noopener noreferrer" className="text-[9px] text-muted-foreground truncate font-mono hover:text-blue-400">{ordinal.outpoint.substring(0, 8)}...</a>
74
+ <a
75
+ href={ordinal.contentUrl}
76
+ target="_blank"
77
+ rel="noopener noreferrer"
78
+ className="text-[9px] text-muted-foreground truncate font-mono hover:text-blue-400"
79
+ >
80
+ {ordinal.outpoint.substring(0, 8)}...
81
+ </a>
48
82
  )}
49
83
  </div>
50
- );
84
+ )
51
85
  }
52
86
 
53
- export function FundingSection({ funding, totalBsv, sweepAmount, onSweepAmountChange, onSweep, onSend, walletConnected }: {
54
- funding: IndexedOutput[]; totalBsv: number; sweepAmount: number | null; onSweepAmountChange: (amount: number | null) => void; onSweep: () => void; onSend?: (destination: string) => void; walletConnected: boolean;
87
+ export function FundingSection({
88
+ funding,
89
+ totalBsv,
90
+ sweepAmount,
91
+ onSweepAmountChange,
92
+ onSweep,
93
+ onSend,
94
+ walletConnected,
95
+ }: {
96
+ funding: IndexedOutput[]
97
+ totalBsv: number
98
+ sweepAmount: number | null
99
+ onSweepAmountChange: (amount: number | null) => void
100
+ onSweep: () => void
101
+ onSend?: (destination: string) => void
102
+ walletConnected: boolean
55
103
  }) {
56
- const [address, setAddress] = useState("");
57
- if (funding.length === 0) return null;
58
- const isMax = sweepAmount === null;
59
- const displayAmount = isMax ? formatSats(totalBsv) : formatSats(sweepAmount!);
104
+ const [address, setAddress] = useState('')
105
+ if (funding.length === 0) return null
106
+ const isMax = sweepAmount === null
107
+ const displayAmount = isMax ? formatSats(totalBsv) : formatSats(sweepAmount!)
60
108
 
61
109
  return (
62
110
  <div className="border border-green-500/20 bg-green-500/5 p-4 rounded-lg">
63
111
  <div className="flex items-center gap-2 mb-2">
64
112
  <span className="h-2 w-2 rounded-full bg-green-500" />
65
- <span className="text-sm font-semibold text-green-500">BSV Funding</span>
113
+ <span className="text-sm font-semibold text-green-500">
114
+ BSV Funding
115
+ </span>
66
116
  </div>
67
117
  <div className="flex items-baseline justify-between mb-3">
68
118
  <div>
69
- <div className="text-2xl font-bold text-green-500">{formatSats(totalBsv)} sats</div>
70
- <div className="text-xs text-muted-foreground">{(totalBsv / 100_000_000).toFixed(8)} BSV</div>
119
+ <div className="text-2xl font-bold text-green-500">
120
+ {formatSats(totalBsv)} sats
121
+ </div>
122
+ <div className="text-xs text-muted-foreground">
123
+ {(totalBsv / 100_000_000).toFixed(8)} BSV
124
+ </div>
71
125
  </div>
72
- <Badge variant="secondary">{funding.length} UTXO{funding.length !== 1 ? "s" : ""}</Badge>
126
+ <Badge variant="secondary">
127
+ {funding.length} UTXO{funding.length !== 1 ? 's' : ''}
128
+ </Badge>
73
129
  </div>
74
130
  <div className="flex items-center gap-2">
75
- <Input type="number" min={0} max={totalBsv} placeholder="Max" value={isMax ? "" : sweepAmount} onChange={(e) => { const val = e.target.value; onSweepAmountChange(val === "" ? null : Math.max(0, Math.min(totalBsv, Number(val)))); }} className="flex-1 font-mono" />
131
+ <Input
132
+ type="number"
133
+ min={0}
134
+ max={totalBsv}
135
+ placeholder="Max"
136
+ value={isMax ? '' : sweepAmount}
137
+ onChange={(e) => {
138
+ const val = e.target.value
139
+ onSweepAmountChange(
140
+ val === '' ? null : Math.max(0, Math.min(totalBsv, Number(val))),
141
+ )
142
+ }}
143
+ className="flex-1 font-mono"
144
+ />
76
145
  <span className="text-xs text-muted-foreground">sats</span>
77
- <Button variant="outline" size="sm" className="h-9 text-xs" onClick={() => onSweepAmountChange(null)} disabled={isMax}>Max</Button>
146
+ <Button
147
+ variant="outline"
148
+ size="sm"
149
+ className="h-9 text-xs"
150
+ onClick={() => onSweepAmountChange(null)}
151
+ disabled={isMax}
152
+ >
153
+ Max
154
+ </Button>
78
155
  </div>
79
156
  <div className="mt-3 space-y-2">
80
157
  {onSend && (
81
- <Input type="text" placeholder="Destination address..." value={address} onChange={(e) => setAddress(e.target.value)} className="font-mono text-xs" />
158
+ <Input
159
+ type="text"
160
+ placeholder="Destination address..."
161
+ value={address}
162
+ onChange={(e) => setAddress(e.target.value)}
163
+ className="font-mono text-xs"
164
+ />
82
165
  )}
83
166
  <div className="flex gap-2">
84
167
  {onSend && (
85
- <Button variant="outline" size="sm" className="flex-1" disabled={!address.trim()} onClick={() => onSend(address.trim())}>Send {displayAmount} sats</Button>
168
+ <Button
169
+ variant="outline"
170
+ size="sm"
171
+ className="flex-1"
172
+ disabled={!address.trim()}
173
+ onClick={() => onSend(address.trim())}
174
+ >
175
+ Send {displayAmount} sats
176
+ </Button>
86
177
  )}
87
- <Button size="sm" className="flex-1" onClick={onSweep} disabled={!walletConnected} title={walletConnected ? undefined : "Connect BRC-100 wallet to sweep"}>Sweep to Wallet</Button>
178
+ <Button
179
+ size="sm"
180
+ className="flex-1"
181
+ onClick={onSweep}
182
+ disabled={!walletConnected}
183
+ title={
184
+ walletConnected ? undefined : 'Connect BRC-100 wallet to sweep'
185
+ }
186
+ >
187
+ Sweep to Wallet
188
+ </Button>
88
189
  </div>
89
190
  </div>
90
191
  </div>
91
- );
192
+ )
92
193
  }
93
194
 
94
- export function OrdinalsSection({ ordinals, selectedOrdinals, onToggle, onSelectAll, onDeselectAll, onSweep, onSend, onBurn, walletConnected }: {
95
- ordinals: EnrichedOrdinal[]; selectedOrdinals: Set<string>; onToggle: (outpoint: string) => void; onSelectAll: () => void; onDeselectAll: () => void; onSweep: () => void; onSend?: (destination: string) => void; onBurn?: () => void; walletConnected: boolean;
195
+ export function OrdinalsSection({
196
+ ordinals,
197
+ selectedOrdinals,
198
+ onToggle,
199
+ onSelectAll,
200
+ onDeselectAll,
201
+ onSweep,
202
+ onSend,
203
+ onBurn,
204
+ walletConnected,
205
+ }: {
206
+ ordinals: EnrichedOrdinal[]
207
+ selectedOrdinals: Set<string>
208
+ onToggle: (outpoint: string) => void
209
+ onSelectAll: () => void
210
+ onDeselectAll: () => void
211
+ onSweep: () => void
212
+ onSend?: (destination: string) => void
213
+ onBurn?: () => void
214
+ walletConnected: boolean
96
215
  }) {
97
- const [page, setPage] = useState(0);
98
- const [address, setAddress] = useState("");
99
- if (ordinals.length === 0) return null;
100
- const totalPages = Math.ceil(ordinals.length / ORDINALS_PER_PAGE);
101
- const start = page * ORDINALS_PER_PAGE;
102
- const pageItems = ordinals.slice(start, start + ORDINALS_PER_PAGE);
216
+ const [page, setPage] = useState(0)
217
+ const [address, setAddress] = useState('')
218
+ if (ordinals.length === 0) return null
219
+ const totalPages = Math.ceil(ordinals.length / ORDINALS_PER_PAGE)
220
+ const start = page * ORDINALS_PER_PAGE
221
+ const pageItems = ordinals.slice(start, start + ORDINALS_PER_PAGE)
103
222
 
104
223
  return (
105
224
  <div className="border border-blue-500/20 bg-blue-500/5 p-4 rounded-lg">
@@ -107,149 +226,313 @@ export function OrdinalsSection({ ordinals, selectedOrdinals, onToggle, onSelect
107
226
  <div>
108
227
  <div className="flex items-center gap-2 mb-1">
109
228
  <span className="h-2 w-2 rounded-full bg-blue-500" />
110
- <span className="text-sm font-semibold text-blue-500">Ordinals</span>
229
+ <span className="text-sm font-semibold text-blue-500">
230
+ Ordinals
231
+ </span>
111
232
  </div>
112
233
  <div className="text-xs text-muted-foreground">
113
- {ordinals.length} inscription{ordinals.length !== 1 ? "s" : ""}
114
- {selectedOrdinals.size > 0 && <span className="text-blue-400 ml-1">({selectedOrdinals.size} selected)</span>}
234
+ {ordinals.length} inscription{ordinals.length !== 1 ? 's' : ''}
235
+ {selectedOrdinals.size > 0 && (
236
+ <span className="text-blue-400 ml-1">
237
+ ({selectedOrdinals.size} selected)
238
+ </span>
239
+ )}
115
240
  </div>
116
241
  </div>
117
242
  <div className="flex gap-2">
118
- <Button variant="outline" size="sm" className="h-7 text-[11px]" onClick={onSelectAll}>Select All</Button>
119
- <Button variant="outline" size="sm" className="h-7 text-[11px]" onClick={onDeselectAll} disabled={selectedOrdinals.size === 0}>Deselect</Button>
243
+ <Button
244
+ variant="outline"
245
+ size="sm"
246
+ className="h-7 text-[11px]"
247
+ onClick={onSelectAll}
248
+ >
249
+ Select All
250
+ </Button>
251
+ <Button
252
+ variant="outline"
253
+ size="sm"
254
+ className="h-7 text-[11px]"
255
+ onClick={onDeselectAll}
256
+ disabled={selectedOrdinals.size === 0}
257
+ >
258
+ Deselect
259
+ </Button>
120
260
  </div>
121
261
  </div>
122
262
  <div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-2 mb-3">
123
- {pageItems.map((ord) => (<OrdinalCard key={ord.outpoint} ordinal={ord} isSelected={selectedOrdinals.has(ord.outpoint)} onToggle={() => onToggle(ord.outpoint)} />))}
263
+ {pageItems.map((ord) => (
264
+ <OrdinalCard
265
+ key={ord.outpoint}
266
+ ordinal={ord}
267
+ isSelected={selectedOrdinals.has(ord.outpoint)}
268
+ onToggle={() => onToggle(ord.outpoint)}
269
+ />
270
+ ))}
124
271
  </div>
125
272
  {totalPages > 1 && (
126
273
  <div className="flex items-center justify-center gap-4">
127
- <Button variant="ghost" size="sm" className="text-xs" onClick={() => setPage(page - 1)} disabled={page === 0}>Prev</Button>
128
- <span className="text-xs text-muted-foreground">Page {page + 1} of {totalPages}</span>
129
- <Button variant="ghost" size="sm" className="text-xs" onClick={() => setPage(page + 1)} disabled={page >= totalPages - 1}>Next</Button>
274
+ <Button
275
+ variant="ghost"
276
+ size="sm"
277
+ className="text-xs"
278
+ onClick={() => setPage(page - 1)}
279
+ disabled={page === 0}
280
+ >
281
+ Prev
282
+ </Button>
283
+ <span className="text-xs text-muted-foreground">
284
+ Page {page + 1} of {totalPages}
285
+ </span>
286
+ <Button
287
+ variant="ghost"
288
+ size="sm"
289
+ className="text-xs"
290
+ onClick={() => setPage(page + 1)}
291
+ disabled={page >= totalPages - 1}
292
+ >
293
+ Next
294
+ </Button>
130
295
  </div>
131
296
  )}
132
297
  {selectedOrdinals.size > 0 && (
133
298
  <div className="mt-3 space-y-2">
134
299
  {onSend && (
135
- <Input type="text" placeholder="Destination address..." value={address} onChange={(e) => setAddress(e.target.value)} className="font-mono text-xs" />
300
+ <Input
301
+ type="text"
302
+ placeholder="Destination address..."
303
+ value={address}
304
+ onChange={(e) => setAddress(e.target.value)}
305
+ className="font-mono text-xs"
306
+ />
136
307
  )}
137
308
  <div className="flex gap-2">
138
309
  {onSend && (
139
- <Button variant="outline" size="sm" className="flex-1" disabled={!address.trim()} onClick={() => onSend(address.trim())}>Send {selectedOrdinals.size} Ordinal{selectedOrdinals.size !== 1 ? "s" : ""}</Button>
310
+ <Button
311
+ variant="outline"
312
+ size="sm"
313
+ className="flex-1"
314
+ disabled={!address.trim()}
315
+ onClick={() => onSend(address.trim())}
316
+ >
317
+ Send {selectedOrdinals.size} Ordinal
318
+ {selectedOrdinals.size !== 1 ? 's' : ''}
319
+ </Button>
140
320
  )}
141
- <Button size="sm" className="flex-1" onClick={onSweep} disabled={!walletConnected} title={walletConnected ? undefined : "Connect BRC-100 wallet to sweep"}>Sweep to Wallet</Button>
321
+ <Button
322
+ size="sm"
323
+ className="flex-1"
324
+ onClick={onSweep}
325
+ disabled={!walletConnected}
326
+ title={
327
+ walletConnected ? undefined : 'Connect BRC-100 wallet to sweep'
328
+ }
329
+ >
330
+ Sweep to Wallet
331
+ </Button>
142
332
  {onBurn && (
143
- <Button size="sm" className="bg-red-600 hover:bg-red-700 text-white" onClick={() => { if (window.confirm(`Permanently burn ${selectedOrdinals.size} ordinal${selectedOrdinals.size !== 1 ? "s" : ""}? This cannot be undone.`)) onBurn(); }}>Burn</Button>
333
+ <Button
334
+ size="sm"
335
+ className="bg-red-600 hover:bg-red-700 text-white"
336
+ onClick={() => {
337
+ if (
338
+ window.confirm(
339
+ `Permanently burn ${selectedOrdinals.size} ordinal${selectedOrdinals.size !== 1 ? 's' : ''}? This cannot be undone.`,
340
+ )
341
+ )
342
+ onBurn()
343
+ }}
344
+ >
345
+ Burn
346
+ </Button>
144
347
  )}
145
348
  </div>
146
349
  </div>
147
350
  )}
148
351
  </div>
149
- );
352
+ )
150
353
  }
151
354
 
152
- function TokenRow({ tb, onSweep, walletConnected }: { tb: TokenBalance; onSweep?: (tokenId: string) => void; walletConnected: boolean }) {
355
+ function TokenRow({
356
+ tb,
357
+ onSweep,
358
+ walletConnected,
359
+ }: {
360
+ tb: TokenBalance
361
+ onSweep?: (tokenId: string) => void
362
+ walletConnected: boolean
363
+ }) {
153
364
  return (
154
- <div className={`flex items-center justify-between p-3 rounded-lg border ${tb.isActive ? "bg-black/20 border-purple-500/10" : "bg-black/10 border-muted/20 opacity-60"}`}>
365
+ <div
366
+ className={`flex items-center justify-between p-3 rounded-lg border ${tb.isActive ? 'bg-black/20 border-purple-500/10' : 'bg-black/10 border-muted/20 opacity-60'}`}
367
+ >
155
368
  <div className="flex items-center gap-3">
156
- <img src={tb.icon} alt={tb.symbol || "Token"} className="w-8 h-8 rounded-full object-cover" onError={(e) => { (e.target as HTMLImageElement).style.display = "none"; }} />
369
+ <img
370
+ src={tb.icon}
371
+ alt={tb.symbol || 'Token'}
372
+ className="w-8 h-8 rounded-full object-cover"
373
+ onError={(e) => {
374
+ ;(e.target as HTMLImageElement).style.display = 'none'
375
+ }}
376
+ />
157
377
  <div>
158
378
  <div className="flex items-center gap-2">
159
- <span className="font-medium text-foreground">{tb.symbol || tb.tokenId.slice(0, 8) + "..."}</span>
379
+ <span className="font-medium text-foreground">
380
+ {tb.symbol || tb.tokenId.slice(0, 8) + '...'}
381
+ </span>
160
382
  {tb.isActive ? (
161
- <span className="px-1.5 py-0.5 text-[9px] rounded bg-green-600/20 text-green-700 dark:text-green-400">active</span>
383
+ <span className="px-1.5 py-0.5 text-[9px] rounded bg-green-600/20 text-green-700 dark:text-green-400">
384
+ active
385
+ </span>
162
386
  ) : (
163
- <span className="px-1.5 py-0.5 text-[9px] rounded bg-muted text-muted-foreground">inactive</span>
387
+ <span className="px-1.5 py-0.5 text-[9px] rounded bg-muted text-muted-foreground">
388
+ inactive
389
+ </span>
164
390
  )}
165
391
  </div>
166
392
  <div className="text-xs text-muted-foreground">
167
- {formatTokenAmount(tb.totalAmount.toString(), tb.decimals)} {tb.symbol || ""}
168
- <span className="ml-2">({tb.outputs.length} output{tb.outputs.length !== 1 ? "s" : ""})</span>
393
+ {formatTokenAmount(tb.totalAmount.toString(), tb.decimals)}{' '}
394
+ {tb.symbol || ''}
395
+ <span className="ml-2">
396
+ ({tb.outputs.length} output{tb.outputs.length !== 1 ? 's' : ''})
397
+ </span>
169
398
  </div>
170
399
  </div>
171
400
  </div>
172
401
  {tb.isActive && onSweep && (
173
- <Button size="sm" onClick={() => onSweep(tb.tokenId)} disabled={!walletConnected} title={walletConnected ? undefined : "Connect BRC-100 wallet to sweep"}>
402
+ <Button
403
+ size="sm"
404
+ onClick={() => onSweep(tb.tokenId)}
405
+ disabled={!walletConnected}
406
+ title={
407
+ walletConnected ? undefined : 'Connect BRC-100 wallet to sweep'
408
+ }
409
+ >
174
410
  Sweep to Wallet
175
411
  </Button>
176
412
  )}
177
413
  </div>
178
- );
414
+ )
179
415
  }
180
416
 
181
- export function Bsv21Section({ tokens, onSweep, walletConnected }: { tokens: TokenBalance[]; onSweep?: (tokenId: string) => void; walletConnected: boolean }) {
182
- if (tokens.length === 0) return null;
183
- const active = tokens.filter((t) => t.isActive);
184
- const inactive = tokens.filter((t) => !t.isActive);
417
+ export function Bsv21Section({
418
+ tokens,
419
+ onSweep,
420
+ walletConnected,
421
+ }: {
422
+ tokens: TokenBalance[]
423
+ onSweep?: (tokenId: string) => void
424
+ walletConnected: boolean
425
+ }) {
426
+ if (tokens.length === 0) return null
427
+ const active = tokens.filter((t) => t.isActive)
428
+ const inactive = tokens.filter((t) => !t.isActive)
185
429
 
186
430
  return (
187
431
  <div className="border border-purple-500/20 bg-purple-500/5 p-4 rounded-lg">
188
432
  <div className="flex items-center gap-2 mb-3">
189
433
  <span className="h-2 w-2 rounded-full bg-purple-500" />
190
- <span className="text-sm font-semibold text-purple-500">BSV-21 Tokens</span>
434
+ <span className="text-sm font-semibold text-purple-500">
435
+ BSV-21 Tokens
436
+ </span>
191
437
  </div>
192
438
  <div className="space-y-3">
193
- {active.map((tb) => (<TokenRow key={tb.tokenId} tb={tb} onSweep={onSweep} walletConnected={walletConnected} />))}
439
+ {active.map((tb) => (
440
+ <TokenRow
441
+ key={tb.tokenId}
442
+ tb={tb}
443
+ onSweep={onSweep}
444
+ walletConnected={walletConnected}
445
+ />
446
+ ))}
194
447
  {inactive.length > 0 && active.length > 0 && (
195
448
  <div className="border-t border-purple-500/10 pt-3 mt-3">
196
- <div className="text-xs text-muted-foreground mb-2">Inactive overlays ({inactive.length}) — cannot be swept</div>
449
+ <div className="text-xs text-muted-foreground mb-2">
450
+ Inactive overlays ({inactive.length}) — cannot be swept
451
+ </div>
197
452
  </div>
198
453
  )}
199
- {inactive.map((tb) => (<TokenRow key={tb.tokenId} tb={tb} walletConnected={walletConnected} />))}
454
+ {inactive.map((tb) => (
455
+ <TokenRow
456
+ key={tb.tokenId}
457
+ tb={tb}
458
+ walletConnected={walletConnected}
459
+ />
460
+ ))}
200
461
  </div>
201
462
  </div>
202
- );
463
+ )
203
464
  }
204
465
 
205
466
  export function Bsv20Section({ tokens }: { tokens: IndexedOutput[] }) {
206
- if (tokens.length === 0) return null;
467
+ if (tokens.length === 0) return null
207
468
  return (
208
469
  <div className="border border-muted/30 bg-muted/10 p-4 rounded-lg">
209
470
  <div className="flex items-center gap-2 mb-2">
210
471
  <span className="h-2 w-2 rounded-full bg-muted-foreground" />
211
- <span className="text-sm font-semibold text-muted-foreground">BSV-20 Tokens</span>
472
+ <span className="text-sm font-semibold text-muted-foreground">
473
+ BSV-20 Tokens
474
+ </span>
212
475
  </div>
213
- <p className="text-xs text-muted-foreground mb-2">Cannot be swept automatically.</p>
476
+ <p className="text-xs text-muted-foreground mb-2">
477
+ Cannot be swept automatically.
478
+ </p>
214
479
  <div className="flex flex-wrap gap-2">
215
480
  {tokens.slice(0, 10).map((o) => {
216
- const tickEvent = o.events?.find((e) => e.startsWith("tick:"));
217
- const tick = tickEvent ? tickEvent.slice(5) : "Token";
218
- return (<span key={o.outpoint} className="px-2 py-1 text-xs rounded bg-muted/30 text-muted-foreground">{tick}</span>);
481
+ const tickEvent = o.events?.find((e) => e.startsWith('tick:'))
482
+ const tick = tickEvent ? tickEvent.slice(5) : 'Token'
483
+ return (
484
+ <span
485
+ key={o.outpoint}
486
+ className="px-2 py-1 text-xs rounded bg-muted/30 text-muted-foreground"
487
+ >
488
+ {tick}
489
+ </span>
490
+ )
219
491
  })}
220
- {tokens.length > 10 && <span className="text-xs text-muted-foreground">+{tokens.length - 10} more</span>}
492
+ {tokens.length > 10 && (
493
+ <span className="text-xs text-muted-foreground">
494
+ +{tokens.length - 10} more
495
+ </span>
496
+ )}
221
497
  </div>
222
498
  </div>
223
- );
499
+ )
224
500
  }
225
501
 
226
502
  export function LockedSection({ locked }: { locked: IndexedOutput[] }) {
227
- if (locked.length === 0) return null;
503
+ if (locked.length === 0) return null
228
504
  return (
229
505
  <div className="border border-yellow-500/20 bg-yellow-500/5 p-4 rounded-lg">
230
506
  <div className="flex items-center gap-2 mb-2">
231
507
  <span className="h-2 w-2 rounded-full bg-yellow-500" />
232
- <span className="text-sm font-semibold text-yellow-500">Locked Outputs</span>
508
+ <span className="text-sm font-semibold text-yellow-500">
509
+ Locked Outputs
510
+ </span>
233
511
  </div>
234
512
  <p className="text-xs text-muted-foreground">
235
- {locked.length} locked output{locked.length !== 1 ? "s" : ""}. These are in contracts and cannot be swept directly.
513
+ {locked.length} locked output{locked.length !== 1 ? 's' : ''}. These are
514
+ in contracts and cannot be swept directly.
236
515
  </p>
237
516
  </div>
238
- );
517
+ )
239
518
  }
240
519
 
241
520
  export function RunSection({ run }: { run: IndexedOutput[] }) {
242
- if (run.length === 0) return null;
243
- const totalSats = run.reduce((sum, o) => sum + (o.satoshis ?? 0), 0);
521
+ if (run.length === 0) return null
522
+ const totalSats = run.reduce((sum, o) => sum + (o.satoshis ?? 0), 0)
244
523
  return (
245
524
  <div className="border border-orange-500/20 bg-orange-500/5 p-4 rounded-lg">
246
525
  <div className="flex items-center gap-2 mb-2">
247
526
  <span className="h-2 w-2 rounded-full bg-orange-500" />
248
- <span className="text-sm font-semibold text-orange-500">RUN Protocol Tokens</span>
527
+ <span className="text-sm font-semibold text-orange-500">
528
+ RUN Protocol Tokens
529
+ </span>
249
530
  </div>
250
531
  <p className="text-xs text-muted-foreground">
251
- {run.length} output{run.length !== 1 ? "s" : ""} ({totalSats.toLocaleString()} sats). These are RUN protocol token outputs and cannot be swept as BSV.
532
+ {run.length} output{run.length !== 1 ? 's' : ''} (
533
+ {totalSats.toLocaleString()} sats). These are RUN protocol token outputs
534
+ and cannot be swept as BSV.
252
535
  </p>
253
536
  </div>
254
- );
537
+ )
255
538
  }