BigBlocks

Market Grid

Paginated grid of active ordinal NFT listings from the global BSV orderbook with sorting, filtering, and buy actions

A responsive grid of OrdLock listings fetched from the 1sat-stack API. Each card shows an ORDFS thumbnail, price badge, seller avatar, and a buy action. Supports pagination with a "Load More" button, loading skeletons, error recovery, and empty states. The underlying useMarketGrid hook is also exported for headless usage.

Installation

bunx shadcn@latest add https://registry.bigblocks.dev/r/market-grid.json

Usage

import { MarketGrid } from "@/components/blocks/market-grid"
import { useRouter } from "next/navigation"
 
export default function MarketplacePage() {
  const router = useRouter()
 
  return (
    <MarketGrid
      onBuy={(outpoint, price) => {
        router.push(`/buy/${outpoint}?price=${price}`)
      }}
      onListingClick={(outpoint) => {
        router.push(`/ordinal/${outpoint}`)
      }}
      pageSize={24}
      sortBy="recent"
      sortDir="desc"
    />
  )
}

Filtered by content type

<MarketGrid
  contentTypeFilter="image/png"
  sortBy="price"
  sortDir="asc"
  onBuy={(outpoint, price) => handleBuy(outpoint, price)}
/>

API Reference

MarketGrid

PropTypeDefaultDescription
onBuy(outpoint: string, price: number) => voidRequired. Called when the "Buy" button on a listing card is clicked.
onListingClick(outpoint: string) => voidCalled when a listing card body is clicked (for navigation).
apiUrlstring"https://api.1sat.app"Base URL of the 1sat-stack API.
pageSizenumber20Number of listings to load per page.
sortBy"price" | "recent""recent"Sort field.
sortDir"asc" | "desc""desc"Sort direction.
contentTypeFilterstringOptional MIME type filter (e.g. "image/png").
skeletonCountnumberNumber of skeleton cards to show during initial load.
classNamestringAdditional CSS classes applied to the outer container.

Types

MarketListing

The normalized listing shape used for display.

interface MarketListing {
  outpoint: string      // txid.vout format
  price: number         // Listing price in satoshis
  seller: string        // Seller address (base58check)
  contentType: string   // MIME type
  origin: string        // Origin outpoint for ORDFS thumbnail
  name: string | null   // Name from MAP metadata, or null
}

OneSatTxo

Raw TXO record from the API before normalization. Available from the re-export if you need to build your own normalization.

interface OneSatTxo {
  txid: string
  vout: number
  outpoint: string
  satoshis: number
  script: string
  type?: string
  origin?: string
  MAP?: Record<string, string>
  price?: number
  seller?: string
  name?: string
}

Hook

Use useMarketGrid directly when you need a custom listing layout or additional control over pagination.

import { useMarketGrid } from "@/components/blocks/market-grid"
 
function CustomMarket() {
  const market = useMarketGrid({
    pageSize: 12,
    sortBy: "price",
    sortDir: "asc",
    contentTypeFilter: "image/jpeg",
  })
 
  return (
    <div>
      {market.error && (
        <p>
          {market.error}{" "}
          <button onClick={market.refresh}>Retry</button>
        </p>
      )}
 
      <ul>
        {market.listings.map((listing) => (
          <li key={listing.outpoint}>
            {listing.name ?? listing.outpoint}{listing.price.toLocaleString()} sats
          </li>
        ))}
      </ul>
 
      {market.hasMore && (
        <button
          onClick={market.loadMore}
          disabled={market.isLoadingMore}
        >
          {market.isLoadingMore ? "Loading..." : "Load more"}
        </button>
      )}
    </div>
  )
}

UseMarketGridReturn

PropertyTypeDescription
listingsMarketListing[]Currently loaded listings across all loaded pages.
isLoadingbooleanWhether the initial page load is in progress.
isLoadingMorebooleanWhether a subsequent page is being fetched.
errorstring | nullError message from the most recent failed fetch.
hasMorebooleanWhether there are additional pages to load.
loadMore() => voidFetch the next page and append results. No-op when isLoadingMore or !hasMore.
refresh() => voidRe-fetch from the first page, clearing existing results.

Notes

  • Listings with a price of 0 are filtered out during normalization.
  • Requests in flight are aborted when sort/filter parameters change, preventing stale results from appearing.
  • The API endpoint queried is GET /1sat/txo/search?tags=ordlock&unspent=true&limit=N&offset=N.