An x402 payments protocol for the internet. Built on gRPC-Gateway reverse-proxy server which translates a RESTful HTTP API into gRPC.
grpc-gateway-x402 is an early-stage Go project in the AI payments / x402 ecosystem. It currently has 5 GitHub stars and 0 forks.
Payment middleware for gRPC services that implements the x402 protocol.
go get github.com/becomeliminal/grpc-gateway-x402/v2
package main
import (
"net/http"
x402 "github.com/becomeliminal/grpc-gateway-x402/v2"
"github.com/becomeliminal/grpc-gateway-x402/v2/evm"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
)
func main() {
// Create EVM verifier (supports all EVM chains + ERC-20 tokens)
verifier, _ := evm.NewEVMVerifier("https://facilitator.liminal.cash")
// Configure payment requirements
x402Config := x402.Config{
Verifier: verifier,
EndpointPricing: map[string]x402.PricingRule{
"/v1/premium/*": {
Amount: "1000000", // 1 USDC (6 decimals, atomic units)
AcceptedTokens: []x402.TokenRequirement{
{
Network: "eip155:8453", // Base (CAIP-2)
AssetContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
Symbol: "USDC",
Recipient: "0xYourAddress",
},
},
},
},
}
// Create grpc-gateway mux with payment metadata propagation
mux := runtime.NewServeMux(x402.WithPaymentMetadata())
// Wrap with payment middleware
handler := x402.PaymentMiddleware(x402Config)(mux)
// Start server
http.ListenAndServe(":8080", handler)
}
func (s *server) GetPremiumContent(ctx context.Context, req *pb.Request) (*pb.Response, error) {
payment, ok := x402.GetPaymentFromGRPCContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "payment required")
}
log.Printf("Payment: %s %s on %s (tx: %s)",
payment.Amount, payment.TokenSymbol, payment.Network, payment.TransactionHash)
return &pb.Response{Data: "premium content"}, nil
}
| V1 | V2 |
|---|---|
go get github.com/becomeliminal/grpc-gateway-x402 |
go get github.com/becomeliminal/grpc-gateway-x402/v2 |
Header: X-PAYMENT |
Header: PAYMENT-SIGNATURE |
Header: X-PAYMENT-RESPONSE |
Header: PAYMENT-RESPONSE |
| 402 body only | 402 body + PAYMENT-REQUIRED header |
Network: "base-mainnet" |
Network: "eip155:8453" (CAIP-2) |
Amount: "1.00" (human-readable) |
Amount: "1000000" (atomic units) |
ChainVerifier.Verify(ctx, payment) |
ChainVerifier.Verify(ctx, payload, requirements) |
SupportedNetworks() []NetworkInfo |
SupportedKinds() []SupportedKind |
| No cross-validation | Payload accepted cross-validated against requirements |
V2 uses atomic units (smallest denomination) instead of human-readable decimals:
| Token | Decimals | 1.00 token | Atomic units |
|---|---|---|---|
| USDC | 6 | 1 USDC | "1000000" |
| EURC | 6 | 1 EURC | "1000000" |
| DAI | 18 | 1 DAI | "1000000000000000000" |
| USDT | 6 | 1 USDT | "1000000" |
x402Config := x402.Config{
Verifier: verifier,
EndpointPricing: map[string]x402.PricingRule{
"/v1/api/*": {
Amount: "100000", // 0.10 USDC
AcceptedTokens: []x402.TokenRequirement{
{
Network: "eip155:8453", // Base
AssetContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
Symbol: "USDC",
Recipient: "0xYourAddress",
},
{
Network: "eip155:42161", // Arbitrum
AssetContract: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
Symbol: "USDC",
Recipient: "0xYourAddress",
},
{
Network: "eip155:137", // Polygon
AssetContract: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
Symbol: "USDC",
Recipient: "0xYourAddress",
},
},
},
},
}
EndpointPricing: map[string]x402.PricingRule{
"/v1/premium/*": {Amount: "1000000", ...}, // 1 USDC
"/v1/basic/*": {Amount: "100000", ...}, // 0.10 USDC
"/v1/micro/*": {Amount: "10000", ...}, // 0.01 USDC
"/v1/specific-path": {Amount: "500000", ...}, // 0.50 USDC
}
Config{
SkipPaths: []string{
"/health",
"/metrics",
"/v1/public/*",
},
}
Config{
DefaultPricing: &x402.PricingRule{
Amount: "50000", // 0.05 USDC default for unmatched paths
AcceptedTokens: []x402.TokenRequirement{...},
},
}
Config{
CustomPaywallHTML: `
<html>
<head><title>Payment Required</title></head>
<body>
<h1>Payment Required</h1>
<p>This content requires payment. Please connect your wallet to continue.</p>
</body>
</html>
`,
}
Browsers get HTML, API clients get JSON.
PricingRule{
Amount: "1000000",
OutputSchema: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"joke": map[string]interface{}{
"type": "string",
"description": "A programming joke",
},
},
},
AcceptedTokens: []x402.TokenRequirement{...},
}
Client Gateway Facilitator Blockchain
| | | |
|-- GET /v1/premium/data ->| | |
| | | |
|<- 402 Payment Required --| | |
| Body: {accepts: [...]} | | |
| PAYMENT-REQUIRED: ... | | |
| | | |
|-- GET /v1/premium/data ->| | |
| PAYMENT-SIGNATURE: ... | | |
| |-- POST /v2/x402/verify --->| |
| |<- {valid: true} -----------| |
| | | |
| |-- POST /v2/x402/settle --->| |
| | |-- transfer() --------->|
| | |<- tx hash -------------|
| |<- {txHash: "0x..."} -------| |
| | | |
|<- 200 OK ----------------| | |
| PAYMENT-RESPONSE: ... | | |
| Premium Data | | |
Configtype Config struct {
Verifier ChainVerifier // Payment verification backend
EndpointPricing map[string]PricingRule // URL patterns to pricing (HTTP)
MethodPricing map[string]PricingRule // gRPC method names to pricing
DefaultPricing *PricingRule // Fallback pricing (optional)
ValidityDuration time.Duration // Payment validity (default: 5 min)
SkipPaths []string // HTTP paths to skip
SkipMethods []string // gRPC methods to skip
CustomPaywallHTML string // HTML for browser 402 responses
}
PricingRuletype PricingRule struct {
Amount string // Atomic units (e.g., "1000000" = 1 USDC)
AcceptedTokens []TokenRequirement // Accepted payment options
Description string // What this payment is for
MimeType string // Resource MIME type (optional)
OutputSchema map[string]interface{} // Response JSON schema (optional)
}
TokenRequirementtype TokenRequirement struct {
Network string // CAIP-2 (e.g., "eip155:8453")
AssetContract string // Token contract address
Symbol string // Token symbol (e.g., "USDC")
Recipient string // Payment recipient address
TokenName string // Human-readable name (optional)
TokenDecimals int // Token decimals (optional)
}
PaymentRequirementsStructured requirements sent in the 402 response. Each entry in the Accepts array is one of these.
type PaymentRequirements struct {
Scheme string `json:"scheme"`
Network string `json:"network"` // CAIP-2
Amount string `json:"amount"` // atomic units
Asset string `json:"asset"` // token contract
PayTo string `json:"payTo"` // recipient
MaxTimeoutSeconds int `json:"maxTimeoutSeconds,omitempty"`
Extra map[string]interface{} `json:"extra,omitempty"` // merchant metadata
}
The Extra map allows merchant-defined metadata (e.g., {"orderId": "...", "tier": "premium"}).
PaymentPayloadWhat the client sends in the PAYMENT-SIGNATURE header.
type PaymentPayload struct {
X402Version int `json:"x402Version"`
Accepted PaymentRequirements `json:"accepted"` // what client agreed to
Payload interface{} `json:"payload"` // scheme-specific (e.g., EVMPayload)
Extensions map[string]interface{} `json:"extensions,omitempty"` // protocol extensions
}
The Extensions map allows protocol extensions without breaking changes.
PaymentRequiredResponseThe 402 response body. The Accepts array lets merchants offer multiple payment options (different chains, tokens, amounts).
type PaymentRequiredResponse struct {
X402Version int `json:"x402Version"`
Error string `json:"error"`
Accepts []PaymentRequirements `json:"accepts"`
}
PaymentResponseSettlement receipt sent in the PAYMENT-RESPONSE header.
type PaymentResponse struct {
Success bool `json:"success"`
Transaction string `json:"transaction,omitempty"` // tx hash
Network string `json:"network,omitempty"` // CAIP-2
Payer string `json:"payer,omitempty"`
ErrorReason string `json:"errorReason,omitempty"`
}
PaymentContextWhat handlers receive after payment is verified and settled.
type PaymentContext struct {
Verified bool
PayerAddress string
Amount string
TokenSymbol string
Network string // CAIP-2
TransactionHash string
SettledAt time.Time
}
SupportedKindScheme + network pair returned by the facilitator.
type SupportedKind struct {
Scheme string `json:"scheme"` // e.g., "exact"
Network string `json:"network"` // CAIP-2
}
ChainVerifier Interfacetype ChainVerifier interface {
Verify(ctx context.Context, payload *PaymentPayload, requirements *PaymentRequirements) (*VerificationResult, error)
Settle(ctx context.Context, payload *PaymentPayload, requirements *PaymentRequirements) (*SettlementResult, error)
SupportedKinds() []SupportedKind
}
| Function | Description |
|---|---|
PaymentMiddleware(cfg Config) |
Creates HTTP middleware |
GetPaymentFromContext(ctx) |
Extract payment from HTTP context |
GetPaymentFromGRPCContext(ctx) |
Extract payment from gRPC metadata |
RequirePayment(ctx) |
Extract payment or return error |
WithPaymentMetadata() |
grpc-gateway option to propagate payment context |
EncodePaymentPayload(payload) |
Encode payload for PAYMENT-SIGNATURE header |
DecodePaymentResponse(header) |
Decode PAYMENT-RESPONSE header |
ReadPaymentRequirements(resp) |
Read requirements from 402 response |
evm.NewEVMVerifier(url) |
Create EVM chain verifier |
| Network | CAIP-2 | Address |
|---|---|---|
| Arbitrum | eip155:42161 |
0xaf88d065e77c8cC2239327C5EDb3A432268e5831 |
| Base | eip155:8453 |
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Polygon | eip155:137 |
0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 |
| Avalanche | eip155:43114 |
0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E |
| Gnosis | eip155:100 |
0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83 |
| Codex | eip155:81224 |
0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4 |
| Network | CAIP-2 | Address |
|---|---|---|
| Base | eip155:8453 |
0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42 |
For native gRPC-to-gRPC communication, use the interceptors instead of HTTP middleware.
import x402grpc "github.com/becomeliminal/grpc-gateway-x402/v2/grpc"
srv := grpc.NewServer(
grpc.UnaryInterceptor(x402grpc.UnaryServerInterceptor(x402Config)),
grpc.StreamInterceptor(x402grpc.StreamServerInterceptor(x402Config)),
)
| Version | Payment | Response | Requirements |
|---|---|---|---|
| V2 | payment-signature |
payment-response |
payment-required |
| V1 | x402-payment |
x402-payment-response |
x402-payment-requirements |
payment, ok := x402grpc.GetPaymentFromContext(ctx)
// or
payment, err := x402grpc.RequirePayment(ctx)
// Liminal facilitator
verifier, _ := evm.NewEVMVerifier("https://facilitator.liminal.cash")
// Coinbase facilitator
verifier, _ := evm.NewEVMVerifier("https://facilitator.x402.org")
// Your own facilitator
verifier, _ := evm.NewEVMVerifier("https://my-facilitator.com")
// Local development
verifier, _ := evm.NewEVMVerifier("http://localhost:3000")
Skip facilitators entirely by implementing ChainVerifier:
type CustomVerifier struct{}
func (v *CustomVerifier) Verify(ctx context.Context, payload *x402.PaymentPayload, requirements *x402.PaymentRequirements) (*x402.VerificationResult, error) {
// Your verification logic
return &x402.VerificationResult{Valid: true}, nil
}
func (v *CustomVerifier) Settle(ctx context.Context, payload *x402.PaymentPayload, requirements *x402.PaymentRequirements) (*x402.SettlementResult, error) {
// Your settlement logic
return &x402.SettlementResult{TransactionHash: "0x..."}, nil
}
func (v *CustomVerifier) SupportedKinds() []x402.SupportedKind {
return []x402.SupportedKind{{Scheme: "exact", Network: "eip155:8453"}}
}
config := x402.Config{Verifier: &CustomVerifier{}}
The V2 middleware auto-detects V1 clients via header detection:
X-PAYMENT header → V1 path (responds with X-PAYMENT-RESPONSE)PAYMENT-SIGNATURE header → V2 path (responds with PAYMENT-RESPONSE)Both work simultaneously. Existing V1 clients require no changes.
cd examples/basic
export RECIPIENT_ADDRESS="0xYourAddress"
go run main.go
See examples/ for more:
basic/ - Single token integrationmulti-token/ - Multiple currencies and networks, tiered pricingjokes-api/ - Fun demo (pay $0.0001 for programming jokes)go test ./...
go test -cover ./...
MIT