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 chain, 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/ethereum' ;
function FundWalletButton () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
if ( wallets . length === 0 ) return ;
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
chain: 'eip155:1' ,
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 Ethereum chain to fund on. Use 'eip155:1' for Ethereum mainnet, 'eip155:8453' for Base, etc. If not specified, defaults to the chain 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' | {erc20: string}
The asset to fund with:
'native-currency' - The chain’s native currency (ETH, etc.)
'USDC' - USDC stablecoin
{erc20: '0x...'} - Any ERC-20 token by contract address
Defaults to 'native-currency'.
Testnets are not supported. Only use mainnet chain IDs.
Examples
Fund with ETH
Fund with the native currency (ETH):
import { useWallets , useFundWallet } from '@moon-key/react-auth/ethereum' ;
function FundWithETH () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
chain: 'eip155:8453' , // Base
amount: '0.01' // Defaults to 'native-currency' (ETH)
});
};
return < button onClick = { handleFund } > Fund with 0.01 ETH </ button > ;
}
Fund with USDC
Fund with USDC stablecoin:
import { useWallets , useFundWallet } from '@moon-key/react-auth/ethereum' ;
function FundWithUSDC () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
chain: 'eip155:8453' , // Base
amount: '15' ,
asset: 'USDC'
});
};
return < button onClick = { handleFund } > Fund with 15 USDC </ button > ;
}
Fund with arbitrary ERC-20
Fund with any ERC-20 token:
import { useWallets , useFundWallet } from '@moon-key/react-auth/ethereum' ;
function FundWithCustomToken () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
chain: 'eip155:8453' , // Base
amount: '30' ,
asset: {
erc20: '0x0578d8A44db98B23BF096A382e016e29a5Ce0ffe' // Custom token
}
});
};
return < button onClick = { handleFund } > Fund with Custom Token </ button > ;
}
Fund on different networks
Fund on various EVM networks:
import { useWallets , useFundWallet } from '@moon-key/react-auth/ethereum' ;
function NetworkSelector () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const fundOnNetwork = async ( chainId : string , chainName : string ) => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
chain: chainId ,
amount: '0.05'
});
};
return (
< div >
< button onClick = { () => fundOnNetwork ( 'eip155:1' , 'Ethereum' ) } >
Fund on Ethereum
</ button >
< button onClick = { () => fundOnNetwork ( 'eip155:8453' , 'Base' ) } >
Fund on Base
</ button >
< button onClick = { () => fundOnNetwork ( 'eip155:137' , 'Polygon' ) } >
Fund on Polygon
</ button >
< button onClick = { () => fundOnNetwork ( 'eip155:42161' , 'Arbitrum' ) } >
Fund on Arbitrum
</ button >
</ div >
);
}
Common chain IDs
Network Chain ID Ethereum Mainnet eip155:1Base eip155:8453Polygon eip155:137Arbitrum One eip155:42161Optimism eip155:10Avalanche C-Chain eip155:43114BNB Smart Chain eip155:56
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/ethereum' ;
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 , {
chain: 'eip155:1' ,
amount: '0.1'
});
};
return < button onClick = { handleFund } > Fund Wallet </ button > ;
}
Callback parameters
The onUserExited callback receives an object with:
The wallet address that was funded.
The chain ID where funding occurred.
The funding method used (e.g., 'card').
The current balance of the wallet being funded.
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/ethereum' ;
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 }) {
if ( balance < 1000 n ) {
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 , {
chain: 'eip155:8453' , // Base
amount: '0.05'
});
} 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/ethereum' ;
function CustomizedFunding () {
const { wallets } = useWallets ();
const { fundWallet } = useFundWallet ();
const handleFund = async () => {
const selectedWallet = wallets [ 0 ];
await fundWallet ( selectedWallet . address , {
chain: 'eip155:8453' ,
amount: '0.05' ,
uiConfig: {
receiveFundsTitle: 'Receive 0.05 ETH' ,
receiveFundsSubtitle: 'Scan this code or copy your wallet address to receive funds on Base.'
}
});
};
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 selection and funding:
'use client' ;
import { useWallets , useFundWallet } from '@moon-key/react-auth/ethereum' ;
import { useState } from 'react' ;
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:' , balance . toString ());
}
});
const [ amount , setAmount ] = useState ( '0.05' );
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 , {
chain: 'eip155:8453' , // Base
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" > ETH </ 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.05"
/>
</ div >
< button onClick = { handleFund } disabled = { isFunding } >
{ isFunding ? 'Processing...' : `Fund with ${ amount } ${ asset === 'native-currency' ? 'ETH' : 'USDC' } ` }
</ button >
< p className = "wallet-address" >
Wallet: { wallets [ 0 ]. address }
</ p >
</ div >
);
}
Best practices
Set appropriate default amounts
Consider typical transaction costs on the target network
Use smaller amounts for L2s (Base, Arbitrum, Optimism) where gas is cheap
Use larger amounts for Ethereum mainnet where gas is expensive
Make amounts easy to adjust before purchase
Use L2s for better user experience (lower fees)
Match the network to where your contracts are deployed
Consider which networks MoonPay supports
Communicate funding status
Explain that purchases are not instant
Show expected delivery times
Provide transaction status updates
Handle edge cases gracefully
Use callbacks effectively
Track funding completion with onUserExited
Navigate users appropriately based on balance
Log funding events for analytics
Handle insufficient balance scenarios