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

# Use Passkeys as a PIN

> Secure wallet authentication with biometric passkeys instead of PINs

## Overview

WebAuthn passkeys replace PINs with biometric authentication (Face ID, Touch ID, Windows Hello). No PINs to remember or store.

## Prerequisites

* Modern browser with WebAuthn support (Chrome, Safari, Firefox, Edge)
* Biometric hardware (fingerprint, face recognition)
* Chipi React SDK installed

## Installation

```bash theme={null}
npm install @chipi-stack/chipi-passkey
```

## Basic Implementation

**1. Create wallet with passkey**

```typescript Import theme={null}
import { useCreateWallet, Chain } from "@chipi-stack/chipi-react";
```

```typescript Usage theme={null}
<Snippet file="sdk/use-passkeys-create.mdx" />
```

**2. Authenticate with passkey**

```typescript Import theme={null}
import { usePasskeyAuth } from "@chipi-stack/chipi-passkey/hooks";
```

```typescript Usage theme={null}
<Snippet file="sdk/use-passkeys-transfer.mdx" />
```

**3. Migrate existing wallet (optional)**

If users already have a PIN-based wallet, they can migrate to passkey:

```typescript Import theme={null}
import { useMigrateWalletToPasskey } from "@chipi-stack/chipi-react";
```

```typescript Usage theme={null}
<Snippet file="sdk/use-passkeys-migrate.mdx" />
```

> [!NOTE]
>
> `useCreateWallet` with `usePasskey: true` is the recommended way to create a passkey wallet: it creates the passkey, stores `credentialId` and `prfSupported` on the wallet row, and sets `authMethod: "passkey"`. If you create the passkey yourself with `usePasskeySetup` and pass its key as `encryptKey`, do **not** also set `usePasskey: true` (that creates a second passkey), and note that the wallet row then records `authMethod: "pin"` with no `credentialId`. See [Passkeys](/sdk/guides/passkeys) for the PIN backup option and for unlocking the wallet on another device.

## Example

**Create Wallet**

```typescript theme={null}
import { useCreateWallet, Chain } from '@chipi-stack/chipi-react';
import { useState } from 'react';

export function CreateWalletWithPasskey({ userId }: { userId: string }) {
  const { createWalletAsync, isLoading, error } = useCreateWallet();
  const [wallet, setWallet] = useState(null);

  const handleCreate = async () => {
    try {
      // usePasskey: true creates the passkey (biometric prompt) and encrypts
      // the private key with its PRF output. Passkey-only: no PIN.
      const result = await createWalletAsync({
        params: {
          externalUserId: userId,
          chain: Chain.STARKNET,
          usePasskey: true,
        },
        bearerToken: 'your-jwt-token',
      });

      // Result is flat — publicKey is at top level, not nested under .wallet.
      // result.credentialId and result.prfSupported are stored on the wallet row.
      localStorage.setItem('wallet', JSON.stringify(result));
      setWallet(result);
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <div>
      <button onClick={handleCreate} disabled={isLoading}>
        {isLoading ? 'Creating...' : 'Create Wallet with Passkey'}
      </button>
      {error && <p>Error: {error.message}</p>}
      {wallet && <p>Wallet created: {wallet.publicKey}</p>}
    </div>
  );
}
```

**Transfer with Passkey**

```typescript theme={null}
import { useTransfer, useChipiWallet, ChainToken } from '@chipi-stack/chipi-react';
import { usePasskeyAuth } from '@chipi-stack/chipi-passkey/hooks';
import { useState } from 'react';

export function TransferWithPasskey({
  userId,
  getBearerToken,
}: {
  userId: string;
  getBearerToken: () => Promise<string | null>;
}) {
  // The wallet row from Chipi carries credentialId, so this works on any
  // device the passkey syncs to, not only the one that created it.
  const { wallet } = useChipiWallet({ externalUserId: userId, getBearerToken });
  const { transferAsync, isLoading } = useTransfer();
  const { authenticate } = usePasskeyAuth();
  const [recipient, setRecipient] = useState('');
  const [amount, setAmount] = useState('');

  const handleTransfer = async () => {
    try {
      if (!wallet) return;

      // Triggers biometric prompt — returns encryptKey or null if cancelled.
      const encryptKey = await authenticate(wallet.credentialId ?? undefined);
      if (!encryptKey) return;
      
      // Do NOT pass usePasskey: true — authenticate() already handled it
      const txHash = await transferAsync({
        params: {
          encryptKey,
          wallet,
          token: ChainToken.USDC,
          recipient,
          amount: Number(amount),
        },
        bearerToken: (await getBearerToken()) ?? '',
      });
      
      alert(`Transfer complete: ${txHash}`);
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <div>
      <input 
        placeholder="Recipient address" 
        value={recipient}
        onChange={(e) => setRecipient(e.target.value)}
      />
      <input 
        placeholder="Amount" 
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
        type="number"
      />
      <button onClick={handleTransfer} disabled={isLoading}>
        {isLoading ? 'Sending...' : 'Send with Passkey'}
      </button>
    </div>
  );
}
```

**Migrate to Passkey**

```typescript theme={null}
import { useMigrateWalletToPasskey } from '@chipi-stack/chipi-react';
import { useState } from 'react';

export function MigrateToPasskey() {
  const { migrateWalletToPasskeyAsync, isLoading } = useMigrateWalletToPasskey();
  const [currentPin, setCurrentPin] = useState('');

  const handleMigrate = async () => {
    try {
      const wallet = JSON.parse(localStorage.getItem('wallet')!);

      // The hook handles passkey creation internally —
      // just pass the old PIN and it re-encrypts with passkey
      await migrateWalletToPasskeyAsync({
        wallet,
        oldEncryptKey: currentPin,
        externalUserId: 'user-123',
        bearerToken: 'your-jwt-token',
      });
      
      alert('Successfully migrated to passkey!');
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <div>
      <input
        type="password"
        placeholder="Current PIN"
        value={currentPin}
        onChange={(e) => setCurrentPin(e.target.value)}
      />
      <button onClick={handleMigrate} disabled={isLoading}>
        {isLoading ? 'Migrating...' : 'Migrate to Passkey'}
      </button>
    </div>
  );
}
```

## Hooks Reference

| Hook | Purpose |
| - | - |
| `usePasskeySetup` | Create new passkey |
| `usePasskeyAuth` | Authenticate with existing passkey |
| `usePasskeyStatus` | Check passkey support & status |
| `useMigrateWalletToPasskey` | Migrate from PIN to passkey |

## Browser Support

* ✅ Chrome 67+ (Desktop & Android)
* ✅ Safari 14+ (iOS 14+, macOS)
* ✅ Firefox 60+ (Desktop)
* ✅ Edge 18+ (Desktop)

> [!NOTE]
>
> Passkeys are stored in the browser and synced via platform (iCloud Keychain, Google Password Manager).

## Security Benefits

* **No PINs stored** - Keys derived on-demand from biometric
* **Platform-level security** - Hardware-backed authentication
* **Phishing resistant** - Domain-bound credentials
* **User convenience** - One tap authentication

## Next Steps

* Check out [useCreateWallet](/sdk/react/hooks/use-create-wallet) for wallet creation options
* Learn about [useTransfer](/sdk/react/hooks/use-transfer) for token transfers
* Explore [session keys](/sdk/react/hooks/use-create-session-key) for additional security


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.