Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,24 @@ end

### Accept payments in Phoenix

Route specification according to V2 specification of x402.
See https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md

```elixir
# In your router or endpoint
plug X402.Plug.PaymentGate,
facilitator_url: "https://x402-facilitator-app.fly.dev",
routes: %{
"GET /api/weather" => %{
routes: [
%{
method: :get,
path: "/api/weather",
price: "0.005",
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
pay_to: "0xYourWalletAddress",
description: "Weather data API"
}
}
]
```

That's it. Requests without payment get a `402` response with payment instructions. Requests with a valid `PAYMENT-SIGNATURE` header are verified and passed through.
Expand Down Expand Up @@ -99,7 +105,16 @@ end
plug X402.Plug.PaymentGate,
facilitator_url: "https://x402-facilitator-app.fly.dev",
hooks: MyApp.PaymentHooks,
routes: %{...}
routes: [
%{
method: :get,
path: "/api/data",
price: "0.01",
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
pay_to: "0xYourWalletAddress"
}
]
```

### "upto" scheme — flexible pricing
Expand All @@ -108,15 +123,18 @@ plug X402.Plug.PaymentGate,
# Server: accept up to a max price (agent bids what they're willing to pay)
plug X402.Plug.PaymentGate,
facilitator_url: "https://x402-facilitator-app.fly.dev",
routes: %{
"GET /api/premium" => %{
routes: [
%{
method: :get,
path: "/api/premium",
scheme: "upto",
maxPrice: "1.00",
price: "1.00",
network: "eip155:8453",
pay_to: "0xYourWallet",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
pay_to: "0xYourWalletAddress",
description: "Premium data — pay what you want up to $1"
}
}
]

# Encode/decode upto payment requirements
{:ok, header} = X402.PaymentRequired.encode(%{
Expand Down
2 changes: 2 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
- [ ] Facilitator client support for upto verification
- [ ] Tests + docs

### "batch" settlement scheme
- [ ]
---

## v0.3 — SIWX (Sign-In-With-X)
Expand Down
29 changes: 17 additions & 12 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,21 @@ Incoming HTTP request
X402.Plug.PaymentGate
├─ No PAYMENT-SIGNATURE header? → 402 response (PAYMENT-REQUIRED header)
├─ No PAYMENT-SIGNATURE? → 402 + PAYMENT-REQUIRED (v2 PaymentRequired)
└─ Signature present?
└─ PAYMENT-SIGNATURE present?
├─ Call X402.Facilitator.verify/2
└─ POST /verify to facilitator URL
├─ Decode PaymentPayload (x402Version must be 2)
malformed / wrong version → 400 + PAYMENT-REQUIRED
├─ Verify OK? → Call X402.Facilitator.settle/2
│ └─ POST /settle to facilitator URL
├─ Match payload.accepted to route accepts
│ (scheme, network, amount, asset, payTo)
│ no match → 402 + PAYMENT-REQUIRED
├─ Hooks: before_verify → after_verify → on_failure
├─ Facilitator.verify → Facilitator.settle
│ failure → 402 (+ PAYMENT-RESPONSE when settle body present)
└─ Pass through to app handler (conn)
└─ Success → PAYMENT-RESPONSE, assigns, pass through
```

## Optional Dependencies
Expand All @@ -67,13 +69,16 @@ X402.Plug.PaymentGate

All optional deps are guarded by compile-time checks. Must `mix compile --no-optional-deps` successfully.

## Data Formats
## Data Formats (x402 v2)

All x402 headers carry **Base64-encoded JSON payloads**:

- `PAYMENT-REQUIRED`: `{scheme, network, maxAmountRequired, payTo, asset, extra}`
- `PAYMENT-SIGNATURE`: `{x402Version, scheme, network, payload, authorization}`
- `PAYMENT-RESPONSE`: `{success, transaction, networkId, errorReason?}`
- `PAYMENT-REQUIRED`: `{x402Version: 2, error?, resource, accepts[], extensions?}`
- Each accept: `{scheme, network, amount, asset, payTo, maxTimeoutSeconds, extra?}`
- `resource`: `{url, description?, mimeType?, serviceName?, tags?, iconUrl?}`
- `PAYMENT-SIGNATURE` (PaymentPayload): `{x402Version: 2, resource?, accepted, payload, extensions?}`
- `accepted` is a full PaymentRequirements object (must match a server accept)
- `PAYMENT-RESPONSE` (SettleResponse): `{success, transaction, network, payer?, amount?, errorReason?, extensions?}`

Network IDs use CAIP-2 format: `"eip155:8453"` (Base mainnet), `"eip155:84532"` (Base Sepolia).

Expand Down
Loading