Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
OneAuthClient – 1auth
Skip to content

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

OptionTypeRequiredDescription
providerUrlstringNoURL of the 1auth provider (defaults to https://passkey.1auth.app)
sponsorshipSponsorshipConfigFor intents and assetsEndpoints (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.
dialogUrlstringNoURL for the dialog UI (defaults to providerUrl)
redirectUrlstringNoRedirect target for redirect flow
themeThemeConfigNoUI customization options
blind_signingbooleanNoHide the 1auth signing review iframe for supported signing calls. The browser WebAuthn prompt still appears.
testnetsbooleanNoWhen true, chain queries prefer testnets. wallet_getAssets always returns grouped mainnet and testnet balances.
prewarmbooleanNoWhen 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:

FieldDescription
labelPrimary line in the sign dialog action card (e.g. "Mint NFT")
sublabelSecondary line under the label (e.g. "0.10 USDC")
iconFallback 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.
abiOptional 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:

StatusDescription
pendingIntent created, waiting for quote
quotedQuote received from orchestrator
signedUser has signed with their passkey
submittedSubmitted to the Rhinestone orchestrator
claimedA solver has claimed the intent
preconfirmedPre-confirmation received (typically < 1 second)
filledTransaction filled on the target chain
completedFully confirmed on-chain
failedIntent failed
expiredIntent 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 closeOn parameter to control when promises resolve
  • The client handles all WebAuthn and passkey operations internally
  • signMessage and signTypedData require a passkey session (call authWithModal() first)
  • If you see 401 from /api/sign/options, your session cookie is missing or blocked