Skip to main content
This example demonstrates how to make x402 payments using Privy embedded Solana wallets. Privy provides managed wallet infrastructure with social login, eliminating the need for users to install browser extensions.

When to Use Privy

Use Privy when:
  • You want users to pay without installing wallet extensions
  • You’re building consumer-facing applications requiring low friction
  • You need social login (email, Google, Twitter) with automatic wallet creation
  • You want to provide wallet recovery for non-crypto-native users
  • You’re building on React/Next.js
Use browser wallets (Phantom) when:
  • Users already have crypto wallets
  • You need non-custodial wallets
  • You want to minimize third-party dependencies

Frontend Code

The React app integrates Privy for wallet management and Faremeter for payments:
import React, { useState } from "react";
import { PrivyProvider, usePrivy } from "@privy-io/react-auth";
import { useWallets, toSolanaWalletConnectors } from "@privy-io/react-auth/solana";
import { createSolanaRpc } from "@solana/kit";
import { PublicKey, Connection, VersionedTransaction } from "@solana/web3.js";
import { createPaymentHandler } from "@faremeter/payment-solana/exact";
import { wrap as wrapFetch } from "@faremeter/fetch";
import { solana } from "@faremeter/info";

const NETWORK = "mainnet-beta";
const ASSET = "USDC";
const CHAIN_ID = "solana:mainnet";

function PaymentDemo() {
  const { login, logout, authenticated, ready } = usePrivy();
  const { wallets } = useWallets();
  const [loading, setLoading] = useState(false);
  const [response, setResponse] = useState("");

  const wallet = wallets?.[0]; // First Solana wallet

  const handlePayment = async () => {
    if (!wallet) {
      setResponse("Error: No Solana wallet connected");
      return;
    }

    try {
      setLoading(true);

      // Lookup USDC token info
      const tokenInfo = solana.lookupKnownSPLToken(NETWORK, ASSET);
      if (!tokenInfo) throw new Error(`Could not lookup ${ASSET}`);

      const assetAddress = new PublicKey(tokenInfo.address);
      const publicKey = new PublicKey(wallet.address);
      const connection = new Connection("https://api.mainnet-beta.solana.com");

      // Create wallet interface for Faremeter
      const paymentWallet = {
        network: NETWORK,
        publicKey,
        updateTransaction: async (tx: VersionedTransaction) => {
          const { signedTransaction } = await wallet.signTransaction({
            transaction: tx.serialize(),
            chain: CHAIN_ID,
          });
          return VersionedTransaction.deserialize(signedTransaction);
        },
      };

      // Create payment handler
      const handler = createPaymentHandler(paymentWallet, assetAddress, connection);
      const fetchWithPayment = wrapFetch(fetch, { handlers: [handler] });

      // Make payment request
      const res = await fetchWithPayment("https://triton.api.corbits.dev", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          jsonrpc: "2.0",
          id: 1,
          method: "getBlockHeight",
        }),
      });

      const data = await res.json();
      setResponse(JSON.stringify(data, null, 2));
    } catch (error: any) {
      setResponse(`Error: ${error.message}`);
    } finally {
      setLoading(false);
    }
  };

  if (!ready) {
    return <div>Loading Privy...</div>;
  }

  if (!authenticated) {
    return (
      <div>
        <h1>Privy + Faremeter Demo</h1>
        <p>Connect your wallet to create a Solana embedded wallet via Privy</p>
        <button onClick={login}>Connect Wallet</button>
      </div>
    );
  }

  return (
    <div>
      <h1>Connected: {wallet?.address.slice(0, 8)}...</h1>
      <button onClick={handlePayment} disabled={loading || !wallet}>
        {loading ? "Processing..." : "Make Payment"}
      </button>
      <button onClick={logout}>Disconnect</button>
      {response && <pre>{response}</pre>}
    </div>
  );
}

function App() {
  return (
    <PrivyProvider
      appId={process.env.PRIVY_APP_ID!}
      config={{
        loginMethods: ["wallet", "email"],
        appearance: {
          theme: "light",
          walletChainType: "solana-only",
          walletList: ["detected_solana_wallets", "phantom", "solflare", "privy"],
        },
        embeddedWallets: {
          solana: {
            createOnLogin: "users-without-wallets",
          },
        },
        externalWallets: {
          solana: {
            connectors: toSolanaWalletConnectors(),
          },
        },
        solana: {
          rpcs: {
            "solana:mainnet": {
              rpc: createSolanaRpc("https://api.mainnet-beta.solana.com"),
            },
          },
        },
      }}
    >
      <PaymentDemo />
    </PrivyProvider>
  );
}

export default App;

Step-by-Step Breakdown

1. Setup Privy Provider

Wrap your app with PrivyProvider to enable authentication:
import { PrivyProvider } from "@privy-io/react-auth";

<PrivyProvider appId={process.env.PRIVY_APP_ID!} config={{ ... }}>
  <App />
</PrivyProvider>

2. Get Connected Wallet

Use Privy hooks to access the authenticated user’s wallet:
import { usePrivy } from "@privy-io/react-auth";
import { useWallets } from "@privy-io/react-auth/solana";

const { authenticated, login } = usePrivy();
const { wallets } = useWallets();
const wallet = wallets?.[0]; // First Solana wallet

3. Create Wallet Interface

The updateTransaction method signs the transaction to prove to the Solana blockchain that you authorize spending your USDC. Without a valid signature, the network rejects the transaction as unauthorized.
const paymentWallet = {
  network: NETWORK,
  publicKey: new PublicKey(wallet.address), // Convert address string to PublicKey
  updateTransaction: async (tx: VersionedTransaction) => {
    // Call Privy's signTransaction with the tx
    const { signedTransaction } = await wallet.signTransaction({
      transaction: tx.serialize(),
      chain: CHAIN_ID,
    });
    // Return the signed transaction in Faremeter's expected format
    return VersionedTransaction.deserialize(signedTransaction);
  },
};

4. Create Payment Handler

Use Faremeter’s payment handler to wrap your fetch calls:
import { createPaymentHandler } from "@faremeter/payment-solana/exact";
import { wrap as wrapFetch } from "@faremeter/fetch";

const handler = createPaymentHandler(paymentWallet, assetAddress, connection);
const fetchWithPayment = wrapFetch(fetch, { handlers: [handler] });

5. Make Payment Request

Use the wrapped fetch to automatically handle 402 payment flows:
const response = await fetchWithPayment("https://triton.api.corbits.dev", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "getBlockHeight",
  }),
});

Privy Configuration

Login Methods

Control how users can authenticate:
config={{
  loginMethods: ["wallet", "email"],
}}
Available methods: "wallet", "email", "google", "twitter", "discord", "github"

Embedded Wallets

Configure automatic wallet creation:
config={{
  embeddedWallets: {
    solana: {
      createOnLogin: "users-without-wallets",
    },
  },
}}
Options: "off", "users-without-wallets", "all-users"

External Wallets

Allow users to connect existing browser wallets like Enable Phantom, Solflare, etc.
config={{
  externalWallets: {
    solana: {
      connectors: toSolanaWalletConnectors(),
    },
  },
}}

Wallet List

Customize which wallets appear in the connection UI:
config={{
  appearance: {
    walletList: [
      "detected_solana_wallets",
      "phantom",
      "solflare",
      "privy",
    ],
  },
}}