crossmint_flutter 0.1.4
crossmint_flutter: ^0.1.4 copied to clipboard
Flutter SDK for Crossmint wallets, authentication, signers, embedded checkout, and verifiable credentials. Headless-first — you own the UI.
Crossmint Flutter SDK
The Flutter SDK for integrating Crossmint wallets, authentication, checkout, and verifiable credentials into your mobile app
Explore the docs »
View Example App
·
Report Bug
·
Request Feature
Table of Contents
About The Project #
The Crossmint Flutter SDK brings the full power of the Crossmint platform to Flutter mobile apps. It is the Flutter counterpart to the Crossmint SDK for React and React Native.
The SDK is headless-first: you own the app UI, while the SDK provides client surfaces, runtime wallet helpers, auth callback wiring, a hidden signer runtime, and native device-signer integration. Optional UI components are available for common flows like authentication forms and OTP prompts.
Why Crossmint? #
- Quick Integration: Get up and running in minutes with a single package dependency
- Developer-First: Build end-to-end blockchain solutions without deep Web3 expertise
- Secure: Enterprise-grade security with Secure Enclave / Android Keystore device signers
- Fiat-First: Enable users to participate in Web3 without cryptocurrency via embedded checkout
- Free to Start: Start developing at no cost
Core Features #
- Embedded wallets: Create and manage EVM, Solana, and Stellar wallets
- Authentication: Email OTP, OAuth (Google, Twitter), wallet-based sign-in, and session management
- Signers: Email, phone, device, external wallet, passkey, and server signers
- Embedded checkout: Credit card and crypto payments via a drop-in WebView widget
- Verifiable credentials: Issue, present, and verify on-chain credentials
Package Overview #
Most apps should depend only on the main SDK package:
crossmint_flutter: The main Flutter SDK — auth, wallets, signers, checkout, and credentials.
The SDK is composed of several internal packages that you typically do not need to depend on directly:
crossmint_core: Pure Dart base abstractions (no Flutter or external dependencies)crossmint_auth: Authentication domain logiccrossmint_wallets: Wallet domain logiccrossmint_verifiable_credentials: Verifiable credentials helperscrossmint_device_signer: Native plugin for on-device key storage (Secure Enclave / Android Keystore)
Getting Started #
Prerequisites #
- Flutter mobile app targeting iOS and/or Android
- Flutter 3.41+ and Dart 3.11+
- A Crossmint client key (get one here)
- An app callback scheme for OAuth flows on mobile
Installation #
Add the SDK to your project:
flutter pub add crossmint_flutter
Quickstart #
A full integration is four steps: initialize the client, sign the user in, mount the wallet host, and load a wallet. OTP prompt handling is covered in a reference subsection afterwards.
1. Initialize the client
import 'package:flutter/widgets.dart';
import 'package:crossmint_flutter/crossmint_client.dart';
import 'package:crossmint_flutter/crossmint_flutter_auth.dart';
import 'package:crossmint_flutter/crossmint_flutter_controllers.dart';
late final CrossmintClient client;
late final CrossmintWalletController walletController;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
client = CrossmintClient(
config: const CrossmintClientConfig(
apiKey: String.fromEnvironment('CROSSMINT_API_KEY'),
appScheme: String.fromEnvironment('CROSSMINT_APP_SCHEME'),
),
);
await client.initialize();
await client.auth.restoreSession();
final authRouter = CrossmintAuthCallbackRouter(auth: client.auth);
await authRouter.start();
walletController = client.createWalletController(
CrossmintWalletControllerConfig(
createOnLogin: CrossmintCreateOnLoginConfig(
chain: CrossmintChain.baseSepolia.apiValue,
recovery: const CrossmintEmailSignerConfig(),
),
),
);
runApp(MyApp(client: client, walletController: walletController));
}
restoreSession() hydrates a persisted JWT if a previous session is still valid, so returning users skip sign-in. createOnLogin (consumed by walletController.ensureLoaded() in step 4) tells the controller to create-or-fetch a wallet when it's invoked; it's not an auth-state listener on its own.
Recovery signer.
CrossmintEmailSignerConfig()with no email means "use the signed-in user's email," which the controller reads fromauth.getUser()during wallet creation. This assumes email-based sign-in (OTP or OAuth). If you use wallet sign-in — where the authenticated user may not have an email — either passCrossmintEmailSignerConfig(email: '...')with an explicit address or choose a different recovery signer (e.g. an external-wallet signer); otherwise wallet creation throwsCrossmintWalletException.OTP UI.
CrossmintWalletControllerConfig.showOtpSignerPromptis inert plumbing at the moment — nothing in the runtime OTP path reads it. To render the default Material OTP modal, useCrossmintWalletProvider(fromcrossmint_flutter_ui.dart) instead ofCrossmintWalletHostand passotpPromptBuilder: crossmintDefaultOtpPromptBuilderonCrossmintWalletProviderConfig. In the headlessCrossmintWalletHostflow shown here, drive OTP yourself viawalletController.otp.challengeListenable(see OTP prompt handling).
2. Sign the user in
Email OTP — read back challenge.state and pass it as emailId when confirming:
final challenge = await client.auth.sendEmailOtp(email);
final ok = await client.auth.confirmEmailOtp(
email: email,
emailId: challenge.state ?? '',
token: codeEnteredByUser,
);
OAuth — the redirect returns through the CrossmintAuthCallbackRouter started in step 1. This requires the deep-link entries in Platform Setup.
await client.auth.loginWithOAuth(CrossmintOAuthProvider.google);
// or CrossmintOAuthProvider.twitter
Wallet sign-in (SIWE / SIWS) is also supported via client.auth.signInWithWallet(address, type) — see doc/architecture/public-api-spec.md.
3. Mount the wallet host
Wrap your app tree in CrossmintWalletHost so the hidden signer WebView bridge has a place to run:
class MyApp extends StatelessWidget {
const MyApp({super.key, required this.client, required this.walletController});
final CrossmintClient client;
final CrossmintWalletController walletController;
@override
Widget build(BuildContext context) {
return CrossmintWalletHost(
client: client,
walletController: walletController,
child: /* your app tree */,
);
}
}
Required for non-custodial signers. Email and phone signers run inside a hidden WebView that the SDK manages for you.
CrossmintWalletHost(headless, re-exported fromcrossmint_flutter_controllers.dart) — orCrossmintWalletProviderfromcrossmint_flutter_ui.dartif you want the opt-in default UI — must be mounted in the widget tree. Without one,wallet.useSigner(CrossmintEmailSignerConfig(...))throwsCrossmintSignerException(code: nonCustodialBridgeHostNotMounted). Passkey signers sign viaonSignWithPasskeycallbacks and do not require a host.
4. Load or create a wallet
Loading is a two-step process: (a) resolve a CrossmintWallet into walletController.currentWallet (a data model), then (b) wrap it in a runtime wallet that can sign and send transactions. End-to-end:
await walletController.ensureLoaded(); // a. resolve currentWallet
final wallet = walletController.createEvmWallet(); // b. wrap as runtime EVM wallet
await wallet.useSigner(CrossmintEmailSignerConfig(email: signedInEmail));
await wallet.sendToken('0xRecipient', 'usdc', '1.0');
The subsections below break each step down.
Step a — resolve currentWallet. Pick by user journey:
ensureLoaded()+createOnLogin. ConfigurecreateOnLogin(see step 1) and callwalletController.ensureLoaded()yourself after a successful sign-in. The controller creates-or-fetches a wallet matching the config. (If you useCrossmintWalletProviderfromcrossmint_flutter_ui.dartinstead of the headlessCrossmintWalletHost, the provider invokesensureLoaded()for you on auth-state change whenCrossmintWalletProviderConfig.autoLoadWalletis true — the default.)- Explicit fetch / create — for returning users or multi-wallet apps. Fetch by chain:
await walletController.getWallet(
CrossmintWalletLookupRequest(chain: CrossmintChain.baseSepolia.apiValue),
);
Or create explicitly with a recovery signer:
await walletController.createWallet(
CrossmintWalletCreateRequest(
chain: CrossmintChain.baseSepolia.apiValue,
recovery: const CrossmintEmailSignerConfig(),
),
);
- Runtime-only facade — for apps that manage wallet lifecycle themselves (no controller state). The facade returns a typed runtime wallet directly, skipping the data-model / runtime split. Signer configs must be fully resolved (pass an explicit email; no
auth.getUser()lookup happens here). OTP prompts come throughonAuthRequired— parameter semantics in the subsection below:
final wallets = CrossmintWallets.from(
client,
onAuthRequired: (signerType, signerLocator, needsAuth, sendOtp, verifyOtp, reject) async {
if (!needsAuth) return;
await sendOtp();
final code = await showMyOtpDialog();
await verifyOtp(code);
},
);
final wallet = await wallets.createWallet(
chain: CrossmintChain.baseSepolia.apiValue,
recovery: const CrossmintEmailSignerConfig(email: 'user@example.com'),
);
await wallet.useSigner(const CrossmintEmailSignerConfig(email: 'user@example.com'));
await wallet.sendToken('0xRecipient', 'usdc', '1.0');
Step b — build a runtime wallet. walletController.currentWallet is a CrossmintWallet data model; transaction APIs (useSigner, sendToken, sendTransaction, …) live on the runtime wallet types. Wrap the loaded model with one of the controller's builders:
final wallet = walletController.createEvmWallet();
await wallet.useSigner(CrossmintEmailSignerConfig(email: signedInEmail));
await wallet.sendToken('0xRecipient', 'usdc', '1.0');
Use createSolanaWallet() / createStellarWallet() for other chain families, or one of the signer-bundled conveniences when you want the signer attached at construction:
createEvmWalletWithDeviceSigner— hardware-backed device keys (Secure Enclave / Android Keystore).createEvmWalletWithNonCustodialSigner— email / phone signer backed by the hidden WebView bridge.createEvmWalletWithExternalWalletSigner— user-supplied sign callbacks (e.g. an in-app external-wallet adapter).createEvmWalletWithPasskeySigner— passkey signer via host-provided callbacks.
All throw CrossmintWalletException if no wallet is loaded.
Chain values.
CrossmintChain(inpackages/crossmint_core/lib/src/models/crossmint_chain.dart) enumerates every supported chain (EVM mainnets + testnets, Solana, Stellar) and itsapiValue. UseCrossmintChain.fromApiValue('...')for safe parsing from dynamic strings, andtryFromApiValue(...)for a nullable return.
OTP prompt handling
When a non-custodial email or phone signer needs authentication, the SDK raises an OTP challenge. Two handling paths:
- Controller-driven (recommended). Observe
walletController.otp.challengeListenable(aValueListenable<CrossmintOtpChallenge?>) and render your own prompt, or passotpPromptBuilder: crossmintDefaultOtpPromptBuilder(fromcrossmint_flutter_ui.dart) toCrossmintWalletProviderConfigfor the Material default modal. - Headless callback. When using
CrossmintWallets.from(...)directly, supplyonAuthRequired:
| Param | Meaning |
|---|---|
signerType |
"email" or "phone" |
signerLocator |
The locator string (e.g. email address or E.164 phone) |
needsAuth |
true when the challenge opens, false when the SDK resolves it |
sendOtp() |
Asks the Crossmint API to (re)send the code |
verifyOtp(code) |
Submits the code the user entered |
reject() |
Cancels the challenge |
For more details, see the Crossmint documentation.
Embedded Checkout #
The SDK includes CrossmintEmbeddedCheckout, a drop-in WebView widget for credit card and crypto payments. It supports fiat and crypto payment methods, custom theming, and can delegate crypto signing to a runtime wallet:
import 'package:crossmint_flutter/crossmint_flutter_ui.dart';
import 'package:crossmint_flutter/crossmint_flutter_wallets.dart';
final config = CrossmintCheckoutConfig(
order: CrossmintCheckoutNewOrder.typed(
lineItems: [
CrossmintCheckoutLineItem.collection(
collectionLocator: 'crossmint:your-collection-id',
callData: const {'quantity': 1},
),
],
),
payment: CrossmintCheckoutPayment(
crypto: CrossmintCheckoutCryptoPayment(
enabled: true,
payer: CrossmintEvmWalletCheckoutPayer(
wallet: wallet,
supportedChains: const ['base-sepolia'],
),
),
defaultMethodType: CrossmintCheckoutDefaultMethod.crypto,
),
);
// In your widget tree:
CrossmintEmbeddedCheckout(
apiKey: 'ck_staging_...',
config: config,
onOrderUpdated: (order) => debugPrint('Order: ${order['orderId']}'),
);
CrossmintEvmWalletCheckoutPayer requires a runtime CrossmintEvmWallet (not the CrossmintWallet data model) — build it with walletController.createEvmWallet() (or one of the signer-bundled conveniences) per step 4b of the Quickstart. onOrderUpdated receives a Map<String, Object?>, so read fields with order['key']. Pass supportedChains as API-value strings (e.g. CrossmintChain.baseSepolia.apiValue).
EVM-only crypto payer for now.
CrossmintEvmWalletCheckoutPayeris currently the only bundled crypto payer; Solana and Stellar runtime wallets don't have equivalents yet. Fiat-only flows work across all chains.
Widget reference #
| Param | Type | Purpose |
|---|---|---|
apiKey |
String |
Crossmint client API key. Required. |
config |
CrossmintCheckoutConfig |
Order, payment, and appearance config. Required. |
payer |
CrossmintCheckoutPayer? |
Crypto payer override. Takes precedence over config.payment.crypto.payer. |
checkoutController |
CrossmintCheckoutController? |
Reactive handle on order state (phase, quote, orderClientSecret). |
onOrderUpdated |
void Function(Map<String, Object?>)? |
Fires on order state changes. |
onOrderCreationFailed |
void Function(String)? |
Fires when the server rejects order creation. |
onDiagnostic |
void Function(CrossmintCheckoutDiagnostic)? |
Runtime diagnostics (blocked navigation, malformed hosted messages, WebView errors). |
loadingBuilder |
CrossmintCheckoutLoadingBuilder? |
Custom loading widget (typedef: Widget Function(BuildContext)). Defaults to a Material spinner; override for headless apps. |
defaultHeight |
double |
Initial height in logical pixels before the hosted page reports its actual height. Defaults to 400. |
Fiat-only checkout #
For a pure credit-card / Apple Pay / Google Pay flow, omit the crypto payer
and pass an email recipient so Crossmint mints into a managed wallet. Swap
in CrossmintCheckoutWalletRecipient when minting directly to a
user-owned external wallet address instead:
final config = CrossmintCheckoutConfig(
order: CrossmintCheckoutNewOrder.typed(
lineItems: [
CrossmintCheckoutLineItem.collection(
collectionLocator: 'crossmint:your-collection-id',
),
],
recipient: const CrossmintCheckoutEmailRecipient(email: 'user@example.com'),
),
payment: const CrossmintCheckoutPayment(
fiat: CrossmintCheckoutFiatPayment(enabled: true, defaultCurrency: 'USD'),
defaultMethodType: CrossmintCheckoutDefaultMethod.fiat,
),
);
Physical goods #
For a merchandise-style line item, use
CrossmintCheckoutPhysicalRecipient with a shipping address:
final config = CrossmintCheckoutConfig(
order: CrossmintCheckoutNewOrder.typed(
lineItems: [
CrossmintCheckoutLineItem.product(productLocator: 'crossmint:tshirt'),
],
recipient: CrossmintCheckoutPhysicalRecipient(
email: 'user@example.com',
physicalAddress: const CrossmintCheckoutPhysicalAddress(
name: 'Jane Doe',
line1: '123 Market St',
city: 'San Francisco',
state: 'CA',
postalCode: '94103',
country: 'US',
),
),
),
payment: const CrossmintCheckoutPayment(
fiat: CrossmintCheckoutFiatPayment(enabled: true),
),
);
Recipient types #
Pick the recipient that matches the fulfillment shape of your line items:
| Recipient | Use when | Required fields |
|---|---|---|
CrossmintCheckoutEmailRecipient |
Minting to a Crossmint-managed wallet keyed to an email address. | email |
CrossmintCheckoutWalletRecipient |
Minting directly to a user-owned external wallet (EVM / Solana address). | walletAddress |
CrossmintCheckoutPhysicalRecipient |
Shipping physical goods — no mint, just fulfillment. | email, physicalAddress |
Any recipient may additionally carry a CrossmintCheckoutPhysicalAddress when a line item is hybrid (digital + physical).
Theming #
Pass a CrossmintCheckoutAppearance on CrossmintCheckoutConfig(appearance: ...) to theme the hosted page. Three tiers: CSS custom properties, per-element rules, and web fonts.
final appearance = CrossmintCheckoutAppearance(
variables: const <String, Object?>{
'colorPrimary': '#0F172A',
'borderRadius': '12px',
},
rules: const <String, Object?>{
'.Input': {'borderColor': '#CBD5E1'},
'.Tab': {'fontWeight': '600'},
},
fonts: const <Map<String, String>>[
<String, String>{'cssSrc': 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap'},
],
);
The checkout_playground/ app has an interactive theming screen (checkout_playground/lib/screens/theme_screen.dart) you can use to dial in variables and rules before copying them into your app.
Observing diagnostics #
onDiagnostic surfaces navigation blocks, malformed hosted messages, and
WebView resource errors — wire it to your logger in staging / production.
onOrderCreationFailed is the complementary hook for server-side order
rejections:
CrossmintEmbeddedCheckout(
apiKey: 'ck_staging_...',
config: config,
onDiagnostic: (diag) {
debugPrint('[${diag.severity.name}] ${diag.code}: ${diag.message}');
},
onOrderCreationFailed: (message) {
debugPrint('Order creation failed: $message');
},
);
Platform Setup #
OAuth sign-in and CrossmintAuthCallbackRouter rely on a deep-link scheme that matches the appScheme passed to CrossmintClientConfig. Register it on both platforms:
Android — in android/app/src/main/AndroidManifest.xml, inside your main <activity>:
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="your-app-scheme"/>
</intent-filter>
iOS — in ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>your-app-scheme</string>
</array>
</dict>
</array>
The scheme string must match the appScheme you pass to CrossmintClientConfig. See example/android/ and example/ios/ for a working reference.
Demo Applications #
- Wallets Playground — the main example app with auth, wallets, signer management, and transaction flows. Supports multiple scenarios via environment config. See the playground feature checklist.
- Checkout Playground — a standalone app for interactively testing the embedded checkout widget with real-time configuration: payment methods, theming, product metadata, and more.
Building the SDK Locally #
- Clone the repository:
git clone https://github.com/Paella-Labs/crossmint-flutter-sdk.git
- Install dependencies:
cd crossmint-flutter-sdk
flutter pub get
- Run checks:
./scripts/check_pr_gate.sh
For dependency and publish validation:
./scripts/check_dependency_hygiene.sh
Documentation #
- Official Crossmint Docs
- Architecture
- Public API Spec — complete method signatures and types
- Host App Integration Guide — step-by-step host app walkthrough
example/for the full integration shape,checkout_playground/for interactive checkout configuration
Known Limitations #
- iOS physical-device test automation is limited by Flutter #184254 — physical iOS validation requires manual QA.
- Solana device signers are intentionally unsupported to match current Crossmint wallet behavior.
- Passkey signers require explicit callbacks (Stage 1). Native platform passkey support (Stage 2) is pending.
- Some Crossmint API families may require project entitlements.
Contact #
To get in touch with the Crossmint team, visit our contact page or find us on X.