{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ordinals-grid",
  "title": "Ordinals Grid",
  "author": "Satchmo",
  "description": "Responsive grid of owned ordinal NFTs with ORDFS thumbnails, content type badges, and name/outpoint display. Fetches from 1sat API or accepts pre-loaded data.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "card",
    "badge",
    "dropdown-menu",
    "skeleton",
    "scroll-area"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/ordinals-grid/index.tsx",
      "content": "\"use client\"\n\nimport {\n  OrdinalsGridUI,\n  type OrdinalsGridUIProps,\n} from \"./ordinals-grid-ui\"\nimport {\n  useOrdinalsGrid,\n  type OrdinalOutput,\n  type UseOrdinalsGridOptions,\n  type UseOrdinalsGridReturn,\n} from \"./use-ordinals-grid\"\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport {\n  OrdinalsGridUI,\n  type OrdinalsGridUIProps,\n} from \"./ordinals-grid-ui\"\nexport {\n  useOrdinalsGrid,\n  type OrdinalOutput,\n  type UseOrdinalsGridOptions,\n  type UseOrdinalsGridReturn,\n} from \"./use-ordinals-grid\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OrdinalsGridProps {\n  /** Ordinal address to fetch from the 1sat API */\n  address?: string\n  /** Pre-fetched ordinals to display (bypasses API fetch) */\n  ordinals?: OrdinalOutput[]\n  /** Base URL for the 1sat owner API */\n  apiUrl?: string\n  /** ORDFS base URL for content resolution (default: https://ordfs.network) */\n  ordfsBase?: string\n  /** Max number of items to display */\n  limit?: number\n  /** Called when an ordinal card is clicked */\n  onSelect?: (ordinal: OrdinalOutput) => void\n  /** Called when the Transfer action is triggered on an ordinal */\n  onTransfer?: (ordinal: OrdinalOutput) => void\n  /** Called when the List action is triggered on an ordinal */\n  onList?: (ordinal: OrdinalOutput) => void\n  /** Called when the View Detail action is triggered on an ordinal */\n  onDetail?: (ordinal: OrdinalOutput) => void\n  /** Called when an external link action is triggered. Receives the URL string. */\n  onExternalLink?: (url: string) => void\n  /** Whether to show the item count header (default: true) */\n  showCount?: boolean\n  /** Whether to wrap the grid in a ScrollArea (default: false) */\n  scrollable?: boolean\n  /** Max height for the scroll area when scrollable is true */\n  maxHeight?: string\n  /** Additional CSS classes */\n  className?: string\n}\n\n// ---------------------------------------------------------------------------\n// Composed component\n// ---------------------------------------------------------------------------\n\n/**\n * Responsive grid of owned ordinal NFTs with ORDFS thumbnails.\n *\n * Supply either an `address` to auto-fetch from the 1sat API, or pass\n * pre-fetched `ordinals` directly. Each card displays the inscription\n * image, name, content type badge, and truncated outpoint.\n *\n * @example\n * ```tsx\n * import { OrdinalsGrid } from \"@/components/blocks/ordinals-grid\"\n *\n * // Fetch from address\n * <OrdinalsGrid\n *   address=\"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa\"\n *   onSelect={(ordinal) => console.log(\"Selected:\", ordinal.outpoint)}\n * />\n *\n * // Or pass pre-fetched ordinals\n * <OrdinalsGrid\n *   ordinals={myOrdinals}\n *   onSelect={(ordinal) => router.push(`/ordinal/${ordinal.outpoint}`)}\n * />\n * ```\n */\nexport function OrdinalsGrid({\n  address,\n  ordinals,\n  apiUrl,\n  ordfsBase,\n  limit,\n  onSelect,\n  onTransfer,\n  onList,\n  onDetail,\n  onExternalLink,\n  showCount,\n  scrollable,\n  maxHeight,\n  className,\n}: OrdinalsGridProps) {\n  const grid = useOrdinalsGrid({ address, ordinals, apiUrl, limit })\n\n  return (\n    <OrdinalsGridUI\n      items={grid.items}\n      isLoading={grid.isLoading}\n      error={grid.error}\n      count={grid.count}\n      onSelect={onSelect}\n      onTransfer={onTransfer}\n      onList={onList}\n      onDetail={onDetail}\n      onExternalLink={onExternalLink}\n      ordfsBase={ordfsBase}\n      showCount={showCount}\n      scrollable={scrollable}\n      maxHeight={maxHeight}\n      className={className}\n    />\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/ordinals-grid/index.tsx"
    },
    {
      "path": "registry/new-york/blocks/ordinals-grid/ordinals-grid-ui.tsx",
      "content": "\"use client\"\n\nimport { useCallback, useState } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport {\n  ImageOff,\n  Grid2x2,\n  ArrowRightLeft,\n  Tag,\n  Eye,\n  ExternalLink,\n  MoreVertical,\n} from \"lucide-react\"\nimport type { OrdinalOutput } from \"./use-ordinals-grid\"\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_ORDFS_BASE = \"https://ordfs.network\"\n\n/** Number of skeleton cards to show during loading */\nconst SKELETON_COUNT = 8\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OrdinalsGridUIProps {\n  /** Ordinal items to display */\n  items: OrdinalOutput[]\n  /** Whether the grid is in a loading state */\n  isLoading: boolean\n  /** Error to display */\n  error: Error | null\n  /** Total count of items */\n  count: number\n  /** Called when an ordinal card is clicked */\n  onSelect?: (ordinal: OrdinalOutput) => void\n  /** Called when the Transfer action is triggered on an ordinal */\n  onTransfer?: (ordinal: OrdinalOutput) => void\n  /** Called when the List action is triggered on an ordinal */\n  onList?: (ordinal: OrdinalOutput) => void\n  /** Called when the View Detail action is triggered on an ordinal */\n  onDetail?: (ordinal: OrdinalOutput) => void\n  /** Called when an external link action is triggered. Receives the URL string. */\n  onExternalLink?: (url: string) => void\n  /** ORDFS base URL for content resolution (default: https://ordfs.network) */\n  ordfsBase?: string\n  /** Whether to show the item count header (default: true) */\n  showCount?: boolean\n  /** Whether to wrap the grid in a ScrollArea (default: false) */\n  scrollable?: boolean\n  /** Max height for the scroll area when scrollable is true */\n  maxHeight?: string\n  /** Additional CSS classes for the root container */\n  className?: string\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\ninterface OrdinalThumbnailProps {\n  origin: string\n  name: string | undefined\n  outpoint: string\n  ordfsBase: string\n}\n\nfunction OrdinalThumbnail({ origin, name, outpoint, ordfsBase }: OrdinalThumbnailProps) {\n  const [hasError, setHasError] = useState(false)\n  const [isImageLoading, setIsImageLoading] = useState(true)\n\n  const handleError = useCallback(() => {\n    setHasError(true)\n    setIsImageLoading(false)\n  }, [])\n\n  const handleLoad = useCallback(() => {\n    setIsImageLoading(false)\n  }, [])\n\n  const alt = name ?? `Ordinal ${outpoint.slice(0, 8)}...`\n  const src = `${ordfsBase}/content/${origin}`\n\n  if (hasError) {\n    return (\n      <div className=\"flex h-full w-full items-center justify-center bg-muted\">\n        <ImageOff\n          className=\"size-8 text-muted-foreground/50\"\n          aria-hidden=\"true\"\n        />\n      </div>\n    )\n  }\n\n  return (\n    <>\n      {isImageLoading && (\n        <Skeleton className=\"absolute inset-0 h-full w-full rounded-none\" />\n      )}\n      <img\n        src={src}\n        alt={alt}\n        className={cn(\n          \"h-full w-full object-cover transition-transform duration-200 group-hover:scale-105\",\n          isImageLoading && \"invisible\",\n        )}\n        loading=\"lazy\"\n        onError={handleError}\n        onLoad={handleLoad}\n      />\n    </>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Action menu\n// ---------------------------------------------------------------------------\n\ninterface OrdinalActionMenuProps {\n  ordinal: OrdinalOutput\n  truncatedOutpoint: string\n  onTransfer?: (ordinal: OrdinalOutput) => void\n  onList?: (ordinal: OrdinalOutput) => void\n  onDetail?: (ordinal: OrdinalOutput) => void\n  onExternalLink?: (url: string) => void\n}\n\nfunction OrdinalActionMenu({\n  ordinal,\n  truncatedOutpoint,\n  onTransfer,\n  onList,\n  onDetail,\n  onExternalLink,\n}: OrdinalActionMenuProps) {\n  const label = ordinal.name ?? truncatedOutpoint\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant=\"secondary\"\n          size=\"icon\"\n          className={cn(\n            \"absolute right-2 top-2 size-7\",\n            \"opacity-0 transition-opacity duration-200\",\n            \"group-hover:opacity-100 focus:opacity-100\",\n          )}\n          aria-label={`Actions for ${label}`}\n          onClick={(e: React.MouseEvent) => e.stopPropagation()}\n        >\n          <MoreVertical className=\"size-3.5\" />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-40\">\n        {onDetail && (\n          <DropdownMenuItem\n            onClick={(e: React.MouseEvent) => {\n              e.stopPropagation()\n              onDetail(ordinal)\n            }}\n          >\n            <Eye className=\"size-4\" />\n            View Details\n          </DropdownMenuItem>\n        )}\n        {onTransfer && (\n          <DropdownMenuItem\n            onClick={(e: React.MouseEvent) => {\n              e.stopPropagation()\n              onTransfer(ordinal)\n            }}\n          >\n            <ArrowRightLeft className=\"size-4\" />\n            Transfer\n          </DropdownMenuItem>\n        )}\n        {onList && (\n          <DropdownMenuItem\n            onClick={(e: React.MouseEvent) => {\n              e.stopPropagation()\n              onList(ordinal)\n            }}\n          >\n            <Tag className=\"size-4\" />\n            List for Sale\n          </DropdownMenuItem>\n        )}\n        {onExternalLink && (\n          <DropdownMenuItem\n            onClick={(e: React.MouseEvent) => {\n              e.stopPropagation()\n              const txid = ordinal.outpoint.split(\"_\")[0]\n              onExternalLink(`https://whatsonchain.com/tx/${txid}`)\n            }}\n          >\n            <ExternalLink className=\"size-4\" />\n            View on Explorer\n          </DropdownMenuItem>\n        )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Ordinal card\n// ---------------------------------------------------------------------------\n\ninterface OrdinalCardProps {\n  ordinal: OrdinalOutput\n  onSelect?: (ordinal: OrdinalOutput) => void\n  onTransfer?: (ordinal: OrdinalOutput) => void\n  onList?: (ordinal: OrdinalOutput) => void\n  onDetail?: (ordinal: OrdinalOutput) => void\n  onExternalLink?: (url: string) => void\n  ordfsBase: string\n}\n\nfunction OrdinalCard({\n  ordinal,\n  onSelect,\n  onTransfer,\n  onList,\n  onDetail,\n  onExternalLink,\n  ordfsBase,\n}: OrdinalCardProps) {\n  const truncatedOutpoint =\n    ordinal.outpoint.length > 16\n      ? `${ordinal.outpoint.slice(0, 8)}...${ordinal.outpoint.slice(-6)}`\n      : ordinal.outpoint\n\n  const hasActions = !!onTransfer || !!onList || !!onDetail || !!onExternalLink\n\n  const handleClick = useCallback(() => {\n    onSelect?.(ordinal)\n  }, [ordinal, onSelect])\n\n  const handleKeyDown = useCallback(\n    (e: React.KeyboardEvent) => {\n      if (e.key === \"Enter\" || e.key === \" \") {\n        e.preventDefault()\n        onSelect?.(ordinal)\n      }\n    },\n    [ordinal, onSelect],\n  )\n\n  return (\n    <Card\n      className={cn(\n        \"group overflow-hidden transition-all duration-200\",\n        \"hover:shadow-md hover:border-primary/30\",\n        onSelect && \"cursor-pointer\",\n      )}\n      role={onSelect ? \"button\" : undefined}\n      tabIndex={onSelect ? 0 : undefined}\n      aria-label={\n        ordinal.name\n          ? `Ordinal: ${ordinal.name}`\n          : `Ordinal ${truncatedOutpoint}`\n      }\n      onClick={onSelect ? handleClick : undefined}\n      onKeyDown={onSelect ? handleKeyDown : undefined}\n    >\n      <div className=\"relative aspect-square overflow-hidden bg-muted/50\">\n        <OrdinalThumbnail\n          origin={ordinal.origin}\n          name={ordinal.name}\n          outpoint={ordinal.outpoint}\n          ordfsBase={ordfsBase}\n        />\n        {hasActions && (\n          <OrdinalActionMenu\n            ordinal={ordinal}\n            truncatedOutpoint={truncatedOutpoint}\n            onTransfer={onTransfer}\n            onList={onList}\n            onDetail={onDetail}\n            onExternalLink={onExternalLink}\n          />\n        )}\n      </div>\n      <CardContent className=\"flex flex-col gap-1.5 p-3\">\n        {ordinal.name ? (\n          <p className=\"truncate text-sm font-medium leading-none\">\n            {ordinal.name}\n          </p>\n        ) : (\n          <p className=\"truncate text-sm font-medium leading-none text-muted-foreground\">\n            Unnamed\n          </p>\n        )}\n        <div className=\"flex items-center justify-between gap-2\">\n          {ordinal.contentType && (\n            <Badge variant=\"secondary\" className=\"text-[10px] px-1.5 py-0\">\n              {ordinal.contentType.split(\"/\").pop()}\n            </Badge>\n          )}\n          <span className=\"truncate text-[11px] font-mono text-muted-foreground\">\n            {truncatedOutpoint}\n          </span>\n        </div>\n      </CardContent>\n    </Card>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Loading skeleton\n// ---------------------------------------------------------------------------\n\nfunction SkeletonGrid() {\n  return (\n    <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4\">\n      {Array.from({ length: SKELETON_COUNT }, (_, i) => (\n        <Card key={i} className=\"overflow-hidden\">\n          <Skeleton className=\"aspect-square w-full rounded-none\" />\n          <div className=\"flex flex-col gap-2 p-3\">\n            <Skeleton className=\"h-4 w-3/4\" />\n            <div className=\"flex items-center justify-between gap-2\">\n              <Skeleton className=\"h-4 w-12\" />\n              <Skeleton className=\"h-3 w-20\" />\n            </div>\n          </div>\n        </Card>\n      ))}\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Empty state\n// ---------------------------------------------------------------------------\n\nfunction EmptyState() {\n  return (\n    <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n      <div className=\"mb-3 flex size-12 items-center justify-center rounded-full bg-muted\">\n        <Grid2x2\n          className=\"size-6 text-muted-foreground\"\n          aria-hidden=\"true\"\n        />\n      </div>\n      <p className=\"text-sm font-medium text-muted-foreground\">\n        No ordinals found\n      </p>\n      <p className=\"mt-1 text-xs text-muted-foreground/70\">\n        Ordinals owned by this address will appear here\n      </p>\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Error state\n// ---------------------------------------------------------------------------\n\ninterface ErrorStateProps {\n  error: Error\n}\n\nfunction ErrorState({ error }: ErrorStateProps) {\n  return (\n    <div className=\"flex flex-col items-center justify-center py-16 text-center\">\n      <p className=\"text-sm font-medium text-destructive\">\n        Failed to load ordinals\n      </p>\n      <p className=\"mt-1 max-w-sm text-xs text-muted-foreground\">\n        {error.message}\n      </p>\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Main UI component\n// ---------------------------------------------------------------------------\n\nexport function OrdinalsGridUI({\n  items,\n  isLoading,\n  error,\n  count,\n  onSelect,\n  onTransfer,\n  onList,\n  onDetail,\n  onExternalLink,\n  ordfsBase = DEFAULT_ORDFS_BASE,\n  showCount = true,\n  scrollable = false,\n  maxHeight = \"600px\",\n  className,\n}: OrdinalsGridUIProps) {\n  // Loading state\n  if (isLoading) {\n    return (\n      <div className={cn(\"flex flex-col gap-4\", className)}>\n        {showCount && <Skeleton className=\"h-5 w-24\" />}\n        <SkeletonGrid />\n      </div>\n    )\n  }\n\n  // Error state\n  if (error) {\n    return (\n      <div className={className}>\n        <ErrorState error={error} />\n      </div>\n    )\n  }\n\n  // Empty state\n  if (items.length === 0) {\n    return (\n      <div className={className}>\n        <EmptyState />\n      </div>\n    )\n  }\n\n  const gridContent = (\n    <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4\">\n      {items.map((ordinal) => (\n        <OrdinalCard\n          key={ordinal.outpoint}\n          ordinal={ordinal}\n          onSelect={onSelect}\n          onTransfer={onTransfer}\n          onList={onList}\n          onDetail={onDetail}\n          onExternalLink={onExternalLink}\n          ordfsBase={ordfsBase}\n        />\n      ))}\n    </div>\n  )\n\n  return (\n    <div className={cn(\"flex flex-col gap-4\", className)}>\n      {showCount && (\n        <div className=\"flex items-center justify-between\">\n          <h3 className=\"text-sm font-medium text-muted-foreground\">\n            {count} Ordinal{count !== 1 ? \"s\" : \"\"}\n          </h3>\n        </div>\n      )}\n      {scrollable ? (\n        <ScrollArea style={{ maxHeight }}>{gridContent}</ScrollArea>\n      ) : (\n        gridContent\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/ordinals-grid/ordinals-grid-ui.tsx"
    },
    {
      "path": "registry/new-york/blocks/ordinals-grid/use-ordinals-grid.ts",
      "content": "import { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Represents a single ordinal NFT output */\nexport interface OrdinalOutput {\n  /** Outpoint in txid_vout or txid.vout format */\n  outpoint: string\n  /** Content type from tags (e.g. \"image/png\") */\n  contentType: string\n  /** Display name from MAP metadata */\n  name?: string\n  /** Origin outpoint for ORDFS content resolution */\n  origin: string\n  /** Satoshis held by this output */\n  satoshis: number\n  /** Raw tags array from the wallet output */\n  tags: string[]\n}\n\nexport interface UseOrdinalsGridOptions {\n  /** Ordinal address to fetch from the 1sat API. If omitted, ordinals must be passed via `ordinals` prop. */\n  address?: string\n  /** Pre-fetched ordinals to display (bypasses API fetch) */\n  ordinals?: OrdinalOutput[]\n  /** Base URL for the 1sat owner API (default: \"https://api.1sat.app/1sat\") */\n  apiUrl?: string\n  /** Max number of items to display (default: unlimited) */\n  limit?: number\n}\n\nexport interface UseOrdinalsGridReturn {\n  /** Parsed ordinal items ready for display */\n  items: OrdinalOutput[]\n  /** Whether the initial fetch is in progress */\n  isLoading: boolean\n  /** Error from the fetch, if any */\n  error: Error | null\n  /** Total count of ordinals loaded */\n  count: number\n  /** Reload ordinals from the API */\n  reload: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_API_URL = \"https://api.1sat.app/1sat\"\n\n/** Extract a tag value by prefix (e.g. \"type:\" -> \"image/png\") */\nfunction getTag(tags: string[], prefix: string): string | undefined {\n  const tag = tags.find((t) => t.startsWith(prefix))\n  return tag ? tag.slice(prefix.length) : undefined\n}\n\n/** Normalize an outpoint to underscore format (txid_vout) */\nfunction normalizeOutpoint(outpoint: string): string {\n  return outpoint.replace(\".\", \"_\")\n}\n\n/**\n * Parse a single SSE line from the 1sat owner API into an OrdinalOutput.\n * Expected JSON shape: { outpoint: string, satoshis: number, tags?: string[] }\n */\nfunction parseTxoLine(json: string): OrdinalOutput | null {\n  try {\n    const data: {\n      outpoint: string\n      satoshis: number\n      tags?: string[]\n    } = JSON.parse(json)\n\n    const tags = data.tags ?? []\n    const contentType = getTag(tags, \"type:\") ?? \"\"\n    const name = getTag(tags, \"name:\")\n    const origin = getTag(tags, \"origin:\") ?? data.outpoint\n\n    return {\n      outpoint: normalizeOutpoint(data.outpoint),\n      contentType,\n      name,\n      origin: normalizeOutpoint(origin),\n      satoshis: data.satoshis,\n      tags,\n    }\n  } catch {\n    return null\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useOrdinalsGrid({\n  address,\n  ordinals: externalOrdinals,\n  apiUrl = DEFAULT_API_URL,\n  limit,\n}: UseOrdinalsGridOptions = {}): UseOrdinalsGridReturn {\n  const [fetchedOrdinals, setFetchedOrdinals] = useState<OrdinalOutput[]>([])\n  const [isLoading, setIsLoading] = useState(false)\n  const [error, setError] = useState<Error | null>(null)\n  const abortRef = useRef<AbortController | null>(null)\n\n  const fetchOrdinals = useCallback(\n    (addr: string) => {\n      // Cancel any in-flight request\n      abortRef.current?.abort()\n\n      const controller = new AbortController()\n      abortRef.current = controller\n\n      setIsLoading(true)\n      setError(null)\n\n      const url = `${apiUrl}/owner/${addr}/txos`\n      const items: OrdinalOutput[] = []\n\n      fetch(url, { signal: controller.signal })\n        .then(async (response) => {\n          if (!response.ok) {\n            throw new Error(`Failed to fetch ordinals: ${response.status}`)\n          }\n\n          if (!response.body) {\n            throw new Error(\"Response body is null\")\n          }\n\n          const reader = response.body.getReader()\n          const decoder = new TextDecoder()\n          let buffer = \"\"\n\n          for (;;) {\n            const { done, value } = await reader.read()\n            if (done) break\n\n            buffer += decoder.decode(value, { stream: true })\n            const lines = buffer.split(\"\\n\")\n            // Keep the last incomplete line in the buffer\n            buffer = lines.pop() ?? \"\"\n\n            for (const line of lines) {\n              const trimmed = line.trim()\n              if (!trimmed) continue\n\n              const parsed = parseTxoLine(trimmed)\n              if (parsed) {\n                items.push(parsed)\n              }\n            }\n          }\n\n          // Process any remaining buffer\n          if (buffer.trim()) {\n            const parsed = parseTxoLine(buffer.trim())\n            if (parsed) {\n              items.push(parsed)\n            }\n          }\n\n          if (!controller.signal.aborted) {\n            setFetchedOrdinals(items)\n            setIsLoading(false)\n          }\n        })\n        .catch((err: unknown) => {\n          if (controller.signal.aborted) return\n          const fetchError =\n            err instanceof Error ? err : new Error(\"Failed to fetch ordinals\")\n          setError(fetchError)\n          setIsLoading(false)\n        })\n    },\n    [apiUrl],\n  )\n\n  const reload = useCallback(() => {\n    if (address) {\n      fetchOrdinals(address)\n    }\n  }, [address, fetchOrdinals])\n\n  // Fetch on mount or when address changes\n  useEffect(() => {\n    if (externalOrdinals) return\n    if (!address) return\n\n    fetchOrdinals(address)\n\n    return () => {\n      abortRef.current?.abort()\n    }\n  }, [address, externalOrdinals, fetchOrdinals])\n\n  const items = useMemo(() => {\n    const source = externalOrdinals ?? fetchedOrdinals\n    if (limit !== undefined && limit > 0) {\n      return source.slice(0, limit)\n    }\n    return source\n  }, [externalOrdinals, fetchedOrdinals, limit])\n\n  return {\n    items,\n    isLoading: externalOrdinals ? false : isLoading,\n    error: externalOrdinals ? null : error,\n    count: items.length,\n    reload,\n  }\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/ordinals-grid/use-ordinals-grid.ts"
    }
  ],
  "type": "registry:block"
}