Sera Sera for Agents / Docs / Tutorials / Payment widget
Tutorial 01

Cross-border payment widget.

A drop-in React component. User picks recipient + amount in their currency, widget fetches the cheapest source asset, generates a quote, and triggers wallet signing. Settles on Sera in one transaction.

25 min React 18+ wagmi sera.pay_invoice
What you'll have at the end

A working <SeraPayWidget /> React component. Drop it on a page, pass recipient + amount + currency as props, and your users can settle international payments at on-chain cost (~$2 instead of $80+ via SWIFT).

Prerequisites

Architecture

Browser (React)              Your Node API              sera-mcp           Sera Protocol
     │                            │                         │                    │
     ├── POST /api/quote ─────────▶│                         │                    │
     │                            ├── sera.pay_invoice ─────▶│                    │
     │                            │                         ├── /quote ─────────▶│
     │                            │                         │◀── typed_data ─────┤
     │                            │◀── quote {uuid, ...} ────│                    │
     │◀── { quote, typed_data } ──┤                         │                    │
     │                            │                         │                    │
     ├── wallet.signTypedData ────▶│  (browser-only, key never leaves the wallet) │
     │                            │                         │                    │
     ├── POST /api/execute ───────▶│                         │                    │
     │   (uuid + signature)       ├── sera.execute_swap ────▶│                    │
     │                            │                         ├── /execute ───────▶│
     │                            │                         │◀── trade_id ───────┤
     │◀── { trade_id, tx_hash } ───┤                         │                    │

The browser never talks to sera-mcp directly — your backend brokers the calls. The wallet signs in the browser. The MCP never sees the key.

Steps

1Set up the backend route

Spawn sera-mcp as a subprocess of your Node server (or import its tool functions directly if you're embedding). Expose two routes: /api/quote and /api/execute.

api/sera.ts
// Pseudocode — wire to your framework's router (Next.js, Express, etc.)
import { spawnSeraMcp } from './mcpClient';
const sera = spawnSeraMcp({ env: { SERA_NETWORK: 'mainnet', POLICY_PRESET: 'standard' } });

export async function POST_quote(req) {
  const { recipient, target_amount, target_currency } = req.body;
  return await sera.call('pay_invoice', {
    recipient, target_amount, target_currency
  });
}

export async function POST_execute(req) {
  const { quote_id, signature } = req.body;
  return await sera.call('execute_swap', { quote_id, signature });
}

2Create the widget component

A single React component that handles the four states: idle, quoting, signing, executing.

components/SeraPayWidget.tsx
import { useState } from 'react';
import { useSignTypedData } from 'wagmi';

type Props = { recipient: string; amount: number; currency: string };

export function SeraPayWidget({ recipient, amount, currency }: Props) {
  const [state, setState] = useState<'idle'|'quoting'|'signing'|'executing'|'done'>('idle');
  const [quote, setQuote] = useState<any>(null);
  const [tradeId, setTradeId] = useState<string|null>(null);
  const { signTypedDataAsync } = useSignTypedData();

  async function pay() {
    setState('quoting');
    const q = await fetch('/api/quote', {
      method: 'POST',
      body: JSON.stringify({ recipient, target_amount: amount, target_currency: currency })
    }).then(r => r.json());
    setQuote(q);

    setState('signing');
    const sig = await signTypedDataAsync(q.typed_data);

    setState('executing');
    const r = await fetch('/api/execute', {
      method: 'POST',
      body: JSON.stringify({ quote_id: q.uuid, signature: sig })
    }).then(r => r.json());
    setTradeId(r.trade_id);
    setState('done');
  }

  return (
    <div className="sera-widget">
      {state === 'idle' && (
        <button onClick={pay}>
          Send {amount} {currency} to {recipient.slice(0,6)}…
        </button>
      )}
      {state === 'quoting' && 'Fetching cheapest source…'}
      {state === 'signing' && 'Sign in your wallet'}
      {state === 'executing' && 'Settling on Sera…'}
      {state === 'done' && (
        <span>Done. trade_id: <code>{tradeId}</code></span>
      )}
    </div>
  );
}

3Use it in a page

pages/checkout.tsx
import { SeraPayWidget } from '@/components/SeraPayWidget';

export default function Checkout() {
  return (
    <div>
      <h1>Pay vendor in Malaysia</h1>
      <SeraPayWidget
        recipient="0xAa…vendor"
        amount={5000}
        currency="MYR"
      />
    </div>
  );
}

4Test in dry-run mode first

Set SERA_DRY_RUN=true in your backend env. Every execute_swap call returns a dry-run trace instead of an on-chain transaction. Run the full flow end-to-end without spending anything.

Once green, flip SERA_DRY_RUN=false and run a real low-notional ($10) settlement to verify on-chain.

5Show the user the cost saving (the marketing piece)

The widget should display the cost. Use sera.compare_to_external_fx to fetch a comparable TradFi rate and show a "$87 → $2.50" callout. This is what makes users understand they're getting a genuinely better deal.

const ext = await fetch('/api/compare?base=USD&quote=MYR').then(r => r.json());
// ext.swift_estimate_usd vs quote.cost_usd

Reference repo

templates/web-chat in the sera-agents repo is a working starter that includes a wallet connector, a simple form, and the same backend ↔ MCP wiring. Fork it for the fastest path.

Common gotchas

Next: Try the FX trading dashboard tutorial for read-only Sera consumption, or the x402-paid API tutorial if you'd rather skip MCP entirely.