Skip to main content
This feature is experimental. Support for unauthenticated users and more currencies is coming soon.
Fiat Onramp Privy provides a useFiatOnramp hook in @privy-io/react-auth that starts a fiat onramp flow in the Privy modal. Your app can use this hook to let authenticated users buy crypto with supported fiat currencies, such as USD and EUR.

Access the hook

Import and initialize useFiatOnramp:
import {useFiatOnramp} from '@privy-io/react-auth';

const {fund} = useFiatOnramp();

Start a fiat onramp flow

Call fund with source currency options, and a destination wallet.
await fund({
  source: {
    assets: ['usd', 'eur']
  },
  destination: {
    asset: 'usdc',
    chain: 'eip155:8453',
    address: '<wallet_address>'
  }
});
destination.chain accepts a CAIP-2 identifier (for example, eip155:8453 for Base or solana:mainnet for Solana).

Parameters

fund accepts an object with the following fields:
ParameterTypeDescription
source.assets('usd' | 'eur' | 'mxn' | 'brl' | 'gbp')[]Required. The list of fiat source currencies your app allows. Must be non-empty.
source.defaultAsset'usd' | 'eur' | 'mxn' | 'brl' | 'gbp'Optional. The source currency selected when the flow opens.
destination.asset'usdc'Required. Destination crypto asset for this flow.
destination.chain`${string}:${string}`Required. Destination chain in CAIP-2 format.
destination.addressstringRequired. Destination wallet address for purchased funds.
environment'sandbox' | 'production'Optional. Onramp environment for provider APIs.
defaultAmountstringOptional. Initial fiat amount displayed in the amount step.

Return value

fund returns a Promise with one of the following statuses:
StatusMeaning
'submitted'The user completed the provider flow, then exited before final confirmation finished in Privy.
'confirmed'The flow reached provider confirmation, and the user completed the success step.

Error handling

fund rejects on invalid configuration or incomplete flows. Common error cases include:
  • source.assets is empty
  • another fiat onramp flow is already in progress
  • the user closes the flow before submitting a purchase
  • provider session or status requests fail
Your app should wrap calls in try/catch and show clear UI feedback.

Complete example

import {useState} from 'react';
import {useFiatOnramp} from '@privy-io/react-auth';

export const BuyUsdcButton = ({address}: {address: string}) => {
  const {fund} = useFiatOnramp();
  const [isLoading, setIsLoading] = useState(false);

  const onBuyUsdc = async () => {
    setIsLoading(true);

    try {
      const result = await fund({
        source: {
          assets: ['usd', 'eur', 'gbp'],
          defaultAsset: 'usd'
        },
        destination: {
          asset: 'usdc',
          chain: 'eip155:8453',
          address
        },
        environment: 'production',
        defaultAmount: '50'
      });

      if (result.status === 'confirmed') {
        // Update post-purchase UI immediately.
      }

      if (result.status === 'submitted') {
        // Show pending state while the provider finalizes the transaction.
      }
    } catch (error) {
      // Show retry UI or an error banner.
      console.error(error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <button type="button" onClick={onBuyUsdc} disabled={isLoading}>
      {isLoading ? 'Starting onramp…' : 'Buy USDC'}
    </button>
  );
};

Use with deposit addresses

Apps that already use deposit addresses to receive funds on behalf of users can pass that address directly as the destination.address in the fund call. The onramp flow purchases the specified crypto asset and delivers it straight to the deposit address, letting your app credit the user’s account through your existing settlement logic.
const onFundDepositAddress = async () => {
  return await fund({
    source: {
      assets: ['usd']
    },
    destination: {
      address: depositAddress,
      asset: 'usdc',
      chain: 'eip155:8453'
    }
  });
};
The destination.address does not need to be the user’s own wallet. It can be any valid address your app controls, such as a per-user deposit address generated by your backend. Set destination.asset and destination.chain to match the crypto asset and network that the deposit address expects as input.