Skip to content
Closed
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
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@ proxy
.env
__pycache__/
**/.DS_Store

# pnpm
.pnpm-store/
.pnpm-debug.log

# Python
.venv/
*.pyc
.pytest_cache/
uv.lock
.env.local
13 changes: 9 additions & 4 deletions e2e/clients/axios/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import axios from "axios";
import { config } from "dotenv";
import { Hex } from "viem";
import { withPaymentInterceptor, decodeXPaymentResponse, createSigner, MultiNetworkSigner } from "x402-axios";
import {
withPaymentInterceptor,
decodeXPaymentResponse,
createSigner,
MultiNetworkSigner,
} from "x402-axios";

config();

Expand Down Expand Up @@ -32,14 +37,14 @@ api
console.log("Response received:", {
status: response.status,
headers: response.headers,
data: response.data
data: response.data,
});

const result = {
success: true,
data: response.data,
status_code: response.status,
payment_response: decodeXPaymentResponse(response.headers["x-payment-response"])
payment_response: decodeXPaymentResponse(response.headers["x-payment-response"]),
};

// Output structured result as JSON for proxy to parse
Expand All @@ -50,7 +55,7 @@ api
const errorResult = {
success: false,
error: error.message || String(error),
status_code: error.response?.status
status_code: error.response?.status,
};

console.log(JSON.stringify(errorResult));
Expand Down
63 changes: 63 additions & 0 deletions examples/python/clients/solana/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Solana x402 Client Example

This example demonstrates how to use the x402 Python SDK to make payments on Solana.

## Setup

1. Install dependencies:

```bash
uv sync
# or
pip install -r requirements.txt
```

2. Create a `.env` file with your Solana private key:

```bash
SOLANA_PRIVATE_KEY=your_base58_private_key
SERVER_URL=http://localhost:4021
```

3. Fund your Solana address with:
- Devnet SOL (for transaction fees): https://faucet.solana.com/
- Devnet USDC: Use a Solana devnet faucet or transfer from another wallet

## Run

```bash
uv run main.py
# or
python main.py
```

## How it Works

1. Loads Solana keypair from environment variable
2. Creates an httpx client with x402 payment hooks
3. Makes a request to a protected endpoint
4. x402 automatically:
- Detects 402 Payment Required response
- Creates and signs a Solana transaction
- Retries the request with payment header
- Receives and displays the response

## Token Support

This example uses USDC on Solana Devnet:

- Mint: `4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU`
- Decimals: 6

## Generating a Keypair

You can generate a new Solana keypair using:

```python
from x402.svm import generate_keypair
import base58

keypair = generate_keypair()
print(f"Address: {keypair.address}")
print(f"Private Key (base58): {base58.b58encode(bytes(keypair.keypair)).decode()}")
```
65 changes: 65 additions & 0 deletions examples/python/clients/solana/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Example x402 client for Solana payments using httpx."""

import asyncio
import os
from httpx import AsyncClient
from x402 import create_keypair_from_base58, x402Client
from x402.clients.httpx import x402_payment_hooks
from dotenv import load_dotenv

load_dotenv()

# Disable proxy for localhost connections
os.environ['NO_PROXY'] = '*'
os.environ['no_proxy'] = '*'


async def main():
# Load Solana keypair from environment
solana_private_key = os.getenv("SOLANA_PRIVATE_KEY")
if not solana_private_key:
print("Error: SOLANA_PRIVATE_KEY environment variable not set")
print("Generate a keypair and fund it with devnet SOL and USDC")
return

# Create Solana keypair
keypair = create_keypair_from_base58(solana_private_key)
print(f"Using Solana address: {keypair.address}")

# Create x402 client with SVM support
x402_client = x402Client(svm_keypair=keypair)

# Create httpx client with x402 payment hooks
async with AsyncClient(event_hooks=x402_payment_hooks(keypair)) as client:
# Make request to protected endpoint
server_url = os.getenv("SERVER_URL", "http://localhost:4021")
endpoint = f"{server_url}/protected-svm"

print(f"Making request to {endpoint}...")

try:
response = await client.get(endpoint)

if response.status_code == 200:
print("✅ Success!")
print(f"Response: {response.json()}")

# Check for payment response header
if "X-PAYMENT-RESPONSE" in response.headers:
from x402.clients.base import decode_x_payment_response

payment_response = decode_x_payment_response(
response.headers["X-PAYMENT-RESPONSE"]
)
print(f"Payment settled: {payment_response}")
else:
print(f"❌ Failed with status {response.status_code}")
print(f"Response: {response.text}")

except Exception as e:
print(f"❌ Error: {e}")


if __name__ == "__main__":
asyncio.run(main())

15 changes: 15 additions & 0 deletions examples/python/clients/solana/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "x402-solana-client-example"
version = "0.1.0"
description = "Example x402 client for Solana payments"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"httpx>=0.27.0",
"python-dotenv>=1.0.1",
"x402",
]

[tool.uv.sources]
x402 = { path = "../../../../python/x402", editable = true }

77 changes: 77 additions & 0 deletions examples/python/servers/solana/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Solana x402 Server Example

This example demonstrates how to create a FastAPI server that accepts x402 payments on Solana.

## Setup

1. Install dependencies:

```bash
uv sync
# or
pip install -r requirements.txt
```

2. Create a `.env` file with your Solana address:

```bash
SOLANA_ADDRESS=your_solana_address
PORT=4021
```

## Run

```bash
uv run main.py
# or
python main.py
```

The server will start on `http://localhost:4021`

## Endpoints

- `GET /` - Public endpoint (no payment required)
- `GET /protected-svm` - Protected endpoint requiring $0.001 USDC payment on Solana Devnet
- `GET /health` - Health check

## How it Works

1. Client makes a request to `/protected-svm`
2. Server responds with `402 Payment Required` and payment requirements
3. Client creates a Solana transaction with the required USDC amount
4. Client signs the transaction and sends it in the `X-PAYMENT` header
5. Server verifies the transaction with the facilitator
6. Facilitator signs as fee payer and submits to Solana
7. Server returns the protected content

## Payment Flow

```
Client -> Server: GET /protected-svm
Server -> Client: 402 with payment requirements
Client -> Client: Create & sign Solana transaction
Client -> Server: GET /protected-svm + X-PAYMENT header
Server -> Facilitator: Verify transaction
Facilitator -> Solana: Submit transaction
Server -> Client: 200 with content + X-PAYMENT-RESPONSE
```

## Environment Variables

- `SOLANA_ADDRESS`: Your Solana address to receive payments (required)
- `PORT`: Server port (default: 4021)
- `SVM_FEE_PAYER_ADDRESS`: Optional fee payer address (defaults to SOLANA_ADDRESS)

## Testing

You can test with curl:

```bash
# Get payment requirements
curl -i http://localhost:4021/protected-svm

# Response will be 402 with payment requirements in JSON
```

Or use the Python client example from `examples/python/clients/solana/`
65 changes: 65 additions & 0 deletions examples/python/servers/solana/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Example x402 FastAPI server for Solana payments."""

import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from x402.fastapi import require_payment
from dotenv import load_dotenv

load_dotenv()

app = FastAPI(title="x402 Solana Payment Example")

# Get Solana address from environment
SOLANA_ADDRESS = os.getenv("SOLANA_ADDRESS")
if not SOLANA_ADDRESS:
raise ValueError("SOLANA_ADDRESS environment variable must be set")

print(f"Server receiving payments at: {SOLANA_ADDRESS}")


@app.get("/")
async def root():
"""Public endpoint - no payment required."""
return {
"message": "x402 Solana Payment Server",
"endpoints": {
"/": "Public - no payment",
"/protected-svm": "Protected - requires $0.001 USDC payment on Solana Devnet",
},
}


@app.get("/protected-svm")
@require_payment(
price="$0.001", # $0.001 USDC
pay_to_address=SOLANA_ADDRESS,
network="solana-devnet",
description="Access to premium content on Solana",
path="/protected-svm",
)
async def protected_svm_endpoint(request: Request):
"""Protected endpoint requiring Solana payment."""
return {
"message": "Successfully accessed protected Solana endpoint!",
"content": "This is premium content accessible via x402 payment on Solana",
"payment": {
"network": "solana-devnet",
"amount": "0.001 USDC",
},
}


@app.get("/health")
async def health():
"""Health check endpoint."""
return {"status": "healthy"}


if __name__ == "__main__":
import uvicorn

port = int(os.getenv("PORT", "4021"))
print(f"Starting server on http://localhost:{port}")
uvicorn.run(app, host="0.0.0.0", port=port)

Loading