{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bitcoin-avatar",
  "title": "Bitcoin Avatar",
  "author": "Satchmo",
  "description": "Hybrid avatar that resolves on-chain images for Bitcoin addresses with deterministic sigma-avatars fallback",
  "dependencies": [
    "sigma-avatars",
    "bitcoin-image"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/bitcoin-avatar/index.tsx",
      "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { BitcoinAvatarUI } from \"./ui\"\nimport {\n  useBitcoinAvatar,\n  type UseBitcoinAvatarReturn,\n  type UseBitcoinAvatarOptions,\n} from \"./use-bitcoin-avatar\"\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport { BitcoinAvatarUI, type BitcoinAvatarUIProps } from \"./ui\"\nexport {\n  useBitcoinAvatar,\n  type UseBitcoinAvatarReturn,\n  type UseBitcoinAvatarOptions,\n} from \"./use-bitcoin-avatar\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Supported avatar variant styles from sigma-avatars */\ntype AvatarVariant =\n  | \"marble\"\n  | \"pixel\"\n  | \"beam\"\n  | \"ring\"\n  | \"sunset\"\n  | \"bauhaus\"\n  | \"fractal\"\n  | \"mage\"\n  | \"barcode\"\n  | \"pepe\"\n\nexport interface BitcoinAvatarProps {\n  /** Bitcoin address or BAP ID used as the deterministic seed */\n  address: string\n  /** On-chain image URL (ord://, b://, bitfs://) to resolve and display */\n  imageUrl?: string\n  /** Size variant: sm=32, md=40, lg=64, xl=96 */\n  size?: \"sm\" | \"md\" | \"lg\" | \"xl\"\n  /** Visual style for the deterministic fallback avatar */\n  variant?: AvatarVariant\n  /** Optional CSS class name */\n  className?: string\n}\n\nconst SIZE_MAP: Record<\"sm\" | \"md\" | \"lg\" | \"xl\", number> = {\n  sm: 32,\n  md: 40,\n  lg: 64,\n  xl: 96,\n}\n\n// ---------------------------------------------------------------------------\n// Composed component\n// ---------------------------------------------------------------------------\n\n/**\n * A hybrid avatar component for Bitcoin addresses.\n *\n * When `imageUrl` is provided (e.g. `ord://txid`), it resolves the on-chain\n * image via `bitcoin-image` and displays it. If resolution fails or no\n * `imageUrl` is given, it renders a deterministic SVG avatar from\n * `sigma-avatars` seeded by the `address` prop.\n */\nexport function BitcoinAvatar({\n  address,\n  imageUrl,\n  size = \"md\",\n  variant = \"marble\",\n  className,\n}: BitcoinAvatarProps) {\n  const px = SIZE_MAP[size]\n  const hook = useBitcoinAvatar({ address, imageUrl })\n\n  return (\n    <BitcoinAvatarUI\n      address={address}\n      px={px}\n      variant={variant}\n      className={className}\n      showOnChainImage={hook.showOnChainImage}\n      resolvedUrl={hook.resolvedUrl}\n      onImageError={hook.markResolutionFailed}\n    />\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/bitcoin-avatar/index.tsx"
    },
    {
      "path": "registry/new-york/blocks/bitcoin-avatar/ui.tsx",
      "content": "\"use client\"\n\nimport Avatar, { defaultColors } from \"sigma-avatars\"\nimport { cn } from \"@/lib/utils\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Supported avatar variant styles from sigma-avatars */\ntype AvatarVariant =\n  | \"marble\"\n  | \"pixel\"\n  | \"beam\"\n  | \"ring\"\n  | \"sunset\"\n  | \"bauhaus\"\n  | \"fractal\"\n  | \"mage\"\n  | \"barcode\"\n  | \"pepe\"\n\nexport interface BitcoinAvatarUIProps {\n  /** Bitcoin address or BAP ID used as the deterministic seed */\n  address: string\n  /** Size in pixels */\n  px: number\n  /** Visual style for the deterministic fallback avatar */\n  variant?: AvatarVariant\n  /** Optional CSS class name */\n  className?: string\n  /** Whether to show the resolved on-chain image */\n  showOnChainImage: boolean\n  /** The resolved HTTP URL for the on-chain image */\n  resolvedUrl: string | null\n  /** Callback when the on-chain <img> fails to load */\n  onImageError: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function BitcoinAvatarUI({\n  address,\n  px,\n  variant = \"marble\",\n  className,\n  showOnChainImage,\n  resolvedUrl,\n  onImageError,\n}: BitcoinAvatarUIProps) {\n  if (showOnChainImage && resolvedUrl) {\n    return (\n      <img\n        src={resolvedUrl}\n        alt={`Avatar for ${address.slice(0, 8)}...`}\n        width={px}\n        height={px}\n        className={cn(\"rounded-full object-cover\", className)}\n        data-testid=\"bitcoin-avatar\"\n        onError={onImageError}\n      />\n    )\n  }\n\n  // Deterministic fallback via sigma-avatars\n  return (\n    <div\n      className={cn(\"inline-flex shrink-0\", className)}\n      data-testid=\"bitcoin-avatar\"\n    >\n      <Avatar\n        name={address}\n        variant={variant}\n        size={px}\n        colors={defaultColors}\n        className=\"rounded-full\"\n      />\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/bitcoin-avatar/ui.tsx"
    },
    {
      "path": "registry/new-york/blocks/bitcoin-avatar/use-bitcoin-avatar.ts",
      "content": "import { useCallback, useEffect, useState } from \"react\"\nimport { ImageProtocols } from \"bitcoin-image\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseBitcoinAvatarOptions {\n  /** Bitcoin address or BAP ID used as the deterministic seed */\n  address: string\n  /** On-chain image URL (ord://, b://, bitfs://) to resolve and display */\n  imageUrl?: string\n}\n\nexport interface UseBitcoinAvatarReturn {\n  /** The resolved HTTP URL for the on-chain image, or null */\n  resolvedUrl: string | null\n  /** Whether the on-chain image resolution failed */\n  resolutionFailed: boolean\n  /** Whether to show the on-chain image vs the deterministic avatar */\n  showOnChainImage: boolean\n  /** Mark the on-chain image as failed (e.g. on <img> load error) */\n  markResolutionFailed: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Singleton ImageProtocols instance shared across all BitcoinAvatar mounts\n// ---------------------------------------------------------------------------\n\nlet sharedImageProtocols: ImageProtocols | null = null\n\nfunction getImageProtocols(): ImageProtocols {\n  if (!sharedImageProtocols) {\n    sharedImageProtocols = new ImageProtocols({\n      cacheEnabled: true,\n      cacheTTL: 3600,\n    })\n  }\n  return sharedImageProtocols\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useBitcoinAvatar({\n  address,\n  imageUrl,\n}: UseBitcoinAvatarOptions): UseBitcoinAvatarReturn {\n  const [resolvedUrl, setResolvedUrl] = useState<string | null>(null)\n  const [resolutionFailed, setResolutionFailed] = useState(false)\n\n  // Resolve on-chain image URL when provided\n  useEffect(() => {\n    if (!imageUrl) {\n      setResolvedUrl(null)\n      setResolutionFailed(false)\n      return\n    }\n\n    let cancelled = false\n    setResolutionFailed(false)\n\n    const protocols = getImageProtocols()\n    protocols\n      .getDisplayUrl(imageUrl)\n      .then((url) => {\n        if (cancelled) return\n        // ImageProtocols returns its fallback data URI on failure;\n        // treat that as a failed resolution so we show the sigma avatar\n        const parsed = protocols.parse(imageUrl)\n        if (!parsed.isValid) {\n          setResolutionFailed(true)\n          return\n        }\n        setResolvedUrl(url)\n      })\n      .catch(() => {\n        if (!cancelled) {\n          setResolutionFailed(true)\n        }\n      })\n\n    return () => {\n      cancelled = true\n    }\n  }, [imageUrl])\n\n  const showOnChainImage = Boolean(imageUrl && resolvedUrl && !resolutionFailed)\n\n  const markResolutionFailed = useCallback(() => {\n    setResolutionFailed(true)\n  }, [])\n\n  return {\n    resolvedUrl,\n    resolutionFailed,\n    showOnChainImage,\n    markResolutionFailed,\n  }\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/bitcoin-avatar/use-bitcoin-avatar.ts"
    }
  ],
  "type": "registry:block"
}