Fetching News
This guide shows you how to fetch news articles from TUNA’s on-chain registry and retrieve their content from Walrus.Complete Service Implementation
Here’s a production-ready service class that handles fetching news from TUNA:tunaService.ts
import { SuiClient } from '@mysten/sui.js/client';
// Configuration
const CONFIG = {
// Sui Testnet RPC
RPC: 'https://fullnode.testnet.sui.io:443',
// The TUNA Registry Object
REGISTRY_ID: '0x68c01d2c08923d5257a5a9959d7c9250c4053dbe4641e229ccff2f35e6a3bb6d',
// Walrus Aggregator (Public Gateway)
WALRUS_GATEWAY: 'https://aggregator.walrus-testnet.walrus.space/v1',
};
// Data Types
export interface TunaArticle {
title: string;
summary: string;
content: string; // HTML string
source: string; // e.g., "Sui Blog", "@SuiNetwork"
url: string; // Original link
image?: string; // Cover image URL
timestamp: number;
author?: string;
}
export class TunaService {
private client: SuiClient;
constructor() {
this.client = new SuiClient({ url: CONFIG.RPC });
}
/**
* Fetches the latest N articles from the TUNA protocol.
*/
async getLatestNews(limit: number = 20): Promise<TunaArticle[]> {
try {
// 1. Fetch the Registry Object from Sui
// We request the 'showContent' option to see the fields inside.
const registry = await this.client.getObject({
id: CONFIG.REGISTRY_ID,
options: { showContent: true }
});
if (!registry.data?.content || registry.data.content.dataType !== 'moveObject') {
throw new Error('Failed to fetch valid Registry object from Sui');
}
// 2. Extract the list of Blob IDs from the Move struct
// The contract field is named 'latest_blobs'
const fields = registry.data.content.fields as any;
const allBlobIds = fields.latest_blobs as string[];
console.log(`TUNA Registry contains ${allBlobIds.length} total articles.`);
// 3. Get the most recent IDs
// The contract appends new items to the end.
// So we slice from the end (-limit) and reverse to get Newest First.
const recentBlobIds = allBlobIds.slice(-limit).reverse();
// 4. Fetch the actual content from Walrus in parallel
const articles = await Promise.all(
recentBlobIds.map(id => this.fetchFromWalrus(id))
);
// Filter out any failed fetches (nulls)
return articles.filter(a => a !== null) as TunaArticle[];
} catch (error) {
console.error('Error fetching TUNA news:', error);
return [];
}
}
/**
* Helper to fetch a single blob from Walrus
*/
private async fetchFromWalrus(blobId: string): Promise<TunaArticle | null> {
try {
const response = await fetch(`${CONFIG.WALRUS_GATEWAY}/${blobId}`);
if (!response.ok) {
console.warn(`Walrus fetch failed for ${blobId}: ${response.status}`);
return null;
}
const data = await response.json();
return data as TunaArticle;
} catch (error) {
console.warn(`Failed to parse article ${blobId}`, error);
return null;
}
}
}
Basic Usage
Here’s how to use the service in your application:import { TunaService } from './tunaService';
const newsFeed = new TunaService();
async function displayNews() {
console.log("Fetching latest news...");
const articles = await newsFeed.getLatestNews(5);
articles.forEach(article => {
console.log(`[${article.source}] ${article.title}`);
console.log(`Link: ${article.url}\n`);
});
}
displayNews();
Advanced: React Hook Implementation
For React applications, here’s a hook that uses React Query for caching and automatic refetching:hooks/useNews.ts
import { useQuery } from '@tanstack/react-query';
import { SuiClient } from '@mysten/sui.js/client';
import { CONTRACT_CONFIG, NETWORK_CONFIG } from '../config';
const suiClient = new SuiClient({ url: NETWORK_CONFIG.RPC_URL });
/**
* Fetch latest news articles from the registry
*/
export function useLatestNews(limit: number = 100) {
return useQuery({
queryKey: ['latestNews', limit],
queryFn: async (): Promise<NewsArticle[]> => {
// Get the registry object
const registry = await suiClient.getObject({
id: CONTRACT_CONFIG.REGISTRY_ID,
options: { showContent: true },
});
if (!registry.data?.content || registry.data.content.dataType !== 'moveObject') {
throw new Error('Invalid registry object');
}
const fields = registry.data.content.fields as any;
const latestBlobs = fields.latest_blobs as string[];
// Take only the requested number of articles (NEWEST FIRST)
// Slice from the end (-limit) to get the latest, then reverse to show newest first
const blobsToFetch = latestBlobs.slice(-limit).reverse();
// Fetch content from Walrus for each article
const articles = await Promise.all(
blobsToFetch.map(async (blobId) => {
try {
const walrusContent = await fetchFromWalrus(blobId);
return {
id: blobId,
blob_id: blobId,
...walrusContent,
};
} catch (error) {
console.error(`Error fetching article ${blobId}:`, error);
return null;
}
})
);
return articles.filter(article => article !== null);
},
staleTime: 30000, // 30 seconds
});
}
async function fetchFromWalrus(blobId: string) {
const response = await fetch(
`https://aggregator.walrus-testnet.walrus.space/v1/${blobId}`
);
if (!response.ok) {
throw new Error(`Failed to fetch blob ${blobId}`);
}
return await response.json();
}
import { useLatestNews } from './hooks/useNews';
function NewsFeed() {
const { data: articles, isLoading, error } = useLatestNews(20);
if (isLoading) return <div>Loading news...</div>;
if (error) return <div>Error loading news</div>;
return (
<div>
{articles?.map(article => (
<article key={article.id}>
<h2>{article.title}</h2>
<p>{article.summary}</p>
<a href={article.url}>Read more</a>
</article>
))}
</div>
);
}
Fetching a Single Article
To fetch a specific article by its blob ID:import { fetchFromWalrus } from './lib/walrus';
async function getArticle(blobId: string) {
try {
const article = await fetchFromWalrus(blobId);
return article;
} catch (error) {
console.error('Failed to fetch article:', error);
return null;
}
}
// Usage
const article = await getArticle('your-blob-id-here');
Error Handling
Always implement proper error handling when fetching from TUNA and Walrus.
1. Validate Registry Response
if (!registry.data?.content || registry.data.content.dataType !== 'moveObject') {
throw new Error('Failed to fetch valid Registry object from Sui');
}
2. Handle Walrus Fetch Failures
if (!response.ok) {
console.warn(`Walrus fetch failed for ${blobId}: ${response.status}`);
return null;
}
3. Filter Failed Fetches
const articles = await Promise.all(
recentBlobIds.map(id => this.fetchFromWalrus(id))
);
// Filter out any failed fetches (nulls)
return articles.filter(a => a !== null) as TunaArticle[];
Fallback Aggregators
For production applications, implement fallback to multiple Walrus aggregators:lib/walrus.ts
const WALRUS_AGGREGATORS = [
'https://walrus-testnet-aggregator.nodes.guru/v1',
'https://walrus-testnet-aggregator.stakely.io/v1',
'https://aggregator.walrus-testnet.walrus.space/v1',
'https://walrus-testnet-aggregator.everstake.one/v1',
];
export async function fetchFromWalrus(blobId: string) {
// Validate blob ID format
if (!blobId || blobId.length < 30) {
throw new Error(`Invalid blob ID: ${blobId}`);
}
// Try each aggregator
for (const aggregatorBase of WALRUS_AGGREGATORS) {
try {
const response = await fetch(`${aggregatorBase}/${blobId}`);
if (response.ok) {
return await response.json();
}
} catch (error) {
// Continue to next aggregator
continue;
}
}
throw new Error(`All aggregators failed for blob ${blobId}`);
}
Performance Tips
Optimize your integration for better performance and user experience.
1. Implement Caching
Cache article data to reduce network requests:const articleCache = new Map<string, TunaArticle>();
async function getCachedArticle(blobId: string): Promise<TunaArticle | null> {
if (articleCache.has(blobId)) {
return articleCache.get(blobId)!;
}
const article = await fetchFromWalrus(blobId);
if (article) {
articleCache.set(blobId, article);
}
return article;
}
2. Paginate Results
Fetch articles in chunks for better loading times:async function getPaginatedNews(page: number, pageSize: number = 20) {
const offset = page * pageSize;
return await newsFeed.getLatestNews(offset + pageSize);
}
3. Use Parallel Fetching
The implementation already usesPromise.all() to fetch multiple articles in parallel, which significantly improves performance.
Next Steps
Now that you can fetch news, learn how to interact with articles:Transactions
Build transactions to tip articles and post comments
