Self-contained HTML paywall page for browser-facing 402 responses.
Renders a single lean page — inline CSS, no external requests, no build
step — from a v2 PaymentRequired payload:
- the resource description plus amount, asset, network, and recipient for every advertised payment option
- the exact Base64
PAYMENT-REQUIREDheader value for tooling, with manual retry instructions - a minimal EIP-1193 (
window.ethereum) payment flow for"exact"EVM options using the defaulteip3009asset transfer method: connect a wallet, sign theTransferWithAuthorizationtyped data viaeth_signTypedData_v4, assemble the v2PaymentPayload, and retry the request with aPAYMENT-SIGNATUREheader
Without a browser wallet — or when no advertised option is wallet-payable — the page degrades to the requirement details and the manual header instructions. All interpolated values are HTML-escaped and the embedded configuration JSON is encoded script-safe, so route descriptions and service names cannot inject markup.
Configure it through X402.Plug.PaymentGate:
plug X402.Plug.PaymentGate,
paywall: X402.Paywall.Default,
routes: [...]
Summary
Functions
Returns a human-readable display name for a CAIP-2 network identifier.
Renders the default HTML paywall page.
Functions
Returns a human-readable display name for a CAIP-2 network identifier.
Examples
iex> X402.Paywall.Default.network_display_name("eip155:8453")
"Base"
iex> X402.Paywall.Default.network_display_name("eip155:999999")
"EVM chain 999999"
iex> X402.Paywall.Default.network_display_name("otherchain:mainnet")
"otherchain:mainnet"
@spec render(map(), X402.Paywall.conn_info()) :: {:ok, iodata()} | {:error, term()}
Renders the default HTML paywall page.
Returns {:error, reason} when the payload cannot be encoded as a
PAYMENT-REQUIRED header value (see X402.PaymentRequired.encode/1).
Examples
iex> payment_required = %{
...> "x402Version" => 2,
...> "error" => "PAYMENT-SIGNATURE header is required",
...> "resource" => %{
...> "url" => "https://example.com/premium",
...> "description" => "Premium report",
...> "mimeType" => "application/json"
...> },
...> "accepts" => [
...> %{
...> "scheme" => "exact",
...> "network" => "eip155:84532",
...> "amount" => "10000",
...> "asset" => "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
...> "payTo" => "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
...> "maxTimeoutSeconds" => 60,
...> "extra" => %{"name" => "USDC", "version" => "2"}
...> }
...> ],
...> "extensions" => %{}
...> }
iex> conn_info = %{method: "GET", request_path: "/premium", status: 402}
iex> {:ok, html} = X402.Paywall.Default.render(payment_required, conn_info)
iex> html =~ "Premium report" and html =~ "Base Sepolia"
true