Verified against @dexterai/x402 5.3.1 · 2026-07-07
Pay per call from a browser wallet
By the end of this page you will have a React button that calls a 402-gated endpoint, prompts the user's connected wallet to pay in USDC, and hands you the paid response. The Solana path is the worked example. The EVM path is the same hook with a different wallet object, covered at the end.
What you need
- A running React app (18 or 19).
- An endpoint that returns HTTP 402 with x402 payment terms. If you don't have one yet, build it from Get paid — sell with tabs.
- A wallet the user connects in the browser. On Solana that is any wallet-adapter wallet (Phantom, Solflare, Backpack). On EVM that is any wagmi connector.
- USDC in that wallet on the network the endpoint charges on.
The wallet signs. Your app never holds a key.
Install
react and @solana/wallet-adapter-base are peer dependencies, so a normal wallet-adapter setup already satisfies them.
The hook
useX402Payment takes a set of wallets and returns a fetch that pays when it hits a 402. You call that fetch exactly like the global one. The first response is a 402, the hook builds and signs the payment with the wallet you passed, retries, and resolves with the real 200.
It needs a wallet object per chain. The Solana shape is two members:
A wallet-adapter wallet already has both. There is one type seam: wallet-adapter constrains signTransaction to Solana transaction types, and SolanaWallet declares it generic, so you bridge them with a single cast at the boundary. The component below shows exactly that.
Solana component
The solana object is undefined until the user connects, and the button stays disabled while it is. Once connected, one click runs the whole 402 round trip.
useX402Payment must run inside your wallet-adapter provider tree (ConnectionProvider and WalletProvider), because useWallet does. Put PayButton anywhere below those providers.
What the hook returns
You destructure only what you need. The full return:
| Field | Type | Use |
|---|---|---|
fetch | (input, init?) => Promise<Response> | Same signature as global fetch; pays on 402. |
isLoading | boolean | A payment is in progress. |
status | 'idle' | 'pending' | 'success' | 'error' | Current flow state. |
error | Error | null | Set when the last call failed. |
transactionId | string | null | Signature or hash of the settled payment. |
transactionUrl | string | null | Explorer link for that transaction. |
transactionNetwork | string | null | CAIP-2 network the payment settled on. |
balances | BalanceInfo[] | { network, chainName, balance, asset } per connected chain. |
connectedChains | { solana: boolean; evm: boolean } | Which chains have a wallet. |
isAnyWalletConnected | boolean | True if either wallet is set. |
reset | () => void | Clear error and transaction fields. |
refreshBalances | () => Promise<void> | Re-read balances on demand. |
Call reset() before a retry so a stale error doesn't linger in your UI.
EVM
The hook is chain-agnostic. Pass an evm wallet instead of (or alongside) solana. The EVM shape signs EIP-712 typed data:
wagmi's useAccount() returns an address and connection state, not a signer. An address alone cannot sign an x402 payment. Build the evm wallet from useWalletClient(), which gives you a viem client with signTypedData.
You can pass both wallets at once — useX402Payment({ wallets: { solana, evm } }) — and the hook settles on whichever network the endpoint's 402 asks for.
When it fails
A 402 round trip can fail on a wrong network, an empty balance, or a facilitator error. The hook surfaces those on error and flips status to 'error'. Map codes to messages in Pay — handling errors.