API & Integration

Integration Guide

Practical examples for integrating pRPC and building custom features

Building a Custom Dashboard

Create a custom dashboard showing specific metrics:

'use client';

import { useAllPNodes, useNetworkStats } from '@/lib/hooks';
import { Card } from '@/components/ui/card';

export function CustomDashboard() {
  const { data: nodes } = useAllPNodes();
  const { data: stats } = useNetworkStats();

  // Filter high-performance nodes
  const topNodes = nodes
    ?.filter(n => n.healthScore > 90)
    .slice(0, 10);

  // Calculate custom metric
  const avgStorageUsage = nodes?.reduce(
    (acc, node) => acc + node.storage.usagePercentage, 0
  ) / (nodes?.length || 1);

  return (
    <div>
      <h1>My Custom Dashboard</h1>

      <Card>
        <h2>Top 10 Nodes (Health &gt; 90)</h2>
        {topNodes?.map(node => (
          <div key={node.publicKey}>
            {node.moniker}: {node.healthScore}
          </div>
        ))}
      </Card>

      <Card>
        <h2>Network Metrics</h2>
        <p>Total Nodes: {stats?.totalNodes}</p>
        <p>Avg Storage Usage: {avgStorageUsage.toFixed(1)}%</p>
      </Card>
    </div>
  );
}

Filtering and Searching Nodes

import { useAllPNodes } from '@/lib/hooks';
import { useState, useMemo } from 'react';

export function NodeSearch() {
  const { data: nodes } = useAllPNodes();
  const [search, setSearch] = useState('');
  const [minHealth, setMinHealth] = useState(0);

  // Filter and sort nodes
  const filteredNodes = useMemo(() => {
    if (!nodes) return [];

    return nodes
      .filter(node =>
        node.moniker.toLowerCase().includes(search.toLowerCase()) &&
        node.healthScore >= minHealth
      )
      .sort((a, b) => b.healthScore - a.healthScore);
  }, [nodes, search, minHealth]);

  return (
    <div>
      <input
        type="text"
        placeholder="Search by moniker..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      <input
        type="range"
        min="0"
        max="100"
        value={minHealth}
        onChange={(e) => setMinHealth(Number(e.target.value))}
      />
      <span>Min Health: {minHealth}</span>

      <div>
        Found {filteredNodes.length} nodes
      </div>
    </div>
  );
}

Building Custom Analytics

import { useAllPNodes } from '@/lib/hooks';
import { useMemo } from 'react';

export function GeoAnalytics() {
  const { data: nodes } = useAllPNodes();

  const analytics = useMemo(() => {
    if (!nodes) return null;

    // Group by country
    const byCountry = nodes.reduce((acc, node) => {
      const country = node.location.country;
      if (!acc[country]) {
        acc[country] = {
          count: 0,
          totalStorage: 0,
          avgHealth: 0,
        };
      }
      acc[country].count++;
      acc[country].totalStorage += node.storage.total;
      acc[country].avgHealth += node.healthScore;
      return acc;
    }, {} as Record<string, any>);

    // Calculate averages
    Object.values(byCountry).forEach((data: any) => {
      data.avgHealth /= data.count;
    });

    // Sort by count
    const sorted = Object.entries(byCountry)
      .sort(([, a]: any, [, b]: any) => b.count - a.count);

    return sorted;
  }, [nodes]);

  return (
    <div>
      <h2>Nodes by Country</h2>
      {analytics?.map(([country, data]: [string, any]) => (
        <div key={country}>
          <strong>{country}</strong>: {data.count} nodes,
          Avg Health: {data.avgHealth.toFixed(1)}
        </div>
      ))}
    </div>
  );
}

Real-time Monitoring

Set up monitoring with custom refresh intervals:

import { useAllPNodes } from '@/lib/hooks';
import { useEffect } from 'react';

export function RealTimeMonitor() {
  const { data: nodes, refetch } = useAllPNodes();

  // Custom refresh interval (30 seconds)
  useEffect(() => {
    const interval = setInterval(() => {
      refetch();
    }, 30000);

    return () => clearInterval(interval);
  }, [refetch]);

  // Monitor for critical nodes
  const criticalNodes = nodes?.filter(node =>
    node.uptime < 95 ||
    node.storage.usagePercentage > 90 ||
    node.performance.avgLatency > 200
  );

  return (
    <div>
      <h2>Critical Nodes Alert</h2>
      {criticalNodes?.length === 0 ? (
        <p>All nodes healthy</p>
      ) : (
        criticalNodes?.map(node => (
          <div key={node.publicKey} className="alert">
            {node.moniker}:
            {node.uptime < 95 && ' Low uptime'}
            {node.storage.usagePercentage > 90 && ' High storage'}
            {node.performance.avgLatency > 200 && ' High latency'}
          </div>
        ))
      )}
    </div>
  );
}

Custom Data Export

import { useAllPNodes } from '@/lib/hooks';

export function CustomExport() {
  const { data: nodes } = useAllPNodes();

  const exportFilteredNodes = () => {
    // Filter nodes
    const filtered = nodes?.filter(n => n.healthScore > 80);

    // Create custom CSV
    const headers = ['Moniker', 'Country', 'Health', 'Uptime'];
    const rows = filtered?.map(n => [
      n.moniker,
      n.location.country,
      n.healthScore,
      n.uptime,
    ]);

    const csv = [
      headers.join(','),
      ...rows.map(row => row.join(',')),
    ].join('\n');

    // Download
    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'high-health-nodes.csv';
    a.click();
  };

  return (
    <button onClick={exportFilteredNodes}>
      Export High Health Nodes
    </button>
  );
}

Building Alerts

import { useAllPNodes } from '@/lib/hooks';
import { useEffect } from 'react';

export function NodeAlerts() {
  const { data: nodes } = useAllPNodes();

  useEffect(() => {
    if (!nodes) return;

    // Check for nodes with issues
    nodes.forEach(node => {
      if (node.uptime < 90) {
        console.warn(`Low uptime alert: ${node.moniker}`);
        // Could send notification here
      }

      if (node.storage.usagePercentage > 95) {
        console.error(`Storage critical: ${node.moniker}`);
        // Could trigger email/webhook
      }
    });
  }, [nodes]);

  return <div>Monitoring {nodes?.length} nodes...</div>;
}

Integration with External APIs

Combine XandScan data with external services:

import { useAllPNodes } from '@/lib/hooks';

export async function enrichNodeData(publicKey: string) {
  // Get node from XandScan
  const client = getPNodeClient();
  const node = await client.getPNodeDetails(publicKey);

  // Enrich with external data
  const geoData = await fetch(
    `https://api.ipgeolocation.io/ipgeo?ip=${node.ipAddress}`
  ).then(r => r.json());

  return {
    ...node,
    enriched: {
      isp: geoData.isp,
      continent: geoData.continent_name,
      currency: geoData.currency,
    },
  };
}

Best Practices

  • • Use React Query hooks instead of direct PNodeClient calls
  • • Leverage useMemo for expensive calculations
  • • Respect the 60s cache - don't over-refetch
  • • Handle loading and error states properly
  • • Use TypeScript for type safety
  • • Test with real pNode data before deploying

Next Steps