Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.agg.market/llms.txt

Use this file to discover all available pages before exploring further.

Start with the Setup Guide if you still need to wire createAggClient, AggProvider, or AggAuthProvider. After sign-in, see Token Refresh for session renewal, Account Linking for adding additional providers, and Partner External ID Linking if you need to attach your own internal user ID to the AGG profile.
AGG supports six authentication providers:
  • siwe for Ethereum wallets
  • siws for Solana wallets
  • google for Google OAuth
  • twitter for X OAuth
  • apple for Apple Sign In
  • email for magic-link email sign-in

How It Works

All flows start with client.authStart(...), but they complete in two different ways:
FlowProvidersCompletion
Message signingsiwe, siwsCall client.verify({ message, signature }) to receive tokens directly.
Redirect / magic linkgoogle, twitter, apple, emailAGG redirects back with ?code=...; call client.exchangeAuthCode(code).

Bot Protection (challenge_required)

For wallet (siwe, siws) and email (email) providers, AGG enforces an optional Cloudflare Turnstile challenge when your app crosses its configured user-count threshold. OAuth providers (google, twitter, apple) bypass this layer — the identity provider is already the human proof. When the challenge is active, POST /auth/start may return an extra response type:
{ "type": "challenge_required", "siteKey": "0x4AAAAAAA..." }
Your flow becomes:
  1. Call client.authStart({ provider: "siwe" }).
  2. If the response type is challenge_required, render the Cloudflare Turnstile widget using the returned siteKey.
  3. On solve, resubmit client.authStart({ provider: "siwe", turnstileToken: "<cf-token>" }).
  4. The server verifies the token against its linked widget and returns the normal nonce / magic_link response.
  5. An invalid or reused token returns 403 — retry with a fresh Turnstile solve.
const start = await client.authStart({ provider: "siwe" });

if (start.type === "challenge_required") {
  const turnstileToken = await renderTurnstileWidget(start.siteKey); // your UI helper
  const retried = await client.authStart({ provider: "siwe", turnstileToken });
  // retried.type === "nonce" — continue the SIWE flow below
}

SDK: SIWE

SIWE is fully client-side and does not redirect.
import { createAggClient } from "@agg-build/sdk";

const client = createAggClient({
  baseUrl: "https://api.agg.market",
  appId: "your-app-id",
});

const { nonce } = await client.authStart({ provider: "siwe" });

const message = client.buildSiweMessage({
  address: walletAddress,
  chainId: 1,
  nonce,
  domain: window.location.host,
  uri: window.location.origin,
});

const signature = await signMessage({ message }); // wagmi, viem, ethers, etc.
const { accessToken, refreshToken, user } = await client.verify({ message, signature });
If your browser app wants the refresh token set as an HttpOnly cookie instead, configure the SDK client with authDelivery: "cookie-refresh". In that mode, verify() and exchangeAuthCode() may omit refreshToken from the JSON response. That cookie stays on the AGG API host and is scoped to /auth routes.

SDK: SIWS

SIWS is also fully client-side and does not redirect.
import bs58 from "bs58";
import { createAggClient } from "@agg-build/sdk";

const client = createAggClient({
  baseUrl: "https://api.agg.market",
  appId: "your-app-id",
});

const { nonce } = await client.authStart({ provider: "siws" });

const message = client.buildSiwsMessage({
  address: walletPublicKey.toBase58(),
  chainId: "mainnet",
  nonce,
  domain: window.location.host,
  uri: window.location.origin,
});

const signedBytes = await wallet.signMessage(new TextEncoder().encode(message));
const signature = bs58.encode(signedBytes);
const { accessToken, refreshToken, user } = await client.verify({ message, signature });

SIWS with Ledger

Ledger is not a separate AGG auth flow. It uses the exact same SIWS contract:
  1. client.authStart({ provider: "siws" })
  2. client.buildSiwsMessage(...)
  3. wallet.signMessage(new TextEncoder().encode(message))
  4. bs58.encode(signature)
  5. client.verify({ message, signature })
This matters because Ledger supports signMessage() but not Phantom’s wallet-specific signIn() helper. AGG’s SIWS recipe stays Ledger-compatible by building the message in the dapp and signing the raw UTF-8 bytes directly.

SDK: Google / Twitter / Apple / Email

These four providers all use the same redirect-based flow.
const response = await client.authStart({
  provider: "google",
  redirectUrl: "https://yourapp.com/auth/callback",
});

if (response.type === "redirect") {
  window.location.assign(response.url);
}
After the user authenticates, AGG redirects back with a one-time auth code:
https://yourapp.com/auth/callback?code=a1b2c3d4...
Exchange that code for tokens on the redirect target page:
async function handleAuthRedirect() {
  const params = new URLSearchParams(window.location.search);
  const code = params.get("code");
  if (!code) return;

  const { accessToken, refreshToken, user } = await client.exchangeAuthCode(code);

  window.history.replaceState(null, "", window.location.pathname);
}

handleAuthRedirect();
The auth code is single-use and expires quickly, so exchange it immediately. Use @agg-build/auth when you want AGG’s connect/sign-in UI without forcing wallet dependencies into @agg-build/ui. See the Connect Button reference for the component surface shown below.

Mixed providers

import { AggProvider } from "@agg-build/hooks";
import { createAggClient } from "@agg-build/sdk";
import {
  AggAuthProvider,
  ConnectButton,
  createEmailAuthMethod,
  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.agg.market",
  appId: "your-app-id",
});

function AuthButton() {
  const siwe = useSiweAuthMethod({
    statement: "Sign in to AGG",
  });

  return (
    <AggAuthProvider methods={[siwe, createGoogleAuthMethod(), createEmailAuthMethod()]}>
      <ConnectButton />
    </AggAuthProvider>
  );
}

export function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <AggProvider client={client}>
        <AuthButton />
      </AggProvider>
    </WagmiProvider>
  );
}
Browse the live Connect Button reference.

Dedicated callback pages

If your app uses a standalone callback route, use useAggAuthCallback():
import { useAggAuthCallback } from "@agg-build/auth";

export function AuthCallbackPage() {
  const { error, isHandled, isHandling, user } = useAggAuthCallback();

  if (isHandling) return <p>Finishing sign-in...</p>;
  if (error) return <p>{error.message}</p>;
  if (isHandled) return <p>Signed in as {user?.id}</p>;

  return <p>No AGG auth callback data was found.</p>;
}

React: useAggAuth

useAggAuth keeps the documented wallet ergonomics for custom UIs.

Ethereum

import { useAggAuth } from "@agg-build/hooks";
import { useAccount, useSignMessage } from "wagmi";

function SignIn() {
  const { address, chainId } = useAccount();
  const { signMessageAsync } = useSignMessage();
  const { signIn, signOut, isAuthenticated, user, isLoading } = useAggAuth({
    address,
    chainId,
    signMessage: async (message) => signMessageAsync({ message }),
  });

  if (isAuthenticated) {
    return (
      <div>
        <p>Signed in as {user.id}</p>
        <button onClick={signOut}>Sign Out</button>
      </div>
    );
  }

  return (
    <button onClick={() => signIn("Sign in to AGG")} disabled={isLoading}>
      Sign in with Ethereum
    </button>
  );
}

Solana

import bs58 from "bs58";
import { useWallet } from "@solana/wallet-adapter-react";
import { useAggAuth } from "@agg-build/hooks";

function SolanaSignIn() {
  const { publicKey, signMessage } = useWallet();
  const { signIn, isLoading } = useAggAuth({
    address: publicKey?.toBase58(),
    chain: "solana",
    chainId: "mainnet",
    signMessage: async (message) => {
      const signedBytes = await signMessage!(new TextEncoder().encode(message));
      return bs58.encode(signedBytes);
    },
  });

  return (
    <button onClick={() => signIn("Sign in to AGG")} disabled={isLoading}>
      Sign in with Solana
    </button>
  );
}

Token storage

The SDK automatically persists auth tokens in localStorage, keyed by appId, and restores them when the client is constructed.

Installation matrix

# Base React packages
npm install @agg-build/sdk @agg-build/hooks @agg-build/ui @agg-build/auth

# Add only the providers you use
npm install wagmi
npm install @solana/wallet-adapter-react bs58

Setup Guide

Wire the base client, providers, and WebSocket connection first.

Token Refresh

Renew access tokens and recover gracefully from session expiry.

Account Linking

Connect additional OAuth providers to the current user profile.

User Notifications

Reuse the same session for authenticated WebSocket events.

Bot Protection

Render and verify Cloudflare Turnstile challenges during sign-in.