Build a Custom Fiat Experience

Learn how to enable your users to purchase your application’s token with any fiat payment method with our headless flow.

In this guide, we'll show you how to purchase 0.01 Base ETH from USD in Typescript.

  • Install the Connect SDK

    npm i thirdweb
  • Get Your Client ID

    Log in to the thirdweb dashboard. Navigate to the Settings page and create an API key to get your Client ID. You'll need your Client ID to interact with the Connect SDK.

  • Get a “Buy with Fiat” quote

    Buying with fiat can require one or two steps depending on your destination token:

    If the destination token can be bought directly with fiat, your users can onramp directly to their destination token.

    If the destination token can not be bought directly with fiat, your users will need to onramp to an intermediate token, then convert the onramp token to the destination token.

    For example, when Buying Base ETH:

    • Users will receive Avalance AVAX ( native token ) in exchange for USD
    • Users will be prompted to convert Avalanche AVAX to Base ETH

    This process requires your user's wallet address. Refer to this guide to learn how to connect a wallet.

    import { getBuyWithFiatQuote } from "thirdweb/pay";
    import { NATIVE_TOKEN_ADDRESS } from "thirdweb";
    import { base } from "thirdweb/chains";
    // create a thirdweb client
    const client = createThirdwebClient({
    clientId: "<your_client_id>",
    // Get a quote for buying 0.01 Base ETH with USD
    const quote = await getBuyWithFiatQuote({
    client: client, // thirdweb client
    fromCurrencySymbol: "USD", // fiat currency symbol
    toChainId:, // base chain id
    toAmount: "0.01", // amount of token to buy
    toTokenAddress: NATIVE_TOKEN_ADDRESS, // native token
    toAddress: "0x...", // user's wallet address
    // display quote information to user
    // etc...

    The quote object contains detailed transaction information including the estimated time, processing fees, amount of fiat currency required, and more that you can display in your application.

  • Check for a Buy With Crypto Step

    The quote object contains quote.onRampToken and quote.toToken objects containing intermediate and detination token information.

    If quote.onRampToken is not the same as quote.toToken, then your users will need to onramp to intermediary token before arriving at their destination token.

    You can use isSwapRequiredPostOnramp to check this

    import { isSwapRequiredPostOnramp } from "thirdweb/pay";
    const hasTwoSteps = isSwapRequiredPostOnramp(quote);
    if (hasTwoSteps) {
    // display the two steps to the user
  • Display the Onramp Experience

    Once you have a quote from getBuyWithFiatQuote, you can open a new tab with quote.onRampLink to show the onramp experience. This onramp experience handles all regulatory requirements, know your customer (KYC) verifications, and sanctions screening.

    After they've KYC'd (if required), customers have the option of saving payment methods, KYC data, and wallet information in the onramp, which makes the returning onramp experience much faster.

    Your users will be able to purchase the quote.onRampToken with the specified fiat currency., "_blank");
  • Poll for Transaction Status

    When you open the quote.onRampLink in a new tab, you can begin polling for the onramp transaction status in your app by calling getBuyWithFiatStatus.

    getBuyWithFiatStatus requires an intentId which you can get from quote object.

    getBuyWithFiatStatus requires passing an intentId which you can get from quote object.

    There are a number of transactions statuses:

    // Keep calling the below code at some regular intervals to poll the status
    const fiatStatus = await getBuyWithFiatStatus({
    client: client, // thirdweb client
    intentId: quote.intentId, // pass intentId from quote
    if (fiatStatus.status === "NOT_FOUND") {
    // invalid intentId
    // Show error in your page
    if (fiatStatus.status === "NONE") {
    // No information available yet
    // Show "loading" status on your page
    // keep polling
    if (fiatStatus.status === "PENDING_PAYMENT") {
    // Payment is in progress in the on-ramp provider
    // Show "loading" status on your page
    // keep polling
    if (fiatStatus.status === "PENDING_ON_RAMP_TRANSFER") {
    // payment is done, on-ramp process has not started
    // show "loading" status on your page
    // keep polling
    if (fiatStatus.status === "ON_RAMP_TRANSFER_IN_PROGRESS") {
    // on-ramp provider is doing on-ramp with fiat currency
    // show "loading" status on your page
    // keep polling
    if (fiatStatus.status === "ON_RAMP_TRANSFER_FAILED") {
    // on-ramp provider failed to do onramp
    // show error in your UI
    // STOP polling
    if (fiatStatus.status === "ON_RAMP_TRANSFER_COMPLETED") {
    // if only on-ramp is required - process is done!
    if (!hasTwoSteps) {
    // show "success"
    // Stop polling
    // That's it!
    } else {
    // Wait for "CRYPTO_SWAP_REQUIRED" state to convert tokens
    // Show "loading" status on your page
    // Stop polling
    if (fiatStatus.status === "CRYPTO_SWAP_REQUIRED") {
    // go to step 5
    // Show UI for Buy with Crypto quote.onRampToken to quote.toToken
    // Stop polling
  • Get a Buy With Crypto Quote (Optional)

    This step is only relevant when a crypto-to-crypto purchase is required after perfmorming an onramp to an intermediary token.

    In this case, you can use getPostOnRampQuote to get a quote to convert the intermediary token to the destination token.

    // when fiatStatus.status === "CRYPTO_SWAP_REQUIRED"
    // and hasTwoStep === true
    const swapQuote = await getPostOnRampQuote({
    client: client,
    buyWithFiatStatus: fiatStatus,
    if (!swapQuote) {
    // invalid fiatStatus status
    } else {
    // Go to step 6 to kick off the "Swap" flow
  • Execute Buy With Crypto

    Executing Buy With Crypto may involve either a single step or 2 steps

    If your source token is an ERC-20 token, an approval step is required before executing the Buy With Crypto transaction.

    You can check if approval is required by checking quote.approval

    import { sendTransaction, waitForReceipt } from "thirdweb";
    const account = wallet.getAccount();
    // If approval is required, send the approval transaction
    // show a button to user to request approval and send the transaction on click
    if (quote.approval) {
    // request spending tokens from wallet
    const approveTxResult = await sendTransaction({
    transaction: quote.approval, // approval transaction
    account: account, // account from user's connected wallet
    await waitForReceipt(approveTxResult);
    // The above step may result in error if user rejects or transaction fails
    // If it results in error, it needs to be done again until its successful
    // Once the approval is done, you can send the buyWithCrypto transaction
    // show a button to user to request sending buyWithCrypto transaction and send the transaction on click
    const buyWithCryptoTxResult = await sendTransaction({
    transaction: quote.transactionRequest,
    account: account,
    await waitForReceipt(buyWithCryptoTxResult);
    // Save the buy with crypto transaction hash for polling the status as mentioned in step 7
    const buyWithCryptoTxHash = buyWithCryptoTxResult.transactionHash;
  • Poll for Buy With Crypto Status

    Once you've initiated your Buy With Crypto transaction, you'll want to track the status. You can notify users throughout this journey by checking for the following statuses:

    import { getBuyWithCryptoStatus } from 'thirdweb/pay'
    // Keep calling the below code at regular intervals if the status is in a pending state
    const swapStatus = await getBuyWithCryptoStatus({
    client: client,
    transactionHash: swapTxHash,
    if (swapStatus.status === "NOT_FOUND") {
    // invalid swap tx
    // Show error in your page
    // Stop polling
    if (swapStatus.status === "NONE") {
    // No information available yet
    // show "loading" in UI
    // Keep polling
    if (swapStatus.status === "FAILED") {
    // swap failed
    // show "error" in UI - show a retry option in UI
    // Stop polling
    if (swapStatus.status === "COMPLETED") {
    // swap completed
    // show "success" in UI
    // Stop polling
    if (swapStatus.status === "PENDING") {
    // swap is in progress
    // show "loading" in UI
    // Keep polling

  • Build a Custom Experience in React

    If you are using React, we provide Hooks for each of the functions mentioned above:

    React HookTypescript