# 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/`.