Skip to main content
The AgrospAI Data Space Portal provides powerful search and discovery capabilities to help you find the perfect datasets, algorithms, and services for your needs. Built on Aquarius metadata indexing and Elasticsearch, the platform offers fast, flexible, and intuitive asset discovery.

Discovery Features

Full-Text Search

Search across asset names, descriptions, tags, and metadata

Advanced Filters

Filter by asset type, price, network, publisher, and more

Smart Sorting

Sort by relevance, date, price, or popularity

Bookmarks

Save favorite assets for quick access later

Search Component Architecture

The search functionality is built with real-time query updates and optimized performance:
// From: src/components/Search/index.tsx
export default function SearchPage({
  setTotalResults,
  setTotalPagesNumber
}: {
  setTotalResults: (totalResults: number) => void
  setTotalPagesNumber: (totalPagesNumber: number) => void
}): ReactElement {
  const router = useRouter()
  const [parsed, setParsed] = useState<queryString.ParsedQuery<string>>()
  const { chainIds } = useUserPreferences()
  const [queryResult, setQueryResult] = useState<PagedAssets>()
  const [loading, setLoading] = useState<boolean>(true)
  const newCancelToken = useCancelToken()

  // Parse URL query parameters
  useEffect(() => {
    const parsed = queryString.parse(location.search, {
      arrayFormat: 'separator'
    })
    setParsed(parsed)
  }, [router])

  // Debounced search to avoid excessive API calls
  const fetchAssets = useDebouncedCallback(
    async (parsed: queryString.ParsedQuery<string>, chainIds: number[]) => {
      setLoading(true)
      setTotalResults(undefined)
      const queryResult = await getResults(parsed, chainIds, newCancelToken())
      setQueryResult(queryResult)

      setTotalResults(queryResult?.totalResults || 0)
      setTotalPagesNumber(queryResult?.totalPages || 0)
      setLoading(false)
    },
    500 // 500ms debounce delay
  )

  useEffect(() => {
    if (!parsed || !chainIds) return
    fetchAssets(parsed, chainIds)
  }, [parsed, chainIds, fetchAssets])

  return (
    <div className={styles.container}>
      <div className={styles.filterContainer}>
        <Filter addFiltersToUrl expanded />
        <Sort expanded />
      </div>
      <div className={styles.results}>
        <AssetList
          assets={queryResult?.results}
          showPagination
          isLoading={loading}
          page={queryResult?.page}
          totalPages={queryResult?.totalPages}
          onPageChange={updatePage}
          showAssetViewSelector
        />
      </div>
    </div>
  )
}
Search queries are debounced by 500ms to optimize performance and reduce server load while maintaining a responsive user experience.

URL-Based Search State

All search parameters are stored in the URL, enabling:
  • Shareable searches: Send search URLs to colleagues
  • Browser history: Use back/forward buttons to navigate searches
  • Bookmarkable results: Save frequently used queries
// URL format:
// /search?text=agricultural&sort=nft.created&sortOrder=desc&page=1

const updatePage = useCallback(
  (page: number) => {
    const { pathname, query } = router
    const newUrl = updateQueryStringParameter(
      pathname + '?' + JSON.stringify(query)
        .replace(/"|{|}/g, '')
        .replace(/:/g, '=')
        .replace(/,/g, '&'),
      'page',
      `${page}`
    )
    return router.push(newUrl)
  },
  [router]
)

Search Filters

Filter Component

The Filter component provides granular control over search results:
// From: src/components/Search/Filter.tsx
<Filter addFiltersToUrl expanded>
  <FilterSection title="Asset Type">
    <Checkbox name="dataset" label="Datasets" />
    <Checkbox name="algorithm" label="Algorithms" />
    <Checkbox name="saas" label="SaaS Services" />
  </FilterSection>
  
  <FilterSection title="Pricing">
    <Checkbox name="free" label="Free" />
    <Checkbox name="fixed" label="Fixed Price" />
  </FilterSection>
  
  <FilterSection title="Network">
    <Checkbox name="1" label="Ethereum" />
    <Checkbox name="137" label="Polygon" />
    <Checkbox name="56" label="BNB Chain" />
  </FilterSection>
  
  <FilterSection title="Access Type">
    <Checkbox name="access" label="Download" />
    <Checkbox name="compute" label="Compute" />
  </FilterSection>
</Filter>

Available Filters

Filter by the type of asset:
  • Dataset: Raw data files (CSV, JSON, etc.)
  • Algorithm: Executable code for compute-to-data
  • SaaS: Software services with external access

Sorting Options

Customize result ordering based on your priorities:
// From: src/components/Search/sort.tsx
export const sortOptions = [
  {
    value: 'nft.created',
    label: 'Published Date',
    direction: 'desc'
  },
  {
    value: 'metadata.name',
    label: 'Name',
    direction: 'asc'
  },
  {
    value: 'stats.orders',
    label: 'Sales',
    direction: 'desc'
  },
  {
    value: 'accessDetails.price',
    label: 'Price',
    direction: 'asc'
  },
  {
    value: '_score',
    label: 'Relevance',
    direction: 'desc'
  }
]

<Sort expanded>
  <Select
    name="sort"
    options={sortOptions}
    value={currentSort}
    onChange={handleSortChange}
  />
  
  <SortOrder
    value={sortOrder}
    onChange={toggleSortOrder}
  />
</Sort>

By Date

Find newest or oldest assets

By Sales

Discover popular datasets

By Price

Sort by cost, low to high

Homepage Discovery Sections

The homepage showcases curated and trending assets:
// From: src/components/Home/index.tsx
export default function HomePage(): ReactElement {
  const { chainIds } = useUserPreferences()
  const { featured, hasFeaturedAssets } = useAddressConfig()

  const [queryRecent, setQueryRecent] = useState<SearchQuery>()
  const [queryMostSales, setQueryMostSales] = useState<SearchQuery>()

  useEffect(() => {
    const baseParams = {
      chainIds,
      esPaginationOptions: {
        size: 4
      },
      sortOptions: {
        sortBy: SortTermOptions.Created
      } as SortOptions
    } as BaseQueryParams

    // Query for recently published assets
    setQueryRecent(generateBaseQuery(baseParams))

    // Query for best-selling assets
    const baseParamsSales = {
      ...baseParams,
      sortOptions: {
        sortBy: SortTermOptions.Orders
      }
    }
    setQueryMostSales(generateBaseQuery(baseParamsSales))
  }, [chainIds])

  return (
    <>
      {hasFeaturedAssets() && (
        <>
          {queryFeatured.map((section, i) => (
            <SectionQueryResult
              key={`${section.title}-${i}`}
              title={section.title}
              query={section.query}
            />
          ))}
        </>
      )}
      <SectionQueryResult title="Recently Published" query={queryRecent} />
      <SectionQueryResult title="Most Sales" query={queryMostSales} />
      <AllAssetsButton />
    </>
  )
}
Administrators can curate featured asset collections:
// Featured sections configuration
const featured = [
  {
    title: 'Agricultural Data',
    assets: [
      'did:op:abc123...',
      'did:op:def456...',
      'did:op:ghi789...'
    ]
  },
  {
    title: 'Climate Models',
    assets: [
      'did:op:jkl012...',
      'did:op:mno345...'
    ]
  }
]

Asset Preview Cards

Assets are displayed in informative cards showing key details:
<AssetCard asset={asset}>
  <AssetImage src={asset.metadata.nft?.image} />
  <AssetTitle>{asset.metadata.name}</AssetTitle>
  <AssetDescription>{asset.metadata.description}</AssetDescription>
  
  <AssetMetadata>
    <MetaItem icon="tag" label={asset.metadata.type} />
    <MetaItem icon="network" label={asset.chainId} />
    <MetaItem icon="clock" label={publishedDate} />
  </AssetMetadata>
  
  <AssetPrice>
    <Price price={asset.accessDetails?.price} />
    <PriceToken symbol={asset.accessDetails?.baseToken?.symbol} />
  </AssetPrice>
  
  <AssetStats>
    <Stat icon="download" value={asset.stats.orders} />
    <Stat icon="star" value={asset.stats.favorites} />
  </AssetStats>
</AssetCard>

Bookmark Feature

Save assets for quick access later:
// From: src/components/Asset/AssetContent/Bookmark.tsx
export default function Bookmark({
  did,
  defaultChecked
}: {
  did: string
  defaultChecked: boolean
}): ReactElement {
  const { addBookmark, removeBookmark, bookmarks } = useBookmarks()
  const [checked, setChecked] = useState(defaultChecked)

  const handleBookmark = () => {
    if (checked) {
      removeBookmark(did)
      setChecked(false)
    } else {
      addBookmark(did)
      setChecked(true)
    }
  }

  return (
    <button
      className={styles.button}
      onClick={handleBookmark}
      aria-label={checked ? 'Remove bookmark' : 'Add bookmark'}
    >
      <BookmarkIcon
        className={checked ? styles.bookmarked : styles.bookmark}
      />
    </button>
  )
}
View all bookmarked assets at /bookmarks:
<BookmarksPage>
  <AssetList
    assets={bookmarkedAssets}
    emptyMessage="You haven't bookmarked any assets yet"
  />
</BookmarksPage>
Bookmarks are stored locally in your browser. Sign in to sync bookmarks across devices.

Asset Detail View

Clicking an asset opens its detailed page:
// From: src/components/Asset/index.tsx
export default function AssetDetails({ uri }: { uri: string }): ReactElement {
  const { asset, title, error, isInPurgatory, loading } = useAsset()
  const [pageTitle, setPageTitle] = useState<string>()

  useEffect(() => {
    if (!asset || error) {
      setPageTitle(title || 'Could not retrieve asset')
      return
    }
    setPageTitle(isInPurgatory ? '' : title)
  }, [asset, error, isInPurgatory, title])

  return asset && pageTitle !== undefined && !loading ? (
    <Page title={pageTitle} uri={uri}>
      <AssetContent asset={asset} />
    </Page>
  ) : error ? (
    <Page title={pageTitle} noPageHeader uri={uri}>
      <Alert title={pageTitle} text={error} state={'error'} />
    </Page>
  ) : (
    <Page title={undefined} uri={uri}>
      <Loader />
    </Page>
  )
}

Asset Metadata Display

The detail page shows comprehensive asset information:
<AssetContent asset={asset}>
  {/* Main metadata */}
  <MetaMain>
    <MetaAsset
      title={asset.metadata.name}
      description={asset.metadata.description}
      publisher={asset.nft.owner}
      created={asset.nft.created}
      tags={asset.metadata.tags}
    />
    
    <MetaInfo
      license={asset.metadata.license}
      type={asset.metadata.type}
      algorithm={asset.metadata.algorithm}
    />
  </MetaMain>
  
  {/* Secondary metadata */}
  <MetaSecondary>
    <MetaItem label="Network" value={getNetworkName(asset.chainId)} />
    <MetaItem label="Datatoken" value={asset.datatokens[0].address} />
    <MetaItem label="NFT Address" value={asset.nft.address} />
    <MetaItem label="DID" value={asset.id} />
  </MetaSecondary>
  
  {/* File information */}
  <FileInfo
    file={asset.services[0].files}
    isLoading={fileIsLoading}
  />
  
  {/* Actions (download, compute) */}
  <AssetActions
    asset={asset}
    accountId={accountId}
    signer={signer}
  />
  
  {/* Edit history */}
  <EditHistory transactions={asset.nft.eventHistory} />
  
  {/* Related assets */}
  <RelatedAssets did={asset.id} />
</AssetContent>
Discover similar assets based on tags and publisher:
// From: src/components/Asset/RelatedAssets/index.tsx
export default function RelatedAssets({
  did
}: {
  did: string
}): ReactElement {
  const { asset } = useAsset()
  const [relatedAssets, setRelatedAssets] = useState<Asset[]>()
  const { chainIds } = useUserPreferences()

  useEffect(() => {
    if (!asset || !chainIds) return

    async function getRelated() {
      // Query assets with matching tags
      const tagQuery = generateBaseQuery({
        chainIds,
        filters: [
          getFilterTerm('metadata.tags.keyword', asset.metadata.tags)
        ],
        esPaginationOptions: { size: 4 }
      })
      
      const results = await queryMetadata(tagQuery)
      
      // Exclude current asset
      const filtered = results.filter(a => a.id !== did)
      
      setRelatedAssets(filtered)
    }

    getRelated()
  }, [asset, chainIds, did])

  return relatedAssets?.length > 0 ? (
    <section className={styles.related}>
      <h3>Related Assets</h3>
      <AssetList assets={relatedAssets} />
    </section>
  ) : null
}

Search Best Practices

1

Use Descriptive Keywords

Search terms are matched against names, descriptions, and tags. Use specific agricultural or data science terminology.
2

Combine Filters

Use multiple filters together to narrow results. For example, filter by “Dataset” + “Free” + “Polygon” to find free datasets on Polygon.
3

Check Asset Details

Review the full asset page before purchasing. Check file samples, publisher history, and edit logs.
4

Bookmark for Later

Save interesting assets to your bookmarks to compare options before making a decision.
5

Review Publisher

Click on publisher addresses to see all their published assets and transaction history.

Advanced Query Syntax

Power users can construct complex queries:
// Query structure
interface SearchQuery {
  query: {
    bool: {
      must: Array<any>     // Required matches
      should: Array<any>   // Optional matches (boost relevance)
      filter: Array<any>   // Filter results (no scoring)
      must_not: Array<any> // Exclude matches
    }
  }
  sort: Array<any>
  from: number  // Pagination offset
  size: number  // Results per page
}

// Example: Find free datasets on Polygon with "agriculture" tag
const query = {
  query: {
    bool: {
      must: [
        { match: { 'metadata.type': 'dataset' } }
      ],
      filter: [
        { term: { chainId: 137 } },
        { term: { 'accessDetails.type': 'free' } },
        { term: { 'metadata.tags.keyword': 'agriculture' } }
      ]
    }
  },
  sort: [{ 'nft.created': 'desc' }],
  from: 0,
  size: 20
}
Direct Elasticsearch queries require advanced knowledge. Use the UI filters for most use cases to ensure valid query syntax.

Performance Optimization

Pagination

Results are paginated to maintain fast load times:
<AssetList
  assets={queryResult?.results}
  showPagination
  page={queryResult?.page}
  totalPages={queryResult?.totalPages}
  onPageChange={(page) => updatePage(page)}
/>

Caching Strategy

Asset metadata is cached by Aquarius to reduce blockchain queries:
  • TTL: 60 seconds for most queries
  • Invalidation: Automatic on asset updates
  • Refresh: Manual refresh available on asset pages

Cancel Tokens

Pending requests are cancelled when new searches start:
const newCancelToken = useCancelToken()

const fetchAssets = async () => {
  const queryResult = await getResults(
    parsed,
    chainIds,
    newCancelToken() // Cancels previous request
  )
}

See Also

Data Marketplace

Learn about the marketplace ecosystem and purchasing

Asset Publishing

Publish your own discoverable assets

Wallet Integration

Connect your wallet to interact with assets

Compute-to-Data

Run algorithms on discovered datasets

Build docs developers (and LLMs) love