This guide shows how to pay for x402-protected resources using a WDK self-custodial wallet on Plasma or Stable. By the end you’ll have a working fetch wrapper that automatically handles 402 Payment Required responses with USD₮.
Install
npm install @x402/fetch @x402/evm @tetherto/wdk-wallet-evm
Create a wallet
Create a WalletAccountEvm pointed at the chain you want to pay on. The account derives keys locally from your seed phrase.
import { WalletAccountEvm } from "@tetherto/wdk-wallet-evm";
const account = new WalletAccountEvm(process.env.SEED_PHRASE, {
provider: "https://rpc.plasma.to", // or "https://rpc.stable.xyz"
});
const address = await account.getAddress();
console.log("Buyer address:", address);
Register with x402
WalletAccountEvm already satisfies the signer interface that x402 expects.
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
That’s it. fetchWithPayment now intercepts any 402 Payment Required response, signs an EIP-3009 transferWithAuthorization using your WDK wallet, and retries the request with the payment header attached.
Make a paid request
const response = await fetchWithPayment("https://api.example.com/weather", {
method: "GET",
});
const data = await response.json();
console.log("Response:", data);
If the endpoint requires payment, the x402 client handles the full flow automatically:
- Initial request returns
402 with a PAYMENT-REQUIRED header
- Client parses the payment requirements (amount, token, network, recipient)
- Client signs an EIP-3009 authorization with your WDK wallet
- Client retries the request with the
PAYMENT-SIGNATURE header
- The facilitator settles the payment on-chain and the server returns the resource
Full example
import { WalletAccountEvm } from "@tetherto/wdk-wallet-evm";
import { x402Client, wrapFetchWithPayment, x402HTTPClient } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
// --- Config ---
const SEED_PHRASE = process.env.SEED_PHRASE;
const RPC = process.env.RPC_URL || "https://rpc.plasma.to";
const ENDPOINT = process.env.ENDPOINT || "https://api.example.com/weather";
// --- Wallet ---
const account = new WalletAccountEvm(SEED_PHRASE, {
provider: RPC,
});
console.log("Address:", await account.getAddress());
// --- x402 client ---
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
// --- Request ---
const response = await fetchWithPayment(ENDPOINT, { method: "GET" });
const body = await response.json();
console.log("Response:", body);
// --- Receipt ---
if (response.ok) {
const httpClient = new x402HTTPClient(client);
const receipt = httpClient.getPaymentSettleResponse(
(name) => response.headers.get(name),
);
console.log("Payment receipt:", JSON.stringify(receipt, null, 2));
}
Environment variables
# .env
SEED_PHRASE="your twelve word seed phrase here"
RPC_URL="https://rpc.plasma.to" # Plasma mainnet
# RPC_URL="https://rpc.stable.xyz" # Stable mainnet
ENDPOINT="https://api.example.com/weather"
Your seed phrase controls your funds. Never commit it to version control. Use environment variables or a secrets manager.
Checking your balance
Before making paid requests, verify your wallet has USDT0 on the target chain:
const USDT0_PLASMA = "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb";
const USDT0_STABLE = "0x779Ded0c9e1022225f8E0630b35a9b54bE713736";
const balance = await account.getTokenBalance(USDT0_PLASMA);
console.log("USDT0 balance:", Number(balance) / 1e6, "USDâ‚®");
USDT0 uses 6 decimals. A balance of 1000000 equals 1.00 USDâ‚®.
Using Axios instead of fetch
import { x402Client, wrapAxiosWithPayment } from "@x402/axios";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import axios from "axios";
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const api = wrapAxiosWithPayment(
axios.create({ baseURL: "https://api.example.com" }),
client,
);
const response = await api.get("/weather");
console.log("Response:", response.data);
What happens under the hood
x402 uses EIP-3009 (transferWithAuthorization) for payment settlement. When your WDK wallet signs a payment, it creates an off-chain authorization that allows the facilitator to transfer a specific amount of USDT0 from your address to the seller’s address. The facilitator then submits this authorization on-chain in a single transaction.
Because Plasma and Stable both support EIP-3009 natively on their USDT0 contracts, the facilitator can settle payments without any gas cost to the buyer. The buyer only pays the exact amount specified in the payment requirements.