# 1auth
> Passkey-based authentication and transaction signing for smart accounts.
## Architecture
Understanding how 1auth components work together to provide secure, gasless authentication and transactions.
### Overview
1auth is built on top of the [Rhinestone SDK](https://docs.rhinestone.dev/home/introduction/welcome-to-rhinestone), adding a passkey-based authentication layer to Rhinestone's chain abstraction and smart account infrastructure. The result: fully chain-abstracted accounts secured by passkeys.
1. **Security through passkeys** - WebAuthn provides hardware-backed, biometric authentication
2. **Full chain abstraction** - One account, one balance, works across all supported chains
3. **Simplicity through abstraction** - Complex blockchain operations are hidden from users
4. **Flexibility through standards** - Built on [ERC-7579](https://erc7579.com) modular accounts with EIP-1193 compatibility for wide tooling support
### System Components
### 1auth Service
The 1auth service runs at `passkey.1auth.box` and consists of two main parts:
#### Passkey Provider (iframe)
The passkey provider runs in a secure iframe embedded in your application and handles all WebAuthn operations:
* Passkey registration and authentication
* EIP-712 typed data signing
* Intent preparation and user approval
* Session persistence across origins
The iframe architecture ensures that passkey credentials are never exposed to the parent application, providing an additional layer of security.
:::note[White-Labeling]
The `passkey.1auth.box` domain can be whitelabeled to your own domain, so the wallet experience is fully branded and siloed to your application. Contact us for whitelabel setup.
:::
##### Cross-App Session Persistence
Because the passkey provider runs on a dedicated domain (`passkey.1auth.box`), users stay logged in across all apps that integrate 1auth. Session state is stored in the provider's localStorage and cookies, separate from each app's origin.
This means:
* **One login, many apps** - Users authenticate once and are recognized across all 1auth-powered apps
* **No unnecessary repeated passkey prompts** - Returning users get a seamless experience
* **App isolation** - Individual apps never access session data directly; they communicate through the secure iframe
#### 1auth Backend
The backend integrates the [Rhinestone Orchestrator SDK](https://docs.rhinestone.dev) to handle onchain execution:
* Receives signed intents from the passkey provider
* Uses Rhinestone SDK to convert intents into executable transactions
* Submits transactions across multiple chains
* Handles gas sponsorship and transaction monitoring
### Chain Abstraction
1auth accounts are fully chain abstracted, powered by the Rhinestone SDK. Users interact with a single account that works seamlessly across all supported chains:
| Feature | Description |
| ------------------------- | ------------------------------------------------------------------- |
| **Cross-chain execution** | Execute transactions on any supported chain from a single signature |
| **Unified balances** | Users see aggregated balances across chains |
| **Automatic bridging** | Assets are bridged as needed to fulfill transactions |
| **Gas sponsorship** | Users never need native tokens for gas |
| **MEV protection** | Transactions are protected from front-running and sandwich attacks |
### Smart Account
Each user gets a fully [self-custodial](/self-custody) smart contract account that supports modular functionality via [ERC-7579](https://erc7579.com). The account is controlled by a passkey validator module that verifies WebAuthn signatures onchain—only the user can authorize transactions.
Key features:
* Counterfactual deployment (account exists before first transaction)
* Modular architecture for extensibility
* Same address on EVM chains
### Request Flow
1. **User Action** - User clicks PayButton or initiates transaction in your app
2. **SDK Request** - 1auth SDK sends the transaction request to the passkey provider iframe
3. **Passkey Signing** - User reviews and authenticates with Face ID/Touch ID
4. **Backend Processing** - Signed intent is sent to 1auth backend
5. **Rhinestone Execution** - Backend uses Rhinestone SDK to execute onchain
6. **Confirmation** - SDK receives confirmation, updates UI
### Learn More
* [Rhinestone Documentation](https://docs.rhinestone.dev/home/introduction/welcome-to-rhinestone) - Deep dive into the underlying infrastructure
* [Passkeys](/passkey) - How passkeys provide secure, non-custodial authentication
## FAQ
### General
#### What is 1auth?
1auth is a passkey-based authentication and transaction signing SDK for smart accounts. It lets users authenticate and sign transactions using biometrics (Face ID, Touch ID, Windows Hello) instead of seed phrases or browser extensions.
#### Do users need a wallet extension?
No. 1auth uses passkeys which are built into modern browsers and operating systems. No extensions or apps required.
#### Which chains are supported?
1auth supports any EVM-compatible chain. Transactions are executed via cross-chain intents, so users can pay from any chain.
### Accounts
#### What type of account do users get?
Each user gets a smart contract account (ERC-7579 modular account) controlled by their passkey. The account is fully self-custodial—only the user can authorize transactions.
#### Is my account address the same on every chain?
Yes. Your 1auth account has the same address on all EVM chains. You can receive funds on any chain to the same address.
#### What is chain abstraction?
Chain abstraction means users don't need to think about which chain they're on. Powered by [Rhinestone](https://rhinestone.wtf), 1auth handles bridging assets and executing transactions across chains automatically. Users see a unified balance and can pay from any chain.
#### Do I need to bridge tokens manually?
No. When you make a transaction, 1auth automatically bridges assets as needed. You never need to manually bridge or worry about having the right tokens on the right chain.
#### Can I use the same account across different apps?
Yes. Your 1auth account works across all apps that integrate 1auth. Because authentication happens on a shared provider domain (`passkey.1auth.box`), you stay logged in across apps—no need to re-authenticate for each one.
### Compatibility
#### Which browsers are supported?
Passkeys work in all modern browsers:
| Browser | Support |
| ------- | -------------------- |
| Chrome | Full support (v109+) |
| Safari | Full support (v16+) |
| Firefox | Full support (v122+) |
| Edge | Full support (v109+) |
#### Which devices support passkeys?
| Platform | Passkey Storage |
| ----------- | -------------------------------------------- |
| macOS / iOS | iCloud Keychain (syncs across Apple devices) |
| Windows | Windows Hello |
#### Do password managers work with passkeys?
Yes. These password managers support passkey storage and sync:
| Password Manager | Passkey Support |
| ---------------- | ---------------------- |
| 1Password | Full support |
| Bitwarden | Full support |
| iCloud Keychain | Native (Apple devices) |
Using a password manager lets you sync passkeys across all your devices and browsers.
### Integration
#### Do I need viem or wagmi?
No. The React components work standalone without any web3 libraries. If you already use viem/wagmi, we provide adapters for those too.
#### Can I test against 1auth from localhost?
No. Passkeys (WebAuthn) require a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts), and browsers block passkey operations on plain `http://` origins other than `localhost` itself. Because 1auth authenticates on a separate provider domain (`passkey.1auth.box`), the cross-origin iframe and credential scoping make `http://localhost` unreliable or non-functional for the full authentication flow.
To develop locally, use one of these approaches:
* **Self-signed certificate + `/etc/hosts`** — Generate a self-signed TLS cert, add an entry like `127.0.0.1 local.myapp.dev` to `/etc/hosts`, and serve your app over HTTPS on that hostname.
* **[Tailscale](https://tailscale.com/kb/1153/enabling-https/)** — Tailscale can provision valid HTTPS certificates for your machine via `tailscale cert`, giving you a `*.ts.net` domain that works out of the box.
* **[Caddy](https://caddyserver.com/)** — Use Caddy as a local reverse proxy; it automatically provisions locally-trusted TLS certificates.
#### Is there a testnet?
Yes, testnets are currently available.
### Security
#### How are passkeys secured?
Passkeys use public key cryptography. The private key never leaves the user's device and is protected by the device's secure enclave (TPM, Secure Enclave, etc.).
#### What happens if a user loses their device?
Passkeys sync across devices via iCloud Keychain, Google Password Manager, or Windows Hello. Users can also register multiple passkeys as backup.
## Introduction
Passkey-based authentication and transaction signing for smart accounts. Build secure, gasless, cross-chain applications without the complexity.
### What is 1auth?
1auth provides a complete authentication and transaction signing solution built on top of WebAuthn passkeys and smart contract accounts. It enables your users to interact with blockchain applications using just their device's biometrics - no seed phrases, no browser extensions, no gas fees.
### Motivation
Traditional wallet experiences create friction at every step. Users must manage seed phrases, install browser extensions, manage gas tokens, and understand complex blockchain concepts before they can interact with applications. The goal of 1auth is to abstract all this with a simple, but powerful, embedded wallet solution that leverages passkeys.
Passkeys are an open source tool that any developer can leverage. So why use 1auth? Building a produciton-ready passkey wallet from scratch is hard:
* **Key recovery** - If a user loses their device, how do they regain access? Passkeys sync across devices via platform providers (such as iCloud and Google), but coordinating this with onchain accounts in a self-custodial manner requires careful architecture.
* **Crosschain key management (i.e., keystore)** - Passkey credentials must be mapped to smart account ownership across multiple chains, with proper validation methods deployed and maintained onchain.
* **Crosschain complexity** - A single user action might require bridging assets, managing random gas tekens to execute on a different chain, and monitoring multiple transactions.
* **Gas abstraction** - Users shouldn't need native tokens on every chain. Sponsoring gas requires paymaster integration and infrastructure across all supported chains.
1auth handles all of this so you can focus on your application.
### The advantages of 1auth
* **Full self-custody** - Users hold their own keys in device secure hardware. 1auth never has access to private key material.
* **Transact on any chain** - One account works across all supported chains with automatic bridging and unified balances.
* **No seed phrases, no extensions** - Users authenticate with Face ID or Touch ID, in any modern browser.
* **Gasless by default** - Transactions are sponsored. Users never need native tokens.
### 1auth Use Cases
#### Fintechs
Build applications with crypto functionality while maintaining a non-custodial architecture. Because 1auth never holds keys, your application can align with unhosted wallet classifications under MiCA, SEC, and FinCEN frameworks. See [Self-Custody & Compliance](/self-custody).
#### Crypto Consumer Apps
Create a clean new user onboarding experience using familiar web2 flows (Oauth, SSO, etc) that is fully self-custodial, highly performant (with transaction signing local to the user), and with great economics (passkeys leverage existing device hardware at zero infrastructure cost).
#### DeFi Onboarding
Bring new users into DeFi without the traditional wallet setup barrier. 1auth's EIP-1193 provider works with viem, wagmi, and ethers, so you can integrate with existing DeFi tooling while offering a Web2-like sign-up experience.
#### Multi-Chain Applications
Build applications that work across chains without users managing multiple wallets or bridging assets manually. 1auth accounts have unified balances and execute transactions on any supported chain from a single signature.
#### Agents
Generate wallets for AI agents or enable co-piloted self-custodial wallets between your users and agents using session keys for scoped agent spending authorisations.
### Self-Custody
1auth is built on a fully self-custodial architecture. Private keys are generated and stored exclusively in the user's device secure hardware (Secure Enclave, TPM). 1auth cannot access, reconstruct, or use private keys—and cannot sign transactions or censor users on their behalf.
This means users have true ownership of their assets with no third-party risk, while developers benefit from reduced compliance burden.
[Learn more about Self-Custody & Compliance →](/self-custody)
## Why Passkeys
Passkeys are the foundation of 1auth's security model. They provide a user-friendly alternative to passwords and seed phrases, while ensuring your users maintain full custody of their private keys.
### What are Passkeys?
Passkeys are a modern authentication standard based on [WebAuthn (FIDO2)](https://www.w3.org/TR/webauthn-2/). Instead of remembering passwords or securing seed phrases, users authenticate with their device's built-in biometrics:
* **Face ID** or **Touch ID** on Apple devices
* **Windows Hello** on Windows devices
* **Fingerprint sensors** on Android devices
Each passkey is bound to a specific domain and protected by the device's secure hardware. They cannot be reused on a different site, which makes credential theft through fake websites ineffective.
### How Passkeys Work
Passkeys use public key cryptography:
1. **Registration**: When a user creates an account, their device generates a key pair. The private key is stored in secure hardware (Secure Enclave, TPM), while the public key is sent to the server.
2. **Authentication**: When signing in or signing a transaction, the server sends a challenge. The device signs this challenge with the private key after biometric verification.
3. **Verification**: The server verifies the signature using the stored public key.
The critical point: **the private key never leaves the device**. The server only ever sees the public key and signed challenges.
```
┌─────────────────────────────────────────────────────────┐
│ User's Device │
│ │
│ ┌──────────────────┐ ┌─────────────────────────┐ │
│ │ Biometric │ │ Secure Hardware │ │
│ │ Verification │─────▶│ (Secure Enclave) │ │
│ │ │ │ │ │
│ │ Face ID │ │ Private Key Stored │ │
│ │ Touch ID │ │ Signing Operations │ │
│ └──────────────────┘ └───────────┬─────────────┘ │
│ │ │
└────────────────────────────────────────┼────────────────┘
│ Signed Intent
▼
┌─────────────────────┐
│ 1auth Server │
│ │
│ Public Key Only │
│ No Key Material │
└─────────────────────┘
```
### Benefits
#### For Users
* **Full self-custody** - Private keys live in your device's secure hardware. No third party—including 1auth—can access or move your assets. Secure enough for anyone to use.
* **No passwords or seed phrases** - Just use Face ID or Touch ID. Nothing to remember, write down, or lose.
* **Cross-platform sync** - Passkeys sync across your devices via iCloud Keychain, Google Password Manager, or Windows Hello, so you're never locked to a single device.
* **Simple and familiar** - The same biometric experience as unlocking your phone or making an Apple Pay purchase.
#### For Developers
* **Self-custody = regulatory hedge** - Because users hold their own keys, your application can align with non-custodial wallet classifications. No custody license required.
* **Clean user onboarding** - No wallet downloads, no seed phrase UX, no browser extension requirements. Users sign up with a single biometric prompt.
* **Fast conversion** - Users are more likely to complete sign-up without password friction or extension install steps.
* **Cost-effective** - No MPC infrastructure, no key management servers, no HSMs. Passkeys leverage existing device hardware at zero infrastructure cost.
* **Standard browser APIs** - WebAuthn is supported in all modern browsers with no dependencies.
### Passkey Support
Passkey support is effectively universal on modern hardware.
| Platform | Passkey Support | Source |
| --------------- | --------------- | ---------------------------------------------------- |
| **iOS / macOS** | 95%+ | [passkeys.dev](https://passkeys.dev/device-support/) |
| **Android** | 95%+ | [passkeys.dev](https://passkeys.dev/device-support/) |
| **Windows** | \~75% | [passkeys.dev](https://passkeys.dev/device-support/) |
:::note[Windows Support]
TPM 2.0—which enables Windows Hello and passkey support—has been built into essentially all CPUs since 2015 (Intel PTT, AMD fTPM). Microsoft **mandated** it for all new Windows device certifications **starting 2016**.
:::
#### Password Manager Support
Major password managers also support passkey storage and sync, extending coverage beyond platform-native implementations:
| Password Manager | Passkey Support |
| ---------------- | ------------------------------- |
| **1Password** | Full support (all platforms) |
| **Dashlane** | Full support (all platforms) |
| **Bitwarden** | Full support (all platforms) |
| **Enpass** | Full support (desktop + mobile) |
| **NordPass** | Full support (all platforms) |
This means users can store and sync passkeys through their preferred password manager, independent of their device platform.
### How 1auth Uses Passkeys
1auth leverages passkeys throughout the authentication and transaction flow:
1. **Account Creation** - User registers a passkey, creating a smart account controlled by that passkey
2. **Authentication** - Users authenticate with biometrics via WebAuthn
3. **Transaction Signing** - EIP-712 typed data is signed using the passkey's private key
4. **Cross-Origin Security** - The passkey provider runs in a secure iframe, isolating credentials from your application
This architecture ensures that even if your application is compromised, the user's private keys remain protected in their device's secure hardware.
## Quick Start
::::steps
#### Install the SDK
:::code-group
```bash [npm]
npm install @rhinestone/1auth
```
```bash [yarn]
yarn add @rhinestone/1auth
```
```bash [pnpm]
pnpm add @rhinestone/1auth
```
:::
#### Initialize Client
```tsx
import { OneAuthClient } from '@rhinestone/1auth';
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
});
```
::::
### Next Steps
### References
* [API Reference](/sdk) - Full SDK documentation
* [RPC Reference](/rpc) - EIP-1193 provider methods
* [GitHub](https://github.com/rhinestonewtf/1auth-sdk) - Source code and examples
## Self-Custody & Compliance
Passkeys enable true self-custody of crypto assets, which has significant implications for regulatory compliance.
### Self-Custody Tests
1auth's architecture passes the key tests that distinguish a self-custodial wallet from a custodial service:
| Test | 1auth |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Provider cannot access private keys** | Keys are generated and stored exclusively in user device hardware (Secure Enclave, TPM). 1auth never sees key material. |
| **Provider cannot sign transactions** | All signing happens on-device via WebAuthn. 1auth has no technical ability to produce valid signatures. |
| **Provider cannot censor or block users** | 1auth cannot prevent a user from signing or submitting transactions. Users can also interact with their smart account directly onchain. |
| **Provider cannot freeze or seize funds** | Assets are held in user-owned smart contract accounts. 1auth has no admin keys or backdoors. |
| **User can recover access independently** | Passkeys sync across devices via platform providers (iCloud Keychain, Google Password Manager). Users can also add backup passkeys or recovery modules to their smart account. |
| Test | 1auth |
| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **The "Reconstruction" Test:** Provider cannot access or reconstruct the private keys | Keys are generated and stored exclusively in user device hardware (Secure Enclave, TPM). 1auth never sees the key material. |
| **The "Seizure" Test:** Provider cannot sign transactions or withdraw funds | All signing happens on-device via WebAuthn. 1auth has no technical ability to produce valid signatures. |
| **The "Censorship" Test:** Provider cannot block users | 1auth cannot prevent a user from signing or submitting transactions. Users can also interact with their smart account directly onchain. |
| **The "Independency" Test:** User can recover access independently | Passkeys sync across devices via platform providers (iCloud Keychain, Google Password Manager). 1auth also provides a recovery module with a recovery path that is independent of 1auth systems. |
#### Portability
A common concern with any wallet provider: what happens if the service shuts down?
* **Passkeys are synced by platform providers** - iCloud Keychain, Google Password Manager, and password managers like 1Password store and sync passkeys independently of 1auth.
* **Smart accounts are onchain** - User accounts exist as smart contracts on public blockchains. They continue to function regardless of whether 1auth's service is running.
* **Direct onchain interaction** - Users can interact with their smart account directly through any EVM-compatible tool (e.g., Etherscan, Cast) using their passkey-derived signature.
* **Module-based recovery** - Users can install recovery modules (social recovery, backup keys) on their smart account for additional portability.
### Regulatory Framework Alignment
This self-custodial design aligns with major regulatory frameworks:
**European Union (MiCA)**
* Custody requires safekeeping crypto-assets or means of access (like private keys)
* 1auth doesn't hold assets or keys—users retain exclusive control
* Meets requirements for non-custodial service classification
**United States (SEC/FinCEN)**
* Guidance distinguishes "hosted" (custodial) from "unhosted" (non-custodial) wallets
* Unhosted wallets: users independently control keys and transact directly
* 1auth's architecture aligns with unhosted wallet classification
### What This Means for You
| Stakeholder | Benefit |
| --------------- | ---------------------------------------------------------------------------- |
| **Developers** | Reduced compliance burden when adding crypto functionality to your app |
| **Users** | Full ownership of assets with no third-party risk |
| **Enterprises** | Architecture compatible with self-custodial positioning in regulated markets |
### Passkeys vs Traditional Approaches
### 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
```tsx
import { OneAuthClient } from '@rhinestone/1auth';
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
theme: {
mode: 'dark',
accent: '#6366f1',
},
});
```
#### Config Options
| Option | Type | Required | Description |
| ------------- | ------------- | -------- | ------------------------------------------------------------------- |
| `providerUrl` | `string` | No | URL of the 1auth provider (defaults to `https://passkey.1auth.box`) |
| `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 |
### Authentication Methods
#### authWithModal
Opens a combined sign-in/sign-up modal:
```tsx
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:
```tsx
const result = await client.authenticate({
username: 'user@example.com',
challenge: '0x...', // Optional: sign a challenge
});
```
### Signing Methods
Multiple UX modes for transaction signing:
#### signWithModal
Full-screen modal with transaction details:
```tsx
const result = await client.signWithModal({
username: 'user@example.com',
calls: [{ to: '0x...', data: '0x...' }],
targetChain: 8453,
});
```
#### signWithPopup
Opens signing in a popup window:
```tsx
const result = await client.signWithPopup({
username: 'user@example.com',
calls: [{ to: '0x...', data: '0x...' }],
targetChain: 8453,
});
```
#### signWithEmbed
Embeds signing UI in your page:
```tsx
const result = await client.signWithEmbed({
username: 'user@example.com',
calls: [{ to: '0x...', data: '0x...' }],
targetChain: 8453,
embed: {
containerId: 'signing-container',
},
});
```
### Intent Execution
#### sendIntent
Submit cross-chain intents to the Rhinestone orchestrator.
```tsx
const result = await client.sendIntent({
username: 'user@example.com',
targetChain: 8453,
calls: [
{
to: '0x...',
data: '0x...',
value: parseEther('0.1'),
},
],
closeOn: 'completed',
});
if (result.success) {
console.log('TX Hash:', result.transactionHash);
}
```
##### With Token Requests (Output-First)
Use `tokenRequests` to specify what tokens you want to receive. The orchestrator determines the optimal path to deliver them from the user's assets across any chain.
```tsx
import { parseUnits } from 'viem';
const result = await client.sendIntent({
username: 'user@example.com',
targetChain: 8453,
calls: [{ to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', data: '0x' }],
tokenRequests: [
{
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
amount: parseUnits('100', 6), // 100 USDC (bigint)
},
],
closeOn: 'completed',
});
```
This is ideal for swaps, cross-chain transfers, or any "output-first" operation.
Pass `waitForHash: true` if you need a transaction hash. Otherwise, rely on `intentId` + `getIntentStatus`.
#### sendSwap
High-level token swap API:
```tsx
const result = await client.sendSwap({
username: 'user@example.com',
fromToken: '0x...', // USDC
toToken: '0x...', // WETH
amount: parseUnits('100', 6),
targetChain: 8453,
});
```
### Utility Methods
#### getIntentStatus
Poll for transaction completion:
```tsx
const status = await client.getIntentStatus(intentId);
console.log(status.status); // 'pending' | 'completed' | 'failed'
```
#### getPasskeys
Fetch user's registered passkeys:
```tsx
const { passkeys } = await client.getPasskeys('user@example.com');
passkeys.forEach(p => console.log(p.name, p.createdAt));
```
#### setTheme
Update theme at runtime:
```tsx
client.setTheme({
accent: '#10b981',
mode: 'dark',
});
```
### Error Handling
All methods return result objects with success/error info:
```tsx
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:
```tsx
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()` and `sendSwap()` 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 `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
## API Reference
The 1auth SDK provides everything you need to add passkey authentication and transaction signing to your app.
* **React components** - Drop-in components that work standalone, no viem or wagmi required
* **Client API** - Full control over auth flows, signing, and intent execution
* **Viem/Wagmi integration** - Optional adapters for existing web3 apps
### Installation
:::code-group
```bash [npm]
npm install @rhinestone/1auth
```
```bash [yarn]
yarn add @rhinestone/1auth
```
```bash [pnpm]
pnpm add @rhinestone/1auth
```
:::
### Quick Links
#### Client
Core SDK classes for authentication and transaction signing.
* [**OneAuthClient**](/sdk/client) - Main SDK client for auth, signing, and intents
* [**Sign in with 1auth**](/sdk/sign-in) - Passkey authentication flow
* [**PayButton**](/sdk/pay-button) - One-click payment component
#### React
React components and hooks for building passkey-powered UIs.
* [**React Components & Hooks**](/sdk/react) - PayButton, BatchQueue, useBatchQueue
#### Utilities
Helper functions for signing and verification.
* [**signTypedData**](/sdk/sign-typed-data) - EIP-712 typed data signing
### Basic Example
```tsx
import { OneAuthClient } from '@rhinestone/1auth';
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
});
// Authenticate
const auth = await client.authWithModal();
if (auth.success) {
console.log('Logged in as:', auth.username);
}
// Send a transaction
const result = await client.sendIntent({
username: auth.username,
targetChain: 8453,
calls: [{ to: '0x...', value: parseEther('0.1') }],
});
```
import { PayButtonDemo } from '../../layout'
## PayButton
One-click payment button with built-in authentication. Users click, authenticate with their passkey, and the transaction executes.
### Usage
```tsx
import { PayButton } from '@rhinestone/1auth/react';
console.log('Paid:', result.transactionHash)}
onError={(error) => console.error(error)}
/>
```
### Props
| Prop | Type | Required | Description |
| ----------- | ------------------ | -------- | --------------------------------------- |
| `client` | `OneAuthClient` | Yes | SDK client instance |
| `intent` | `IntentParams` | Yes | Transaction parameters |
| `onSuccess` | `(result) => void` | No | Called on successful transaction |
| `onError` | `(error) => void` | No | Called on error |
| `children` | `ReactNode` | No | Button text (default: "Pay with 1auth") |
| `className` | `string` | No | Custom CSS class |
| `disabled` | `boolean` | No | Disabled state |
### IntentParams
```tsx
interface IntentParams {
targetChain: number; // Chain ID to execute on
calls: IntentCall[]; // Array of calls to execute
tokenRequests?: TokenRequest[]; // Optional: tokens to receive (output-first)
}
interface IntentCall {
to: `0x${string}`; // Target contract address
value?: bigint; // ETH value to send
data?: `0x${string}`; // Encoded calldata
}
interface TokenRequest {
token: string; // ERC-20 token contract address
amount: bigint; // Amount in base units
}
```
### Examples
#### Simple Payment
```tsx
Pay $10
```
#### Contract Interaction
```tsx
{
toast.success('NFT minted!');
router.push(`/nft/${tokenId}`);
}}
>
Mint NFT
```
#### Custom Styling
```tsx
Complete Purchase
```
#### ERC20 Payment with tokenRequests
For payments in ERC20 tokens (like USDC), use `tokenRequests` to tell the orchestrator what tokens are needed. This enables cross-chain funding - users can pay even if their funds are on a different chain.
```tsx
import { encodeFunctionData, parseUnits } from 'viem';
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const amount = parseUnits('10', 6); // 10 USDC
const transferData = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [merchantAddress, amount],
});
toast.success('Payment complete!')}
>
Pay 10 USDC
```
:::tip
When `tokenRequests` is specified, the orchestrator automatically bridges or swaps from any chain where the user has funds. The user doesn't need to have USDC on Base - they can pay with ETH on Arbitrum and 1auth handles the conversion.
:::
### Chain Abstraction
The user can pay from any chain they have funds on. If their balance is on Arbitrum but `targetChain` is Base, 1auth automatically bridges the assets. Users see a unified balance and never worry about which chain they're on.
import { AuthDemo, PayButtonDemo, BatchQueueDemo } from '../../layout'
## React Components
Drop-in React components for passkey authentication and crypto payments. No viem, wagmi, or web3 knowledge required.
```tsx
import { PayButton, BatchQueueProvider, BatchQueueWidget, useBatchQueue } from '@rhinestone/1auth/react';
```
### Why Use React Components?
Building a crypto-enabled app shouldn't require deep web3 expertise. Our React components abstract away the complexity:
* **No web3 libraries required** - Works without viem, wagmi, ethers, or any other dependency
* **Built-in passkey flow** - Authentication, signing, and status updates handled for you
* **Chain abstraction included** - Users pay from any chain, bridging happens automatically
* **Production-ready UI** - Styled components that work out of the box
### Overview
These components work standalone - just pass a `OneAuthClient` instance and they handle everything: wallet connection, passkey prompts, transaction signing, and status updates.
**Zero web3 boilerplate.** No provider setup, no wallet connectors, no chain configuration. Your users click a button, authenticate with their passkey, and the transaction executes.
| Export | Type | Description |
| ------------------------------------------- | --------- | -------------------------------------------------- |
| [`PayButton`](#paybutton) | Component | One-click payment button with built-in auth flow |
| [`BatchQueueProvider`](#batchqueueprovider) | Component | Context provider for queuing multiple transactions |
| [`BatchQueueWidget`](#batchqueuewidget) | Component | Floating UI to review and sign queued transactions |
| [`useBatchQueue`](#usebatchqueue) | Hook | Access batch queue state and methods |
**When to use these components:**
* **PayButton** - E-commerce checkouts, donations, tips, or any single-action payment
* **BatchQueue** - Shopping carts, multi-step workflows, or batching gas-efficient operations
All components handle the complete passkey flow internally: authentication prompts, transaction signing, and status updates.
***
### AuthDemo
Interactive authentication component with message signing.
#### Authentication
```tsx
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
});
const result = await client.authWithModal();
if (result.success) {
console.log('Connected as:', result.user?.username);
}
```
#### Message Signing
```tsx
const result = await client.signMessage({
username: 'alice',
message: `Sign in to MyApp\nTimestamp: ${Date.now()}`,
description: 'Verify your identity',
});
if (result.success) {
console.log('Signature:', result.signature);
}
```
#### SignMessageOptions
| Property | Type | Required | Description |
| ------------- | ------------------------- | -------- | ------------------------------------------- |
| `username` | `string` | Yes | Username of the signer |
| `message` | `string` | Yes | Human-readable message to sign |
| `description` | `string` | No | Description shown in dialog |
| `metadata` | `Record` | No | Additional data to display |
| `challenge` | `string` | No | Custom challenge (defaults to message hash) |
| `theme` | `ThemeConfig` | No | Theme configuration for the dialog |
***
### PayButton
One-click payment button with built-in authentication.
#### Usage
```tsx
import { PayButton } from '@rhinestone/1auth/react';
console.log('Paid:', result.transactionHash)}
onError={(error) => console.error(error)}
/>
```
#### Props
| Prop | Type | Required | Description |
| ----------- | ------------------------------------ | -------- | -------------------------------------------------------------------------------------------------- |
| `client` | `OneAuthClient` | Yes | SDK client instance |
| `intent` | `SendIntentOptions` | Yes | Transaction parameters (calls, targetChain, tokenRequests) |
| `onSuccess` | `(result: SendIntentResult) => void` | No | Called on successful transaction |
| `onError` | `(error: Error) => void` | No | Called on error |
| `closeOn` | `CloseOnStatus` | No | When to close dialog: "claimed", "preconfirmed", "filled", or "completed". Default: "preconfirmed" |
| `children` | `ReactNode` | No | Button text (default: "Pay with 1auth") |
| `className` | `string` | No | Custom CSS class |
| `style` | `React.CSSProperties` | No | Custom inline styles (merged with defaults) |
| `disabled` | `boolean` | No | Disabled state |
| `hideIcon` | `boolean` | No | Hide the fingerprint icon |
See the full [PayButton reference](/sdk/pay-button) for more details.
***
### BatchQueueProvider
Context provider for managing batched transaction queues with localStorage persistence.
#### Usage
```tsx
import { BatchQueueProvider, BatchQueueWidget } from '@rhinestone/1auth/react';
function App() {
return (
);
}
```
#### Props
| Prop | Type | Required | Description |
| ---------- | --------------- | -------- | ----------------------------- |
| `client` | `OneAuthClient` | Yes | SDK client instance |
| `username` | `string` | No | Username for localStorage key |
| `children` | `ReactNode` | Yes | Child components |
#### Chain Validation
All calls in a batch must target the same chain:
```tsx
const { addToBatch } = useBatchQueue();
addToBatch({ to: '0x...', label: 'Action 1' }, 8453); // Base
// This will fail - different chain
const result = addToBatch({ to: '0x...', label: 'Action 2' }, 1);
if (!result.success) {
console.log(result.error); // "Batch is set to Base..."
}
```
***
### BatchQueueWidget
Floating widget UI showing queued transactions.
#### Usage
```tsx
import { BatchQueueWidget, useBatchQueue } from '@rhinestone/1auth/react';
function SignAllHandler() {
const { signAll } = useBatchQueue();
const handleSignAll = async () => {
const result = await signAll('user@example.com');
if (result.success) {
console.log('All signed!', result.transactionHash);
}
};
return ;
}
```
#### Props
| Prop | Type | Required | Description |
| ----------- | ------------ | -------- | ----------------------------------- |
| `onSignAll` | `() => void` | Yes | Callback when "Sign All" is clicked |
#### Features
* Collapsible header with call counter badge
* Chain indicator for current batch
* Remove individual calls on hover
* Clear all button
* Bounce animation on new calls
* Auto-hide when queue is empty
***
### useBatchQueue
Hook to access batch queue context.
#### Usage
```tsx
import { useBatchQueue } from '@rhinestone/1auth/react';
function AddToCartButton({ item }) {
const { addToBatch, queue } = useBatchQueue();
const handleClick = () => {
const result = addToBatch(
{
to: item.contractAddress,
data: item.calldata,
label: `Buy ${item.name}`,
},
8453
);
if (!result.success) alert(result.error);
};
return ;
}
```
#### Return Value
| Property | Type | Description |
| ----------------- | ----------------------------------------------------------------------------- | ----------------------------------------- |
| `queue` | `BatchedCall[]` | Array of queued calls |
| `batchChainId` | `number \| null` | Chain ID of the batch (set by first call) |
| `addToBatch` | `(call: IntentCall, chainId: number) => { success: boolean; error?: string }` | Add a call to queue |
| `removeFromBatch` | `(id: string) => void` | Remove a specific call |
| `clearBatch` | `() => void` | Clear all calls |
| `signAll` | `(username: string) => Promise` | Sign and submit all calls |
| `isSigning` | `boolean` | Whether signing is in progress |
| `isExpanded` | `boolean` | Whether the widget is expanded |
| `setExpanded` | `(expanded: boolean) => void` | Set widget expanded state |
#### BatchedCall Type
```tsx
interface BatchedCall {
id: string;
call: IntentCall;
targetChain: number;
addedAt: number;
}
```
import { AuthDemo } from '../../layout'
## Sign in with 1auth
Add passkey authentication to your app with a single method call. Users authenticate with Face ID or Touch ID - no passwords, no seed phrases.
### Authentication
```tsx
import { OneAuthClient } from '@rhinestone/1auth';
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
});
// Open the authentication modal
const result = await client.authWithModal();
if (result.success) {
console.log('Connected as:', result.user?.username);
console.log('Address:', result.user?.address); // typed `0x${string}`
}
```
### How It Works
1. **User clicks sign in** - Your app calls `authWithModal()`
2. **1auth modal opens** - User enters their username or creates a new account
3. **Passkey prompt** - Device prompts for Face ID / Touch ID
4. **Success** - You receive user details and wallet address
The entire flow happens in a secure iframe. Your app never sees the passkey credentials.
### Message Signing
Request the user to sign a message for verification:
```tsx
const result = await client.signMessage({
username: 'alice',
message: `Sign in to MyApp\nTimestamp: ${Date.now()}`,
description: 'Verify your identity',
});
if (result.success) {
console.log('Signature:', result.signature);
// Verify signature on your backend
}
```
### SignMessageOptions
| Property | Type | Required | Description |
| ------------- | ------------------------- | -------- | --------------------------------------- |
| `username` | `string` | Yes | Username of the signer |
| `message` | `string` | Yes | Human-readable message to sign |
| `description` | `string` | No | Description shown in the signing dialog |
| `metadata` | `Record` | No | Additional data to display |
### AuthResult
`authWithModal()` returns an `AuthResult`:
```tsx
import type { AuthResult } from '@rhinestone/1auth';
interface AuthResult {
success: boolean;
/** Authenticated user (present when success is true) */
user?: {
id: string;
username?: string;
address: `0x${string}`;
};
/** Error details when success is false */
error?: {
code: string; // e.g. "USER_CANCELLED"
message: string;
};
}
```
### Challenge-Based Authentication
Use `authenticate()` for server-verified login. It returns an `AuthenticateResult` — the same shape as `AuthResult` plus a `challenge` object with the cryptographic signature.
```tsx
const result = await client.authenticate({
challenge: `Login to MyApp\nTimestamp: ${Date.now()}\nNonce: ${crypto.randomUUID()}`
});
if (result.success && result.challenge) {
// Send signature + signedHash to your backend for verification
await fetch('/api/verify', {
method: 'POST',
body: JSON.stringify({
username: result.user?.username,
address: result.user?.address,
signature: result.challenge.signature,
signedHash: result.challenge.signedHash,
}),
});
}
```
#### AuthenticateResult
`authenticate()` returns an `AuthenticateResult`, which extends `AuthResult` with a `challenge` field:
```tsx
import type { AuthenticateResult } from '@rhinestone/1auth';
interface AuthenticateResult extends AuthResult {
/** Present when a challenge was provided in the options */
challenge?: {
signature: WebAuthnSignature;
signedHash: `0x${string}`;
};
}
```
### Example: Protected Route
```tsx
import { useState, useEffect } from 'react';
import { OneAuthClient } from '@rhinestone/1auth';
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
});
function App() {
const [user, setUser] = useState(null);
const handleSignIn = async () => {
const result = await client.authWithModal();
if (result.success && result.user) {
setUser({ username: result.user.username, address: result.user.address });
}
};
if (!user) {
return (
);
}
return (
Welcome, {user.username}!
Address: {user.address}
);
}
```
import { SignTypedDataDemo } from "../../layout"
## signTypedData
Sign [EIP-712](https://eips.ethereum.org/EIPS/eip-712) structured data using passkeys. Used for gasless approvals, off-chain voting, order signing, and meta-transactions.
EIP-712 provides human-readable signing - users see exactly what they're signing instead of a raw hash.
### Live Demo
### Overview
Sign EIP-712 structured data with a human-readable preview. The dialog displays:
1. **Domain info** - Application name, version, chain, and contract
2. **Message fields** - Formatted values with type-aware display
3. **Signature** - WebAuthn signature with the EIP-712 hash
### Basic Usage
You need an active passkey session before calling `signTypedData`. Use `authWithModal()` once per session.
```tsx
import { OneAuthClient } from '@rhinestone/1auth';
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
});
await client.authWithModal();
const result = await client.signTypedData({
username: 'alice',
domain: {
name: 'My dApp',
version: '1',
chainId: 8453,
verifyingContract: '0x...',
},
types: {
Vote: [
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'bool' },
{ name: 'voter', type: 'address' },
],
},
primaryType: 'Vote',
message: {
proposalId: '42',
support: true,
voter: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
},
description: 'Cast your vote',
});
if (result.success) {
console.log('Signature:', result.signature);
console.log('EIP-712 Hash:', result.signedHash);
}
```
### ERC-2612 Permit Example
Sign a token approval without an onchain transaction:
```tsx
const result = await client.signTypedData({
username: 'alice',
domain: {
name: 'Dai Stablecoin',
version: '1',
chainId: 1,
verifyingContract: '0x6B175474E89094C44Da98b954EecdeCB5BE3830F',
},
types: {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
primaryType: 'Permit',
message: {
owner: '0xabc...',
spender: '0xdef...',
value: '1000000000000000000',
nonce: '0',
deadline: '1735689600',
},
description: 'Allow spending of DAI',
});
```
### SignTypedDataOptions
| Property | Type | Required | Description |
| ------------- | ------------------------- | -------- | --------------------------- |
| `username` | `string` | Yes | Username of the signer |
| `domain` | `EIP712Domain` | Yes | EIP-712 domain parameters |
| `types` | `EIP712Types` | Yes | Type definitions |
| `primaryType` | `string` | Yes | Primary type to sign |
| `message` | `Record` | Yes | Message values |
| `description` | `string` | No | Description shown in dialog |
| `theme` | `ThemeConfig` | No | Theme override |
### EIP712Domain
| Property | Type | Required | Description |
| ------------------- | ------------- | -------- | ------------------- |
| `name` | `string` | Yes | Application name |
| `version` | `string` | Yes | Application version |
| `chainId` | `number` | No | Chain ID |
| `verifyingContract` | `0x${string}` | No | Contract address |
| `salt` | `0x${string}` | No | Salt value |
### SignTypedDataResult
| Property | Type | Description |
| ------------- | -------------------- | -------------------------------------------------- |
| `success` | `boolean` | Whether signing succeeded |
| `signature?` | `WebAuthnSignature` | The WebAuthn signature (only on success) |
| `signedHash?` | `0x${string}` | The EIP-712 hash that was signed (only on success) |
| `passkey?` | `PasskeyCredentials` | Passkey used for signing (only on success) |
| `error?` | `{ code, message }` | Error details (only on failure) |
### Use Cases
* **ERC-2612 Permits** - Gasless token approvals
* **Governance** - Off-chain voting with typed signatures
* **Order signing** - DEX limit orders (EIP-712 format)
* **Meta-transactions** - Relay-compatible signatures
### How It Works
1. The SDK sends the typed data (domain, types, message) to the sign dialog
2. The sign dialog displays the data in human-readable format for user review
3. **The sign dialog computes the EIP-712 hash locally** using viem's `hashTypedData`
4. The user signs the locally-computed hash with their passkey
5. The signature and hash are returned to your app
### Security Model
The EIP-712 hash is computed **inside the sign dialog**, not by the calling application. This ensures users sign exactly what they see displayed:
```
┌─────────────────────────────────────┐
│ Your App (SDK Client) │
│ │
│ 1. Calls client.signTypedData() │
│ 2. SDK sends typed data to dialog │
└──────────────┬──────────────────────┘
│ postMessage (typed data only)
▼
┌─────────────────────────────────────┐
│ Sign Dialog (Trusted) │
│ │
│ 3. Displays data for user review │
│ 4. Computes EIP-712 hash locally │ ◄── Hash computed HERE
│ 5. Signs hash with passkey │
│ 6. Returns signature + hash │
└─────────────────────────────────────┘
```
This prevents a malicious app from displaying one thing while getting the user to sign something different. The sign dialog is the trusted component that ensures **what you see is what you sign**.
### Overview
`PasskeyWalletClient` is a viem-compatible wallet client that extends the standard `WalletClient` with passkey-authenticated transaction signing. It provides a familiar viem API while handling all WebAuthn operations internally.
Use this when you want:
* A standard viem `WalletClient` interface
* Batched transaction support via `sendCalls()`
* Output-first token requests (specify what tokens you want to receive)
### Creating a Wallet Client
```typescript
import { createPasskeyWalletClient } from '@rhinestone/1auth';
import { base } from 'viem/chains';
import { http } from 'viem';
const walletClient = createPasskeyWalletClient({
accountAddress: '0x...', // User's smart account address
username: 'user@example.com',
chain: base,
transport: http(),
});
```
#### Config Options
| Option | Type | Required | Description |
| ---------------- | ----------- | -------- | ------------------------------------------------------------------- |
| `accountAddress` | `Address` | Yes | User's smart account address |
| `username` | `string` | Yes | Username for the passkey provider |
| `chain` | `Chain` | Yes | viem chain configuration |
| `transport` | `Transport` | Yes | viem transport (e.g., `http()`) |
| `providerUrl` | `string` | No | URL of the 1auth provider (defaults to `https://passkey.1auth.box`) |
| `dialogUrl` | `string` | No | URL for the dialog UI |
| `waitForHash` | `boolean` | No | Wait for transaction hash before resolving (default: `true`) |
| `hashTimeoutMs` | `number` | No | Maximum time to wait for hash in ms |
| `hashIntervalMs` | `number` | No | Poll interval for hash in ms |
### sendCalls
Send multiple calls as a single batched transaction. Opens the passkey modal for user approval.
```typescript
const hash = await walletClient.sendCalls({
calls: [
{
to: '0x...', // Contract address
data: '0x...', // Encoded calldata
value: parseEther('0.1'), // Optional ETH value
label: 'Swap ETH for USDC', // Optional: shown in signing UI
sublabel: '0.1 ETH → 250 USDC', // Optional: additional context
},
],
chainId: 8453, // Optional: override chain
});
```
#### Parameters
| Property | Type | Required | Description |
| --------------- | ------------------------------------- | -------- | ----------------------------- |
| `calls` | `TransactionCall[]` | Yes | Array of calls to execute |
| `chainId` | `number` | No | Override the default chain ID |
| `tokenRequests` | `{ token: string; amount: bigint }[]` | No | Output tokens to deliver |
#### TransactionCall
| Property | Type | Required | Description |
| ---------- | --------- | -------- | ------------------------- |
| `to` | `Address` | Yes | Target contract address |
| `data` | `Hex` | No | Encoded calldata |
| `value` | `bigint` | No | ETH value in wei |
| `label` | `string` | No | Label shown in signing UI |
| `sublabel` | `string` | No | Additional context for UI |
#### Returns
`Promise` - The transaction hash once confirmed.
### Token Requests (Output-First Model)
The `tokenRequests` parameter lets you specify what tokens and amounts you want to receive as the output of your transaction. This is the **output-first model** - you declare the desired result, and the orchestrator figures out how to achieve it.
```typescript
import { parseUnits } from 'viem';
const hash = await walletClient.sendCalls({
calls: [
{
to: '0xUSDC_ADDRESS',
data: '0x', // Empty for simple transfers
label: 'Receive USDC',
},
],
tokenRequests: [
{
token: '0xUSDC_ADDRESS', // Token contract address
amount: parseUnits('100', 6), // 100 USDC (6 decimals)
},
],
});
```
#### Type Signature
```typescript
tokenRequests?: {
token: string; // ERC-20 token contract address
amount: bigint; // Amount in base units (use parseUnits)
}[];
```
#### When to Use Token Requests
* **Swaps**: Specify the output token and amount you want to receive
* **Cross-chain transfers**: Declare the destination token and amount
* **Multi-step operations**: Let the orchestrator optimize the path
The orchestrator will automatically:
1. Determine required input tokens from your portfolio
2. Find the optimal route across chains
3. Execute bridging and swaps as needed
### Example: Token Swap
```typescript
import { parseUnits } from 'viem';
// Swap: receive 100 USDC on Base
const hash = await walletClient.sendCalls({
calls: [
{
to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
data: '0x',
label: 'Swap to USDC',
sublabel: 'Receive 100 USDC',
},
],
tokenRequests: [
{
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
amount: parseUnits('100', 6),
},
],
chainId: 8453, // Base
});
console.log('Transaction hash:', hash);
```
### Notes
* The client extends viem's `WalletClient`, so standard methods like `getAddresses()` work as expected
* `sendCalls()` opens the passkey modal for user approval before executing
* Use `waitForHash: false` if you only need intent confirmation (faster, but no hash)
* Token amounts use `bigint` for viem compatibility - use `parseUnits()` for decimals
import { AccountsDemo } from "../../layout"
### eth\_accounts
Returns the list of accounts that the user has previously connected. This allows your dApp to check if a user is already connected on page load without prompting them to authenticate again. Unlike `eth_requestAccounts`, this method never shows a modal.
### Try it
### Parameters
None.
### Returns
`string[]` - Array of account addresses. Returns an empty array if not connected.
### Example
```typescript
const accounts = await provider.request({ method: 'eth_accounts' });
if (accounts.length > 0) {
console.log('Connected as:', accounts[0]);
} else {
console.log('Not connected');
}
```
### Notes
* Returns addresses from localStorage if the user has previously connected
* Does not prompt the user to connect - use `eth_requestAccounts` for that
* Always returns an array (empty if not connected)
import { ChainIdDemo } from "../../layout"
### eth\_chainId
Returns the chain ID of the currently selected network. This is useful for verifying which blockchain your dApp is connected to before initiating transactions, and for displaying network information to users.
### Try it
### Parameters
None.
### Returns
`string` - The current chain ID as a hexadecimal string (e.g., `"0x1"` for Ethereum mainnet).
### Example
```typescript
const chainId = await provider.request({ method: 'eth_chainId' });
console.log(chainId); // "0x1" (mainnet), "0x89" (polygon), etc.
```
### Notes
* The chain ID is returned as a hex-encoded string, not a number
* Use `parseInt(chainId, 16)` to convert to a number if needed
* This method does not require the user to be connected
import { ConnectionDemo } from "../../layout"
### eth\_requestAccounts
Prompts the user to connect their wallet to your dApp. If the user hasn't authenticated before, this opens the passkey registration/login flow. If already authenticated but not connected to your app, it shows a lightweight connect prompt. This is the standard method for initiating wallet connection.
### Try it
### Parameters
None.
### Returns
`string[]` - Array of account addresses that the user has authorized.
### Example
```typescript
try {
const accounts = await provider.request({ method: 'eth_requestAccounts' });
console.log('Connected as:', accounts[0]);
} catch (error) {
if (error.message.includes('cancelled')) {
console.log('User cancelled the connection');
}
}
```
### Behavior
1. If the user is already connected, returns the stored account immediately
2. If not connected, opens the lightweight connect modal
3. If the user has never authenticated, shows the full auth modal with passkey registration/login
4. The user can approve or cancel the connection request
### Notes
* This is the standard way to request wallet connection
* Equivalent to calling `wallet_connect`
* The returned address is the user's smart account address
import { SendTransactionDemo } from "../../layout"
### eth\_sendTransaction
Sends a single transaction through the 1auth intent system. The user reviews the transaction details and approves with their passkey. Gas is automatically sponsored, so users don't need ETH for fees. Returns an intent ID that you can poll with `wallet_getCallsStatus` to track execution.
### Try it
### Parameters
| Position | Type | Description |
| -------- | -------- | ---------------------- |
| 0 | `object` | The transaction object |
#### Transaction Object
| Field | Type | Description |
| --------------- | ------------------ | -------------------------------------------------------- |
| `to` | `string` | The recipient address or contract |
| `data` | `string` | The calldata (optional, defaults to `"0x"`) |
| `value` | `string \| number` | The value in wei (optional, defaults to `"0"`) |
| `chainId` | `number` | Target chain ID (optional, uses current chain) |
| `tokenRequests` | `array` | Tokens needed for this transaction (optional, see below) |
#### Token Requests
For ERC20 transfers or contract calls that consume tokens, specify `tokenRequests` to enable cross-chain funding:
| Field | Type | Description |
| -------- | -------- | ------------------------------------------------ |
| `token` | `string` | Token contract address on target chain |
| `amount` | `bigint` | Amount in base units (e.g., 6 decimals for USDC) |
### Returns
`string` - The intent ID (can be used with `wallet_getCallsStatus`).
### Example
```typescript
import { encodeFunctionData, parseUnits } from 'viem';
// Simple ETH transfer (no tokenRequests needed)
const intentId = await provider.request({
method: 'eth_sendTransaction',
params: [{
to: '0x...',
value: '1000000000000000000', // 1 ETH in wei
}],
});
// ERC20 transfer with tokenRequests
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const amount = parseUnits('100', 6); // 100 USDC
const transferData = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [recipientAddress, amount],
});
const intentId = await provider.request({
method: 'eth_sendTransaction',
params: [{
to: USDC,
data: transferData,
tokenRequests: [{ token: USDC, amount }],
}],
});
```
:::tip
When you include `tokenRequests`, the orchestrator automatically bridges or swaps tokens from any chain where the user has funds. This enables seamless cross-chain payments.
:::
### Notes
* Returns an intent ID, not a transaction hash
* Use `wallet_getCallsStatus` to track the transaction status
* The transaction is executed via the 1auth intent system
* Gas is automatically sponsored - no ETH needed for fees
import { SignTypedDataDemo } from "../../layout"
### eth\_signTypedData\_v4
Signs structured data according to EIP-712. Unlike `personal_sign`, this method understands the structure of your data and displays it in a human-readable format. Use this for permit signatures, order signing, and any protocol that requires typed structured data signatures.
### Try it
### Parameters
| Position | Type | Description |
| -------- | ------------------ | -------------------------------------- |
| 0 | `string` | The account address |
| 1 | `string \| object` | The typed data object (or JSON string) |
### Typed Data Structure
```typescript
interface TypedData {
domain: {
name?: string;
version?: string;
chainId?: number;
verifyingContract?: string;
};
types: {
[typeName: string]: Array<{ name: string; type: string }>;
};
primaryType: string;
message: Record;
}
```
### Returns
`string` - The signature as a hex string.
### Example
```typescript
const typedData = {
domain: {
name: 'My App',
version: '1',
chainId: 1,
verifyingContract: '0x...',
},
types: {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
},
primaryType: 'Person',
message: {
name: 'Alice',
wallet: '0x...',
},
};
const signature = await provider.request({
method: 'eth_signTypedData_v4',
params: [accounts[0], typedData],
});
```
### Notes
* Shows a human-readable preview of the structured data
* Also available as `eth_signTypedData` (alias)
* The typed data is validated before signing
import { SignMessageDemo } from "../../layout"
### eth\_sign
Signs arbitrary hex-encoded data. This is a legacy method - prefer `personal_sign` for new implementations. The data is decoded and displayed to the user for review before signing. Internally, this method behaves identically to `personal_sign`.
### Try it
### Parameters
| Position | Type | Description |
| -------- | -------- | ------------------------------ |
| 0 | `string` | The account address |
| 1 | `string` | The data to sign (hex-encoded) |
### Returns
`string` - The signature as a hex string.
### Example
```typescript
const accounts = await provider.request({ method: 'eth_accounts' });
const data = '0x' + Buffer.from('Hello').toString('hex');
const signature = await provider.request({
method: 'eth_sign',
params: [accounts[0], data],
});
```
### Notes
* This method is deprecated in favor of `personal_sign`
* The data is displayed to the user after hex decoding
* Works the same as `personal_sign` internally
## RPC Reference
The 1auth provider implements the [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) interface, making it compatible with viem, wagmi, ethers, and other Ethereum libraries.
### Overview
All RPC methods can be called via the standard `request` interface:
```tsx
const result = await provider.request({
method: 'method_name',
params: [...],
})
```
### Supported Methods
#### Ethereum Standard
| Method | Description | EIP |
| -------------------------------------------------- | ---------------------------- | ---- |
| [`eth_chainId`](/rpc/eth-chainid) | Returns the current chain ID | 695 |
| [`eth_accounts`](/rpc/eth-accounts) | Returns connected accounts | - |
| [`eth_requestAccounts`](/rpc/eth-request-accounts) | Requests account access | 1102 |
| [`eth_sendTransaction`](/rpc/eth-send-transaction) | Sends a transaction | - |
#### Message Signing
| Method | Description | EIP |
| -------------------------------------------------- | ------------------------- | --- |
| [`personal_sign`](/rpc/personal-sign) | Signs a message (EIP-191) | 191 |
| [`eth_sign`](/rpc/eth-sign) | Signs raw message hash | - |
| [`eth_signTypedData_v4`](/rpc/eth-sign-typed-data) | Signs typed data | 712 |
#### Wallet Namespace
| Method | Description | EIP |
| ----------------------------------------------------------------- | ------------------------------------- | ---- |
| [`wallet_connect`](/rpc/wallet-connect) | Connects wallet and requests accounts | - |
| [`wallet_disconnect`](/rpc/wallet-disconnect) | Disconnects wallet session | - |
| [`wallet_switchEthereumChain`](/rpc/wallet-switch-ethereum-chain) | Switches to a different chain | 3326 |
| [`wallet_sendCalls`](/rpc/wallet-send-calls) | Sends batched calls atomically | 5792 |
| [`wallet_getCallsStatus`](/rpc/wallet-get-calls-status) | Gets batch execution status | 5792 |
| [`wallet_getCapabilities`](/rpc/wallet-get-capabilities) | Gets wallet capabilities | 5792 |
| [`wallet_getAssets`](/rpc/wallet-get-assets) | Gets user's token balances | - |
### Usage Example
```tsx
import { createOneAuthProvider, OneAuthClient } from '@rhinestone/1auth'
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
})
const provider = createOneAuthProvider({
client,
chainId: 8453, // Base
})
// Request accounts
const accounts = await provider.request({
method: 'eth_requestAccounts',
})
// Sign a message
const signature = await provider.request({
method: 'personal_sign',
params: ['Hello, 1auth!', accounts[0]],
})
```
### Cross-Chain with tokenRequests
For ERC20 transfers and contract calls that need tokens, include `tokenRequests` to enable automatic cross-chain funding. The user can pay from any chain - the orchestrator handles bridging and swaps automatically.
```tsx
import { encodeFunctionData, parseUnits } from 'viem';
const amount = parseUnits('100', 6); // 100 USDC
const transferData = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [recipient, amount],
});
await provider.request({
method: 'eth_sendTransaction',
params: [{
to: USDC_ADDRESS,
data: transferData,
tokenRequests: [{ token: USDC_ADDRESS, amount }],
}],
});
```
See [Token Requests](/guides/token-requests) for detailed documentation.
import { SignMessageDemo } from "../../layout"
### personal\_sign
Signs a plaintext message using the user's passkey. This opens a signing modal where the user can review the message before approving. Use this for "Sign in with Ethereum" flows, proving account ownership, or any scenario where you need a cryptographic signature on arbitrary text.
### Try it
### Parameters
| Position | Type | Description |
| -------- | -------- | ------------------------------------------------------ |
| 0 | `string` | The message to sign (can be hex-encoded or plain text) |
| 1 | `string` | The account address (optional, uses connected account) |
### Returns
`string` - The signature as a hex string, encoded for smart account verification.
### Example
```typescript
const message = 'Hello, 1auth!';
const accounts = await provider.request({ method: 'eth_accounts' });
const signature = await provider.request({
method: 'personal_sign',
params: [message, accounts[0]],
});
console.log('Signature:', signature);
```
### Notes
* Opens the signing modal showing the message to the user
* The user must approve the signature with their passkey
* Returns a WebAuthn-encoded signature compatible with ERC-6492
* Hex-encoded messages are automatically decoded for display
import { ConnectionDemo } from "../../layout"
### wallet\_connect
An alias for `eth_requestAccounts`. Prompts the user to connect their wallet to your dApp if not already connected. Both methods behave identically - use whichever naming convention fits your codebase.
### Try it
### Parameters
None.
### Returns
`string[]` - Array of account addresses that the user has authorized.
### Example
```typescript
const accounts = await provider.request({ method: 'wallet_connect' });
console.log('Connected as:', accounts[0]);
```
### Notes
* This is an alias for `eth_requestAccounts`
* Opens the connection modal if not already connected
* Returns immediately if the user is already connected
import { DisconnectDemo } from "../../layout"
### wallet\_disconnect
Disconnects the user's wallet from your dApp and clears the stored session. After disconnecting, the user will need to reconnect to interact with your app again. This is a client-side disconnect only - it doesn't revoke any onchain permissions.
### Try it
### Parameters
None.
### Returns
`boolean` - Returns `true` on success.
### Example
```typescript
await provider.request({ method: 'wallet_disconnect' });
console.log('Disconnected');
```
### Events
After disconnecting, the provider emits:
* `accountsChanged` with an empty array `[]`
* `disconnect`
```typescript
provider.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
console.log('User disconnected');
}
});
provider.on('disconnect', () => {
console.log('Wallet disconnected');
});
```
### Notes
* Clears the stored session from localStorage
* The user will need to reconnect to use the wallet again
* Does not revoke any onchain permissions
import { GetAssetsDemo } from "../../layout"
### wallet\_getAssets
Fetches the user's token balances and portfolio across all supported chains. This provides a unified view of the user's holdings with USD values when available. Use this to display wallet balances or to check if the user has sufficient funds for a transaction.
### Try it
### Parameters
None.
### Returns
Returns the user's portfolio data including token balances across all supported chains.
```typescript
interface AssetsResponse {
// Portfolio data structure varies - check API response
balances: Array<{
chainId: number;
token: string;
symbol: string;
decimals: number;
balance: string;
usdValue?: number;
}>;
}
```
### Example
```typescript
const assets = await provider.request({
method: 'wallet_getAssets',
});
console.log('Portfolio:', assets);
// Display balances
for (const balance of assets.balances) {
const amount = Number(balance.balance) / 10 ** balance.decimals;
console.log(`${balance.symbol}: ${amount} ($${balance.usdValue})`);
}
```
### Notes
* Requires the user to be connected
* Fetches data from the 1auth portfolio API
* Returns balances across all supported chains
* USD values are fetched from price feeds when available
import { SendCallsDemo } from "../../layout"
### wallet\_getCallsStatus
Checks the execution status of a previously submitted transaction or batch. After calling `eth_sendTransaction` or `wallet_sendCalls`, use this method to poll for confirmation. Returns the current status (pending or confirmed) along with transaction receipts when complete.
### Try it
### Parameters
| Position | Type | Description |
| -------- | -------- | --------------------------------------------- |
| 0 | `string` | The calls ID returned from `wallet_sendCalls` |
### Returns
```typescript
interface CallsStatus {
status: 'PENDING' | 'CONFIRMED';
receipts: Array<{
logs: any[];
status: '0x1' | '0x0'; // success or failure
blockHash?: string;
blockNumber?: string;
transactionHash?: string;
}>;
}
```
### Example
```typescript
const callsId = await provider.request({
method: 'wallet_sendCalls',
params: [{ calls: [...] }],
});
// Poll for status
const checkStatus = async () => {
const result = await provider.request({
method: 'wallet_getCallsStatus',
params: [callsId],
});
if (result.status === 'CONFIRMED') {
const receipt = result.receipts[0];
if (receipt.status === '0x1') {
console.log('Transaction succeeded:', receipt.transactionHash);
} else {
console.log('Transaction failed');
}
} else {
console.log('Still pending...');
setTimeout(checkStatus, 2000);
}
};
checkStatus();
```
### Status Values
| Status | Description |
| ----------- | ------------------------------------------------------------------------------- |
| `PENDING` | Transaction is being processed |
| `CONFIRMED` | Transaction has been confirmed (check `receipts[0].status` for success/failure) |
### Notes
* The intent system handles cross-chain execution
* `PENDING` includes both preconfirmed and in-flight states
* Transaction hash is available once confirmed
import { CapabilitiesDemo } from "../../layout"
### wallet\_getCapabilities
Returns the wallet's supported features for each chain. Use this to discover capabilities like atomic batching, gas sponsorship, and cross-chain fund sourcing. This helps your dApp adapt its UX based on what the wallet supports.
### Try it
### Parameters
| Position | Type | Description |
| -------- | ---------- | -------------------------------------------------------------- |
| 0 | `string` | The wallet address (optional, ignored) |
| 1 | `string[]` | Array of chain IDs to query (optional, returns all if omitted) |
### Returns
```typescript
type Capabilities = {
[chainId: `0x${string}`]: {
atomic: { status: 'supported' };
paymasterService: { supported: boolean };
auxiliaryFunds: { supported: boolean };
};
};
```
### Example
```typescript
const capabilities = await provider.request({
method: 'wallet_getCapabilities',
params: [],
});
console.log(capabilities);
// {
// "0x1": {
// atomic: { status: "supported" },
// paymasterService: { supported: true },
// auxiliaryFunds: { supported: true }
// },
// "0x89": { ... },
// ...
// }
```
### Capabilities
| Capability | Description |
| ------------------ | ---------------------------------------------------- |
| `atomic` | Batch calls are executed atomically (all-or-nothing) |
| `paymasterService` | Gas fees are sponsored - users don't need ETH |
| `auxiliaryFunds` | Cross-chain funds can be used for transactions |
***
#### atomic
Multiple calls sent via `wallet_sendCalls` execute atomically. If any call fails, all calls are reverted.
```typescript
// All calls execute together or none execute
const callsId = await provider.request({
method: 'wallet_sendCalls',
params: [{
chainId: 8453,
calls: [
{ to: tokenAddress, data: approveData },
{ to: dexAddress, data: swapData },
],
}],
});
```
***
#### paymasterService
Gas fees are sponsored. Users don't need to hold ETH to pay for transactions.
**How it works:**
1. User submits a transaction via `eth_sendTransaction` or `wallet_sendCalls`
2. The 1auth intent system handles gas payment automatically
3. User signs with their passkey - no ETH required
***
#### auxiliaryFunds
Funds from other supported chains can be used to fulfill transactions. The 1auth intent system automatically sources funds cross-chain when needed.
**How it works:**
1. User initiates a transaction on Chain A
2. If insufficient funds on Chain A, the system checks other chains
3. Funds are automatically bridged to complete the transaction
4. User only signs once - cross-chain complexity is abstracted away
### Notes
* Returns capabilities for all supported chains by default
* All 1auth accounts have the same capabilities
* Use this to check if features like gas sponsorship are available
import { SendCallsDemo } from "../../layout"
### wallet\_sendCalls
Sends multiple contract calls as a single atomic batch. All calls execute together or none execute, preventing partial failures. This is ideal for multi-step operations like "approve + swap" that must succeed together. Gas is sponsored and the user signs once for the entire batch.
### Try it
### Parameters
| Position | Type | Description |
| -------- | -------- | ----------------------- |
| 0 | `object` | The batch calls payload |
#### Payload Object
| Field | Type | Description |
| --------------- | ---------------- | ------------------------------------------------ |
| `chainId` | `number` | **Required**: Target chain ID for execution |
| `calls` | `Call[]` | Array of calls to execute |
| `tokenRequests` | `TokenRequest[]` | Optional: tokens to receive (output-first model) |
#### Call Object
| Field | Type | Description |
| ------- | ------------------ | --------------------------- |
| `to` | `string` | The recipient address |
| `data` | `string` | The calldata (optional) |
| `value` | `string \| number` | The value in wei (optional) |
#### TokenRequest Object
| Field | Type | Description |
| -------- | -------- | ------------------------------------------------- |
| `token` | `string` | ERC-20 token contract address |
| `amount` | `string` | Amount in base units (e.g., "1000000" for 1 USDC) |
### Returns
`string` - The calls ID (intent ID) for tracking status.
### Example
The `chainId` parameter specifies which chain the calls execute on. This enables crosschain transactions - the user's funds can be on any chain and the orchestrator handles bridging automatically.
```typescript
// Batch multiple contract calls
const callsId = await provider.request({
method: 'wallet_sendCalls',
params: [{
chainId: 8453, // Target chain is required! // [!code ++]
calls: [
{
to: '0x...', // Token contract
data: '0x...', // approve()
},
{
to: '0x...', // DEX contract
data: '0x...', // swap()
},
],
}],
});
// Track the status
const status = await provider.request({
method: 'wallet_getCallsStatus',
params: [callsId],
});
```
### Crosschain execution
The `chainId` parameter is the key to crosschain transactions. Set it to the chain where you want the calls to execute:
```typescript
// Execute on Base (chainId: 8453)
const callsId = await provider.request({
method: 'wallet_sendCalls',
params: [{
chainId: 8453, // Calls execute on Base // [!code ++]
calls: [{
to: '0x...', // Contract on Base
data: '0x...',
}],
}],
});
```
The orchestrator finds the cheapest route to fund the transaction, regardless of where the user's assets are. User signs once with their passkey.
### Token requests (output-first)
Use `tokenRequests` to specify what tokens you want to receive. The orchestrator automatically determines the best path to deliver those tokens from the user's available assets across chains.
```typescript
const callsId = await provider.request({
method: 'wallet_sendCalls',
params: [{
chainId: 8453, // Base // [!code ++]
calls: [{ to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', data: '0x' }],
tokenRequests: [ // [!code ++]
{ // [!code ++]
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base // [!code ++]
amount: '100000000', // 100 USDC (6 decimals) // [!code ++]
}, // [!code ++]
], // [!code ++]
}],
});
```
This is useful for swaps, cross-chain transfers, or any operation where you want to specify the output rather than the input.
### Notes
* All calls are executed atomically - if one fails, all fail
* User sees a summary of all calls before signing
* Gas is sponsored - no ETH needed for fees
* Use `wallet_getCallsStatus` to track execution status
* `tokenRequests.amount` is a string at the RPC level; the SDK accepts `bigint` and converts automatically
import { SwitchChainDemo } from "../../layout"
### wallet\_switchEthereumChain
Changes the active network to a different supported chain. The switch is instant and doesn't require user approval. After switching, subsequent transactions will target the new chain. Use this when your dApp needs to operate on a specific network.
### Try it
### Parameters
| Position | Type | Description |
| -------- | -------- | --------------------------- |
| 0 | `object` | Object containing `chainId` |
#### Parameter Object
| Field | Type | Description |
| --------- | ------------------ | ----------------------------------- |
| `chainId` | `string \| number` | The chain ID (hex string or number) |
### Returns
`null` - Returns `null` on success.
### Example
```typescript
// Switch to Polygon (chainId: 137)
await provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x89' }], // 137 in hex
});
// Or using a number
await provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: 137 }],
});
```
### Events
After switching, the provider emits:
```typescript
provider.on('chainChanged', (chainId) => {
console.log('Switched to chain:', chainId);
});
```
### Supported Chains
The provider supports all chains configured in the 1auth registry. Common chains include:
* Ethereum Mainnet (`0x1`)
* Polygon (`0x89`)
* Arbitrum (`0xa4b1`)
* Optimism (`0xa`)
* Base (`0x2105`)
### Notes
* Chain switching is instant - no user approval required
* Unsupported chain IDs will throw an error
## Authentication
After users sign in, you can request them to sign messages for verification. This enables secure off-chain authentication without exposing passkey credentials.
### Using wagmi
Wagmi provides hooks to sign messages with the user's passkey.
::::steps
#### Install the SDK
```bash
npm install @rhinestone/1auth
```
#### Create config.ts
Create a wagmi config file with the 1auth connector:
```ts [config.ts]
import { createConfig, http } from 'wagmi'
import { base } from 'wagmi/chains'
import { OneAuthClient } from '@rhinestone/1auth' // [!code ++]
import { oneAuth } from '@rhinestone/1auth/wagmi' // [!code ++]
// Create the 1auth client
const client = new OneAuthClient({ // [!code ++]
providerUrl: 'https://passkey.1auth.box', // [!code ++]
}) // [!code ++]
// Create the wagmi config with 1auth connector
export const config = createConfig({
chains: [base],
connectors: [oneAuth({ client })], // [!code ++]
transports: {
[base.id]: http(),
},
})
```
#### Sign a message
Use the `useSignMessage` hook to request a signature:
:::code-group
```tsx [Example.tsx]
import { useSignMessage, useAccount } from 'wagmi'
export function SignMessage() {
const { address } = useAccount()
const { signMessage, data: signature } = useSignMessage()
const handleSign = () => {
signMessage({
message: `Sign in to MyApp\nTimestamp: ${Date.now()}`,
})
}
if (!address) return
Connect wallet first
return (
{signature &&
Signature: {(signature.length - 2) / 2} bytes
}
)
}
```
:::
#### Verify on your backend
Send the signature to your backend for verification:
:::code-group
```tsx [Example.tsx]
import { useSignMessage, useAccount } from 'wagmi'
export function AuthenticatedAction() {
const { address } = useAccount()
const { signMessageAsync } = useSignMessage()
const handleAuth = async () => {
const message = `Sign in to MyApp\nTimestamp: ${Date.now()}`
const signature = await signMessageAsync({ message })
// Send to your backend for verification
const response = await fetch('/api/verify', {
method: 'POST',
body: JSON.stringify({ message, signature, address }),
})
if (response.ok) {
console.log('Authenticated!')
}
}
return
}
```
```ts [api/verify.ts]
import { createPublicClient, http } from 'viem'
import { base } from 'viem/chains'
const publicClient = createPublicClient({
chain: base,
transport: http(),
})
export async function POST(req: Request) {
const { message, signature, address } = await req.json()
// verifyMessage supports ERC-1271 for smart accounts
const isValid = await publicClient.verifyMessage({ // [!code ++]
address, // [!code ++]
message, // [!code ++]
signature, // [!code ++]
}) // [!code ++]
if (!isValid) {
return new Response('Invalid signature', { status: 401 })
}
// Create session, return token, etc.
return new Response('OK')
}
```
:::
#### Done
You have successfully implemented message signing for authentication!
::::
### Using the SDK directly
If you prefer not to use wagmi, use the SDK's `signMessage` method:
```tsx
const result = await client.signMessage({
username: 'alice',
message: `Sign in to MyApp\nTimestamp: ${Date.now()}`,
description: 'Verify your identity',
})
if (result.success) {
console.log('Signature:', result.signature)
// Send to backend for verification
}
```
### SignMessageOptions
| Property | Type | Required | Description |
| ------------- | ------------------------- | -------- | ------------------------------------------- |
| `username` | `string` | Yes | Username of the signer |
| `message` | `string` | Yes | Human-readable message to sign |
| `challenge` | `string` | No | Custom challenge (defaults to message hash) |
| `description` | `string` | No | Description shown in the signing dialog |
| `metadata` | `Record` | No | Additional data to display |
| `theme` | `ThemeConfig` | No | Theme configuration for the dialog |
### Security considerations
* **Include timestamps** to prevent replay attacks
* **Include domain** to prevent cross-site signature reuse
* **Verify on backend** - never trust client-side verification alone
* **Use HTTPS** for all API calls
### Next steps
* [Checkout](/guides/checkout) - Add payments with PayButton
* [Crosschain](/guides/crosschain) - Execute transactions on any chain
import { BatchQueueDemo } from '../../layout'
## Batch Transactions
Queue multiple transactions and sign them all with a single passkey confirmation. Perfect for shopping carts, multi-step workflows, or gas-efficient operations.
### Using BatchQueue
The BatchQueue components let users add items to a queue and sign everything at once.
::::steps
#### Install the SDK
```bash
npm install @rhinestone/1auth
```
#### Configure BatchQueueProvider
Create a client and wrap your app with `BatchQueueProvider`:
```tsx [App.tsx]
import { OneAuthClient } from '@rhinestone/1auth' // [!code ++]
import { BatchQueueProvider } from '@rhinestone/1auth/react' // [!code ++]
const client = new OneAuthClient({ // [!code ++]
providerUrl: 'https://passkey.1auth.box', // [!code ++]
}) // [!code ++]
export function App({ username }: { username?: string }) {
return (
// [!code ++]
// [!code ++]
)
}
```
#### Add items to the queue
Use the `useBatchQueue` hook to add transactions:
:::code-group
```tsx [AddToCart.tsx]
import { useBatchQueue } from '@rhinestone/1auth/react' // [!code ++]
export function AddToCartButton({ item }) {
const { addToBatch, queue } = useBatchQueue() // [!code ++]
const handleClick = () => { // [!code ++]
const result = addToBatch( // [!code ++]
{ // [!code ++]
to: item.contractAddress, // [!code ++]
data: item.calldata, // [!code ++]
label: `Buy ${item.name}`, // [!code ++]
}, // [!code ++]
8453 // Base chain ID // [!code ++]
) // [!code ++]
// [!code ++]
if (!result.success) { // [!code ++]
alert(result.error) // [!code ++]
} // [!code ++]
} // [!code ++]
return (
)
}
```
:::
#### Display the queue widget
Add the `BatchQueueWidget` to show queued items:
:::code-group
```tsx [App.tsx]
import { BatchQueueProvider, BatchQueueWidget, useBatchQueue } from '@rhinestone/1auth/react'
function SignAllHandler({ username }: { username: string }) {
const { signAll } = useBatchQueue() // [!code ++]
const handleSignAll = async () => {
const result = await signAll(username) // [!code ++]
if (result.success) {
console.log('All signed!', result.transactionHash)
}
}
return // [!code ++]
}
export function App() {
return (
)
}
```
:::
#### Done
You have successfully set up batch transactions!
::::
### Chain validation
All calls in a batch must target the same chain:
```tsx
const { addToBatch } = useBatchQueue()
// First call sets the batch chain
addToBatch({ to: '0x...', label: 'Action 1' }, 8453) // Base
// This will fail - different chain
const result = addToBatch({ to: '0x...', label: 'Action 2' }, 1) // Ethereum
if (!result.success) {
console.log(result.error) // "Batch is set to Base..."
}
```
### BatchQueueProvider props
| Prop | Type | Required | Description |
| ---------- | --------------- | -------- | ----------------------------------------------------------------------------- |
| `client` | `OneAuthClient` | Yes | SDK client instance |
| `username` | `string` | No | Username for localStorage persistence (enables queue to survive page refresh) |
| `children` | `ReactNode` | Yes | Child components |
### useBatchQueue reference
| Property | Type | Description |
| ----------------- | ----------------------------------------------------------------------------- | ----------------------------------------- |
| `queue` | `BatchedCall[]` | Array of queued calls |
| `batchChainId` | `number \| null` | Chain ID of the batch (set by first call) |
| `addToBatch` | `(call: IntentCall, chainId: number) => { success: boolean; error?: string }` | Add a call to queue |
| `removeFromBatch` | `(id: string) => void` | Remove a specific call |
| `clearBatch` | `() => void` | Clear all calls |
| `signAll` | `(username: string) => Promise` | Sign and submit all calls |
| `isSigning` | `boolean` | Whether signing is in progress |
| `isExpanded` | `boolean` | Whether the widget is expanded |
| `setExpanded` | `(expanded: boolean) => void` | Set widget expanded state |
### Widget features
The `BatchQueueWidget` includes:
* Collapsible header with call counter badge
* Chain indicator for current batch
* Remove individual calls on hover
* Clear all button
* Bounce animation on new additions
* Auto-hide when queue is empty
See the full [React Components reference](/sdk/react) for more details.
### Next steps
* [Crosschain](/guides/crosschain) - Execute transactions on any chain
* [Deposit](/guides/deposit) - Fund user accounts
import { PayButtonDemo } from '../../layout'
## Checkout
Add one-click payments to your app with the PayButton component. Users click, authenticate with their passkey, and the transaction executes.
### Using PayButton
The PayButton component handles the entire payment flow: authentication, signing, and transaction execution.
::::steps
#### Install the SDK
```bash
npm install @rhinestone/1auth
```
#### Create client.ts
Create a client instance for the PayButton component:
```ts [client.ts]
import { OneAuthClient } from '@rhinestone/1auth' // [!code ++]
// Create the 1auth client // [!code ++]
export const client = new OneAuthClient({ // [!code ++]
providerUrl: 'https://passkey.1auth.box', // [!code ++]
}) // [!code ++]
```
#### Add PayButton
Import and use the PayButton component:
:::code-group
```tsx [Checkout.tsx]
import { PayButton } from '@rhinestone/1auth/react' // [!code ++]
import { parseEther } from 'viem'
export function Checkout() {
return (
{ // [!code ++]
console.log('Paid:', result.transactionHash) // [!code ++]
}} // [!code ++]
onError={(error) => console.error(error)} // [!code ++]
> // [!code ++]
Pay $10 // [!code ++]
// [!code ++]
)
}
```
:::
#### Handle success
Use the `onSuccess` callback to update your UI or backend:
:::code-group
```tsx [Checkout.tsx]
import { PayButton, SendIntentResult } from '@rhinestone/1auth/react'
import { parseEther } from 'viem'
import { useRouter } from 'next/navigation'
export function Checkout({ orderId }: { orderId: string }) {
const router = useRouter()
const handleSuccess = async (result: SendIntentResult) => { // [!code ++]
// Update your backend // [!code ++]
await fetch('/api/orders/complete', { // [!code ++]
method: 'POST', // [!code ++]
body: JSON.stringify({ // [!code ++]
orderId, // [!code ++]
txHash: result.transactionHash, // [!code ++]
}), // [!code ++]
}) // [!code ++]
// [!code ++]
// Redirect to confirmation // [!code ++]
router.push(`/order/${orderId}/confirmation`) // [!code ++]
} // [!code ++]
return (
Complete Purchase
)
}
```
:::
#### Done
You have successfully added one-click payments to your app!
::::
### Contract interactions
PayButton supports any contract call, not just simple transfers:
```tsx
import { encodeFunctionData } from 'viem'
toast.success('NFT minted!')}
>
Mint NFT
```
### Custom styling
Apply your own styles with `className`:
```tsx
Complete Purchase
```
### Props reference
| Prop | Type | Required | Description |
| ----------- | ------------------------------------ | -------- | -------------------------------------------------------------------------------------------------- |
| `client` | `OneAuthClient` | Yes | SDK client instance |
| `intent` | `SendIntentOptions` | Yes | Transaction parameters (calls, targetChain, tokenRequests) |
| `onSuccess` | `(result: SendIntentResult) => void` | No | Called on successful transaction |
| `onError` | `(error: Error) => void` | No | Called on error |
| `closeOn` | `CloseOnStatus` | No | When to close dialog: "claimed", "preconfirmed", "filled", or "completed". Default: "preconfirmed" |
| `children` | `ReactNode` | No | Button text (default: "Pay with 1auth") |
| `className` | `string` | No | Custom CSS class |
| `style` | `React.CSSProperties` | No | Custom inline styles (merged with defaults) |
| `disabled` | `boolean` | No | Disabled state |
| `hideIcon` | `boolean` | No | Hide the fingerprint icon |
See the full [PayButton API reference](/sdk/pay-button) for more details.
### Next steps
* [Crosschain](/guides/crosschain) - Execute transactions on any chain
* [Batch Transactions](/guides/batch-transactions) - Queue multiple operations
## Crosschain
1auth provides complete chain abstraction through intents. Users can execute transactions on any chain using funds from any other chain - no bridging, no chain switching, no gas token management.
### How it works
1. User has funds on Chain A (e.g., USDC on Ethereum)
2. User wants to execute a transaction on Chain B (e.g., mint NFT on Base)
3. 1auth's orchestrator handles the bridging and execution automatically
4. User signs once with their passkey - done
The user's balance is aggregated and abstracted across all chains. They see one unified balance, not separate balances per chain.
### Using wagmi
Wagmi provides hooks to integrate crosschain transactions into your React app.
::::steps
#### Install the SDK
```bash
npm install @rhinestone/1auth
```
#### Create config.ts
Create a wagmi config file with the 1auth connector. For crosschain transactions, add all the chains you want to support:
```ts [config.ts]
import { createConfig, http } from 'wagmi'
import { base, mainnet, optimism, arbitrum } from 'wagmi/chains'
import { OneAuthClient } from '@rhinestone/1auth' // [!code ++]
import { oneAuth } from '@rhinestone/1auth/wagmi' // [!code ++]
// Create the 1auth client
const client = new OneAuthClient({ // [!code ++]
providerUrl: 'https://passkey.1auth.box', // [!code ++]
}) // [!code ++]
// Create the wagmi config with 1auth connector
export const config = createConfig({
chains: [mainnet, base, optimism, arbitrum], // [!code ++]
connectors: [oneAuth({ client })], // [!code ++]
transports: {
[mainnet.id]: http(),
[base.id]: http(),
[optimism.id]: http(),
[arbitrum.id]: http(),
},
})
```
#### Build your calls
The `calls` array defines what contract interactions to execute. Each call has a `to` address, optional `data` (calldata), and optional `value` (ETH amount). Multiple calls execute atomically - all succeed or all fail together.
```ts
import { encodeFunctionData, parseUnits } from 'viem'
// Example: Approve + Swap in a single atomic batch
const calls = [
{
to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC contract
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: [DEX_ADDRESS, parseUnits('100', 6)],
}),
},
{
to: DEX_ADDRESS, // DEX contract
data: encodeFunctionData({
abi: dexAbi,
functionName: 'swap',
args: [USDC, WETH, parseUnits('100', 6)],
}),
},
]
```
#### Send to any chain
Use `useSendCalls` with your calls array. The key crosschain parameters are:
* **`chainId`** - specifies which chain the calls execute on (required)
* **`tokenRequests`** - specifies what tokens the user needs for the transaction (optional)
```ts
sendCalls({
calls,
chainId: 8453, // Execute on Base // [!code ++]
tokenRequests: [{ // [!code ++]
token: USDC_ADDRESS, // [!code ++]
amount: parseUnits('100', 6), // [!code ++]
}], // [!code ++]
})
```
The `tokenRequests` parameter enables the output-first model: instead of specifying which tokens to spend, you specify what tokens you need. The orchestrator automatically finds the cheapest route to deliver those tokens from the user's assets across all chains.
:::code-group
```tsx [SwapOnBase.tsx]
import { useSendCalls } from 'wagmi'
import { encodeFunctionData, parseUnits } from 'viem'
export function SwapOnBase() {
const { sendCalls, isPending } = useSendCalls()
const calls = [
{
to: USDC_ADDRESS,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: [DEX_ADDRESS, parseUnits('100', 6)],
}),
},
{
to: DEX_ADDRESS,
data: encodeFunctionData({
abi: dexAbi,
functionName: 'swap',
args: [USDC, WETH, parseUnits('100', 6)],
}),
},
]
const handleSwap = () => {
sendCalls({
calls,
chainId: 8453, // Execute on Base // [!code ++]
tokenRequests: [{ // [!code ++]
token: USDC_ADDRESS, // USDC needed for swap // [!code ++]
amount: parseUnits('100', 6), // [!code ++]
}], // [!code ++]
})
}
return (
)
}
```
:::
The orchestrator finds the cheapest route to fund the transaction, regardless of where the user's assets currently are. See [Token Requests](/guides/crosschain/token-requests) for more details on the output-first model.
#### Done
You're now sending crosschain transactions with 1auth! Users sign once with their passkey and the orchestrator handles everything else.
::::
### Using the SDK directly
If you prefer not to use wagmi, you can use the SDK's `sendIntent` method:
```tsx
import { OneAuthClient } from '@rhinestone/1auth'
import { parseEther } from 'viem'
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
})
const result = await client.sendIntent({
username: 'alice',
targetChain: 8453, // Base // [!code ++]
calls: [
{
to: '0x...', // Contract on Base
data: '0x...', // Calldata
value: parseEther('0.1'),
},
],
closeOn: 'completed',
})
if (result.success) {
console.log('TX Hash:', result.transactionHash)
}
```
### Next steps
* [Token Requests](/guides/crosschain/token-requests) - Deep dive into the output-first model
* [Batch Transactions](/guides/batch-transactions) - Queue multiple transactions with a shopping cart UI
* [Fee Sponsorship](/guides/fee-sponsorship) - Sponsor gas fees for your users
## Custom ENS
:::info
Contact Rhinestone if you require this feature.
:::
1auth supports custom ENS resolution for account addresses via an offchain ENS resolver (CCIP-Read / EIP-3668). This allows developers to provide human-readable addresses for their users.
### How It Works
Instead of sharing a `0x...` address, your users can have readable names like:
```
alice.yourapp.eth
bob.yourapp.eth
```
The offchain resolver maps these names to the user's 1auth smart account address without requiring onchain transactions for each registration.
### Setup
1. Deploy an `OffchainResolver` contract pointing to the 1auth gateway
2. Set your ENS subdomain's resolver to your deployed contract
**Gateway URL:**
```
https://passkey.1auth.box/api/ens
```
### Benefits
* **User-friendly** - Easy to share and remember addresses
* **Branded** - Use your own `.eth` subdomain
* **Gasless** - Offchain resolution means no gas costs for name registration
* **Universal** - Works with any ENS-compatible wallet or dApp
## Deposit
:::warning
This feature is coming soon.
:::
The deposit service allows users to easily fund their 1auth accounts. It can be integrated via:
* **React component** - Drop-in component for your app
* **Iframe embed** - Embed the deposit flow in any website
* **Signing dialog** - Users can fund directly within the 1auth signing dialog
This enables seamless onboarding where users can deposit funds without leaving your app.
## Fee Sponsorship
:::warning
This feature is coming soon.
:::
Developers will be able to sponsor intents for their users, covering gas fees and/or bridge fees. This enables gasless transactions where users don't need to hold native tokens to interact with your app.
## Manage Account
1auth provides a hosted account management app at [account.1auth.box](https://account.1auth.box) where users and developers can:

* **Login with passkey** - Authenticate using your 1auth passkey
* **View accounts** - See all your smart accounts across chains
* **Manage funds** - View balances and transfer assets
* **Manage passkeys** - Add or remove passkeys from your account
### For Users
Visit [account.1auth.box](https://account.1auth.box) and sign in with your passkey to manage your account.
### For Developers
You can link your users to the account management app if they need to manage their funds or passkeys directly.
```
https://account.1auth.box
```
Note that wallet developers can implement their own account management frontend if needed.
## Onramp
:::warning
This feature is coming soon.
:::
The onramp feature will allow users to purchase crypto directly within your app using fiat payment methods. Users will be able to fund their 1auth accounts without leaving your application.
### Planned features
* **Credit/debit card support** - Purchase crypto with standard payment cards
* **Bank transfers** - Direct bank-to-crypto funding
* **Regional support** - Multiple currencies and payment methods
* **Embedded flow** - Seamless integration without redirects
### Next steps
* [Deposit](/guides/deposit) - Allow users to fund accounts with existing crypto
* [Checkout](/guides/checkout) - Add payments with PayButton
## Permissions
:::warning
This feature is coming soon.
:::
Session keys allow users to grant limited permissions to applications, enabling automated transactions without requiring passkey authentication for every action. This is useful for:
* **Recurring payments** - Subscribe to services without manual approval each time
* **Gaming** - Execute in-game transactions seamlessly
* **DeFi automation** - Auto-compound, rebalance, or execute strategies
import { AuthDemo } from '../../layout'
## Signup & Login
Add passkey authentication to your app with a single method call. Users authenticate with Face ID or Touch ID - no passwords, no seed phrases.
### Using wagmi
Wagmi provides hooks to integrate 1auth authentication into your React app.
::::steps
#### Install the SDK
```bash
npm install @rhinestone/1auth
```
#### Create config.ts
Create a wagmi config file with the 1auth connector:
```ts [config.ts]
import { createConfig, http } from 'wagmi'
import { base } from 'wagmi/chains'
import { OneAuthClient } from '@rhinestone/1auth' // [!code ++]
import { oneAuth } from '@rhinestone/1auth/wagmi' // [!code ++]
// Create the 1auth client
const client = new OneAuthClient({ // [!code ++]
providerUrl: 'https://passkey.1auth.box', // [!code ++]
}) // [!code ++]
// Create the wagmi config with 1auth connector
export const config = createConfig({
chains: [base],
connectors: [oneAuth({ client, chainId: base.id })], // [!code ++]
transports: {
[base.id]: http(),
},
})
```
#### Display sign in button
Create a component that connects users with their passkey. The `connectors` array contains the 1auth connector from your config:
```tsx [SignInButton.tsx]
import { useConnect } from 'wagmi'
export function SignInButton() {
const { connectors, connect } = useConnect()
const connector = connectors.find((c) => c.id === '1auth') // [!code ++]
return (
)
}
```
#### Display account & sign out
After the user signs in, display their account and provide a sign out button:
:::code-group
```tsx [Example.tsx]
import { useAccount, useConnect, useDisconnect } from 'wagmi'
export function Account() {
const { address } = useAccount() // [!code ++]
const { disconnect } = useDisconnect() // [!code ++]
const { connectors, connect } = useConnect()
const connector = connectors.find((c) => c.id === '1auth')
if (address) { // [!code ++]
return ( // [!code ++]
// [!code ++]
{address.slice(0, 6)}...{address.slice(-4)}
// [!code ++]
// [!code ++]
// [!code ++]
) // [!code ++]
} // [!code ++]
return (
)
}
```
:::
#### Done
You have successfully set up passkey authentication with 1auth!
::::
### Using the SDK directly
If you prefer not to use wagmi, you can use the SDK's `authWithModal()` method directly:
```tsx
import { OneAuthClient } from '@rhinestone/1auth'
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
})
// Open the authentication modal
const result = await client.authWithModal()
if (result.success) {
console.log('Connected as:', result.user?.username)
console.log('Address:', result.user?.address)
}
```
### How it works
1. **User clicks sign in** - Your app calls `authWithModal()` or uses the wagmi connector
2. **1auth modal opens** - User enters their username or creates a new account
3. **Passkey prompt** - Device prompts for Face ID / Touch ID
4. **Success** - You receive the username and wallet address
The entire flow happens in a secure iframe. Your app never sees the passkey credentials.
### Next steps
* [Authentication](/guides/authentication) - Learn about message signing and verification
* [Checkout](/guides/checkout) - Add payments with PayButton
## Theming
Customize the appearance of 1auth dialogs to match your brand. Set color mode and accent colors when initializing the client or update them at runtime.
### ThemeConfig
Pass a `theme` option when creating your client:
```ts
import { OneAuthClient } from '@rhinestone/1auth'
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
theme: { // [!code ++]
mode: 'dark', // 'light' | 'dark' | 'system' // [!code ++]
accent: '#7c3aed', // Hex color for buttons and interactive elements // [!code ++]
}, // [!code ++]
})
```
### Theme options
| Property | Type | Description |
| -------- | ------------------------------- | ---------------------------------------------------------------- |
| `mode` | `'light' \| 'dark' \| 'system'` | Color mode. "system" follows the user's OS preference |
| `accent` | `string` | Hex color for buttons and interactive elements (e.g., "#7c3aed") |
### Update theme at runtime
Change the theme dynamically using `setTheme()`:
```ts
// Switch to dark mode
client.setTheme({ mode: 'dark' })
// Change accent color
client.setTheme({ accent: '#ec4899' })
// Update both
client.setTheme({
mode: 'light',
accent: '#3b82f6',
})
```
### Per-method theming
You can also pass theme options to individual methods:
```ts
const result = await client.signMessage({
username: 'alice',
message: 'Hello World',
theme: {
mode: 'dark',
accent: '#10b981',
},
})
```
### Planned features
:::warning
These features are coming soon.
:::
* **Typography** - Custom fonts and text styles
* **Border radius** - Rounded corners configuration
* **CSS variables** - Full control via CSS custom properties
### Next steps
* [Signup & Login](/guides/signup-login) - Add passkey authentication
* [Checkout](/guides/checkout) - Add payments with PayButton
## Token Requests
The `tokenRequests` parameter tells the orchestrator what tokens are needed to execute your transaction. This enables automatic cross-chain funding - users can pay from any chain with any token.
### Rhinestone Warp
1auth is powered by [Rhinestone Warp](https://docs.rhinestone.dev/intents/use-cases/chain-abstracted-swaps) - a cross-chain intent protocol that handles asset movement automatically. When you submit an intent:
1. Warp's solver network finds the optimal route across chains
2. Solvers compete to fulfill your intent at the best price
3. Assets are bridged, swapped, and delivered atomically
4. You get execution guarantees - it either completes fully or reverts
This means users don't need to think about which chain their funds are on. They just sign once, and Warp handles bridging, swapping, and gas across all chains.
### User Experience
When `tokenRequests` is specified, users see exactly what tokens will be used in the signing dialog. This provides full transparency about the transaction before they approve it.
The dialog shows:
* The token being transferred (e.g., USDC)
* The amount in human-readable format
* The destination chain
* Where the funds are coming from (if cross-chain)
### When to Use tokenRequests
Use `tokenRequests` when your transaction requires specific tokens to be delivered:
| Scenario | tokenRequests |
| ------------------------------- | ----------------------------------------------------------------- |
| ERC20 transfer via `sendIntent` | **Required** - specify the token and amount being sent |
| Contract call needing tokens | **Required** - specify tokens the contract will consume |
| Native ETH transfer | Not needed - use `value` field instead |
| Swap via `sendSwap` | Built automatically - `sendSwap` creates tokenRequests internally |
:::info
The `sendSwap` helper internally builds `tokenRequests` for you based on the `toToken` and `amount` parameters. You only need to specify `tokenRequests` manually when using `sendIntent` directly.
:::
### How It Works
1. You specify what tokens are needed via `tokenRequests`
2. Orchestrator checks user's balances across all chains
3. Orchestrator finds the optimal route (bridges, swaps) to acquire those tokens
4. User signs once - orchestrator handles everything else
5. Tokens are delivered and your calls execute atomically
### Interface
```tsx
interface TokenRequest {
token: string; // Token contract address on target chain
amount: bigint; // Amount in base units (use parseUnits)
}
```
### Example: ERC20 Transfer
When sending USDC, tell the orchestrator you need USDC:
```tsx
import { encodeFunctionData, parseUnits } from 'viem';
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const amount = parseUnits('100', 6); // 100 USDC
const transferData = encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [recipientAddress, amount],
});
const result = await client.sendIntent({
username: 'alice',
targetChain: 8453, // Base
calls: [{
to: USDC_BASE,
data: transferData,
label: 'Send USDC',
sublabel: '100 USDC',
}],
tokenRequests: [{
token: USDC_BASE,
amount: amount,
}],
});
```
Even if Alice has no USDC on Base, the orchestrator will:
* Find her assets on other chains (e.g., USDC on Ethereum, ETH on Arbitrum)
* Bridge/swap to get 100 USDC on Base
* Execute the transfer
### Using with EIP-1193 Provider
The `tokenRequests` parameter also works with [`eth_sendTransaction`](/rpc/eth-send-transaction) and [`wallet_sendCalls`](/rpc/wallet-send-calls):
```tsx
import { createOneAuthProvider } from '@rhinestone/1auth';
const provider = createOneAuthProvider({ client, chainId: 84532 });
// Single transaction with tokenRequests
await provider.request({
method: 'eth_sendTransaction',
params: [{
to: USDC_ADDRESS,
data: transferData,
tokenRequests: [{ token: USDC_ADDRESS, amount }],
}],
});
// Batch calls with tokenRequests
await provider.request({
method: 'wallet_sendCalls',
params: [{
chainId: 84532,
calls: [
{ to: USDC_ADDRESS, data: transfer1Data },
{ to: USDC_ADDRESS, data: transfer2Data },
],
tokenRequests: [{ token: USDC_ADDRESS, amount: totalAmount }],
}],
});
```
### Constraining Source Assets
Use `sourceAssets` to limit which tokens the orchestrator can use as input:
```tsx
const result = await client.sendIntent({
username: 'alice',
targetChain: 8453,
calls: [{ to: USDC_BASE, data: transferData }],
tokenRequests: [{ token: USDC_BASE, amount }],
sourceAssets: ['USDC'], // Only use USDC from any chain
});
```
Without `sourceAssets`, the orchestrator picks the cheapest route from all available balances.
### Usage in Components
#### PayButton
```tsx
Pay 100 USDC
```
See [PayButton](/sdk/pay-button) for more details.
## Integrate into viem / wagmi
Already have an app built with viem or wagmi? You can add 1auth as a wallet provider in minutes. 1auth provides a standard EIP-1193 compatible provider that works seamlessly with any web3 library.
### Why Integrate?
* **Keep your existing code** - No need to rewrite your dApp, just add 1auth as another connector
* **Passkey authentication** - Give your users the option to authenticate with Face ID / Touch ID
* **Chain abstraction** - Your users get unified balances and automatic bridging
* **Works alongside other wallets** - Offer 1auth alongside MetaMask, WalletConnect, and others
This approach is ideal for:
* **Existing dApps** that already use viem/wagmi and want to add passkey support
* **Multi-wallet apps** that want 1auth as one connector option alongside MetaMask, WalletConnect, etc.
* **Developers familiar with the Ethereum stack** who want standard wallet APIs
### Setup
:::code-group
```bash [npm]
npm install @rhinestone/1auth
```
```bash [yarn]
yarn add @rhinestone/1auth
```
```bash [pnpm]
pnpm add @rhinestone/1auth
```
:::
```typescript
import { OneAuthClient, createOneAuthProvider } from '@rhinestone/1auth';
const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
});
const provider = createOneAuthProvider({ client, chainId: 8453 });
```
### With viem
```typescript
import { createWalletClient, custom } from 'viem';
import { base } from 'viem/chains';
const walletClient = createWalletClient({
chain: base,
transport: custom(provider),
});
// Connect
const [address] = await walletClient.requestAddresses();
// Send transaction
const hash = await walletClient.sendTransaction({
to: '0x...',
value: parseEther('0.01'),
});
```
### With wagmi
```typescript
import { createConfig, http } from 'wagmi';
import { base } from 'wagmi/chains';
import { oneAuth } from '@rhinestone/1auth/wagmi';
const config = createConfig({
chains: [base],
connectors: [oneAuth({ client, chainId: base.id })],
transports: { [base.id]: http() },
});
```
Then use standard wagmi hooks:
```tsx
import { useConnect, useAccount } from 'wagmi';
function App() {
const { connect, connectors } = useConnect();
const { address, isConnected } = useAccount();
if (!isConnected) {
return ;
}
return
Connected: {address}
;
}
```
### Supported Methods
| Method | Description |
| ------------------------ | ----------------------------- |
| `eth_requestAccounts` | Connect and get address |
| `eth_accounts` | Get connected address |
| `personal_sign` | Sign message (EIP-191) |
| `eth_signTypedData_v4` | Sign typed data (EIP-712) |
| `eth_sendTransaction` | Send transaction |
| `wallet_sendCalls` | Batch transactions (EIP-5792) |
| `wallet_getCallsStatus` | Check batch status |
| `wallet_getCapabilities` | Query wallet capabilities |
### Events
```typescript
provider.on('accountsChanged', (accounts) => console.log('Account:', accounts[0]));
provider.on('chainChanged', (chainId) => console.log('Chain:', chainId));
provider.on('disconnect', () => console.log('Disconnected'));
```
See [SDK Reference](/sdk) for more details.
## Swap
Swap tokens on any chain or bridge across chains with `sendSwap`. Users authenticate once with their passkey, and the swap executes automatically.
### Using sendSwap
The `sendSwap` method handles token swaps and cross-chain bridges with built-in slippage protection.
:::steps
#### Install the SDK
```bash
npm install @rhinestone/1auth
```
#### Create client
```ts [client.ts]
import { OneAuthClient } from '@rhinestone/1auth'
export const client = new OneAuthClient({
providerUrl: 'https://passkey.1auth.box',
})
```
#### Swap tokens
```ts
const result = await client.sendSwap({
username: 'alice',
targetChain: 8453, // Base
fromToken: 'ETH',
toToken: 'USDC',
amount: '0.1', // Human-readable amount of toToken
})
if (result.success) {
console.log('Swap executed:', result.intentId)
console.log('Quote:', result.quote)
}
```
#### Done
You have successfully swapped tokens!
:::
### Cross-chain bridge
`sendSwap` automatically handles cross-chain bridging. The user's funds are sourced from any chain where they have balance:
```ts
// Bridge ETH from any chain to USDC on Base
const result = await client.sendSwap({
username: 'alice',
targetChain: 8453, // Base
fromToken: 'ETH',
toToken: 'USDC',
amount: '100', // Get 100 USDC on Base
})
```
The orchestrator finds the best route, whether that's a same-chain swap or a cross-chain bridge.
### Slippage control
Set maximum slippage in basis points (1 bp = 0.01%):
```ts
const result = await client.sendSwap({
username: 'alice',
targetChain: 8453,
fromToken: 'ETH',
toToken: 'USDC',
amount: '100',
slippageBps: 100, // 1% max slippage (default: 50 = 0.5%)
})
```
### Supported tokens
You can use token symbols or addresses:
```ts
// Using symbols (ETH, USDC, USDT, WETH, etc.)
await client.sendSwap({
username: 'alice',
targetChain: 8453,
fromToken: 'ETH',
toToken: 'USDC',
amount: '100',
})
// Using addresses
await client.sendSwap({
username: 'alice',
targetChain: 8453,
fromToken: '0x0000000000000000000000000000000000000000', // ETH
toToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
amount: '100',
})
```
### Constraining source assets
By default, `sendSwap` uses the `fromToken` as the source. You can constrain which tokens the orchestrator uses:
```ts
const result = await client.sendSwap({
username: 'alice',
targetChain: 8453,
fromToken: 'ETH',
toToken: 'USDC',
amount: '100',
sourceAssets: ['ETH', 'WETH'], // Only use ETH or WETH as input
})
```
### Swap quote
The result includes quote information:
```ts
const result = await client.sendSwap({
username: 'alice',
targetChain: 8453,
fromToken: 'ETH',
toToken: 'USDC',
amount: '100',
})
if (result.success && result.quote) {
console.log('From:', result.quote.fromToken)
console.log('To:', result.quote.toToken)
console.log('Amount in:', result.quote.amountIn)
console.log('Amount out:', result.quote.amountOut)
console.log('Rate:', result.quote.rate)
console.log('Price impact:', result.quote.priceImpact)
}
```
### SendSwapOptions
| Property | Type | Required | Description |
| ---------------- | --------------- | -------- | --------------------------------------------------------- |
| `username` | `string` | Yes | Username of the signer |
| `targetChain` | `number` | Yes | Chain ID where swap executes |
| `fromToken` | `string` | Yes | Token to swap from (address or symbol like "ETH", "USDC") |
| `toToken` | `string` | Yes | Token to swap to (address or symbol) |
| `amount` | `string` | Yes | Amount in human-readable format (e.g., "0.1") |
| `slippageBps` | `number` | No | Max slippage in basis points. Default: 50 (0.5%) |
| `sourceAssets` | `string[]` | No | Override source assets for the swap |
| `closeOn` | `CloseOnStatus` | No | When to close dialog. Default: "preconfirmed" |
| `waitForHash` | `boolean` | No | Wait for transaction hash before resolving |
| `hashTimeoutMs` | `number` | No | Max time to wait for hash in ms |
| `hashIntervalMs` | `number` | No | Poll interval for hash in ms |
### SendSwapResult
| Property | Type | Description |
| ----------------- | ----------------------------------- | ----------------------------------------- |
| `success` | `boolean` | Whether the swap was successful |
| `intentId` | `string` | Intent ID for status tracking |
| `transactionHash` | `string` | Transaction hash (if `waitForHash: true`) |
| `quote` | `SwapQuote` | Quote information for the executed swap |
| `error` | `{ code: string; message: string }` | Error details if failed |
### Next steps
* [Token Requests](/guides/crosschain/token-requests) - Request specific token outputs
* [Batch Transactions](/guides/batch-transactions) - Queue multiple operations
## Token Requests
Token requests enable the **output-first model** for crosschain transactions. Instead of specifying which tokens to spend (input), you specify what tokens you need to receive (output). The orchestrator automatically finds the optimal route to deliver those tokens.
### Why output-first?
Traditional blockchain transactions require you to:
1. Know exactly which token to spend
2. Calculate swap rates and slippage
3. Handle bridging between chains
4. Manage gas tokens on each chain
With token requests, you simply say "I need 100 USDC on Base" and the orchestrator handles everything else.
#### Benefits
* **Simpler UX** - Users don't need to think about routing or bridging
* **Optimal routing** - Orchestrator finds the cheapest path across all chains
* **Flexible funding** - Uses whatever assets the user has available
* **Single signature** - User signs once with their passkey
### Using token requests
Add `tokenRequests` to your `sendCalls` to specify what tokens you need:
:::code-group
```tsx [Example.tsx]
import { useSendCalls } from 'wagmi'
import { parseUnits } from 'viem'
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
export function BuyWithUSDC() {
const { sendCalls, isPending } = useSendCalls()
const handlePurchase = () => {
sendCalls({
calls: [{
to: '0x...', // Your contract
data: '0x...', // Purchase calldata
}],
chainId: 8453, // Execute on Base
tokenRequests: [ // [!code ++]
{ // [!code ++]
token: USDC_BASE, // [!code ++]
amount: parseUnits('50', 6), // 50 USDC // [!code ++]
}, // [!code ++]
], // [!code ++]
})
}
return (
)
}
```
:::
### How it works
When you include `tokenRequests`:
1. **Orchestrator analyzes** the user's balances across all supported chains
2. **Finds optimal route** - cheapest combination of bridges and swaps
3. **Executes atomically** - bridging, swapping, and your calls all succeed or fail together
4. **Delivers tokens** - the requested tokens arrive on the target chain before your calls execute
### TokenRequest object
| Field | Type | Description |
| -------- | -------- | ---------------------------------------------------------------- |
| `token` | `string` | ERC-20 token contract address on the target chain |
| `amount` | `bigint` | Amount in base units (e.g., `parseUnits('100', 6)` for 100 USDC) |
### Multiple token requests
You can request multiple tokens in a single transaction:
```tsx
sendCalls({
calls: [{ to: '0x...', data: '0x...' }],
chainId: 8453,
tokenRequests: [
{
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
amount: parseUnits('100', 6),
},
{
token: '0x4200000000000000000000000000000000000006', // WETH
amount: parseUnits('0.05', 18),
},
],
})
```
### Common use cases
#### E-commerce checkout
User wants to buy an item priced in USDC, but only has ETH on mainnet:
```tsx
sendCalls({
calls: [{
to: SHOP_CONTRACT,
data: encodeFunctionData({
abi: shopAbi,
functionName: 'purchase',
args: [itemId],
}),
}],
chainId: 8453, // Shop is on Base
tokenRequests: [{
token: USDC_BASE,
amount: parseUnits('29.99', 6), // Item price
}],
})
```
#### DeFi interactions
Provide liquidity with tokens from any chain:
```tsx
sendCalls({
calls: [{
to: LP_CONTRACT,
data: encodeFunctionData({
abi: lpAbi,
functionName: 'addLiquidity',
args: [amount0, amount1],
}),
}],
chainId: 42161, // Arbitrum
tokenRequests: [
{ token: USDC_ARB, amount: parseUnits('1000', 6) },
{ token: WETH_ARB, amount: parseUnits('0.5', 18) },
],
})
```
#### NFT minting
Mint an NFT with payment in any token:
```tsx
sendCalls({
calls: [{
to: NFT_CONTRACT,
data: encodeFunctionData({
abi: nftAbi,
functionName: 'mint',
args: [tokenId],
}),
}],
chainId: 8453,
tokenRequests: [{
token: USDC_BASE,
amount: parseUnits('0.5', 6), // Mint price
}],
})
```
### Next steps
* [Crosschain](/guides/crosschain) - Back to crosschain overview
* [Batch Transactions](/guides/batch-transactions) - Combine multiple operations
## Lost Passkey Recovery
If a user loses their phone, gets a new laptop, or their passkey simply stops working, they can recover their account through **guardians** — trusted backup signers that can authorize adding a new passkey.
### Guardians
A guardian is someone (or something) that can vouch for the account owner during recovery. 1auth supports two types:
* **Google account** — The user links their Google account during setup. During recovery, they sign in with Google to prove their identity. This is the recommended default.
* **Personal backup** — The user creates a passphrase-based backup key. The passphrase never leaves the browser — it's used locally to derive a signing key. This works fully offline, but the user must remember or securely store the passphrase.
Both guardians can be set up together with a threshold: require **one** (either guardian can authorize recovery) or **both** (both must approve). Most users should use a threshold of one for simplicity.
### Setting Up Recovery
Users set up recovery directly in the 1auth dialog — the same popup they use to sign in and approve transactions. The setup flow walks them through three steps:
1. **Link a Google account** — Sign in with Google to register it as an external guardian. This binds the user's Google identity to the account on-chain.
2. **Optional personal backup** — Create a passphrase-based backup key, or connect an external wallet as a second guardian
3. **Confirm with biometrics** — Approve the guardian configuration with Face ID, Touch ID, or another authenticator. The setup is deployed on-chain across all supported chains.
Once configured, the user's account can be recovered even if their original passkey device is completely lost.
### Recovering an Account
When a user needs to recover, they open the recovery flow from any app that integrates 1auth:
1. **Identify their account** — Enter their account address or username. The system looks up the guardian configuration on-chain.
2. **Register a new passkey** — Create a new passkey on their current device using Face ID, Touch ID, or another authenticator
3. **Prove their identity** — Sign in with Google (or enter their passphrase) to authenticate with the guardian. The guardian verifies this against the identity linked during setup.
4. **Automatic execution** — The recovery transaction is submitted across all chains where the account is deployed. Once confirmed, the user can sign in with their new passkey immediately.
The entire process takes a few minutes. No funds are at risk during recovery — the account remains secure and unchanged until the recovery transaction completes.
### For Developers
To let users set up recovery from your app, open the account management dialog:
```typescript
await client.openAccountDialog();
```
This opens the 1auth dialog where users can configure their guardians, link their Google account, and set a recovery threshold. The dialog handles the full setup flow including on-chain deployment.
If a user has lost their passkey and needs to recover, the recovery flow is accessible from the 1auth login dialog — users click "Recover" to start the process. No additional SDK integration is needed.
Users can also manage their recovery settings at [account.1auth.box](https://account.1auth.box).
## Recovery
With traditional authentication, losing access usually means losing your account. Password resets depend on email providers. Seed phrases, once lost, are gone forever. 1auth takes a different approach.
Every 1auth account is a smart contract on the blockchain. The account's rules — who can sign, who can authorize recovery — are stored on-chain, not in any single company's database. This means recovery can work through multiple independent paths, and no single point of failure can permanently lock a user out.
### Two Recovery Scenarios
Recovery in 1auth is designed around two fundamentally different situations:
#### The user loses their passkey
A phone breaks, a laptop is stolen, or a passkey simply stops working. The user is fine — they just need a way to prove who they are and register a new passkey on a new device. This is handled by **guardians**: trusted signers that the user sets up in advance (like a linked Google account) that can vouch for their identity and authorize adding a new passkey.
[Learn how lost passkey recovery works](/architecture/lost-passkey)
#### The service goes offline
The 1auth service itself becomes unavailable — temporarily or permanently. In this case, the user still has their passkey, but the infrastructure they normally interact with is gone. Because the account and its recovery configuration live entirely on-chain, **independent signers** that operate outside of 1auth can step in. The user proves their identity directly to these signers, who authorize recovery by reading verification data straight from the blockchain.
[Learn how trustless recovery works](/architecture/trustless-recovery)
### The Underlying Principle
Both scenarios share the same design principle: the user's account is defined by on-chain state, not by any service's availability. Guardians, identity commitments, and signer configurations are all stored in the smart contract. Any authorized party — whether it's the 1auth backend, an independent signer, or a decentralized network — can read that state and help the user recover, as long as the user can prove they are who they say they are.
## Trustless Recovery
1auth accounts are self-custodial smart contracts deployed on-chain. If the 1auth service goes offline — whether temporarily or permanently — users never lose access to their funds or their account. This page explains the safeguards that make this possible.
### Your Account Lives On-Chain
When a user creates a 1auth account, a smart contract is deployed to the blockchain. This contract holds the user's assets and enforces who can authorize transactions. The 1auth service helps manage this account, but it does not control it — only the user's passkey (or their recovery guardians) can authorize changes.
If 1auth becomes unavailable:
* **Funds remain safe** — Assets are in the on-chain smart contract, not in any 1auth database
* **Account state is unchanged** — The account's signers, guardians, and configuration are all stored on-chain
* **No one can lock you out** — 1auth has no admin key or backdoor into user accounts
### Independent Recovery Signers
To ensure recovery works without the 1auth backend, the guardian system uses **independent recovery signers** — services that run separately from 1auth and can authorize recovery on their own.
When a user sets up recovery with their Google account, the linked guardian is an independent signing service. This service:
* Runs on its own infrastructure, separate from 1auth
* Reads the user's guardian configuration directly from the blockchain
* Verifies the user's identity through Google sign-in
* Authorizes recovery without contacting the 1auth backend at any point
Because the signer reads all verification data from the blockchain and the identity provider directly, it has no dependency on 1auth being online.
### Lit Protocol: Decentralized Recovery
:::note[Coming Soon]
Lit Protocol integration is currently in development. Once live, it will provide a fully decentralized recovery path with no single point of failure.
:::
For even stronger guarantees, 1auth will use [Lit Protocol](https://litprotocol.com) as a decentralized recovery signer. Instead of relying on a single signing service, the recovery key is split across a distributed network of nodes.
How it works:
* **No single point of failure** — The signing key is distributed across the Lit network. No single node holds the complete key.
* **Threshold signing** — A minimum number of nodes must agree before a recovery signature is produced. Compromising a few nodes is not enough to forge a signature.
* **Programmable conditions** — Recovery is only authorized when the user proves their identity (e.g., Google sign-in), enforced by the network itself
* **Fully independent** — The Lit network operates independently of 1auth, its infrastructure, and its team
This means that even in the worst case — 1auth shuts down entirely, its servers go offline, and its team is unreachable — users will be able to recover their accounts through the Lit network by proving their identity.
### The Self-Custody Guarantee
Traditional wallets and auth providers create a dependency: if the service disappears, users may lose access. 1auth is built differently:
| Scenario | What happens |
| ----------------------------------- | ------------------------------------------------------------------------------ |
| 1auth service goes down temporarily | Users can still transact via recovery signers or wait for service to return |
| 1auth shuts down permanently | Users recover via independent signers and Lit Protocol |
| A recovery signer goes offline | Other guardians (personal backup, Lit Protocol) can still authorize recovery |
| User loses their passkey | Guardians authorize adding a new passkey — no dependency on any single service |
The account is always the user's. The infrastructure around it is designed so that no single failure — technical or organizational — can lock them out.