From bc6049282c06a58be12b7abc2b469b1ef3995e37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:21:53 +0000 Subject: [PATCH 1/4] Initial plan From 5024d6bbd7ddf87c7e938a8fded46a732ded36b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:28:44 +0000 Subject: [PATCH 2/4] Add actix-web seller example to validate actix integration Co-authored-by: takasaki404 <193405421+takasaki404@users.noreply.github.com> --- x402-kit/examples/actix_web_seller.rs | 236 ++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 x402-kit/examples/actix_web_seller.rs diff --git a/x402-kit/examples/actix_web_seller.rs b/x402-kit/examples/actix_web_seller.rs new file mode 100644 index 0000000..c68395a --- /dev/null +++ b/x402-kit/examples/actix_web_seller.rs @@ -0,0 +1,236 @@ +use actix_web::{ + body::MessageBody, + dev::{ServiceRequest, ServiceResponse}, + middleware::{self, Next}, + web, App, Error, HttpMessage, HttpResponse, HttpServer, +}; +use alloy::primitives::address; +use serde_json::json; +use solana_pubkey::pubkey; +use url::Url; +use url_macro::url; +use x402_kit::{ + core::Resource, + facilitator_client::{FacilitatorClient, StandardFacilitatorClient}, + networks::{evm::assets::UsdcBaseSepolia, svm::assets::UsdcSolanaDevnet}, + paywall::{paywall::PayWall, processor::PaymentState}, + schemes::{exact_evm::ExactEvm, exact_svm::ExactSvm}, + transport::Accepts, +}; + +struct AppState { + facilitator: StandardFacilitatorClient, +} + +async fn standard_paywall( + req: ServiceRequest, + next: Next, +) -> Result, Error> { + let state = req + .app_data::>() + .expect("AppState not configured") + .clone(); + + let (http_req, payload) = req.into_parts(); + let http_req_clone = http_req.clone(); + + let paywall = PayWall::builder() + .facilitator(state.facilitator.clone()) + .accepts( + ExactEvm::builder() + .amount(1000) + .asset(UsdcBaseSepolia) + .pay_to(address!("0x3CB9B3bBfde8501f411bB69Ad3DC07908ED0dE20")) + .build(), + ) + .resource( + Resource::builder() + .url(url!("https://example.com/resource/standard")) + .description("X402 payment protected resource") + .mime_type("application/json") + .build(), + ) + .build(); + + // Run the paywall + let response = paywall + .handle_payment(http_req, |http_req| async move { + let srv_req = ServiceRequest::from_parts(http_req, payload); + match next.call(srv_req).await { + Ok(res) => res.map_into_boxed_body().into_parts().1, + Err(err) => err.error_response().map_into_boxed_body(), + } + }) + .await + .map_err(Error::from)?; + + Ok(ServiceResponse::new(http_req_clone, response)) +} + +async fn custom_paywall( + req: ServiceRequest, + next: Next, +) -> Result, Error> { + let state = req + .app_data::>() + .expect("AppState not configured") + .clone(); + + let (http_req, payload) = req.into_parts(); + let http_req_clone = http_req.clone(); + + let paywall = PayWall::builder() + .facilitator(state.facilitator.clone()) + .accepts( + ExactEvm::builder() + .amount(1000) + .asset(UsdcBaseSepolia) + .pay_to(address!("0x3CB9B3bBfde8501f411bB69Ad3DC07908ED0dE20")) + .build(), + ) + .resource( + Resource::builder() + .url(url!("https://example.com/resource/custom")) + .description("X402 payment protected resource") + .mime_type("application/json") + .build(), + ) + .build(); + + // Skip updating accepts from facilitator, skip verifying, and settle payment before running handler + let response = paywall + .process_request(http_req) + .map_err(Error::from)? + .settle() + .await + .map_err(Error::from)? + .run_handler(|http_req| async move { + let srv_req = ServiceRequest::from_parts(http_req, payload); + match next.call(srv_req).await { + Ok(res) => res.map_into_boxed_body().into_parts().1, + Err(err) => err.error_response().map_into_boxed_body(), + } + }) + .await + .map_err(Error::from)? + .response(); + + Ok(ServiceResponse::new(http_req_clone, response)) +} + +async fn multi_payments_paywall( + req: ServiceRequest, + next: Next, +) -> Result, Error> { + let state = req + .app_data::>() + .expect("AppState not configured") + .clone(); + + let (http_req, payload) = req.into_parts(); + let http_req_clone = http_req.clone(); + + let paywall = PayWall::builder() + .facilitator(state.facilitator.clone()) + .accepts( + Accepts::new() + .push( + ExactEvm::builder() + .amount(1000) + .asset(UsdcBaseSepolia) + .pay_to(address!("0x3CB9B3bBfde8501f411bB69Ad3DC07908ED0dE20")) + .build(), + ) + .push( + ExactSvm::builder() + .amount(1000) + .asset(UsdcSolanaDevnet) + .pay_to(pubkey!("Ge3jkza5KRfXvaq3GELNLh6V1pjjdEKNpEdGXJgjjKUR")) + .build(), + ), + ) + .resource( + Resource::builder() + .url(url!("https://example.com/resource/standard")) + .description("X402 payment protected resource") + .mime_type("application/json") + .build(), + ) + .build(); + + // Run the paywall + let response = paywall + .handle_payment(http_req, |http_req| async move { + let srv_req = ServiceRequest::from_parts(http_req, payload); + match next.call(srv_req).await { + Ok(res) => res.map_into_boxed_body().into_parts().1, + Err(err) => err.error_response().map_into_boxed_body(), + } + }) + .await + .map_err(Error::from)?; + + Ok(ServiceResponse::new(http_req_clone, response)) +} + +/// Example handler for a protected resource. +/// +/// The `PayWall` middleware will inject the `PaymentState` into the request extensions. +async fn example_handler(req: actix_web::HttpRequest) -> HttpResponse { + let extensions = req.extensions(); + let payment_state = extensions.get::(); + + HttpResponse::Ok().json(json!({ + "message": "You have accessed a protected resource!", + "verify_state": payment_state + .and_then(|ps| serde_json::to_value(&ps.verified).ok()) + .unwrap_or(json!(null)), + "settle_state": payment_state + .and_then(|ps| serde_json::to_value(&ps.settled).ok()) + .unwrap_or(json!(null)), + })) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + tracing_subscriber::fmt::init(); + + let facilitator_url = std::env::var("FACILITATOR_URL") + .expect("Please set `FACILITATOR_URL` in environment variables"); + let facilitator_url = + Url::parse(&facilitator_url).expect("FACILITATOR_URL must be a valid URL"); + tracing::info!("Using facilitator at {}", facilitator_url); + let facilitator = FacilitatorClient::from_url(facilitator_url); + + let port = std::env::var("PORT") + .unwrap_or_else(|_| "3000".to_string()) + .parse::() + .expect("PORT must be a valid u16 integer"); + + tracing::info!("Starting server on 0.0.0.0:{}", port); + + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(AppState { + facilitator: facilitator.clone(), + })) + .service( + web::resource("/resource/standard") + .wrap(middleware::from_fn(standard_paywall)) + .route(web::post().to(example_handler)), + ) + .service( + web::resource("/resource/custom") + .wrap(middleware::from_fn(custom_paywall)) + .route(web::post().to(example_handler)), + ) + .service( + web::resource("/resource/multi_payments") + .wrap(middleware::from_fn(multi_payments_paywall)) + .route(web::post().to(example_handler)), + ) + }) + .bind(("0.0.0.0", port))? + .run() + .await +} From c04071c730d2d4477c36bc04e7ef6af02b72429f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:32:29 +0000 Subject: [PATCH 3/4] Fix resource URL for multi_payments_paywall in actix-web example Co-authored-by: takasaki404 <193405421+takasaki404@users.noreply.github.com> --- x402-kit/examples/actix_web_seller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x402-kit/examples/actix_web_seller.rs b/x402-kit/examples/actix_web_seller.rs index c68395a..23a661b 100644 --- a/x402-kit/examples/actix_web_seller.rs +++ b/x402-kit/examples/actix_web_seller.rs @@ -151,7 +151,7 @@ async fn multi_payments_paywall( ) .resource( Resource::builder() - .url(url!("https://example.com/resource/standard")) + .url(url!("https://example.com/resource/multi_payments")) .description("X402 payment protected resource") .mime_type("application/json") .build(), From 5a4d36d10457dc4567650f88a5ee6b727d9f4da9 Mon Sep 17 00:00:00 2001 From: takasaki404 Date: Tue, 24 Feb 2026 23:05:57 +0800 Subject: [PATCH 4/4] chore(style): fix code format --- x402-kit/examples/actix_web_seller.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x402-kit/examples/actix_web_seller.rs b/x402-kit/examples/actix_web_seller.rs index 23a661b..e45e072 100644 --- a/x402-kit/examples/actix_web_seller.rs +++ b/x402-kit/examples/actix_web_seller.rs @@ -1,8 +1,9 @@ use actix_web::{ + App, Error, HttpMessage, HttpResponse, HttpServer, body::MessageBody, dev::{ServiceRequest, ServiceResponse}, middleware::{self, Next}, - web, App, Error, HttpMessage, HttpResponse, HttpServer, + web, }; use alloy::primitives::address; use serde_json::json;