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

# Controllers

> Flutter controllers for the Flutter SDK reference for Crossmint wallets

The Flutter SDK uses `ChangeNotifier`-based controllers instead of React hooks. Controllers can be used directly (headless) or accessed through the widget tree via providers.

***

## CrossmintClient

The top-level facade for the Crossmint SDK. Provides access to auth, wallets, and all sub-clients.

### Properties

<ResponseField name="config" type="CrossmintClientConfig">
  The configuration this client was built with.

  <Expandable title="properties">
    <ResponseField name="authStorage" type="CrossmintAuthStorage?">
      Custom session / token store. When null, the SDK uses `FlutterSecureStorage` under the hood.
    </ResponseField>

    <ResponseField name="logger" type="CrossmintLogger?">
      Custom logger sink. When null, the SDK uses a no-op logger in release and a `debugPrint`-backed logger in debug.
    </ResponseField>

    <ResponseField name="refreshRoute" type="Uri?">
      Optional custom server endpoint for token refresh.
    </ResponseField>

    <ResponseField name="logoutRoute" type="Uri?">
      Optional custom server endpoint for logout.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="auth" type="CrossmintAuthClient">
  Authentication sub-client — email OTP, OAuth, wallet sign-in, session persistence, JWT refresh.
</ResponseField>

<ResponseField name="wallets" type="CrossmintWalletsClient">
  Wallet sub-client — create, list, and read Crossmint-hosted wallets (EVM, Solana, Stellar) and their signers.
</ResponseField>

<ResponseField name="credentials" type="CrossmintCredentialsClient">
  Verifiable credentials sub-client — issue, query, and verify credentials.
</ResponseField>

<ResponseField name="orders" type="CrossmintOrdersClient">
  Orders sub-client — checkout orders and mint/purchase flows.
</ResponseField>

<ResponseField name="tokens" type="CrossmintTokensClient">
  Tokens sub-client — metadata and balances for tokens held by wallets.
</ResponseField>

<ResponseField name="users" type="CrossmintUsersClient">
  Users sub-client — user profile lookups and updates.
</ResponseField>

<ResponseField name="appId" type="String?">
  The effective app ID — explicit \[CrossmintClientConfig.appId] if set, otherwise the value auto-resolved from platform metadata during \[initialize]. May be `null` until \[initialize] has completed.
</ResponseField>

<ResponseField name="isInitialized" type="bool">
  Whether \[initialize] has completed successfully.
</ResponseField>

<ResponseField name="isDisposed" type="bool">
  Whether \[dispose] has been called. Once disposed, the client cannot be reused — construct a new one.
</ResponseField>

<ResponseField name="hiddenSignerBridgeController" type="HiddenSignerBridgeRuntimeController">
  The shared hidden signer bridge controller, lazily constructed on first access. Powers non-custodial email/phone signing inside a hidden WebView.

  <Expandable title="properties">
    <ResponseField name="config" type="HiddenSignerBridgeConfig" required>
      <Expandable title="properties">
        <ResponseField name="uri" type="Uri" required />

        <ResponseField name="javaScriptChannelName" type="String" />

        <ResponseField name="dispatchScriptBuilder" type="HiddenSignerDispatchScriptBuilder" />

        <ResponseField name="handshakeSignal" type="String" />

        <ResponseField name="handshakeTimeout" type="Duration" />

        <ResponseField name="requestTimeout" type="Duration" />

        <ResponseField name="allowedOrigins" type="Set<String>" />
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="authStorage" type="CrossmintAuthStorage">
  The auth storage adapter resolved during construction — either the one passed via \[CrossmintClientConfig.authStorage] or the default `flutter_secure_storage`-backed implementation.
</ResponseField>

<ResponseField name="transport" type="CrossmintTransport">
  The HTTP transport resolved during construction. Shared by every sub-client; injects the live JWT via a closure so token refresh works without rebuilding dependencies.
</ResponseField>

### Methods

<ResponseField name="createWalletController(config)" type="CrossmintWalletController">
  Creates a \[CrossmintWalletController] bound to this client.

  <Expandable title="parameters">
    <ResponseField name="config" type="CrossmintWalletControllerConfig" required />
  </Expandable>
</ResponseField>

<ResponseField name="createHiddenSignerBridge(config)" type="HiddenSignerBridgeRuntimeController">
  Builds a new \[HiddenSignerBridgeRuntimeController] instance — used internally by \[hiddenSignerBridgeController]. Override the bridge `config` to point at a custom signer URL (e.g. for testing).

  <Expandable title="parameters">
    <ResponseField name="config" type="HiddenSignerBridgeConfig?" />
  </Expandable>
</ResponseField>

<ResponseField name="createExportSignerBridge(config)" type="CrossmintExportSignerBridgeRuntimeController">
  Builds a new \[CrossmintExportSignerBridgeRuntimeController] instance. Used internally by \[exportSignerBridgeController].

  <Expandable title="parameters">
    <ResponseField name="config" type="HiddenSignerBridgeConfig?" />
  </Expandable>
</ResponseField>

<ResponseField name="createEvmNonCustodialSigner(bridge, signerType, signerLocator, address, onAuthRequired)" type="CrossmintEvmNonCustodialSigner">
  Builds a non-custodial EVM signer bound to an existing \[HiddenSignerBridge]. Produces secp256k1 signatures inside the WebView without exposing keys to Dart.

  <Expandable title="parameters">
    <ResponseField name="bridge" type="HiddenSignerBridge" required />

    <ResponseField name="signerType" type="String" required />

    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String?" />

    <ResponseField name="onAuthRequired" type="CrossmintSignerAuthRequiredCallback?" />
  </Expandable>
</ResponseField>

<ResponseField name="createSolanaNonCustodialSigner(bridge, signerType, signerLocator, address, onAuthRequired)" type="CrossmintSolanaNonCustodialSigner">
  Builds a non-custodial Solana signer bound to an existing \[HiddenSignerBridge]. Produces ed25519 signatures inside the WebView.

  <Expandable title="parameters">
    <ResponseField name="bridge" type="HiddenSignerBridge" required />

    <ResponseField name="signerType" type="String" required />

    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String?" />

    <ResponseField name="onAuthRequired" type="CrossmintSignerAuthRequiredCallback?" />
  </Expandable>
</ResponseField>

<ResponseField name="createStellarNonCustodialSigner(bridge, signerType, signerLocator, address, onAuthRequired)" type="CrossmintStellarNonCustodialSigner">
  Builds a non-custodial Stellar signer bound to an existing \[HiddenSignerBridge]. Produces ed25519 signatures inside the WebView.

  <Expandable title="parameters">
    <ResponseField name="bridge" type="HiddenSignerBridge" required />

    <ResponseField name="signerType" type="String" required />

    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String?" />

    <ResponseField name="onAuthRequired" type="CrossmintSignerAuthRequiredCallback?" />
  </Expandable>
</ResponseField>

<ResponseField name="createEvmWallet(wallet, recoverySigner, signer, additionalSigners, deviceSignerKeyStorage, onAuthRequired)" type="CrossmintEvmWallet">
  Wraps a server-issued \[CrossmintWallet] in a runtime \[CrossmintEvmWallet].

  <Expandable title="parameters">
    <ResponseField name="wallet" type="CrossmintWallet" required />

    <ResponseField name="recoverySigner" type="CrossmintSignerConfig?" />

    <ResponseField name="signer" type="CrossmintWalletApprovalSigner?" />

    <ResponseField name="additionalSigners" type="List<CrossmintWalletApprovalSigner>" />

    <ResponseField name="deviceSignerKeyStorage" type="DeviceSignerKeyStorage?" />

    <ResponseField name="onAuthRequired" type="CrossmintSignerAuthRequiredCallback?" />
  </Expandable>
</ResponseField>

<ResponseField name="createSolanaWallet(wallet, recoverySigner, signer, additionalSigners, deviceSignerKeyStorage, onAuthRequired)" type="CrossmintSolanaWallet">
  Wraps a server-issued \[CrossmintWallet] in a runtime \[CrossmintSolanaWallet].

  <Expandable title="parameters">
    <ResponseField name="wallet" type="CrossmintWallet" required />

    <ResponseField name="recoverySigner" type="CrossmintSignerConfig?" />

    <ResponseField name="signer" type="CrossmintWalletApprovalSigner?" />

    <ResponseField name="additionalSigners" type="List<CrossmintWalletApprovalSigner>" />

    <ResponseField name="deviceSignerKeyStorage" type="DeviceSignerKeyStorage?" />

    <ResponseField name="onAuthRequired" type="CrossmintSignerAuthRequiredCallback?" />
  </Expandable>
</ResponseField>

<ResponseField name="createStellarWallet(wallet, recoverySigner, signer, additionalSigners, deviceSignerKeyStorage, onAuthRequired)" type="CrossmintStellarWallet">
  Wraps a server-issued \[CrossmintWallet] in a runtime \[CrossmintStellarWallet].

  <Expandable title="parameters">
    <ResponseField name="wallet" type="CrossmintWallet" required />

    <ResponseField name="recoverySigner" type="CrossmintSignerConfig?" />

    <ResponseField name="signer" type="CrossmintWalletApprovalSigner?" />

    <ResponseField name="additionalSigners" type="List<CrossmintWalletApprovalSigner>" />

    <ResponseField name="deviceSignerKeyStorage" type="DeviceSignerKeyStorage?" />

    <ResponseField name="onAuthRequired" type="CrossmintSignerAuthRequiredCallback?" />
  </Expandable>
</ResponseField>

<ResponseField name="createDeviceWalletSigner(signerLocator, address, storage)" type="CrossmintDeviceWalletSigner">
  Builds a hardware-backed device signer for an existing server signer.

  <Expandable title="parameters">
    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String" required />

    <ResponseField name="storage" type="DeviceSignerKeyStorage?" />
  </Expandable>
</ResponseField>

<ResponseField name="createEvmExternalWalletSigner(address, signerLocator, onSign)" type="CrossmintEvmExternalWalletSigner">
  Builds an external-wallet EVM signer that delegates to a user-supplied \[CrossmintExternalWalletSignCallback]. Use for wallets owned by the user (e.g. MetaMask, Rainbow) where the SDK only needs to request signatures.

  <Expandable title="parameters">
    <ResponseField name="address" type="String" required />

    <ResponseField name="signerLocator" type="String?" />

    <ResponseField name="onSign" type="CrossmintExternalWalletSignCallback" required />
  </Expandable>
</ResponseField>

<ResponseField name="createSolanaExternalWalletSigner(address, signerLocator, onSign)" type="CrossmintSolanaExternalWalletSigner">
  Builds an external-wallet Solana signer that delegates to a user-supplied \[CrossmintExternalWalletSignCallback].

  <Expandable title="parameters">
    <ResponseField name="address" type="String" required />

    <ResponseField name="signerLocator" type="String?" />

    <ResponseField name="onSign" type="CrossmintExternalWalletSignCallback" required />
  </Expandable>
</ResponseField>

<ResponseField name="createStellarExternalWalletSigner(address, signerLocator, onSign)" type="CrossmintStellarExternalWalletSigner">
  Builds an external-wallet Stellar signer that delegates to a user-supplied \[CrossmintExternalWalletSignCallback].

  <Expandable title="parameters">
    <ResponseField name="address" type="String" required />

    <ResponseField name="signerLocator" type="String?" />

    <ResponseField name="onSign" type="CrossmintExternalWalletSignCallback" required />
  </Expandable>
</ResponseField>

<ResponseField name="createPasskeyApprovalSigner(credentialId, signerLocator, onSign)" type="CrossmintPasskeyApprovalSigner">
  Builds a passkey approval signer. The caller provides `onSign` — a callback that performs the WebAuthn / platform-native passkey assertion and returns a \[CrossmintPasskeySignResult]. Native platform passkey support is pending (Stage 2); today every passkey signer requires an explicit callback or `CrossmintSignerException(code: passkeyCallbackMissing)` is thrown during signing.

  <Expandable title="parameters">
    <ResponseField name="credentialId" type="String" required />

    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="onSign" type="Future<CrossmintPasskeySignResult> Function(String message)" required />
  </Expandable>
</ResponseField>

<ResponseField name="dispose()" type="Future<void>">
  Releases the signer bridges and auth client, then marks the client as disposed. Safe to call multiple times. After disposal, construct a new \[CrossmintClient] rather than reusing this one.
</ResponseField>

<ResponseField name="initialize()" type="Future<void>">
  Validates configuration and prepares the client for use.
</ResponseField>

### Usage

```dart theme={null}
import 'package:crossmint_flutter/crossmint_client.dart';

final client = CrossmintClient(
  config: CrossmintClientConfig(
    apiKey: 'YOUR_CLIENT_API_KEY',
    appScheme: 'myapp',
  ),
);

await client.initialize();
await client.auth.restoreSession();
```

***

## CrossmintAuthClient

Manages authentication state and login flows. Exposes state via `ValueListenable` and `Stream` for reactive UI updates. Use `setJwt()` to provide your own JWT when not using the built-in auth flows.

### Properties

<ResponseField name="state" type="CrossmintAuthState">
  The current auth state snapshot.
</ResponseField>

<ResponseField name="stateListenable" type="ValueListenable<CrossmintAuthState>">
  Reactive handle on \[state] — suitable for \[ValueListenableBuilder].
</ResponseField>

<ResponseField name="states" type="Stream<CrossmintAuthState>">
  Stream of auth-state changes. Emits on every state mutation.
</ResponseField>

### Methods

<ResponseField name="restoreSession()" type="Future<void>">
  Reads any persisted session from \[CrossmintAuthStorage] and applies it to the client. Safe to call once per app launch — prefer using the result of `CrossmintClient.initialize` which calls this internally.
</ResponseField>

<ResponseField name="loginWithOAuth(provider)" type="Future<void>">
  Opens the Crossmint-hosted OAuth flow for \[provider] (Google, Twitter, etc.). The response comes back through \[CrossmintAuthCallbackRouter] — make sure it is started before calling this.

  <Expandable title="parameters">
    <ResponseField name="provider" type="CrossmintOAuthProvider" required />
  </Expandable>
</ResponseField>

<ResponseField name="sendEmailOtp(email)" type="Future<CrossmintEmailOtpChallenge>">
  Requests the Crossmint API to send an email OTP to \[email]. Returns a challenge that \[confirmEmailOtp] completes.

  <Expandable title="parameters">
    <ResponseField name="email" type="String" required />
  </Expandable>
</ResponseField>

<ResponseField name="confirmEmailOtp(email, emailId, token)" type="Future<bool>">
  Submits an email OTP challenge — pass the `emailId` from the challenge and the `token` the user entered. Returns `true` on success.

  <Expandable title="parameters">
    <ResponseField name="email" type="String" required />

    <ResponseField name="emailId" type="String" required />

    <ResponseField name="token" type="String" required />
  </Expandable>
</ResponseField>

<ResponseField name="completeOAuth(callbackUri)" type="Future<bool>">
  Completes an OAuth flow from a deep-link callback URI. Normally invoked by \[CrossmintAuthCallbackRouter]; call directly only for custom routing.

  <Expandable title="parameters">
    <ResponseField name="callbackUri" type="Uri" required />
  </Expandable>
</ResponseField>

<ResponseField name="completeOAuthWithOneTimeSecret(oneTimeSecret)" type="Future<bool>">
  Completes an OAuth flow from a one-time secret (SSR / server-mediated callbacks). Returns `true` on success.

  <Expandable title="parameters">
    <ResponseField name="oneTimeSecret" type="String" required />
  </Expandable>
</ResponseField>

<ResponseField name="signInWithWallet(address, type)" type="Future<CrossmintWalletAuthChallenge>">
  Starts wallet-based authentication. Returns a challenge that the wallet must sign. \[type] is `'evm'` or `'solana'`.

  <Expandable title="parameters">
    <ResponseField name="address" type="String" required />

    <ResponseField name="type" type="String" required />
  </Expandable>
</ResponseField>

<ResponseField name="authenticateWallet(address, type, signature)" type="Future<bool>">
  Completes wallet-based authentication with the signed challenge.

  <Expandable title="parameters">
    <ResponseField name="address" type="String" required />

    <ResponseField name="type" type="String" required />

    <ResponseField name="signature" type="String" required />
  </Expandable>
</ResponseField>

<ResponseField name="getSession()" type="Future<CrossmintAuthSession?>">
  Returns the current session (JWT + metadata), or `null` if not signed in. Does not hit the network — reads from in-memory state.
</ResponseField>

<ResponseField name="getUser(forceRefresh)" type="Future<CrossmintAuthenticatedUser?>">
  Returns the authenticated user's profile. Caches the result; pass `forceRefresh: true` to bypass the cache and refetch.

  <Expandable title="parameters">
    <ResponseField name="forceRefresh" type="bool" />
  </Expandable>
</ResponseField>

<ResponseField name="logout()" type="Future<void>">
  Signs the user out — clears persisted storage, cached session/user, and POSTs to the Crossmint (or custom) logout endpoint.
</ResponseField>

<ResponseField name="setJwt(jwt)" type="Future<void>">
  Sets a custom JWT for "Bring Your Own Auth" (BYOA) flows.

  <Expandable title="parameters">
    <ResponseField name="jwt" type="String?" required />
  </Expandable>
</ResponseField>

### Usage

```dart theme={null}
import 'package:crossmint_flutter/crossmint_flutter_auth.dart';

client.auth.stateListenable.addListener(() {
  print('Auth state: ${client.auth.state}');
});

final challenge = await client.auth.sendEmailOtp('YOUR_USER_EMAIL');
await client.auth.confirmEmailOtp(
  email: 'YOUR_USER_EMAIL',
  emailId: challenge.id,
  token: 'YOUR_OTP_CODE',
);

await client.auth.loginWithOAuth(CrossmintOAuthProvider.google);

await client.auth.logout();
```

***

## CrossmintWalletController

Manages wallet lifecycle — creation, loading, signing, and OTP flows. Extends `ChangeNotifier` for reactive UI updates.

### Properties

<ResponseField name="client" type="CrossmintClient">
  The underlying client — exposed for code that needs direct access to sub-clients (auth, orders, credentials, etc.).

  <Expandable title="properties">
    <ResponseField name="config" type="CrossmintClientConfig" required>
      <Expandable title="properties">
        <ResponseField name="authStorage" type="CrossmintAuthStorage?">
          Custom session / token store. When null, the SDK uses `FlutterSecureStorage` under the hood.
        </ResponseField>

        <ResponseField name="logger" type="CrossmintLogger?">
          Custom logger sink. When null, the SDK uses a no-op logger in release and a `debugPrint`-backed logger in debug.
        </ResponseField>

        <ResponseField name="refreshRoute" type="Uri?">
          Optional custom server endpoint for token refresh.
        </ResponseField>

        <ResponseField name="logoutRoute" type="Uri?">
          Optional custom server endpoint for logout.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="auth" type="CrossmintAuthClient?" />

    <ResponseField name="wallets" type="CrossmintWalletsClient?" />

    <ResponseField name="credentials" type="CrossmintCredentialsClient?" />

    <ResponseField name="orders" type="CrossmintOrdersClient?" />

    <ResponseField name="tokens" type="CrossmintTokensClient?" />

    <ResponseField name="users" type="CrossmintUsersClient?" />

    <ResponseField name="transport" type="CrossmintTransport?" />

    <ResponseField name="authLauncher" type="CrossmintAuthLauncher?" />

    <ResponseField name="runtimeOverrides" type="CrossmintClientRuntimeOverrides?">
      <Expandable title="properties">
        <ResponseField name="authStorage" type="CrossmintAuthStorage?" />

        <ResponseField name="logger" type="CrossmintLogger?" />
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="config" type="CrossmintWalletControllerConfig">
  The configuration this controller was built with.

  <Expandable title="properties">
    <ResponseField name="createOnLogin" type="CrossmintCreateOnLoginConfig?">
      When set, the controller automatically creates (or loads) a wallet on the given chain once the user is authenticated. Fill `recovery` with the recovery signer config (e.g. `CrossmintEmailSignerConfig()`).
    </ResponseField>

    <ResponseField name="showOtpSignerPrompt" type="bool">
      Whether the SDK should surface the default OTP prompt automatically. Set to `false` to drive OTP UI yourself by listening to \[CrossmintWalletController.otp].
    </ResponseField>

    <ResponseField name="callbacks" type="CrossmintWalletLifecycleCallbacks?">
      Optional lifecycle hooks — see \[CrossmintWalletLifecycleCallbacks].

      <Expandable title="properties">
        <ResponseField name="onWalletCreationStart" type="Future<void> Function()?">
          Called immediately before the controller issues a `createWallet` API call. Await the returned future to block the operation until your UI is ready.
        </ResponseField>

        <ResponseField name="onTransactionStart" type="Future<void> Function()?">
          Called immediately before the controller issues a create-transaction API call. Await to block until your UI is ready.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="deviceSignerKeyStorage" type="DeviceSignerKeyStorage?">
      Storage adapter used to persist device-signer keys. Defaults to a secure-enclave / keystore-backed implementation on device.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="otp" type="CrossmintWalletOtpController">
  OTP controller for non-custodial email/phone signer challenges.
</ResponseField>

<ResponseField name="state" type="CrossmintWalletStateView">
  Read-only observation surface for the underlying wallet state. Advanced consumers can subscribe to wallet / status changes directly via this \[Listenable] while still routing all mutations through the controller's public API (`getWallet`, `createWallet`, `refresh`, `clear`, …).
</ResponseField>

<ResponseField name="currentWallet" type="CrossmintWallet?">
  The currently-loaded wallet, or `null` if none is loaded.

  <Expandable title="properties">
    <ResponseField name="id" type="String" required />

    <ResponseField name="chain" type="String?" />

    <ResponseField name="address" type="String?" />

    <ResponseField name="owner" type="String?" />

    <ResponseField name="raw" type="CrossmintJsonMap" />
  </Expandable>
</ResponseField>

<ResponseField name="status" type="CrossmintWalletStatus">
  Current wallet lifecycle status.
</ResponseField>

<ResponseField name="onAuthRequired" type="CrossmintWalletAuthRequiredCallback">
  The auth-required callback the controller wires into every signer it builds. Exposed so apps building signers manually can share the same OTP-handling path.
</ResponseField>

<ResponseField name="pendingOtpChallenge" type="CrossmintOtpChallenge?">
  Shortcut to \[CrossmintWalletOtpController.pendingChallenge].

  <Expandable title="properties">
    <ResponseField name="signerType" type="String" required />

    <ResponseField name="signerLocator" type="String" required />
  </Expandable>
</ResponseField>

<ResponseField name="otpChallengeListenable" type="ValueListenable<CrossmintOtpChallenge?>">
  Shortcut to \[CrossmintWalletOtpController.challengeListenable].
</ResponseField>

<ResponseField name="requireCurrentWallet" type="CrossmintWallet">
  Returns the currently-loaded wallet or throws \[CrossmintWalletException] when none is loaded.

  <Expandable title="properties">
    <ResponseField name="id" type="String" required />

    <ResponseField name="chain" type="String?" />

    <ResponseField name="address" type="String?" />

    <ResponseField name="owner" type="String?" />

    <ResponseField name="raw" type="CrossmintJsonMap" />
  </Expandable>
</ResponseField>

### Methods

<ResponseField name="getWallet(request)" type="Future<CrossmintWallet?>">
  Looks up an existing wallet and stores the result in controller state. Returns `null` when no wallet matches. Listeners fire on state changes.

  <Expandable title="parameters">
    <ResponseField name="request" type="CrossmintWalletLookupRequest" required />
  </Expandable>
</ResponseField>

<ResponseField name="loadWallet(request)" type="Future<CrossmintWallet?>">
  Deprecated: use \[getWallet] instead.

  <Expandable title="parameters">
    <ResponseField name="request" type="CrossmintWalletLookupRequest" required />
  </Expandable>
</ResponseField>

<ResponseField name="createWallet(request)" type="Future<CrossmintWallet>">
  Creates a new wallet and stores it in controller state. Validates passkey signers up-front via \[crossmintValidatePasskeySigners] and fires \[CrossmintWalletLifecycleCallbacks.onWalletCreationStart] if set.

  <Expandable title="parameters">
    <ResponseField name="request" type="CrossmintWalletCreateRequest" required />
  </Expandable>
</ResponseField>

<ResponseField name="ensureLoaded()" type="Future<CrossmintWallet?>">
  Ensures a wallet is loaded, creating one if configured to do so.
</ResponseField>

<ResponseField name="refresh()" type="Future<CrossmintWallet?>">
  Re-fetches the currently-loaded wallet from the API. Returns `null` when nothing is loaded.
</ResponseField>

<ResponseField name="clear()" type="Future<void>">
  Drops the loaded wallet and any pending OTP challenge. Call during sign-out.
</ResponseField>

<ResponseField name="sendOtp()" type="Future<void>">
  Shortcut to \[CrossmintWalletOtpController.sendOtp].
</ResponseField>

<ResponseField name="verifyOtp(otpCode)" type="Future<void>">
  Shortcut to \[CrossmintWalletOtpController.verifyOtp].

  <Expandable title="parameters">
    <ResponseField name="otpCode" type="String" required />
  </Expandable>
</ResponseField>

<ResponseField name="rejectOtp()" type="void">
  Shortcut to \[CrossmintWalletOtpController.reject].
</ResponseField>

<ResponseField name="createEvmWallet(signer, additionalSigners)" type="CrossmintEvmWallet">
  Wraps the currently-loaded wallet as a runtime \[CrossmintEvmWallet]. Throws \[CrossmintWalletException] if no wallet is loaded.

  <Expandable title="parameters">
    <ResponseField name="signer" type="CrossmintWalletApprovalSigner?" />

    <ResponseField name="additionalSigners" type="List<CrossmintWalletApprovalSigner>" />
  </Expandable>
</ResponseField>

<ResponseField name="createSolanaWallet(signer, additionalSigners)" type="CrossmintSolanaWallet">
  Wraps the currently-loaded wallet as a runtime \[CrossmintSolanaWallet]. On Solana, device signer support depends on the wallet type and is validated server-side. A default device signer is not auto-injected for Solana; it is registered reactively and falls back to the recovery signer when the server rejects it.

  <Expandable title="parameters">
    <ResponseField name="signer" type="CrossmintWalletApprovalSigner?" />

    <ResponseField name="additionalSigners" type="List<CrossmintWalletApprovalSigner>" />
  </Expandable>
</ResponseField>

<ResponseField name="createStellarWallet(signer, additionalSigners)" type="CrossmintStellarWallet">
  Wraps the currently-loaded wallet as a runtime \[CrossmintStellarWallet].

  <Expandable title="parameters">
    <ResponseField name="signer" type="CrossmintWalletApprovalSigner?" />

    <ResponseField name="additionalSigners" type="List<CrossmintWalletApprovalSigner>" />
  </Expandable>
</ResponseField>

<ResponseField name="createEvmWalletWithDeviceSigner(signerLocator, address, storage)" type="CrossmintEvmWallet">
  Builds a runtime EVM wallet backed by a hardware-backed device signer. Use for low-friction, day-to-day signing with keys in Secure Enclave / Android Keystore.

  <Expandable title="parameters">
    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String?" />

    <ResponseField name="storage" type="DeviceSignerKeyStorage?" />
  </Expandable>
</ResponseField>

<ResponseField name="createEvmWalletWithNonCustodialSigner(signerType, signerLocator, address, bridge)" type="CrossmintEvmWallet">
  Builds a runtime EVM wallet backed by a non-custodial email/phone signer. Requires \[CrossmintWalletHost] (or `CrossmintWalletProvider`) mounted in the widget tree.

  <Expandable title="parameters">
    <ResponseField name="signerType" type="String" required />

    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String?" />

    <ResponseField name="bridge" type="HiddenSignerBridge?" />
  </Expandable>
</ResponseField>

<ResponseField name="createSolanaWalletWithNonCustodialSigner(signerType, signerLocator, address, bridge)" type="CrossmintSolanaWallet">
  Builds a runtime Solana wallet backed by a non-custodial email/phone signer. Requires a mounted wallet host.

  <Expandable title="parameters">
    <ResponseField name="signerType" type="String" required />

    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String?" />

    <ResponseField name="bridge" type="HiddenSignerBridge?" />
  </Expandable>
</ResponseField>

<ResponseField name="createStellarWalletWithNonCustodialSigner(signerType, signerLocator, address, bridge)" type="CrossmintStellarWallet">
  Builds a runtime Stellar wallet backed by a non-custodial email/phone signer. Requires a mounted wallet host.

  <Expandable title="parameters">
    <ResponseField name="signerType" type="String" required />

    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String?" />

    <ResponseField name="bridge" type="HiddenSignerBridge?" />
  </Expandable>
</ResponseField>

<ResponseField name="createStellarWalletWithDeviceSigner(signerLocator, address, storage)" type="CrossmintStellarWallet">
  Builds a runtime Stellar wallet backed by a hardware device signer.

  <Expandable title="parameters">
    <ResponseField name="signerLocator" type="String" required />

    <ResponseField name="address" type="String?" />

    <ResponseField name="storage" type="DeviceSignerKeyStorage?" />
  </Expandable>
</ResponseField>

<ResponseField name="createEvmWalletWithExternalWalletSigner(address, signerLocator, onSign)" type="CrossmintEvmWallet">
  Builds a runtime EVM wallet backed by a user-owned external wallet (e.g. MetaMask). The caller supplies \[onSign] to perform signatures.

  <Expandable title="parameters">
    <ResponseField name="address" type="String" required />

    <ResponseField name="signerLocator" type="String?" />

    <ResponseField name="onSign" type="CrossmintExternalWalletSignCallback" required />
  </Expandable>
</ResponseField>

<ResponseField name="createSolanaWalletWithExternalWalletSigner(address, signerLocator, onSign)" type="CrossmintSolanaWallet">
  Builds a runtime Solana wallet backed by a user-owned external wallet.

  <Expandable title="parameters">
    <ResponseField name="address" type="String" required />

    <ResponseField name="signerLocator" type="String?" />

    <ResponseField name="onSign" type="CrossmintExternalWalletSignCallback" required />
  </Expandable>
</ResponseField>

<ResponseField name="createStellarWalletWithExternalWalletSigner(address, signerLocator, onSign)" type="CrossmintStellarWallet">
  Builds a runtime Stellar wallet backed by a user-owned external wallet.

  <Expandable title="parameters">
    <ResponseField name="address" type="String" required />

    <ResponseField name="signerLocator" type="String?" />

    <ResponseField name="onSign" type="CrossmintExternalWalletSignCallback" required />
  </Expandable>
</ResponseField>

<ResponseField name="createEvmWalletWithPasskeySigner(id, onSign)" type="CrossmintEvmWallet">
  Builds a runtime EVM wallet backed by a passkey signer.

  <Expandable title="parameters">
    <ResponseField name="id" type="String?" />

    <ResponseField name="onSign" type="Future<CrossmintPasskeySignResult> Function(String message)" required />
  </Expandable>
</ResponseField>

<ResponseField name="createSolanaWalletWithPasskeySigner(id, onSign)" type="CrossmintSolanaWallet">
  Builds a runtime Solana wallet backed by a passkey signer. See \[createEvmWalletWithPasskeySigner] for passkey selection semantics.

  <Expandable title="parameters">
    <ResponseField name="id" type="String?" />

    <ResponseField name="onSign" type="Future<CrossmintPasskeySignResult> Function(String message)" required />
  </Expandable>
</ResponseField>

<ResponseField name="createStellarWalletWithPasskeySigner(id, onSign)" type="CrossmintStellarWallet">
  Builds a runtime Stellar wallet backed by a passkey signer. See \[createEvmWalletWithPasskeySigner] for passkey selection semantics.

  <Expandable title="parameters">
    <ResponseField name="id" type="String?" />

    <ResponseField name="onSign" type="Future<CrossmintPasskeySignResult> Function(String message)" required />
  </Expandable>
</ResponseField>

<ResponseField name="dispose()" type="void" />

<Note>
  The controller also provides chain-specific convenience factories that build
  the signer for you: `createEvmWalletWithDeviceSigner`,
  `createEvmWalletWithPasskeySigner`,
  `createEvmWalletWithExternalWalletSigner`,
  `createEvmWalletWithNonCustodialSigner`, and their Solana / Stellar
  counterparts.
</Note>

### Usage

```dart theme={null}
import 'package:crossmint_flutter/crossmint_flutter_controllers.dart';

final controller = client.createWalletController(
  CrossmintWalletControllerConfig(
    createOnLogin: CrossmintCreateOnLoginConfig(
      chain: 'base-sepolia',
      recovery: const CrossmintEmailSignerConfig(),
    ),
  ),
);

// Listen to status changes
controller.addListener(() {
  print('Status: ${controller.status}');
  final wallet = controller.currentWallet;
  if (wallet != null) {
    print('Address: ${wallet.address}');
  }
});

// Manual OTP handling
controller.otp.challengeListenable.addListener(() {
  final challenge = controller.otp.challengeListenable.value;
  if (challenge != null) {
    // Show your own OTP prompt UI
    // Then call: controller.otp.verifyOtp('123456');
  }
});
```

***

## CrossmintWallets

Stateless convenience facade for wallet operations. Mirrors the `CrossmintWallets` entry point used by the TS, Kotlin, and Swift SDKs. For reactive state management (e.g. listening to wallet changes in a widget tree), use `CrossmintWalletController` instead.

Unlike the controller, the facade does **not** auto-resolve signer configs. Callers must provide fully-resolved configs (e.g. include the email address in `CrossmintEmailSignerConfig`, or pre-create a device signer via `createDeviceSigner()`).

### Constructor

<ResponseField name="CrossmintWallets.from()" type="CrossmintWallets">
  Creates a `CrossmintWallets` facade backed by `client`.
</ResponseField>

### Methods

<ResponseField name="getWallet(chain, alias, plugins, recoverySigner)" type="Future<CrossmintRuntimeWalletBase>">
  Retrieves an existing wallet and returns a typed runtime wallet.

  <Expandable title="parameters">
    <ResponseField name="chain" type="String" required />

    <ResponseField name="alias" type="String?" />

    <ResponseField name="plugins" type="List<String>" />

    <ResponseField name="recoverySigner" type="CrossmintSignerConfig?" />
  </Expandable>
</ResponseField>

<ResponseField name="createWallet(chain, recovery, signers, owner, alias, plugins)" type="Future<CrossmintRuntimeWalletBase>">
  Creates a new wallet and returns a typed runtime wallet.

  <Expandable title="parameters">
    <ResponseField name="chain" type="String" required />

    <ResponseField name="recovery" type="CrossmintSignerConfig" required />

    <ResponseField name="signers" type="List<CrossmintSignerConfig>" />

    <ResponseField name="owner" type="String?" />

    <ResponseField name="alias" type="String?" />

    <ResponseField name="plugins" type="List<String>" />
  </Expandable>
</ResponseField>

<ResponseField name="createDeviceSigner(storage, address)" type="Future<CrossmintDeviceSignerDescriptor>">
  Creates a device signer descriptor.

  <Expandable title="parameters">
    <ResponseField name="storage" type="DeviceSignerKeyStorage?" />

    <ResponseField name="address" type="String?" />
  </Expandable>
</ResponseField>

<ResponseField name="createPasskeySigner(name, config)" type="Future<CrossmintPasskeySignerConfig>">
  Creates a passkey signer configuration.

  <Expandable title="parameters">
    <ResponseField name="name" type="String" required />

    <ResponseField name="config" type="CrossmintPasskeyConfig" required />
  </Expandable>
</ResponseField>

### Usage

```dart theme={null}
import 'package:crossmint_flutter/crossmint_flutter_wallets.dart';

final wallets = CrossmintWallets.from(
  client,
  onAuthRequired: (challenge) {
    // Show your OTP prompt UI, then call challenge.verify(code)
  },
);

// Get an existing wallet
final wallet = await wallets.getWallet(chain: 'base-sepolia');
print('Address: ${wallet.address}');

// Create a new wallet
final newWallet = await wallets.createWallet(
  chain: 'base-sepolia',
  recovery: CrossmintEmailSignerConfig(email: 'YOUR_USER_EMAIL'),
);

// Send tokens
final tx = await newWallet.sendToken('RECIPIENT_WALLET_ADDRESS', 'usdc', '10');
```

***

## Wallet Methods

The runtime wallet instances returned by the controller provide methods for token transfers, balances, signing, and more.

### Properties

| Property                        | Type                                   | Description                                                                          |
| ------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------ |
| `wallet.client`                 | `CrossmintClient`                      | Top-level Crossmint client shared by all wallet operations.                          |
| `wallet.wallet`                 | `CrossmintWallet`                      | Server-issued wallet snapshot backing this runtime instance.                         |
| `wallet.deviceSignerKeyStorage` | `DeviceSignerKeyStorage?`              | Storage backend for hardware-backed device signer keys.                              |
| `wallet.onAuthRequired`         | `CrossmintSignerAuthRequiredCallback?` | Callback invoked when a signer operation requires user authentication.               |
| `wallet.defaultChain`           | `String`                               | Chain identifier used when the wallet snapshot has no explicit chain.                |
| `wallet.chain`                  | `String`                               | Blockchain this wallet operates on (falls back to \[defaultChain]).                  |
| `wallet.address`                | `String`                               | On-chain address of the wallet.                                                      |
| `wallet.owner`                  | `String?`                              | Owner identifier associated with the wallet, if any.                                 |
| `wallet.recovery`               | `CrossmintSignerConfig?`               | Recovery (admin) signer config parsed from the wallet's backend state.               |
| `wallet.locator`                | `String`                               | Unique wallet locator used for API calls.                                            |
| `wallet.signer`                 | `CrossmintWalletApprovalSigner?`       | Currently active approval signer, or null if none is selected.                       |
| `wallet.additionalSigners`      | `List<CrossmintWalletApprovalSigner>`  | Secondary signers available alongside the primary \[signer].                         |
| `wallet.needsRecovery`          | `bool`                                 | Whether the wallet needs recovery (signer registration) before the next transaction. |
| `wallet.canExportPrivateKey`    | `bool`                                 | Whether the active signer supports private-key export.                               |

### Methods

| Method                                                                    | Description                                                                                                                                                                                              |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wallet.useSigner(signer)`                                                | Sets the active signer for this wallet. Accepts a signer config object; the locator is inferred internally. Works for device, non-custodial, external-wallet, and passkey signers.                       |
| `wallet.balances(tokens)`                                                 | Returns the wallet balances — always includes USDC and the native token. Pass additional \[tokens] to request extra token balances.                                                                      |
| `wallet.transactions()`                                                   | Lists transaction summaries for this wallet.                                                                                                                                                             |
| `wallet.transaction(transactionId)`                                       | Retrieves details for a specific transaction by ID.                                                                                                                                                      |
| `wallet.approve(request)`                                                 | Approves a pending transaction or signature request.                                                                                                                                                     |
| `wallet.nfts(page, perPage)`                                              | Returns the wallet's NFTs (paginated).                                                                                                                                                                   |
| `wallet.fund(request)`                                                    | Initiates a funding (on-ramp) request for this wallet.                                                                                                                                                   |
| `wallet.transfers(page, perPage, tokens, status)`                         | Reads transfer history for this wallet (unstable API).                                                                                                                                                   |
| `wallet.send(request)`                                                    | Sends a token transfer and waits for confirmation.                                                                                                                                                       |
| `wallet.sendToken(to, token, amount)`                                     | Convenience wrapper around \[send] for simple token transfers.                                                                                                                                           |
| `wallet.signers()`                                                        | Lists all signers registered on this wallet with their current status.                                                                                                                                   |
| `wallet.removeSigner(signerLocator)`                                      | Removes a signer from the wallet by its locator.                                                                                                                                                         |
| `wallet.signerIsRegistered(signerLocator)`                                | Checks whether a signer with the given locator is registered.                                                                                                                                            |
| `wallet.isSignerApproved(signerLocator)`                                  | Checks whether a registered signer is approved and usable.                                                                                                                                               |
| `wallet.addSigner(signer)`                                                | Registers a new signer on the wallet and approves the registration.                                                                                                                                      |
| `wallet.preAuthIfNeeded()`                                                | Awaits any in-flight recovery; if `_needsRecovery` is set and no recovery is in-flight, starts one and awaits it. Concurrent callers coalesce onto the same future so a single recovery runs per wallet. |
| `wallet.recover()`                                                        | Runs device-signer recovery: resumes any pending operation or registers a new device signer using the recovery signer. Returns early if the device signer is already approved on-chain.                  |
| `wallet.exportPrivateKey(bridge)`                                         | Exports the wallet's private key via the export signer bridge.                                                                                                                                           |
| `wallet.approveTransactionAndWait(transactionId)`                         | Approves a pending transaction and polls until it reaches a terminal state.                                                                                                                              |
| `wallet.transactionApprovalPayload(transaction, pendingApproval, signer)` | Extracts the signable payload from a pending transaction approval.                                                                                                                                       |
| `wallet.completeApprovedTransaction(transaction)`                         | Polls a transaction until it reaches a terminal state and attaches explorer links.                                                                                                                       |
| `wallet.waitForTransaction(transactionId)`                                | Polls a transaction by ID until it succeeds or fails.                                                                                                                                                    |
| `wallet.walletSnapshotForSigners()`                                       | Fetches a fresh wallet snapshot to inspect registered signers.                                                                                                                                           |
| `wallet.requireActiveSigner(operation)`                                   | Returns the active signer or throws if none is selected.                                                                                                                                                 |

**Chain-specific methods:**

* **`CrossmintEvmWallet`** — `sendTransaction()`, `signMessage()`, `signTypedData()`
* **`CrossmintSolanaWallet`** — `sendTransaction()`
* **`CrossmintStellarWallet`** — `sendTransaction()`

### Usage

`controller.currentWallet` holds the loaded domain model. Signing, balance, and transaction methods live on the runtime wallet returned by `controller.createEvmWallet()` (or `createSolanaWallet` / `createStellarWallet`).

```dart theme={null}
if (controller.currentWallet == null) return;

final wallet = controller.createEvmWallet();

final balances = await wallet.balances(tokens: <String>['usdc']);
print('USDC: ${balances.usdc.amount}');
print('Native: ${balances.nativeToken.amount}');

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

final signature = await wallet.signMessage('Hello, Web3!');
print('Signature: $signature');
```
