A recovery signer lets users regain access to their wallet when their primary (device) signer is no longer available — for example, when they switch to a new phone or clear their browser data. The recovery signer authorizes the enrollment of a new device signer on the new device.
Recovery signers are configured at wallet creation time . You can choose from email OTP, SMS OTP, or a server signer depending on your application’s needs.
Recovery signers can also sign transactions when needed — they are not limited to recovery only. However, they involve higher friction (OTP verification or server-side signing), so they are best reserved for recovery flows and as a fallback.
Prerequisites
A Crossmint API key with wallets.create scope
For email OTP: the user’s email address
For SMS OTP: the user’s phone number in E.164 format (e.g., +1234567890)
For server signer: a signer secret stored on your server
Email OTP Recovery
The user verifies ownership of their email address via a one-time password sent by Crossmint. This is the most common recovery method for consumer applications.
React
Node.js
React Native
Flutter
REST
Using createOnLogin on the provider (recommended): import {
CrossmintProvider ,
CrossmintAuthProvider ,
CrossmintWalletProvider ,
} from "@crossmint/client-sdk-react-ui" ;
function App ({ children }) {
return (
< CrossmintProvider apiKey = "YOUR_CLIENT_API_KEY" >
< CrossmintAuthProvider
loginMethods = { [ "email" , "google" ] }
>
< CrossmintWalletProvider
createOnLogin = { {
chain: "base-sepolia" ,
recovery: { type: "email" },
} }
>
{ children }
</ CrossmintWalletProvider >
</ CrossmintAuthProvider >
</ CrossmintProvider >
);
}
Or using createWallet directly: import { useWallet } from "@crossmint/client-sdk-react-ui" ;
const { createWallet } = useWallet ();
const wallet = await createWallet ({
chain: "base-sepolia" ,
recovery: {
type: "email" ,
email: "user@example.com" , // Omit when using CrossmintAuthProvider (auto-populated)
},
});
import {
createCrossmint ,
CrossmintWallets ,
} from "@crossmint/wallets-sdk" ;
const crossmint = createCrossmint ({
apiKey: "YOUR_SERVER_API_KEY" ,
});
const crossmintWallets = CrossmintWallets . from ( crossmint );
try {
const wallet = await crossmintWallets . createWallet ({
chain: "base-sepolia" ,
owner: "email:user@example.com" ,
recovery: {
type: "email" ,
email: "user@example.com" ,
},
});
} catch ( error ) {
console . error ( "Failed to create wallet:" , error );
}
import {
CrossmintProvider ,
CrossmintAuthProvider ,
CrossmintWalletProvider ,
} from "@crossmint/client-sdk-react-native-ui" ;
function App ({ children }) {
return (
< CrossmintProvider apiKey = "YOUR_CLIENT_API_KEY" >
< CrossmintAuthProvider
loginMethods = { [ "email" , "google" ] }
>
< CrossmintWalletProvider
createOnLogin = { {
chain: "base-sepolia" ,
recovery: { type: "email" },
} }
>
{ children }
</ CrossmintWalletProvider >
</ CrossmintAuthProvider >
</ CrossmintProvider >
);
}
import 'package:flutter/material.dart' ;
import 'package:crossmint_flutter/crossmint_flutter_ui.dart' ;
class MyApp extends StatelessWidget {
const MyApp ({ super .key});
@override
Widget build ( BuildContext context) {
return MaterialApp (
home : CrossmintWalletProvider (
config : CrossmintWalletProviderConfig (
clientConfig : const CrossmintClientConfig (
apiKey : 'YOUR_CLIENT_API_KEY' ,
appScheme : 'myapp' ,
),
walletControllerConfig : const CrossmintWalletControllerConfig (
createOnLogin : CrossmintCreateOnLoginConfig (
chain : 'base-sepolia' ,
recovery : CrossmintEmailSignerConfig (),
),
),
otpPromptBuilder : crossmintDefaultOtpPromptBuilder,
),
child : const HomeScreen (),
),
);
}
}
When CrossmintEmailSignerConfig() is used with createOnLogin, the email is
auto-populated from the authenticated user. curl --request POST \
--url https://staging.crossmint.com/api/2025-06-09/wallets \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_SERVER_API_KEY' \
--data '{
"type": "evm-smart-wallet",
"config": {
"adminSigner": {
"type": "email",
"email": "user@example.com"
}
}
}'
The REST API uses the legacy field name adminSigner for the recovery signer. The SDK uses recovery.
SMS OTP Recovery
The user verifies ownership of their phone number via a one-time password delivered by SMS (or optionally WhatsApp). This is ideal for mobile-first applications.
React
Node.js
React Native
Flutter
REST
Using createOnLogin on the provider: import {
CrossmintProvider ,
CrossmintAuthProvider ,
CrossmintWalletProvider ,
} from "@crossmint/client-sdk-react-ui" ;
function App ({ children }) {
return (
< CrossmintProvider apiKey = "YOUR_CLIENT_API_KEY" >
< CrossmintAuthProvider
loginMethods = { [ "email" , "google" ] }
>
< CrossmintWalletProvider
createOnLogin = { {
chain: "base-sepolia" ,
recovery: {
type: "phone" ,
phone: "+1234567890" ,
},
} }
>
{ children }
</ CrossmintWalletProvider >
</ CrossmintAuthProvider >
</ CrossmintProvider >
);
}
Or using createWallet directly: import { useWallet } from "@crossmint/client-sdk-react-ui" ;
const { createWallet } = useWallet ();
const wallet = await createWallet ({
chain: "base-sepolia" ,
recovery: {
type: "phone" ,
phone: "+1234567890" , // E.164 format required
},
});
import {
createCrossmint ,
CrossmintWallets ,
} from "@crossmint/wallets-sdk" ;
const crossmint = createCrossmint ({
apiKey: "YOUR_SERVER_API_KEY" ,
});
const crossmintWallets = CrossmintWallets . from ( crossmint );
try {
const wallet = await crossmintWallets . createWallet ({
chain: "base-sepolia" ,
owner: "phone:+12223334444" ,
recovery: {
type: "phone" ,
phone: "+12223334444" , // E.164 format required
},
});
} catch ( error ) {
console . error ( "Failed to create wallet:" , error );
}
import {
CrossmintProvider ,
CrossmintAuthProvider ,
CrossmintWalletProvider ,
} from "@crossmint/client-sdk-react-native-ui" ;
function App ({ children }) {
return (
< CrossmintProvider apiKey = "YOUR_CLIENT_API_KEY" >
< CrossmintAuthProvider
loginMethods = { [ "email" , "google" ] }
>
< CrossmintWalletProvider
createOnLogin = { {
chain: "base-sepolia" ,
recovery: {
type: "phone" ,
phone: "+1234567890" ,
},
} }
>
{ children }
</ CrossmintWalletProvider >
</ CrossmintAuthProvider >
</ CrossmintProvider >
);
}
import 'package:crossmint_flutter/crossmint_flutter_ui.dart' ;
CrossmintWalletProvider (
config : CrossmintWalletProviderConfig (
clientConfig : const CrossmintClientConfig (
apiKey : 'YOUR_CLIENT_API_KEY' ,
appScheme : 'myapp' ,
),
walletControllerConfig : const CrossmintWalletControllerConfig (
createOnLogin : CrossmintCreateOnLoginConfig (
chain : 'base-sepolia' ,
recovery : CrossmintPhoneSignerConfig (phone : 'YOUR_E164_PHONE_NUMBER' ),
),
),
otpPromptBuilder : crossmintDefaultOtpPromptBuilder,
),
child : const HomeScreen (),
)
Phone numbers must be in E.164 format (e.g. +1234567890). curl --request POST \
--url https://staging.crossmint.com/api/2025-06-09/wallets \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_SERVER_API_KEY' \
--data '{
"type": "evm-smart-wallet",
"config": {
"adminSigner": {
"type": "phone",
"phone": "+1234567890"
}
}
}'
Server Signer as Recovery
Use a server signer as the recovery signer when you want your backend to manage recovery without user-facing OTP flows. This is common for AI agents, backend automation, and hybrid architectures where the server creates wallets that clients later use.
import {
createCrossmint ,
CrossmintWallets ,
} from "@crossmint/wallets-sdk" ;
const crossmint = createCrossmint ({
apiKey: "YOUR_SERVER_API_KEY" ,
});
const crossmintWallets = CrossmintWallets . from ( crossmint );
const wallet = await crossmintWallets . createWallet ({
chain: "base-sepolia" ,
recovery: {
type: "server" ,
secret: process . env . CROSSMINT_SIGNER_SECRET ,
},
});
For more on generating and managing server signer secrets, see the Server Signer guide .
How Recovery Works on a New Device
When a user accesses their wallet from a new device where no device signer exists:
The user authenticates via your app — the SDK retrieves the wallet
The SDK detects no local device signer on this device
On the first transaction (or when recover() is called), the SDK triggers the recovery flow:
Email OTP : Crossmint sends a one-time code to the user’s email
SMS OTP : Crossmint sends a one-time code to the user’s phone
Server signer : your backend signs the recovery approval automatically
The recovery signer authorizes a new device signer for this device
All subsequent transactions on the new device are frictionless
import { useWallet } from "@crossmint/client-sdk-react-ui" ;
const { wallet } = useWallet ();
// Check if recovery is needed on this device
if ( wallet . needsRecovery ()) {
// Trigger recovery proactively (optional)
await wallet . recover ();
}
The previous device’s signer remains valid. Each device maintains its own independent device signer.
Choosing a Recovery Method
Method Best For User Friction Server Required Email OTP Consumer apps with email-based auth Medium (OTP prompt) No SMS OTP Mobile-first apps Medium (OTP prompt) No Server signer AI agents, backend automation, hybrid apps None (automatic) Yes
Next Steps
Device Signer Understand how the default client-side signer works
Server Signer Set up server-side signing with key derivation
Add Signers Add passkeys or external wallets to an existing wallet