> ## Documentation Index
> Fetch the complete documentation index at: https://developers.stellar.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Passkeys Prerequisites

> Passkeys are a great way to connect users with your dapp without the friction of seed phrases or browser extensions.

Passkeys are a great way to connect users with your dapp without the friction of seed phrases or browser extensions. Learn more on the [smart wallets guide](https://developers.stellar.org/docs/build/guides/contract-accounts/smart-wallets.md).

For this tutorial we'll be using [Smart Account Kit](https://github.com/stellar/smart-account-kit), which handles the WebAuthn ceremony and deploys an [OpenZeppelin Smart Account](https://docs.openzeppelin.com/stellar-contracts/accounts/smart-account) for each of your users.

There's exactly one thing to provision before we can write a line of passkey code: an API key for the relayer. The rest of this page covers the infrastructure that's quietly doing work on your behalf, which is worth understanding even though you won't configure any of it.

## OpenZeppelin Relayer

The [OpenZeppelin Relayer](https://docs.openzeppelin.com/relayer/guides/stellar-channels-guide), running the Stellar Channels plugin, submits transactions on your users' behalf through a pool of managed channel accounts. It takes care of fees, sequence numbers, and source accounts, so your users never need to hold XLM just to write a guestbook message. If you're coming from the EVM world, this is the rough equivalent of a [paymaster](https://eips.ethereum.org/EIPS/eip-4337#extension-paymasters).

We'll use the hosted Testnet relayer at `https://channels.openzeppelin.com/testnet`. To authenticate with it, you'll need an API key.

1. Visit [https://channels.openzeppelin.com/testnet/gen](https://channels.openzeppelin.com/testnet/gen) to generate a Testnet key.
2. Copy the key into your `.env` file:

   ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
   PRIVATE_RELAYER_BASE_URL="https://channels.openzeppelin.com/testnet"
   PRIVATE_RELAYER_API_KEY=<paste-your-testnet-api-key>
   ```

That's the whole prerequisite. Short and sweet!

> [!NOTE]
>
> SvelteKit doesn't decide what's private by looking at a variable's name. Every variable the app uses is declared in `src/env.ts` (the template ships with this file already filled in), and anything declared there is private unless it's marked `public: true`. Private variables like these two are only importable by server-side code via [`$app/env/private`](https://svelte.dev/docs/kit/environment-variables), so the relayer key never ships to the browser. The `PUBLIC_*` variables from `.env.example` are the ones marked `public: true`, which makes them importable from `$app/env/public`. The `PRIVATE_` and `PUBLIC_` prefixes are just our naming convention, to make each one's audience obvious at a glance.

> [!WARNING]
>
> **Never expose the relayer key client-side**
>
> Calls to the Channels relayer authenticate with the API key in an `Authorization: Bearer` header. Put that call in the browser and you've handed the key to anyone who opens their network tab, and they can then spend your credits on whatever they like. This tutorial keeps the key server-side by routing every submission through a same-origin `/api/send` route, which we'll build on the [next page](https://developers.stellar.org/docs/build/apps/guestbook/setup-passkeys.md).

## What's handled for you

A couple of jobs that a passkey dapp genuinely needs are taken care of without any setup on your part. It's worth knowing *who* is doing that work, though, because "I don't configure it" is not the same as "it isn't happening."

### Funding new wallets

[Friendbot](https://developers.stellar.org/docs/networks.md#friendbot) only funds classic `G...` accounts, so it can't top up a `C...` contract address directly. Smart Account Kit does that dance for you: ask it to fund a wallet and it creates a temporary account, funds *that* from Friendbot, and transfers the XLM on to your user's contract address. There's no funder keypair to generate and no secret key in your `.env`. We'll see the two places this shows up (at signup, and behind a "Fund Wallet" button) on the [frontend page](https://developers.stellar.org/docs/build/apps/guestbook/frontend.md).

### Finding a returning user's wallet

When a returning user taps their passkey, your dapp gets a credential ID back from the authenticator. Before it can do anything useful with that, it has to turn the credential ID into the smart account contract address the credential was added to, and that reverse lookup requires indexed network data.

Smart Account Kit ships an indexer client for exactly this, defaulting to Mercury's hosted `smart-account-indexer` on Testnet and Mainnet. Its read endpoints are public, so there's no account to create, no JWT to fetch, and no indexing program of your own to write and deploy. You can point `indexerUrl` at any wire-compatible provider, or pass `indexerUrl: false` to switch discovery off entirely.

> [!NOTE]
>
> The indexer is easy to overlook precisely *because* you never configure it, so it's worth being explicit about the division of labor. The relayer handles **submission and fee sponsorship**. The indexer handles the **credential-to-address lookup** that lets a returning user log in. They're separate pieces doing separate jobs, and an app that turns discovery off will fail to log users in on any device that hasn't seen that passkey before.

The other half of this story is local: we'll configure the kit with `IndexedDBStorage` so a connected session survives a page reload. That's a per-browser cache sitting in front of the indexer, not a substitute for it.

## Troubleshooting

Things that can trip you up while getting this wired up:

1. **The relayer returns a 401 or a 403.** Generate a fresh key at [https://channels.openzeppelin.com/testnet/gen](https://channels.openzeppelin.com/testnet/gen). Testnet keys are free to re-issue, so there's no harm in doing this whenever you're suspicious of one.
2. **Your `PRIVATE_*` variables come back undefined.** SvelteKit reads `.env` when the dev server starts. If you edited the file while `pnpm dev` was running, restart it.
3. **Login can't find a wallet you know you created.** You're probably in a different browser or profile than the one you signed up in, with an empty IndexedDB, which means you're exercising the indexer path. Give it a few ledgers to catch up on a freshly deployed wallet, and check your browser console for a failed request to the indexer.

If you get stuck, drop a question in the [`#passkeys`](https://discord.com/channels/897514728459468821/1250851135561142423) channel on the [Stellar Developer Discord](https://discord.gg/stellardev). There's usually somebody around who's ready and willing to help out!
