@1sat/sweep-ui 0.0.18 → 0.0.20

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