Get up and running with MoonKey in minutes. This guide walks you through authenticating users, creating embedded wallets, and executing blockchain transactions.
Prerequisites
Before starting, make sure you’ve completed the Setup guide and have the MoonKeyProvider configured in your application.
Step 1: Authenticate users with email
MoonKey makes authentication simple with email-based one-time passwords (OTPs). While this guide focuses on email authentication, MoonKey supports multiple methods including OAuth providers, wallet signatures, and WebAuthn.
Want to explore other authentication options? Check out our Authentication methods documentation to learn about social logins, passkeys, and Web3 wallet authentication.
Using the email authentication hook
The useLoginWithEmail hook provides everything you need to implement email OTP authentication:
import { useLoginWithEmail } from '@moon-key/react-auth';
function LoginComponent() {
const { sendCode, loginWithCode } = useLoginWithEmail();
// Use these methods to authenticate your user
}
Make sure your component is wrapped by MoonKeyProvider to use MoonKey hooks. See the Setup guide for details.
Building a complete login flow
Here’s a complete example showing how to collect an email, send an OTP, and authenticate the user:
import { useState } from 'react';
import { useLoginWithEmail } from '@moon-key/react-auth';
export default function EmailLogin() {
const [email, setEmail] = useState('');
const [otp, setOtp] = useState('');
const [codeSent, setCodeSent] = useState(false);
const { sendCode, loginWithCode, state } = useLoginWithEmail();
const handleSendCode = async () => {
try {
await sendCode({ email });
setCodeSent(true);
} catch (error) {
console.error('Failed to send code:', error);
}
};
const handleLogin = async () => {
try {
await loginWithCode({ code: otp });
// User is now authenticated!
} catch (error) {
console.error('Failed to login:', error);
}
};
if (!codeSent) {
return (
<div>
<h2>Log in with Email</h2>
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button onClick={handleSendCode} disabled={!email || state === 'sending'}>
{state === 'sending' ? 'Sending...' : 'Send Code'}
</button>
</div>
);
}
return (
<div>
<h2>Enter your code</h2>
<p>We sent a 6-digit code to {email}</p>
<input
type="text"
placeholder="000000"
value={otp}
onChange={(e) => setOtp(e.target.value)}
maxLength={6}
/>
<button onClick={handleLogin} disabled={otp.length !== 6 || state === 'logging-in'}>
{state === 'logging-in' ? 'Logging in...' : 'Verify & Login'}
</button>
<button onClick={() => setCodeSent(false)}>Change email</button>
</div>
);
}
The OTP is valid for 10 minutes. If it expires, users can request a new code by re-entering their email.
Step 2: Create embedded wallets
MoonKey can automatically create self-custodial wallets for your users during the authentication flow. Configure this in your MoonKeyProvider:
<MoonKeyProvider
publishableKey="your_publishable_key"
config={{
embeddedWallets: {
createOnLogin: 'always' // Creates a wallet for every authenticated user
}
}}
>
{children}
</MoonKeyProvider>
Wallet creation options:
'always' — Automatically create a wallet for every user upon login
'none' — Disable automatic wallet creation (create manually when needed)
MoonKey supports embedded wallet creation on both Ethereum (and all EVM chains) and Solana. Choose your blockchain when importing the provider.
Manual wallet creation
Alternatively, you can create wallets on-demand when users need them:
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
function CreateWalletButton() {
const { createWallet } = useCreateWallet();
const handleCreateWallet = async () => {
const wallet = await createWallet();
console.log('Wallet created:', wallet.address);
};
return <button onClick={handleCreateWallet}>Create Wallet</button>;
}
Step 3: Send transactions
Once users have embedded wallets, your app can prompt them to sign and send blockchain transactions.
Use the useSendTransaction hook to send transactions on Ethereum and EVM-compatible chains:import { useSendTransaction } from '@moon-key/react-auth/ethereum';
export default function SendEthButton() {
const { sendTransaction } = useSendTransaction();
const handleSend = async () => {
try {
const txHash = await sendTransaction({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
value: '1000000000000000', // 0.001 ETH in wei
});
console.log('Transaction sent:', txHash);
} catch (error) {
console.error('Transaction failed:', error);
}
};
return <button onClick={handleSend}>Send 0.001 ETH</button>;
}
Accessing user data
After authentication, access user information with the useMoonKey hook:
import { useMoonKey } from '@moon-key/react-auth';
function UserProfile() {
const { user, isAuthenticated } = useMoonKey();
if (!isAuthenticated) {
return <p>Please log in</p>;
}
return (
<div>
<h2>Welcome!</h2>
<p>Email: {user?.email?.address}</p>
<p>User ID: {user?.id}</p>
<p>Wallet: {user?.wallet?.address}</p>
</div>
);
}
Handling logout
Allow users to log out and clear their session:
import { useLogout } from '@moon-key/react-auth';
function LogoutButton() {
const { logout } = useLogout();
return <button onClick={logout}>Log Out</button>;
}
Troubleshooting
”User is not authenticated” error
Make sure you’re checking the isAuthenticated state before attempting wallet operations:
const { isAuthenticated } = useMoonKey();
if (!isAuthenticated) {
return <p>Please log in first</p>;
}
Transactions failing
Common reasons transactions fail:
- Insufficient balance in the wallet
- Invalid recipient address
- Network congestion or RPC issues
- User rejected the transaction
Always wrap transaction calls in try-catch blocks to handle errors gracefully.
Hook errors
If you see “Hook can only be used inside MoonKeyProvider”, ensure:
- Your component is wrapped by
MoonKeyProvider
- You’re importing from the correct package (
@moon-key/react-auth/ethereum or @moon-key/react-auth/solana)
- The provider is mounted before components using hooks