This guide covers the core concepts of the LazorKit Mobile SDK and how it differs from the web SDK.
npm install @lazorkit/wallet-mobile-adapterThe mobile SDK provides the same core functionality as web:
- Passkey-based wallet creation
- Gasless transaction sending via paymaster
- Smart wallet management
But the authentication flow is different due to mobile platform constraints.
Wrap your app with LazorKitProvider:
import { LazorKitProvider } from '@lazorkit/wallet-mobile-adapter';
export default function RootLayout() {
return (
<LazorKitProvider
rpcUrl="https://api.devnet.solana.com"
portalUrl="https://portal.lazor.sh"
configPaymaster={{
paymasterUrl: "https://kora.devnet.lazorkit.com"
}}
>
{/* Your app */}
</LazorKitProvider>
);
}The core hook provides:
import { useWallet } from '@lazorkit/wallet-mobile-adapter';
const {
wallet, // { smartWallet: string } | null
isConnected, // boolean
connect, // (options) => Promise
disconnect, // (options) => Promise
signAndSendTransaction, // (payload, options) => Promise<signature>
signMessage, // (message, options) => Promise
} = useWallet();// Web - simple, no redirect needed
await connect();
await signAndSendTransaction({ instructions });import * as Linking from 'expo-linking';
// Mobile - requires redirectUrl for every operation
await connect({
redirectUrl: Linking.createURL('your/return/path'),
onSuccess: (wallet) => console.log('Connected:', wallet.smartWallet),
onFail: (error) => console.error('Failed:', error.message),
});
await signAndSendTransaction(
{ instructions },
{ redirectUrl: Linking.createURL('your/return/path') }
);On mobile, passkey authentication cannot happen in an embedded webview. The flow is:
- Your app calls
connect()→ Opens external browser - User authenticates → Face ID/Touch ID in LazorKit portal
- Browser redirects back → Your app receives the wallet data via deep link
This is why every operation needs a redirectUrl - so the browser knows where to return.
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Your App │────▶│ External │────▶│ LazorKit │
│ │ │ Browser │ │ Portal │
└──────────────┘ └─────────────────┘ └──────────────┘
▲ │
│ Deep Link Redirect │
└────────────────────────────────────────────┘
(with wallet data)
await connect({
// Required: Where to return after authentication
redirectUrl: Linking.createURL('examples/01-connect-wallet'),
// Callbacks
onSuccess: (wallet) => {
// wallet.smartWallet contains the address
console.log('Connected:', wallet.smartWallet);
},
onFail: (error) => {
// error.message contains the reason
Alert.alert('Failed', error.message);
},
});const signature = await signAndSendTransaction(
{
// The transaction payload
instructions: [...],
transactionOptions: {
computeUnitLimit: 200_000, // Optional: default is handled by paymaster
},
},
{
// Required: Where to return after signing
redirectUrl: Linking.createURL('examples/02-transfer'),
}
);await disconnect({
onSuccess: () => {
console.log('Disconnected');
},
onFail: (error) => {
Alert.alert('Error', error.message);
},
});For deep links to work, configure your app's URL scheme in app.json:
{
"expo": {
"scheme": "lazorkitcookbook",
"name": "LazorKit Cookbook"
}
}Then use Linking.createURL() to generate redirect URLs:
import * as Linking from 'expo-linking';
// Creates: lazorkitcookbook://examples/01-connect-wallet
const redirectUrl = Linking.createURL('examples/01-connect-wallet');| Feature | Web SDK | Mobile SDK |
|---|---|---|
| Package | @lazorkit/wallet |
@lazorkit/wallet-mobile-adapter |
| Auth method | Popup window | External browser + deep link |
connect() |
connect() |
connect({ redirectUrl, onSuccess, onFail }) |
signAndSendTransaction() |
signAndSendTransaction(payload) |
signAndSendTransaction(payload, { redirectUrl }) |
| User flow | Stays in app | Leaves app, returns via deep link |
| State persistence | Automatic | May need local state sync |
When returning from authentication, your component may re-mount. Sync SDK state to local state:
const [localWallet, setLocalWallet] = useState(null);
useEffect(() => {
if (sdkWallet?.smartWallet) {
setLocalWallet(sdkWallet);
}
}, [sdkWallet?.smartWallet]);Use specific paths so the user returns to the right screen:
// Good - returns to the specific screen
connect('examples/02-gasless-transfer');
// Avoid - might confuse navigation state
connect('');Deep link operations take time. Always show feedback:
const [connecting, setConnecting] = useState(false);
const handleConnect = async () => {
setConnecting(true);
await connect({
redirectUrl: Linking.createURL('home'),
onSuccess: () => setConnecting(false),
onFail: () => setConnecting(false),
});
};
return (
<Button
title={connecting ? 'Connecting...' : 'Connect'}
disabled={connecting}
onPress={handleConnect}
/>
);- Cookbook Patterns - WalletContext wrapper for simplified API
- Connect Wallet Example - Complete implementation
- Gasless Transfer Example - Send USDC without gas
- Raydium Swap Example - External SDK integration