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

# Device Signer

> How device signers work — hardware-backed P256 keypairs for frictionless transaction signing.

<Note>
  **This page has been updated for Wallets SDK V1.** If you are using the previous version,
  see the [previous version docs](/wallets/v0/overview) or the [V1 migration guide](/wallets/guides/migrate-to-v1).
</Note>

The device signer is the **default signer** for client-side wallets. It generates a P256 keypair inside the device's secure hardware — the iOS Secure Enclave, Android Keystore, or the browser's Web Crypto API — so the private key never leaves the device. Transactions are signed silently, with no OTP or user confirmation required.

<Note>
  Device signers are not currently available for Solana. For Solana wallets, the recovery signer is used as a fallback for signing transactions.
</Note>

## How It Works

When a client-side wallet is created, the SDK automatically generates a device signer unless you explicitly configure a different signer. The flow:

1. The SDK initializes secure storage on the device (Secure Enclave, Keystore, or a hidden iframe in the browser)
2. A P256 keypair is generated — the private key is **non-extractable** and never leaves the hardware boundary
3. The public key is sent to the Crossmint backend, which registers the device signer on the wallet
4. Subsequent transactions are signed locally on the device — no OTP, no network round-trips for signing

### Browser Architecture

In browser environments, the device signer key lives inside a hidden iframe hosted at `crossmint-signer.io` — a separate origin from your application. Key generation and signing happen inside the iframe via `postMessage`. Keys are stored as non-extractable `CryptoKey` objects in the iframe's IndexedDB, isolated from the parent page by the browser's same-origin policy.

### Mobile Architecture

On iOS, keys are stored in the Secure Enclave. On Android, keys are stored in the Android Keystore. Both provide hardware-level isolation for the private key material.

## Setup

No explicit configuration is needed. When you create a wallet on the client side, a device signer is added automatically.

<Tabs>
  <Tab title="React">
    Using `createOnLogin` on the provider (recommended for most apps):

    ```tsx theme={null}
    import {
        CrossmintProvider,
        CrossmintAuthProvider,
        CrossmintWalletProvider,
    } from "@crossmint/client-sdk-react-ui";

    function App({ children }) {
        return (
            <CrossmintProvider apiKey="YOUR_CLIENT_API_KEY">
                <CrossmintAuthProvider loginMethods={["email", "google"]}>
                    <CrossmintWalletProvider
                        createOnLogin={{
                            chain: "base-sepolia",
                            recovery: { type: "email" },
                            // Device signer is added automatically
                        }}
                    >
                        {children}
                    </CrossmintWalletProvider>
                </CrossmintAuthProvider>
            </CrossmintProvider>
        );
    }
    ```

    Or using `createWallet` directly:

    ```typescript theme={null}
    import { useWallet } from "@crossmint/client-sdk-react-ui";

    const { createWallet } = useWallet();

    const wallet = await createWallet({
        chain: "base-sepolia",
        recovery: { type: "email", email: "user@example.com" },
        // signers defaults to [{ type: "device" }] on EVM and Stellar (not Solana)
    });
    ```
  </Tab>

  <Tab title="React Native">
    ```tsx theme={null}
    import {
        CrossmintProvider,
        CrossmintAuthProvider,
        CrossmintWalletProvider,
    } from "@crossmint/client-sdk-react-native-ui";

    function App({ children }) {
        return (
            <CrossmintProvider apiKey="YOUR_CLIENT_API_KEY">
                <CrossmintAuthProvider loginMethods={["email", "google"]}>
                    <CrossmintWalletProvider
                        createOnLogin={{
                            chain: "base-sepolia",
                            recovery: { type: "email" },
                        }}
                    >
                        {children}
                    </CrossmintWalletProvider>
                </CrossmintAuthProvider>
            </CrossmintProvider>
        );
    }
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart theme={null}
    import 'package:crossmint_flutter/crossmint_flutter_ui.dart';

    CrossmintWalletProvider(
      config: CrossmintWalletProviderConfig(
        clientConfig: const CrossmintClientConfig(
          apiKey: 'YOUR_CLIENT_API_KEY',
          appScheme: 'myapp',
        ),
        walletControllerConfig: const CrossmintWalletControllerConfig(
          createOnLogin: CrossmintCreateOnLoginConfig(
            chain: 'base-sepolia',
            recovery: CrossmintEmailSignerConfig(),
            // Device signer is added automatically on EVM and Stellar.
          ),
        ),
        otpPromptBuilder: crossmintDefaultOtpPromptBuilder,
      ),
      child: const HomeScreen(),
    )
    ```

    On iOS the private key lives in Secure Enclave; on Android it uses
    Android Keystore. Both are provided by the `crossmint_device_signer` plugin.
  </Tab>
</Tabs>

## Signing Transactions

With a device signer, `useSigner()` is **not needed** — the device signer is auto-selected as the default. Transactions are signed without any user-facing prompts:

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    import { useWallet } from "@crossmint/client-sdk-react-ui";

    const { wallet } = useWallet();

    const { hash, explorerLink } = await wallet.send(
        "0xRecipientAddress",
        "usdc",
        "10"
    );
    ```
  </Tab>

  <Tab title="React Native">
    ```typescript theme={null}
    import { useWallet } from "@crossmint/client-sdk-react-native-ui";

    const { wallet } = useWallet();

    const { hash, explorerLink } = await wallet.send(
        "0xRecipientAddress",
        "usdc",
        "10"
    );
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart theme={null}
    import 'package:crossmint_flutter/crossmint_flutter_ui.dart';

    final controller = CrossmintWalletContext.of(context).requireWalletController;
    final wallet = controller.createEvmWallet();

    final tx = await wallet.sendToken('0xRecipientAddress', 'usdc', '10');
    print('Transaction: ${tx.id}');
    ```

    The SDK auto-recovers the device signer on first signing if the wallet
    reports `needsRecovery == true`, so no explicit setup is required.
  </Tab>
</Tabs>

## New Device Recovery

Device signers are **per-device** — they do not sync across devices. When a user accesses their wallet from a new device:

1. The user authenticates via your app (JWT) and the SDK retrieves the wallet
2. No local device signer exists on the new device, so `wallet.needsRecovery()` returns `true`
3. On the first transaction, the SDK automatically triggers recovery: the user verifies via their recovery signer (e.g., email OTP) to authorize a new device signer for this device
4. After recovery, all subsequent transactions on the new device are frictionless again

The previous device's signer remains valid — each device maintains its own independent signer.

### Pre-emptive Recovery

Recovery runs automatically before the first signing operation on a new device. If you want to trigger it earlier (e.g., on app startup to avoid an OTP prompt mid-transaction), call `recover()` explicitly:

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    import { useWallet } from "@crossmint/client-sdk-react-ui";

    const { wallet } = useWallet();

    try {
        if (wallet.needsRecovery()) {
            await wallet.recover();
        }
    } catch (error) {
        console.error("Recovery failed:", error);
    }
    ```
  </Tab>

  <Tab title="React Native">
    ```typescript theme={null}
    import { useWallet } from "@crossmint/client-sdk-react-native-ui";

    const { wallet } = useWallet();

    try {
        if (wallet.needsRecovery()) {
            await wallet.recover();
        }
    } catch (error) {
        console.error("Recovery failed:", error);
    }
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart theme={null}
    import 'package:crossmint_flutter/crossmint_flutter_ui.dart';

    final controller = CrossmintWalletContext.of(context).requireWalletController;
    final wallet = controller.createEvmWallet();

    try {
      if (wallet.needsRecovery) {
        await wallet.recover();
      }
    } catch (error) {
      debugPrint('Recovery failed: $error');
    }
    ```

    `needsRecovery` is a getter (no parens) on the Flutter runtime wallet.
  </Tab>
</Tabs>

## Chain Support

| Chain                               | Device Signer Support   |
| ----------------------------------- | ----------------------- |
| EVM (Base, Ethereum, Polygon, etc.) | Supported               |
| Stellar                             | Supported               |
| Solana                              | Not currently available |

For Solana wallets, use an email or phone recovery signer — it serves as the signing fallback. See [Configure Wallet Recovery](/wallets/guides/signers/configure-recovery) for setup instructions.

## Next Steps

<CardGroup cols={3}>
  <Card title="Configure Recovery" icon="shield-halved" href="/wallets/guides/signers/configure-recovery">
    Set up email or phone recovery for new device flows
  </Card>

  <Card title="Add Signers" icon="key" href="/wallets/guides/signers/add-signers">
    Add passkeys or external wallets as additional signers
  </Card>

  <Card title="Transfer Tokens" icon="arrow-right-arrow-left" href="/wallets/guides/transfer-tokens">
    Send tokens from your wallet
  </Card>
</CardGroup>
