Overview
OneAuthClient is the core class of the 1auth SDK. It handles all interactions with the passkey authentication system including:
- User registration and authentication
- Transaction signing with multiple UX modes
- Cross-chain intent submission
- Token swaps
Constructor
import { OneAuthClient } from "@rhinestone/1auth";
const client = new OneAuthClient({
providerUrl: "https://passkey.1auth.app",
clientId: "my-app",
sponsorship: {
accessTokenUrl: "/api/sponsorship/access-token",
extensionTokenUrl: "/api/sponsorship/extension-token",
},
theme: {
mode: "dark",
accent: "#6366f1",
},
recovery: {
// Passkey recovery is attempted first. This controls only the fallback.
fallback: { type: "verified-identity" },
},
});Config Options
| Option | Type | Required | Description |
|---|---|---|---|
providerUrl | string | No | URL of the 1auth provider (defaults to https://passkey.1auth.app) |
clientId | string | For app sponsorship/developer features | Identifies app metadata for sponsorship, attribution, recovery guardians, and related features. It does not select the browser WebAuthn RP namespace. |
webauthn | { mode?: 'app_origin'; rpId?: string } | { mode: 'experimental_cross_origin' } | No | Ceremony placement. App-origin is the default with or without clientId and derives its RP ID from the exact page hostname unless rpId selects a shared namespace. A custom rpId requires clientId. The cross-origin passkey-service flow is experimental and requires the explicit experimental_cross_origin label. |
sponsorship | SponsorshipConfig | For intents and assets | Endpoints (or callbacks) that mint your app's JWT. Required by sendIntent, sendBatchIntent, grantPermissions, and getAssets; setting an individual intent's sponsor flag to false skips its extension token, not the app access token. Each method reports missing or failed credentials through its own result/exception contract, detailed below. Get a signing key from the Rhinestone dashboard; see Fee Sponsorship. |
dialogUrl | string | No | URL for the dialog UI (defaults to providerUrl) |
redirectUrl | string | No | Redirect target for redirect flow |
storageKey | string | No | Cross-origin session storage key (defaults to "1auth-user"). A storageKey supplied to createOneAuthProvider() or the experimental connection takes precedence for that bound client. |
theme | ThemeConfig | No | UI customization options |
experimental_clear_signing | boolean | No | Experimental. Show the 1auth signing review iframe before WebAuthn. Defaults to false; blind signing remains the established flow. |
testnets | boolean | No | When true, chain queries prefer testnets. wallet_getAssets always returns grouped mainnet and testnet balances. |
prewarm | boolean | No | When true, schedules a one-time prewarm() on an idle callback after construction. Off by default — prefer calling prewarm() on button hover/focus. |
recovery | AccountRecoveryConfig | No | Controls account creation when passkey-based recovery is unavailable. Defaults to an encrypted backup file. Options: backup-file, verified-identity, app-guardian, or block. App guardians require a verified application origin. |
telemetry | OneAuthTelemetryConfig | No | Bridges SDK events and trace context into the host app's observability setup. Enabled by default when configured; set enabled: false to disable it. |
onDisconnect | () => void | No | Called after the SDK clears its session when a trusted passkey dialog invalidates it, including deployment-forced logout. Use it to clear host-app in-memory authentication state. |
Account recovery fallback
Passkey-based recovery is always attempted first. Configure recovery.fallback
to decide what account creation does when the selected passkey provider cannot
support it:
const client = new OneAuthClient({
recovery: {
fallback: { type: "verified-identity" },
},
});-
backup-file— default; ask the user to save an encrypted recovery file. -
verified-identity— rely on the verified email or OAuth recovery guardian. -
app-guardian— install an Ethereum address supplied by the app:recovery: { fallback: { type: 'app-guardian', address: '0x1111111111111111111111111111111111111111', }, }The dialog discloses this address before account creation. The passkey service accepts it only when the embedding origin resolves to one active registered app and the address exactly matches that app's recovery guardian configured in the authenticated developer portal. The URL/body value is never authoritative.
The app must control the corresponding signer and provide its own recovery authorization flow. 1auth stores and installs only the public guardian address; it never receives the guardian private key.
-
block— refuse account creation unless passkey-based recovery succeeds.
Authentication Methods
authenticate
Authenticate using the verified identity's current account state. In app-origin mode, the server selects registration when no account exists, login when a usable passkey exists, and recovery when the account has no usable passkey or the authentication ceremony is rejected. Identity and recovery UI stay in 1auth, while WebAuthn runs in the top-level app page.
const result = await client.authenticate();
if (result.success) {
console.log("Address:", result.session.accountAddress); // typed `0x${string}`
console.log("Mode:", result.session.webAuthnMode);
}Use authenticate({ flow: "login" }) or authenticate({ flow: "create-account" }) when you want to select cross-origin route presentation. App-origin treats flow as a hint while the server remains authoritative for account-state classification. The SDK never falls back between WebAuthn modes because that would change the credential, signer, and account namespace.
getSession
Return the current session as an AuthResult. In app-origin mode the stored bearer is verified with the passkey service, and a rejected session disconnects the client. In experimental cross-origin mode it reflects locally persisted state only — server validation there would depend on third-party cookies that app-origin avoids.
const session = await client.getSession();
if (session.success) {
console.log("Address:", session.session.accountAddress);
} else if (session.error.code === "APP_ORIGIN_SESSION_REQUIRED") {
await client.authenticate();
}Asset Methods
getAssets
Fetches a unified portfolio across mainnets and testnets by account address.
This uses your configured sponsorship.accessToken to send an app JWT to the
1auth provider before any balance data is returned.
const assets = await client.getAssets({
accountAddress: "0x1111111111111111111111111111111111111111",
});
console.log("All balances:", assets.balances);
console.log("Mainnet balances:", assets.mainnets.balances);
console.log("Testnet balances:", assets.testnets.balances);getAssets() is exception-based. Missing sponsorship, token callback failures,
and portfolio request failures reject with an Error; handle the call with
try/catch.
Signing Methods
Multiple UX modes for transaction signing:
signWithModal
Full-screen modal with transaction details:
const result = await client.signWithModal({
accountAddress: "0x...",
challenge: "0x...",
description: "Review and sign",
transaction: {
actions: [{ type: "custom", label: "Approve action" }],
},
});In app-origin mode, signWithModal() is the supported legacy presentation:
the 1auth iframe renders review, then the SDK runs WebAuthn on the exact
top-level application origin and returns the verified signature. The flow
requires an active app-origin session and correlates iframe approval by exact
window source, provider origin, request ID, and per-modal nonce.
experimental_signWithPopup
Opens signing in a cross-origin popup window:
const result = await client.experimental_signWithPopup({
accountAddress: "0x...",
challenge: "0x...",
description: "Review and sign",
});experimental_signWithPopup() is cross-origin-only. App-origin clients fail before creating
a signing request or opening a window; use signWithModal() instead.
experimental_signWithEmbed
Embeds cross-origin signing UI in your page:
const result = await client.experimental_signWithEmbed(
{
accountAddress: "0x...",
challenge: "0x...",
description: "Review and sign",
},
{
container: document.getElementById("signing-container")!,
},
);experimental_signWithEmbed() is cross-origin-only. App-origin clients fail before creating
an iframe or network request; use signWithModal() instead. experimental_signWithRedirect()
and experimental_handleRedirectCallback() are also cross-origin-only because a navigation
cannot preserve an app-origin WebAuthn ceremony on the original top-level page. The redirect
method currently throws its app-origin unsupported error while the other experimental methods
return a failure result.
experimental_clear_signing
Set experimental_clear_signing: true to evaluate 1auth's visible review
iframe for supported signing flows. The user reviews the payload before the
passkey ceremony starts; the browser's WebAuthn prompt is still shown.
const client = new OneAuthClient({
providerUrl: "https://passkey.1auth.app",
clientId: "my-app",
experimental_clear_signing: true,
});
const result = await client.sendIntent({
accountAddress: "0x...",
targetChain: 8453,
calls: [{ to: "0x...", data: "0x..." }],
});Per-call options can override the client setting:
await client.signTypedData({
accountAddress: "0x...",
domain,
types,
primaryType,
message,
experimental_clear_signing: false,
});The option applies to sendIntent, sendBatchIntent, signMessage,
signTypedData, and signWithModal. It does not apply to signup, login,
recovery, or permission-grant dialogs. See
Signing for limitations and failure behavior.
Intent Execution
sendIntent
Submit cross-chain intents to the Rhinestone orchestrator.
const result = await client.sendIntent({
accountAddress: "0x...",
targetChain: 8453,
calls: [
{
to: "0x...",
data: "0x...",
value: parseEther("0.1"),
label: "Mint NFT", // Optional — shown in the sign dialog
sublabel: "0.10 USDC",
icon: "https://your-app.example/nft.svg", // Optional fallback icon
},
],
closeOn: "completed",
});
if (result.success) {
console.log("TX Hash:", result.transactionHash);
}sendIntent() is result-based for sponsorship failures. Missing configuration
returns success: false with error.code === 'MISSING_APP_CREDENTIALS'.
Access-token or extension-token failures return success: false with
error.code === 'SPONSORSHIP_FETCH_FAILED'; the visible dialog may first offer
its prepare retry flow.
sendBatchIntent() uses a different result shape. It reports top-level failures
through its own optional errorCode and error fields rather than a nested
error.code, and some paths populate neither — a failed access-token fetch
returns only success: false with empty results, surfacing the reason through
SDK telemetry instead. Missing sponsorship sets error. See
Troubleshooting for the full list and how to read it.
Each call in calls is an IntentCall and accepts four optional UI fields:
| Field | Description |
|---|---|
label | Primary line in the sign dialog action card (e.g. "Mint NFT") |
sublabel | Secondary line under the label (e.g. "0.10 USDC") |
icon | Fallback icon URL (SVG / square PNG / data: URL, ≤ 8 KB) shown when 1auth's built-in token registry can't resolve a logo. Built-in icons always win — USDC / ETH / MATIC etc. ignore icon. |
abi | Optional ABI used by the sign dialog to render an unverified human-readable decode of data (function name + args). Same trust model as label / sublabel / icon — the dialog renders the decoded preview behind an "Unverified" badge and always shows the raw to + selector alongside as ground truth. See Per-call ABIs. |
With Token Requests (Output-First)
Use tokenRequests to specify what tokens a target-chain call needs before it
executes. The orchestrator determines the optimal path to deliver them from the
user's assets across any chain.
import { encodeFunctionData, parseUnits } from "viem";
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const amount = parseUnits("100", 6);
const transferData = encodeFunctionData({
abi: erc20Abi,
functionName: "transfer",
args: ["0xRecipient...", amount],
});
const result = await client.sendIntent({
accountAddress: "0x...",
targetChain: 8453,
calls: [
{
to: USDC_BASE,
data: transferData,
label: "Send USDC",
sublabel: "100 USDC",
},
],
tokenRequests: [
{
token: USDC_BASE,
amount,
},
],
closeOn: "completed",
});This is ideal for app actions such as transfers, deposits, and purchases that
need an output token before execution. For a plain swap where the user only
receives the output token, call sendIntent with calls: [] rather than
adding placeholder calldata.
Pass waitForHash: true if you need a transaction hash. Otherwise, rely on intentId + getIntentStatus.
grantPermissions
Grant a scoped SmartSession permission to an app-owned session key. 1auth
opens the permission review, collects the user's passkey approval, and
submits the install/enable transaction. Your app sends only the public
sessionKeyAddress; keep the private key in your own signer.
import { definePermissions, OneAuthClient } from "@rhinestone/1auth";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { parseUnits } from "viem";
const oneAuth = new OneAuthClient({
providerUrl: "https://passkey.1auth.app",
clientId: "my-app",
sponsorship: {
accessTokenUrl: "/api/sponsorship/access-token",
extensionTokenUrl: "/api/sponsorship/extension-token",
},
});
const privateKey = generatePrivateKey();
const sessionKeyAddress = privateKeyToAccount(privateKey).address;
const now = Math.floor(Date.now() / 1000);
const validUntil = now + 24 * 60 * 60;
const permissions = definePermissions({
address: mUSD,
name: "mUSD",
abi: erc20Abi,
functions: {
mint: {
params: {
to: { condition: "equal", value: accountAddress },
amount: { condition: "equal", value: parseUnits("0.1", 6) },
},
},
},
});
const result = await oneAuth.grantPermissions({
accountAddress,
targetChains: [84532],
sessionKeyAddress,
validAfter: now,
validUntil,
maxUses: 25,
...permissions,
});grantPermissions() also reports sponsorship failures as structured results:
MISSING_APP_CREDENTIALS when sponsorship is not configured and
SPONSORSHIP_FETCH_FAILED when the access-token callback fails.
After this grant succeeds, the app can prepare and submit a matching
mUSD.mint(accountAddress, parseUnits('0.1', 6)) intent with its
session key. That later mint does not open another 1auth user-signature
dialog; the SmartSession validator enforces the to and amount
constraints on-chain.
For bridge or cross-chain swap sessions, include a bridge claim
permission with crossChainPermits. Selector permissions still describe
the destination-chain calls; crossChainPermits describes the
source-chain Permit2 claim that funds the route.
import {
createCrossChainPermission,
definePermissions,
} from "@rhinestone/1auth";
import { parseUnits } from "viem";
import { arbitrumSepolia, baseSepolia } from "viem/chains";
const ARB_SEPOLIA = 421614;
const BASE_SEPOLIA = 84532;
const amount = parseUnits("0.1", 6);
const now = Math.floor(Date.now() / 1000);
const validUntil = now + 86400;
const usdcOnArb = "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d";
const swap = definePermissions({
address: rwaSwap,
name: "RWASwap",
abi: rwaSwapAbi,
functions: {
swap: {
params: {
usdcIn: { condition: "equal", value: amount },
recipient: { condition: "equal", value: accountAddress },
},
},
},
});
const bridge = createCrossChainPermission({
from: { chain: baseSepolia, token: mUSDOnBase, maxAmount: amount },
to: { chain: arbitrumSepolia, token: usdcOnArb, recipient: accountAddress },
validAfter: BigInt(now),
validUntil: BigInt(validUntil),
settlementLayers: ["ACROSS"],
});
await oneAuth.grantPermissions({
accountAddress,
// Destination chain for swap; source chain for Permit2 claim.
targetChains: [ARB_SEPOLIA],
sourceChains: [BASE_SEPOLIA],
sessionKeyAddress,
validAfter: now,
validUntil,
maxUses: 25,
permissions: swap.permissions,
crossChainPermits: [bridge],
contracts: swap.contracts,
});The source chain must be in sourceChains. During settlement, Permit2
verifies the claim by calling isValidSignature on the source-chain
account, so the SmartSession validator must be installed and enabled
there too.
Plain swaps
Plain swaps use sendIntent with an empty calls array and one or more
tokenRequests:
import { parseUnits } from "viem";
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const result = await client.sendIntent({
accountAddress: "0x...",
targetChain: 8453,
calls: [],
tokenRequests: [
{
token: USDC_BASE,
amount: parseUnits("100", 6),
},
],
sourceAssets: ["0x0000000000000000000000000000000000000000"],
});For routes that fund target-chain execution, use sendIntent or
walletClient.sendCalls with real calls plus explicit
tokenRequests. Do not pass dummy data: '0x' calls or self-transfers just to
make an intent shape fit.
Utility Methods
getIntentStatus
Poll for transaction completion:
const status = await client.getIntentStatus(intentId);
console.log(status.status); // 'pending' | 'completed' | 'failed'setTheme
Update theme at runtime:
client.setTheme({
accent: "#10b981",
mode: "dark",
});prewarm
Load the dialog ahead of the first user interaction so it opens instantly. The SDK already pre-connects (DNS/TLS) at construction; prewarm() goes further and loads the dialog bundle + font into a hidden iframe, so when the user opens a real dialog it paints from cache instead of doing a cold load.
Call it on a likely-intent signal — hover or focus of your sign-in / pay button — so pages where the user never authenticates don't pay for a cross-origin iframe:
<button
onClick={() => client.authenticate()}
onPointerEnter={() => client.prewarm()}
onFocus={() => client.prewarm()}
>
Sign in
</button>prewarm() is idempotent and best-effort: it never throws and never blocks a real open (a failed warm just falls back to a normal load). It resolves true once the dialog bundle has loaded. It loads a dedicated, side-effect-free warm route (it runs no auth flow and creates no backend state), which still warms the shared bundle every dialog flow reuses. Call client.destroyPrewarm() to release the hidden iframe when auth is no longer likely on the current view.
Error handling
SDK methods preserve machine-readable codes and provider diagnostics for signing, intent, simulation, sponsorship, and network failures.
const result = await client.sendIntent({ ... })
if (!result.success) {
console.error(result.error?.code, result.error?.details)
}See Error Codes and Diagnostics for the complete code reference,
OneAuthError usage, simulation links, trace IDs, and recommended application
behavior.
Supported Networks
1auth supports all chains in the Rhinestone orchestrator network. Query supported chains at runtime:
import { getSupportedChainIds, getSupportedChains } from "@rhinestone/1auth";
// Catalog IDs include every orchestrator-supported EVM chain.
const chainIds = await getSupportedChainIds();
// viem Chain objects are returned when the installed viem version defines them.
const chains = await getSupportedChains();
// Include testnets or restrict discovery to an explicit allowlist.
const testnets = await getSupportedChains({ includeTestnets: true });
const selected = await getSupportedChains({ chainIds: [1, 8453] });Mainnet chains include Ethereum, Base, Arbitrum, Optimism, Polygon, and others. The targetChain parameter in sendIntent() accepts any supported chain ID.
Transaction Lifecycle
When you call sendIntent(), the transaction progresses through these stages:
| Status | Description |
|---|---|
pending | Intent created, waiting for quote |
quoted | Quote received from orchestrator |
signed | User has signed with their passkey |
submitted | Submitted to the Rhinestone orchestrator |
claimed | A solver has claimed the intent |
preconfirmed | Pre-confirmation received (typically < 1 second) |
filled | Transaction filled on the target chain |
completed | Fully confirmed on-chain |
failed | Intent failed |
expired | Intent expired before execution |
The closeOn option controls when sendIntent() resolves:
"preconfirmed"(default) — Resolves quickly, recommended for most use cases"claimed"— Resolves at first solver claim (fastest, less certain)"filled"— Resolves when the transaction hits the target chain"completed"— Waits for full on-chain confirmation (slowest)
For a transaction hash, pass waitForHash: true or poll with getIntentStatus() after the intent resolves.
Notes
- Create one client instance and reuse it
- Use the
closeOnparameter to control when promises resolve - The client handles identity verification and WebAuthn orchestration internally.
- App-origin is the default with or without
clientId; browser credentials and accounts are isolated by the exact hostname unless a registered shared RP ID and exact server-authorizedrpOriginsare configured. The SDK sends onlyrpId; origins are never client-owned configuration. - App-origin supports message signing, typed-data signing, batch intents, recovery, and ordinary passkey account connection. Set
webauthn: { mode: 'experimental_cross_origin' }only for theexperimental_*presentation methods or the experimental EOA connection surface; those APIs reject withAPP_ORIGIN_FLOW_UNSUPPORTEDin app-origin mode rather than switching credential namespaces.