Sera Sera for Agents / Docs / Tutorials / Trading dashboard
Tutorial 02

FX trading dashboard.

A live FX dashboard reading Sera's reference rates + multi-source mid + executable depth across 60+ stablecoin pairs. Read-only — no wallet, no signer, no notional commitment. Useful as an internal tool for traders, an external "data product," or a public marketing surface.

30 min Next.js / Vite no wallet find_deals · spread_radar
What you'll have at the end

A web page showing live FX rates for any subset of Sera's pairs, with deviation-from-external-mid in basis points, sortable, refreshing every 60s. Pair this with charting (Lightweight Charts, Recharts) for a full trading dashboard.

Prerequisites

What the dashboard shows

Steps

1Backend route — proxy MCP calls

Browser fetches read-only data from your Node backend. Your backend calls the MCP. Cache responses for 60s — every page-load otherwise costs you a fresh upstream round-trip, and rates don't move that fast anyway.

api/sera-data.ts
import { spawnSeraMcp } from './mcpClient';
const sera = spawnSeraMcp({ env: { SERA_NETWORK: 'mainnet' } });

const cache = new Map();
async function cached(key: string, ttlMs: number, fn: () => Promise<any>) {
  const hit = cache.get(key);
  if (hit && hit.exp > Date.now()) return hit.val;
  const val = await fn();
  cache.set(key, { val, exp: Date.now() + ttlMs });
  return val;
}

export async function GET_grid() {
  return cached('grid', 60_000, () => sera.call('find_deals', { min_bps: 0 }));
}
export async function GET_deals() {
  return cached('deals', 60_000, () => sera.call('find_deals', { min_bps: 25 }));
}
export async function GET_radar(req) {
  const currencies = req.query.basket.split(',');
  return cached(`r:${currencies.join(',')}`, 60_000,
    () => sera.call('spread_radar', { currencies, min_bps: 25 }));
}

2Frontend grid component

Sortable table. Color the deviation column by sign + magnitude. Refresh every 60s.

components/RateGrid.tsx
import { useEffect, useState } from 'react';

export function RateGrid() {
  const [rows, setRows] = useState<any[]>([]);
  useEffect(() => {
    const tick = async () => {
      const r = await fetch('/api/grid').then(r => r.json());
      setRows(r.deals.sort((a, b) => b.edge_bps - a.edge_bps));
    };
    tick();
    const id = setInterval(tick, 60_000);
    return () => clearInterval(id);
  }, []);

  return (
    <table>
      <thead><tr>
        <th>Pair</th><th>Sera</th><th>External mid</th><th>Δ bps</th>
      </tr></thead>
      <tbody>
        {rows.map(r => (
          <tr key={r.pair}>
            <td>{r.pair}</td>
            <td>{r.sera_rate}</td>
            <td>{r.external_mid}</td>
            <td style={{color: r.edge_bps > 0 ? '#3DE8A0' : '#FF6B6B'}}>
              {r.edge_bps.toFixed(1)}
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

3Spread radar tab — triangular arb scanner

async function loadRadar(basket: string[]) {
  const r = await fetch(`/api/radar?basket=${basket.join(',')}`).then(r => r.json());
  return r.triangles;  // [{ a, b, c, edge_bps, direction }, ...]
}

Visualize triangles as arrows on a 2D layout, or just list them sorted by edge_bps. Anything >100 bps is suspicious — verify with a fresh probe.

4(Optional) Add a chart panel

If you set SERA_HISTORY_DB on the MCP, every fx_rate call gets logged. After a few hours of dashboard runtime, you can plot sera.fx_history({ pair, hours }) series with any chart library.

const series = await sera.call('fx_history', { pair: 'USD/JPY', hours: 24 });
// [{ ts, rate }, ...]

5Deploy

This runs anywhere — Vercel, Cloudflare Pages, Railway. The only gotcha is your backend needs to spawn sera-mcp as a long-lived process. On serverless platforms, run sera-mcp as a separate service (Fly.io, Render, or a tiny VPS) and have your serverless functions call it over a private RPC.

Common gotchas

Next: Prediction market on FX uses Sera's reference rate as an oracle, or treasury rebalancer for the same data feeding a real action.