@1sat/sweep-ui 0.0.85 → 0.0.86

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