@1sat/sweep-ui 0.0.1
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 +6 -0
- package/dist/components/SweepApp.d.ts.map +1 -0
- package/dist/components/asset-preview.d.ts +32 -0
- package/dist/components/asset-preview.d.ts.map +1 -0
- package/dist/components/connect-wallet.d.ts +8 -0
- package/dist/components/connect-wallet.d.ts.map +1 -0
- package/dist/components/opns-section.d.ts +13 -0
- package/dist/components/opns-section.d.ts.map +1 -0
- package/dist/components/sweep-progress.d.ts +9 -0
- package/dist/components/sweep-progress.d.ts.map +1 -0
- package/dist/components/tx-history.d.ts +14 -0
- package/dist/components/tx-history.d.ts.map +1 -0
- package/dist/components/ui/badge.d.ts +8 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/button.d.ts +9 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/card.d.ts +10 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/input.d.ts +4 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/tabs.d.ts +14 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/wif-input.d.ts +9 -0
- package/dist/components/wif-input.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2556 -0
- package/dist/lib/legacy-send.d.ts +24 -0
- package/dist/lib/legacy-send.d.ts.map +1 -0
- package/dist/lib/scanner.d.ts +33 -0
- package/dist/lib/scanner.d.ts.map +1 -0
- package/dist/lib/services.d.ts +4 -0
- package/dist/lib/services.d.ts.map +1 -0
- package/dist/lib/sweeper.d.ts +18 -0
- package/dist/lib/sweeper.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +6 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/wallet.d.ts +9 -0
- package/dist/lib/wallet.d.ts.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +44 -0
- package/src/components/SweepApp.tsx +269 -0
- package/src/components/asset-preview.tsx +224 -0
- package/src/components/connect-wallet.tsx +59 -0
- package/src/components/opns-section.tsx +108 -0
- package/src/components/sweep-progress.tsx +69 -0
- package/src/components/tx-history.tsx +63 -0
- package/src/components/ui/badge.tsx +39 -0
- package/src/components/ui/button.tsx +52 -0
- package/src/components/ui/card.tsx +32 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/tabs.tsx +51 -0
- package/src/components/wif-input.tsx +332 -0
- package/src/index.ts +28 -0
- package/src/lib/legacy-send.ts +234 -0
- package/src/lib/scanner.ts +226 -0
- package/src/lib/services.ts +18 -0
- package/src/lib/sweeper.ts +93 -0
- package/src/lib/utils.ts +23 -0
- package/src/lib/wallet.ts +32 -0
- package/src/types.ts +5 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2556 @@
|
|
|
1
|
+
// src/components/SweepApp.tsx
|
|
2
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useState as useState5 } from "react";
|
|
3
|
+
import { Toaster, toast } from "sonner";
|
|
4
|
+
|
|
5
|
+
// src/components/ui/badge.tsx
|
|
6
|
+
import { cva } from "class-variance-authority";
|
|
7
|
+
|
|
8
|
+
// src/lib/utils.ts
|
|
9
|
+
import { clsx } from "clsx";
|
|
10
|
+
import { twMerge } from "tailwind-merge";
|
|
11
|
+
function cn(...inputs) {
|
|
12
|
+
return twMerge(clsx(inputs));
|
|
13
|
+
}
|
|
14
|
+
function formatSats(sats) {
|
|
15
|
+
return sats.toLocaleString();
|
|
16
|
+
}
|
|
17
|
+
function formatTokenAmount(rawAmount, decimals) {
|
|
18
|
+
if (decimals === 0)
|
|
19
|
+
return rawAmount;
|
|
20
|
+
const padded = rawAmount.padStart(decimals + 1, "0");
|
|
21
|
+
const intPart = padded.slice(0, -decimals) || "0";
|
|
22
|
+
const decPart = padded.slice(-decimals).replace(/0+$/, "");
|
|
23
|
+
return decPart ? `${intPart}.${decPart}` : intPart;
|
|
24
|
+
}
|
|
25
|
+
function truncate(s, len = 8) {
|
|
26
|
+
if (s.length <= len * 2 + 3)
|
|
27
|
+
return s;
|
|
28
|
+
return `${s.slice(0, len)}...${s.slice(-len)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/components/ui/badge.tsx
|
|
32
|
+
import { jsx } from "react/jsx-runtime";
|
|
33
|
+
var badgeVariants = cva("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", {
|
|
34
|
+
variants: {
|
|
35
|
+
variant: {
|
|
36
|
+
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
37
|
+
secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
38
|
+
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",
|
|
39
|
+
outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
40
|
+
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
41
|
+
link: "text-primary underline-offset-4 [a&]:hover:underline"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
defaultVariants: {
|
|
45
|
+
variant: "default"
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
function Badge({
|
|
49
|
+
className,
|
|
50
|
+
variant = "default",
|
|
51
|
+
...props
|
|
52
|
+
}) {
|
|
53
|
+
return /* @__PURE__ */ jsx("span", {
|
|
54
|
+
"data-slot": "badge",
|
|
55
|
+
"data-variant": variant,
|
|
56
|
+
className: cn(badgeVariants({ variant }), className),
|
|
57
|
+
...props
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/components/ui/tabs.tsx
|
|
62
|
+
import * as React from "react";
|
|
63
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
64
|
+
var TabsContext = React.createContext(null);
|
|
65
|
+
function useTabsContext() {
|
|
66
|
+
const context = React.useContext(TabsContext);
|
|
67
|
+
if (!context) {
|
|
68
|
+
throw new Error("Tabs components must be used within a <Tabs> provider");
|
|
69
|
+
}
|
|
70
|
+
return context;
|
|
71
|
+
}
|
|
72
|
+
function Tabs({ value, onValueChange, className, ...props }) {
|
|
73
|
+
return /* @__PURE__ */ jsx2(TabsContext.Provider, {
|
|
74
|
+
value: { value, onValueChange },
|
|
75
|
+
children: /* @__PURE__ */ jsx2("div", {
|
|
76
|
+
"data-slot": "tabs",
|
|
77
|
+
className: cn("flex flex-col gap-2", className),
|
|
78
|
+
...props
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function TabsList({ className, ...props }) {
|
|
83
|
+
return /* @__PURE__ */ jsx2("div", {
|
|
84
|
+
"data-slot": "tabs-list",
|
|
85
|
+
className: cn("inline-flex h-9 items-center justify-start gap-1 rounded-lg bg-muted p-1 text-muted-foreground", className),
|
|
86
|
+
...props
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function TabsTrigger({ value, className, ...props }) {
|
|
90
|
+
const context = useTabsContext();
|
|
91
|
+
const isActive = context.value === value;
|
|
92
|
+
return /* @__PURE__ */ jsx2("button", {
|
|
93
|
+
"data-slot": "tabs-trigger",
|
|
94
|
+
"data-active": isActive ? "" : undefined,
|
|
95
|
+
className: cn("inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50", isActive && "bg-background text-foreground shadow-sm", className),
|
|
96
|
+
onClick: () => context.onValueChange(value),
|
|
97
|
+
...props
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function TabsContent({ value, className, ...props }) {
|
|
101
|
+
const context = useTabsContext();
|
|
102
|
+
if (context.value !== value)
|
|
103
|
+
return null;
|
|
104
|
+
return /* @__PURE__ */ jsx2("div", {
|
|
105
|
+
"data-slot": "tabs-content",
|
|
106
|
+
className: cn("mt-2", className),
|
|
107
|
+
...props
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/components/connect-wallet.tsx
|
|
112
|
+
import { useState } from "react";
|
|
113
|
+
import { Loader2, Wallet, X } from "lucide-react";
|
|
114
|
+
|
|
115
|
+
// src/components/ui/button.tsx
|
|
116
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
117
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
118
|
+
var buttonVariants = cva2("inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
119
|
+
variants: {
|
|
120
|
+
variant: {
|
|
121
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
122
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
123
|
+
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
124
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
125
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
126
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
127
|
+
},
|
|
128
|
+
size: {
|
|
129
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
130
|
+
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
131
|
+
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
|
132
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
133
|
+
icon: "size-9",
|
|
134
|
+
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
135
|
+
"icon-sm": "size-8",
|
|
136
|
+
"icon-lg": "size-10"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
defaultVariants: {
|
|
140
|
+
variant: "default",
|
|
141
|
+
size: "default"
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
function Button({
|
|
145
|
+
className,
|
|
146
|
+
variant = "default",
|
|
147
|
+
size = "default",
|
|
148
|
+
...props
|
|
149
|
+
}) {
|
|
150
|
+
return /* @__PURE__ */ jsx3("button", {
|
|
151
|
+
"data-slot": "button",
|
|
152
|
+
"data-variant": variant,
|
|
153
|
+
"data-size": size,
|
|
154
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
155
|
+
...props
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/lib/wallet.ts
|
|
160
|
+
import { connectWallet as connect } from "@1sat/connect";
|
|
161
|
+
var connection = null;
|
|
162
|
+
async function connectWallet() {
|
|
163
|
+
const result = await connect();
|
|
164
|
+
if (!result)
|
|
165
|
+
throw new Error("No wallet available");
|
|
166
|
+
connection = result;
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
function getWallet() {
|
|
170
|
+
return connection?.wallet ?? null;
|
|
171
|
+
}
|
|
172
|
+
function getIdentityKey() {
|
|
173
|
+
return connection?.identityKey ?? null;
|
|
174
|
+
}
|
|
175
|
+
function getProvider() {
|
|
176
|
+
return connection?.provider ?? null;
|
|
177
|
+
}
|
|
178
|
+
function disconnectWallet() {
|
|
179
|
+
connection?.disconnect();
|
|
180
|
+
connection = null;
|
|
181
|
+
}
|
|
182
|
+
function isConnected() {
|
|
183
|
+
return connection !== null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/components/connect-wallet.tsx
|
|
187
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
188
|
+
function ConnectWallet({ onConnected, onDisconnected, connected }) {
|
|
189
|
+
const [connecting, setConnecting] = useState(false);
|
|
190
|
+
const [error, setError] = useState(null);
|
|
191
|
+
async function handleConnect() {
|
|
192
|
+
setConnecting(true);
|
|
193
|
+
setError(null);
|
|
194
|
+
try {
|
|
195
|
+
await connectWallet();
|
|
196
|
+
onConnected();
|
|
197
|
+
} catch (e) {
|
|
198
|
+
setError(e instanceof Error ? e.message : "Failed to connect wallet");
|
|
199
|
+
} finally {
|
|
200
|
+
setConnecting(false);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function handleDisconnect() {
|
|
204
|
+
disconnectWallet();
|
|
205
|
+
onDisconnected();
|
|
206
|
+
}
|
|
207
|
+
if (connected) {
|
|
208
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
209
|
+
className: "flex items-center justify-between px-3 py-2 rounded-lg border border-green-500/20 bg-green-500/5",
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ jsxs("div", {
|
|
212
|
+
className: "flex items-center gap-2 text-sm",
|
|
213
|
+
children: [
|
|
214
|
+
/* @__PURE__ */ jsx4("span", {
|
|
215
|
+
className: "h-2 w-2 rounded-full bg-green-500"
|
|
216
|
+
}),
|
|
217
|
+
/* @__PURE__ */ jsxs("span", {
|
|
218
|
+
className: "text-muted-foreground",
|
|
219
|
+
children: [
|
|
220
|
+
getProvider() === "brc100" ? "BRC-100" : "OneSat",
|
|
221
|
+
" · ",
|
|
222
|
+
getIdentityKey()?.slice(0, 12),
|
|
223
|
+
"..."
|
|
224
|
+
]
|
|
225
|
+
})
|
|
226
|
+
]
|
|
227
|
+
}),
|
|
228
|
+
/* @__PURE__ */ jsx4(Button, {
|
|
229
|
+
variant: "ghost",
|
|
230
|
+
size: "sm",
|
|
231
|
+
className: "h-6 w-6 p-0",
|
|
232
|
+
onClick: handleDisconnect,
|
|
233
|
+
children: /* @__PURE__ */ jsx4(X, {
|
|
234
|
+
className: "h-3 w-3"
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
]
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
241
|
+
className: "space-y-1",
|
|
242
|
+
children: [
|
|
243
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
244
|
+
variant: "outline",
|
|
245
|
+
size: "sm",
|
|
246
|
+
className: "w-full text-xs gap-2",
|
|
247
|
+
onClick: handleConnect,
|
|
248
|
+
disabled: connecting,
|
|
249
|
+
children: [
|
|
250
|
+
connecting ? /* @__PURE__ */ jsx4(Loader2, {
|
|
251
|
+
className: "h-3 w-3 animate-spin"
|
|
252
|
+
}) : /* @__PURE__ */ jsx4(Wallet, {
|
|
253
|
+
className: "h-3 w-3"
|
|
254
|
+
}),
|
|
255
|
+
connecting ? "Connecting..." : "Connect BRC-100 Wallet (optional)"
|
|
256
|
+
]
|
|
257
|
+
}),
|
|
258
|
+
error && /* @__PURE__ */ jsx4("p", {
|
|
259
|
+
className: "text-xs text-destructive text-center",
|
|
260
|
+
children: error
|
|
261
|
+
})
|
|
262
|
+
]
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/components/wif-input.tsx
|
|
267
|
+
import { useCallback, useRef, useState as useState2 } from "react";
|
|
268
|
+
import { KeyRound, Loader2 as Loader22, Search, Upload, X as X2 } from "lucide-react";
|
|
269
|
+
|
|
270
|
+
// src/components/ui/card.tsx
|
|
271
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
272
|
+
function Card({ className, ...props }) {
|
|
273
|
+
return /* @__PURE__ */ jsx5("div", {
|
|
274
|
+
"data-slot": "card",
|
|
275
|
+
className: cn("flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm", className),
|
|
276
|
+
...props
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function CardHeader({ className, ...props }) {
|
|
280
|
+
return /* @__PURE__ */ jsx5("div", {
|
|
281
|
+
"data-slot": "card-header",
|
|
282
|
+
className: cn("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", className),
|
|
283
|
+
...props
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function CardTitle({ className, ...props }) {
|
|
287
|
+
return /* @__PURE__ */ jsx5("div", {
|
|
288
|
+
"data-slot": "card-title",
|
|
289
|
+
className: cn("leading-none font-semibold", className),
|
|
290
|
+
...props
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function CardDescription({ className, ...props }) {
|
|
294
|
+
return /* @__PURE__ */ jsx5("div", {
|
|
295
|
+
"data-slot": "card-description",
|
|
296
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
297
|
+
...props
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function CardAction({ className, ...props }) {
|
|
301
|
+
return /* @__PURE__ */ jsx5("div", {
|
|
302
|
+
"data-slot": "card-action",
|
|
303
|
+
className: cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className),
|
|
304
|
+
...props
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
function CardContent({ className, ...props }) {
|
|
308
|
+
return /* @__PURE__ */ jsx5("div", {
|
|
309
|
+
"data-slot": "card-content",
|
|
310
|
+
className: cn("px-6", className),
|
|
311
|
+
...props
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function CardFooter({ className, ...props }) {
|
|
315
|
+
return /* @__PURE__ */ jsx5("div", {
|
|
316
|
+
"data-slot": "card-footer",
|
|
317
|
+
className: cn("flex items-center px-6 [.border-t]:pt-6", className),
|
|
318
|
+
...props
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/components/ui/input.tsx
|
|
323
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
324
|
+
function Input({ className, type, ...props }) {
|
|
325
|
+
return /* @__PURE__ */ jsx6("input", {
|
|
326
|
+
type,
|
|
327
|
+
"data-slot": "input",
|
|
328
|
+
className: cn("h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30", "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", className),
|
|
329
|
+
...props
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/components/wif-input.tsx
|
|
334
|
+
import {
|
|
335
|
+
decryptBackup,
|
|
336
|
+
isOneSatBackup,
|
|
337
|
+
isYoursWalletBackup,
|
|
338
|
+
isWifBackup,
|
|
339
|
+
getBackupType
|
|
340
|
+
} from "bitcoin-backup";
|
|
341
|
+
|
|
342
|
+
// src/lib/scanner.ts
|
|
343
|
+
import { PrivateKey } from "@bsv/sdk";
|
|
344
|
+
|
|
345
|
+
// src/lib/services.ts
|
|
346
|
+
import { OneSatServices } from "@1sat/client";
|
|
347
|
+
var _services = null;
|
|
348
|
+
var _baseUrl;
|
|
349
|
+
function configureServices(baseUrl) {
|
|
350
|
+
_baseUrl = baseUrl;
|
|
351
|
+
_services = null;
|
|
352
|
+
}
|
|
353
|
+
function getServices() {
|
|
354
|
+
if (!_services) {
|
|
355
|
+
const url = _baseUrl ?? (typeof window !== "undefined" ? window.location.origin : "");
|
|
356
|
+
if (!url)
|
|
357
|
+
throw new Error("No base URL configured. Call configureServices() first.");
|
|
358
|
+
_services = new OneSatServices("main", url);
|
|
359
|
+
}
|
|
360
|
+
return _services;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/lib/scanner.ts
|
|
364
|
+
function deriveAddress(wif) {
|
|
365
|
+
return PrivateKey.fromWif(wif.trim()).toPublicKey().toAddress();
|
|
366
|
+
}
|
|
367
|
+
function getEvent(events, prefix) {
|
|
368
|
+
const e = events.find((e2) => e2.startsWith(prefix));
|
|
369
|
+
return e ? e.slice(prefix.length) : undefined;
|
|
370
|
+
}
|
|
371
|
+
function getEvents(events, prefix) {
|
|
372
|
+
return events.filter((e) => e.startsWith(prefix)).map((e) => e.slice(prefix.length));
|
|
373
|
+
}
|
|
374
|
+
function enrichOrdinal(out) {
|
|
375
|
+
const events = out.events ?? [];
|
|
376
|
+
const origin = getEvent(events, "origin:");
|
|
377
|
+
const types = getEvents(events, "type:");
|
|
378
|
+
const contentType = types.find((t) => t.includes("/")) ?? types[0];
|
|
379
|
+
const name = getEvent(events, "name:");
|
|
380
|
+
const contentUrl = getServices().ordfs.getContentUrl(origin ?? out.outpoint, { raw: true });
|
|
381
|
+
return { ...out, origin, contentType, name, contentUrl };
|
|
382
|
+
}
|
|
383
|
+
function resolveIconOutpoint(tokenId, icon) {
|
|
384
|
+
if (!icon)
|
|
385
|
+
return;
|
|
386
|
+
if (icon.startsWith("_")) {
|
|
387
|
+
const txid = tokenId.split("_")[0];
|
|
388
|
+
return `${txid}${icon}`;
|
|
389
|
+
}
|
|
390
|
+
return icon;
|
|
391
|
+
}
|
|
392
|
+
async function groupBsv21Tokens(outputs) {
|
|
393
|
+
const groups = new Map;
|
|
394
|
+
for (const out of outputs) {
|
|
395
|
+
const events = out.events ?? [];
|
|
396
|
+
const tokenId = getEvent(events, "bsv21:");
|
|
397
|
+
if (!tokenId)
|
|
398
|
+
continue;
|
|
399
|
+
const amtStr = getEvent(events, "amt:");
|
|
400
|
+
const amount = amtStr ? BigInt(amtStr) : 0n;
|
|
401
|
+
let group = groups.get(tokenId);
|
|
402
|
+
if (!group) {
|
|
403
|
+
group = { outputs: [], totalAmount: 0n };
|
|
404
|
+
groups.set(tokenId, group);
|
|
405
|
+
}
|
|
406
|
+
group.outputs.push(out);
|
|
407
|
+
group.totalAmount += amount;
|
|
408
|
+
}
|
|
409
|
+
if (groups.size === 0)
|
|
410
|
+
return [];
|
|
411
|
+
const services = getServices();
|
|
412
|
+
const tokenIds = [...groups.keys()];
|
|
413
|
+
let details = [];
|
|
414
|
+
try {
|
|
415
|
+
details = await services.bsv21.lookupTokens(tokenIds);
|
|
416
|
+
} catch {}
|
|
417
|
+
const detailMap = new Map(details.map((d) => [d.tokenId, d]));
|
|
418
|
+
const balances = [];
|
|
419
|
+
for (const [tokenId, group] of groups) {
|
|
420
|
+
const detail = detailMap.get(tokenId);
|
|
421
|
+
const iconOutpoint = resolveIconOutpoint(tokenId, detail?.token?.icon);
|
|
422
|
+
balances.push({
|
|
423
|
+
tokenId,
|
|
424
|
+
symbol: detail?.token?.sym,
|
|
425
|
+
icon: iconOutpoint ? services.ordfs.getContentUrl(iconOutpoint) : "",
|
|
426
|
+
decimals: Number(detail?.token?.dec ?? 0),
|
|
427
|
+
totalAmount: group.totalAmount,
|
|
428
|
+
outputs: group.outputs,
|
|
429
|
+
isActive: detail?.status?.is_active ?? false
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return balances;
|
|
433
|
+
}
|
|
434
|
+
async function categorizeOutputs(outputs) {
|
|
435
|
+
const funding = [];
|
|
436
|
+
const rawOrdinals = [];
|
|
437
|
+
const opnsRaw = [];
|
|
438
|
+
const bsv21Raw = [];
|
|
439
|
+
const bsv20Tokens = [];
|
|
440
|
+
const locked = [];
|
|
441
|
+
for (const out of outputs) {
|
|
442
|
+
const events = out.events ?? [];
|
|
443
|
+
const sats = out.satoshis ?? 0;
|
|
444
|
+
if (events.some((e) => e.startsWith("bsv21:"))) {
|
|
445
|
+
bsv21Raw.push(out);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (events.some((e) => e.startsWith("lock:"))) {
|
|
449
|
+
locked.push(out);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (events.some((e) => e === "type:application/bsv-20" || e === "type:Token")) {
|
|
453
|
+
bsv20Tokens.push(out);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (sats === 1) {
|
|
457
|
+
if (events.some((e) => e === "type:application/op-ns")) {
|
|
458
|
+
opnsRaw.push(out);
|
|
459
|
+
} else {
|
|
460
|
+
rawOrdinals.push(out);
|
|
461
|
+
}
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (sats > 1) {
|
|
465
|
+
funding.push(out);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
funding,
|
|
470
|
+
ordinals: rawOrdinals.map(enrichOrdinal),
|
|
471
|
+
opnsNames: opnsRaw.map(enrichOrdinal),
|
|
472
|
+
bsv21Tokens: await groupBsv21Tokens(bsv21Raw),
|
|
473
|
+
bsv20Tokens,
|
|
474
|
+
locked,
|
|
475
|
+
totalBsv: funding.reduce((sum, o) => sum + (o.satoshis ?? 0), 0)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
async function scanAddress(address, onProgress) {
|
|
479
|
+
const services = getServices();
|
|
480
|
+
onProgress?.({ phase: "sync", detail: "Syncing address..." });
|
|
481
|
+
for await (const event of services.owner.getTxos(address, { refresh: true, limit: 1 })) {
|
|
482
|
+
if (event.type === "sync") {
|
|
483
|
+
const p = event.data;
|
|
484
|
+
onProgress?.({
|
|
485
|
+
phase: "sync",
|
|
486
|
+
detail: `${p.phase}: ${p.processed ?? 0}/${p.total ?? "?"}`
|
|
487
|
+
});
|
|
488
|
+
} else if (event.type === "done" || event.type === "error") {
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
onProgress?.({ phase: "search", detail: "Searching for assets..." });
|
|
493
|
+
const allOutputs = await services.txo.search(`own:${address}`, {
|
|
494
|
+
unspent: true,
|
|
495
|
+
events: true,
|
|
496
|
+
sats: true,
|
|
497
|
+
limit: 0
|
|
498
|
+
});
|
|
499
|
+
onProgress?.({ phase: "categorize", detail: "Loading token details..." });
|
|
500
|
+
return await categorizeOutputs(allOutputs);
|
|
501
|
+
}
|
|
502
|
+
async function scanAddresses(addresses, onProgress) {
|
|
503
|
+
const unique = [...new Set(addresses)];
|
|
504
|
+
const allResults = [];
|
|
505
|
+
for (const addr of unique) {
|
|
506
|
+
onProgress?.({ phase: "sync", detail: `Scanning ${addr.slice(0, 8)}...` });
|
|
507
|
+
allResults.push(await scanAddress(addr, onProgress));
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
funding: allResults.flatMap((r) => r.funding),
|
|
511
|
+
ordinals: allResults.flatMap((r) => r.ordinals),
|
|
512
|
+
opnsNames: allResults.flatMap((r) => r.opnsNames),
|
|
513
|
+
bsv21Tokens: allResults.flatMap((r) => r.bsv21Tokens),
|
|
514
|
+
bsv20Tokens: allResults.flatMap((r) => r.bsv20Tokens),
|
|
515
|
+
locked: allResults.flatMap((r) => r.locked),
|
|
516
|
+
totalBsv: allResults.reduce((sum, r) => sum + r.totalBsv, 0)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/components/wif-input.tsx
|
|
521
|
+
import { deriveIdentityKey } from "@1sat/utils";
|
|
522
|
+
import { jsx as jsx7, jsxs as jsxs2, Fragment } from "react/jsx-runtime";
|
|
523
|
+
function withIdentityKey(keys) {
|
|
524
|
+
if (keys.identityPk)
|
|
525
|
+
return keys;
|
|
526
|
+
return { ...keys, identityPk: deriveIdentityKey(keys.payPk, keys.ordPk).toWif() };
|
|
527
|
+
}
|
|
528
|
+
function extractKeys(backup) {
|
|
529
|
+
if (isOneSatBackup(backup)) {
|
|
530
|
+
return withIdentityKey({
|
|
531
|
+
payPk: backup.payPk,
|
|
532
|
+
ordPk: backup.ordPk,
|
|
533
|
+
identityPk: backup.identityPk
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (isYoursWalletBackup(backup)) {
|
|
537
|
+
return withIdentityKey({
|
|
538
|
+
payPk: backup.payPk,
|
|
539
|
+
ordPk: backup.ordPk,
|
|
540
|
+
identityPk: backup.identityPk
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
if (isWifBackup(backup)) {
|
|
544
|
+
return withIdentityKey({
|
|
545
|
+
payPk: backup.wif,
|
|
546
|
+
ordPk: backup.wif
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
function tryParseBackup(text) {
|
|
552
|
+
try {
|
|
553
|
+
const parsed = JSON.parse(text);
|
|
554
|
+
if (parsed.payPk && parsed.ordPk) {
|
|
555
|
+
return withIdentityKey({
|
|
556
|
+
payPk: parsed.payPk,
|
|
557
|
+
ordPk: parsed.ordPk,
|
|
558
|
+
identityPk: parsed.identityPk
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
if (parsed.accounts && parsed.selectedAccount) {
|
|
562
|
+
const account = parsed.accounts[parsed.selectedAccount];
|
|
563
|
+
if (account?.encryptedKeys) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
} catch {}
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
function looksEncrypted(text) {
|
|
571
|
+
if (text.startsWith("{"))
|
|
572
|
+
return false;
|
|
573
|
+
if (text.startsWith("5") || text.startsWith("K") || text.startsWith("L"))
|
|
574
|
+
return false;
|
|
575
|
+
return text.length > 50;
|
|
576
|
+
}
|
|
577
|
+
function WifInput({ onScan, scanning, disabled }) {
|
|
578
|
+
const [mode, setMode] = useState2("choose");
|
|
579
|
+
const [keys, setKeys] = useState2(null);
|
|
580
|
+
const [backupText, setBackupText] = useState2("");
|
|
581
|
+
const [password, setPassword] = useState2("");
|
|
582
|
+
const [needsPassword, setNeedsPassword] = useState2(false);
|
|
583
|
+
const [decrypting, setDecrypting] = useState2(false);
|
|
584
|
+
const [error, setError] = useState2("");
|
|
585
|
+
const [backupType, setBackupType] = useState2("");
|
|
586
|
+
const fileInputRef = useRef(null);
|
|
587
|
+
const [payWif, setPayWif] = useState2("");
|
|
588
|
+
const [ordWif, setOrdWif] = useState2("");
|
|
589
|
+
const [sameKey, setSameKey] = useState2(true);
|
|
590
|
+
const handleReset = useCallback(() => {
|
|
591
|
+
setKeys(null);
|
|
592
|
+
setBackupText("");
|
|
593
|
+
setPassword("");
|
|
594
|
+
setNeedsPassword(false);
|
|
595
|
+
setError("");
|
|
596
|
+
setBackupType("");
|
|
597
|
+
setMode("choose");
|
|
598
|
+
setPayWif("");
|
|
599
|
+
setOrdWif("");
|
|
600
|
+
}, []);
|
|
601
|
+
const processBackupText = useCallback(async (text) => {
|
|
602
|
+
setError("");
|
|
603
|
+
setBackupText(text);
|
|
604
|
+
const directKeys = tryParseBackup(text);
|
|
605
|
+
if (directKeys) {
|
|
606
|
+
setKeys(directKeys);
|
|
607
|
+
setBackupType("JSON");
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
const parsed = JSON.parse(text);
|
|
612
|
+
if (parsed.encryptedBackup) {
|
|
613
|
+
setBackupText(parsed.encryptedBackup);
|
|
614
|
+
setNeedsPassword(true);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (parsed.encryptedKeys) {
|
|
618
|
+
setBackupText(parsed.encryptedKeys);
|
|
619
|
+
setNeedsPassword(true);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (parsed.accounts && parsed.selectedAccount) {
|
|
623
|
+
const account = parsed.accounts[parsed.selectedAccount];
|
|
624
|
+
if (account?.encryptedKeys) {
|
|
625
|
+
setBackupText(account.encryptedKeys);
|
|
626
|
+
setNeedsPassword(true);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
} catch {}
|
|
631
|
+
if (looksEncrypted(text)) {
|
|
632
|
+
setNeedsPassword(true);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
setError("Unrecognized backup format");
|
|
636
|
+
}, []);
|
|
637
|
+
const handleDecrypt = useCallback(async () => {
|
|
638
|
+
if (!backupText || !password)
|
|
639
|
+
return;
|
|
640
|
+
setDecrypting(true);
|
|
641
|
+
setError("");
|
|
642
|
+
try {
|
|
643
|
+
const decrypted = await decryptBackup(backupText, password);
|
|
644
|
+
const extracted = extractKeys(decrypted);
|
|
645
|
+
if (!extracted) {
|
|
646
|
+
setError(`Unsupported backup type: ${getBackupType(decrypted)}`);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
setKeys(extracted);
|
|
650
|
+
setBackupType(getBackupType(decrypted));
|
|
651
|
+
setNeedsPassword(false);
|
|
652
|
+
} catch (e) {
|
|
653
|
+
setError(e instanceof Error ? e.message : "Decryption failed");
|
|
654
|
+
} finally {
|
|
655
|
+
setDecrypting(false);
|
|
656
|
+
}
|
|
657
|
+
}, [backupText, password]);
|
|
658
|
+
const handleFileUpload = useCallback((e) => {
|
|
659
|
+
const file = e.target.files?.[0];
|
|
660
|
+
if (!file)
|
|
661
|
+
return;
|
|
662
|
+
const reader = new FileReader;
|
|
663
|
+
reader.onload = () => {
|
|
664
|
+
processBackupText(reader.result.trim());
|
|
665
|
+
};
|
|
666
|
+
reader.readAsText(file);
|
|
667
|
+
e.target.value = "";
|
|
668
|
+
}, [processBackupText]);
|
|
669
|
+
const handleScan = useCallback(() => {
|
|
670
|
+
if (keys) {
|
|
671
|
+
onScan(keys);
|
|
672
|
+
} else if (mode === "wif") {
|
|
673
|
+
const pay = payWif.trim();
|
|
674
|
+
const ord = sameKey ? pay : ordWif.trim();
|
|
675
|
+
if (pay)
|
|
676
|
+
onScan({ payPk: pay, ordPk: ord });
|
|
677
|
+
}
|
|
678
|
+
}, [keys, mode, payWif, ordWif, sameKey, onScan]);
|
|
679
|
+
if (keys) {
|
|
680
|
+
return /* @__PURE__ */ jsxs2(Card, {
|
|
681
|
+
children: [
|
|
682
|
+
/* @__PURE__ */ jsx7(CardHeader, {
|
|
683
|
+
children: /* @__PURE__ */ jsxs2("div", {
|
|
684
|
+
className: "flex items-center justify-between",
|
|
685
|
+
children: [
|
|
686
|
+
/* @__PURE__ */ jsxs2(CardTitle, {
|
|
687
|
+
className: "flex items-center gap-2",
|
|
688
|
+
children: [
|
|
689
|
+
/* @__PURE__ */ jsx7(KeyRound, {
|
|
690
|
+
className: "h-5 w-5"
|
|
691
|
+
}),
|
|
692
|
+
"Legacy Keys"
|
|
693
|
+
]
|
|
694
|
+
}),
|
|
695
|
+
/* @__PURE__ */ jsx7(Button, {
|
|
696
|
+
variant: "ghost",
|
|
697
|
+
size: "sm",
|
|
698
|
+
className: "h-6 w-6 p-0",
|
|
699
|
+
onClick: handleReset,
|
|
700
|
+
children: /* @__PURE__ */ jsx7(X2, {
|
|
701
|
+
className: "h-3 w-3"
|
|
702
|
+
})
|
|
703
|
+
})
|
|
704
|
+
]
|
|
705
|
+
})
|
|
706
|
+
}),
|
|
707
|
+
/* @__PURE__ */ jsxs2(CardContent, {
|
|
708
|
+
className: "space-y-3",
|
|
709
|
+
children: [
|
|
710
|
+
backupType && /* @__PURE__ */ jsx7("span", {
|
|
711
|
+
className: "text-xs px-2 py-0.5 rounded bg-blue-500/20 text-blue-400",
|
|
712
|
+
children: backupType
|
|
713
|
+
}),
|
|
714
|
+
/* @__PURE__ */ jsxs2("div", {
|
|
715
|
+
className: "space-y-1 text-xs text-muted-foreground font-mono",
|
|
716
|
+
children: [
|
|
717
|
+
/* @__PURE__ */ jsxs2("div", {
|
|
718
|
+
children: [
|
|
719
|
+
"Pay: ",
|
|
720
|
+
deriveAddress(keys.payPk)
|
|
721
|
+
]
|
|
722
|
+
}),
|
|
723
|
+
keys.ordPk !== keys.payPk && /* @__PURE__ */ jsxs2("div", {
|
|
724
|
+
children: [
|
|
725
|
+
"Ord: ",
|
|
726
|
+
deriveAddress(keys.ordPk)
|
|
727
|
+
]
|
|
728
|
+
}),
|
|
729
|
+
keys.identityPk && keys.identityPk !== keys.payPk && /* @__PURE__ */ jsxs2("div", {
|
|
730
|
+
children: [
|
|
731
|
+
"ID: ",
|
|
732
|
+
deriveAddress(keys.identityPk)
|
|
733
|
+
]
|
|
734
|
+
})
|
|
735
|
+
]
|
|
736
|
+
}),
|
|
737
|
+
/* @__PURE__ */ jsxs2(Button, {
|
|
738
|
+
onClick: handleScan,
|
|
739
|
+
disabled: disabled || scanning,
|
|
740
|
+
className: "w-full",
|
|
741
|
+
children: [
|
|
742
|
+
scanning ? /* @__PURE__ */ jsx7(Loader22, {
|
|
743
|
+
className: "h-4 w-4 animate-spin mr-2"
|
|
744
|
+
}) : /* @__PURE__ */ jsx7(Search, {
|
|
745
|
+
className: "h-4 w-4 mr-2"
|
|
746
|
+
}),
|
|
747
|
+
scanning ? "Scanning..." : "Scan for Assets"
|
|
748
|
+
]
|
|
749
|
+
})
|
|
750
|
+
]
|
|
751
|
+
})
|
|
752
|
+
]
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
if (mode === "choose") {
|
|
756
|
+
return /* @__PURE__ */ jsxs2(Card, {
|
|
757
|
+
children: [
|
|
758
|
+
/* @__PURE__ */ jsx7(CardHeader, {
|
|
759
|
+
children: /* @__PURE__ */ jsxs2(CardTitle, {
|
|
760
|
+
className: "flex items-center gap-2",
|
|
761
|
+
children: [
|
|
762
|
+
/* @__PURE__ */ jsx7(KeyRound, {
|
|
763
|
+
className: "h-5 w-5"
|
|
764
|
+
}),
|
|
765
|
+
"Legacy Keys"
|
|
766
|
+
]
|
|
767
|
+
})
|
|
768
|
+
}),
|
|
769
|
+
/* @__PURE__ */ jsxs2(CardContent, {
|
|
770
|
+
className: "space-y-3",
|
|
771
|
+
children: [
|
|
772
|
+
/* @__PURE__ */ jsx7("input", {
|
|
773
|
+
ref: fileInputRef,
|
|
774
|
+
type: "file",
|
|
775
|
+
accept: ".json,.bep,.txt",
|
|
776
|
+
className: "hidden",
|
|
777
|
+
onChange: handleFileUpload
|
|
778
|
+
}),
|
|
779
|
+
/* @__PURE__ */ jsxs2(Button, {
|
|
780
|
+
variant: "outline",
|
|
781
|
+
className: "w-full gap-2",
|
|
782
|
+
onClick: () => fileInputRef.current?.click(),
|
|
783
|
+
children: [
|
|
784
|
+
/* @__PURE__ */ jsx7(Upload, {
|
|
785
|
+
className: "h-4 w-4"
|
|
786
|
+
}),
|
|
787
|
+
"Import Backup File"
|
|
788
|
+
]
|
|
789
|
+
}),
|
|
790
|
+
/* @__PURE__ */ jsx7(Button, {
|
|
791
|
+
variant: "outline",
|
|
792
|
+
className: "w-full gap-2",
|
|
793
|
+
onClick: () => setMode("backup"),
|
|
794
|
+
children: "Paste Backup Data"
|
|
795
|
+
}),
|
|
796
|
+
/* @__PURE__ */ jsx7(Button, {
|
|
797
|
+
variant: "ghost",
|
|
798
|
+
className: "w-full text-xs text-muted-foreground",
|
|
799
|
+
onClick: () => setMode("wif"),
|
|
800
|
+
children: "Enter WIF keys manually"
|
|
801
|
+
})
|
|
802
|
+
]
|
|
803
|
+
})
|
|
804
|
+
]
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
if (mode === "backup") {
|
|
808
|
+
return /* @__PURE__ */ jsxs2(Card, {
|
|
809
|
+
children: [
|
|
810
|
+
/* @__PURE__ */ jsx7(CardHeader, {
|
|
811
|
+
children: /* @__PURE__ */ jsxs2("div", {
|
|
812
|
+
className: "flex items-center justify-between",
|
|
813
|
+
children: [
|
|
814
|
+
/* @__PURE__ */ jsxs2(CardTitle, {
|
|
815
|
+
className: "flex items-center gap-2",
|
|
816
|
+
children: [
|
|
817
|
+
/* @__PURE__ */ jsx7(KeyRound, {
|
|
818
|
+
className: "h-5 w-5"
|
|
819
|
+
}),
|
|
820
|
+
"Import Backup"
|
|
821
|
+
]
|
|
822
|
+
}),
|
|
823
|
+
/* @__PURE__ */ jsx7(Button, {
|
|
824
|
+
variant: "ghost",
|
|
825
|
+
size: "sm",
|
|
826
|
+
className: "h-6 w-6 p-0",
|
|
827
|
+
onClick: handleReset,
|
|
828
|
+
children: /* @__PURE__ */ jsx7(X2, {
|
|
829
|
+
className: "h-3 w-3"
|
|
830
|
+
})
|
|
831
|
+
})
|
|
832
|
+
]
|
|
833
|
+
})
|
|
834
|
+
}),
|
|
835
|
+
/* @__PURE__ */ jsxs2(CardContent, {
|
|
836
|
+
className: "space-y-3",
|
|
837
|
+
children: [
|
|
838
|
+
!needsPassword ? /* @__PURE__ */ jsxs2(Fragment, {
|
|
839
|
+
children: [
|
|
840
|
+
/* @__PURE__ */ jsx7("textarea", {
|
|
841
|
+
placeholder: "Paste backup JSON or encrypted data...",
|
|
842
|
+
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",
|
|
843
|
+
onChange: (e) => processBackupText(e.target.value.trim())
|
|
844
|
+
}),
|
|
845
|
+
/* @__PURE__ */ jsx7("input", {
|
|
846
|
+
ref: fileInputRef,
|
|
847
|
+
type: "file",
|
|
848
|
+
accept: ".json,.bep,.txt",
|
|
849
|
+
className: "hidden",
|
|
850
|
+
onChange: handleFileUpload
|
|
851
|
+
}),
|
|
852
|
+
/* @__PURE__ */ jsxs2(Button, {
|
|
853
|
+
variant: "outline",
|
|
854
|
+
size: "sm",
|
|
855
|
+
className: "w-full gap-2",
|
|
856
|
+
onClick: () => fileInputRef.current?.click(),
|
|
857
|
+
children: [
|
|
858
|
+
/* @__PURE__ */ jsx7(Upload, {
|
|
859
|
+
className: "h-4 w-4"
|
|
860
|
+
}),
|
|
861
|
+
"Or upload a file"
|
|
862
|
+
]
|
|
863
|
+
})
|
|
864
|
+
]
|
|
865
|
+
}) : /* @__PURE__ */ jsxs2(Fragment, {
|
|
866
|
+
children: [
|
|
867
|
+
/* @__PURE__ */ jsx7("p", {
|
|
868
|
+
className: "text-sm text-muted-foreground",
|
|
869
|
+
children: "This backup is encrypted. Enter the password to decrypt."
|
|
870
|
+
}),
|
|
871
|
+
/* @__PURE__ */ jsx7(Input, {
|
|
872
|
+
type: "password",
|
|
873
|
+
placeholder: "Backup password...",
|
|
874
|
+
value: password,
|
|
875
|
+
onChange: (e) => setPassword(e.target.value),
|
|
876
|
+
onKeyDown: (e) => {
|
|
877
|
+
if (e.key === "Enter")
|
|
878
|
+
handleDecrypt();
|
|
879
|
+
}
|
|
880
|
+
}),
|
|
881
|
+
/* @__PURE__ */ jsxs2(Button, {
|
|
882
|
+
onClick: handleDecrypt,
|
|
883
|
+
disabled: !password || decrypting,
|
|
884
|
+
className: "w-full",
|
|
885
|
+
children: [
|
|
886
|
+
decrypting ? /* @__PURE__ */ jsx7(Loader22, {
|
|
887
|
+
className: "h-4 w-4 animate-spin mr-2"
|
|
888
|
+
}) : null,
|
|
889
|
+
decrypting ? "Decrypting..." : "Decrypt"
|
|
890
|
+
]
|
|
891
|
+
})
|
|
892
|
+
]
|
|
893
|
+
}),
|
|
894
|
+
error && /* @__PURE__ */ jsx7("p", {
|
|
895
|
+
className: "text-sm text-destructive",
|
|
896
|
+
children: error
|
|
897
|
+
})
|
|
898
|
+
]
|
|
899
|
+
})
|
|
900
|
+
]
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
return /* @__PURE__ */ jsxs2(Card, {
|
|
904
|
+
children: [
|
|
905
|
+
/* @__PURE__ */ jsx7(CardHeader, {
|
|
906
|
+
children: /* @__PURE__ */ jsxs2("div", {
|
|
907
|
+
className: "flex items-center justify-between",
|
|
908
|
+
children: [
|
|
909
|
+
/* @__PURE__ */ jsxs2(CardTitle, {
|
|
910
|
+
className: "flex items-center gap-2",
|
|
911
|
+
children: [
|
|
912
|
+
/* @__PURE__ */ jsx7(KeyRound, {
|
|
913
|
+
className: "h-5 w-5"
|
|
914
|
+
}),
|
|
915
|
+
"Manual WIF Entry"
|
|
916
|
+
]
|
|
917
|
+
}),
|
|
918
|
+
/* @__PURE__ */ jsx7(Button, {
|
|
919
|
+
variant: "ghost",
|
|
920
|
+
size: "sm",
|
|
921
|
+
className: "h-6 w-6 p-0",
|
|
922
|
+
onClick: handleReset,
|
|
923
|
+
children: /* @__PURE__ */ jsx7(X2, {
|
|
924
|
+
className: "h-3 w-3"
|
|
925
|
+
})
|
|
926
|
+
})
|
|
927
|
+
]
|
|
928
|
+
})
|
|
929
|
+
}),
|
|
930
|
+
/* @__PURE__ */ jsxs2(CardContent, {
|
|
931
|
+
className: "space-y-4",
|
|
932
|
+
children: [
|
|
933
|
+
/* @__PURE__ */ jsxs2("div", {
|
|
934
|
+
className: "space-y-2",
|
|
935
|
+
children: [
|
|
936
|
+
/* @__PURE__ */ jsx7("label", {
|
|
937
|
+
className: "text-sm font-medium",
|
|
938
|
+
children: sameKey ? "Private Key (WIF)" : "Pay Key (WIF)"
|
|
939
|
+
}),
|
|
940
|
+
/* @__PURE__ */ jsx7(Input, {
|
|
941
|
+
type: "password",
|
|
942
|
+
placeholder: "Enter WIF private key...",
|
|
943
|
+
value: payWif,
|
|
944
|
+
onChange: (e) => setPayWif(e.target.value),
|
|
945
|
+
disabled: disabled || scanning
|
|
946
|
+
})
|
|
947
|
+
]
|
|
948
|
+
}),
|
|
949
|
+
/* @__PURE__ */ jsxs2("label", {
|
|
950
|
+
className: "flex items-center gap-2 text-sm",
|
|
951
|
+
children: [
|
|
952
|
+
/* @__PURE__ */ jsx7("input", {
|
|
953
|
+
type: "checkbox",
|
|
954
|
+
checked: sameKey,
|
|
955
|
+
onChange: (e) => setSameKey(e.target.checked),
|
|
956
|
+
disabled: disabled || scanning
|
|
957
|
+
}),
|
|
958
|
+
"Same key for pay and ordinals"
|
|
959
|
+
]
|
|
960
|
+
}),
|
|
961
|
+
!sameKey && /* @__PURE__ */ jsxs2("div", {
|
|
962
|
+
className: "space-y-2",
|
|
963
|
+
children: [
|
|
964
|
+
/* @__PURE__ */ jsx7("label", {
|
|
965
|
+
className: "text-sm font-medium",
|
|
966
|
+
children: "Ordinals Key (WIF)"
|
|
967
|
+
}),
|
|
968
|
+
/* @__PURE__ */ jsx7(Input, {
|
|
969
|
+
type: "password",
|
|
970
|
+
placeholder: "Enter ordinals WIF...",
|
|
971
|
+
value: ordWif,
|
|
972
|
+
onChange: (e) => setOrdWif(e.target.value),
|
|
973
|
+
disabled: disabled || scanning
|
|
974
|
+
})
|
|
975
|
+
]
|
|
976
|
+
}),
|
|
977
|
+
/* @__PURE__ */ jsxs2(Button, {
|
|
978
|
+
onClick: handleScan,
|
|
979
|
+
disabled: disabled || scanning || !payWif.trim(),
|
|
980
|
+
className: "w-full",
|
|
981
|
+
children: [
|
|
982
|
+
scanning ? /* @__PURE__ */ jsx7(Loader22, {
|
|
983
|
+
className: "h-4 w-4 animate-spin mr-2"
|
|
984
|
+
}) : /* @__PURE__ */ jsx7(Search, {
|
|
985
|
+
className: "h-4 w-4 mr-2"
|
|
986
|
+
}),
|
|
987
|
+
scanning ? "Scanning..." : "Scan for Assets"
|
|
988
|
+
]
|
|
989
|
+
})
|
|
990
|
+
]
|
|
991
|
+
})
|
|
992
|
+
]
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// src/components/asset-preview.tsx
|
|
997
|
+
import { useState as useState3 } from "react";
|
|
998
|
+
import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
999
|
+
var ORDINALS_PER_PAGE = 20;
|
|
1000
|
+
function isImageType(ct) {
|
|
1001
|
+
return ct.startsWith("image/") && ct !== "image/svg+xml";
|
|
1002
|
+
}
|
|
1003
|
+
function OrdinalCard({ ordinal, isSelected, onToggle }) {
|
|
1004
|
+
const ct = ordinal.contentType ?? "";
|
|
1005
|
+
const isImage = isImageType(ct);
|
|
1006
|
+
const subtype = ct.includes("/") ? ct.split("/")[1] : ct;
|
|
1007
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
1008
|
+
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"}`,
|
|
1009
|
+
onClick: onToggle,
|
|
1010
|
+
children: [
|
|
1011
|
+
/* @__PURE__ */ jsx8("div", {
|
|
1012
|
+
className: "absolute top-1.5 right-1.5 z-10",
|
|
1013
|
+
children: /* @__PURE__ */ jsx8("div", {
|
|
1014
|
+
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"}`,
|
|
1015
|
+
children: isSelected && "✓"
|
|
1016
|
+
})
|
|
1017
|
+
}),
|
|
1018
|
+
/* @__PURE__ */ jsx8("div", {
|
|
1019
|
+
className: "w-full aspect-square mb-1.5 rounded overflow-hidden bg-black/30 flex items-center justify-center",
|
|
1020
|
+
children: !ordinal.contentUrl ? /* @__PURE__ */ jsx8("span", {
|
|
1021
|
+
className: "text-muted-foreground text-lg",
|
|
1022
|
+
children: "◆"
|
|
1023
|
+
}) : isImage ? /* @__PURE__ */ jsx8("img", {
|
|
1024
|
+
src: ordinal.contentUrl,
|
|
1025
|
+
alt: ordinal.name || "Ordinal",
|
|
1026
|
+
className: "w-full h-full object-cover",
|
|
1027
|
+
loading: "lazy"
|
|
1028
|
+
}) : /* @__PURE__ */ jsx8("iframe", {
|
|
1029
|
+
src: ordinal.contentUrl,
|
|
1030
|
+
title: ordinal.name || "Ordinal",
|
|
1031
|
+
className: "w-full h-full border-0 pointer-events-none",
|
|
1032
|
+
sandbox: "allow-scripts",
|
|
1033
|
+
loading: "lazy"
|
|
1034
|
+
})
|
|
1035
|
+
}),
|
|
1036
|
+
subtype && /* @__PURE__ */ jsx8("div", {
|
|
1037
|
+
className: "mb-1",
|
|
1038
|
+
children: /* @__PURE__ */ jsx8("span", {
|
|
1039
|
+
className: "px-1 py-0.5 text-[9px] rounded bg-blue-500/20 text-blue-400 truncate",
|
|
1040
|
+
children: subtype
|
|
1041
|
+
})
|
|
1042
|
+
}),
|
|
1043
|
+
ordinal.name ? /* @__PURE__ */ jsx8("a", {
|
|
1044
|
+
href: ordinal.contentUrl,
|
|
1045
|
+
target: "_blank",
|
|
1046
|
+
rel: "noopener noreferrer",
|
|
1047
|
+
className: "text-[10px] text-foreground truncate font-medium hover:text-blue-400",
|
|
1048
|
+
title: ordinal.name,
|
|
1049
|
+
children: ordinal.name
|
|
1050
|
+
}) : /* @__PURE__ */ jsxs3("a", {
|
|
1051
|
+
href: ordinal.contentUrl,
|
|
1052
|
+
target: "_blank",
|
|
1053
|
+
rel: "noopener noreferrer",
|
|
1054
|
+
className: "text-[9px] text-muted-foreground truncate font-mono hover:text-blue-400",
|
|
1055
|
+
children: [
|
|
1056
|
+
ordinal.outpoint.substring(0, 8),
|
|
1057
|
+
"..."
|
|
1058
|
+
]
|
|
1059
|
+
})
|
|
1060
|
+
]
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
function FundingSection({ funding, totalBsv, sweepAmount, onSweepAmountChange, onSweep, onSend, walletConnected }) {
|
|
1064
|
+
const [address, setAddress] = useState3("");
|
|
1065
|
+
if (funding.length === 0)
|
|
1066
|
+
return null;
|
|
1067
|
+
const isMax = sweepAmount === null;
|
|
1068
|
+
const displayAmount = isMax ? formatSats(totalBsv) : formatSats(sweepAmount);
|
|
1069
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
1070
|
+
className: "border border-green-500/20 bg-green-500/5 p-4 rounded-lg",
|
|
1071
|
+
children: [
|
|
1072
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1073
|
+
className: "flex items-center gap-2 mb-2",
|
|
1074
|
+
children: [
|
|
1075
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1076
|
+
className: "h-2 w-2 rounded-full bg-green-500"
|
|
1077
|
+
}),
|
|
1078
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1079
|
+
className: "text-sm font-semibold text-green-500",
|
|
1080
|
+
children: "BSV Funding"
|
|
1081
|
+
})
|
|
1082
|
+
]
|
|
1083
|
+
}),
|
|
1084
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1085
|
+
className: "flex items-baseline justify-between mb-3",
|
|
1086
|
+
children: [
|
|
1087
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1088
|
+
children: [
|
|
1089
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1090
|
+
className: "text-2xl font-bold text-green-500",
|
|
1091
|
+
children: [
|
|
1092
|
+
formatSats(totalBsv),
|
|
1093
|
+
" sats"
|
|
1094
|
+
]
|
|
1095
|
+
}),
|
|
1096
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1097
|
+
className: "text-xs text-muted-foreground",
|
|
1098
|
+
children: [
|
|
1099
|
+
(totalBsv / 1e8).toFixed(8),
|
|
1100
|
+
" BSV"
|
|
1101
|
+
]
|
|
1102
|
+
})
|
|
1103
|
+
]
|
|
1104
|
+
}),
|
|
1105
|
+
/* @__PURE__ */ jsxs3(Badge, {
|
|
1106
|
+
variant: "secondary",
|
|
1107
|
+
children: [
|
|
1108
|
+
funding.length,
|
|
1109
|
+
" UTXO",
|
|
1110
|
+
funding.length !== 1 ? "s" : ""
|
|
1111
|
+
]
|
|
1112
|
+
})
|
|
1113
|
+
]
|
|
1114
|
+
}),
|
|
1115
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1116
|
+
className: "flex items-center gap-2",
|
|
1117
|
+
children: [
|
|
1118
|
+
/* @__PURE__ */ jsx8(Input, {
|
|
1119
|
+
type: "number",
|
|
1120
|
+
min: 0,
|
|
1121
|
+
max: totalBsv,
|
|
1122
|
+
placeholder: "Max",
|
|
1123
|
+
value: isMax ? "" : sweepAmount,
|
|
1124
|
+
onChange: (e) => {
|
|
1125
|
+
const val = e.target.value;
|
|
1126
|
+
onSweepAmountChange(val === "" ? null : Math.max(0, Math.min(totalBsv, Number(val))));
|
|
1127
|
+
},
|
|
1128
|
+
className: "flex-1 font-mono"
|
|
1129
|
+
}),
|
|
1130
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1131
|
+
className: "text-xs text-muted-foreground",
|
|
1132
|
+
children: "sats"
|
|
1133
|
+
}),
|
|
1134
|
+
/* @__PURE__ */ jsx8(Button, {
|
|
1135
|
+
variant: "outline",
|
|
1136
|
+
size: "sm",
|
|
1137
|
+
className: "h-9 text-xs",
|
|
1138
|
+
onClick: () => onSweepAmountChange(null),
|
|
1139
|
+
disabled: isMax,
|
|
1140
|
+
children: "Max"
|
|
1141
|
+
})
|
|
1142
|
+
]
|
|
1143
|
+
}),
|
|
1144
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1145
|
+
className: "mt-3 space-y-2",
|
|
1146
|
+
children: [
|
|
1147
|
+
/* @__PURE__ */ jsx8(Input, {
|
|
1148
|
+
type: "text",
|
|
1149
|
+
placeholder: "Destination address...",
|
|
1150
|
+
value: address,
|
|
1151
|
+
onChange: (e) => setAddress(e.target.value),
|
|
1152
|
+
className: "font-mono text-xs"
|
|
1153
|
+
}),
|
|
1154
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1155
|
+
className: "flex gap-2",
|
|
1156
|
+
children: [
|
|
1157
|
+
/* @__PURE__ */ jsxs3(Button, {
|
|
1158
|
+
variant: "outline",
|
|
1159
|
+
size: "sm",
|
|
1160
|
+
className: "flex-1",
|
|
1161
|
+
disabled: !address.trim(),
|
|
1162
|
+
onClick: () => onSend(address.trim()),
|
|
1163
|
+
children: [
|
|
1164
|
+
"Send ",
|
|
1165
|
+
displayAmount,
|
|
1166
|
+
" sats"
|
|
1167
|
+
]
|
|
1168
|
+
}),
|
|
1169
|
+
/* @__PURE__ */ jsx8(Button, {
|
|
1170
|
+
size: "sm",
|
|
1171
|
+
className: "flex-1",
|
|
1172
|
+
onClick: onSweep,
|
|
1173
|
+
disabled: !walletConnected,
|
|
1174
|
+
title: walletConnected ? undefined : "Connect BRC-100 wallet to sweep",
|
|
1175
|
+
children: "Sweep to Wallet"
|
|
1176
|
+
})
|
|
1177
|
+
]
|
|
1178
|
+
})
|
|
1179
|
+
]
|
|
1180
|
+
})
|
|
1181
|
+
]
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
function OrdinalsSection({ ordinals, selectedOrdinals, onToggle, onSelectAll, onDeselectAll, onSweep, onSend, onBurn, walletConnected }) {
|
|
1185
|
+
const [page, setPage] = useState3(0);
|
|
1186
|
+
const [address, setAddress] = useState3("");
|
|
1187
|
+
if (ordinals.length === 0)
|
|
1188
|
+
return null;
|
|
1189
|
+
const totalPages = Math.ceil(ordinals.length / ORDINALS_PER_PAGE);
|
|
1190
|
+
const start = page * ORDINALS_PER_PAGE;
|
|
1191
|
+
const pageItems = ordinals.slice(start, start + ORDINALS_PER_PAGE);
|
|
1192
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
1193
|
+
className: "border border-blue-500/20 bg-blue-500/5 p-4 rounded-lg",
|
|
1194
|
+
children: [
|
|
1195
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1196
|
+
className: "flex items-start justify-between mb-3",
|
|
1197
|
+
children: [
|
|
1198
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1199
|
+
children: [
|
|
1200
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1201
|
+
className: "flex items-center gap-2 mb-1",
|
|
1202
|
+
children: [
|
|
1203
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1204
|
+
className: "h-2 w-2 rounded-full bg-blue-500"
|
|
1205
|
+
}),
|
|
1206
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1207
|
+
className: "text-sm font-semibold text-blue-500",
|
|
1208
|
+
children: "Ordinals"
|
|
1209
|
+
})
|
|
1210
|
+
]
|
|
1211
|
+
}),
|
|
1212
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1213
|
+
className: "text-xs text-muted-foreground",
|
|
1214
|
+
children: [
|
|
1215
|
+
ordinals.length,
|
|
1216
|
+
" inscription",
|
|
1217
|
+
ordinals.length !== 1 ? "s" : "",
|
|
1218
|
+
selectedOrdinals.size > 0 && /* @__PURE__ */ jsxs3("span", {
|
|
1219
|
+
className: "text-blue-400 ml-1",
|
|
1220
|
+
children: [
|
|
1221
|
+
"(",
|
|
1222
|
+
selectedOrdinals.size,
|
|
1223
|
+
" selected)"
|
|
1224
|
+
]
|
|
1225
|
+
})
|
|
1226
|
+
]
|
|
1227
|
+
})
|
|
1228
|
+
]
|
|
1229
|
+
}),
|
|
1230
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1231
|
+
className: "flex gap-2",
|
|
1232
|
+
children: [
|
|
1233
|
+
/* @__PURE__ */ jsx8(Button, {
|
|
1234
|
+
variant: "outline",
|
|
1235
|
+
size: "sm",
|
|
1236
|
+
className: "h-7 text-[11px]",
|
|
1237
|
+
onClick: onSelectAll,
|
|
1238
|
+
children: "Select All"
|
|
1239
|
+
}),
|
|
1240
|
+
/* @__PURE__ */ jsx8(Button, {
|
|
1241
|
+
variant: "outline",
|
|
1242
|
+
size: "sm",
|
|
1243
|
+
className: "h-7 text-[11px]",
|
|
1244
|
+
onClick: onDeselectAll,
|
|
1245
|
+
disabled: selectedOrdinals.size === 0,
|
|
1246
|
+
children: "Deselect"
|
|
1247
|
+
})
|
|
1248
|
+
]
|
|
1249
|
+
})
|
|
1250
|
+
]
|
|
1251
|
+
}),
|
|
1252
|
+
/* @__PURE__ */ jsx8("div", {
|
|
1253
|
+
className: "grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-2 mb-3",
|
|
1254
|
+
children: pageItems.map((ord) => /* @__PURE__ */ jsx8(OrdinalCard, {
|
|
1255
|
+
ordinal: ord,
|
|
1256
|
+
isSelected: selectedOrdinals.has(ord.outpoint),
|
|
1257
|
+
onToggle: () => onToggle(ord.outpoint)
|
|
1258
|
+
}, ord.outpoint))
|
|
1259
|
+
}),
|
|
1260
|
+
totalPages > 1 && /* @__PURE__ */ jsxs3("div", {
|
|
1261
|
+
className: "flex items-center justify-center gap-4",
|
|
1262
|
+
children: [
|
|
1263
|
+
/* @__PURE__ */ jsx8(Button, {
|
|
1264
|
+
variant: "ghost",
|
|
1265
|
+
size: "sm",
|
|
1266
|
+
className: "text-xs",
|
|
1267
|
+
onClick: () => setPage(page - 1),
|
|
1268
|
+
disabled: page === 0,
|
|
1269
|
+
children: "Prev"
|
|
1270
|
+
}),
|
|
1271
|
+
/* @__PURE__ */ jsxs3("span", {
|
|
1272
|
+
className: "text-xs text-muted-foreground",
|
|
1273
|
+
children: [
|
|
1274
|
+
"Page ",
|
|
1275
|
+
page + 1,
|
|
1276
|
+
" of ",
|
|
1277
|
+
totalPages
|
|
1278
|
+
]
|
|
1279
|
+
}),
|
|
1280
|
+
/* @__PURE__ */ jsx8(Button, {
|
|
1281
|
+
variant: "ghost",
|
|
1282
|
+
size: "sm",
|
|
1283
|
+
className: "text-xs",
|
|
1284
|
+
onClick: () => setPage(page + 1),
|
|
1285
|
+
disabled: page >= totalPages - 1,
|
|
1286
|
+
children: "Next"
|
|
1287
|
+
})
|
|
1288
|
+
]
|
|
1289
|
+
}),
|
|
1290
|
+
selectedOrdinals.size > 0 && /* @__PURE__ */ jsxs3("div", {
|
|
1291
|
+
className: "mt-3 space-y-2",
|
|
1292
|
+
children: [
|
|
1293
|
+
/* @__PURE__ */ jsx8(Input, {
|
|
1294
|
+
type: "text",
|
|
1295
|
+
placeholder: "Destination address...",
|
|
1296
|
+
value: address,
|
|
1297
|
+
onChange: (e) => setAddress(e.target.value),
|
|
1298
|
+
className: "font-mono text-xs"
|
|
1299
|
+
}),
|
|
1300
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1301
|
+
className: "flex gap-2",
|
|
1302
|
+
children: [
|
|
1303
|
+
/* @__PURE__ */ jsxs3(Button, {
|
|
1304
|
+
variant: "outline",
|
|
1305
|
+
size: "sm",
|
|
1306
|
+
className: "flex-1",
|
|
1307
|
+
disabled: !address.trim(),
|
|
1308
|
+
onClick: () => onSend(address.trim()),
|
|
1309
|
+
children: [
|
|
1310
|
+
"Send ",
|
|
1311
|
+
selectedOrdinals.size,
|
|
1312
|
+
" Ordinal",
|
|
1313
|
+
selectedOrdinals.size !== 1 ? "s" : ""
|
|
1314
|
+
]
|
|
1315
|
+
}),
|
|
1316
|
+
/* @__PURE__ */ jsx8(Button, {
|
|
1317
|
+
size: "sm",
|
|
1318
|
+
className: "flex-1",
|
|
1319
|
+
onClick: onSweep,
|
|
1320
|
+
disabled: !walletConnected,
|
|
1321
|
+
title: walletConnected ? undefined : "Connect BRC-100 wallet to sweep",
|
|
1322
|
+
children: "Sweep to Wallet"
|
|
1323
|
+
}),
|
|
1324
|
+
/* @__PURE__ */ jsx8(Button, {
|
|
1325
|
+
size: "sm",
|
|
1326
|
+
className: "bg-red-600 hover:bg-red-700 text-white",
|
|
1327
|
+
onClick: () => {
|
|
1328
|
+
if (window.confirm(`Permanently burn ${selectedOrdinals.size} ordinal${selectedOrdinals.size !== 1 ? "s" : ""}? This cannot be undone.`))
|
|
1329
|
+
onBurn();
|
|
1330
|
+
},
|
|
1331
|
+
children: "Burn"
|
|
1332
|
+
})
|
|
1333
|
+
]
|
|
1334
|
+
})
|
|
1335
|
+
]
|
|
1336
|
+
})
|
|
1337
|
+
]
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
function TokenRow({ tb }) {
|
|
1341
|
+
return /* @__PURE__ */ jsx8("div", {
|
|
1342
|
+
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"}`,
|
|
1343
|
+
children: /* @__PURE__ */ jsxs3("div", {
|
|
1344
|
+
className: "flex items-center gap-3",
|
|
1345
|
+
children: [
|
|
1346
|
+
/* @__PURE__ */ jsx8("img", {
|
|
1347
|
+
src: tb.icon,
|
|
1348
|
+
alt: tb.symbol || "Token",
|
|
1349
|
+
className: "w-8 h-8 rounded-full object-cover",
|
|
1350
|
+
onError: (e) => {
|
|
1351
|
+
e.target.style.display = "none";
|
|
1352
|
+
}
|
|
1353
|
+
}),
|
|
1354
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1355
|
+
children: [
|
|
1356
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1357
|
+
className: "flex items-center gap-2",
|
|
1358
|
+
children: [
|
|
1359
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1360
|
+
className: "font-medium text-foreground",
|
|
1361
|
+
children: tb.symbol || tb.tokenId.slice(0, 8) + "..."
|
|
1362
|
+
}),
|
|
1363
|
+
tb.isActive ? /* @__PURE__ */ jsx8("span", {
|
|
1364
|
+
className: "px-1.5 py-0.5 text-[9px] rounded bg-green-600/20 text-green-700 dark:text-green-400",
|
|
1365
|
+
children: "active"
|
|
1366
|
+
}) : /* @__PURE__ */ jsx8("span", {
|
|
1367
|
+
className: "px-1.5 py-0.5 text-[9px] rounded bg-muted text-muted-foreground",
|
|
1368
|
+
children: "inactive"
|
|
1369
|
+
})
|
|
1370
|
+
]
|
|
1371
|
+
}),
|
|
1372
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1373
|
+
className: "text-xs text-muted-foreground",
|
|
1374
|
+
children: [
|
|
1375
|
+
formatTokenAmount(tb.totalAmount.toString(), tb.decimals),
|
|
1376
|
+
" ",
|
|
1377
|
+
tb.symbol || "",
|
|
1378
|
+
/* @__PURE__ */ jsxs3("span", {
|
|
1379
|
+
className: "ml-2",
|
|
1380
|
+
children: [
|
|
1381
|
+
"(",
|
|
1382
|
+
tb.outputs.length,
|
|
1383
|
+
" output",
|
|
1384
|
+
tb.outputs.length !== 1 ? "s" : "",
|
|
1385
|
+
")"
|
|
1386
|
+
]
|
|
1387
|
+
})
|
|
1388
|
+
]
|
|
1389
|
+
})
|
|
1390
|
+
]
|
|
1391
|
+
})
|
|
1392
|
+
]
|
|
1393
|
+
})
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
function Bsv21Section({ tokens }) {
|
|
1397
|
+
if (tokens.length === 0)
|
|
1398
|
+
return null;
|
|
1399
|
+
const active = tokens.filter((t) => t.isActive);
|
|
1400
|
+
const inactive = tokens.filter((t) => !t.isActive);
|
|
1401
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
1402
|
+
className: "border border-purple-500/20 bg-purple-500/5 p-4 rounded-lg",
|
|
1403
|
+
children: [
|
|
1404
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1405
|
+
className: "flex items-center gap-2 mb-3",
|
|
1406
|
+
children: [
|
|
1407
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1408
|
+
className: "h-2 w-2 rounded-full bg-purple-500"
|
|
1409
|
+
}),
|
|
1410
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1411
|
+
className: "text-sm font-semibold text-purple-500",
|
|
1412
|
+
children: "BSV-21 Tokens"
|
|
1413
|
+
})
|
|
1414
|
+
]
|
|
1415
|
+
}),
|
|
1416
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1417
|
+
className: "space-y-3",
|
|
1418
|
+
children: [
|
|
1419
|
+
active.map((tb) => /* @__PURE__ */ jsx8(TokenRow, {
|
|
1420
|
+
tb
|
|
1421
|
+
}, tb.tokenId)),
|
|
1422
|
+
inactive.length > 0 && active.length > 0 && /* @__PURE__ */ jsx8("div", {
|
|
1423
|
+
className: "border-t border-purple-500/10 pt-3 mt-3",
|
|
1424
|
+
children: /* @__PURE__ */ jsxs3("div", {
|
|
1425
|
+
className: "text-xs text-muted-foreground mb-2",
|
|
1426
|
+
children: [
|
|
1427
|
+
"Inactive overlays (",
|
|
1428
|
+
inactive.length,
|
|
1429
|
+
") — cannot be swept"
|
|
1430
|
+
]
|
|
1431
|
+
})
|
|
1432
|
+
}),
|
|
1433
|
+
inactive.map((tb) => /* @__PURE__ */ jsx8(TokenRow, {
|
|
1434
|
+
tb
|
|
1435
|
+
}, tb.tokenId))
|
|
1436
|
+
]
|
|
1437
|
+
})
|
|
1438
|
+
]
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
function Bsv20Section({ tokens }) {
|
|
1442
|
+
if (tokens.length === 0)
|
|
1443
|
+
return null;
|
|
1444
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
1445
|
+
className: "border border-muted/30 bg-muted/10 p-4 rounded-lg",
|
|
1446
|
+
children: [
|
|
1447
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1448
|
+
className: "flex items-center gap-2 mb-2",
|
|
1449
|
+
children: [
|
|
1450
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1451
|
+
className: "h-2 w-2 rounded-full bg-muted-foreground"
|
|
1452
|
+
}),
|
|
1453
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1454
|
+
className: "text-sm font-semibold text-muted-foreground",
|
|
1455
|
+
children: "BSV-20 Tokens"
|
|
1456
|
+
})
|
|
1457
|
+
]
|
|
1458
|
+
}),
|
|
1459
|
+
/* @__PURE__ */ jsx8("p", {
|
|
1460
|
+
className: "text-xs text-muted-foreground mb-2",
|
|
1461
|
+
children: "Cannot be swept automatically."
|
|
1462
|
+
}),
|
|
1463
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1464
|
+
className: "flex flex-wrap gap-2",
|
|
1465
|
+
children: [
|
|
1466
|
+
tokens.slice(0, 10).map((o) => {
|
|
1467
|
+
const tickEvent = o.events?.find((e) => e.startsWith("tick:"));
|
|
1468
|
+
const tick = tickEvent ? tickEvent.slice(5) : "Token";
|
|
1469
|
+
return /* @__PURE__ */ jsx8("span", {
|
|
1470
|
+
className: "px-2 py-1 text-xs rounded bg-muted/30 text-muted-foreground",
|
|
1471
|
+
children: tick
|
|
1472
|
+
}, o.outpoint);
|
|
1473
|
+
}),
|
|
1474
|
+
tokens.length > 10 && /* @__PURE__ */ jsxs3("span", {
|
|
1475
|
+
className: "text-xs text-muted-foreground",
|
|
1476
|
+
children: [
|
|
1477
|
+
"+",
|
|
1478
|
+
tokens.length - 10,
|
|
1479
|
+
" more"
|
|
1480
|
+
]
|
|
1481
|
+
})
|
|
1482
|
+
]
|
|
1483
|
+
})
|
|
1484
|
+
]
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
function LockedSection({ locked }) {
|
|
1488
|
+
if (locked.length === 0)
|
|
1489
|
+
return null;
|
|
1490
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
1491
|
+
className: "border border-yellow-500/20 bg-yellow-500/5 p-4 rounded-lg",
|
|
1492
|
+
children: [
|
|
1493
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1494
|
+
className: "flex items-center gap-2 mb-2",
|
|
1495
|
+
children: [
|
|
1496
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1497
|
+
className: "h-2 w-2 rounded-full bg-yellow-500"
|
|
1498
|
+
}),
|
|
1499
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1500
|
+
className: "text-sm font-semibold text-yellow-500",
|
|
1501
|
+
children: "Locked Outputs"
|
|
1502
|
+
})
|
|
1503
|
+
]
|
|
1504
|
+
}),
|
|
1505
|
+
/* @__PURE__ */ jsxs3("p", {
|
|
1506
|
+
className: "text-xs text-muted-foreground",
|
|
1507
|
+
children: [
|
|
1508
|
+
locked.length,
|
|
1509
|
+
" locked output",
|
|
1510
|
+
locked.length !== 1 ? "s" : "",
|
|
1511
|
+
". These are in contracts and cannot be swept directly."
|
|
1512
|
+
]
|
|
1513
|
+
})
|
|
1514
|
+
]
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// src/components/opns-section.tsx
|
|
1519
|
+
import { useState as useState4, useEffect } from "react";
|
|
1520
|
+
import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1521
|
+
function OpnsSection({ opnsNames, selectedOpns, onToggle, onSelectAll, onDeselectAll, onSweep, onSend, onBurn, walletConnected }) {
|
|
1522
|
+
const [address, setAddress] = useState4("");
|
|
1523
|
+
const [resolvedNames, setResolvedNames] = useState4(new Map);
|
|
1524
|
+
const [overlayValid, setOverlayValid] = useState4(new Map);
|
|
1525
|
+
const [validating, setValidating] = useState4(false);
|
|
1526
|
+
useEffect(() => {
|
|
1527
|
+
if (opnsNames.length === 0)
|
|
1528
|
+
return;
|
|
1529
|
+
const controller = new AbortController;
|
|
1530
|
+
const pending = new Map;
|
|
1531
|
+
Promise.all(opnsNames.map(async (item) => {
|
|
1532
|
+
try {
|
|
1533
|
+
const res = await fetch(item.contentUrl, { signal: controller.signal });
|
|
1534
|
+
if (res.ok)
|
|
1535
|
+
pending.set(item.outpoint, await res.text());
|
|
1536
|
+
} catch {}
|
|
1537
|
+
})).then(() => {
|
|
1538
|
+
if (!controller.signal.aborted)
|
|
1539
|
+
setResolvedNames(new Map(pending));
|
|
1540
|
+
});
|
|
1541
|
+
return () => controller.abort();
|
|
1542
|
+
}, [opnsNames]);
|
|
1543
|
+
useEffect(() => {
|
|
1544
|
+
if (opnsNames.length === 0)
|
|
1545
|
+
return;
|
|
1546
|
+
let cancelled = false;
|
|
1547
|
+
setValidating(true);
|
|
1548
|
+
const originToOutpoints = new Map;
|
|
1549
|
+
for (const item of opnsNames) {
|
|
1550
|
+
const origin = item.origin ?? item.outpoint;
|
|
1551
|
+
const list = originToOutpoints.get(origin) ?? [];
|
|
1552
|
+
list.push(item.outpoint);
|
|
1553
|
+
originToOutpoints.set(origin, list);
|
|
1554
|
+
}
|
|
1555
|
+
getServices().opns.validateOrigins([...originToOutpoints.keys()]).then((result) => {
|
|
1556
|
+
if (cancelled)
|
|
1557
|
+
return;
|
|
1558
|
+
const map = new Map;
|
|
1559
|
+
for (const [origin, valid] of Object.entries(result)) {
|
|
1560
|
+
for (const outpoint of originToOutpoints.get(origin) ?? [])
|
|
1561
|
+
map.set(outpoint, valid);
|
|
1562
|
+
}
|
|
1563
|
+
setOverlayValid(map);
|
|
1564
|
+
}).catch(() => {}).finally(() => {
|
|
1565
|
+
if (!cancelled)
|
|
1566
|
+
setValidating(false);
|
|
1567
|
+
});
|
|
1568
|
+
return () => {
|
|
1569
|
+
cancelled = true;
|
|
1570
|
+
};
|
|
1571
|
+
}, [opnsNames]);
|
|
1572
|
+
if (opnsNames.length === 0)
|
|
1573
|
+
return null;
|
|
1574
|
+
return /* @__PURE__ */ jsxs4("div", {
|
|
1575
|
+
className: "border border-orange-500/20 bg-orange-500/5 p-4 rounded-lg",
|
|
1576
|
+
children: [
|
|
1577
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
1578
|
+
className: "flex items-start justify-between mb-3",
|
|
1579
|
+
children: [
|
|
1580
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
1581
|
+
children: [
|
|
1582
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
1583
|
+
className: "flex items-center gap-2 mb-1",
|
|
1584
|
+
children: [
|
|
1585
|
+
/* @__PURE__ */ jsx9("span", {
|
|
1586
|
+
className: "h-2 w-2 rounded-full bg-orange-500"
|
|
1587
|
+
}),
|
|
1588
|
+
/* @__PURE__ */ jsx9("span", {
|
|
1589
|
+
className: "text-sm font-semibold text-orange-500",
|
|
1590
|
+
children: "OPNS Domains"
|
|
1591
|
+
})
|
|
1592
|
+
]
|
|
1593
|
+
}),
|
|
1594
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
1595
|
+
className: "text-xs text-muted-foreground",
|
|
1596
|
+
children: [
|
|
1597
|
+
opnsNames.length,
|
|
1598
|
+
" domain",
|
|
1599
|
+
opnsNames.length !== 1 ? "s" : "",
|
|
1600
|
+
selectedOpns.size > 0 && /* @__PURE__ */ jsxs4("span", {
|
|
1601
|
+
className: "text-orange-400 ml-1",
|
|
1602
|
+
children: [
|
|
1603
|
+
"(",
|
|
1604
|
+
selectedOpns.size,
|
|
1605
|
+
" selected)"
|
|
1606
|
+
]
|
|
1607
|
+
})
|
|
1608
|
+
]
|
|
1609
|
+
})
|
|
1610
|
+
]
|
|
1611
|
+
}),
|
|
1612
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
1613
|
+
className: "flex gap-2",
|
|
1614
|
+
children: [
|
|
1615
|
+
/* @__PURE__ */ jsx9(Button, {
|
|
1616
|
+
variant: "outline",
|
|
1617
|
+
size: "sm",
|
|
1618
|
+
className: "h-7 text-[11px]",
|
|
1619
|
+
onClick: onSelectAll,
|
|
1620
|
+
children: "Select All"
|
|
1621
|
+
}),
|
|
1622
|
+
/* @__PURE__ */ jsx9(Button, {
|
|
1623
|
+
variant: "outline",
|
|
1624
|
+
size: "sm",
|
|
1625
|
+
className: "h-7 text-[11px]",
|
|
1626
|
+
onClick: onDeselectAll,
|
|
1627
|
+
disabled: selectedOpns.size === 0,
|
|
1628
|
+
children: "Deselect"
|
|
1629
|
+
})
|
|
1630
|
+
]
|
|
1631
|
+
})
|
|
1632
|
+
]
|
|
1633
|
+
}),
|
|
1634
|
+
/* @__PURE__ */ jsx9("div", {
|
|
1635
|
+
className: "space-y-1",
|
|
1636
|
+
children: opnsNames.map((item) => {
|
|
1637
|
+
const isSelected = selectedOpns.has(item.outpoint);
|
|
1638
|
+
const displayName = resolvedNames.get(item.outpoint) ?? item.outpoint.substring(0, 8) + "...";
|
|
1639
|
+
return /* @__PURE__ */ jsxs4("div", {
|
|
1640
|
+
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"}`,
|
|
1641
|
+
onClick: () => onToggle(item.outpoint),
|
|
1642
|
+
children: [
|
|
1643
|
+
/* @__PURE__ */ jsx9("div", {
|
|
1644
|
+
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"}`,
|
|
1645
|
+
children: isSelected && "✓"
|
|
1646
|
+
}),
|
|
1647
|
+
/* @__PURE__ */ jsx9("span", {
|
|
1648
|
+
className: "text-sm text-foreground truncate",
|
|
1649
|
+
children: displayName
|
|
1650
|
+
}),
|
|
1651
|
+
!validating && overlayValid.has(item.outpoint) && (overlayValid.get(item.outpoint) ? /* @__PURE__ */ jsx9("span", {
|
|
1652
|
+
className: "shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-green-500/20 text-green-400",
|
|
1653
|
+
children: "valid"
|
|
1654
|
+
}) : /* @__PURE__ */ jsx9("span", {
|
|
1655
|
+
className: "shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded bg-red-500/20 text-red-400",
|
|
1656
|
+
children: "invalid"
|
|
1657
|
+
}))
|
|
1658
|
+
]
|
|
1659
|
+
}, item.outpoint);
|
|
1660
|
+
})
|
|
1661
|
+
}),
|
|
1662
|
+
selectedOpns.size > 0 && /* @__PURE__ */ jsxs4("div", {
|
|
1663
|
+
className: "mt-3 space-y-2",
|
|
1664
|
+
children: [
|
|
1665
|
+
/* @__PURE__ */ jsx9(Input, {
|
|
1666
|
+
type: "text",
|
|
1667
|
+
placeholder: "Destination address...",
|
|
1668
|
+
value: address,
|
|
1669
|
+
onChange: (e) => setAddress(e.target.value),
|
|
1670
|
+
className: "font-mono text-xs"
|
|
1671
|
+
}),
|
|
1672
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
1673
|
+
className: "flex gap-2",
|
|
1674
|
+
children: [
|
|
1675
|
+
/* @__PURE__ */ jsxs4(Button, {
|
|
1676
|
+
variant: "outline",
|
|
1677
|
+
size: "sm",
|
|
1678
|
+
className: "flex-1",
|
|
1679
|
+
disabled: !address.trim(),
|
|
1680
|
+
onClick: () => onSend(address.trim()),
|
|
1681
|
+
children: [
|
|
1682
|
+
"Send ",
|
|
1683
|
+
selectedOpns.size,
|
|
1684
|
+
" Domain",
|
|
1685
|
+
selectedOpns.size !== 1 ? "s" : ""
|
|
1686
|
+
]
|
|
1687
|
+
}),
|
|
1688
|
+
/* @__PURE__ */ jsx9(Button, {
|
|
1689
|
+
size: "sm",
|
|
1690
|
+
className: "flex-1",
|
|
1691
|
+
onClick: onSweep,
|
|
1692
|
+
disabled: !walletConnected,
|
|
1693
|
+
title: walletConnected ? undefined : "Connect BRC-100 wallet to sweep",
|
|
1694
|
+
children: "Sweep to Wallet"
|
|
1695
|
+
}),
|
|
1696
|
+
/* @__PURE__ */ jsx9(Button, {
|
|
1697
|
+
size: "sm",
|
|
1698
|
+
className: "bg-red-600 hover:bg-red-700 text-white",
|
|
1699
|
+
onClick: () => {
|
|
1700
|
+
if (window.confirm(`Permanently burn ${selectedOpns.size} domain${selectedOpns.size !== 1 ? "s" : ""}? This cannot be undone.`))
|
|
1701
|
+
onBurn();
|
|
1702
|
+
},
|
|
1703
|
+
children: "Burn"
|
|
1704
|
+
})
|
|
1705
|
+
]
|
|
1706
|
+
})
|
|
1707
|
+
]
|
|
1708
|
+
})
|
|
1709
|
+
]
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// src/components/tx-history.tsx
|
|
1714
|
+
import { Loader2 as Loader23, ExternalLink } from "lucide-react";
|
|
1715
|
+
import { jsx as jsx10, jsxs as jsxs5, Fragment as Fragment2 } from "react/jsx-runtime";
|
|
1716
|
+
var EXPLORER_BASE = "https://bananablocks.com/tx/";
|
|
1717
|
+
function TxHistory({ sweeping, progress, history }) {
|
|
1718
|
+
return /* @__PURE__ */ jsxs5(Fragment2, {
|
|
1719
|
+
children: [
|
|
1720
|
+
sweeping && /* @__PURE__ */ jsxs5("div", {
|
|
1721
|
+
className: "text-center space-y-4 py-8",
|
|
1722
|
+
children: [
|
|
1723
|
+
/* @__PURE__ */ jsx10(Loader23, {
|
|
1724
|
+
className: "h-8 w-8 animate-spin mx-auto text-primary"
|
|
1725
|
+
}),
|
|
1726
|
+
/* @__PURE__ */ jsx10("p", {
|
|
1727
|
+
className: "text-sm text-muted-foreground animate-pulse",
|
|
1728
|
+
children: progress
|
|
1729
|
+
}),
|
|
1730
|
+
/* @__PURE__ */ jsx10("p", {
|
|
1731
|
+
className: "text-xs text-destructive/80",
|
|
1732
|
+
children: "Do not close this page."
|
|
1733
|
+
})
|
|
1734
|
+
]
|
|
1735
|
+
}),
|
|
1736
|
+
history.length > 0 && !sweeping && /* @__PURE__ */ jsxs5("div", {
|
|
1737
|
+
className: "border border-border/50 rounded-lg p-3 space-y-2",
|
|
1738
|
+
children: [
|
|
1739
|
+
/* @__PURE__ */ jsxs5("div", {
|
|
1740
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
1741
|
+
children: [
|
|
1742
|
+
"Transactions (",
|
|
1743
|
+
history.length,
|
|
1744
|
+
")"
|
|
1745
|
+
]
|
|
1746
|
+
}),
|
|
1747
|
+
/* @__PURE__ */ jsx10("div", {
|
|
1748
|
+
className: "space-y-2 max-h-48 overflow-y-auto",
|
|
1749
|
+
children: [...history].reverse().map((tx, i) => /* @__PURE__ */ jsxs5("div", {
|
|
1750
|
+
className: "flex items-center justify-between gap-2 text-xs",
|
|
1751
|
+
children: [
|
|
1752
|
+
/* @__PURE__ */ jsxs5("div", {
|
|
1753
|
+
className: "flex items-center gap-2 min-w-0",
|
|
1754
|
+
children: [
|
|
1755
|
+
/* @__PURE__ */ jsx10("span", {
|
|
1756
|
+
className: tx.error ? "text-red-500" : "text-green-500",
|
|
1757
|
+
children: tx.error ? "✗" : "✓"
|
|
1758
|
+
}),
|
|
1759
|
+
/* @__PURE__ */ jsx10("span", {
|
|
1760
|
+
className: "text-muted-foreground truncate",
|
|
1761
|
+
children: tx.label
|
|
1762
|
+
})
|
|
1763
|
+
]
|
|
1764
|
+
}),
|
|
1765
|
+
tx.error ? /* @__PURE__ */ jsx10("span", {
|
|
1766
|
+
className: "text-red-500 text-[10px] truncate max-w-[200px]",
|
|
1767
|
+
children: tx.error
|
|
1768
|
+
}) : /* @__PURE__ */ jsxs5("a", {
|
|
1769
|
+
href: `${EXPLORER_BASE}${tx.txid}`,
|
|
1770
|
+
target: "_blank",
|
|
1771
|
+
rel: "noopener noreferrer",
|
|
1772
|
+
className: "text-blue-400 hover:text-blue-300 flex items-center gap-1 shrink-0",
|
|
1773
|
+
children: [
|
|
1774
|
+
/* @__PURE__ */ jsxs5("code", {
|
|
1775
|
+
className: "text-[10px] font-mono",
|
|
1776
|
+
children: [
|
|
1777
|
+
tx.txid.substring(0, 12),
|
|
1778
|
+
"..."
|
|
1779
|
+
]
|
|
1780
|
+
}),
|
|
1781
|
+
/* @__PURE__ */ jsx10(ExternalLink, {
|
|
1782
|
+
className: "h-3 w-3"
|
|
1783
|
+
})
|
|
1784
|
+
]
|
|
1785
|
+
})
|
|
1786
|
+
]
|
|
1787
|
+
}, `${tx.txid}-${i}`))
|
|
1788
|
+
})
|
|
1789
|
+
]
|
|
1790
|
+
})
|
|
1791
|
+
]
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// src/lib/sweeper.ts
|
|
1796
|
+
import {
|
|
1797
|
+
createContext as createContext2,
|
|
1798
|
+
prepareSweepInputs,
|
|
1799
|
+
sweepBsv,
|
|
1800
|
+
sweepOrdinals,
|
|
1801
|
+
sweepBsv21
|
|
1802
|
+
} from "@1sat/actions";
|
|
1803
|
+
async function executeSweep(params) {
|
|
1804
|
+
const { wallet, wif, funding, ordinals, bsv21Tokens, amount, onProgress } = params;
|
|
1805
|
+
const ctx = createContext2(wallet, { services: getServices(), chain: "main" });
|
|
1806
|
+
const result = {
|
|
1807
|
+
ordinalTxids: [],
|
|
1808
|
+
bsv21Txids: [],
|
|
1809
|
+
errors: []
|
|
1810
|
+
};
|
|
1811
|
+
if (funding.length > 0) {
|
|
1812
|
+
onProgress(`Sweeping ${funding.length} BSV UTXOs...`);
|
|
1813
|
+
try {
|
|
1814
|
+
const inputs = await prepareSweepInputs(ctx, funding);
|
|
1815
|
+
const bsvResult = await sweepBsv.execute(ctx, { inputs, wif, amount });
|
|
1816
|
+
if (bsvResult.error)
|
|
1817
|
+
result.errors.push(`BSV: ${bsvResult.error}`);
|
|
1818
|
+
else if (bsvResult.txid)
|
|
1819
|
+
result.bsvTxid = bsvResult.txid;
|
|
1820
|
+
} catch (e) {
|
|
1821
|
+
result.errors.push(`BSV: ${e instanceof Error ? e.message : String(e)}`);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
if (ordinals.length > 0) {
|
|
1825
|
+
onProgress(`Sweeping ${ordinals.length} ordinals...`);
|
|
1826
|
+
try {
|
|
1827
|
+
const inputs = await prepareSweepInputs(ctx, ordinals);
|
|
1828
|
+
const ordResult = await sweepOrdinals.execute(ctx, { inputs, wif });
|
|
1829
|
+
if (ordResult.error)
|
|
1830
|
+
result.errors.push(`Ordinals: ${ordResult.error}`);
|
|
1831
|
+
else if (ordResult.txid)
|
|
1832
|
+
result.ordinalTxids.push(ordResult.txid);
|
|
1833
|
+
} catch (e) {
|
|
1834
|
+
result.errors.push(`Ordinals: ${e instanceof Error ? e.message : String(e)}`);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
if (bsv21Tokens.length > 0) {
|
|
1838
|
+
const groups = new Map;
|
|
1839
|
+
for (const token of bsv21Tokens) {
|
|
1840
|
+
const tokenEvent = token.events?.find((e) => e.startsWith("tokenId:"));
|
|
1841
|
+
const tokenId = tokenEvent?.slice(8) ?? "unknown";
|
|
1842
|
+
const group = groups.get(tokenId) ?? [];
|
|
1843
|
+
group.push(token);
|
|
1844
|
+
groups.set(tokenId, group);
|
|
1845
|
+
}
|
|
1846
|
+
for (const [tokenId, tokens] of groups) {
|
|
1847
|
+
onProgress(`Sweeping ${tokens.length} tokens (${tokenId.slice(0, 8)}...)...`);
|
|
1848
|
+
try {
|
|
1849
|
+
const inputs = await prepareSweepInputs(ctx, tokens);
|
|
1850
|
+
const tokenResult = await sweepBsv21.execute(ctx, {
|
|
1851
|
+
inputs: inputs.map((inp) => ({
|
|
1852
|
+
...inp,
|
|
1853
|
+
tokenId,
|
|
1854
|
+
amount: "0"
|
|
1855
|
+
})),
|
|
1856
|
+
wif
|
|
1857
|
+
});
|
|
1858
|
+
if (tokenResult.error)
|
|
1859
|
+
result.errors.push(`BSV-21 (${tokenId.slice(0, 8)}): ${tokenResult.error}`);
|
|
1860
|
+
else if (tokenResult.txid)
|
|
1861
|
+
result.bsv21Txids.push(tokenResult.txid);
|
|
1862
|
+
} catch (e) {
|
|
1863
|
+
result.errors.push(`BSV-21 (${tokenId.slice(0, 8)}): ${e instanceof Error ? e.message : String(e)}`);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
onProgress("Sweep complete");
|
|
1868
|
+
return result;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// src/lib/legacy-send.ts
|
|
1872
|
+
import { parseOutpoint } from "@1sat/utils";
|
|
1873
|
+
import { MAP_PREFIX } from "@1sat/types";
|
|
1874
|
+
import { OP, P2PKH, PrivateKey as PrivateKey2, Script, Transaction, Utils } from "@bsv/sdk";
|
|
1875
|
+
async function fetchSourceTx(txid) {
|
|
1876
|
+
const services = getServices();
|
|
1877
|
+
const beef = await services.getBeefForTxid(txid);
|
|
1878
|
+
const found = beef.findTxid(txid);
|
|
1879
|
+
if (!found?.tx)
|
|
1880
|
+
throw new Error(`Transaction ${txid} not found in BEEF`);
|
|
1881
|
+
return found.tx;
|
|
1882
|
+
}
|
|
1883
|
+
function buildKeyMap(keys) {
|
|
1884
|
+
const map = new Map;
|
|
1885
|
+
const payKey = PrivateKey2.fromWif(keys.payPk);
|
|
1886
|
+
const ordKey = PrivateKey2.fromWif(keys.ordPk);
|
|
1887
|
+
map.set(deriveAddress(keys.payPk), payKey);
|
|
1888
|
+
map.set(deriveAddress(keys.ordPk), ordKey);
|
|
1889
|
+
if (keys.identityPk) {
|
|
1890
|
+
map.set(deriveAddress(keys.identityPk), PrivateKey2.fromWif(keys.identityPk));
|
|
1891
|
+
}
|
|
1892
|
+
return map;
|
|
1893
|
+
}
|
|
1894
|
+
function keyForOutput(output, keyMap, fallback) {
|
|
1895
|
+
if (output.events) {
|
|
1896
|
+
for (const event of output.events) {
|
|
1897
|
+
if (event.startsWith("own:") || event.startsWith("p2pkh:")) {
|
|
1898
|
+
const addr = event.split(":")[1];
|
|
1899
|
+
const key = keyMap.get(addr);
|
|
1900
|
+
if (key)
|
|
1901
|
+
return key;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
return fallback;
|
|
1906
|
+
}
|
|
1907
|
+
async function legacySendBsv(params) {
|
|
1908
|
+
const { funding, keys, destination, amount } = params;
|
|
1909
|
+
if (!funding.length)
|
|
1910
|
+
throw new Error("No funding UTXOs");
|
|
1911
|
+
if (!destination)
|
|
1912
|
+
throw new Error("No destination address");
|
|
1913
|
+
const keyMap = buildKeyMap(keys);
|
|
1914
|
+
const payKey = PrivateKey2.fromWif(keys.payPk);
|
|
1915
|
+
const sourceAddress = payKey.toPublicKey().toAddress();
|
|
1916
|
+
const p2pkh = new P2PKH;
|
|
1917
|
+
const tx = new Transaction;
|
|
1918
|
+
for (const utxo of funding) {
|
|
1919
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
1920
|
+
const key = keyForOutput(utxo, keyMap, payKey);
|
|
1921
|
+
tx.addInput({
|
|
1922
|
+
sourceTXID: txid,
|
|
1923
|
+
sourceOutputIndex: vout,
|
|
1924
|
+
sourceTransaction: await fetchSourceTx(txid),
|
|
1925
|
+
unlockingScriptTemplate: p2pkh.unlock(key),
|
|
1926
|
+
sequence: 4294967295
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
if (amount) {
|
|
1930
|
+
tx.addOutput({
|
|
1931
|
+
lockingScript: p2pkh.lock(destination),
|
|
1932
|
+
satoshis: amount
|
|
1933
|
+
});
|
|
1934
|
+
tx.addOutput({
|
|
1935
|
+
lockingScript: p2pkh.lock(sourceAddress),
|
|
1936
|
+
change: true
|
|
1937
|
+
});
|
|
1938
|
+
} else {
|
|
1939
|
+
tx.addOutput({
|
|
1940
|
+
lockingScript: p2pkh.lock(destination),
|
|
1941
|
+
change: true
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
await tx.fee();
|
|
1945
|
+
await tx.sign();
|
|
1946
|
+
const rawTx = tx.toBinary();
|
|
1947
|
+
const result = await getServices().arcade.submitTransaction(rawTx);
|
|
1948
|
+
return {
|
|
1949
|
+
txid: result.txid,
|
|
1950
|
+
rawtx: Utils.toHex(rawTx)
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
async function legacySendOrdinals(params) {
|
|
1954
|
+
const { ordinals, funding, keys, destination } = params;
|
|
1955
|
+
if (!ordinals.length)
|
|
1956
|
+
throw new Error("No ordinals to send");
|
|
1957
|
+
if (!funding.length)
|
|
1958
|
+
throw new Error("No funding UTXOs for fees");
|
|
1959
|
+
if (!destination)
|
|
1960
|
+
throw new Error("No destination address");
|
|
1961
|
+
const keyMap = buildKeyMap(keys);
|
|
1962
|
+
const payKey = PrivateKey2.fromWif(keys.payPk);
|
|
1963
|
+
const sourceAddress = payKey.toPublicKey().toAddress();
|
|
1964
|
+
const p2pkh = new P2PKH;
|
|
1965
|
+
const tx = new Transaction;
|
|
1966
|
+
for (const ord of ordinals) {
|
|
1967
|
+
const { txid, vout } = parseOutpoint(ord.outpoint);
|
|
1968
|
+
const key = keyForOutput(ord, keyMap, payKey);
|
|
1969
|
+
tx.addInput({
|
|
1970
|
+
sourceTXID: txid,
|
|
1971
|
+
sourceOutputIndex: vout,
|
|
1972
|
+
sourceTransaction: await fetchSourceTx(txid),
|
|
1973
|
+
unlockingScriptTemplate: p2pkh.unlock(key),
|
|
1974
|
+
sequence: 4294967295
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
for (const _ord of ordinals) {
|
|
1978
|
+
tx.addOutput({
|
|
1979
|
+
lockingScript: p2pkh.lock(destination),
|
|
1980
|
+
satoshis: 1
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
for (const utxo of funding) {
|
|
1984
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
1985
|
+
const key = keyForOutput(utxo, keyMap, payKey);
|
|
1986
|
+
tx.addInput({
|
|
1987
|
+
sourceTXID: txid,
|
|
1988
|
+
sourceOutputIndex: vout,
|
|
1989
|
+
sourceTransaction: await fetchSourceTx(txid),
|
|
1990
|
+
unlockingScriptTemplate: p2pkh.unlock(key),
|
|
1991
|
+
sequence: 4294967295
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
tx.addOutput({
|
|
1995
|
+
lockingScript: p2pkh.lock(sourceAddress),
|
|
1996
|
+
change: true
|
|
1997
|
+
});
|
|
1998
|
+
await tx.fee();
|
|
1999
|
+
await tx.sign();
|
|
2000
|
+
const rawTx = tx.toBinary();
|
|
2001
|
+
const result = await getServices().arcade.submitTransaction(rawTx);
|
|
2002
|
+
return {
|
|
2003
|
+
txid: result.txid,
|
|
2004
|
+
rawtx: Utils.toHex(rawTx)
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
async function legacyBurnOrdinals(params) {
|
|
2008
|
+
const { ordinals, funding, keys } = params;
|
|
2009
|
+
if (!ordinals.length)
|
|
2010
|
+
throw new Error("No ordinals to burn");
|
|
2011
|
+
const keyMap = buildKeyMap(keys);
|
|
2012
|
+
const payKey = PrivateKey2.fromWif(keys.payPk);
|
|
2013
|
+
const sourceAddress = payKey.toPublicKey().toAddress();
|
|
2014
|
+
const p2pkh = new P2PKH;
|
|
2015
|
+
const tx = new Transaction;
|
|
2016
|
+
for (const ord of ordinals) {
|
|
2017
|
+
const { txid, vout } = parseOutpoint(ord.outpoint);
|
|
2018
|
+
const key = keyForOutput(ord, keyMap, payKey);
|
|
2019
|
+
tx.addInput({
|
|
2020
|
+
sourceTXID: txid,
|
|
2021
|
+
sourceOutputIndex: vout,
|
|
2022
|
+
sourceTransaction: await fetchSourceTx(txid),
|
|
2023
|
+
unlockingScriptTemplate: p2pkh.unlock(key),
|
|
2024
|
+
sequence: 4294967295
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
for (const utxo of funding) {
|
|
2028
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
2029
|
+
const key = keyForOutput(utxo, keyMap, payKey);
|
|
2030
|
+
tx.addInput({
|
|
2031
|
+
sourceTXID: txid,
|
|
2032
|
+
sourceOutputIndex: vout,
|
|
2033
|
+
sourceTransaction: await fetchSourceTx(txid),
|
|
2034
|
+
unlockingScriptTemplate: p2pkh.unlock(key),
|
|
2035
|
+
sequence: 4294967295
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
const burnScript = new Script().writeOpCode(OP.OP_FALSE).writeOpCode(OP.OP_RETURN).writeBin(Utils.toArray(MAP_PREFIX)).writeBin(Utils.toArray("SET")).writeBin(Utils.toArray("app")).writeBin(Utils.toArray("1sat-sweep")).writeBin(Utils.toArray("type")).writeBin(Utils.toArray("ord")).writeBin(Utils.toArray("op")).writeBin(Utils.toArray("burn"));
|
|
2039
|
+
tx.addOutput({ satoshis: 0, lockingScript: burnScript });
|
|
2040
|
+
tx.addOutput({
|
|
2041
|
+
lockingScript: p2pkh.lock(sourceAddress),
|
|
2042
|
+
change: true
|
|
2043
|
+
});
|
|
2044
|
+
await tx.fee();
|
|
2045
|
+
await tx.sign();
|
|
2046
|
+
const rawTx = tx.toBinary();
|
|
2047
|
+
const result = await getServices().arcade.submitTransaction(rawTx);
|
|
2048
|
+
return {
|
|
2049
|
+
txid: result.txid,
|
|
2050
|
+
rawtx: Utils.toHex(rawTx)
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// src/components/SweepApp.tsx
|
|
2055
|
+
import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2056
|
+
function SweepApp({ legacyKeys: initialKeys }) {
|
|
2057
|
+
const [walletConnected, setWalletConnected] = useState5(false);
|
|
2058
|
+
const [scanning, setScanning] = useState5(false);
|
|
2059
|
+
const [scanProgress, setScanProgress] = useState5("");
|
|
2060
|
+
const [assets, setAssets] = useState5(null);
|
|
2061
|
+
const [legacyKeys, setLegacyKeys] = useState5(null);
|
|
2062
|
+
const [sweeping, setSweeping] = useState5(false);
|
|
2063
|
+
const [sweepProgress, setSweepProgress] = useState5("");
|
|
2064
|
+
const [txHistory, setTxHistory] = useState5([]);
|
|
2065
|
+
const [selectedOrdinals, setSelectedOrdinals] = useState5(new Set);
|
|
2066
|
+
const [selectedOpns, setSelectedOpns] = useState5(new Set);
|
|
2067
|
+
const [sweepAmount, setSweepAmount] = useState5(null);
|
|
2068
|
+
const [activeTab, setActiveTab] = useState5("ordinals");
|
|
2069
|
+
const addTx = useCallback2((label, txid, error) => {
|
|
2070
|
+
setTxHistory((prev) => [...prev, { label, txid, timestamp: new Date, error }]);
|
|
2071
|
+
}, []);
|
|
2072
|
+
const tabs = useMemo(() => {
|
|
2073
|
+
if (!assets)
|
|
2074
|
+
return [];
|
|
2075
|
+
const t = [];
|
|
2076
|
+
if (assets.ordinals.length > 0)
|
|
2077
|
+
t.push({ id: "ordinals", label: "Ordinals", count: assets.ordinals.length });
|
|
2078
|
+
if (assets.opnsNames.length > 0)
|
|
2079
|
+
t.push({ id: "opns", label: "OpNS", count: assets.opnsNames.length });
|
|
2080
|
+
if (assets.bsv21Tokens.length > 0)
|
|
2081
|
+
t.push({ id: "bsv21", label: "BSV-21", count: assets.bsv21Tokens.length });
|
|
2082
|
+
if (assets.bsv20Tokens.length > 0)
|
|
2083
|
+
t.push({ id: "bsv20", label: "BSV-20", count: assets.bsv20Tokens.length });
|
|
2084
|
+
if (assets.locked.length > 0)
|
|
2085
|
+
t.push({ id: "locks", label: "Locks", count: assets.locked.length });
|
|
2086
|
+
return t;
|
|
2087
|
+
}, [assets]);
|
|
2088
|
+
const handleToggleOrdinal = useCallback2((outpoint) => {
|
|
2089
|
+
setSelectedOrdinals((prev) => {
|
|
2090
|
+
const next = new Set(prev);
|
|
2091
|
+
if (next.has(outpoint))
|
|
2092
|
+
next.delete(outpoint);
|
|
2093
|
+
else
|
|
2094
|
+
next.add(outpoint);
|
|
2095
|
+
return next;
|
|
2096
|
+
});
|
|
2097
|
+
}, []);
|
|
2098
|
+
const handleSelectAllOrdinals = useCallback2(() => {
|
|
2099
|
+
if (assets)
|
|
2100
|
+
setSelectedOrdinals(new Set(assets.ordinals.map((o) => o.outpoint)));
|
|
2101
|
+
}, [assets]);
|
|
2102
|
+
const handleDeselectAllOrdinals = useCallback2(() => setSelectedOrdinals(new Set), []);
|
|
2103
|
+
const handleToggleOpns = useCallback2((outpoint) => {
|
|
2104
|
+
setSelectedOpns((prev) => {
|
|
2105
|
+
const next = new Set(prev);
|
|
2106
|
+
if (next.has(outpoint))
|
|
2107
|
+
next.delete(outpoint);
|
|
2108
|
+
else
|
|
2109
|
+
next.add(outpoint);
|
|
2110
|
+
return next;
|
|
2111
|
+
});
|
|
2112
|
+
}, []);
|
|
2113
|
+
const handleSelectAllOpns = useCallback2(() => {
|
|
2114
|
+
if (assets)
|
|
2115
|
+
setSelectedOpns(new Set(assets.opnsNames.map((o) => o.outpoint)));
|
|
2116
|
+
}, [assets]);
|
|
2117
|
+
const handleDeselectAllOpns = useCallback2(() => setSelectedOpns(new Set), []);
|
|
2118
|
+
const refreshAssets = useCallback2(async () => {
|
|
2119
|
+
if (!legacyKeys)
|
|
2120
|
+
return;
|
|
2121
|
+
const addresses = [...new Set([deriveAddress(legacyKeys.payPk), deriveAddress(legacyKeys.ordPk), ...legacyKeys.identityPk ? [deriveAddress(legacyKeys.identityPk)] : []])];
|
|
2122
|
+
const result = await scanAddresses(addresses);
|
|
2123
|
+
setAssets(result);
|
|
2124
|
+
setSelectedOrdinals(new Set);
|
|
2125
|
+
setSelectedOpns(new Set);
|
|
2126
|
+
setSweepAmount(null);
|
|
2127
|
+
}, [legacyKeys]);
|
|
2128
|
+
const handleScan = useCallback2(async (keys) => {
|
|
2129
|
+
setScanning(true);
|
|
2130
|
+
setAssets(null);
|
|
2131
|
+
setSelectedOrdinals(new Set);
|
|
2132
|
+
setSelectedOpns(new Set);
|
|
2133
|
+
setSweepAmount(null);
|
|
2134
|
+
setLegacyKeys(keys);
|
|
2135
|
+
try {
|
|
2136
|
+
const addresses = [...new Set([deriveAddress(keys.payPk), deriveAddress(keys.ordPk), ...keys.identityPk ? [deriveAddress(keys.identityPk)] : []])];
|
|
2137
|
+
const result = await scanAddresses(addresses, (p) => setScanProgress(p.detail ?? p.phase));
|
|
2138
|
+
setAssets(result);
|
|
2139
|
+
const total = result.funding.length + result.ordinals.length + result.opnsNames.length + result.bsv21Tokens.reduce((n, t) => n + t.outputs.length, 0) + result.bsv20Tokens.length + result.locked.length;
|
|
2140
|
+
if (total === 0)
|
|
2141
|
+
toast.info("No assets found at legacy addresses");
|
|
2142
|
+
if (result.ordinals.length > 0)
|
|
2143
|
+
setActiveTab("ordinals");
|
|
2144
|
+
else if (result.opnsNames.length > 0)
|
|
2145
|
+
setActiveTab("opns");
|
|
2146
|
+
else if (result.bsv21Tokens.length > 0)
|
|
2147
|
+
setActiveTab("bsv21");
|
|
2148
|
+
else if (result.bsv20Tokens.length > 0)
|
|
2149
|
+
setActiveTab("bsv20");
|
|
2150
|
+
else if (result.locked.length > 0)
|
|
2151
|
+
setActiveTab("locks");
|
|
2152
|
+
} catch (e) {
|
|
2153
|
+
toast.error(e instanceof Error ? e.message : "Scan failed");
|
|
2154
|
+
} finally {
|
|
2155
|
+
setScanning(false);
|
|
2156
|
+
}
|
|
2157
|
+
}, []);
|
|
2158
|
+
useEffect2(() => {
|
|
2159
|
+
if (initialKeys)
|
|
2160
|
+
handleScan(initialKeys);
|
|
2161
|
+
}, [initialKeys, handleScan]);
|
|
2162
|
+
const runOperation = useCallback2(async (label, op) => {
|
|
2163
|
+
setSweeping(true);
|
|
2164
|
+
setSweepProgress(label + "...");
|
|
2165
|
+
try {
|
|
2166
|
+
const txid = await op();
|
|
2167
|
+
addTx(label, txid);
|
|
2168
|
+
toast.success(label);
|
|
2169
|
+
await refreshAssets();
|
|
2170
|
+
} catch (e) {
|
|
2171
|
+
const msg = e instanceof Error ? e.message : "Operation failed";
|
|
2172
|
+
addTx(label, "", msg);
|
|
2173
|
+
toast.error(msg);
|
|
2174
|
+
} finally {
|
|
2175
|
+
setSweeping(false);
|
|
2176
|
+
}
|
|
2177
|
+
}, [addTx, refreshAssets]);
|
|
2178
|
+
const getSelectedFunding = useCallback2(() => {
|
|
2179
|
+
if (!assets)
|
|
2180
|
+
return [];
|
|
2181
|
+
if (sweepAmount === null)
|
|
2182
|
+
return assets.funding;
|
|
2183
|
+
const selected = [];
|
|
2184
|
+
let accumulated = 0;
|
|
2185
|
+
for (const utxo of assets.funding) {
|
|
2186
|
+
selected.push(utxo);
|
|
2187
|
+
accumulated += utxo.satoshis ?? 0;
|
|
2188
|
+
if (accumulated >= sweepAmount)
|
|
2189
|
+
break;
|
|
2190
|
+
}
|
|
2191
|
+
return selected;
|
|
2192
|
+
}, [assets, sweepAmount]);
|
|
2193
|
+
const handleSweepBsv = useCallback2(async () => {
|
|
2194
|
+
const wallet = getWallet();
|
|
2195
|
+
if (!wallet || !legacyKeys || !assets)
|
|
2196
|
+
return;
|
|
2197
|
+
await runOperation("Sweep BSV", async () => {
|
|
2198
|
+
const result = await executeSweep({ wallet, wif: legacyKeys.payPk, funding: getSelectedFunding(), ordinals: [], bsv21Tokens: [], amount: sweepAmount ?? undefined, onProgress: setSweepProgress });
|
|
2199
|
+
if (result.errors.length > 0)
|
|
2200
|
+
throw new Error(result.errors[0]);
|
|
2201
|
+
return result.bsvTxid ?? "";
|
|
2202
|
+
});
|
|
2203
|
+
}, [legacyKeys, assets, sweepAmount, getSelectedFunding, runOperation]);
|
|
2204
|
+
const handleSendBsv = useCallback2(async (destination) => {
|
|
2205
|
+
if (!legacyKeys || !assets)
|
|
2206
|
+
return;
|
|
2207
|
+
await runOperation("Send BSV", async () => {
|
|
2208
|
+
const result = await legacySendBsv({ funding: getSelectedFunding(), keys: legacyKeys, destination, amount: sweepAmount ?? undefined });
|
|
2209
|
+
return result.txid;
|
|
2210
|
+
});
|
|
2211
|
+
}, [legacyKeys, assets, sweepAmount, getSelectedFunding, runOperation]);
|
|
2212
|
+
const handleSweepOrdinals = useCallback2(async () => {
|
|
2213
|
+
const wallet = getWallet();
|
|
2214
|
+
if (!wallet || !legacyKeys || !assets)
|
|
2215
|
+
return;
|
|
2216
|
+
const selected = assets.ordinals.filter((o) => selectedOrdinals.has(o.outpoint));
|
|
2217
|
+
if (selected.length === 0)
|
|
2218
|
+
return;
|
|
2219
|
+
await runOperation(`Sweep ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2220
|
+
const result = await executeSweep({ wallet, wif: legacyKeys.payPk, funding: [], ordinals: selected, bsv21Tokens: [], onProgress: setSweepProgress });
|
|
2221
|
+
if (result.errors.length > 0)
|
|
2222
|
+
throw new Error(result.errors[0]);
|
|
2223
|
+
return result.ordinalTxids[0] ?? "";
|
|
2224
|
+
});
|
|
2225
|
+
}, [legacyKeys, assets, selectedOrdinals, runOperation]);
|
|
2226
|
+
const handleSendOrdinals = useCallback2(async (destination) => {
|
|
2227
|
+
if (!legacyKeys || !assets)
|
|
2228
|
+
return;
|
|
2229
|
+
const selected = assets.ordinals.filter((o) => selectedOrdinals.has(o.outpoint));
|
|
2230
|
+
if (selected.length === 0)
|
|
2231
|
+
return;
|
|
2232
|
+
await runOperation(`Send ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2233
|
+
const result = await legacySendOrdinals({ ordinals: selected, funding: assets.funding, keys: legacyKeys, destination });
|
|
2234
|
+
return result.txid;
|
|
2235
|
+
});
|
|
2236
|
+
}, [legacyKeys, assets, selectedOrdinals, runOperation]);
|
|
2237
|
+
const handleBurnOrdinals = useCallback2(async () => {
|
|
2238
|
+
if (!legacyKeys || !assets)
|
|
2239
|
+
return;
|
|
2240
|
+
const selected = assets.ordinals.filter((o) => selectedOrdinals.has(o.outpoint));
|
|
2241
|
+
if (selected.length === 0)
|
|
2242
|
+
return;
|
|
2243
|
+
await runOperation(`Burn ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2244
|
+
const result = await legacyBurnOrdinals({ ordinals: selected, funding: assets.funding, keys: legacyKeys });
|
|
2245
|
+
return result.txid;
|
|
2246
|
+
});
|
|
2247
|
+
}, [legacyKeys, assets, selectedOrdinals, runOperation]);
|
|
2248
|
+
const handleSweepOpns = useCallback2(async () => {
|
|
2249
|
+
const wallet = getWallet();
|
|
2250
|
+
if (!wallet || !legacyKeys || !assets)
|
|
2251
|
+
return;
|
|
2252
|
+
const selected = assets.opnsNames.filter((o) => selectedOpns.has(o.outpoint));
|
|
2253
|
+
if (selected.length === 0)
|
|
2254
|
+
return;
|
|
2255
|
+
await runOperation(`Sweep ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2256
|
+
const result = await executeSweep({ wallet, wif: legacyKeys.payPk, funding: [], ordinals: selected, bsv21Tokens: [], onProgress: setSweepProgress });
|
|
2257
|
+
if (result.errors.length > 0)
|
|
2258
|
+
throw new Error(result.errors[0]);
|
|
2259
|
+
return result.ordinalTxids[0] ?? "";
|
|
2260
|
+
});
|
|
2261
|
+
}, [legacyKeys, assets, selectedOpns, runOperation]);
|
|
2262
|
+
const handleSendOpns = useCallback2(async (destination) => {
|
|
2263
|
+
if (!legacyKeys || !assets)
|
|
2264
|
+
return;
|
|
2265
|
+
const selected = assets.opnsNames.filter((o) => selectedOpns.has(o.outpoint));
|
|
2266
|
+
if (selected.length === 0)
|
|
2267
|
+
return;
|
|
2268
|
+
await runOperation(`Send ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2269
|
+
const result = await legacySendOrdinals({ ordinals: selected, funding: assets.funding, keys: legacyKeys, destination });
|
|
2270
|
+
return result.txid;
|
|
2271
|
+
});
|
|
2272
|
+
}, [legacyKeys, assets, selectedOpns, runOperation]);
|
|
2273
|
+
const handleBurnOpns = useCallback2(async () => {
|
|
2274
|
+
if (!legacyKeys || !assets)
|
|
2275
|
+
return;
|
|
2276
|
+
const selected = assets.opnsNames.filter((o) => selectedOpns.has(o.outpoint));
|
|
2277
|
+
if (selected.length === 0)
|
|
2278
|
+
return;
|
|
2279
|
+
await runOperation(`Burn ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2280
|
+
const result = await legacyBurnOrdinals({ ordinals: selected, funding: assets.funding, keys: legacyKeys });
|
|
2281
|
+
return result.txid;
|
|
2282
|
+
});
|
|
2283
|
+
}, [legacyKeys, assets, selectedOpns, runOperation]);
|
|
2284
|
+
return /* @__PURE__ */ jsxs6("div", {
|
|
2285
|
+
className: "min-h-screen bg-background text-foreground",
|
|
2286
|
+
children: [
|
|
2287
|
+
/* @__PURE__ */ jsx11(Toaster, {
|
|
2288
|
+
position: "top-right"
|
|
2289
|
+
}),
|
|
2290
|
+
/* @__PURE__ */ jsxs6("div", {
|
|
2291
|
+
className: "mx-auto max-w-lg p-4 space-y-4 py-12",
|
|
2292
|
+
children: [
|
|
2293
|
+
/* @__PURE__ */ jsxs6("div", {
|
|
2294
|
+
className: "text-center space-y-2 mb-4",
|
|
2295
|
+
children: [
|
|
2296
|
+
/* @__PURE__ */ jsx11("h1", {
|
|
2297
|
+
className: "text-3xl font-bold tracking-tight",
|
|
2298
|
+
children: "1Sat Sweep"
|
|
2299
|
+
}),
|
|
2300
|
+
/* @__PURE__ */ jsx11("p", {
|
|
2301
|
+
className: "text-sm text-muted-foreground",
|
|
2302
|
+
children: "Transfer or sweep legacy assets"
|
|
2303
|
+
})
|
|
2304
|
+
]
|
|
2305
|
+
}),
|
|
2306
|
+
/* @__PURE__ */ jsx11(ConnectWallet, {
|
|
2307
|
+
onConnected: () => setWalletConnected(true),
|
|
2308
|
+
onDisconnected: () => setWalletConnected(false),
|
|
2309
|
+
connected: walletConnected
|
|
2310
|
+
}),
|
|
2311
|
+
!initialKeys && /* @__PURE__ */ jsx11(WifInput, {
|
|
2312
|
+
onScan: handleScan,
|
|
2313
|
+
scanning,
|
|
2314
|
+
disabled: sweeping
|
|
2315
|
+
}),
|
|
2316
|
+
scanning && /* @__PURE__ */ jsx11("p", {
|
|
2317
|
+
className: "text-sm text-center text-muted-foreground animate-pulse",
|
|
2318
|
+
children: scanProgress
|
|
2319
|
+
}),
|
|
2320
|
+
assets && !sweeping && /* @__PURE__ */ jsxs6("div", {
|
|
2321
|
+
className: "space-y-3",
|
|
2322
|
+
children: [
|
|
2323
|
+
/* @__PURE__ */ jsx11(FundingSection, {
|
|
2324
|
+
funding: assets.funding,
|
|
2325
|
+
totalBsv: assets.totalBsv,
|
|
2326
|
+
sweepAmount,
|
|
2327
|
+
onSweepAmountChange: setSweepAmount,
|
|
2328
|
+
onSweep: handleSweepBsv,
|
|
2329
|
+
onSend: handleSendBsv,
|
|
2330
|
+
walletConnected
|
|
2331
|
+
}),
|
|
2332
|
+
tabs.length > 0 && /* @__PURE__ */ jsxs6(Tabs, {
|
|
2333
|
+
value: activeTab,
|
|
2334
|
+
onValueChange: (v) => setActiveTab(v),
|
|
2335
|
+
children: [
|
|
2336
|
+
/* @__PURE__ */ jsx11(TabsList, {
|
|
2337
|
+
className: "w-full",
|
|
2338
|
+
children: tabs.map((tab) => /* @__PURE__ */ jsxs6(TabsTrigger, {
|
|
2339
|
+
value: tab.id,
|
|
2340
|
+
className: "flex-1 gap-1.5",
|
|
2341
|
+
children: [
|
|
2342
|
+
tab.label,
|
|
2343
|
+
/* @__PURE__ */ jsx11(Badge, {
|
|
2344
|
+
variant: "secondary",
|
|
2345
|
+
className: "text-[10px] px-1.5 py-0",
|
|
2346
|
+
children: tab.count
|
|
2347
|
+
})
|
|
2348
|
+
]
|
|
2349
|
+
}, tab.id))
|
|
2350
|
+
}),
|
|
2351
|
+
/* @__PURE__ */ jsx11(TabsContent, {
|
|
2352
|
+
value: "ordinals",
|
|
2353
|
+
children: /* @__PURE__ */ jsx11(OrdinalsSection, {
|
|
2354
|
+
ordinals: assets.ordinals,
|
|
2355
|
+
selectedOrdinals,
|
|
2356
|
+
onToggle: handleToggleOrdinal,
|
|
2357
|
+
onSelectAll: handleSelectAllOrdinals,
|
|
2358
|
+
onDeselectAll: handleDeselectAllOrdinals,
|
|
2359
|
+
onSweep: handleSweepOrdinals,
|
|
2360
|
+
onSend: handleSendOrdinals,
|
|
2361
|
+
onBurn: handleBurnOrdinals,
|
|
2362
|
+
walletConnected
|
|
2363
|
+
})
|
|
2364
|
+
}),
|
|
2365
|
+
/* @__PURE__ */ jsx11(TabsContent, {
|
|
2366
|
+
value: "opns",
|
|
2367
|
+
children: /* @__PURE__ */ jsx11(OpnsSection, {
|
|
2368
|
+
opnsNames: assets.opnsNames,
|
|
2369
|
+
selectedOpns,
|
|
2370
|
+
onToggle: handleToggleOpns,
|
|
2371
|
+
onSelectAll: handleSelectAllOpns,
|
|
2372
|
+
onDeselectAll: handleDeselectAllOpns,
|
|
2373
|
+
onSweep: handleSweepOpns,
|
|
2374
|
+
onSend: handleSendOpns,
|
|
2375
|
+
onBurn: handleBurnOpns,
|
|
2376
|
+
walletConnected
|
|
2377
|
+
})
|
|
2378
|
+
}),
|
|
2379
|
+
/* @__PURE__ */ jsx11(TabsContent, {
|
|
2380
|
+
value: "bsv21",
|
|
2381
|
+
children: /* @__PURE__ */ jsx11(Bsv21Section, {
|
|
2382
|
+
tokens: assets.bsv21Tokens
|
|
2383
|
+
})
|
|
2384
|
+
}),
|
|
2385
|
+
/* @__PURE__ */ jsx11(TabsContent, {
|
|
2386
|
+
value: "bsv20",
|
|
2387
|
+
children: /* @__PURE__ */ jsx11(Bsv20Section, {
|
|
2388
|
+
tokens: assets.bsv20Tokens
|
|
2389
|
+
})
|
|
2390
|
+
}),
|
|
2391
|
+
/* @__PURE__ */ jsx11(TabsContent, {
|
|
2392
|
+
value: "locks",
|
|
2393
|
+
children: /* @__PURE__ */ jsx11(LockedSection, {
|
|
2394
|
+
locked: assets.locked
|
|
2395
|
+
})
|
|
2396
|
+
})
|
|
2397
|
+
]
|
|
2398
|
+
})
|
|
2399
|
+
]
|
|
2400
|
+
}),
|
|
2401
|
+
/* @__PURE__ */ jsx11(TxHistory, {
|
|
2402
|
+
sweeping,
|
|
2403
|
+
progress: sweepProgress,
|
|
2404
|
+
history: txHistory
|
|
2405
|
+
})
|
|
2406
|
+
]
|
|
2407
|
+
})
|
|
2408
|
+
]
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
// src/components/sweep-progress.tsx
|
|
2412
|
+
import { CheckCircle2, Loader2 as Loader24, AlertTriangle, ExternalLink as ExternalLink2 } from "lucide-react";
|
|
2413
|
+
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2414
|
+
var EXPLORER_BASE2 = "https://bananablocks.com/tx/";
|
|
2415
|
+
function TxLink({ label, txid }) {
|
|
2416
|
+
return /* @__PURE__ */ jsxs7("div", {
|
|
2417
|
+
className: "border-b border-border/30 pb-2 space-y-1",
|
|
2418
|
+
children: [
|
|
2419
|
+
/* @__PURE__ */ jsxs7("div", {
|
|
2420
|
+
className: "flex items-center justify-between text-sm",
|
|
2421
|
+
children: [
|
|
2422
|
+
/* @__PURE__ */ jsx12("span", {
|
|
2423
|
+
className: "text-muted-foreground",
|
|
2424
|
+
children: label
|
|
2425
|
+
}),
|
|
2426
|
+
/* @__PURE__ */ jsxs7("a", {
|
|
2427
|
+
href: `${EXPLORER_BASE2}${txid}`,
|
|
2428
|
+
target: "_blank",
|
|
2429
|
+
rel: "noopener noreferrer",
|
|
2430
|
+
className: "text-blue-400 hover:text-blue-300 flex items-center gap-1",
|
|
2431
|
+
children: [
|
|
2432
|
+
/* @__PURE__ */ jsx12(ExternalLink2, {
|
|
2433
|
+
className: "h-3 w-3"
|
|
2434
|
+
}),
|
|
2435
|
+
/* @__PURE__ */ jsx12("span", {
|
|
2436
|
+
className: "text-xs",
|
|
2437
|
+
children: "View"
|
|
2438
|
+
})
|
|
2439
|
+
]
|
|
2440
|
+
})
|
|
2441
|
+
]
|
|
2442
|
+
}),
|
|
2443
|
+
/* @__PURE__ */ jsx12("code", {
|
|
2444
|
+
className: "text-xs font-mono text-muted-foreground break-all",
|
|
2445
|
+
children: txid
|
|
2446
|
+
})
|
|
2447
|
+
]
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
function SweepProgress({ sweeping, progress, result }) {
|
|
2451
|
+
if (sweeping) {
|
|
2452
|
+
return /* @__PURE__ */ jsxs7("div", {
|
|
2453
|
+
className: "text-center space-y-4 py-8",
|
|
2454
|
+
children: [
|
|
2455
|
+
/* @__PURE__ */ jsx12(Loader24, {
|
|
2456
|
+
className: "h-8 w-8 animate-spin mx-auto text-primary"
|
|
2457
|
+
}),
|
|
2458
|
+
/* @__PURE__ */ jsx12("p", {
|
|
2459
|
+
className: "text-sm text-muted-foreground animate-pulse",
|
|
2460
|
+
children: progress
|
|
2461
|
+
}),
|
|
2462
|
+
/* @__PURE__ */ jsx12("p", {
|
|
2463
|
+
className: "text-xs text-destructive/80",
|
|
2464
|
+
children: "Do not close this page."
|
|
2465
|
+
})
|
|
2466
|
+
]
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
if (!result)
|
|
2470
|
+
return null;
|
|
2471
|
+
const hasErrors = result.errors.length > 0;
|
|
2472
|
+
const hasTxids = result.bsvTxid || result.ordinalTxids.length > 0 || result.bsv21Txids.length > 0;
|
|
2473
|
+
return /* @__PURE__ */ jsxs7("div", {
|
|
2474
|
+
className: "space-y-4 py-4",
|
|
2475
|
+
children: [
|
|
2476
|
+
/* @__PURE__ */ jsxs7("div", {
|
|
2477
|
+
className: "flex items-center gap-2",
|
|
2478
|
+
children: [
|
|
2479
|
+
hasErrors ? /* @__PURE__ */ jsx12(AlertTriangle, {
|
|
2480
|
+
className: "h-5 w-5 text-yellow-500"
|
|
2481
|
+
}) : /* @__PURE__ */ jsx12(CheckCircle2, {
|
|
2482
|
+
className: "h-5 w-5 text-green-500"
|
|
2483
|
+
}),
|
|
2484
|
+
/* @__PURE__ */ jsx12("span", {
|
|
2485
|
+
className: "font-semibold",
|
|
2486
|
+
children: hasErrors && !hasTxids ? "Failed" : hasErrors ? "Completed with Errors" : "Complete"
|
|
2487
|
+
})
|
|
2488
|
+
]
|
|
2489
|
+
}),
|
|
2490
|
+
result.bsvTxid && /* @__PURE__ */ jsx12(TxLink, {
|
|
2491
|
+
label: "BSV",
|
|
2492
|
+
txid: result.bsvTxid
|
|
2493
|
+
}),
|
|
2494
|
+
result.ordinalTxids.map((txid) => /* @__PURE__ */ jsx12(TxLink, {
|
|
2495
|
+
label: "Ordinals",
|
|
2496
|
+
txid
|
|
2497
|
+
}, txid)),
|
|
2498
|
+
result.bsv21Txids.map((txid) => /* @__PURE__ */ jsx12(TxLink, {
|
|
2499
|
+
label: "Tokens",
|
|
2500
|
+
txid
|
|
2501
|
+
}, txid)),
|
|
2502
|
+
result.errors.map((err) => /* @__PURE__ */ jsx12("p", {
|
|
2503
|
+
className: "text-xs text-destructive",
|
|
2504
|
+
children: err
|
|
2505
|
+
}, err))
|
|
2506
|
+
]
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
export {
|
|
2510
|
+
truncate,
|
|
2511
|
+
scanAddresses,
|
|
2512
|
+
scanAddress,
|
|
2513
|
+
legacySendOrdinals,
|
|
2514
|
+
legacySendBsv,
|
|
2515
|
+
legacyBurnOrdinals,
|
|
2516
|
+
isConnected,
|
|
2517
|
+
getWallet,
|
|
2518
|
+
getServices,
|
|
2519
|
+
getProvider,
|
|
2520
|
+
getIdentityKey,
|
|
2521
|
+
formatTokenAmount,
|
|
2522
|
+
formatSats,
|
|
2523
|
+
executeSweep,
|
|
2524
|
+
disconnectWallet,
|
|
2525
|
+
deriveAddress,
|
|
2526
|
+
connectWallet,
|
|
2527
|
+
configureServices,
|
|
2528
|
+
cn,
|
|
2529
|
+
buttonVariants,
|
|
2530
|
+
badgeVariants,
|
|
2531
|
+
WifInput,
|
|
2532
|
+
TxHistory,
|
|
2533
|
+
TabsTrigger,
|
|
2534
|
+
TabsList,
|
|
2535
|
+
TabsContent,
|
|
2536
|
+
Tabs,
|
|
2537
|
+
SweepProgress,
|
|
2538
|
+
SweepApp,
|
|
2539
|
+
OrdinalsSection,
|
|
2540
|
+
OpnsSection,
|
|
2541
|
+
LockedSection,
|
|
2542
|
+
Input,
|
|
2543
|
+
FundingSection,
|
|
2544
|
+
ConnectWallet,
|
|
2545
|
+
CardTitle,
|
|
2546
|
+
CardHeader,
|
|
2547
|
+
CardFooter,
|
|
2548
|
+
CardDescription,
|
|
2549
|
+
CardContent,
|
|
2550
|
+
CardAction,
|
|
2551
|
+
Card,
|
|
2552
|
+
Button,
|
|
2553
|
+
Bsv21Section,
|
|
2554
|
+
Bsv20Section,
|
|
2555
|
+
Badge
|
|
2556
|
+
};
|