@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.
- package/dist/components/SweepApp.d.ts +3 -3
- package/dist/components/SweepApp.d.ts.map +1 -1
- package/dist/components/asset-preview.d.ts +5 -5
- package/dist/components/asset-preview.d.ts.map +1 -1
- package/dist/components/connect-wallet.d.ts +1 -1
- package/dist/components/connect-wallet.d.ts.map +1 -1
- package/dist/components/opns-section.d.ts +2 -2
- package/dist/components/opns-section.d.ts.map +1 -1
- package/dist/components/sweep-progress.d.ts +1 -1
- package/dist/components/sweep-progress.d.ts.map +1 -1
- package/dist/components/tx-history.d.ts.map +1 -1
- package/dist/components/ui/badge.d.ts +3 -3
- package/dist/components/ui/badge.d.ts.map +1 -1
- package/dist/components/ui/button.d.ts +3 -3
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/card.d.ts +9 -9
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/input.d.ts +2 -2
- package/dist/components/ui/input.d.ts.map +1 -1
- package/dist/components/ui/tabs.d.ts +5 -5
- package/dist/components/ui/tabs.d.ts.map +1 -1
- package/dist/components/wif-input.d.ts +1 -1
- package/dist/components/wif-input.d.ts.map +1 -1
- package/dist/index.d.ts +19 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1911 -1757
- package/dist/lib/legacy-send.d.ts +2 -2
- package/dist/lib/legacy-send.d.ts.map +1 -1
- package/dist/lib/scanner.d.ts +3 -3
- package/dist/lib/scanner.d.ts.map +1 -1
- package/dist/lib/services.d.ts +1 -1
- package/dist/lib/services.d.ts.map +1 -1
- package/dist/lib/sweeper.d.ts +3 -3
- package/dist/lib/sweeper.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/wallet.d.ts +2 -2
- package/dist/lib/wallet.d.ts.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +53 -44
- package/src/components/SweepApp.tsx +480 -222
- package/src/components/asset-preview.tsx +380 -97
- package/src/components/connect-wallet.tsx +50 -25
- package/src/components/opns-section.tsx +167 -60
- package/src/components/sweep-progress.tsx +40 -17
- package/src/components/tx-history.tsx +30 -17
- package/src/components/ui/badge.tsx +17 -14
- package/src/components/ui/button.tsx +26 -22
- package/src/components/ui/card.tsx +76 -17
- package/src/components/ui/input.tsx +7 -7
- package/src/components/ui/tabs.tsx +51 -12
- package/src/components/wif-input.tsx +243 -135
- package/src/index.ts +54 -19
- package/src/lib/legacy-send.ts +110 -106
- package/src/lib/scanner.ts +45 -40
- package/src/lib/services.ts +11 -9
- package/src/lib/sweeper.ts +67 -54
- package/src/lib/utils.ts +11 -11
- package/src/lib/wallet.ts +16 -13
- package/src/types.ts +3 -3
|
@@ -1,34 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
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({
|
|
13
|
-
|
|
14
|
-
|
|
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 :
|
|
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() ===
|
|
49
|
+
{getProvider() === 'brc100' ? 'BRC-100' : 'OneSat'} ·{' '}
|
|
50
|
+
{getIdentityKey()?.slice(0, 12)}...
|
|
41
51
|
</span>
|
|
42
52
|
</div>
|
|
43
|
-
<Button
|
|
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
|
|
53
|
-
|
|
54
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
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({
|
|
8
|
-
opnsNames
|
|
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>>(
|
|
12
|
-
|
|
13
|
-
|
|
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, {
|
|
23
|
-
|
|
24
|
-
|
|
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(() => {
|
|
27
|
-
|
|
28
|
-
|
|
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()
|
|
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) ?? [])
|
|
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(() => {
|
|
52
|
-
|
|
53
|
-
|
|
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">
|
|
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 ?
|
|
67
|
-
{selectedOpns.size > 0 &&
|
|
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
|
|
72
|
-
|
|
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 =
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
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">
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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">
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
2
|
-
|
|
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 =
|
|
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
|
|
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">
|
|
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">
|
|
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 =
|
|
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
|
|
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">
|
|
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 {
|
|
1
|
+
import { ExternalLink, Loader2 } from 'lucide-react'
|
|
2
2
|
|
|
3
|
-
const EXPLORER_BASE =
|
|
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">
|
|
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
|
|
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
|
|
39
|
-
{tx.error ?
|
|
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]">
|
|
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">
|
|
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
|
|
2
|
-
import
|
|
3
|
-
import { cn } from
|
|
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
|
-
|
|
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:
|
|
11
|
-
secondary:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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:
|
|
22
|
+
variant: 'default',
|
|
20
23
|
},
|
|
21
|
-
}
|
|
24
|
+
},
|
|
22
25
|
)
|
|
23
26
|
|
|
24
27
|
function Badge({
|
|
25
28
|
className,
|
|
26
|
-
variant =
|
|
29
|
+
variant = 'default',
|
|
27
30
|
...props
|
|
28
|
-
}: React.ComponentProps<
|
|
31
|
+
}: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {
|
|
29
32
|
return (
|
|
30
33
|
<span
|
|
31
34
|
data-slot="badge"
|