@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.
Files changed (62) hide show
  1. package/dist/components/SweepApp.d.ts +6 -0
  2. package/dist/components/SweepApp.d.ts.map +1 -0
  3. package/dist/components/asset-preview.d.ts +32 -0
  4. package/dist/components/asset-preview.d.ts.map +1 -0
  5. package/dist/components/connect-wallet.d.ts +8 -0
  6. package/dist/components/connect-wallet.d.ts.map +1 -0
  7. package/dist/components/opns-section.d.ts +13 -0
  8. package/dist/components/opns-section.d.ts.map +1 -0
  9. package/dist/components/sweep-progress.d.ts +9 -0
  10. package/dist/components/sweep-progress.d.ts.map +1 -0
  11. package/dist/components/tx-history.d.ts +14 -0
  12. package/dist/components/tx-history.d.ts.map +1 -0
  13. package/dist/components/ui/badge.d.ts +8 -0
  14. package/dist/components/ui/badge.d.ts.map +1 -0
  15. package/dist/components/ui/button.d.ts +9 -0
  16. package/dist/components/ui/button.d.ts.map +1 -0
  17. package/dist/components/ui/card.d.ts +10 -0
  18. package/dist/components/ui/card.d.ts.map +1 -0
  19. package/dist/components/ui/input.d.ts +4 -0
  20. package/dist/components/ui/input.d.ts.map +1 -0
  21. package/dist/components/ui/tabs.d.ts +14 -0
  22. package/dist/components/ui/tabs.d.ts.map +1 -0
  23. package/dist/components/wif-input.d.ts +9 -0
  24. package/dist/components/wif-input.d.ts.map +1 -0
  25. package/dist/index.d.ts +20 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +2556 -0
  28. package/dist/lib/legacy-send.d.ts +24 -0
  29. package/dist/lib/legacy-send.d.ts.map +1 -0
  30. package/dist/lib/scanner.d.ts +33 -0
  31. package/dist/lib/scanner.d.ts.map +1 -0
  32. package/dist/lib/services.d.ts +4 -0
  33. package/dist/lib/services.d.ts.map +1 -0
  34. package/dist/lib/sweeper.d.ts +18 -0
  35. package/dist/lib/sweeper.d.ts.map +1 -0
  36. package/dist/lib/utils.d.ts +6 -0
  37. package/dist/lib/utils.d.ts.map +1 -0
  38. package/dist/lib/wallet.d.ts +9 -0
  39. package/dist/lib/wallet.d.ts.map +1 -0
  40. package/dist/types.d.ts +6 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/package.json +44 -0
  43. package/src/components/SweepApp.tsx +269 -0
  44. package/src/components/asset-preview.tsx +224 -0
  45. package/src/components/connect-wallet.tsx +59 -0
  46. package/src/components/opns-section.tsx +108 -0
  47. package/src/components/sweep-progress.tsx +69 -0
  48. package/src/components/tx-history.tsx +63 -0
  49. package/src/components/ui/badge.tsx +39 -0
  50. package/src/components/ui/button.tsx +52 -0
  51. package/src/components/ui/card.tsx +32 -0
  52. package/src/components/ui/input.tsx +20 -0
  53. package/src/components/ui/tabs.tsx +51 -0
  54. package/src/components/wif-input.tsx +332 -0
  55. package/src/index.ts +28 -0
  56. package/src/lib/legacy-send.ts +234 -0
  57. package/src/lib/scanner.ts +226 -0
  58. package/src/lib/services.ts +18 -0
  59. package/src/lib/sweeper.ts +93 -0
  60. package/src/lib/utils.ts +23 -0
  61. package/src/lib/wallet.ts +32 -0
  62. 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
+ };