> ## 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="auth" type="CrossmintAuthClient">
  The authentication client for login/logout flows.
</ResponseField>

<ResponseField name="wallets" type="CrossmintWalletsClient">
  The wallets API client for direct wallet operations.
</ResponseField>

<ResponseField name="credentials" type="CrossmintCredentialsClient">
  The verifiable credentials client.
</ResponseField>

<ResponseField name="orders" type="CrossmintOrdersClient">
  The orders API client.
</ResponseField>

<ResponseField name="tokens" type="CrossmintTokensClient">
  The tokens API client.
</ResponseField>

<ResponseField name="users" type="CrossmintUsersClient">
  The users API client.
</ResponseField>

### Methods

<ResponseField name="initialize()" type="Future<void>">
  Initializes the SDK. Must be called before using any other methods.
</ResponseField>

<ResponseField name="dispose()" type="void">
  Cleans up resources.
</ResponseField>

<ResponseField name="createWalletController(config)" type="CrossmintWalletController">
  Creates a wallet controller with the specified configuration.

  <Expandable title="parameters">
    <ResponseField name="config" type="CrossmintWalletControllerConfig" required>
      Configuration for wallet creation and management. See [Providers](/sdk-reference/wallets/flutter/providers#crossmintwalletcontrollerconfig).
    </ResponseField>
  </Expandable>
</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.

### Properties

<ResponseField name="state" type="CrossmintAuthState">
  The current auth state (synchronous getter).
</ResponseField>

<ResponseField name="stateListenable" type="ValueListenable<CrossmintAuthState>">
  Listenable for auth state changes. Use with `ListenableBuilder` or `ValueListenableBuilder`.
</ResponseField>

### Methods

<ResponseField name="restoreSession()" type="Future<void>">
  Restores a persisted auth session on app launch. Call during app initialization.
</ResponseField>

<ResponseField name="sendEmailOtp(email)" type="Future<CrossmintEmailOtpChallenge>">
  Sends a one-time password to the user's email address.

  <Expandable title="parameters">
    <ResponseField name="email" type="String" required>
      The email address to send the OTP to.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="confirmEmailOtp(email, emailId, token)" type="Future<bool>">
  Verifies the one-time password and completes authentication.

  <Expandable title="parameters">
    <ResponseField name="email" type="String" required>
      The email address that was used to send the OTP.
    </ResponseField>

    <ResponseField name="emailId" type="String" required>
      The email ID returned by `sendEmailOtp`.
    </ResponseField>

    <ResponseField name="token" type="String" required>
      The OTP code entered by the user.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="loginWithOAuth(provider)" type="Future<void>">
  Initiates an OAuth login flow with the specified provider.

  <Expandable title="parameters">
    <ResponseField name="provider" type="CrossmintOAuthProvider" required>
      The OAuth provider (`CrossmintOAuthProvider.google` or `CrossmintOAuthProvider.twitter`).
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="setJwt(jwt)" type="Future<void>">
  Sets a JWT for bring-your-own-auth (BYOA) flows. Pass a valid JWT to
  authenticate; pass `null` to log out. The SDK does not refresh the token —
  callers are responsible for updating it before expiration.

  <Expandable title="parameters">
    <ResponseField name="jwt" type="String?" required>
      The JWT from your auth provider, or `null` to clear the session.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="getSession()" type="Future<CrossmintAuthSession?>">
  Returns the current auth session, or `null` if not authenticated.
</ResponseField>

<ResponseField name="getUser({forceRefresh})" type="Future<CrossmintAuthenticatedUser?>">
  Returns the authenticated user profile, or `null` if not authenticated.

  <Expandable title="parameters">
    <ResponseField name="forceRefresh" type="bool">
      If `true`, re-fetches the profile from the server instead of using the
      cached value. Defaults to `false`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="logout()" type="Future<void>">
  Signs out and clears the session.
</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="currentWallet" type="CrossmintWallet?">
  The currently-loaded wallet domain model, or null if no wallet is loaded.
  Use [`createEvmWallet`](#methods) / [`createSolanaWallet`](#methods) /
  [`createStellarWallet`](#methods) to wrap it in a runtime wallet that exposes
  signing, balance, and transaction methods.
</ResponseField>

<ResponseField name="status" type="CrossmintWalletStatus">
  Current wallet status: `notLoaded`, `inProgress`, `loaded`, or `error`.
</ResponseField>

<ResponseField name="state" type="CrossmintWalletStateView">
  Read-only `Listenable` surface exposing `currentWallet`, `currentWalletId`, and
  `status`. Subscribe to this (or to the controller itself — it extends
  `ChangeNotifier`) to react to wallet lifecycle changes.
</ResponseField>

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

  <Expandable title="properties">
    <ResponseField name="sendOtp()" type="Future<void>">
      Sends a one-time password to the user's email or phone.
    </ResponseField>

    <ResponseField name="verifyOtp(code)" type="Future<void>">
      Verifies the OTP entered by the user.
    </ResponseField>

    <ResponseField name="challengeListenable" type="ValueListenable<CrossmintOtpChallenge?>">
      Listenable for OTP challenge state. Non-null when an OTP prompt is needed.
    </ResponseField>
  </Expandable>
</ResponseField>

### Methods

<ResponseField name="createWallet(request)" type="Future<CrossmintWallet>">
  Creates a new wallet and stores it as the controller's `currentWallet`.

  <Expandable title="parameters">
    <ResponseField name="request" type="CrossmintWalletCreateRequest" required>
      Request containing `chain`, `recovery` signer, and optional `signers`,
      `owner`, `alias`, and `plugins`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="getWallet(request)" type="Future<CrossmintWallet?>">
  Retrieves an existing wallet and stores it as the controller's `currentWallet`.

  <Expandable title="parameters">
    <ResponseField name="request" type="CrossmintWalletLookupRequest" required>
      Lookup request containing the `chain`, optional `alias`, and `plugins`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="createEvmWallet({signer, additionalSigners})" type="CrossmintEvmWallet">
  Wraps the currently-loaded wallet in a runtime EVM wallet bound to the given
  signer. The controller must already have a loaded wallet (via `getWallet` /
  `createWallet` or auto-creation on login). Returns an instance that exposes
  `sendToken`, `balances`, `signMessage`, `signTypedData`, `sendTransaction`, etc.

  <Expandable title="parameters">
    <ResponseField name="signer" type="CrossmintWalletApprovalSigner">
      Primary approval signer. Optional — omit to rely on recovery-only flows.
    </ResponseField>

    <ResponseField name="additionalSigners" type="List<CrossmintWalletApprovalSigner>">
      Additional approval signers. Defaults to an empty list.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="createSolanaWallet({signer, additionalSigners})" type="CrossmintSolanaWallet">
  Solana equivalent of `createEvmWallet`. Solana wallets do not support device
  signers.
</ResponseField>

<ResponseField name="createStellarWallet({signer, additionalSigners})" type="CrossmintStellarWallet">
  Stellar equivalent of `createEvmWallet`.
</ResponseField>

<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(client, {onAuthRequired, deviceSignerKeyStorage})" type="CrossmintWallets">
  Creates a facade backed by the given client.

  <Expandable title="parameters">
    <ResponseField name="client" type="CrossmintClient" required>
      The initialized Crossmint client.
    </ResponseField>

    <ResponseField name="onAuthRequired" type="CrossmintSignerAuthRequiredCallback?">
      Callback invoked when an OTP challenge starts. Required for email/phone signers.
    </ResponseField>

    <ResponseField name="deviceSignerKeyStorage" type="DeviceSignerKeyStorage?">
      Storage backend for device signer keys.
    </ResponseField>
  </Expandable>
</ResponseField>

### Methods

<ResponseField name="getWallet({chain, alias, plugins})" type="Future<CrossmintRuntimeWalletBase>">
  Retrieves an existing wallet. Throws `CrossmintWalletException` if not found.

  <Expandable title="parameters">
    <ResponseField name="chain" type="String" required>
      The blockchain to look up (e.g. `"base-sepolia"`).
    </ResponseField>

    <ResponseField name="alias" type="String">
      Optional wallet alias.
    </ResponseField>

    <ResponseField name="plugins" type="List<String>">
      Optional wallet plugins.
    </ResponseField>
  </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>
      The blockchain to create the wallet on.
    </ResponseField>

    <ResponseField name="recovery" type="CrossmintSignerConfig" required>
      The recovery signer configuration.
    </ResponseField>

    <ResponseField name="signers" type="List<CrossmintSignerConfig>">
      Additional signer configurations.
    </ResponseField>

    <ResponseField name="owner" type="String">
      Optional wallet owner identifier.
    </ResponseField>

    <ResponseField name="alias" type="String">
      Optional wallet alias.
    </ResponseField>

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

<ResponseField name="createDeviceSigner({storage, address})" type="Future<CrossmintDeviceSignerDescriptor>">
  Creates a device signer descriptor for hardware-backed signing.
</ResponseField>

<ResponseField name="createPasskeySigner({name, config})" type="Future<CrossmintPasskeySignerConfig>">
  Creates a passkey signer configuration.
</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.address`  | `String`                 | The wallet address                                  |
| `wallet.chain`    | `String`                 | The chain this wallet lives on                      |
| `wallet.owner`    | `String?`                | The wallet owner identifier (e.g. email or user ID) |
| `wallet.recovery` | `CrossmintSignerConfig?` | The recovery signer configuration                   |
| `wallet.locator`  | `String`                 | The wallet ID used for API calls                    |

### Methods

| Method                                | Description                                                                                       |
| ------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `wallet.sendToken(to, token, amount)` | Send a token using positional arguments (convenience)                                             |
| `wallet.send(request)`                | Send a token using a `CrossmintWalletTokenTransferRequest` (advanced)                             |
| `wallet.balances({tokens})`           | Get the wallet balances — always includes USDC and native token; pass `tokens:` to include extras |
| `wallet.signers()`                    | List the signers for this wallet                                                                  |
| `wallet.addSigner(config)`            | Add a signer to the wallet using the recovery signer                                              |
| `wallet.removeSigner(locator)`        | Remove a signer from the wallet                                                                   |
| `wallet.signerIsRegistered(locator)`  | Check if a signer is registered                                                                   |
| `wallet.transactions()`               | Get the wallet transaction history                                                                |
| `wallet.transaction(id)`              | Get a transaction by ID                                                                           |
| `wallet.transfers()`                  | Get the wallet transfers                                                                          |
| `wallet.nfts()`                       | Get the wallet NFTs                                                                               |
| `wallet.fund()`                       | Fund the wallet with test tokens (staging only)                                                   |

**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');
```
