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',
sponsorship: {
accessTokenUrl: '/api/sponsorship/access-token',
extensionTokenUrl: '/api/sponsorship/extension-token',
},
theme: {
mode: 'dark',
accent: '#6366f1',
},
});Config Options
| Option | Type | Required | Description |
|---|---|---|---|
providerUrl | string | No | URL of the 1auth provider (defaults to https://passkey.1auth.app) |
sponsorship | SponsorshipConfig | For intents and assets | Endpoints (or callbacks) that mint your app's JWT. Required for sendIntent, sendBatchIntent, grantPermissions, and getAssets — without it the SDK throws MISSING_APP_CREDENTIALS. 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 |
theme | ThemeConfig | No | UI customization options |
blind_signing | boolean | No | Hide the 1auth signing review iframe for supported signing calls. The browser WebAuthn prompt still appears. |
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. |
Authentication Methods
authWithModal
Opens a combined sign-in/sign-up modal:
const result = await client.authWithModal();
if (result.success) {
console.log('User:', result.user?.username);
console.log('Address:', result.user?.address); // typed `0x${string}`
}authenticate
Authenticate with optional challenge signing:
const result = await client.authenticate({
challenge: '0x...', // Optional: sign a challenge
});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);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' }],
},
});signWithPopup
Opens signing in a popup window:
const result = await client.signWithPopup({
accountAddress: '0x...',
challenge: '0x...',
description: 'Review and sign',
});signWithEmbed
Embeds signing UI in your page:
const result = await client.signWithEmbed({
accountAddress: '0x...',
challenge: '0x...',
description: 'Review and sign',
}, {
container: document.getElementById('signing-container')!,
});blind_signing
Set blind_signing: true on the client to hide the 1auth review iframe
for supported signing flows and start the passkey ceremony immediately.
The browser's WebAuthn prompt is still shown.
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.app',
blind_signing: true,
});
const result = await client.sendIntent({
accountAddress: '0x...',
targetChain: 8453,
calls: [{ to: '0x...', data: '0x...' }],
});Per-call options can override the client default:
await client.signTypedData({
accountAddress: '0x...',
domain,
types,
primaryType,
message,
blind_signing: false,
});Blind signing applies to sendIntent, sendBatchIntent,
signMessage, signTypedData, and signWithModal. It does not apply
to signup, login, recovery, or permission-grant dialogs. See
Clear Signing for the visible review UI opt-out
path 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);
}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',
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,
});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,
resolveTokenAddress,
} 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 = resolveTokenAddress('USDC', ARB_SEPOLIA);
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 { resolveTokenAddress } from '@rhinestone/1auth';
import { parseUnits } from 'viem';
const result = await client.sendIntent({
accountAddress: '0x...',
targetChain: 8453,
calls: [],
tokenRequests: [{
token: resolveTokenAddress('USDC', 8453),
amount: parseUnits('100', 6),
}],
sourceAssets: ['ETH'],
});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'getPasskeys
Fetch user's registered passkeys:
const { passkeys } = await client.getPasskeys('user@example.com');
passkeys.forEach(p => console.log(p.name, p.createdAt));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.authWithModal()}
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
All methods return result objects with success/error info:
const result = await client.sendIntent({ ... });
if (!result.success) {
console.error('Code:', result.error?.code);
console.error('Message:', result.error?.message);
}Supported Networks
1auth supports all chains in the Rhinestone orchestrator network. Query supported chains at runtime:
import { getSupportedChains, getAllSupportedChainsAndTokens } from '@rhinestone/1auth';
// Get all supported chain IDs
const chains = getSupportedChains();
// Get chains with their supported tokens
const chainsAndTokens = getAllSupportedChainsAndTokens();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 all WebAuthn and passkey operations internally
signMessageandsignTypedDatarequire a passkey session (callauthWithModal()first)- If you see
401from/api/sign/options, your session cookie is missing or blocked