Skip to main content
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.
  • Ethereum
  • Solana
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:
  1. Your component is wrapped by MoonKeyProvider
  2. You’re importing from the correct package (@moon-key/react-auth/ethereum or @moon-key/react-auth/solana)
  3. The provider is mounted before components using hooks