Signers
Signers control how users authenticate and authorize transactions in Crossmint wallets. The SDK supports multiple signer types, from email/phone-based authentication to external wallet connections and passkeys.
Signer Types
Every wallet requires a signer configuration that determines how the user proves ownership and signs transactions.
// Email signer
const wallet = await crossmint . wallets . get ({
chain: "base" ,
signer: {
type: "email" ,
email: "[email protected] "
}
});
Email Signers
Email-based authentication using OTP (one-time password) verification.
Configuration
import { EmailSignerConfig } from "@crossmint/client-sdk-wallets" ;
const emailSigner : EmailSignerConfig = {
type: "email" ,
email: "[email protected] " ,
onAuthRequired : async ( needsAuth , sendOtp , verifyOtp , reject ) => {
if ( needsAuth ) {
// Send OTP to email
await sendOtp ();
// Get OTP from user (your UI logic)
const otp = await promptUserForOtp ();
// Verify the OTP
try {
await verifyOtp ( otp );
} catch ( error ) {
console . error ( "Invalid OTP" );
reject ();
}
}
}
};
const wallet = await crossmint . wallets . get ({
chain: "base" ,
signer: emailSigner
});
Type Definition
From source code (packages/wallets/src/signers/types.ts:34-43):
export type EmailSignerConfig = {
type : "email" ;
email ?: string ;
onAuthRequired ?: (
needsAuth : boolean ,
sendEmailWithOtp : () => Promise < void >,
verifyOtp : ( otp : string ) => Promise < void >,
reject : () => void
) => Promise < void >;
};
Email signers are non-custodial . The private key is encrypted and can only be decrypted by proving ownership of the email through OTP verification.
Phone Signers
Phone number-based authentication using SMS OTP verification.
Configuration
import { PhoneSignerConfig } from "@crossmint/client-sdk-wallets" ;
const phoneSigner : PhoneSignerConfig = {
type: "phone" ,
phone: "+1234567890" ,
onAuthRequired : async ( needsAuth , sendOtp , verifyOtp , reject ) => {
if ( needsAuth ) {
await sendOtp ();
const otp = await promptUserForOtp ();
await verifyOtp ( otp );
}
}
};
const wallet = await crossmint . wallets . get ({
chain: "base" ,
signer: phoneSigner
});
Type Definition
From source code (packages/wallets/src/signers/types.ts:45-54):
export type PhoneSignerConfig = {
type : "phone" ;
phone ?: string ;
onAuthRequired ?: (
needsAuth : boolean ,
sendEmailWithOtp : () => Promise < void >,
verifyOtp : ( otp : string ) => Promise < void >,
reject : () => void
) => Promise < void >;
};
External Wallet Signers
Connect existing crypto wallets (MetaMask, Phantom, etc.) to sign transactions.
EVM External Wallets
Connect EVM wallets like MetaMask, Coinbase Wallet, WalletConnect, etc.
import { EvmExternalWalletSignerConfig } from "@crossmint/client-sdk-wallets" ;
const externalSigner : EvmExternalWalletSignerConfig = {
type: "external-wallet" ,
address: "0x123..." , // Connected wallet address
onSignMessage : async ( message : string ) => {
// Sign with MetaMask or other provider
const signature = await window . ethereum . request ({
method: "personal_sign" ,
params: [ message , address ]
});
return { signature };
},
onSignTransaction : async ( transaction : string ) => {
// Sign transaction
const signature = await provider . send ( "eth_signTransaction" , [ transaction ]);
return { signature };
}
};
const wallet = await crossmint . wallets . get ({
chain: "base" ,
signer: externalSigner
});
External wallet signers use the connected wallet’s private key to sign transactions. The wallet itself remains under the user’s control.
Solana External Wallets
Connect Solana wallets like Phantom, Solflare, etc.
import { SolanaExternalWalletSignerConfig } from "@crossmint/client-sdk-wallets" ;
const solanaSigner : SolanaExternalWalletSignerConfig = {
type: "external-wallet" ,
address: "Abc123..." ,
onSignMessage : async ( message : string ) => {
const encodedMessage = new TextEncoder (). encode ( message );
const signature = await window . solana . signMessage ( encodedMessage );
return { signature: bs58 . encode ( signature . signature ) };
},
onSignTransaction : async ( transaction : string ) => {
const tx = Transaction . from ( bs58 . decode ( transaction ));
const signed = await window . solana . signTransaction ( tx );
return { signature: bs58 . encode ( signed . signature ) };
}
};
const wallet = await crossmint . wallets . get ({
chain: "solana" ,
signer: solanaSigner
});
Stellar External Wallets
Connect Stellar wallets for Stellar network transactions.
import { StellarExternalWalletSignerConfig } from "@crossmint/client-sdk-wallets" ;
const stellarSigner : StellarExternalWalletSignerConfig = {
type: "external-wallet" ,
address: "GABC123..." ,
onSignMessage : async ( message : string ) => {
// Stellar wallet signing logic
return { signature };
},
onSignTransaction : async ( transaction : string ) => {
// Sign Stellar transaction
return { signature };
}
};
const wallet = await crossmint . wallets . get ({
chain: "stellar" ,
signer: stellarSigner
});
Passkey Signers
Use WebAuthn passkeys (biometric authentication, hardware keys) for signing. Only available for EVM chains.
import { PasskeySignerConfig } from "@crossmint/client-sdk-wallets" ;
const passkeySigner : PasskeySignerConfig = {
type: "passkey" ,
name: "My Hardware Key" ,
onCreatePasskey : async ( name : string ) => {
// Create passkey using WebAuthn API
const credential = await navigator . credentials . create ({
publicKey: {
// WebAuthn options
}
});
return {
id: credential . id ,
publicKey: {
x: "..." , // P-256 public key X coordinate
y: "..." // P-256 public key Y coordinate
}
};
},
onSignWithPasskey : async ( message : string ) => {
// Sign with passkey
const assertion = await navigator . credentials . get ({
publicKey: {
challenge: new TextEncoder (). encode ( message )
}
});
return {
signature: {
r: "..." ,
s: "..."
},
metadata: {
// WebAuthn metadata
}
};
}
};
const wallet = await crossmint . wallets . get ({
chain: "base" ,
signer: passkeySigner
});
Passkeys use the P-256 elliptic curve and return a different signature format than other signers.
Type definition (packages/wallets/src/signers/types.ts:68-73):
export type PasskeySignerConfig = {
type : "passkey" ;
name ?: string ;
onCreatePasskey ?: ( name : string ) => Promise <{ id : string ; publicKey : { x : string ; y : string } }>;
onSignWithPasskey ?: ( message : string ) => Promise < PasskeySignResult >;
};
API Key Signers
Server-side only . Use API keys to sign transactions programmatically without user interaction.
import { createCrossmint } from "@crossmint/server-sdk-base" ;
const crossmint = createCrossmint ({
apiKey: "server-api-key" // Server API key with signing permissions
});
const wallet = await crossmint . wallets . get (
"0x123..." , // Wallet address
{
chain: "base" ,
signer: { type: "api-key" }
}
);
// Transactions are automatically signed
const tx = await wallet . send ( "0x456..." , "usdc" , "10" );
API key signers bypass user authentication and automatically approve all transactions. Use with caution and only in trusted server environments.
Delegated Signers
Add additional signers to a wallet for multi-signature functionality. Delegated signers can authorize transactions alongside the primary signer.
Adding Delegated Signers
// Add an external wallet as delegated signer
await wallet . addDelegatedSigner ({
signer: "0xdelegated..."
});
// For EVM chains, you can also add a passkey
await wallet . addDelegatedSigner ({
signer: {
name: "Backup Key" ,
publicKey: {
x: "..." ,
y: "..."
}
}
});
// List all delegated signers
const signers = await wallet . delegatedSigners ();
console . log ( signers ); // [{ signer: "external-wallet:0xdelegated..." }]
Using Delegated Signers
import { EVMExternalWalletSigner } from "@crossmint/client-sdk-wallets" ;
// Create signer instance
const delegatedSigner = new EVMExternalWalletSigner ({
type: "external-wallet" ,
address: "0xdelegated..." ,
locator: "external-wallet:0xdelegated..." ,
onSignMessage : async ( message ) => {
// Sign with delegated wallet
return { signature };
},
onSignTransaction : async ( tx ) => {
return { signature };
}
});
// Use additional signer for transaction approval
await wallet . approve ({
transactionId: "tx_123..." ,
options: {
additionalSigners: [ delegatedSigner ]
}
});
Delegated signers enable multi-sig wallets, social recovery, and shared wallet access patterns.
Signer Interface
All signers implement the Signer interface:
// From packages/wallets/src/signers/types.ts:142-148
export interface Signer < T extends keyof SignResultMap = keyof SignResultMap > {
type : T ;
locator () : string ;
address ? () : string ;
signMessage ( message : string ) : Promise < SignResultMap [ T ]>;
signTransaction ( transaction : string ) : Promise < SignResultMap [ T ]>;
}
Exportable Signers
Some signers (email, phone) support private key export:
import { isExportableSigner } from "@crossmint/client-sdk-wallets" ;
if ( isExportableSigner ( wallet . signer )) {
// This signer can export its private key
await wallet . signer . _exportPrivateKey ( exportConnection );
}
External wallet signers and API key signers are not exportable since they don’t manage private keys directly.
Signer Locators
Each signer has a unique locator identifier:
Email: email:[email protected]
Phone: phone:+1234567890
External wallet: external-wallet:0x123...
Passkey: passkey:credential_id
API key: api-key:wallet_address
const locator = wallet . signer . locator ();
console . log ( locator ); // "email:[email protected] "
Best Practices
Use Email/Phone for Onboarding Email and phone signers provide the best user experience for Web3 newcomers.
Add Delegated Signers for Recovery Configure backup signers to prevent wallet lockout if primary signer is lost.
Use Passkeys for Security Passkeys provide hardware-backed security with biometric authentication.
API Keys Only on Server Never expose API key signers in client-side code.
Error Handling
import { AuthRejectedError } from "@crossmint/client-sdk-wallets" ;
try {
const wallet = await crossmint . wallets . get ({
chain: "base" ,
signer: {
type: "email" ,
email: "[email protected] " ,
onAuthRequired : async ( needsAuth , sendOtp , verifyOtp , reject ) => {
// User cancelled authentication
reject ();
}
}
});
} catch ( error ) {
if ( error instanceof AuthRejectedError ) {
console . log ( "User cancelled authentication" );
}
}
Next Steps
Authentication Learn about JWT sessions and OAuth flows
Create Wallets Start creating wallets with different signer types