@1sat/sweep-ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/components/SweepApp.d.ts +6 -0
  2. package/dist/components/SweepApp.d.ts.map +1 -0
  3. package/dist/components/asset-preview.d.ts +32 -0
  4. package/dist/components/asset-preview.d.ts.map +1 -0
  5. package/dist/components/connect-wallet.d.ts +8 -0
  6. package/dist/components/connect-wallet.d.ts.map +1 -0
  7. package/dist/components/opns-section.d.ts +13 -0
  8. package/dist/components/opns-section.d.ts.map +1 -0
  9. package/dist/components/sweep-progress.d.ts +9 -0
  10. package/dist/components/sweep-progress.d.ts.map +1 -0
  11. package/dist/components/tx-history.d.ts +14 -0
  12. package/dist/components/tx-history.d.ts.map +1 -0
  13. package/dist/components/ui/badge.d.ts +8 -0
  14. package/dist/components/ui/badge.d.ts.map +1 -0
  15. package/dist/components/ui/button.d.ts +9 -0
  16. package/dist/components/ui/button.d.ts.map +1 -0
  17. package/dist/components/ui/card.d.ts +10 -0
  18. package/dist/components/ui/card.d.ts.map +1 -0
  19. package/dist/components/ui/input.d.ts +4 -0
  20. package/dist/components/ui/input.d.ts.map +1 -0
  21. package/dist/components/ui/tabs.d.ts +14 -0
  22. package/dist/components/ui/tabs.d.ts.map +1 -0
  23. package/dist/components/wif-input.d.ts +9 -0
  24. package/dist/components/wif-input.d.ts.map +1 -0
  25. package/dist/index.d.ts +20 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +2556 -0
  28. package/dist/lib/legacy-send.d.ts +24 -0
  29. package/dist/lib/legacy-send.d.ts.map +1 -0
  30. package/dist/lib/scanner.d.ts +33 -0
  31. package/dist/lib/scanner.d.ts.map +1 -0
  32. package/dist/lib/services.d.ts +4 -0
  33. package/dist/lib/services.d.ts.map +1 -0
  34. package/dist/lib/sweeper.d.ts +18 -0
  35. package/dist/lib/sweeper.d.ts.map +1 -0
  36. package/dist/lib/utils.d.ts +6 -0
  37. package/dist/lib/utils.d.ts.map +1 -0
  38. package/dist/lib/wallet.d.ts +9 -0
  39. package/dist/lib/wallet.d.ts.map +1 -0
  40. package/dist/types.d.ts +6 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/package.json +44 -0
  43. package/src/components/SweepApp.tsx +269 -0
  44. package/src/components/asset-preview.tsx +224 -0
  45. package/src/components/connect-wallet.tsx +59 -0
  46. package/src/components/opns-section.tsx +108 -0
  47. package/src/components/sweep-progress.tsx +69 -0
  48. package/src/components/tx-history.tsx +63 -0
  49. package/src/components/ui/badge.tsx +39 -0
  50. package/src/components/ui/button.tsx +52 -0
  51. package/src/components/ui/card.tsx +32 -0
  52. package/src/components/ui/input.tsx +20 -0
  53. package/src/components/ui/tabs.tsx +51 -0
  54. package/src/components/wif-input.tsx +332 -0
  55. package/src/index.ts +28 -0
  56. package/src/lib/legacy-send.ts +234 -0
  57. package/src/lib/scanner.ts +226 -0
  58. package/src/lib/services.ts +18 -0
  59. package/src/lib/sweeper.ts +93 -0
  60. package/src/lib/utils.ts +23 -0
  61. package/src/lib/wallet.ts +32 -0
  62. package/src/types.ts +5 -0
@@ -0,0 +1,24 @@
1
+ import type { IndexedOutput } from "@1sat/types";
2
+ import type { LegacyKeys } from "../types";
3
+ export interface LegacySendResult {
4
+ txid: string;
5
+ rawtx: string;
6
+ }
7
+ export declare function legacySendBsv(params: {
8
+ funding: IndexedOutput[];
9
+ keys: LegacyKeys;
10
+ destination: string;
11
+ amount?: number;
12
+ }): Promise<LegacySendResult>;
13
+ export declare function legacySendOrdinals(params: {
14
+ ordinals: IndexedOutput[];
15
+ funding: IndexedOutput[];
16
+ keys: LegacyKeys;
17
+ destination: string;
18
+ }): Promise<LegacySendResult>;
19
+ export declare function legacyBurnOrdinals(params: {
20
+ ordinals: IndexedOutput[];
21
+ funding: IndexedOutput[];
22
+ keys: LegacyKeys;
23
+ }): Promise<LegacySendResult>;
24
+ //# sourceMappingURL=legacy-send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-send.d.ts","sourceRoot":"","sources":["../../src/lib/legacy-send.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACd;AAmCD,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC3C,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiD5B;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0D5B;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE,UAAU,CAAC;CACjB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA8D5B"}
@@ -0,0 +1,33 @@
1
+ import type { IndexedOutput } from "@1sat/types";
2
+ export interface EnrichedOrdinal extends IndexedOutput {
3
+ origin?: string;
4
+ contentType?: string;
5
+ name?: string;
6
+ contentUrl: string;
7
+ }
8
+ export interface TokenBalance {
9
+ tokenId: string;
10
+ symbol?: string;
11
+ icon: string;
12
+ decimals: number;
13
+ totalAmount: bigint;
14
+ outputs: IndexedOutput[];
15
+ isActive: boolean;
16
+ }
17
+ export interface ScannedAssets {
18
+ funding: IndexedOutput[];
19
+ ordinals: EnrichedOrdinal[];
20
+ opnsNames: EnrichedOrdinal[];
21
+ bsv21Tokens: TokenBalance[];
22
+ bsv20Tokens: IndexedOutput[];
23
+ locked: IndexedOutput[];
24
+ totalBsv: number;
25
+ }
26
+ export interface ScanProgress {
27
+ phase: string;
28
+ detail?: string;
29
+ }
30
+ export declare function deriveAddress(wif: string): string;
31
+ export declare function scanAddress(address: string, onProgress?: (p: ScanProgress) => void): Promise<ScannedAssets>;
32
+ export declare function scanAddresses(addresses: string[], onProgress?: (p: ScanProgress) => void): Promise<ScannedAssets>;
33
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAuID,wBAAsB,WAAW,CAChC,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GACpC,OAAO,CAAC,aAAa,CAAC,CA0BxB;AAED,wBAAsB,aAAa,CAClC,SAAS,EAAE,MAAM,EAAE,EACnB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GACpC,OAAO,CAAC,aAAa,CAAC,CAkBxB"}
@@ -0,0 +1,4 @@
1
+ import { OneSatServices } from "@1sat/client";
2
+ export declare function configureServices(baseUrl: string): void;
3
+ export declare function getServices(): OneSatServices;
4
+ //# sourceMappingURL=services.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/lib/services.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAK9C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGvD;AAED,wBAAgB,WAAW,IAAI,cAAc,CAO5C"}
@@ -0,0 +1,18 @@
1
+ import type { IndexedOutput } from "@1sat/types";
2
+ import type { WalletInterface } from "@bsv/sdk";
3
+ export interface SweepResult {
4
+ bsvTxid?: string;
5
+ ordinalTxids: string[];
6
+ bsv21Txids: string[];
7
+ errors: string[];
8
+ }
9
+ export declare function executeSweep(params: {
10
+ wallet: WalletInterface;
11
+ wif: string;
12
+ funding: IndexedOutput[];
13
+ ordinals: IndexedOutput[];
14
+ bsv21Tokens: IndexedOutput[];
15
+ amount?: number;
16
+ onProgress: (stage: string) => void;
17
+ }): Promise<SweepResult>;
18
+ //# sourceMappingURL=sweeper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sweeper.d.ts","sourceRoot":"","sources":["../../src/lib/sweeper.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAGhD,MAAM,WAAW,WAAW;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE;IAC1C,MAAM,EAAE,eAAe,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,GAAG,OAAO,CAAC,WAAW,CAAC,CAkEvB"}
@@ -0,0 +1,6 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ export declare function formatSats(sats: number): string;
4
+ export declare function formatTokenAmount(rawAmount: string, decimals: number): string;
5
+ export declare function truncate(s: string, len?: number): string;
6
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAQ,MAAM,MAAM,CAAC;AAG7C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM7E;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,SAAI,GAAG,MAAM,CAGnD"}
@@ -0,0 +1,9 @@
1
+ import { type ConnectWalletResult } from "@1sat/connect";
2
+ import type { WalletInterface } from "@bsv/sdk";
3
+ export declare function connectWallet(): Promise<ConnectWalletResult>;
4
+ export declare function getWallet(): WalletInterface | null;
5
+ export declare function getIdentityKey(): string | null;
6
+ export declare function getProvider(): string | null;
7
+ export declare function disconnectWallet(): void;
8
+ export declare function isConnected(): boolean;
9
+ //# sourceMappingURL=wallet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../../src/lib/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,wBAAsB,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAKlE;AAED,wBAAgB,SAAS,IAAI,eAAe,GAAG,IAAI,CAElD;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAE3C;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED,wBAAgB,WAAW,IAAI,OAAO,CAErC"}
@@ -0,0 +1,6 @@
1
+ export interface LegacyKeys {
2
+ payPk: string;
3
+ ordPk: string;
4
+ identityPk?: string;
5
+ }
6
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@1sat/sweep-ui",
3
+ "version": "0.0.1",
4
+ "description": "Sweep UI components for migrating legacy BSV assets",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist", "src"],
15
+ "scripts": {
16
+ "build": "bun run build.ts && tsc --emitDeclarationOnly",
17
+ "dev": "tsc --watch"
18
+ },
19
+ "keywords": ["1sat", "bsv", "ordinals", "sweep", "migration", "react"],
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "@1sat/actions": "0.0.64",
23
+ "@1sat/client": "0.0.17",
24
+ "@1sat/connect": "0.0.27",
25
+ "@1sat/types": "0.0.14",
26
+ "@1sat/utils": "0.0.12",
27
+ "bitcoin-backup": "^0.0.11",
28
+ "class-variance-authority": "^0.7.1",
29
+ "clsx": "^2.1.1",
30
+ "lucide-react": "^0.577.0",
31
+ "sonner": "^2.0.7",
32
+ "tailwind-merge": "^3.5.0"
33
+ },
34
+ "peerDependencies": {
35
+ "@bsv/sdk": "^2.0.6",
36
+ "react": "^18.0.0 || ^19.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@bsv/sdk": "^2.0.6",
40
+ "@types/react": "^19.0.0",
41
+ "react": "^19.0.0",
42
+ "typescript": "~5.7.0"
43
+ }
44
+ }
@@ -0,0 +1,269 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { Toaster, toast } from "sonner";
3
+ import { Badge } from "./ui/badge";
4
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs";
5
+ import { ConnectWallet } from "./connect-wallet";
6
+ import { WifInput } from "./wif-input";
7
+ import { FundingSection, OrdinalsSection, Bsv21Section, Bsv20Section, LockedSection } from "./asset-preview";
8
+ import { OpnsSection } from "./opns-section";
9
+ import { TxHistory, type TxRecord } from "./tx-history";
10
+ import { deriveAddress, scanAddresses, type ScannedAssets } from "../lib/scanner";
11
+ import { executeSweep } from "../lib/sweeper";
12
+ import { legacySendBsv, legacySendOrdinals, legacyBurnOrdinals } from "../lib/legacy-send";
13
+ import { getWallet } from "../lib/wallet";
14
+ import type { LegacyKeys } from "../types";
15
+
16
+ type TabId = "ordinals" | "opns" | "bsv21" | "bsv20" | "locks";
17
+
18
+ export interface SweepAppProps {
19
+ legacyKeys?: LegacyKeys;
20
+ }
21
+
22
+ export function SweepApp({ legacyKeys: initialKeys }: SweepAppProps) {
23
+ const [walletConnected, setWalletConnected] = useState(false);
24
+ const [scanning, setScanning] = useState(false);
25
+ const [scanProgress, setScanProgress] = useState("");
26
+ const [assets, setAssets] = useState<ScannedAssets | null>(null);
27
+ const [legacyKeys, setLegacyKeys] = useState<LegacyKeys | null>(null);
28
+ const [sweeping, setSweeping] = useState(false);
29
+ const [sweepProgress, setSweepProgress] = useState("");
30
+ const [txHistory, setTxHistory] = useState<TxRecord[]>([]);
31
+ const [selectedOrdinals, setSelectedOrdinals] = useState<Set<string>>(new Set());
32
+ const [selectedOpns, setSelectedOpns] = useState<Set<string>>(new Set());
33
+ const [sweepAmount, setSweepAmount] = useState<number | null>(null);
34
+ const [activeTab, setActiveTab] = useState<TabId>("ordinals");
35
+
36
+ const addTx = useCallback((label: string, txid: string, error?: string) => {
37
+ setTxHistory((prev) => [...prev, { label, txid, timestamp: new Date(), error }]);
38
+ }, []);
39
+
40
+ const tabs = useMemo(() => {
41
+ if (!assets) return [];
42
+ const t: { id: TabId; label: string; count: number }[] = [];
43
+ if (assets.ordinals.length > 0) t.push({ id: "ordinals", label: "Ordinals", count: assets.ordinals.length });
44
+ if (assets.opnsNames.length > 0) t.push({ id: "opns", label: "OpNS", count: assets.opnsNames.length });
45
+ if (assets.bsv21Tokens.length > 0) t.push({ id: "bsv21", label: "BSV-21", count: assets.bsv21Tokens.length });
46
+ if (assets.bsv20Tokens.length > 0) t.push({ id: "bsv20", label: "BSV-20", count: assets.bsv20Tokens.length });
47
+ if (assets.locked.length > 0) t.push({ id: "locks", label: "Locks", count: assets.locked.length });
48
+ return t;
49
+ }, [assets]);
50
+
51
+ const handleToggleOrdinal = useCallback((outpoint: string) => {
52
+ setSelectedOrdinals((prev) => { const next = new Set(prev); if (next.has(outpoint)) next.delete(outpoint); else next.add(outpoint); return next; });
53
+ }, []);
54
+ const handleSelectAllOrdinals = useCallback(() => { if (assets) setSelectedOrdinals(new Set(assets.ordinals.map((o) => o.outpoint))); }, [assets]);
55
+ const handleDeselectAllOrdinals = useCallback(() => setSelectedOrdinals(new Set()), []);
56
+
57
+ const handleToggleOpns = useCallback((outpoint: string) => {
58
+ setSelectedOpns((prev) => { const next = new Set(prev); if (next.has(outpoint)) next.delete(outpoint); else next.add(outpoint); return next; });
59
+ }, []);
60
+ const handleSelectAllOpns = useCallback(() => { if (assets) setSelectedOpns(new Set(assets.opnsNames.map((o) => o.outpoint))); }, [assets]);
61
+ const handleDeselectAllOpns = useCallback(() => setSelectedOpns(new Set()), []);
62
+
63
+ const refreshAssets = useCallback(async () => {
64
+ if (!legacyKeys) return;
65
+ const addresses = [...new Set([deriveAddress(legacyKeys.payPk), deriveAddress(legacyKeys.ordPk), ...(legacyKeys.identityPk ? [deriveAddress(legacyKeys.identityPk)] : [])])];
66
+ const result = await scanAddresses(addresses);
67
+ setAssets(result);
68
+ setSelectedOrdinals(new Set());
69
+ setSelectedOpns(new Set());
70
+ setSweepAmount(null);
71
+ }, [legacyKeys]);
72
+
73
+ const handleScan = useCallback(async (keys: LegacyKeys) => {
74
+ setScanning(true);
75
+ setAssets(null);
76
+ setSelectedOrdinals(new Set());
77
+ setSelectedOpns(new Set());
78
+ setSweepAmount(null);
79
+ setLegacyKeys(keys);
80
+
81
+ try {
82
+ const addresses = [...new Set([deriveAddress(keys.payPk), deriveAddress(keys.ordPk), ...(keys.identityPk ? [deriveAddress(keys.identityPk)] : [])])];
83
+ const result = await scanAddresses(addresses, (p) => setScanProgress(p.detail ?? p.phase));
84
+ setAssets(result);
85
+
86
+ 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;
87
+ if (total === 0) toast.info("No assets found at legacy addresses");
88
+
89
+ if (result.ordinals.length > 0) setActiveTab("ordinals");
90
+ else if (result.opnsNames.length > 0) setActiveTab("opns");
91
+ else if (result.bsv21Tokens.length > 0) setActiveTab("bsv21");
92
+ else if (result.bsv20Tokens.length > 0) setActiveTab("bsv20");
93
+ else if (result.locked.length > 0) setActiveTab("locks");
94
+ } catch (e) {
95
+ toast.error(e instanceof Error ? e.message : "Scan failed");
96
+ } finally {
97
+ setScanning(false);
98
+ }
99
+ }, []);
100
+
101
+ // Auto-scan when keys are provided via props
102
+ useEffect(() => {
103
+ if (initialKeys) handleScan(initialKeys);
104
+ }, [initialKeys, handleScan]);
105
+
106
+ const runOperation = useCallback(async (label: string, op: () => Promise<string>) => {
107
+ setSweeping(true);
108
+ setSweepProgress(label + "...");
109
+ try {
110
+ const txid = await op();
111
+ addTx(label, txid);
112
+ toast.success(label);
113
+ await refreshAssets();
114
+ } catch (e) {
115
+ const msg = e instanceof Error ? e.message : "Operation failed";
116
+ addTx(label, "", msg);
117
+ toast.error(msg);
118
+ } finally {
119
+ setSweeping(false);
120
+ }
121
+ }, [addTx, refreshAssets]);
122
+
123
+ const getSelectedFunding = useCallback(() => {
124
+ if (!assets) return [];
125
+ if (sweepAmount === null) return assets.funding;
126
+ const selected: typeof assets.funding = [];
127
+ let accumulated = 0;
128
+ for (const utxo of assets.funding) {
129
+ selected.push(utxo);
130
+ accumulated += utxo.satoshis ?? 0;
131
+ if (accumulated >= sweepAmount) break;
132
+ }
133
+ return selected;
134
+ }, [assets, sweepAmount]);
135
+
136
+ const handleSweepBsv = useCallback(async () => {
137
+ const wallet = getWallet();
138
+ if (!wallet || !legacyKeys || !assets) return;
139
+ await runOperation("Sweep BSV", async () => {
140
+ const result = await executeSweep({ wallet, wif: legacyKeys.payPk, funding: getSelectedFunding(), ordinals: [], bsv21Tokens: [], amount: sweepAmount ?? undefined, onProgress: setSweepProgress });
141
+ if (result.errors.length > 0) throw new Error(result.errors[0]);
142
+ return result.bsvTxid ?? "";
143
+ });
144
+ }, [legacyKeys, assets, sweepAmount, getSelectedFunding, runOperation]);
145
+
146
+ const handleSendBsv = useCallback(async (destination: string) => {
147
+ if (!legacyKeys || !assets) return;
148
+ await runOperation("Send BSV", async () => {
149
+ const result = await legacySendBsv({ funding: getSelectedFunding(), keys: legacyKeys, destination, amount: sweepAmount ?? undefined });
150
+ return result.txid;
151
+ });
152
+ }, [legacyKeys, assets, sweepAmount, getSelectedFunding, runOperation]);
153
+
154
+ const handleSweepOrdinals = useCallback(async () => {
155
+ const wallet = getWallet();
156
+ if (!wallet || !legacyKeys || !assets) return;
157
+ const selected = assets.ordinals.filter((o) => selectedOrdinals.has(o.outpoint));
158
+ if (selected.length === 0) return;
159
+ await runOperation(`Sweep ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
160
+ const result = await executeSweep({ wallet, wif: legacyKeys.payPk, funding: [], ordinals: selected, bsv21Tokens: [], onProgress: setSweepProgress });
161
+ if (result.errors.length > 0) throw new Error(result.errors[0]);
162
+ return result.ordinalTxids[0] ?? "";
163
+ });
164
+ }, [legacyKeys, assets, selectedOrdinals, runOperation]);
165
+
166
+ const handleSendOrdinals = useCallback(async (destination: string) => {
167
+ if (!legacyKeys || !assets) return;
168
+ const selected = assets.ordinals.filter((o) => selectedOrdinals.has(o.outpoint));
169
+ if (selected.length === 0) return;
170
+ await runOperation(`Send ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
171
+ const result = await legacySendOrdinals({ ordinals: selected, funding: assets.funding, keys: legacyKeys, destination });
172
+ return result.txid;
173
+ });
174
+ }, [legacyKeys, assets, selectedOrdinals, runOperation]);
175
+
176
+ const handleBurnOrdinals = useCallback(async () => {
177
+ if (!legacyKeys || !assets) return;
178
+ const selected = assets.ordinals.filter((o) => selectedOrdinals.has(o.outpoint));
179
+ if (selected.length === 0) return;
180
+ await runOperation(`Burn ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
181
+ const result = await legacyBurnOrdinals({ ordinals: selected, funding: assets.funding, keys: legacyKeys });
182
+ return result.txid;
183
+ });
184
+ }, [legacyKeys, assets, selectedOrdinals, runOperation]);
185
+
186
+ const handleSweepOpns = useCallback(async () => {
187
+ const wallet = getWallet();
188
+ if (!wallet || !legacyKeys || !assets) return;
189
+ const selected = assets.opnsNames.filter((o) => selectedOpns.has(o.outpoint));
190
+ if (selected.length === 0) return;
191
+ await runOperation(`Sweep ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
192
+ const result = await executeSweep({ wallet, wif: legacyKeys.payPk, funding: [], ordinals: selected, bsv21Tokens: [], onProgress: setSweepProgress });
193
+ if (result.errors.length > 0) throw new Error(result.errors[0]);
194
+ return result.ordinalTxids[0] ?? "";
195
+ });
196
+ }, [legacyKeys, assets, selectedOpns, runOperation]);
197
+
198
+ const handleSendOpns = useCallback(async (destination: string) => {
199
+ if (!legacyKeys || !assets) return;
200
+ const selected = assets.opnsNames.filter((o) => selectedOpns.has(o.outpoint));
201
+ if (selected.length === 0) return;
202
+ await runOperation(`Send ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
203
+ const result = await legacySendOrdinals({ ordinals: selected, funding: assets.funding, keys: legacyKeys, destination });
204
+ return result.txid;
205
+ });
206
+ }, [legacyKeys, assets, selectedOpns, runOperation]);
207
+
208
+ const handleBurnOpns = useCallback(async () => {
209
+ if (!legacyKeys || !assets) return;
210
+ const selected = assets.opnsNames.filter((o) => selectedOpns.has(o.outpoint));
211
+ if (selected.length === 0) return;
212
+ await runOperation(`Burn ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
213
+ const result = await legacyBurnOrdinals({ ordinals: selected, funding: assets.funding, keys: legacyKeys });
214
+ return result.txid;
215
+ });
216
+ }, [legacyKeys, assets, selectedOpns, runOperation]);
217
+
218
+ return (
219
+ <div className="min-h-screen bg-background text-foreground">
220
+ <Toaster position="top-right" />
221
+ <div className="mx-auto max-w-lg p-4 space-y-4 py-12">
222
+ <div className="text-center space-y-2 mb-4">
223
+ <h1 className="text-3xl font-bold tracking-tight">1Sat Sweep</h1>
224
+ <p className="text-sm text-muted-foreground">Transfer or sweep legacy assets</p>
225
+ </div>
226
+
227
+ <ConnectWallet onConnected={() => setWalletConnected(true)} onDisconnected={() => setWalletConnected(false)} connected={walletConnected} />
228
+
229
+ {!initialKeys && (
230
+ <WifInput onScan={handleScan} scanning={scanning} disabled={sweeping} />
231
+ )}
232
+
233
+ {scanning && (
234
+ <p className="text-sm text-center text-muted-foreground animate-pulse">{scanProgress}</p>
235
+ )}
236
+
237
+ {assets && !sweeping && (
238
+ <div className="space-y-3">
239
+ <FundingSection funding={assets.funding} totalBsv={assets.totalBsv} sweepAmount={sweepAmount} onSweepAmountChange={setSweepAmount} onSweep={handleSweepBsv} onSend={handleSendBsv} walletConnected={walletConnected} />
240
+
241
+ {tabs.length > 0 && (
242
+ <Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as TabId)}>
243
+ <TabsList className="w-full">
244
+ {tabs.map((tab) => (
245
+ <TabsTrigger key={tab.id} value={tab.id} className="flex-1 gap-1.5">
246
+ {tab.label}
247
+ <Badge variant="secondary" className="text-[10px] px-1.5 py-0">{tab.count}</Badge>
248
+ </TabsTrigger>
249
+ ))}
250
+ </TabsList>
251
+ <TabsContent value="ordinals">
252
+ <OrdinalsSection ordinals={assets.ordinals} selectedOrdinals={selectedOrdinals} onToggle={handleToggleOrdinal} onSelectAll={handleSelectAllOrdinals} onDeselectAll={handleDeselectAllOrdinals} onSweep={handleSweepOrdinals} onSend={handleSendOrdinals} onBurn={handleBurnOrdinals} walletConnected={walletConnected} />
253
+ </TabsContent>
254
+ <TabsContent value="opns">
255
+ <OpnsSection opnsNames={assets.opnsNames} selectedOpns={selectedOpns} onToggle={handleToggleOpns} onSelectAll={handleSelectAllOpns} onDeselectAll={handleDeselectAllOpns} onSweep={handleSweepOpns} onSend={handleSendOpns} onBurn={handleBurnOpns} walletConnected={walletConnected} />
256
+ </TabsContent>
257
+ <TabsContent value="bsv21"><Bsv21Section tokens={assets.bsv21Tokens} /></TabsContent>
258
+ <TabsContent value="bsv20"><Bsv20Section tokens={assets.bsv20Tokens} /></TabsContent>
259
+ <TabsContent value="locks"><LockedSection locked={assets.locked} /></TabsContent>
260
+ </Tabs>
261
+ )}
262
+ </div>
263
+ )}
264
+
265
+ <TxHistory sweeping={sweeping} progress={sweepProgress} history={txHistory} />
266
+ </div>
267
+ </div>
268
+ );
269
+ }