# Sembol documentation (@sembol/passkey-react)
Generated from the same source as https://sembol.xyz/docs.
# Introduction
Sembol is an open-source React library where Face ID becomes a self-custodial Stellar smart account. No seed phrases, no extensions.
**@sembol/passkey-react** gives your app real Stellar wallets with nothing to install and nothing to write down. A wallet is an [OpenZeppelin smart account](https://github.com/OpenZeppelin/stellar-contracts) contract on Stellar, owned by a passkey in the user's device. Your users tap Face ID; your code calls hooks.
> **Proven, not promised.** This entire site, including [the wallet you can open right now](https://sembol.xyz/wallet), runs on the public npm package. The full passkey journey passes end to end against live Stellar testnet in CI.
## Why Sembol
- **Self-custodial by construction**: the passkey never leaves the device's secure enclave, and the wallet is a contract the user owns on-chain. There is no server copy and no vendor custody.
- **No seed phrases**: passkeys sync through iCloud Keychain and Google Password Manager the way passwords already do.
- **Sponsored onboarding**: wallet creation rides a fee relayer, so users start with zero XLM.
- **Your brand, one prop**: a typed `theme` restyles every component. Try it in the [theme builder](https://sembol.xyz/customize).
- **Recovery is on-chain**: backup signers, recovery credentials, and spending caps are enforced by the contract, not by an API.
## The stack
| Layer | What it is |
| --- | --- |
| Your app | React, any bundler. Next.js and Vite both verified |
| @sembol/passkey-react | Provider, typed hooks, prebuilt themeable components (this library) |
| smart-account-kit | The official low-level Stellar SDK Sembol builds on |
| OpenZeppelin contracts | Audited smart account contracts on Stellar (RC v0.7.0 audit, April 2026) |
## Where to go next
- [Quickstart](https://sembol.xyz/docs/quickstart): a working wallet in one component.
- [Theming](https://sembol.xyz/docs/theming): make it look like your product, not ours.
- [Live Storybook](https://storybook.sembol.xyz): every component, every state, interactive.
---
# Quickstart
Install @sembol/passkey-react and ship a working Stellar passkey wallet in one component.
## Install
```bash
npm install @sembol/passkey-react
```
## Wrap and render
One provider, one stylesheet import, and two components:
```tsx title="app.tsx"
import {
PasskeyWalletProvider,
CreateWalletButton,
ConnectWalletButton,
SEMBOL_TESTNET_ARTIFACTS,
} from "@sembol/passkey-react";
import "@sembol/passkey-react/styles.css";
export default function App() {
return (
);
}
```
That is a complete testnet wallet: create with Face ID, sponsored contract deployment, automatic session restore on reload. The provider is SSR safe, so it wraps a Next.js App Router tree directly.
## Read state, send a payment
```tsx
import {
usePasskeyWallet,
useWalletBalance,
useTransfer,
} from "@sembol/passkey-react";
function Wallet() {
const { isConnected, address } = usePasskeyWallet();
const { formatted, symbol } = useWalletBalance();
const { transfer, status } = useTransfer();
if (!isConnected) return null;
return (
<>
{address}
{formatted} {symbol}
>
);
}
```
## Make it yours
```tsx
```
One `accent` retints every component. Build a full theme visually in the [theme builder](https://sembol.xyz/customize) and copy the prop out, or read [Theming](https://sembol.xyz/docs/theming) for the whole API.
> **Warning:** **Passkeys are per domain.** A wallet created on `localhost` will not appear on your production domain. Each domain gets its own passkeys, by WebAuthn design.
---
# Configuration
SembolConfig fields, the testnet preset, relayer requirements, and environment overrides.
`SEMBOL_TESTNET_ARTIFACTS` is a complete, working testnet preset: RPC, network passphrase, the deployed contract hashes, and a public fee relayer. Spread it and override any field.
```ts
import { SEMBOL_TESTNET_ARTIFACTS, type SembolConfig } from "@sembol/passkey-react";
const config: SembolConfig = {
...SEMBOL_TESTNET_ARTIFACTS,
appName: "My App", // shown in the passkey prompt
relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL
?? SEMBOL_TESTNET_ARTIFACTS.relayerUrl,
};
```
## Fields
| Field | Purpose |
| --- | --- |
| `rpcUrl` | Soroban RPC endpoint |
| `networkPassphrase` | Stellar network. Drives explorer links and network display |
| `accountWasmHash` | Smart account contract wasm hash to deploy |
| `webauthnVerifierAddress` | On-chain secp256r1 verifier used by passkey signatures |
| `ed25519VerifierAddress` | Verifier for Ed25519 backup signers |
| `spendingLimitPolicyAddress` | Policy contract behind the spending limit feature |
| `nativeTokenContract` | XLM SAC address. Derived from the passphrase when omitted |
| `relayerUrl` | Fee relayer endpoint. **Required for wallet creation** since smart-account-kit 0.5.0 |
| `appName` | Name in the passkey prompt. Defaults to "Stellar App" |
| `rpId` | WebAuthn relying party id. Defaults to the current domain |
| `webAuthnHints` | Passkey UI ordering hints, e.g. `["client-device", "hybrid"]` |
## The relayer requirement
Since smart-account-kit 0.5.0, the shared deployer is sign-only: **wallet creation must go through a fee relayer**, there is no RPC fallback. The testnet preset points at the public SDF relayer proxy, so testnet works with zero setup. Mainnet currently requires running your own relayer; Sembol Cloud, a hosted option, is in the works.
## Session persistence
Sessions persist in IndexedDB by default, so a reload silently reconnects. Inject your own `storage` adapter or a prebuilt `kit` instance through the provider for tests and advanced setups.
---
# Components
Prebuilt, themeable React components: provider, create, connect, balance, signing modal, signers, recovery, spending limits.
Every component ships styled by the default stylesheet, themeable through the [theme prop](https://sembol.xyz/docs/theming), and escapable via `unstyled`. See each one live, in every state, in the [Storybook](https://storybook.sembol.xyz).
## PasskeyWalletProvider
The root. Builds the smart-account-kit instance, restores sessions, exposes everything to hooks, and applies your `theme`.
```tsx
{children}
```
## CreateWalletButton
The full creation ceremony behind one button: passkey registration, sponsored contract deployment, optional funding. Props: `label`, `nickname`, `fund`, `variant`, `size`, `unstyled`, `onCreated`, `onError`.
## ConnectWalletButton
Connects or restores a wallet. Once connected it becomes an account chip with a menu: copy address, view on stellar.expert, switch wallet, disconnect. Props: `label`, `variant`, `size`, `unstyled`, `onConnected`, `onDisconnected`, `onError`.
## WalletBalance
Live XLM balance with an optional refresh control and polling. Props: `pollInterval`, `showRefresh`, `unstyled`.
## SignTransactionModal
Reviews and signs any `AssembledTransaction` with the passkey: human-readable summary, fee, network, then Face ID. Drive it with state:
```tsx
const [tx, setTx] = useState | null>(null);
setTx(null)}
onSuccess={({ hash }) => console.log(hash)}
/>
```
## Security components
| Component | What it does |
| --- | --- |
| `SignerList` | Every signer on the account, with guarded removal |
| `AddSignerButton` | Add a backup passkey or an Ed25519 public key as a signer |
| `RecoverySetup` | Enroll recovery credentials, or run recovery on a new device (`mode="recover"`) |
| `SpendingPolicyForm` | Set an on-chain spending limit per time window |
---
# Hooks
Headless typed hooks: wallet state, creation, connection, balance, transfers, signing, signers, recovery, spending policy.
Everything the components do is available headless. All hooks require a `PasskeyWalletProvider` above them.
## usePasskeyWallet
```ts
const {
kit, // the smart-account-kit instance
status, // "initializing" | "disconnected" | "connected" ...
isConnected,
address, // C... contract address
capabilities, // WebAuthn support detection
config, // resolved config incl. explorerBaseUrl
connect, createWallet, disconnect, fund,
} = usePasskeyWallet();
```
## Flows
| Hook | Returns |
| --- | --- |
| `useCreateWallet()` | `createWallet` with per-phase progress: `passkey`, `deploying`, `funding` |
| `useConnectWallet()` | `connect` with status |
| `useTransfer()` | `transfer({ to, amount })` building, signing, and submitting an XLM payment |
| `useSignTransaction()` | sign any `AssembledTransaction` with the passkey |
| `useRecovery()` | enroll and execute recovery |
## State
| Hook | Returns |
| --- | --- |
| `useWalletBalance()` | `formatted`, `raw` stroops, `symbol`, `status`, `refetch`, `isRefreshing` |
| `useWalletAddress()` | `address`, `copy()` with `copied` feedback, `explorerUrl` |
| `useSigners()` | the signer list, live |
| `useAddSigner()` / `useRemoveSigner()` | signer mutations |
| `useSpendingPolicy()` | read and set the on-chain limit |
## Utilities
```ts
import { buildTransferTransaction, toSembolError } from "@sembol/passkey-react";
// arbitrary token transfers (returns an AssembledTransaction for the modal)
const tx = await buildTransferTransaction(kit, {
tokenContract: config.nativeTokenContract,
to, amount,
});
// every thrown error normalizes to a typed SembolError
try { ... } catch (e) {
toast(toSembolError(e).userMessage);
}
```
---
# Theming
One typed theme prop restyles every component: accent derivation, 19 tokens per scheme, radius, fonts, presets, scoped themes, CSS variables.
Sembol should look like **your** product. One typed prop does it, and everything compiles down to CSS custom properties you can also set by hand.
```tsx
```
> Build a theme visually in the [theme builder](https://sembol.xyz/customize): live components, every control typed, and the exact prop generated for copy-paste.
## The SembolTheme object
| Option | Values |
| --- | --- |
| `accent` | Any CSS color. Hover, active, muted, and the focus ring derive from it per scheme via `color-mix()` |
| `colors` | Fine-grained overrides, 19 tokens: `bg`, `surface`, `surfaceHover`, `border`, `borderStrong`, `fg`, `fgMuted`, `onAccent`, `success*`, `danger*`, `overlay`, and the accent family |
| `darkColors` | Dark-scheme overrides, layered on top of `colors` |
| `radius` | `"none" | "sm" | "md" | "lg" | "full"` or a number in px |
| `fonts` | `{ body, mono }` CSS stacks |
| `shadows` | `false` for a flat UI |
| `colorScheme` | `"light" | "dark" | "auto"`; the provider manages `data-sembol-theme` for you |
## Presets
```tsx
import { sembolThemes } from "@sembol/passkey-react";
// gold on ink
```
Four ship today: `seal`, `ocean`, `forest`, `mono`. See them side by side in [Storybook: Theming/Presets](https://storybook.sembol.xyz).
## Scoped themes
```ts
import { sembolThemeToCss } from "@sembol/passkey-react";
// theme only one subtree, e.g. an embedded checkout
const css = sembolThemeToCss({ accent: "#16a34a" }, ".checkout");
```
## Plain CSS
Every visual decision is a `--sembol-*` custom property. Override them after the stylesheet import and skip the prop entirely; dark mode is the `data-sembol-theme="dark"` attribute, or the OS preference when the attribute is absent.
```css
:root {
--sembol-color-accent: #e11d48;
--sembol-radius: 6px;
--sembol-font: "Inter", sans-serif;
}
```
---
# Signers & recovery
On-chain security: backup signers, recovery credentials, spending limits enforced by the smart account contract.
A Sembol wallet is a contract, so security is on-chain and survives Sembol itself: extra signers, recovery, and spending caps are enforced by the smart account, not by an API someone could turn off.
## Backup signers
Add a second passkey (another device) or an Ed25519 public key whose secret lives offline. `SignerList` + `AddSignerButton` cover the UI; `useSigners`, `useAddSigner`, `useRemoveSigner` are the headless path. The last signer cannot lock itself out through the UI.
## Recovery
Enroll a recovery credential while the device is still in hand (`RecoverySetup`). To get back in later: open the app on any device and run `RecoverySetup mode="recover"`, which is exactly what the [demo wallet's create page](https://sembol.xyz/wallet) does under "Lost your device?".
## Spending limits
`SpendingPolicyForm` installs an on-chain policy capping XLM out per time window. Payments beyond the cap are rejected by the contract during auth, not by client code. Changing the window re-installs the policy and asks for two approvals.
> **Warning:** Enroll recovery **before** you need it. A passkey that only ever lived on one lost, unsynced device is gone; a recovery credential or second signer is the way back in.
---
# Headless & advanced
unstyled components, injectable WebAuthn and storage adapters, kit injection, React Native readiness.
## unstyled
Every component accepts `unstyled` to drop all `sembol-*` classes and render bare, accessible markup for your own CSS. Or skip the stylesheet entirely and compose the hooks.
## Injectable adapters
The provider accepts injectable `webAuthn` functions (`startRegistration`, `startAuthentication`) and a `storage` adapter. These are the seams that make non-browser targets possible; React Native support is on the roadmap on exactly these hooks.
## Kit injection
```tsx
// tests and advanced setups: bring your own kit
```
## Errors
All failures normalize to `SembolError` with a stable `code` and a human `userMessage`. `contractCodeFromMessage` maps raw Soroban errors when you need to go deeper.
## Bundlers
Next.js (webpack and Turbopack) and Vite are both exercised: the library patches Next's Buffer polyfill gap automatically and stays inert under Vite, where it is not needed.
---
# Troubleshooting
Passkeys per domain, iCloud passkeys in Chrome, relayer errors, Buffer polyfill, domain moves.
| Symptom | Cause and fix |
| --- | --- |
| Connect finds no passkey | Passkeys are per domain. Create one on this domain first |
| Chrome cannot see iCloud passkeys | Enable "Use passkeys and passwords from iCloud Keychain" in `chrome://password-manager/settings`, or use Safari |
| Wallet creation fails instantly | A relayer is required since smart-account-kit 0.5.0. The testnet preset includes one; set `relayerUrl` for your own |
| `readBigInt64BE is not a function` | Old Next.js Buffer polyfill. Fixed automatically by this library since 0.3.1 |
| Wallets missing after a domain move | Same per-domain rule: wallets follow the domain that created them |
| Balance shows unavailable | Testnet RPC hiccup. `refetch` from `useWalletBalance`, and check the RPC URL |
## Still stuck
[Open an issue](https://github.com/keyboord01/sembol/issues) with the `SembolError` code from `toSembolError(err).code`, or read the source, it is small on purpose.
## For AI agents
These docs are machine-readable: [/llms.txt](https://sembol.xyz/llms.txt) indexes every page, [/llms-full.txt](https://sembol.xyz/llms-full.txt) is the whole documentation as one Markdown file, and every page is served as raw Markdown under `/md/docs/`.