{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wallet-overview",
  "title": "Wallet Overview",
  "author": "Satchmo",
  "description": "Wallet dashboard card displaying BSV balance with privacy toggle, payment and ordinal addresses with copy buttons, identity key, and send/receive action buttons. Supports @1sat/react WalletProvider (web), direct wallet source (desktop), or fully prop-driven UI. API URL is overrideable.",
  "dependencies": [
    "@1sat/react",
    "@1sat/connect",
    "lucide-react"
  ],
  "registryDependencies": [
    "card",
    "button",
    "badge",
    "skeleton",
    "separator",
    "tooltip"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/wallet-overview/index.tsx",
      "content": "\"use client\"\n\nimport { WalletOverviewUI } from \"./wallet-overview-ui\"\nimport {\n  useWalletOverview,\n  useWalletOverviewDirect,\n  type UseWalletOverviewOptions,\n  type UseWalletOverviewReturn,\n  type WalletBalance,\n  type WalletSource,\n} from \"./use-wallet-overview\"\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport {\n  WalletOverviewUI,\n  type WalletOverviewUIProps,\n} from \"./wallet-overview-ui\"\nexport {\n  useWalletOverview,\n  useWalletOverviewDirect,\n  type UseWalletOverviewOptions,\n  type UseWalletOverviewReturn,\n  type WalletBalance,\n  type WalletSource,\n} from \"./use-wallet-overview\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface WalletOverviewBaseProps {\n  /** Callback fired when the Send button is clicked */\n  onSend?: () => void\n  /** Callback fired when the Receive button is clicked */\n  onReceive?: () => void\n  /** Additional CSS classes */\n  className?: string\n  /**\n   * Base URL for the 1sat-stack balance API.\n   * Only used when fetching balance via HTTP.\n   * @default \"https://api.1sat.app\"\n   */\n  apiUrl?: string\n}\n\n/**\n * Web mode: rendered inside `WalletProvider` from `@1sat/react`.\n * No `wallet` prop needed — addresses and identity are read from context.\n */\ninterface WalletOverviewProviderProps extends WalletOverviewBaseProps {\n  wallet?: undefined\n}\n\n/**\n * Direct mode: provide wallet data explicitly, no `@1sat/react` required.\n * Used by desktop apps (Electrobun, Tauri, etc.) or any context that\n * manages wallet state independently.\n */\ninterface WalletOverviewDirectProps extends WalletOverviewBaseProps {\n  /**\n   * External wallet data source. When provided, bypasses `@1sat/react`\n   * context entirely. See `WalletSource` for the shape.\n   */\n  wallet: WalletSource\n}\n\nexport type WalletOverviewProps =\n  | WalletOverviewProviderProps\n  | WalletOverviewDirectProps\n\n// ---------------------------------------------------------------------------\n// Composed components\n// ---------------------------------------------------------------------------\n\n/**\n * Internal: renders with `useWalletOverview` (requires `WalletProvider`).\n */\nfunction WalletOverviewWithProvider({\n  onSend,\n  onReceive,\n  className,\n  apiUrl,\n}: WalletOverviewProviderProps) {\n  const {\n    balance,\n    paymentAddress,\n    ordinalAddress,\n    identityKey,\n    isLoading,\n    error,\n    refetch,\n  } = useWalletOverview({ apiUrl })\n\n  return (\n    <WalletOverviewUI\n      balance={balance}\n      paymentAddress={paymentAddress}\n      ordinalAddress={ordinalAddress}\n      identityKey={identityKey}\n      isLoading={isLoading}\n      error={error}\n      onSend={onSend}\n      onReceive={onReceive}\n      onRefresh={refetch}\n      className={className}\n    />\n  )\n}\n\n/**\n * Internal: renders with `useWalletOverviewDirect` (no provider needed).\n */\nfunction WalletOverviewWithDirect({\n  wallet,\n  onSend,\n  onReceive,\n  className,\n  apiUrl,\n}: WalletOverviewDirectProps) {\n  const {\n    balance,\n    paymentAddress,\n    ordinalAddress,\n    identityKey,\n    isLoading,\n    error,\n    refetch,\n  } = useWalletOverviewDirect(wallet, { apiUrl })\n\n  return (\n    <WalletOverviewUI\n      balance={balance}\n      paymentAddress={paymentAddress}\n      ordinalAddress={ordinalAddress}\n      identityKey={identityKey}\n      isLoading={isLoading}\n      error={error}\n      onSend={onSend}\n      onReceive={onReceive}\n      onRefresh={refetch}\n      className={className}\n    />\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Public component\n// ---------------------------------------------------------------------------\n\n/**\n * Wallet overview block displaying BSV balance, addresses, and action buttons.\n *\n * Supports two modes:\n *\n * 1. **Web (default):** Composes `useWalletOverview` with `WalletOverviewUI`.\n *    Must be rendered inside `WalletProvider` from `@1sat/react`.\n *\n * 2. **Direct:** Pass a `wallet` prop with addresses and an optional\n *    `getBalance` function. No `@1sat/react` provider required. Ideal for\n *    desktop apps or custom wallet integrations.\n *\n * For full control, use `WalletOverviewUI` directly with props.\n *\n * @example Web mode (inside WalletProvider)\n * ```tsx\n * import { WalletProvider } from \"@1sat/react\"\n * import { WalletOverview } from \"@/components/blocks/wallet-overview\"\n *\n * function App() {\n *   return (\n *     <WalletProvider>\n *       <WalletOverview\n *         onSend={() => console.log(\"Open send dialog\")}\n *         onReceive={() => console.log(\"Open receive dialog\")}\n *       />\n *     </WalletProvider>\n *   )\n * }\n * ```\n *\n * @example Web mode with custom API\n * ```tsx\n * <WalletProvider>\n *   <WalletOverview apiUrl=\"https://my-api.example.com\" />\n * </WalletProvider>\n * ```\n *\n * @example Direct mode (desktop / custom wallet)\n * ```tsx\n * <WalletOverview\n *   wallet={{\n *     paymentAddress: \"1A1zP1...\",\n *     ordinalAddress: \"1BvBM...\",\n *     identityKey: \"02abc...\",\n *     getBalance: async () => ({\n *       confirmed: 50000,\n *       unconfirmed: 0,\n *       total: 50000,\n *     }),\n *   }}\n *   onSend={() => rpc.request.openSendDialog()}\n *   onReceive={() => rpc.request.openReceiveDialog()}\n * />\n * ```\n */\nexport function WalletOverview(props: WalletOverviewProps) {\n  if (props.wallet) {\n    return <WalletOverviewWithDirect {...props} wallet={props.wallet} />\n  }\n  return <WalletOverviewWithProvider {...props} />\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/wallet-overview/index.tsx"
    },
    {
      "path": "registry/new-york/blocks/wallet-overview/wallet-overview-ui.tsx",
      "content": "\"use client\"\n\nimport { useCallback, useState } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport { Button } from \"@/components/ui/button\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Separator } from \"@/components/ui/separator\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport {\n  Wallet,\n  ArrowUpRight,\n  ArrowDownLeft,\n  Copy,\n  Check,\n  Eye,\n  EyeOff,\n  RefreshCw,\n  AlertCircle,\n  Fingerprint,\n} from \"lucide-react\"\nimport type { WalletBalance } from \"./use-wallet-overview\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface WalletOverviewUIProps {\n  /** Balance breakdown in satoshis */\n  balance: WalletBalance | null\n  /** BSV payment address */\n  paymentAddress: string | null\n  /** Ordinal receiving address */\n  ordinalAddress: string | null\n  /** Identity public key */\n  identityKey: string | null\n  /** Whether balance is loading */\n  isLoading: boolean\n  /** Error from balance fetch */\n  error: Error | null\n  /** Callback for the Send action button */\n  onSend?: () => void\n  /** Callback for the Receive action button */\n  onReceive?: () => void\n  /** Manually refetch the balance */\n  onRefresh?: () => void\n  /** Additional CSS classes */\n  className?: string\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Format satoshis with locale grouping: 1,234,567 */\nfunction formatSats(sats: number): string {\n  return sats.toLocaleString()\n}\n\n/** Convert satoshis to BSV string: 0.01234567 */\nfunction satsToBsv(sats: number): string {\n  return (sats / 100_000_000).toFixed(8)\n}\n\n/** Truncate a string for display: \"abc123...xyz9\" */\nfunction truncate(value: string, startLen = 8, endLen = 6): string {\n  if (value.length <= startLen + endLen + 3) return value\n  return `${value.slice(0, startLen)}...${value.slice(-endLen)}`\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\ninterface CopyFieldProps {\n  label: string\n  value: string\n  icon: React.ReactNode\n}\n\nfunction CopyField({ label, value, icon }: CopyFieldProps) {\n  const [copied, setCopied] = useState(false)\n\n  const handleCopy = useCallback(() => {\n    if (typeof window === \"undefined\") return\n    void navigator.clipboard.writeText(value).then(() => {\n      setCopied(true)\n      setTimeout(() => setCopied(false), 2000)\n    })\n  }, [value])\n\n  return (\n    <div className=\"flex items-center gap-3\">\n      <div className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-md bg-muted\">\n        {icon}\n      </div>\n      <div className=\"min-w-0 flex-1\">\n        <p className=\"text-xs text-muted-foreground\">{label}</p>\n        <p className=\"truncate font-mono text-sm text-foreground\">\n          {truncate(value)}\n        </p>\n      </div>\n      <TooltipProvider delayDuration={200}>\n        <Tooltip>\n          <TooltipTrigger asChild>\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"size-8 flex-shrink-0\"\n              onClick={handleCopy}\n              aria-label={copied ? \"Copied\" : `Copy ${label}`}\n            >\n              {copied ? (\n                <Check className=\"size-3.5 text-primary\" aria-hidden=\"true\" />\n              ) : (\n                <Copy className=\"size-3.5\" aria-hidden=\"true\" />\n              )}\n            </Button>\n          </TooltipTrigger>\n          <TooltipContent>\n            <p>{copied ? \"Copied!\" : \"Copy to clipboard\"}</p>\n          </TooltipContent>\n        </Tooltip>\n      </TooltipProvider>\n    </div>\n  )\n}\n\nfunction BalanceSkeleton() {\n  return (\n    <div className=\"flex flex-col gap-1.5\">\n      <Skeleton className=\"h-8 w-36\" />\n      <Skeleton className=\"h-4 w-24\" />\n    </div>\n  )\n}\n\nfunction AddressSkeleton() {\n  return (\n    <div className=\"flex items-center gap-3\">\n      <Skeleton className=\"size-8 rounded-md\" />\n      <div className=\"flex flex-1 flex-col gap-1.5\">\n        <Skeleton className=\"h-3 w-16\" />\n        <Skeleton className=\"h-4 w-40\" />\n      </div>\n      <Skeleton className=\"size-8 rounded-md\" />\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Main UI\n// ---------------------------------------------------------------------------\n\n/**\n * Pure presentation component for the wallet overview block.\n *\n * Displays BSV balance, payment address, ordinal address, identity key,\n * and action buttons. All data and callbacks are provided via props.\n */\nexport function WalletOverviewUI({\n  balance,\n  paymentAddress,\n  ordinalAddress,\n  identityKey,\n  isLoading,\n  error,\n  onSend,\n  onReceive,\n  onRefresh,\n  className,\n}: WalletOverviewUIProps) {\n  const [balanceHidden, setBalanceHidden] = useState(false)\n\n  const toggleVisibility = useCallback(() => {\n    setBalanceHidden((prev) => !prev)\n  }, [])\n\n  // Not connected state\n  if (!paymentAddress && !isLoading) {\n    return (\n      <Card className={cn(\"w-full\", className)}>\n        <CardContent className=\"flex flex-col items-center gap-3 py-10\">\n          <div className=\"flex size-12 items-center justify-center rounded-full bg-muted\">\n            <Wallet\n              className=\"size-6 text-muted-foreground\"\n              aria-hidden=\"true\"\n            />\n          </div>\n          <div className=\"text-center\">\n            <p className=\"text-sm font-medium text-foreground\">\n              Wallet not connected\n            </p>\n            <p className=\"text-sm text-muted-foreground\">\n              Connect a wallet to view your balance\n            </p>\n          </div>\n        </CardContent>\n      </Card>\n    )\n  }\n\n  return (\n    <Card className={cn(\"w-full\", className)}>\n      <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n        <CardTitle className=\"text-base font-semibold\">Wallet</CardTitle>\n        <div className=\"flex items-center gap-1\">\n          <TooltipProvider delayDuration={200}>\n            <Tooltip>\n              <TooltipTrigger asChild>\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  className=\"size-8\"\n                  onClick={toggleVisibility}\n                  aria-label={\n                    balanceHidden ? \"Show balance\" : \"Hide balance\"\n                  }\n                >\n                  {balanceHidden ? (\n                    <EyeOff className=\"size-4\" aria-hidden=\"true\" />\n                  ) : (\n                    <Eye className=\"size-4\" aria-hidden=\"true\" />\n                  )}\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent>\n                <p>{balanceHidden ? \"Show balance\" : \"Hide balance\"}</p>\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n          {onRefresh && (\n            <TooltipProvider delayDuration={200}>\n              <Tooltip>\n                <TooltipTrigger asChild>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    className=\"size-8\"\n                    onClick={onRefresh}\n                    disabled={isLoading}\n                    aria-label=\"Refresh balance\"\n                  >\n                    <RefreshCw\n                      className={cn(\n                        \"size-4\",\n                        isLoading && \"animate-spin\"\n                      )}\n                      aria-hidden=\"true\"\n                    />\n                  </Button>\n                </TooltipTrigger>\n                <TooltipContent>\n                  <p>Refresh balance</p>\n                </TooltipContent>\n              </Tooltip>\n            </TooltipProvider>\n          )}\n        </div>\n      </CardHeader>\n\n      <CardContent className=\"flex flex-col gap-4\">\n        {/* Balance display */}\n        {error ? (\n          <div className=\"flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2\">\n            <AlertCircle\n              className=\"size-4 flex-shrink-0 text-destructive\"\n              aria-hidden=\"true\"\n            />\n            <p className=\"text-sm text-destructive\">{error.message}</p>\n          </div>\n        ) : isLoading && !balance ? (\n          <BalanceSkeleton />\n        ) : balance ? (\n          <div className=\"flex flex-col gap-1\">\n            <div className=\"flex items-baseline gap-2\">\n              <span className=\"text-2xl font-bold tabular-nums text-foreground\">\n                {balanceHidden\n                  ? \"\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\"\n                  : formatSats(balance.total)}\n              </span>\n              <span className=\"text-sm text-muted-foreground\">sats</span>\n            </div>\n            <span className=\"text-sm tabular-nums text-muted-foreground\">\n              {balanceHidden\n                ? \"\\u2022\\u2022\\u2022\\u2022 BSV\"\n                : `${satsToBsv(balance.total)} BSV`}\n            </span>\n            {!balanceHidden && balance.unconfirmed !== 0 && (\n              <Badge\n                variant=\"secondary\"\n                className=\"mt-1 w-fit text-xs\"\n              >\n                {balance.unconfirmed > 0 ? \"+\" : \"\"}\n                {formatSats(balance.unconfirmed)} unconfirmed\n              </Badge>\n            )}\n          </div>\n        ) : null}\n\n        {/* Action buttons */}\n        <div className=\"flex gap-2\">\n          <Button\n            variant=\"default\"\n            size=\"sm\"\n            className=\"flex-1 gap-1.5\"\n            onClick={onSend}\n            disabled={!paymentAddress}\n          >\n            <ArrowUpRight className=\"size-4\" aria-hidden=\"true\" />\n            Send\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            className=\"flex-1 gap-1.5\"\n            onClick={onReceive}\n            disabled={!paymentAddress}\n          >\n            <ArrowDownLeft className=\"size-4\" aria-hidden=\"true\" />\n            Receive\n          </Button>\n        </div>\n\n        <Separator />\n\n        {/* Addresses */}\n        <div className=\"flex flex-col gap-3\">\n          {isLoading && !paymentAddress ? (\n            <>\n              <AddressSkeleton />\n              <AddressSkeleton />\n              <AddressSkeleton />\n            </>\n          ) : (\n            <>\n              {paymentAddress && (\n                <CopyField\n                  label=\"Payment Address\"\n                  value={paymentAddress}\n                  icon={\n                    <Wallet\n                      className=\"size-4 text-muted-foreground\"\n                      aria-hidden=\"true\"\n                    />\n                  }\n                />\n              )}\n              {ordinalAddress && (\n                <CopyField\n                  label=\"Ordinal Address\"\n                  value={ordinalAddress}\n                  icon={\n                    <svg\n                      className=\"size-4 text-muted-foreground\"\n                      viewBox=\"0 0 24 24\"\n                      fill=\"none\"\n                      stroke=\"currentColor\"\n                      strokeWidth=\"2\"\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                      aria-hidden=\"true\"\n                    >\n                      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n                      <path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\" />\n                      <path d=\"M2 12h20\" />\n                    </svg>\n                  }\n                />\n              )}\n              {identityKey && (\n                <CopyField\n                  label=\"Identity Key\"\n                  value={identityKey}\n                  icon={\n                    <Fingerprint\n                      className=\"size-4 text-muted-foreground\"\n                      aria-hidden=\"true\"\n                    />\n                  }\n                />\n              )}\n            </>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  )\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/wallet-overview/wallet-overview-ui.tsx"
    },
    {
      "path": "registry/new-york/blocks/wallet-overview/use-wallet-overview.ts",
      "content": "import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { useWallet } from \"@1sat/react\"\nimport { loadConnection } from \"@1sat/connect\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Balance breakdown returned by the 1sat-stack API */\nexport interface WalletBalance {\n  /** Confirmed satoshis */\n  confirmed: number\n  /** Unconfirmed satoshis */\n  unconfirmed: number\n  /** Total satoshis (confirmed + unconfirmed) */\n  total: number\n}\n\n/**\n * Wallet data source that decouples from `@1sat/react` `WalletProvider`.\n * Pass this to `useWalletOverviewDirect` or `WalletOverview` to drive the\n * block without any browser wallet provider dependency.\n *\n * Desktop apps (Electrobun, Tauri, etc.) supply addresses and an optional\n * balance fetcher. Alternatively, pass data directly to `WalletOverviewUI`\n * as props to skip hooks entirely.\n */\nexport interface WalletSource {\n  /** BSV payment address */\n  paymentAddress: string\n  /** Ordinal receiving address */\n  ordinalAddress: string\n  /** Identity public key */\n  identityKey: string | null\n  /**\n   * Optional function that resolves the wallet balance.\n   * When provided, the hook calls this instead of fetching from the API.\n   * When omitted, the hook fetches from `apiUrl` using `paymentAddress`.\n   */\n  getBalance?: () => Promise<WalletBalance>\n}\n\nexport interface UseWalletOverviewOptions {\n  /**\n   * Base URL for the 1sat-stack balance API.\n   * Only used when fetching balance via HTTP (i.e. no `getBalance`\n   * function on the wallet source).\n   * @default \"https://api.1sat.app\"\n   */\n  apiUrl?: string\n}\n\nexport interface UseWalletOverviewReturn {\n  /** Balance breakdown in satoshis, null while loading or disconnected */\n  balance: WalletBalance | null\n  /** BSV payment address, null when disconnected */\n  paymentAddress: string | null\n  /** Ordinal receiving address, null when disconnected */\n  ordinalAddress: string | null\n  /** Identity public key, null when disconnected */\n  identityKey: string | null\n  /** Whether balance is being fetched */\n  isLoading: boolean\n  /** Error from the last balance fetch */\n  error: Error | null\n  /** Manually refetch the balance */\n  refetch: () => void\n}\n\n/** Shape returned from the 1sat-stack balance endpoint */\ninterface BalanceApiResponse {\n  confirmed: number\n  unconfirmed: number\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_API_URL = \"https://api.1sat.app\"\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function fetchBalance(\n  address: string,\n  signal: AbortSignal,\n  apiUrl: string\n): Promise<WalletBalance> {\n  const res = await fetch(\n    `${apiUrl}/1sat/owner/${address}/balance`,\n    { signal }\n  )\n  if (!res.ok) {\n    throw new Error(`Balance fetch failed: HTTP ${res.status}`)\n  }\n  const data: BalanceApiResponse = await res.json()\n  return {\n    confirmed: data.confirmed,\n    unconfirmed: data.unconfirmed,\n    total: data.confirmed + data.unconfirmed,\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Hook: useWalletOverviewDirect (no @1sat/react dependency)\n// ---------------------------------------------------------------------------\n\n/**\n * Manages balance fetching for a wallet whose addresses are known upfront.\n * Does **not** depend on `@1sat/react` or `@1sat/connect` — suitable for\n * desktop apps, custom wallet integrations, or any context where wallet\n * data is provided externally.\n *\n * @example Desktop app with custom balance resolver\n * ```ts\n * const overview = useWalletOverviewDirect({\n *   paymentAddress: \"1A1zP1...\",\n *   ordinalAddress: \"1BvBM...\",\n *   identityKey: \"02abc...\",\n *   getBalance: async () => ({\n *     confirmed: 50000,\n *     unconfirmed: 0,\n *     total: 50000,\n *   }),\n * })\n * ```\n *\n * @example Desktop app fetching from a custom API\n * ```ts\n * const overview = useWalletOverviewDirect(\n *   { paymentAddress: \"1A1zP1...\", ordinalAddress: \"1BvBM...\", identityKey: null },\n *   { apiUrl: \"https://my-api.example.com\" }\n * )\n * ```\n */\nexport function useWalletOverviewDirect(\n  wallet: WalletSource,\n  options?: UseWalletOverviewOptions\n): UseWalletOverviewReturn {\n  const apiUrl = options?.apiUrl ?? DEFAULT_API_URL\n  const [balance, setBalance] = useState<WalletBalance | null>(null)\n  const [isLoading, setIsLoading] = useState(false)\n  const [error, setError] = useState<Error | null>(null)\n  const abortRef = useRef<AbortController | null>(null)\n\n  const doFetch = useCallback(async () => {\n    abortRef.current?.abort()\n    const controller = new AbortController()\n    abortRef.current = controller\n\n    setIsLoading(true)\n    setError(null)\n\n    try {\n      const result = wallet.getBalance\n        ? await wallet.getBalance()\n        : await fetchBalance(wallet.paymentAddress, controller.signal, apiUrl)\n      if (!controller.signal.aborted) {\n        setBalance(result)\n      }\n    } catch (err) {\n      if (err instanceof DOMException && err.name === \"AbortError\") return\n      const fetchError =\n        err instanceof Error ? err : new Error(\"Failed to fetch balance\")\n      if (!controller.signal.aborted) {\n        setError(fetchError)\n        setBalance(null)\n      }\n    } finally {\n      if (!controller.signal.aborted) {\n        setIsLoading(false)\n      }\n    }\n  }, [wallet.paymentAddress, wallet.getBalance, apiUrl])\n\n  useEffect(() => {\n    void doFetch()\n    return () => {\n      abortRef.current?.abort()\n    }\n  }, [doFetch])\n\n  return {\n    balance,\n    paymentAddress: wallet.paymentAddress,\n    ordinalAddress: wallet.ordinalAddress,\n    identityKey: wallet.identityKey,\n    isLoading,\n    error,\n    refetch: doFetch,\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Hook: useWalletOverview (@1sat/react provider — original web behavior)\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches wallet addresses from `@1sat/connect` stored connection and\n * balance from the 1sat-stack API. Must be rendered inside `WalletProvider`\n * from `@1sat/react`.\n *\n * For desktop or custom wallet integrations that don't use `@1sat/react`,\n * use `useWalletOverviewDirect` instead, or pass data directly to\n * `WalletOverviewUI` as props.\n *\n * @example Default API\n * ```ts\n * const { balance, paymentAddress, ordinalAddress, identityKey, isLoading } =\n *   useWalletOverview()\n * ```\n *\n * @example Custom API URL\n * ```ts\n * const overview = useWalletOverview({ apiUrl: \"https://my-api.example.com\" })\n * ```\n */\nexport function useWalletOverview(\n  options?: UseWalletOverviewOptions\n): UseWalletOverviewReturn {\n  const apiUrl = options?.apiUrl ?? DEFAULT_API_URL\n  const { status, identityKey } = useWallet()\n  const [balance, setBalance] = useState<WalletBalance | null>(null)\n  const [paymentAddress, setPaymentAddress] = useState<string | null>(null)\n  const [ordinalAddress, setOrdinalAddress] = useState<string | null>(null)\n  const [isLoading, setIsLoading] = useState(false)\n  const [error, setError] = useState<Error | null>(null)\n  const abortRef = useRef<AbortController | null>(null)\n\n  // Resolve addresses from the stored connection when connected\n  useEffect(() => {\n    if (status === \"connected\") {\n      const stored = loadConnection()\n      setPaymentAddress(stored?.paymentAddress ?? null)\n      setOrdinalAddress(stored?.ordinalAddress ?? null)\n    } else {\n      setPaymentAddress(null)\n      setOrdinalAddress(null)\n      setBalance(null)\n      setError(null)\n    }\n  }, [status])\n\n  const doFetch = useCallback(async () => {\n    if (!paymentAddress) return\n\n    abortRef.current?.abort()\n    const controller = new AbortController()\n    abortRef.current = controller\n\n    setIsLoading(true)\n    setError(null)\n\n    try {\n      const result = await fetchBalance(\n        paymentAddress,\n        controller.signal,\n        apiUrl\n      )\n      setBalance(result)\n    } catch (err) {\n      if (err instanceof DOMException && err.name === \"AbortError\") return\n      const fetchError =\n        err instanceof Error ? err : new Error(\"Failed to fetch balance\")\n      setError(fetchError)\n      setBalance(null)\n    } finally {\n      setIsLoading(false)\n    }\n  }, [paymentAddress, apiUrl])\n\n  // Auto-fetch balance when payment address becomes available\n  useEffect(() => {\n    if (paymentAddress) {\n      void doFetch()\n    }\n    return () => {\n      abortRef.current?.abort()\n    }\n  }, [paymentAddress, doFetch])\n\n  return {\n    balance,\n    paymentAddress,\n    ordinalAddress,\n    identityKey,\n    isLoading,\n    error,\n    refetch: doFetch,\n  }\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/wallet-overview/use-wallet-overview.ts"
    }
  ],
  "type": "registry:block"
}