Skip to content

leaderboards.recentEntries()

Get the most recently published entries across all feeds on the platform.

Signature

leaderboards.recentEntries(query?: { 
  page_size?: number; 
  page_token?: string; 
}): Promise<PaginatedResponse<RecentEntry>>

Parameters

query (optional)

{
  page_size?: number;   // Results per page (default: 20)
  page_token?: string;  // Pagination token for next page
}

Returns

  • Type: Promise<PaginatedResponse<RecentEntry>>
interface PaginatedResponse<RecentEntry> {
  data: RecentEntry[];
  next_page_token?: string;
  has_more: boolean;
}
 
interface RecentEntry {
  id: string;
  feed_id: string;
  cid: string;
  mime_type: string;
  title?: string | null;
  description?: string | null;
  metadata?: string | null;
  tags?: string[] | null;
  price: string;
  asset: string;
  is_free: boolean;
  expires_at?: number | null;
  piid?: string | null;
  feed_name: string;
  feed_owner_id: string;
  owner_wallet: string;
  category_name: string;
  created_at: number;
  updated_at: number;
}

Usage

Basic Example

const recent = await grapevine.leaderboards.recentEntries();
 
recent.data.forEach(entry => {
  console.log(`${entry.title || 'Untitled'} in "${entry.feed_name}"`);
  console.log(`  Category: ${entry.category_name}`);
  console.log(`  Price: ${entry.is_free ? 'Free' : formatUSDC(entry.price)}`);
  console.log(`  Posted: ${new Date(entry.created_at * 1000).toLocaleString()}`);
});
 
function formatUSDC(weiAmount: string): string {
  const usdc = Number(BigInt(weiAmount)) / 1e6;
  return `${usdc.toFixed(2)}`;
}

Content Feed

// Get latest 50 entries
const latest = await grapevine.leaderboards.recentEntries({ page_size: 50 });
 
console.log('Latest content:');
latest.data.forEach(entry => {
  const time = new Date(entry.created_at * 1000);
  const timeAgo = getTimeAgo(time);
  
  console.log(`[${timeAgo}] ${entry.title || 'Untitled'}`);
  console.log(`  ${entry.feed_name} • ${entry.category_name}`);
});
 
function getTimeAgo(date: Date): string {
  const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
  if (seconds < 60) return `${seconds}s ago`;
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
  if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
  return `${Math.floor(seconds / 86400)}d ago`;
}

Paginate Through All Recent

async function getAllRecentEntries(limit: number = 100) {
  const entries: RecentEntry[] = [];
  let pageToken: string | undefined;
  
  while (entries.length < limit) {
    const result = await grapevine.leaderboards.recentEntries({
      page_size: Math.min(100, limit - entries.length),
      page_token: pageToken
    });
    
    entries.push(...result.data);
    
    if (!result.has_more) break;
    pageToken = result.next_page_token;
  }
  
  return entries;
}

Filter by Category

async function getRecentByCategory(categoryName: string) {
  const recent = await grapevine.leaderboards.recentEntries({ page_size: 100 });
  
  return recent.data.filter(
    entry => entry.category_name.toLowerCase() === categoryName.toLowerCase()
  );
}
 
// Usage
const techEntries = await getRecentByCategory('Technology');
console.log(`Found ${techEntries.length} recent tech entries`);

Free Content Only

async function getRecentFreeContent() {
  const recent = await grapevine.leaderboards.recentEntries({ page_size: 50 });
  
  return recent.data.filter(entry => entry.is_free);
}

Recent Entries Component

function RecentEntriesFeed() {
  const [entries, setEntries] = useState<RecentEntry[]>([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    grapevine.leaderboards.recentEntries({ page_size: 20 })
      .then(result => {
        setEntries(result.data);
        setLoading(false);
      });
  }, []);
  
  if (loading) return <div>Loading recent content...</div>;
  
  return (
    <div className="entries-feed">
      <h2>Recent Content</h2>
      
      {entries.map(entry => (
        <article key={entry.id} className="entry-card">
          <header>
            <h3>{entry.title || 'Untitled'}</h3>
            <span className="time">{getTimeAgo(entry.created_at)}</span>
          </header>
          
          <p className="meta">
            <span className="feed">{entry.feed_name}</span>
            <span className="category">{entry.category_name}</span>
          </p>
          
          {entry.description && (
            <p className="description">{entry.description}</p>
          )}
          
          <footer>
            <span className="price">
              {entry.is_free ? '🆓 Free' : `💰 ${formatUSDC(entry.price)}`}
            </span>
            <span className="type">{entry.mime_type}</span>
          </footer>
          
          {entry.tags && entry.tags.length > 0 && (
            <div className="tags">
              {entry.tags.map(tag => (
                <span key={tag} className="tag">#{tag}</span>
              ))}
            </div>
          )}
        </article>
      ))}
    </div>
  );
}
 
function getTimeAgo(timestamp: number): string {
  const seconds = Math.floor(Date.now() / 1000 - timestamp);
  if (seconds < 60) return 'Just now';
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
  if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
  return `${Math.floor(seconds / 86400)}d ago`;
}

Content Analytics

async function analyzeRecentContent() {
  const recent = await grapevine.leaderboards.recentEntries({ page_size: 100 });
  
  const entries = recent.data;
  
  // Count by category
  const byCategory = entries.reduce((acc, e) => {
    acc[e.category_name] = (acc[e.category_name] || 0) + 1;
    return acc;
  }, {} as Record<string, number>);
  
  // Count free vs paid
  const freeCount = entries.filter(e => e.is_free).length;
  const paidCount = entries.length - freeCount;
  
  // Count by MIME type
  const byMimeType = entries.reduce((acc, e) => {
    const type = e.mime_type.split('/')[0]; // e.g., "text", "image"
    acc[type] = (acc[type] || 0) + 1;
    return acc;
  }, {} as Record<string, number>);
  
  return {
    total: entries.length,
    byCategory,
    freeCount,
    paidCount,
    freePercentage: `${((freeCount / entries.length) * 100).toFixed(1)}%`,
    byMimeType
  };
}

Notes

  • Authentication: Not required - public endpoint
  • Ordering: Results are ordered by creation time, newest first
  • Pagination: Supports cursor-based pagination for large result sets
  • Includes: Feed and category info for easy display

Related