Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Running an x402 facilitator

This is the facilitator side: you sponsor transaction fees and settle payments on behalf of a resource server. Facilitator is a set of plain async functions, not a bound HTTP server — you wire /verify, /settle, and /supported to whatever framework you already run. See examples/resource_server_x402.rs for a complete reference implementation using axum.

Set up

#![allow(unused)]
fn main() {
use sorocharge_x402::{Facilitator, FacilitatorConfig};

let facilitator = Facilitator::new(
    &rpc,                                     // &stellar_rpc_client::Client
    &facilitator_signer,                      // your fee-sponsoring key, impl Signer
    "Test SDF Network ; September 2015".to_string(),
    FacilitatorConfig::default(),
);
}

FacilitatorConfig has two fields, both stroop amounts: max_transaction_fee_stroops (default 50_000) is a safety ceiling on the settlement-time simulation-derived fee — a circuit breaker against fee-exhaustion attacks, not a protocol constant, since the spec doesn’t mandate a specific number. inclusion_buffer_stroops (default 100, the spec’s required minimum) is added on top of the simulated resource fee.

Verify, then settle

#![allow(unused)]
fn main() {
let verify_response = facilitator.verify(&verify_request).await?;
if !verify_response.is_valid {
    // respond 402 with verify_response.invalid_reason
}

let settlement = facilitator.settle(&settle_request).await?;
if !settlement.success {
    // respond 402 with settlement.error_reason — "settlement-failed", not
    // "verification-failed": the credential was valid but the on-chain
    // submission didn't go through
}
}

verify and settle share the same validation path internally — settle does not trust a prior verify call, and re-checks everything itself, per the spec’s “/settle MUST perform full verification independently.”

What gets checked

Both calls run, in order: protocol fields (x402Version, scheme, matching network), transaction structure (exactly one invokeHostFunction, a bare transfer(from, to, amount), no sub-invocations), the credential type (legacy or AddressV2 only — never AddressWithDelegates), the facilitator-safety checks (your own address can’t appear as source, operation source, from, or in any authorization entry), signature and expiry verification via sorocharge_signer::verify_entry, and a re-simulation checked with sorocharge_signer::verify_transfer_effects to confirm it moves only the expected balance.

settle additionally rebuilds the transaction with your account as source, derives a fresh fee from a settlement-time simulation (capped by max_transaction_fee_stroops), signs it, submits via sendTransaction, and polls until SUCCESS or FAILED.

#![allow(unused)]
fn main() {
let supported = facilitator.supported();
}

Returns a SupportedResponse naming the exact scheme on whichever network you configured, and your own address as a signer — wire this to a /supported route if your resource servers (or anyone else) need to discover it.