@1sat/sweep-ui 0.0.18 → 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.
- 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,31 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { KeyRound, Loader2, Search, Upload, X } from "lucide-react";
|
|
3
|
-
import { Button } from "./ui/button";
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
|
|
5
|
-
import { Input } from "./ui/input";
|
|
1
|
+
import { deriveIdentityKey } from '@1sat/utils'
|
|
6
2
|
import {
|
|
3
|
+
type DecryptedBackup,
|
|
7
4
|
decryptBackup,
|
|
5
|
+
getBackupType,
|
|
8
6
|
isOneSatBackup,
|
|
9
|
-
isYoursWalletBackup,
|
|
10
7
|
isWifBackup,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import type { LegacyKeys } from
|
|
8
|
+
isYoursWalletBackup,
|
|
9
|
+
} from 'bitcoin-backup'
|
|
10
|
+
import { KeyRound, Loader2, Search, Upload, X } from 'lucide-react'
|
|
11
|
+
import { useCallback, useRef, useState } from 'react'
|
|
12
|
+
import { deriveAddress } from '../lib/scanner'
|
|
13
|
+
import type { LegacyKeys } from '../types'
|
|
14
|
+
import { Button } from './ui/button'
|
|
15
|
+
import { Card, CardContent, CardHeader, CardTitle } from './ui/card'
|
|
16
|
+
import { Input } from './ui/input'
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
19
|
-
onScan: (keys: LegacyKeys) => void
|
|
20
|
-
scanning: boolean
|
|
21
|
-
disabled: boolean
|
|
19
|
+
onScan: (keys: LegacyKeys) => void
|
|
20
|
+
scanning: boolean
|
|
21
|
+
disabled: boolean
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
type InputMode =
|
|
24
|
+
type InputMode = 'choose' | 'backup' | 'wif'
|
|
25
25
|
|
|
26
26
|
function withIdentityKey(keys: LegacyKeys): LegacyKeys {
|
|
27
|
-
if (keys.identityPk) return keys
|
|
28
|
-
return {
|
|
27
|
+
if (keys.identityPk) return keys
|
|
28
|
+
return {
|
|
29
|
+
...keys,
|
|
30
|
+
identityPk: deriveIdentityKey(keys.payPk, keys.ordPk).toWif(),
|
|
31
|
+
}
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
function extractKeys(backup: DecryptedBackup): LegacyKeys | null {
|
|
@@ -34,107 +37,108 @@ function extractKeys(backup: DecryptedBackup): LegacyKeys | null {
|
|
|
34
37
|
payPk: backup.payPk,
|
|
35
38
|
ordPk: backup.ordPk,
|
|
36
39
|
identityPk: backup.identityPk,
|
|
37
|
-
})
|
|
40
|
+
})
|
|
38
41
|
}
|
|
39
42
|
if (isYoursWalletBackup(backup)) {
|
|
40
43
|
return withIdentityKey({
|
|
41
44
|
payPk: backup.payPk,
|
|
42
45
|
ordPk: backup.ordPk,
|
|
43
46
|
identityPk: backup.identityPk,
|
|
44
|
-
})
|
|
47
|
+
})
|
|
45
48
|
}
|
|
46
49
|
if (isWifBackup(backup)) {
|
|
47
50
|
return withIdentityKey({
|
|
48
51
|
payPk: backup.wif,
|
|
49
52
|
ordPk: backup.wif,
|
|
50
|
-
})
|
|
53
|
+
})
|
|
51
54
|
}
|
|
52
|
-
return null
|
|
55
|
+
return null
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
function tryParseBackup(text: string): LegacyKeys | null {
|
|
56
59
|
try {
|
|
57
|
-
const parsed = JSON.parse(text)
|
|
60
|
+
const parsed = JSON.parse(text)
|
|
58
61
|
if (parsed.payPk && parsed.ordPk) {
|
|
59
62
|
return withIdentityKey({
|
|
60
63
|
payPk: parsed.payPk,
|
|
61
64
|
ordPk: parsed.ordPk,
|
|
62
65
|
identityPk: parsed.identityPk,
|
|
63
|
-
})
|
|
66
|
+
})
|
|
64
67
|
}
|
|
65
68
|
if (parsed.accounts && parsed.selectedAccount) {
|
|
66
|
-
const account = parsed.accounts[parsed.selectedAccount]
|
|
69
|
+
const account = parsed.accounts[parsed.selectedAccount]
|
|
67
70
|
if (account?.encryptedKeys) {
|
|
68
|
-
return null
|
|
71
|
+
return null
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
} catch {
|
|
72
75
|
// Not JSON
|
|
73
76
|
}
|
|
74
|
-
return null
|
|
77
|
+
return null
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
function looksEncrypted(text: string): boolean {
|
|
78
|
-
if (text.startsWith(
|
|
79
|
-
if (text.startsWith(
|
|
80
|
-
|
|
81
|
+
if (text.startsWith('{')) return false
|
|
82
|
+
if (text.startsWith('5') || text.startsWith('K') || text.startsWith('L'))
|
|
83
|
+
return false
|
|
84
|
+
return text.length > 50
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
export function WifInput({ onScan, scanning, disabled }: Props) {
|
|
84
|
-
const [mode, setMode] = useState<InputMode>(
|
|
85
|
-
const [keys, setKeys] = useState<LegacyKeys | null>(null)
|
|
86
|
-
const [backupText, setBackupText] = useState(
|
|
87
|
-
const [password, setPassword] = useState(
|
|
88
|
-
const [needsPassword, setNeedsPassword] = useState(false)
|
|
89
|
-
const [decrypting, setDecrypting] = useState(false)
|
|
90
|
-
const [error, setError] = useState(
|
|
91
|
-
const [backupType, setBackupType] = useState(
|
|
92
|
-
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
93
|
-
const [payWif, setPayWif] = useState(
|
|
94
|
-
const [ordWif, setOrdWif] = useState(
|
|
95
|
-
const [sameKey, setSameKey] = useState(true)
|
|
88
|
+
const [mode, setMode] = useState<InputMode>('choose')
|
|
89
|
+
const [keys, setKeys] = useState<LegacyKeys | null>(null)
|
|
90
|
+
const [backupText, setBackupText] = useState('')
|
|
91
|
+
const [password, setPassword] = useState('')
|
|
92
|
+
const [needsPassword, setNeedsPassword] = useState(false)
|
|
93
|
+
const [decrypting, setDecrypting] = useState(false)
|
|
94
|
+
const [error, setError] = useState('')
|
|
95
|
+
const [backupType, setBackupType] = useState('')
|
|
96
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
97
|
+
const [payWif, setPayWif] = useState('')
|
|
98
|
+
const [ordWif, setOrdWif] = useState('')
|
|
99
|
+
const [sameKey, setSameKey] = useState(true)
|
|
96
100
|
|
|
97
101
|
const handleReset = useCallback(() => {
|
|
98
|
-
setKeys(null)
|
|
99
|
-
setBackupText(
|
|
100
|
-
setPassword(
|
|
101
|
-
setNeedsPassword(false)
|
|
102
|
-
setError(
|
|
103
|
-
setBackupType(
|
|
104
|
-
setMode(
|
|
105
|
-
setPayWif(
|
|
106
|
-
setOrdWif(
|
|
107
|
-
}, [])
|
|
102
|
+
setKeys(null)
|
|
103
|
+
setBackupText('')
|
|
104
|
+
setPassword('')
|
|
105
|
+
setNeedsPassword(false)
|
|
106
|
+
setError('')
|
|
107
|
+
setBackupType('')
|
|
108
|
+
setMode('choose')
|
|
109
|
+
setPayWif('')
|
|
110
|
+
setOrdWif('')
|
|
111
|
+
}, [])
|
|
108
112
|
|
|
109
113
|
const processBackupText = useCallback(async (text: string) => {
|
|
110
|
-
setError(
|
|
111
|
-
setBackupText(text)
|
|
114
|
+
setError('')
|
|
115
|
+
setBackupText(text)
|
|
112
116
|
|
|
113
|
-
const directKeys = tryParseBackup(text)
|
|
117
|
+
const directKeys = tryParseBackup(text)
|
|
114
118
|
if (directKeys) {
|
|
115
|
-
setKeys(directKeys)
|
|
116
|
-
setBackupType(
|
|
117
|
-
return
|
|
119
|
+
setKeys(directKeys)
|
|
120
|
+
setBackupType('JSON')
|
|
121
|
+
return
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
try {
|
|
121
|
-
const parsed = JSON.parse(text)
|
|
125
|
+
const parsed = JSON.parse(text)
|
|
122
126
|
if (parsed.encryptedBackup) {
|
|
123
|
-
setBackupText(parsed.encryptedBackup)
|
|
124
|
-
setNeedsPassword(true)
|
|
125
|
-
return
|
|
127
|
+
setBackupText(parsed.encryptedBackup)
|
|
128
|
+
setNeedsPassword(true)
|
|
129
|
+
return
|
|
126
130
|
}
|
|
127
131
|
if (parsed.encryptedKeys) {
|
|
128
|
-
setBackupText(parsed.encryptedKeys)
|
|
129
|
-
setNeedsPassword(true)
|
|
130
|
-
return
|
|
132
|
+
setBackupText(parsed.encryptedKeys)
|
|
133
|
+
setNeedsPassword(true)
|
|
134
|
+
return
|
|
131
135
|
}
|
|
132
136
|
if (parsed.accounts && parsed.selectedAccount) {
|
|
133
|
-
const account = parsed.accounts[parsed.selectedAccount]
|
|
137
|
+
const account = parsed.accounts[parsed.selectedAccount]
|
|
134
138
|
if (account?.encryptedKeys) {
|
|
135
|
-
setBackupText(account.encryptedKeys)
|
|
136
|
-
setNeedsPassword(true)
|
|
137
|
-
return
|
|
139
|
+
setBackupText(account.encryptedKeys)
|
|
140
|
+
setNeedsPassword(true)
|
|
141
|
+
return
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
} catch {
|
|
@@ -142,55 +146,58 @@ export function WifInput({ onScan, scanning, disabled }: Props) {
|
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
if (looksEncrypted(text)) {
|
|
145
|
-
setNeedsPassword(true)
|
|
146
|
-
return
|
|
149
|
+
setNeedsPassword(true)
|
|
150
|
+
return
|
|
147
151
|
}
|
|
148
152
|
|
|
149
|
-
setError(
|
|
150
|
-
}, [])
|
|
153
|
+
setError('Unrecognized backup format')
|
|
154
|
+
}, [])
|
|
151
155
|
|
|
152
156
|
const handleDecrypt = useCallback(async () => {
|
|
153
|
-
if (!backupText || !password) return
|
|
154
|
-
setDecrypting(true)
|
|
155
|
-
setError(
|
|
157
|
+
if (!backupText || !password) return
|
|
158
|
+
setDecrypting(true)
|
|
159
|
+
setError('')
|
|
156
160
|
|
|
157
161
|
try {
|
|
158
|
-
const decrypted = await decryptBackup(backupText, password)
|
|
159
|
-
const extracted = extractKeys(decrypted)
|
|
162
|
+
const decrypted = await decryptBackup(backupText, password)
|
|
163
|
+
const extracted = extractKeys(decrypted)
|
|
160
164
|
if (!extracted) {
|
|
161
|
-
setError(`Unsupported backup type: ${getBackupType(decrypted)}`)
|
|
162
|
-
return
|
|
165
|
+
setError(`Unsupported backup type: ${getBackupType(decrypted)}`)
|
|
166
|
+
return
|
|
163
167
|
}
|
|
164
|
-
setKeys(extracted)
|
|
165
|
-
setBackupType(getBackupType(decrypted))
|
|
166
|
-
setNeedsPassword(false)
|
|
168
|
+
setKeys(extracted)
|
|
169
|
+
setBackupType(getBackupType(decrypted))
|
|
170
|
+
setNeedsPassword(false)
|
|
167
171
|
} catch (e) {
|
|
168
|
-
setError(e instanceof Error ? e.message :
|
|
172
|
+
setError(e instanceof Error ? e.message : 'Decryption failed')
|
|
169
173
|
} finally {
|
|
170
|
-
setDecrypting(false)
|
|
174
|
+
setDecrypting(false)
|
|
171
175
|
}
|
|
172
|
-
}, [backupText, password])
|
|
176
|
+
}, [backupText, password])
|
|
173
177
|
|
|
174
|
-
const handleFileUpload = useCallback(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
const handleFileUpload = useCallback(
|
|
179
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
180
|
+
const file = e.target.files?.[0]
|
|
181
|
+
if (!file) return
|
|
182
|
+
const reader = new FileReader()
|
|
183
|
+
reader.onload = () => {
|
|
184
|
+
processBackupText((reader.result as string).trim())
|
|
185
|
+
}
|
|
186
|
+
reader.readAsText(file)
|
|
187
|
+
e.target.value = ''
|
|
188
|
+
},
|
|
189
|
+
[processBackupText],
|
|
190
|
+
)
|
|
184
191
|
|
|
185
192
|
const handleScan = useCallback(() => {
|
|
186
193
|
if (keys) {
|
|
187
|
-
onScan(keys)
|
|
188
|
-
} else if (mode ===
|
|
189
|
-
const pay = payWif.trim()
|
|
190
|
-
const ord = sameKey ? pay : ordWif.trim()
|
|
191
|
-
if (pay) onScan({ payPk: pay, ordPk: ord })
|
|
194
|
+
onScan(keys)
|
|
195
|
+
} else if (mode === 'wif') {
|
|
196
|
+
const pay = payWif.trim()
|
|
197
|
+
const ord = sameKey ? pay : ordWif.trim()
|
|
198
|
+
if (pay) onScan({ payPk: pay, ordPk: ord })
|
|
192
199
|
}
|
|
193
|
-
}, [keys, mode, payWif, ordWif, sameKey, onScan])
|
|
200
|
+
}, [keys, mode, payWif, ordWif, sameKey, onScan])
|
|
194
201
|
|
|
195
202
|
if (keys) {
|
|
196
203
|
return (
|
|
@@ -201,30 +208,49 @@ export function WifInput({ onScan, scanning, disabled }: Props) {
|
|
|
201
208
|
<KeyRound className="h-5 w-5" />
|
|
202
209
|
Legacy Keys
|
|
203
210
|
</CardTitle>
|
|
204
|
-
<Button
|
|
211
|
+
<Button
|
|
212
|
+
variant="ghost"
|
|
213
|
+
size="sm"
|
|
214
|
+
className="h-6 w-6 p-0"
|
|
215
|
+
onClick={handleReset}
|
|
216
|
+
>
|
|
205
217
|
<X className="h-3 w-3" />
|
|
206
218
|
</Button>
|
|
207
219
|
</div>
|
|
208
220
|
</CardHeader>
|
|
209
221
|
<CardContent className="space-y-3">
|
|
210
222
|
{backupType && (
|
|
211
|
-
<span className="text-xs px-2 py-0.5 rounded bg-blue-500/20 text-blue-400">
|
|
223
|
+
<span className="text-xs px-2 py-0.5 rounded bg-blue-500/20 text-blue-400">
|
|
224
|
+
{backupType}
|
|
225
|
+
</span>
|
|
212
226
|
)}
|
|
213
227
|
<div className="space-y-1 text-xs text-muted-foreground font-mono">
|
|
214
228
|
<div>Pay: {deriveAddress(keys.payPk)}</div>
|
|
215
|
-
{keys.ordPk !== keys.payPk &&
|
|
216
|
-
|
|
229
|
+
{keys.ordPk !== keys.payPk && (
|
|
230
|
+
<div>Ord: {deriveAddress(keys.ordPk)}</div>
|
|
231
|
+
)}
|
|
232
|
+
{keys.identityPk && keys.identityPk !== keys.payPk && (
|
|
233
|
+
<div>ID: {deriveAddress(keys.identityPk)}</div>
|
|
234
|
+
)}
|
|
217
235
|
</div>
|
|
218
|
-
<Button
|
|
219
|
-
{
|
|
220
|
-
{
|
|
236
|
+
<Button
|
|
237
|
+
onClick={handleScan}
|
|
238
|
+
disabled={disabled || scanning}
|
|
239
|
+
className="w-full"
|
|
240
|
+
>
|
|
241
|
+
{scanning ? (
|
|
242
|
+
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
|
243
|
+
) : (
|
|
244
|
+
<Search className="h-4 w-4 mr-2" />
|
|
245
|
+
)}
|
|
246
|
+
{scanning ? 'Scanning...' : 'Scan for Assets'}
|
|
221
247
|
</Button>
|
|
222
248
|
</CardContent>
|
|
223
249
|
</Card>
|
|
224
|
-
)
|
|
250
|
+
)
|
|
225
251
|
}
|
|
226
252
|
|
|
227
|
-
if (mode ===
|
|
253
|
+
if (mode === 'choose') {
|
|
228
254
|
return (
|
|
229
255
|
<Card>
|
|
230
256
|
<CardHeader>
|
|
@@ -234,23 +260,41 @@ export function WifInput({ onScan, scanning, disabled }: Props) {
|
|
|
234
260
|
</CardTitle>
|
|
235
261
|
</CardHeader>
|
|
236
262
|
<CardContent className="space-y-3">
|
|
237
|
-
<input
|
|
238
|
-
|
|
263
|
+
<input
|
|
264
|
+
ref={fileInputRef}
|
|
265
|
+
type="file"
|
|
266
|
+
accept=".json,.bep,.txt"
|
|
267
|
+
className="hidden"
|
|
268
|
+
onChange={handleFileUpload}
|
|
269
|
+
/>
|
|
270
|
+
<Button
|
|
271
|
+
variant="outline"
|
|
272
|
+
className="w-full gap-2"
|
|
273
|
+
onClick={() => fileInputRef.current?.click()}
|
|
274
|
+
>
|
|
239
275
|
<Upload className="h-4 w-4" />
|
|
240
276
|
Import Backup File
|
|
241
277
|
</Button>
|
|
242
|
-
<Button
|
|
278
|
+
<Button
|
|
279
|
+
variant="outline"
|
|
280
|
+
className="w-full gap-2"
|
|
281
|
+
onClick={() => setMode('backup')}
|
|
282
|
+
>
|
|
243
283
|
Paste Backup Data
|
|
244
284
|
</Button>
|
|
245
|
-
<Button
|
|
285
|
+
<Button
|
|
286
|
+
variant="ghost"
|
|
287
|
+
className="w-full text-xs text-muted-foreground"
|
|
288
|
+
onClick={() => setMode('wif')}
|
|
289
|
+
>
|
|
246
290
|
Enter WIF keys manually
|
|
247
291
|
</Button>
|
|
248
292
|
</CardContent>
|
|
249
293
|
</Card>
|
|
250
|
-
)
|
|
294
|
+
)
|
|
251
295
|
}
|
|
252
296
|
|
|
253
|
-
if (mode ===
|
|
297
|
+
if (mode === 'backup') {
|
|
254
298
|
return (
|
|
255
299
|
<Card>
|
|
256
300
|
<CardHeader>
|
|
@@ -259,7 +303,12 @@ export function WifInput({ onScan, scanning, disabled }: Props) {
|
|
|
259
303
|
<KeyRound className="h-5 w-5" />
|
|
260
304
|
Import Backup
|
|
261
305
|
</CardTitle>
|
|
262
|
-
<Button
|
|
306
|
+
<Button
|
|
307
|
+
variant="ghost"
|
|
308
|
+
size="sm"
|
|
309
|
+
className="h-6 w-6 p-0"
|
|
310
|
+
onClick={handleReset}
|
|
311
|
+
>
|
|
263
312
|
<X className="h-3 w-3" />
|
|
264
313
|
</Button>
|
|
265
314
|
</div>
|
|
@@ -272,26 +321,53 @@ export function WifInput({ onScan, scanning, disabled }: Props) {
|
|
|
272
321
|
className="w-full h-24 rounded-md border border-input bg-transparent px-3 py-2 text-sm font-mono resize-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
273
322
|
onChange={(e) => processBackupText(e.target.value.trim())}
|
|
274
323
|
/>
|
|
275
|
-
<input
|
|
276
|
-
|
|
324
|
+
<input
|
|
325
|
+
ref={fileInputRef}
|
|
326
|
+
type="file"
|
|
327
|
+
accept=".json,.bep,.txt"
|
|
328
|
+
className="hidden"
|
|
329
|
+
onChange={handleFileUpload}
|
|
330
|
+
/>
|
|
331
|
+
<Button
|
|
332
|
+
variant="outline"
|
|
333
|
+
size="sm"
|
|
334
|
+
className="w-full gap-2"
|
|
335
|
+
onClick={() => fileInputRef.current?.click()}
|
|
336
|
+
>
|
|
277
337
|
<Upload className="h-4 w-4" />
|
|
278
338
|
Or upload a file
|
|
279
339
|
</Button>
|
|
280
340
|
</>
|
|
281
341
|
) : (
|
|
282
342
|
<>
|
|
283
|
-
<p className="text-sm text-muted-foreground">
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
343
|
+
<p className="text-sm text-muted-foreground">
|
|
344
|
+
This backup is encrypted. Enter the password to decrypt.
|
|
345
|
+
</p>
|
|
346
|
+
<Input
|
|
347
|
+
type="password"
|
|
348
|
+
placeholder="Backup password..."
|
|
349
|
+
value={password}
|
|
350
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
351
|
+
onKeyDown={(e) => {
|
|
352
|
+
if (e.key === 'Enter') handleDecrypt()
|
|
353
|
+
}}
|
|
354
|
+
/>
|
|
355
|
+
<Button
|
|
356
|
+
onClick={handleDecrypt}
|
|
357
|
+
disabled={!password || decrypting}
|
|
358
|
+
className="w-full"
|
|
359
|
+
>
|
|
360
|
+
{decrypting ? (
|
|
361
|
+
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
|
362
|
+
) : null}
|
|
363
|
+
{decrypting ? 'Decrypting...' : 'Decrypt'}
|
|
288
364
|
</Button>
|
|
289
365
|
</>
|
|
290
366
|
)}
|
|
291
367
|
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
292
368
|
</CardContent>
|
|
293
369
|
</Card>
|
|
294
|
-
)
|
|
370
|
+
)
|
|
295
371
|
}
|
|
296
372
|
|
|
297
373
|
return (
|
|
@@ -302,31 +378,63 @@ export function WifInput({ onScan, scanning, disabled }: Props) {
|
|
|
302
378
|
<KeyRound className="h-5 w-5" />
|
|
303
379
|
Manual WIF Entry
|
|
304
380
|
</CardTitle>
|
|
305
|
-
<Button
|
|
381
|
+
<Button
|
|
382
|
+
variant="ghost"
|
|
383
|
+
size="sm"
|
|
384
|
+
className="h-6 w-6 p-0"
|
|
385
|
+
onClick={handleReset}
|
|
386
|
+
>
|
|
306
387
|
<X className="h-3 w-3" />
|
|
307
388
|
</Button>
|
|
308
389
|
</div>
|
|
309
390
|
</CardHeader>
|
|
310
391
|
<CardContent className="space-y-4">
|
|
311
392
|
<div className="space-y-2">
|
|
312
|
-
<label className="text-sm font-medium">
|
|
313
|
-
|
|
393
|
+
<label className="text-sm font-medium">
|
|
394
|
+
{sameKey ? 'Private Key (WIF)' : 'Pay Key (WIF)'}
|
|
395
|
+
</label>
|
|
396
|
+
<Input
|
|
397
|
+
type="password"
|
|
398
|
+
placeholder="Enter WIF private key..."
|
|
399
|
+
value={payWif}
|
|
400
|
+
onChange={(e) => setPayWif(e.target.value)}
|
|
401
|
+
disabled={disabled || scanning}
|
|
402
|
+
/>
|
|
314
403
|
</div>
|
|
315
404
|
<label className="flex items-center gap-2 text-sm">
|
|
316
|
-
<input
|
|
405
|
+
<input
|
|
406
|
+
type="checkbox"
|
|
407
|
+
checked={sameKey}
|
|
408
|
+
onChange={(e) => setSameKey(e.target.checked)}
|
|
409
|
+
disabled={disabled || scanning}
|
|
410
|
+
/>
|
|
317
411
|
Same key for pay and ordinals
|
|
318
412
|
</label>
|
|
319
413
|
{!sameKey && (
|
|
320
414
|
<div className="space-y-2">
|
|
321
415
|
<label className="text-sm font-medium">Ordinals Key (WIF)</label>
|
|
322
|
-
<Input
|
|
416
|
+
<Input
|
|
417
|
+
type="password"
|
|
418
|
+
placeholder="Enter ordinals WIF..."
|
|
419
|
+
value={ordWif}
|
|
420
|
+
onChange={(e) => setOrdWif(e.target.value)}
|
|
421
|
+
disabled={disabled || scanning}
|
|
422
|
+
/>
|
|
323
423
|
</div>
|
|
324
424
|
)}
|
|
325
|
-
<Button
|
|
326
|
-
{
|
|
327
|
-
{
|
|
425
|
+
<Button
|
|
426
|
+
onClick={handleScan}
|
|
427
|
+
disabled={disabled || scanning || !payWif.trim()}
|
|
428
|
+
className="w-full"
|
|
429
|
+
>
|
|
430
|
+
{scanning ? (
|
|
431
|
+
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
|
432
|
+
) : (
|
|
433
|
+
<Search className="h-4 w-4 mr-2" />
|
|
434
|
+
)}
|
|
435
|
+
{scanning ? 'Scanning...' : 'Scan for Assets'}
|
|
328
436
|
</Button>
|
|
329
437
|
</CardContent>
|
|
330
438
|
</Card>
|
|
331
|
-
)
|
|
439
|
+
)
|
|
332
440
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,28 +1,63 @@
|
|
|
1
1
|
// Main app component
|
|
2
|
-
export { SweepApp, type SweepAppProps } from
|
|
2
|
+
export { SweepApp, type SweepAppProps } from './components/SweepApp'
|
|
3
3
|
|
|
4
4
|
// Feature components
|
|
5
|
-
export { ConnectWallet } from
|
|
6
|
-
export { WifInput } from
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
export { ConnectWallet } from './components/connect-wallet'
|
|
6
|
+
export { WifInput } from './components/wif-input'
|
|
7
|
+
export {
|
|
8
|
+
FundingSection,
|
|
9
|
+
OrdinalsSection,
|
|
10
|
+
Bsv21Section,
|
|
11
|
+
Bsv20Section,
|
|
12
|
+
LockedSection,
|
|
13
|
+
RunSection,
|
|
14
|
+
} from './components/asset-preview'
|
|
15
|
+
export { OpnsSection } from './components/opns-section'
|
|
16
|
+
export { TxHistory, type TxRecord } from './components/tx-history'
|
|
17
|
+
export { SweepProgress } from './components/sweep-progress'
|
|
11
18
|
|
|
12
19
|
// UI primitives
|
|
13
|
-
export { Badge, badgeVariants } from
|
|
14
|
-
export { Button, buttonVariants } from
|
|
15
|
-
export {
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
export { Badge, badgeVariants } from './components/ui/badge'
|
|
21
|
+
export { Button, buttonVariants } from './components/ui/button'
|
|
22
|
+
export {
|
|
23
|
+
Card,
|
|
24
|
+
CardHeader,
|
|
25
|
+
CardFooter,
|
|
26
|
+
CardTitle,
|
|
27
|
+
CardAction,
|
|
28
|
+
CardDescription,
|
|
29
|
+
CardContent,
|
|
30
|
+
} from './components/ui/card'
|
|
31
|
+
export { Input } from './components/ui/input'
|
|
32
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './components/ui/tabs'
|
|
18
33
|
|
|
19
34
|
// Lib
|
|
20
|
-
export { configureServices, getServices } from
|
|
21
|
-
export {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
export { configureServices, getServices } from './lib/services'
|
|
36
|
+
export {
|
|
37
|
+
connectWallet,
|
|
38
|
+
getWallet,
|
|
39
|
+
getIdentityKey,
|
|
40
|
+
getProvider,
|
|
41
|
+
disconnectWallet,
|
|
42
|
+
isConnected,
|
|
43
|
+
} from './lib/wallet'
|
|
44
|
+
export {
|
|
45
|
+
deriveAddress,
|
|
46
|
+
scanAddress,
|
|
47
|
+
scanAddresses,
|
|
48
|
+
type ScannedAssets,
|
|
49
|
+
type EnrichedOrdinal,
|
|
50
|
+
type TokenBalance,
|
|
51
|
+
type ScanProgress,
|
|
52
|
+
} from './lib/scanner'
|
|
53
|
+
export { executeSweep, type SweepResult } from './lib/sweeper'
|
|
54
|
+
export {
|
|
55
|
+
legacySendBsv,
|
|
56
|
+
legacySendOrdinals,
|
|
57
|
+
legacyBurnOrdinals,
|
|
58
|
+
type LegacySendResult,
|
|
59
|
+
} from './lib/legacy-send'
|
|
60
|
+
export { cn, formatSats, formatTokenAmount, truncate } from './lib/utils'
|
|
26
61
|
|
|
27
62
|
// Types
|
|
28
|
-
export type { LegacyKeys } from
|
|
63
|
+
export type { LegacyKeys } from './types'
|