Skip to content
LogoLogo

Client

Handle 402 responses automatically

The Fetch extension trait adds .send_with_payment() to any reqwest::RequestBuilder—when a server returns 402, it parses the Challenge, signs a stablecoin transfer, and retries with the Credential.

Quick start

use mpp::client::{Fetch, TempoProvider};
use mpp::PrivateKeySigner;
 
let signer = PrivateKeySigner::random();
let provider = TempoProvider::new(signer, "https://rpc.tempo.xyz")?;
 
let response = reqwest::Client::new()
    .get("https://api.example.com/paid")
    .send_with_payment(&provider)
    .await?;
 
println!("Status: {}", response.status());

When the server returns 402, send_with_payment:

  1. Parses the Challenge from the WWW-Authenticate header
  2. Calls provider.pay() to sign a stablecoin transfer
  3. Retries the request with the Credential in the Authorization header

TempoProvider

TempoProvider signs TIP-20 stablecoin transfers on the Tempo blockchain.

use mpp::client::TempoProvider;
use mpp::PrivateKeySigner;
 
let signer: PrivateKeySigner = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
    .parse()?;
 
let provider = TempoProvider::new(signer, "https://rpc.tempo.xyz")?;

With client ID

Attach an identifier to transactions for attribution:

let provider = TempoProvider::new(signer, "https://rpc.tempo.xyz")?
    .with_client_id("my-app");

Middleware

For automatic 402 handling on all requests, use PaymentMiddleware with reqwest-middleware. Requires the middleware feature.

mpp = { version = "0.10.3", features = ["tempo", "client", "middleware"] }
use mpp::client::{PaymentMiddleware, TempoProvider};
use reqwest_middleware::ClientBuilder;
 
let provider = TempoProvider::new(signer, "https://rpc.tempo.xyz")?;
 
let client = ClientBuilder::new(reqwest::Client::new())
    .with(PaymentMiddleware::new(provider))
    .build();
 
// All requests through this client handle 402 automatically
let response = client.get("https://api.example.com/paid").send().await?;

Event handling

Use ClientEvents to record the automatic 402 flow. Use helper methods for named events, on() for one event kind, and on_any() for every client event.

use mpp::client::{
    ClientEvent, ClientEventKind, ClientEvents, PaymentMiddleware, TempoProvider,
};
use reqwest_middleware::ClientBuilder;
 
let provider = TempoProvider::new(signer, "https://rpc.tempo.xyz")?;
let events = ClientEvents::default();
 
// Record the selected Challenge before the provider creates a Credential.
let _challenge = events.on_challenge_received(|ctx| async move {
    println!("challenge received: {}", ctx.challenge.id);
    None
});
 
// Record the retried response after payment.
let _response = events.on(ClientEventKind::PaymentResponse, |event| async move {
    if let ClientEvent::PaymentResponse(ctx) = event {
        println!("payment response: {}", ctx.status);
    }
});
 
// Catch every client payment event in one handler.
let _all = events.on_any(|event| async move {
    println!("payment event: {}", event.kind().as_str());
});
 
let client = ClientBuilder::new(reqwest::Client::new())
    .with(PaymentMiddleware::new(provider).with_events(events))
    .build();

on_challenge_received can return Some(PaymentCredential) to override the default provider payment flow. Other handlers only record payment handling. Keep the returned subscription handles alive while handlers remain registered.

Multiple providers

MultiProvider wraps multiple payment providers and picks the right one based on the Challenge's method and intent:

use mpp::client::{MultiProvider, TempoProvider};
 
let provider = MultiProvider::new()
    .with(TempoProvider::new(signer, "https://rpc.tempo.xyz")?);
 
let response = reqwest::Client::new()
    .get("https://api.example.com/paid")
    .send_with_payment(&provider)
    .await?;

Custom provider

Implement the PaymentProvider trait to add support for custom payment methods:

use mpp::client::PaymentProvider;
use mpp::{PaymentChallenge, PaymentCredential, PaymentPayload, MppError};
 
#[derive(Clone)]
struct MyProvider;
 
impl PaymentProvider for MyProvider {
    fn supports(&self, method: &str, intent: &str) -> bool {
        method == "my_network" && intent == "charge"
    }
 
    async fn pay(
        &self,
        challenge: &PaymentChallenge,
    ) -> Result<PaymentCredential, MppError> {
        let echo = challenge.to_echo();
        Ok(PaymentCredential::new(echo, PaymentPayload::hash("0x...")))
    }
}

Key types

TypeDescription
Fetch (PaymentExt)Extension trait that adds send_with_payment to reqwest::RequestBuilder
MultiProviderWraps multiple providers, routes by method and intent
PaymentMiddlewarereqwest-middleware for automatic 402 handling
PaymentProviderTrait for custom payment method implementations
TempoProviderSigns Tempo stablecoin transfers