Start here

Quickstart: React

Wire the sufwan-local-test client, provider, sign-in, and a first trade into a React app

You'll need: an app ID (Base URL, app IDs & API keys), your site's origin in the app's allowed origins, and React 18 or later. Result: a React app with sufwan-local-test data, sign-in, and a buy that is tracked to a final state.

Install the packages you need. See the Packages overview for what each one does and its peer dependencies. Every layer uses the same client from @agg-build/sdk.

1. Create the client and provider

Mount one AggProvider around your app. Use the hooks export for data hooks, or the UI export to include the UI kit's toast support; the UI export already includes the hooks provider. Mount AggAuthProvider inside it for the connect button. The example assumes you have a Wagmi configuration in ./wagmi-config and your application component in MyApp.

import "@agg-build/ui/styles.css";
import { AggProvider, QueryClient, QueryClientProvider } from "@agg-build/hooks";
import { createAggClient } from "@agg-build/sdk";
import { AggAuthProvider, ConnectButton, createGoogleAuthMethod } from "@agg-build/auth";
import { useSiweAuthMethod } from "@agg-build/auth/siwe";
import { WagmiProvider } from "wagmi";
import { wagmiConfig } from "./wagmi-config";

const client = createAggClient({
  baseUrl: "https://api.t.sufwankhalid.com",
  appId: "your-app-id",
  wsUrl: "wss://ws.t.sufwankhalid.com/ws",
});

const queryClient = new QueryClient();

function AuthButton() {
  const siwe = useSiweAuthMethod({ statement: "Sign in" });
  return (
    <AggAuthProvider methods={[siwe, createGoogleAuthMethod()]}>
      <ConnectButton />
    </AggAuthProvider>
  );
}

function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <QueryClientProvider client={queryClient}>
        <AggProvider
          client={client}
          config={{
            general: { locale: "en-US", theme: "light" },
            features: { enableAnimations: true, enableLiveUpdates: true },
          }}
        >
          <AuthButton />
          <MyApp />
        </AggProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}

For the UI kit's toasts, import AggProvider from @agg-build/ui instead of from @agg-build/hooks. Keep the same client and config props. That component already mounts the hooks provider.

UI components go inside MyApp:

import { EventMarketPage } from "@agg-build/ui/pages";
import { MarketDetails } from "@agg-build/ui/events";
import { LineChart } from "@agg-build/ui/primitives";

// Full event page: hero chart, market cards, orderbooks, all live
<EventMarketPage eventId="..." />

// Or individual components
<MarketDetails event={event} marketId={marketId} defaultTab="graph" />
<LineChart series={series} chartType="candlestick" height={320} live />

See the Event Market Page and Market Details references, and Customize UI for fonts, colors, copy, and slots.

Use outcome IDs for trading, charts, orderbooks, and live trades. Arbitrage and midpoint reads use market IDs. Some hook parameter names retain older naming; the table below lists the ID each one expects.

import { useLiveMarket, useMarketArb, useMarketChart, useSmartRoute } from "@agg-build/hooks";

function MarketView({
  venueMarketOutcomeId,
  venueMarketId,
}: {
  venueMarketOutcomeId: string;
  venueMarketId: string;
}) {
  const { orderbook } = useLiveMarket(venueMarketOutcomeId);
  const { data: chart } = useMarketChart({
    marketId: venueMarketOutcomeId,
    interval: "5m",
    startTs: Date.now() - 86_400_000,
    endTs: Date.now(),
  });
  const { data: route } = useSmartRoute({
    venueMarketOutcomeId,
    tradeSide: "buy",
    maxSpend: 50,
  });
  const { arbReturn } = useMarketArb(venueMarketId);

  const primaryVenue = chart?.primaryVenue;
  const candles = primaryVenue ? chart.venues[primaryVenue]?.candles ?? [] : [];

  return <YourChart data={candles} orderbook={orderbook} route={route} arbReturn={arbReturn} />;
}

No session means a preview quote. See Quotes.

HookId it actually reads
useLiveMarket(canonicalMarketId)venueMarketOutcomeId. Not a venueMarketId.
useMarketOrderbook({ venueMarketOutcomeId })Outcome id. marketId is a deprecated alias for that same outcome id.
useMarketChart({ marketId, venueMarketIds })Both accept outcome IDs: pass VenueMarketOutcome.id values.
useLiveTrades(canonicalMarketId)Outcome id, because it calls useLiveMarket.
useSmartRoute({ venueMarketOutcomeId, tradeSide, maxSpend/sellShares })Outcome id.
useMarketArb(marketId)venueMarketId. Midpoints and arb use venueMarketId.
useArbFeed()Live arbitrage returns for many markets.
useCatalogFeed({ since, onChange })No id. Keeps your copy of events and markets in sync; returns { status, lastChangedAt, error, resync }. See Catalog Sync.

2. Sign the user in

AuthButton above lets the user sign in through @agg-build/auth. Trading calls use that user's session through the shared client.

See the Connect Button reference and Authentication & sessions for every provider.

3. Buy and track to a final state

The user needs a balance first. See Funding for deposits. No session means a preview quote. See Quotes. Then quote, fill, and poll the execution status until terminal is true.

import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useAggClient, useExecuteManaged, useSmartRoute } from "@agg-build/hooks";

function BuyButton({ venueMarketOutcomeId }: { venueMarketOutcomeId: string }) {
  const client = useAggClient();
  const [quoteId, setQuoteId] = useState<string | null>(null);

  const { data: quote } = useSmartRoute({ venueMarketOutcomeId, tradeSide: "buy", maxSpend: 5 });
  const execute = useExecuteManaged({ onSuccess: (fill) => setQuoteId(fill.quoteId) });

  const { data: status } = useQuery({
    queryKey: ["execution-status", quoteId],
    queryFn: () => client.getExecutionStatus({ quoteId: quoteId! }),
    enabled: !!quoteId,
    // Poll at the interval the API suggests; stop once the trade is terminal.
    refetchInterval: (query) =>
      query.state.data?.terminal ? false : (query.state.data?.pollAfterMs ?? 1000),
  });

  if (status?.terminal) return <p>Done: {status.overallState}</p>;
  if (quoteId) return <p>Executing: {status?.overallState ?? "created"}</p>;

  return (
    <button
      className="cursor-pointer disabled:cursor-not-allowed"
      disabled={quote?.status !== "ok" || execute.isPending}
      onClick={() => quote && execute.mutate({ quoteId: quote.quoteId })}
    >
      Buy $5
    </button>
  );
}

Stop polling when terminal is true. overallState then reads filled, partially_filled, failed, cancelled, or expired. Read Order lifecycle & statuses for what each state means.

WebSocket endpoint

The SDK and hooks manage the socket, reconnection, resnapshots, and orderbook integrity checks. If you open raw sockets yourself, connect to:

wss://ws.t.sufwankhalid.com/ws?appId=YOUR_APP_ID

See WebSocket Protocol.

Next steps

On this page