use actix_web::{middleware::Logger, web, App, HttpRequest, HttpResponse, HttpServer, Result};
use rust_x402::{
middleware::PaymentMiddleware,
types::{FacilitatorConfig, PaymentRequirements},
};
use std::str::FromStr;
use rust_x402::actix_web::{create_x402_middleware, handle_payment_verification};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
println!("🚀 Starting x402 Actix-web server on http://localhost:4022");
let facilitator_config = FacilitatorConfig::default();
let payment_middleware = PaymentMiddleware::new(
rust_decimal::Decimal::from_str("0.0001").unwrap(),
"0x209693Bc6afc0C5328bA36FaF03C514EF312287C".to_string(),
)
.with_facilitator_config(facilitator_config)
.with_description("Premium API access".to_string());
let _x402_middleware = create_x402_middleware(payment_middleware);
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.service(
web::scope("/api")
.route("/joke", web::get().to(joke_handler))
.route("/data", web::get().to(api_data_handler))
.route("/download", web::get().to(download_handler)),
)
.route("/health", web::get().to(health_handler))
})
.bind("127.0.0.1:4022")?
.run()
.await
}
async fn joke_handler(req: HttpRequest) -> Result<HttpResponse> {
let requirements = vec![PaymentRequirements {
scheme: "exact".to_string(),
network: "base-sepolia".to_string(),
max_amount_required: "1000000".to_string(), asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913".to_string(), pay_to: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C".to_string(),
resource: "/api/joke".to_string(),
description: "Premium joke access".to_string(),
mime_type: Some("application/json".to_string()),
max_timeout_seconds: 300,
output_schema: None,
extra: None,
}];
match handle_payment_verification(&req, &requirements).await? {
Some(response) => Ok(response),
None => {
Ok(HttpResponse::Ok().json(serde_json::json!({
"joke": "Why do programmers prefer dark mode? Because light attracts bugs!",
"category": "programming",
"premium": true
})))
}
}
}
async fn api_data_handler(req: HttpRequest) -> Result<HttpResponse> {
let requirements = vec![PaymentRequirements {
scheme: "exact".to_string(),
network: "base-sepolia".to_string(),
max_amount_required: "1000000".to_string(),
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913".to_string(),
pay_to: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C".to_string(),
resource: "/api/data".to_string(),
description: "Premium data access".to_string(),
mime_type: Some("application/json".to_string()),
max_timeout_seconds: 300,
output_schema: None,
extra: None,
}];
match handle_payment_verification(&req, &requirements).await? {
Some(response) => Ok(response),
None => Ok(HttpResponse::Ok().json(serde_json::json!({
"data": "This is premium data that requires payment",
"timestamp": chrono::Utc::now(),
"value": 42
}))),
}
}
async fn download_handler(req: HttpRequest) -> Result<HttpResponse> {
let requirements = vec![PaymentRequirements {
scheme: "exact".to_string(),
network: "base-sepolia".to_string(),
max_amount_required: "1000000".to_string(),
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913".to_string(),
pay_to: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C".to_string(),
resource: "/api/download".to_string(),
description: "Premium file download".to_string(),
mime_type: Some("application/octet-stream".to_string()),
max_timeout_seconds: 300,
output_schema: None,
extra: None,
}];
match handle_payment_verification(&req, &requirements).await? {
Some(response) => Ok(response),
None => {
Ok(HttpResponse::Ok()
.content_type("application/octet-stream")
.append_header((
"Content-Disposition",
"attachment; filename=\"premium_file.txt\"",
))
.body("This is premium file content that requires payment!"))
}
}
}
async fn health_handler() -> Result<HttpResponse> {
Ok(HttpResponse::Ok().json(serde_json::json!({
"status": "healthy",
"service": "x402-actix-server",
"timestamp": chrono::Utc::now()
})))
}