NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #5085 most downloaded on pub.dev
Flutter SDK for Crossmint wallets, authentication, signers, embedded checkout, and verifiable credentials. Headless-first — you own the UI.
Last release today
07 Oct 2026
Ships fairly regularly
a new release about every 5 weeks
Nearly every release is documented
notes for 11 of 11 stable releases
Nothing withdrawn
no release was ever pulled
5 months old
11 releases · first in 2026
One column per month.
The first method authorizes admin operations and is forwarded to the runtime wallet. Pass either recoveryMethods or the deprecated recovery, not both.
onSignWithPasskey as 0x-hex, the same as on EVM, so
one callback works on all chains. On Solana a passkey now signs this
challenge, not the serialized transaction.{signer, signature: {r, s}, metadata}. Before, the SDK put the whole
passkey result under signature, so the API rejected every passkey approval
with a 400, on every chain.CrossmintWallets.createWallet takes recoveryMethods, the same as
createWallet in the TypeScript, Swift and Kotlin SDKs. The first method
authorizes admin operations and is forwarded to the runtime wallet. Pass
either recoveryMethods or the deprecated recovery, not both.CrossmintWallets.createWalletWithRecoveryMethods. It forwards
to createWallet(recoveryMethods:), so existing calls keep working.recovery: X to recoveryMethods: [X] keeps the same wallet.
On Solana and Stellar the request carries config.recoveryMethods instead
of config.adminSigner, and the API treats both the same way. On EVM a
one-entry list is still sent as config.adminSigner.createOnLogin config with both recovery and recoveryMethods, or
with neither, now fails when the wallet controller is built, with
walletInvalidRecoveryConfig or walletMissingRecoveryMethod. Inside
CrossmintWalletProvider the error goes to the provider's initialization
error, not to a later factory or ensureLoaded call.crossmint_core ^0.1.8 and crossmint_wallets ^0.2.1.A wallet can now hold several recovery methods. CrossmintRuntimeWalletBase.recoveryMethods reads every entry of config.recoveryMethods (falling back t
CrossmintRuntimeWalletBase.recoveryMethods reads every entry of
config.recoveryMethods (falling back to the single config.adminSigner);
recovery stays the first entry. Multiple methods are accepted on Solana
and Stellar only. On EVM a one-entry recoveryMethods list is sent as
config.adminSigner, and a longer one throws before the API call.CrossmintWalletCreateRequest.withRecoveryMethods (or
CrossmintCreateOnLoginConfig.withRecoveryMethods), or from the runtime
facade with CrossmintWallets.createWalletWithRecoveryMethods. The default
constructors and createWallet keep their single recovery parameter, so
no request holds both. This matches the paired createWallet methods in
the Swift and Kotlin SDKs.addSigner, removeSigner, and resuming a pending recovery approval) must
name which method authorizes it. The new
CrossmintRuntimeWalletBase.useRecoveryMethod(...) selects it without
changing the active signer, like wallet.useRecoveryMethod in the
TypeScript SDK. The method must be one of recoveryMethods, and server
methods are rejected. When nothing is selected, the SDK picks one in order:
the active signer when it is a recovery method, the recoverySigner:
passed at construction, then the sole method. When several methods exist
and none can be inferred it throws a CrossmintWalletException that names
useRecoveryMethod. A single-method wallet omits the approver field
entirely.recoverySigner: the wallet does not hold is now rejected on every
wallet, not only on a multi-method one. Both membership checks used to be
gated on needing an approver, so a single-method wallet accepted any config
at all and then approved with a method it does not hold, which sent an OTP
to an address that cannot authorize anything. A config with no locator is
the one case still accepted unchecked: only resolving it would reveal its
locator, and resolving eagerly would break a method that cannot sign on this
device. A config the wallet does hold is honoured as before.CrossmintWalletController wallet factory method (createEvmWallet,
createSolanaWallet, createStellarWallet and each
create*WalletWith*Signer variant) now takes an optional recoverySigner:
and defaults it to createOnLogin.recovery, the method the app named as
primary. Without it, a wallet created through
CrossmintCreateOnLoginConfig.withRecoveryMethods could not recover at all:
selection found no method, so every send() threw. The default applies only
to a wallet that holds it: the loaded wallet is not always the one this
config created, and the forwarded method outranks the wallet's own, so a
method the wallet does not hold is withheld rather than used to approve, and
the wallet's own recovery method is used. A bare
CrossmintEmailSignerConfig() takes the signed-in user's email, and is
withheld only until the controller knows it. An explicit recoverySigner:
is always honoured.api-key:<address>, and CrossmintApiKeySignerConfig()
carries no address to build that from, so a wallet rejected its own admin
signer. Matching on the type is what the TypeScript descriptor does too, and
a wallet holds one custodial api-key signer.approver sent to the API now carries the locator the wallet
reported, not the one the caller matched with. A bare api-key is not a
locator the backend's schema accepts, and an address in the other valid
casing is a different string, so the value travelling to the API is always
one the backend itself produced.0xd8da... from one source and 0xd8dA... from another. Membership tests
now compare through crossmintSignerLocatorKey, which case-folds an
external-wallet or server locator holding a 40-character hex address and
leaves every other identifier alone, since Solana base58, Stellar base32,
passkey and device identifiers carry meaning in their case. The key is only
a comparison key: the approver sent to the API keeps the caller's own
bytes, which the backend accepts for either casing.email:ops.team@acme.com whatever case the app configured.
A locator was derived from the caller's own spelling, so
CrossmintEmailSignerConfig(email: 'Ops.Team@Acme.COM') matched no method:
on a wallet with several recovery methods, naming it threw "is not one of
this wallet's recovery methods", and the approver sent for it would have
been rejected by EVM and Solana. Derivation now applies the same rule as the
backend and the TypeScript SDK: the address is lowercased, googlemail.com
becomes gmail.com, and a Gmail local part loses its dots. An explicit
locator is still used exactly as given.id (a passkey) or carrying no
locator (an api-key method) is now recognised as a recovery method. Its
locator is derived the same way a caller-supplied config's is, so activating
such a method with useSigner(...) no longer left addSigner and
removeSigner demanding an approver the caller had already named.recovery: CrossmintEmailSignerConfig(email: 'ops@acme.com') alongside any
blank entry was rewritten to the signed-in user's address. It is now left
alone. Because the admin signer decides the wallet address, an app that
relied on the old rewrite will resolve a different wallet from
ensureLoaded() after upgrading.CrossmintWalletController.createWallet no longer drops owner
when rebuilding the create request; it also now threads the new
recoveryMethods through.crossmint_wallets ^0.2.0 and crossmint_core ^0.1.7.Added WhatsApp as an OTP delivery channel for phone signers. Pass channel: CrossmintOtpDeliveryChannel.whatsapp to CrossmintPhoneSignerConfig, to Cros
channel: CrossmintOtpDeliveryChannel.whatsapp to CrossmintPhoneSignerConfig,
to CrossmintWalletController.createEvmWalletWithNonCustodialSigner,
createSolanaWalletWithNonCustodialSigner and
createStellarWalletWithNonCustodialSigner, or to
CrossmintClient.createEvmNonCustodialSigner,
createSolanaNonCustodialSigner and createStellarNonCustodialSigner. Null
keeps the service default, SMS. The signer service does not store the
choice, so supply it each time the phone signer is selected. Countries with
no registered WhatsApp sender fall back to SMS. The channel is client-only:
it is not sent to the wallet API and is absent from a fetched wallet's
recovery config.createOnLogin.recovery config that sets a channel now also
applies to a wallet that was loaded, not created. createEvmWallet,
createSolanaWallet and createStellarWallet on the controller use it as
the recovery signer when its phone number matches the wallet's recovery
signer, so recovery OTPs go over the requested channel. Any other config
is not used there, and the wallet's own recovery signer applies.AA24 bundler error. The SDK now throws a CrossmintSignerException
with CrossmintErrorCode.passkeyUserVerificationMissing before submitting.
Request the assertion in onSignWithPasskey with
userVerification: "required".crossmint_core ^0.1.6 and crossmint_wallets ^0.1.5, which
carry the new types above.Added CrossmintIdentityVerification, a WebView widget that renders Crossmint's hosted identity verification step on your own route instead of inside e
CrossmintIdentityVerification, a WebView widget that renders
Crossmint's hosted identity verification step on your own route instead of
inside embedded checkout. It reports onReady, onComplete, onCancel and
onError, sizes itself from the hosted page, restricts main-frame navigation
to Crossmint hosts while still allowing the provider's capture iframe, and
clears its loading overlay with a non-retriable error if the page fails to
load. Exported from crossmint_flutter_ui.dart.CrossmintCheckoutController.identityVerificationCredentials, which
parses the current order's payment.preparation.kyc and returns null when
the order needs no verification, names an unknown provider, or carries no
usable inquiry id.CrossmintCheckoutConfig.identityVerificationHandling. Setting it to
CrossmintIdentityVerificationHandling.external tells the hosted checkout to
stop rendering the verification step, so the app must mount
CrossmintIdentityVerification itself or the buyer cannot finish. The flag
is ignored by Crossmint deployments that predate it, which fall back to
rendering verification inside checkout.CrossmintIdentityVerificationStatus,
CrossmintIdentityVerificationErrorReason and
CrossmintIdentityVerificationError, each with an unknown fallback, so an
unrecognised status still reaches onComplete rather than being dropped.CrossmintPaymentMethodManagement now reloads when its URL changes.
It previously had no update handling, so changing jwt, apiKey or
appearance on a mounted widget did nothing. Those changes now reload the
hosted page. Hosts that rebuild the widget with a new JWT will see a reload
where they previously saw none.CrossmintEmbeddedCheckout no longer clears its loading overlay on a
sub-resource HTTP error. It previously cleared on any HTTP error, so a
failed font or beacon inside the hosted page hid the spinner while the main
document was still loading. The overlay now clears only when the failing
request is the page itself; the onDiagnostic callback still fires for
every HTTP error, so diagnostics are unchanged.AndroidWebViewController.setOnPlatformPermissionRequest is not wired up in
any widget, so provider live-capture may still be blocked at runtime pending
device testing.flutter_secure_storage 10 replaces the deprecated Jetpack Crypto backend on Android with new default ciphers and migrates existing entries automatical…
app_links to ^7.0.0 and flutter_secure_storage to ^10.0.0.flutter_secure_storage 10
requires Android minSdk 23, iOS 12, and Flutter 3.19.0; app_links 7
requires iOS 13 and Flutter 3.38.1. The SDK already targets iOS 15. The
effective Flutter floor is 3.41.6 (the first Flutter shipping Dart 3.11.4,
which the sdk: ^3.11.4 constraint requires); the environment block now
declares this explicitly.flutter_secure_storage 10 replaces the deprecated Jetpack Crypto backend on
Android with new default ciphers and migrates existing entries automatically;
it also defaults resetOnError to true. Hosts that pin custom
AndroidOptions should review the package's v10 migration notes before
upgrading.Solana device signers are no longer rejected client-side. Support is provider-dependent (Swig/Crossmint support device signers, Squads does not) and v
permissionless_passkeys (and permissionless), dropping the
Android minSdk floor from 30 to 24. The experimental
PermissionlessPasskeySignerFactory is removed; the default passkey factory
now yields walletUnsupportedPasskeySigner unless a host supplies its own.
The supported callback-based passkey path (onCreatePasskey /
onSignWithPasskey) is unaffected.crossmint_core ^0.1.4 and
crossmint_wallets ^0.1.3.Breaking: the minimum iOS deployment target is now 15. This SDK depends on crossmint_device_signer 0.2.0, whose shared CrossmintDeviceSigner native po
crossmint_device_signer 0.2.0, whose shared CrossmintDeviceSigner
native pod requires iOS 15. Because the dependency is direct, the floor
applies to every consumer's iOS build at CocoaPods resolution time — not
only apps that call the device signer at runtime.CrossmintDeviceSigner
packages — the same native code the React Native SDK uses — for cross-SDK
parity. No Dart API change.Opacity(0.01) + IgnorePointer wrapper, not the Offstage widget — and
added a comment explaining why (a zero-area or unpainted WKWebView is
reported as not visible on iOS, throttling JS timers and risking WebContent
process termination). Mirrors crossmint-sdk React Native PR #1878.Fix: addSigner now swaps the runtime wallet's _signer to the recovery signer before approving the pending registration, then restores the previous sig
addSigner now swaps the runtime wallet's _signer to the
recovery signer before approving the pending registration, then
restores the previous signer in a finally. Registering a signer is
an admin-level op — the backend addresses the pending approval to the
wallet's recovery signer, not to whichever signer is currently active.
Before this change, calling addSigner(device2) while signing with
device1 threw signerApprovalSignerMissing because the pending
approval's email:... locator did not match the device signer.
Matches the TS SDK (packages/wallets/src/wallets/wallet.ts). Callers
that previously worked around this with an explicit
useSigner(recovery) before addSigner no longer need to.Docs: README refresh. Quickstart restructured into four numbered steps (initialize client → sign user in → mount wallet host → load or create wallet),
onAuthRequired callback paths. Documents
the CrossmintWallet data-model vs runtime-wallet split and the four
signer-bundled wallet factory helpers
(createEvmWalletWithDeviceSigner / …WithNonCustodialSigner /
…WithExternalWalletSigner / …WithPasskeySigner). Calls out that
CrossmintWalletControllerConfig.showOtpSignerPrompt is inert plumbing
today and points at crossmintDefaultOtpPromptBuilder instead. Flags
the CrossmintEmailSignerConfig() recovery-signer assumption that the
signed-in user has an email.CrossmintCheckoutEmailRecipient /
…WalletRecipient / …PhysicalRecipient), a theming subsection covering
CrossmintCheckoutAppearance (variables / rules / fonts), and
onOrderCreationFailed alongside onDiagnostic. Corrects the
onOrderUpdated example — the callback receives Map<String, Object?>,
so field access uses order['orderId'] rather than order.orderId.AndroidManifest.xml intent-filter and iOS Info.plist
CFBundleURLTypes entries required for OAuth deep links and the
CrossmintAuthCallbackRouter to work.Expanded dartdoc coverage across the public API: CrossmintClient, CrossmintClientConfig, CrossmintWalletController, CrossmintWalletHost, CrossmintWall
CrossmintClient,
CrossmintClientConfig, CrossmintWalletController, CrossmintWalletHost,
CrossmintWalletProvider[Config], the EVM / Solana / Stellar runtime
wallet classes, every CrossmintWalletApprovalSigner implementation
(device, non-custodial email/phone, external, API-key, exportable,
passkey), the CrossmintAuthClient interface, and the
CrossmintException hierarchy.CrossmintCheckoutConfig, the recipient classes (Email, Wallet,
Physical, + PhysicalAddress), the payment classes (Payment,
FiatPayment, CryptoPayment), transaction result variants, diagnostic
severity, line-item subclasses, and the CrossmintCheckoutPayer /
CrossmintEvmWalletCheckoutPayer bridge.lib/ and the sub-packages under packages/.pubspec.yaml so pub.dev
no longer reports "unknown platforms" on the package page.example/README.md to lead with a minimal copy-paste quickstart
(provider + gate + send token + embedded checkout) with the full playground
documentation retained below.README.md embedded checkout section with fiat-only,
physical-goods, and onDiagnostic snippets.Fixed CrossmintNonCustodialSigner deduplicating concurrent ensureAuthenticated() callers so a single in-flight auth attempt no longer surfaces two onA
CrossmintNonCustodialSigner deduplicating concurrent
ensureAuthenticated() callers so a single in-flight auth attempt no longer
surfaces two onAuthRequired prompts for the same auth event.CrossmintSignerBridgeClient cross-talk between concurrent calls that
share a response event (e.g. two request:sign in flight). Requests are now
serialized per response event so unrelated event pairs (e.g. a get-status
while a sign is pending) still run in parallel. Source-compatible source
break: CrossmintSignerBridgeClient is no longer const-constructible
(no in-tree callers used const, but downstream code that did will need
to drop the const keyword).CrossmintWalletProviderLifecycle.disposeDependencies() to keep
disposing the auth router and client when the wallet controller's disposer
throws. Failures are reported via FlutterError instead of skipping the
remaining cleanup.CrossmintDeserializationException and typed parser helpers
(requireString, optionalString, requireInt, requireBool,
requireMap, requireMapList) in crossmint_core. CrossmintUser.fromJson
is migrated as the first caller; remaining model fromJson sites will follow
in a separate PR.CrossmintErrorCode enum to crossmint_core — typed, machine-readable
error codes for programmatic switch/case exception handling. All exception
subclasses now carry an optional code field (defaults to unknown).
Codes cover auth, wallet, signer, credential, order, and token domains.CrossmintPollingConfig and crossmintPoll() utility to crossmint_core
— exponential backoff with jitter replaces the previous flat 1-second polling
loops in signature and transaction confirmation flows. Default: 1 s initial,
2x growth, 8 s cap, 60 attempts, random jitter in [50 %, 100 %) range.fromJson()/
toJson() methods rather than adopting freezed or json_serializable
codegen, with clear re-evaluation triggers.CrossmintChain enum to crossmint_core — typed, environment-aware
chain validation covering all 24 mainnet chains, 20 testnet chains, Solana,
and Stellar. Includes fromApiValue() / tryFromApiValue() for safe
parsing and validateForEnvironment() for automatic mainnet→testnet
conversion in non-production environments.CrossmintApiKeySigner — a backend-only signer matching the official
TypeScript SDK's API key signer. All signing is handled server-side;
calling signMessage() or signTransaction() locally throws a descriptive
error.CrossmintPasskeySignerConfig model and crossmintGuardAgainstPasskeySigners()
guard. Passkey/WebAuthn signers are blocked in Flutter (matching the official
React Native SDK), with a clear error message guiding users to alternative
signer types.CrossmintApiKeySignerConfig and CrossmintPasskeySignerConfig to the
CrossmintSignerConfig sealed hierarchy in crossmint_wallets.fromJson() / toJson() factory methods to all core model classes:
CrossmintWallet, CrossmintSignerConfig (polymorphic dispatch by type),
CrossmintWalletSigner, CrossmintTransactionRecord,
CrossmintSignatureRecord, CrossmintWalletBalanceSnapshot,
CrossmintNftRecord, CrossmintWalletNftPage, CrossmintUser,
CrossmintOrderRecord, CrossmintTokenAvailabilityRecord, and others.bs58 package. The _decodeBase58() and _encodeBase58() helpers in
crossmint_non_custodial_signer.dart now delegate to base58.decode() and
base58.encode().crossmintIsSolanaChain(),
crossmintIsStellarChain(), _isSolanaChain()) to prefer enum-based
matching via CrossmintChain.tryFromApiValue() with string fallback for
backward compatibility.order.orderId response shape
returned by POST /api/2022-06-09/orders.scripts/dart_defines_from_env.sh so JSON-valued Dart defines work with
direct command substitution in the documented flutter run/test $(...) flow.flutter drive and other
example-side validation paths resolve the local package family consistently.pubspec_overrides.yaml.crossmint_flutter and
cleaned up template metadata in the native device-signer plugin package.Your coding agent can read these notes before it upgrades. Set up the MCP server →