With funding methods enabled for your app, MoonKey will prompt users to fund their wallets at two points in their experience:
Manually - When you call MoonKey’s fundWallet method
Automatically - When the user attempts to send a transaction but has insufficient funds
You can configure the cluster, asset, and amount that users should fund their wallets with directly in code.
Manually invoking funding
Once you’ve enabled funding methods for your app in the Dashboard, use the useFundWallet hook to invoke the funding flow:
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
function FundWalletButton () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
if ( wallets . length === 0 ) return ;
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
cluster: 'solana:mainnet' ,
amount: '0.1'
});
};
return < button onClick = { handleFund } > Add Funds </ button > ;
}
Once invoked, the fundWallet method will open a modal with funding options for the user. If only one funding method is enabled, MoonKey will navigate directly to that flow.
Purchases with third-party providers (like MoonPay) are not always instantaneous. There may be some time before the user completes their purchase and the funds are available in their wallet.
Automatically invoking funding
When a user attempts to send a transaction but doesn’t have sufficient funds, MoonKey will automatically show them an “Add funds” button in the transaction modal that enables them to invoke funding flows.
This helps users complete their intended actions without leaving your application.
Setting funding parameters in code
You can override your Dashboard configuration by passing parameters to fundWallet:
Basic parameters
The wallet address to fund.
The Solana cluster to fund on. Use 'solana:mainnet' for mainnet, 'solana:devnet' for devnet. If not specified, defaults to the cluster configured in your Dashboard.
The amount of the asset to fund as a decimal string (e.g., '0.1'). If not specified, defaults to the amount configured in your Dashboard.
config.asset
'native-currency' | 'USDC'
The asset to fund with:
'native-currency' - SOL (Solana’s native currency)
'USDC' - USDC stablecoin
Defaults to 'native-currency'.
Only use 'solana:mainnet' for production. Testnets are not supported for card funding.
Examples
Fund with SOL
Fund with the native currency (SOL):
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
function FundWithSOL () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
cluster: 'solana:mainnet' ,
amount: '0.1' // Defaults to 'native-currency' (SOL)
});
};
return < button onClick = { handleFund } > Fund with 0.1 SOL </ button > ;
}
Fund with USDC
Fund with USDC stablecoin:
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
function FundWithUSDC () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
cluster: 'solana:mainnet' ,
amount: '15' ,
asset: 'USDC'
});
};
return < button onClick = { handleFund } > Fund with 15 USDC </ button > ;
}
Fund with custom amounts
Allow users to choose their funding amount:
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
import { useState } from 'react' ;
function CustomAmountFunding () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const [ amount , setAmount ] = useState ( '0.5' );
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
cluster: 'solana:mainnet' ,
amount
});
};
return (
< div >
< input
type = "text"
value = { amount }
onChange = { ( e ) => setAmount ( e . target . value ) }
placeholder = "Amount in SOL"
/>
< button onClick = { handleFund } > Fund with { amount } SOL </ button >
</ div >
);
}
Predefined funding options
Offer preset funding amounts:
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
function PresetAmountButtons () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const fundWithAmount = async ( amount : string ) => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
cluster: 'solana:mainnet' ,
amount
});
};
return (
< div >
< h3 > Quick Fund Options </ h3 >
< button onClick = { () => fundWithAmount ( '0.1' ) } >
0.1 SOL (~$15)
</ button >
< button onClick = { () => fundWithAmount ( '0.5' ) } >
0.5 SOL (~$75)
</ button >
< button onClick = { () => fundWithAmount ( '1.0' ) } >
1.0 SOL (~$150)
</ button >
</ div >
);
}
Callbacks
To understand when users have gone through a funding flow, use the onUserExited callback with the useFundWallet hook:
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
import { useRouter } from 'next/navigation' ;
function OnboardingWithFunding () {
const router = useRouter ();
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ({
onUserExited ({ balance }) {
if ( balance < 1000 n ) {
router . push ( '/insufficient-funds' );
} else {
router . push ( '/dashboard' );
}
}
});
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
cluster: 'solana:mainnet' ,
amount: '0.5'
});
};
return < button onClick = { handleFund } > Fund Wallet </ button > ;
}
Callback parameters
The onUserExited callback receives an object with:
The wallet address that was funded.
The Solana cluster where funding occurred.
The funding method used (e.g., 'card').
The current balance of the wallet being funded (in lamports).
Onboarding flow example
Prompt new users to fund their wallet as part of onboarding:
import { useMoonKey } from '@moon-key/react-auth' ;
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
import { useLogin } from '@moon-key/react-auth' ;
import { useRouter } from 'next/navigation' ;
export default function LoginWithFunding () {
const router = useRouter ();
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ({
onUserExited ({ balance }) {
// Check if balance is sufficient (in lamports)
const minBalance = 100_000_000 n ; // 0.1 SOL
if ( balance < minBalance ) {
router . push ( '/insufficient-funds' );
} else {
router . push ( '/dashboard' );
}
}
});
const { login } = useLogin ({
onComplete ( user , isNewUser ) {
if ( isNewUser && wallets . length > 0 ) {
// Prompt new users to fund their wallet
fundWallet ( wallets [ 0 ]. address , {
cluster: 'solana:mainnet' ,
amount: '0.5'
});
} else {
router . push ( '/dashboard' );
}
}
});
return < button onClick = { login } > Sign In </ button > ;
}
Customizing the UI
Customize the “Receive funds” screen with the uiConfig option:
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
function CustomizedFunding () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
cluster: 'solana:mainnet' ,
amount: '0.5' ,
uiConfig: {
receiveFundsTitle: 'Receive 0.5 SOL' ,
receiveFundsSubtitle: 'Scan this code or copy your wallet address to receive funds on Solana.'
}
});
};
return < button onClick = { handleFund } > Add Funds </ button > ;
}
UI configuration options
uiConfig.receiveFundsTitle
Custom title for the “Receive funds” screen.
uiConfig.receiveFundsSubtitle
Custom subtitle for the “Receive funds” screen.
Complete example
Here’s a complete example with amount and asset selection:
'use client' ;
import { useWallets , useFundWallet } from '@moon-key/react-auth/solana' ;
import { useState } from 'react' ;
import { LAMPORTS_PER_SOL } from '@solana/web3.js' ;
export default function FlexibleFunding () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ({
onUserExited ({ balance , fundingMethod }) {
console . log ( 'Funding complete' );
console . log ( 'Method:' , fundingMethod );
console . log ( 'New balance:' , ( Number ( balance ) / LAMPORTS_PER_SOL ). toFixed ( 4 ), 'SOL' );
}
});
const [ amount , setAmount ] = useState ( '0.5' );
const [ asset , setAsset ] = useState < 'native-currency' | 'USDC' >( 'native-currency' );
const [ isFunding , setIsFunding ] = useState ( false );
const handleFund = async () => {
if ( wallets . length === 0 ) return ;
const selectedWallet = wallets [ 0 ];
setIsFunding ( true );
try {
await fundWallet ( selectedWallet . address , {
cluster: 'solana:mainnet' ,
amount ,
asset
});
} catch ( error ) {
console . error ( 'Funding failed:' , error );
} finally {
setIsFunding ( false );
}
};
if ( wallets . length === 0 ) {
return < div > No wallet found </ div > ;
}
return (
< div className = "funding-form" >
< h2 > Fund Your Wallet </ h2 >
< div className = "form-group" >
< label > Asset </ label >
< select value = { asset } onChange = { ( e ) => setAsset ( e . target . value as any ) } >
< option value = "native-currency" > SOL </ option >
< option value = "USDC" > USDC </ option >
</ select >
</ div >
< div className = "form-group" >
< label > Amount </ label >
< input
type = "text"
value = { amount }
onChange = { ( e ) => setAmount ( e . target . value ) }
placeholder = "0.5"
/>
</ div >
< button onClick = { handleFund } disabled = { isFunding } >
{ isFunding ? 'Processing...' : `Fund with ${ amount } ${ asset === 'native-currency' ? 'SOL' : 'USDC' } ` }
</ button >
< p className = "wallet-address" >
Wallet: { wallets [ 0 ]. address }
</ p >
< div className = "info" >
< p > 💳 Pay with card, Apple Pay, or Google Pay </ p >
< p > ⚡ Funds typically arrive within minutes </ p >
< p > 🔒 Secure payment processing via MoonPay </ p >
</ div >
</ div >
);
}
Understanding Solana balances
Solana balances are measured in lamports , where 1 SOL = 1,000,000,000 lamports:
import { LAMPORTS_PER_SOL } from '@solana/web3.js' ;
// Convert lamports to SOL
const solBalance = Number ( balanceInLamports ) / LAMPORTS_PER_SOL ;
// Convert SOL to lamports
const lamports = solAmount * LAMPORTS_PER_SOL ;
When checking balances in the onUserExited callback, remember that the balance is in lamports:
const { fundWallet } = useFundWallet ({
onUserExited ({ balance }) {
// balance is in lamports
const solBalance = Number ( balance ) / LAMPORTS_PER_SOL ;
console . log ( `Balance: ${ solBalance . toFixed ( 4 ) } SOL` );
// Check minimum balance (0.1 SOL = 100,000,000 lamports)
if ( balance < 100_000_000 n ) {
alert ( 'Please add more funds' );
}
}
});
Best practices
Set appropriate amounts for Solana
Solana has very low transaction fees (typically < $0.01)
Smaller amounts work well (0.1 - 1 SOL is often sufficient)
Consider typical transaction costs in your app
Don’t require users to fund more than necessary
Communicate funding status
Explain that purchases are not instant
Show expected delivery times (usually faster than Ethereum)
Provide transaction status updates
Handle edge cases gracefully
Always use 'solana:mainnet' for production
Test your integration thoroughly on mainnet with small amounts
Devnet is not supported for card funding
Consider USDC for price stability
USDC provides price stability for users
Useful for applications with fixed pricing
Still allows for Solana transactions
May be preferred by users concerned about volatility
Use callbacks effectively
Track funding completion with onUserExited
Navigate users appropriately based on balance
Log funding events for analytics
Handle insufficient balance scenarios
Solana accounts require minimum balance for rent exemption
Ensure funded amounts are sufficient for account creation
Consider rent costs when setting minimum balances
Guide users if their balance is below rent exemption
Common issues
Balance appears as 0 after funding
Possible causes:
Purchase not yet complete
MoonPay processing payment
Network congestion
Solutions:
Wait a few minutes for processing
Check MoonPay transaction status
Refresh wallet balance after delay
Funding modal doesn't appear
Possible causes:
Funding not enabled in Dashboard
Incorrect cluster configuration
Browser blocking popup
Solutions:
Verify Dashboard configuration
Check console for errors
Ensure popups are allowed
Insufficient funds for transactions
Possible causes:
Funded amount too small
Account rent not covered
Multiple transactions depleting balance
Solutions:
Suggest higher funding amounts
Explain Solana rent requirements
Provide balance warnings before transactions
Next steps