Skip to main content
MoonKey enables you to create embedded wallets for your users programmatically. These self-custodial wallets are owned by the authenticated user and secured using distributed key sharding. You can create wallets either automatically when users authenticate, or manually when needed using the SDK.
MoonKey supports automatically creating embedded wallets for your users when they log in to your app. View the automatic wallet creation guide to learn more.

React SDK

  • Ethereum
  • Solana
To create an Ethereum wallet with the React SDK, use the createWallet method from the useCreateWallet hook:

Usage

import { useCreateWallet } from '@moon-key/react-auth/ethereum';

function CreateWalletButton() {
  const { createWallet } = useCreateWallet();
  
  const handleCreate = async () => {
    try {
      const wallet = await createWallet();
      console.log('Wallet created:', wallet.address);
    } catch (error) {
      console.error('Failed to create wallet:', error);
    }
  };
  
  return <button onClick={handleCreate}>Create Wallet</button>;
}

Returns

wallet
Promise<Wallet>
A Promise for the wallet object containing the created wallet’s details.

Callbacks

You can optionally register onSuccess or onError callbacks on the useCreateWallet hook:
import { useCreateWallet } from '@moon-key/react-auth/ethereum';

const { createWallet } = useCreateWallet({
  onSuccess: (wallet) => {
    console.log('Wallet created successfully:', wallet.address);
    // Navigate to wallet setup or show success message
  },
  onError: (error) => {
    console.error('Failed to create wallet:', error);
    // Show error message to user
  }
});
onSuccess
(wallet: Wallet) => void
Optional callback to run after a user successfully creates a wallet.
onError
(error: Error) => void
Optional callback to run if there is an error during wallet creation.

Complete example

'use client';
import { useCreateWallet, useMoonKey } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';

export default function WalletCreation() {
  const { user, isAuthenticated } = useMoonKey();
  const [isCreating, setIsCreating] = useState(false);
  
  const { createWallet } = useCreateWallet({
    onSuccess: (wallet) => {
      console.log('Wallet created:', wallet.address);
      alert(`Wallet created: ${wallet.address}`);
    },
    onError: (error) => {
      console.error('Error:', error);
      alert('Failed to create wallet');
    }
  });
  
  if (!isAuthenticated) {
    return <div>Please sign in first</div>;
  }
  
  if (user?.wallet) {
    return (
      <div>
        <p>You already have a wallet:</p>
        <p>{user.wallet.address}</p>
      </div>
    );
  }
  
  const handleCreate = async () => {
    setIsCreating(true);
    try {
      await createWallet();
    } finally {
      setIsCreating(false);
    }
  };
  
  return (
    <div>
      <h2>Create Your Wallet</h2>
      <p>Create a self-custodial Ethereum wallet</p>
      <button onClick={handleCreate} disabled={isCreating}>
        {isCreating ? 'Creating...' : 'Create Wallet'}
      </button>
    </div>
  );
}

REST API

To create a new wallet for a user via the REST API, make a POST request to:
POST https://api.moonkey.fun/v1/wallets/create
You must specify the user ID as the owner of the wallet. You can obtain a user ID by first creating a user before creating the wallet.

Authentication

Include your Secret API Key in the Authorization header:
Authorization: Bearer sk_test_YOUR_SECRET_KEY

Request body

wallet_type
'ethereum' | 'solana'
required
The type of wallet to create.
  • 'ethereum' - Create an Ethereum/EVM wallet
  • 'solana' - Create a Solana/SVM wallet
user_id
string
required
The ID of the user who will own this wallet (e.g., user_2Cu2uVhYy0OVgRcO913OsqIVaPI).

Response

id
string
Unique ID of the created wallet. This is the primary identifier for the wallet.
app_id
string
The ID of your MoonKey application.
user_id
string
The ID of the user who owns this wallet.
public_address
string
The wallet’s public address (Ethereum address or Solana public key).
wallet_type
'ethereum' | 'solana'
The type of wallet created.
verified
boolean
Whether the wallet has been verified. Always true for newly created wallets.
is_default
boolean
Whether this is the user’s default wallet for this chain type.
created_at
number
Unix timestamp (in seconds) when the wallet was created.
updated_at
number
Unix timestamp (in seconds) when the wallet was last updated.

Example request

curl --request POST \
  --url https://api.moonkey.fun/v1/wallets/create \
  --header 'Authorization: Bearer sk_test_YOUR_SECRET_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "wallet_type": "ethereum",
    "user_id": "user_2Cu2uVhYy0OVgRcO913OsqIVaPI"
  }'

Example response

{
  "id": "wallet_2Cu2uYcbwY9kcAFe2zd0P0SHftK",
  "app_id": "app_24ydphdixx2ydhF0E5WUFUKWNqi",
  "user_id": "user_2Cu2uVhYy0OVgRcO913OsqIVaPI",
  "public_address": "0xf1347fd847f19c250b4c9678ecaa27b0f6ce8804",
  "wallet_type": "ethereum",
  "verified": true,
  "is_default": true,
  "is_read_only": false,
  "is_imported": false,
  "created_at": 1659638371,
  "updated_at": 1659638371
}

Automatic vs manual wallet creation

MoonKey provides two approaches to wallet creation:

Automatic creation

Configure MoonKey to automatically create wallets when users authenticate:
import { MoonKeyProvider } from '@moon-key/react-auth';

<MoonKeyProvider
  publishableKey="your_publishable_key"
  config={{
    embeddedWallets: {
      createOnLogin: 'always' // Create wallet automatically for all users
    }
  }}
>
  {children}
</MoonKeyProvider>
Best for:
  • Apps where all users need wallets
  • Gaming applications
  • NFT marketplaces
  • DeFi applications
  • Simplest onboarding experience
Learn more about automatic wallet creation.

Manual creation

Use the useCreateWallet hook to create wallets programmatically:
const { createWallet } = useCreateWallet();
await createWallet();
Best for:
  • Apps with optional blockchain features
  • Progressive onboarding flows
  • Users who opt-in to web3 features
  • When you want to delay wallet creation for UX reasons

Checking if a user has a wallet

Before creating a wallet, check if the user already has one:
import { useMoonKey } from '@moon-key/react-auth';

function MyComponent() {
  const { user } = useMoonKey();
  
  if (user?.wallet) {
    // User already has a wallet
    console.log('Wallet address:', user.wallet.address);
  } else {
    // User doesn't have a wallet yet
    // Show create wallet button
  }
}

Common use cases

Onboarding flow with wallet creation

'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useCreateWallet } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';

export default function OnboardingFlow() {
  const { user, isAuthenticated } = useMoonKey();
  const { createWallet } = useCreateWallet();
  const [step, setStep] = useState<'welcome' | 'creating' | 'complete'>('welcome');
  
  if (!isAuthenticated) {
    return <div>Please sign in to continue</div>;
  }
  
  if (user?.wallet) {
    return <div>You already have a wallet! {user.wallet.address}</div>;
  }
  
  const handleCreateWallet = async () => {
    setStep('creating');
    try {
      await createWallet();
      setStep('complete');
    } catch (error) {
      console.error('Failed to create wallet:', error);
      setStep('welcome');
    }
  };
  
  return (
    <div>
      {step === 'welcome' && (
        <div>
          <h2>Welcome to Our App!</h2>
          <p>Create your self-custodial wallet to get started</p>
          <button onClick={handleCreateWallet}>Create Wallet</button>
        </div>
      )}
      
      {step === 'creating' && (
        <div>
          <h2>Creating Your Wallet...</h2>
          <p>This will only take a moment</p>
        </div>
      )}
      
      {step === 'complete' && (
        <div>
          <h2>Wallet Created!</h2>
          <p>Your wallet address: {user?.wallet?.address}</p>
          <button onClick={() => window.location.href = '/dashboard'}>
            Continue to Dashboard
          </button>
        </div>
      )}
    </div>
  );
}

Optional wallet creation

import { useMoonKey } from '@moon-key/react-auth';
import { useCreateWallet } from '@moon-key/react-auth/ethereum';

export default function OptionalWalletFeature() {
  const { user } = useMoonKey();
  const { createWallet } = useCreateWallet();
  
  if (user?.wallet) {
    return <BlockchainFeatures />;
  }
  
  return (
    <div>
      <h2>Unlock Blockchain Features</h2>
      <p>Create a wallet to access NFTs, tokens, and more</p>
      <button onClick={() => createWallet()}>
        Create Wallet
      </button>
      <button onClick={() => window.history.back()}>
        Maybe Later
      </button>
    </div>
  );
}

Server-side wallet creation

// On your backend
async function createWalletForUser(userId: string) {
  const response = await fetch('https://api.moonkey.fun/v1/wallets/create', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.MOONKEY_SECRET_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      wallet_type: 'ethereum',
      user_id: userId
    })
  });
  
  if (!response.ok) {
    throw new Error('Failed to create wallet');
  }
  
  const wallet = await response.json();
  return wallet;
}

// Example usage in an API endpoint
app.post('/api/setup-wallet', async (req, res) => {
  const { userId } = req.body;
  
  try {
    const wallet = await createWalletForUser(userId);
    res.json({ success: true, wallet });
  } catch (error) {
    res.status(500).json({ error: 'Failed to create wallet' });
  }
});

Best practices

Always check if a user already has a wallet before attempting to create one:
if (user?.wallet) {
  console.log('User already has a wallet');
  return;
}
await createWallet();
Provide clear feedback when wallet creation fails:
try {
  await createWallet();
  toast.success('Wallet created successfully!');
} catch (error) {
  toast.error('Failed to create wallet. Please try again.');
  console.error('Wallet creation error:', error);
}
Display loading indicators during wallet creation:
const [isCreating, setIsCreating] = useState(false);

const handleCreate = async () => {
  setIsCreating(true);
  try {
    await createWallet();
  } finally {
    setIsCreating(false);
  }
};
Explain what a wallet is and why they’re creating one:
<div className="wallet-info">
  <h3>What is a wallet?</h3>
  <p>A wallet lets you own digital assets like NFTs and tokens.</p>
  <p>Your wallet is self-custodial - only you can access it.</p>
</div>
Handle navigation or UI updates after successful wallet creation:
const { createWallet } = useCreateWallet({
  onSuccess: (wallet) => {
    // Log analytics
    analytics.track('wallet_created', { address: wallet.address });
    
    // Navigate to next step
    router.push('/wallet/funded');
  }
});
Never expose your Secret API Key in client-side code:
// ❌ WRONG - Never do this
const apiKey = 'sk_live_...';

// ✅ CORRECT - Use environment variables on server
const apiKey = process.env.MOONKEY_SECRET_KEY;

Error handling

Common errors and how to handle them:
try {
  await createWallet();
} catch (error) {
  if (error.message.includes('already has a wallet')) {
    toast.info('You already have a wallet');
  } else if (error.message.includes('not authenticated')) {
    toast.error('Please sign in first');
  } else if (error.message.includes('network')) {
    toast.error('Network error. Please check your connection.');
  } else {
    toast.error('Failed to create wallet. Please try again.');
  }
}

Next steps