Verified against @dexterai/x402 5.3.1 · 2026-07-07
Sign in with Dexter
By the end of this page you will have a working "Sign in with Dexter" button, know the exact shape of the { session, vault } it hands back, and know every --dx-* CSS variable you can set to theme it.
The button runs a passkey ceremony. The user taps their face or fingerprint. Your app gets back an auth session plus their Dexter Wallet. The user keeps their own keys. This component is the front door; the vault engine is what sits behind it.
Install
@dexterai/vault is a required peer. @solana/web3.js is an optional peer, needed only when you drive the passkey signer to open tabs.
The React surface lives at @dexterai/connect/react. The framework-free core (passkeyLogin, createWallet, the wallet store) lives at @dexterai/connect.
The button
One component, two states. Signed out, it renders the branded button. Signed in, it becomes a compact chip showing the Dexter Wallet address and the USD balance. That swap is built in — you do not render the chip yourself. Set showConnectedChip={false} if you want to render your own connected state instead.
What onSuccess hands you
onSuccess fires with a SignInResult:
vault is optional in the type. Treat it as such — guard on it before reading vault fields. If you only need to seat an auth session, session alone is enough.
session (PasskeyLoginTokens) is the auth payload, camelCase:
| Field | Type | Meaning |
|---|---|---|
accessToken | string | Bearer for your API calls |
refreshToken | string | Refresh token |
expiresAt | number | Expiry, unix seconds |
expiresIn | number | Lifetime, seconds |
tokenType | string | Token type |
vault (ConnectVault) is the Dexter Wallet:
| Field | Type | Meaning |
|---|---|---|
swigAddress | string | The user-facing Dexter Wallet address (base58) |
vaultPda | string | Vault PDA (base58) — needed to open tabs |
receiveAddress | string | null | Deposit address; null until the wallet is deployed on-chain |
usdcAta | string | null | USDC token account for the balance read; null until deployed |
publicKey | string | 33-byte SEC1 compressed P-256 authority key, base64 — needed to build the passkey signer |
userHandle | string | Server-minted wallet identity |
credentialId | string | Passkey credential id |
receiveAddress and usdcAta are null until the wallet's on-chain account is deployed (it deploys lazily on first use). Do not assume they are set right after sign-in.
Props
SignInWithDexterProps:
| Prop | Type | Default | What it does |
|---|---|---|---|
onSuccess | (result: SignInResult) => void | — | Fires the moment sign-in completes |
onError | (error: ConnectError) => void | — | Fires with the typed error on failure |
label | string | "Sign in with Dexter" | Signed-out button label |
variant | 'primary' | 'secondary' | 'primary' | primary = filled ember, secondary = outline |
block | boolean | false | Full-width button |
showConnectedChip | boolean | true | Render the built-in connected chip |
className | string | — | Extra class, composed after the brand classes |
apiBase | string | https://api.dexter.cash | dexter-api base |
rpcUrl | string | Dexter's Helius proxy | RPC for the connected-chip balance read |
ConnectError carries a code (the server's snake_case error string) alongside message. Branch on code for typed handling.
Drive it yourself with the hook
If you want your own markup instead of the built-in button, use useSignInWithDexter. It is the same ceremony without the rendered surface.
The hook returns status ('idle' | 'pending' | 'done' | 'error'), the live phase ('challenge' | 'passkey' | 'verifying' | 'finalizing') while pending, signIn() / disconnect(), the resolved session / vault, convenience fields (vaultAddress, vaultPda, credentialId, usdcBalance, refreshBalance()), and the typed error.
signIn() resolves with the SignInResult and also throws ConnectError on failure — so you can handle it imperatively (try/catch) or declaratively (read status and error).
Create a new wallet
SignInWithDexter signs in an existing wallet. For the create flow — mint a brand-new passkey and vault — use CreateWalletPanel.
onCreated fires with a CreateWalletResult: handle (base64url user handle), credentialId (base64url), and vault (a ConnectVault, with the swig not yet deployed — it deploys lazily). Props: transport ('auto' | 'popup' | 'inline', default 'auto'), showName (default true), apiBase, onError, className.
If you want the branded button without either turnkey flow — for example wired to your own create action — use DexterButton, which takes children, onClick, variant, block, loading plus loadingLabel, withMark, and disabled.
The transport flag
The passkey ceremony is bound to an origin. Off dexter.cash, an in-page WebAuthn call hits the rpId-origin problem, so the ceremony runs in a hosted popup instead.
'auto'(default): inline ondexter.cash, popup on any other origin. This is what you want on a third-party site.'popup': always via the hosted popup ondexter.cash/connect. Works anywhere.'inline': always in-page. Only valid on a Dexter origin.
SignInWithDexter passes transport through the same way. The connected result is origin-checked and nonce-bound on both sides.
Open a tab from the connected wallet
Once connected, the hook exposes passkeySigner, a DexterApiBrowserPasskeySigner (or null before connect). It is what authorizes spends and opens x402 tabs.
The signer's one method is signOperation(operationMessage). See the vault engine reference for the signer model and the operation messages it signs.
Theming: the --dx-* variables
The button, chip, and wallet kit read their colors and radius from CSS variables. Override them on any ancestor scope. Prefer this over className for color and shape. The audit that specced this page flagged that themeability was promised but never enumerated, so here is the complete set recoverable from the shipped dist.
| Variable | Default | Controls |
|---|---|---|
--dx-ember | #f26c18 | Primary accent. Start of the filled-button gradient, and (via color-mix) the borders, focus ring, and glow |
--dx-ember-2 | #ba3a00 | End of the filled-button gradient (linear-gradient(135deg, --dx-ember, --dx-ember-2)) |
--dx-fg | #fff4ea | Foreground text and glyph color on the button |
--dx-danger | #e5552e | Error-state text color |
--dx-radius | 0px | Corner radius on the button and chip |
These five are the whole set the shipped dist reads. There is no separate variable for the secondary (outline) variant's fill or for the chip background — both derive from --dx-ember through color-mix. If you need finer control than that, use className and target the brand classes directly.