@1sat/sweep-ui 0.0.19 → 0.0.21

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,34 +1,43 @@
1
- import { useState } from "react";
2
- import { Loader2, Wallet, X } from "lucide-react";
3
- import { Button } from "./ui/button";
4
- import { connectWallet, disconnectWallet, getIdentityKey, getProvider } from "../lib/wallet";
1
+ import { Loader2, Wallet, X } from 'lucide-react'
2
+ import { useState } from 'react'
3
+ import {
4
+ connectWallet,
5
+ disconnectWallet,
6
+ getIdentityKey,
7
+ getProvider,
8
+ } from '../lib/wallet'
9
+ import { Button } from './ui/button'
5
10
 
6
11
  interface Props {
7
- onConnected: () => void;
8
- onDisconnected: () => void;
9
- connected: boolean;
12
+ onConnected: () => void
13
+ onDisconnected: () => void
14
+ connected: boolean
10
15
  }
11
16
 
12
- export function ConnectWallet({ onConnected, onDisconnected, connected }: Props) {
13
- const [connecting, setConnecting] = useState(false);
14
- const [error, setError] = useState<string | null>(null);
17
+ export function ConnectWallet({
18
+ onConnected,
19
+ onDisconnected,
20
+ connected,
21
+ }: Props) {
22
+ const [connecting, setConnecting] = useState(false)
23
+ const [error, setError] = useState<string | null>(null)
15
24
 
16
25
  async function handleConnect() {
17
- setConnecting(true);
18
- setError(null);
26
+ setConnecting(true)
27
+ setError(null)
19
28
  try {
20
- await connectWallet();
21
- onConnected();
29
+ await connectWallet()
30
+ onConnected()
22
31
  } catch (e) {
23
- setError(e instanceof Error ? e.message : "Failed to connect wallet");
32
+ setError(e instanceof Error ? e.message : 'Failed to connect wallet')
24
33
  } finally {
25
- setConnecting(false);
34
+ setConnecting(false)
26
35
  }
27
36
  }
28
37
 
29
38
  function handleDisconnect() {
30
- disconnectWallet();
31
- onDisconnected();
39
+ disconnectWallet()
40
+ onDisconnected()
32
41
  }
33
42
 
34
43
  if (connected) {
@@ -37,23 +46,39 @@ export function ConnectWallet({ onConnected, onDisconnected, connected }: Props)
37
46
  <div className="flex items-center gap-2 text-sm">
38
47
  <span className="h-2 w-2 rounded-full bg-green-500" />
39
48
  <span className="text-muted-foreground">
40
- {getProvider() === "brc100" ? "BRC-100" : "OneSat"} · {getIdentityKey()?.slice(0, 12)}...
49
+ {getProvider() === 'brc100' ? 'BRC-100' : 'OneSat'} ·{' '}
50
+ {getIdentityKey()?.slice(0, 12)}...
41
51
  </span>
42
52
  </div>
43
- <Button variant="ghost" size="sm" className="h-6 w-6 p-0" onClick={handleDisconnect}>
53
+ <Button
54
+ variant="ghost"
55
+ size="sm"
56
+ className="h-6 w-6 p-0"
57
+ onClick={handleDisconnect}
58
+ >
44
59
  <X className="h-3 w-3" />
45
60
  </Button>
46
61
  </div>
47
- );
62
+ )
48
63
  }
49
64
 
50
65
  return (
51
66
  <div className="space-y-1">
52
- <Button variant="outline" size="sm" className="w-full text-xs gap-2" onClick={handleConnect} disabled={connecting}>
53
- {connecting ? <Loader2 className="h-3 w-3 animate-spin" /> : <Wallet className="h-3 w-3" />}
54
- {connecting ? "Connecting..." : "Connect BRC-100 Wallet (optional)"}
67
+ <Button
68
+ variant="outline"
69
+ size="sm"
70
+ className="w-full text-xs gap-2"
71
+ onClick={handleConnect}
72
+ disabled={connecting}
73
+ >
74
+ {connecting ? (
75
+ <Loader2 className="h-3 w-3 animate-spin" />
76
+ ) : (
77
+ <Wallet className="h-3 w-3" />
78
+ )}
79
+ {connecting ? 'Connecting...' : 'Connect BRC-100 Wallet (optional)'}
55
80
  </Button>
56
81
  {error && <p className="text-xs text-destructive text-center">{error}</p>}
57
82
  </div>
58
- );
83
+ )
59
84
  }
@@ -1,58 +1,92 @@
1
- import { useState, useEffect } from "react";
2
- import { Button } from "./ui/button";
3
- import { Input } from "./ui/input";
4
- import type { EnrichedOrdinal } from "../lib/scanner";
5
- import { getServices } from "../lib/services";
1
+ import { useEffect, useState } from 'react'
2
+ import type { EnrichedOrdinal } from '../lib/scanner'
3
+ import { getServices } from '../lib/services'
4
+ import { Button } from './ui/button'
5
+ import { Input } from './ui/input'
6
6
 
7
- export function OpnsSection({ opnsNames, selectedOpns, onToggle, onSelectAll, onDeselectAll, onSweep, onSend, onBurn, walletConnected }: {
8
- opnsNames: EnrichedOrdinal[]; selectedOpns: Set<string>; onToggle: (outpoint: string) => void; onSelectAll: () => void; onDeselectAll: () => void; onSweep: () => void; onSend?: (destination: string) => void; onBurn?: () => void; walletConnected: boolean;
7
+ export function OpnsSection({
8
+ opnsNames,
9
+ selectedOpns,
10
+ onToggle,
11
+ onSelectAll,
12
+ onDeselectAll,
13
+ onSweep,
14
+ onSend,
15
+ onBurn,
16
+ walletConnected,
17
+ }: {
18
+ opnsNames: EnrichedOrdinal[]
19
+ selectedOpns: Set<string>
20
+ onToggle: (outpoint: string) => void
21
+ onSelectAll: () => void
22
+ onDeselectAll: () => void
23
+ onSweep: () => void
24
+ onSend?: (destination: string) => void
25
+ onBurn?: () => void
26
+ walletConnected: boolean
9
27
  }) {
10
- const [address, setAddress] = useState("");
11
- const [resolvedNames, setResolvedNames] = useState<Map<string, string>>(new Map());
12
- const [overlayValid, setOverlayValid] = useState<Map<string, boolean>>(new Map());
13
- const [validating, setValidating] = useState(false);
28
+ const [address, setAddress] = useState('')
29
+ const [resolvedNames, setResolvedNames] = useState<Map<string, string>>(
30
+ new Map(),
31
+ )
32
+ const [overlayValid, setOverlayValid] = useState<Map<string, boolean>>(
33
+ new Map(),
34
+ )
35
+ const [validating, setValidating] = useState(false)
14
36
 
15
37
  useEffect(() => {
16
- if (opnsNames.length === 0) return;
17
- const controller = new AbortController();
18
- const pending = new Map<string, string>();
38
+ if (opnsNames.length === 0) return
39
+ const controller = new AbortController()
40
+ const pending = new Map<string, string>()
19
41
  Promise.all(
20
42
  opnsNames.map(async (item) => {
21
43
  try {
22
- const res = await fetch(item.contentUrl, { signal: controller.signal });
23
- if (res.ok) pending.set(item.outpoint, await res.text());
24
- } catch { /* fetch aborted or failed */ }
44
+ const res = await fetch(item.contentUrl, {
45
+ signal: controller.signal,
46
+ })
47
+ if (res.ok) pending.set(item.outpoint, await res.text())
48
+ } catch {
49
+ /* fetch aborted or failed */
50
+ }
25
51
  }),
26
- ).then(() => { if (!controller.signal.aborted) setResolvedNames(new Map(pending)); });
27
- return () => controller.abort();
28
- }, [opnsNames]);
52
+ ).then(() => {
53
+ if (!controller.signal.aborted) setResolvedNames(new Map(pending))
54
+ })
55
+ return () => controller.abort()
56
+ }, [opnsNames])
29
57
 
30
58
  useEffect(() => {
31
- if (opnsNames.length === 0) return;
32
- let cancelled = false;
33
- setValidating(true);
34
- const originToOutpoints = new Map<string, string[]>();
59
+ if (opnsNames.length === 0) return
60
+ let cancelled = false
61
+ setValidating(true)
62
+ const originToOutpoints = new Map<string, string[]>()
35
63
  for (const item of opnsNames) {
36
- const origin = item.origin ?? item.outpoint;
37
- const list = originToOutpoints.get(origin) ?? [];
38
- list.push(item.outpoint);
39
- originToOutpoints.set(origin, list);
64
+ const origin = item.origin ?? item.outpoint
65
+ const list = originToOutpoints.get(origin) ?? []
66
+ list.push(item.outpoint)
67
+ originToOutpoints.set(origin, list)
40
68
  }
41
- getServices().opns.validateOrigins([...originToOutpoints.keys()])
69
+ getServices()
70
+ .opns.validateOrigins([...originToOutpoints.keys()])
42
71
  .then((result) => {
43
- if (cancelled) return;
44
- const map = new Map<string, boolean>();
72
+ if (cancelled) return
73
+ const map = new Map<string, boolean>()
45
74
  for (const [origin, valid] of Object.entries(result)) {
46
- for (const outpoint of originToOutpoints.get(origin) ?? []) map.set(outpoint, valid);
75
+ for (const outpoint of originToOutpoints.get(origin) ?? [])
76
+ map.set(outpoint, valid)
47
77
  }
48
- setOverlayValid(map);
78
+ setOverlayValid(map)
49
79
  })
50
80
  .catch(() => {})
51
- .finally(() => { if (!cancelled) setValidating(false); });
52
- return () => { cancelled = true; };
53
- }, [opnsNames]);
81
+ .finally(() => {
82
+ if (!cancelled) setValidating(false)
83
+ })
84
+ return () => {
85
+ cancelled = true
86
+ }
87
+ }, [opnsNames])
54
88
 
55
- if (opnsNames.length === 0) return null;
89
+ if (opnsNames.length === 0) return null
56
90
 
57
91
  return (
58
92
  <div className="border border-orange-500/20 bg-orange-500/5 p-4 rounded-lg">
@@ -60,55 +94,128 @@ export function OpnsSection({ opnsNames, selectedOpns, onToggle, onSelectAll, on
60
94
  <div>
61
95
  <div className="flex items-center gap-2 mb-1">
62
96
  <span className="h-2 w-2 rounded-full bg-orange-500" />
63
- <span className="text-sm font-semibold text-orange-500">OPNS Domains</span>
97
+ <span className="text-sm font-semibold text-orange-500">
98
+ OPNS Domains
99
+ </span>
64
100
  </div>
65
101
  <div className="text-xs text-muted-foreground">
66
- {opnsNames.length} domain{opnsNames.length !== 1 ? "s" : ""}
67
- {selectedOpns.size > 0 && <span className="text-orange-400 ml-1">({selectedOpns.size} selected)</span>}
102
+ {opnsNames.length} domain{opnsNames.length !== 1 ? 's' : ''}
103
+ {selectedOpns.size > 0 && (
104
+ <span className="text-orange-400 ml-1">
105
+ ({selectedOpns.size} selected)
106
+ </span>
107
+ )}
68
108
  </div>
69
109
  </div>
70
110
  <div className="flex gap-2">
71
- <Button variant="outline" size="sm" className="h-7 text-[11px]" onClick={onSelectAll}>Select All</Button>
72
- <Button variant="outline" size="sm" className="h-7 text-[11px]" onClick={onDeselectAll} disabled={selectedOpns.size === 0}>Deselect</Button>
111
+ <Button
112
+ variant="outline"
113
+ size="sm"
114
+ className="h-7 text-[11px]"
115
+ onClick={onSelectAll}
116
+ >
117
+ Select All
118
+ </Button>
119
+ <Button
120
+ variant="outline"
121
+ size="sm"
122
+ className="h-7 text-[11px]"
123
+ onClick={onDeselectAll}
124
+ disabled={selectedOpns.size === 0}
125
+ >
126
+ Deselect
127
+ </Button>
73
128
  </div>
74
129
  </div>
75
130
  <div className="space-y-1">
76
131
  {opnsNames.map((item) => {
77
- const isSelected = selectedOpns.has(item.outpoint);
78
- const displayName = resolvedNames.get(item.outpoint) ?? item.outpoint.substring(0, 8) + "...";
132
+ const isSelected = selectedOpns.has(item.outpoint)
133
+ const displayName =
134
+ resolvedNames.get(item.outpoint) ??
135
+ item.outpoint.substring(0, 8) + '...'
79
136
  return (
80
- <div key={item.outpoint} className={`flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer transition-all ${isSelected ? "border border-orange-500 bg-orange-500/10 ring-1 ring-orange-500/30" : "border border-border/50 hover:border-border bg-black/20"}`} onClick={() => onToggle(item.outpoint)}>
81
- <div className={`w-4 h-4 rounded border-2 flex items-center justify-center text-[10px] shrink-0 ${isSelected ? "bg-orange-500 border-orange-500 text-white" : "border-muted-foreground/40"}`}>
82
- {isSelected && "\u2713"}
137
+ <div
138
+ key={item.outpoint}
139
+ className={`flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer transition-all ${isSelected ? 'border border-orange-500 bg-orange-500/10 ring-1 ring-orange-500/30' : 'border border-border/50 hover:border-border bg-black/20'}`}
140
+ onClick={() => onToggle(item.outpoint)}
141
+ >
142
+ <div
143
+ className={`w-4 h-4 rounded border-2 flex items-center justify-center text-[10px] shrink-0 ${isSelected ? 'bg-orange-500 border-orange-500 text-white' : 'border-muted-foreground/40'}`}
144
+ >
145
+ {isSelected && '\u2713'}
83
146
  </div>
84
- <span className="text-sm text-foreground truncate">{displayName}</span>
85
- {!validating && overlayValid.has(item.outpoint) && (
86
- overlayValid.get(item.outpoint) ? (
87
- <span className="shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-green-500/20 text-green-400">valid</span>
147
+ <span className="text-sm text-foreground truncate">
148
+ {displayName}
149
+ </span>
150
+ {!validating &&
151
+ overlayValid.has(item.outpoint) &&
152
+ (overlayValid.get(item.outpoint) ? (
153
+ <span className="shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-green-500/20 text-green-400">
154
+ valid
155
+ </span>
88
156
  ) : (
89
- <span className="shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-red-500/20 text-red-400">invalid</span>
90
- )
91
- )}
157
+ <span className="shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-red-500/20 text-red-400">
158
+ invalid
159
+ </span>
160
+ ))}
92
161
  </div>
93
- );
162
+ )
94
163
  })}
95
164
  </div>
96
165
  {selectedOpns.size > 0 && (
97
166
  <div className="mt-3 space-y-2">
98
167
  {onSend && (
99
- <Input type="text" placeholder="Destination address..." value={address} onChange={(e) => setAddress(e.target.value)} className="font-mono text-xs" />
168
+ <Input
169
+ type="text"
170
+ placeholder="Destination address..."
171
+ value={address}
172
+ onChange={(e) => setAddress(e.target.value)}
173
+ className="font-mono text-xs"
174
+ />
100
175
  )}
101
176
  <div className="flex gap-2">
102
177
  {onSend && (
103
- <Button variant="outline" size="sm" className="flex-1" disabled={!address.trim()} onClick={() => onSend(address.trim())}>Send {selectedOpns.size} Domain{selectedOpns.size !== 1 ? "s" : ""}</Button>
178
+ <Button
179
+ variant="outline"
180
+ size="sm"
181
+ className="flex-1"
182
+ disabled={!address.trim()}
183
+ onClick={() => onSend(address.trim())}
184
+ >
185
+ Send {selectedOpns.size} Domain
186
+ {selectedOpns.size !== 1 ? 's' : ''}
187
+ </Button>
104
188
  )}
105
- <Button size="sm" className="flex-1" onClick={onSweep} disabled={!walletConnected} title={walletConnected ? undefined : "Connect BRC-100 wallet to sweep"}>Sweep to Wallet</Button>
189
+ <Button
190
+ size="sm"
191
+ className="flex-1"
192
+ onClick={onSweep}
193
+ disabled={!walletConnected}
194
+ title={
195
+ walletConnected ? undefined : 'Connect BRC-100 wallet to sweep'
196
+ }
197
+ >
198
+ Sweep to Wallet
199
+ </Button>
106
200
  {onBurn && (
107
- <Button size="sm" className="bg-red-600 hover:bg-red-700 text-white" onClick={() => { if (window.confirm(`Permanently burn ${selectedOpns.size} domain${selectedOpns.size !== 1 ? "s" : ""}? This cannot be undone.`)) onBurn(); }}>Burn</Button>
201
+ <Button
202
+ size="sm"
203
+ className="bg-red-600 hover:bg-red-700 text-white"
204
+ onClick={() => {
205
+ if (
206
+ window.confirm(
207
+ `Permanently burn ${selectedOpns.size} domain${selectedOpns.size !== 1 ? 's' : ''}? This cannot be undone.`,
208
+ )
209
+ )
210
+ onBurn()
211
+ }}
212
+ >
213
+ Burn
214
+ </Button>
108
215
  )}
109
216
  </div>
110
217
  </div>
111
218
  )}
112
219
  </div>
113
- );
220
+ )
114
221
  }
@@ -1,27 +1,39 @@
1
- import { CheckCircle2, Loader2, AlertTriangle, ExternalLink } from "lucide-react";
2
- import type { SweepResult } from "../lib/sweeper";
1
+ import {
2
+ AlertTriangle,
3
+ CheckCircle2,
4
+ ExternalLink,
5
+ Loader2,
6
+ } from 'lucide-react'
7
+ import type { SweepResult } from '../lib/sweeper'
3
8
 
4
- const EXPLORER_BASE = "https://bananablocks.com/tx/";
9
+ const EXPLORER_BASE = 'https://bananablocks.com/tx/'
5
10
 
6
11
  function TxLink({ label, txid }: { label: string; txid: string }) {
7
12
  return (
8
13
  <div className="border-b border-border/30 pb-2 space-y-1">
9
14
  <div className="flex items-center justify-between text-sm">
10
15
  <span className="text-muted-foreground">{label}</span>
11
- <a href={`${EXPLORER_BASE}${txid}`} target="_blank" rel="noopener noreferrer" className="text-blue-400 hover:text-blue-300 flex items-center gap-1">
16
+ <a
17
+ href={`${EXPLORER_BASE}${txid}`}
18
+ target="_blank"
19
+ rel="noopener noreferrer"
20
+ className="text-blue-400 hover:text-blue-300 flex items-center gap-1"
21
+ >
12
22
  <ExternalLink className="h-3 w-3" />
13
23
  <span className="text-xs">View</span>
14
24
  </a>
15
25
  </div>
16
- <code className="text-xs font-mono text-muted-foreground break-all">{txid}</code>
26
+ <code className="text-xs font-mono text-muted-foreground break-all">
27
+ {txid}
28
+ </code>
17
29
  </div>
18
- );
30
+ )
19
31
  }
20
32
 
21
33
  interface Props {
22
- sweeping: boolean;
23
- progress: string;
24
- result: SweepResult | null;
34
+ sweeping: boolean
35
+ progress: string
36
+ result: SweepResult | null
25
37
  }
26
38
 
27
39
  export function SweepProgress({ sweeping, progress, result }: Props) {
@@ -29,16 +41,21 @@ export function SweepProgress({ sweeping, progress, result }: Props) {
29
41
  return (
30
42
  <div className="text-center space-y-4 py-8">
31
43
  <Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" />
32
- <p className="text-sm text-muted-foreground animate-pulse">{progress}</p>
44
+ <p className="text-sm text-muted-foreground animate-pulse">
45
+ {progress}
46
+ </p>
33
47
  <p className="text-xs text-destructive/80">Do not close this page.</p>
34
48
  </div>
35
- );
49
+ )
36
50
  }
37
51
 
38
- if (!result) return null;
52
+ if (!result) return null
39
53
 
40
- const hasErrors = result.errors.length > 0;
41
- const hasTxids = result.bsvTxid || result.ordinalTxids.length > 0 || result.bsv21Txids.length > 0;
54
+ const hasErrors = result.errors.length > 0
55
+ const hasTxids =
56
+ result.bsvTxid ||
57
+ result.ordinalTxids.length > 0 ||
58
+ result.bsv21Txids.length > 0
42
59
 
43
60
  return (
44
61
  <div className="space-y-4 py-4">
@@ -49,7 +66,11 @@ export function SweepProgress({ sweeping, progress, result }: Props) {
49
66
  <CheckCircle2 className="h-5 w-5 text-green-500" />
50
67
  )}
51
68
  <span className="font-semibold">
52
- {hasErrors && !hasTxids ? "Failed" : hasErrors ? "Completed with Errors" : "Complete"}
69
+ {hasErrors && !hasTxids
70
+ ? 'Failed'
71
+ : hasErrors
72
+ ? 'Completed with Errors'
73
+ : 'Complete'}
53
74
  </span>
54
75
  </div>
55
76
 
@@ -62,8 +83,10 @@ export function SweepProgress({ sweeping, progress, result }: Props) {
62
83
  ))}
63
84
 
64
85
  {result.errors.map((err) => (
65
- <p key={err} className="text-xs text-destructive">{err}</p>
86
+ <p key={err} className="text-xs text-destructive">
87
+ {err}
88
+ </p>
66
89
  ))}
67
90
  </div>
68
- );
91
+ )
69
92
  }
@@ -1,18 +1,18 @@
1
- import { Loader2, ExternalLink } from "lucide-react";
1
+ import { ExternalLink, Loader2 } from 'lucide-react'
2
2
 
3
- const EXPLORER_BASE = "https://bananablocks.com/tx/";
3
+ const EXPLORER_BASE = 'https://bananablocks.com/tx/'
4
4
 
5
5
  export interface TxRecord {
6
- label: string;
7
- txid: string;
8
- timestamp: Date;
9
- error?: string;
6
+ label: string
7
+ txid: string
8
+ timestamp: Date
9
+ error?: string
10
10
  }
11
11
 
12
12
  interface Props {
13
- sweeping: boolean;
14
- progress: string;
15
- history: TxRecord[];
13
+ sweeping: boolean
14
+ progress: string
15
+ history: TxRecord[]
16
16
  }
17
17
 
18
18
  export function TxHistory({ sweeping, progress, history }: Props) {
@@ -21,7 +21,9 @@ export function TxHistory({ sweeping, progress, history }: Props) {
21
21
  {sweeping && (
22
22
  <div className="text-center space-y-4 py-8">
23
23
  <Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" />
24
- <p className="text-sm text-muted-foreground animate-pulse">{progress}</p>
24
+ <p className="text-sm text-muted-foreground animate-pulse">
25
+ {progress}
26
+ </p>
25
27
  <p className="text-xs text-destructive/80">Do not close this page.</p>
26
28
  </div>
27
29
  )}
@@ -33,15 +35,24 @@ export function TxHistory({ sweeping, progress, history }: Props) {
33
35
  </div>
34
36
  <div className="space-y-2 max-h-48 overflow-y-auto">
35
37
  {[...history].reverse().map((tx, i) => (
36
- <div key={`${tx.txid}-${i}`} className="flex items-center justify-between gap-2 text-xs">
38
+ <div
39
+ key={`${tx.txid}-${i}`}
40
+ className="flex items-center justify-between gap-2 text-xs"
41
+ >
37
42
  <div className="flex items-center gap-2 min-w-0">
38
- <span className={tx.error ? "text-red-500" : "text-green-500"}>
39
- {tx.error ? "\u2717" : "\u2713"}
43
+ <span
44
+ className={tx.error ? 'text-red-500' : 'text-green-500'}
45
+ >
46
+ {tx.error ? '\u2717' : '\u2713'}
47
+ </span>
48
+ <span className="text-muted-foreground truncate">
49
+ {tx.label}
40
50
  </span>
41
- <span className="text-muted-foreground truncate">{tx.label}</span>
42
51
  </div>
43
52
  {tx.error ? (
44
- <span className="text-red-500 text-[10px] truncate max-w-[200px]">{tx.error}</span>
53
+ <span className="text-red-500 text-[10px] truncate max-w-[200px]">
54
+ {tx.error}
55
+ </span>
45
56
  ) : (
46
57
  <a
47
58
  href={`${EXPLORER_BASE}${tx.txid}`}
@@ -49,7 +60,9 @@ export function TxHistory({ sweeping, progress, history }: Props) {
49
60
  rel="noopener noreferrer"
50
61
  className="text-blue-400 hover:text-blue-300 flex items-center gap-1 shrink-0"
51
62
  >
52
- <code className="text-[10px] font-mono">{tx.txid.substring(0, 12)}...</code>
63
+ <code className="text-[10px] font-mono">
64
+ {tx.txid.substring(0, 12)}...
65
+ </code>
53
66
  <ExternalLink className="h-3 w-3" />
54
67
  </a>
55
68
  )}
@@ -59,5 +72,5 @@ export function TxHistory({ sweeping, progress, history }: Props) {
59
72
  </div>
60
73
  )}
61
74
  </>
62
- );
75
+ )
63
76
  }
@@ -1,31 +1,34 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
- import { cn } from "../../lib/utils"
1
+ import { type VariantProps, cva } from 'class-variance-authority'
2
+ import type * as React from 'react'
3
+ import { cn } from '../../lib/utils'
4
4
 
5
5
  const badgeVariants = cva(
6
- "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
6
+ 'inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3',
7
7
  {
8
8
  variants: {
9
9
  variant: {
10
- default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
11
- secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
12
- destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
13
- outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
14
- ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
15
- link: "text-primary underline-offset-4 [a&]:hover:underline",
10
+ default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
11
+ secondary:
12
+ 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
13
+ destructive:
14
+ 'bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90',
15
+ outline:
16
+ 'border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
17
+ ghost: '[a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
18
+ link: 'text-primary underline-offset-4 [a&]:hover:underline',
16
19
  },
17
20
  },
18
21
  defaultVariants: {
19
- variant: "default",
22
+ variant: 'default',
20
23
  },
21
- }
24
+ },
22
25
  )
23
26
 
24
27
  function Badge({
25
28
  className,
26
- variant = "default",
29
+ variant = 'default',
27
30
  ...props
28
- }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
31
+ }: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {
29
32
  return (
30
33
  <span
31
34
  data-slot="badge"