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.
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:
Flow Providers Completion Message signing siwe, siwsCall client.verify({ message, signature }) to receive tokens directly. Redirect / magic link google, 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:
Call client.authStart({ provider: "siwe" }).
If the response type is challenge_required, render the Cloudflare Turnstile widget using the
returned siteKey.
On solve, resubmit client.authStart({ provider: "siwe", turnstileToken: "<cf-token>" }).
The server verifies the token against its linked widget and returns the normal nonce /
magic_link response.
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:
client.authStart({ provider: "siws" })
client.buildSiwsMessage(...)
wallet.signMessage(new TextEncoder().encode(message))
bs58.encode(signature)
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.
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 );
}
const response = await client . authStart ({
provider: "apple" ,
redirectUrl: "https://yourapp.com/auth/callback" ,
});
if ( response . type === "redirect" ) {
window . location . assign ( response . url );
}
await client . authStart ({
provider: "email" ,
email: "user@example.com" ,
redirectUrl: "https://yourapp.com/auth/callback" ,
});
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.
React: recommended @agg-build/auth
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.