bsv-payment-actix-middleware
BSV payment middleware for Actix-web, implementing the BRC-103 mutual authentication framework payment layer. Wire-compatible with the TypeScript payment-express-middleware, enabling Rust and Node.js servers to enforce micropayments using the same protocol.
Part of the BSV mutual authentication stack defined by BRC-103, BRC-104, and BRC-29.
Table of Contents
- Background
- Features
- Installation
- Pre-requisites
- Quick Start
- Detailed Usage
- API Reference
- Example Payment Flows
- Security Considerations
- Resources and References
- License
Background
The BSV mutual authentication framework (BRC-103) defines a layered approach to server-to-client communication:
- Authentication layer -- verifies the caller's identity using signed request headers and key derivation (BRC-29).
- Payment layer -- enforces micropayment requirements on protected routes, returning 402 Payment Required with a nonce when no payment is attached, and verifying/internalizing transactions when a payment header is present.
This crate implements the payment layer as Actix-web middleware. It runs after the authentication middleware and uses the authenticated caller's identity key for key derivation during transaction internalization.
Features
- 402 Payment Required flows -- automatic challenge/response when a request
lacks the
X-BSV-Paymentheader. - Configurable pricing -- set a default satoshi amount or provide an async callback that prices each request individually.
- Nonce-based security -- derivation prefix nonces are created and verified via the wallet's HMAC operations, preventing replay attacks.
- Auth middleware integration -- reads the authenticated identity from
request extensions (or via the optional
bridgefeature). - Automatic transaction internalization -- parses the payment header,
decodes the base64 transaction, and calls
wallet.internalize_action(). - Zero-price passthrough -- requests priced at 0 satoshis skip payment enforcement entirely.
- Panic safety -- all wallet calls are wrapped in
catch_unwindto guard against panics in the underlying BSV SDK. - Wire compatibility -- JSON error shapes, header names, and camelCase field names match the TypeScript implementation exactly.
Installation
Add the crate to your project:
To enable the auth-to-payment bridge (recommended when using
bsv-auth-actix-middleware in the same application):
Pre-requisites
-
Auth middleware -- the payment middleware must run after an authentication middleware that inserts an
AuthIdentityinto request extensions. Use bsv-auth-actix-middleware with thebridgefeature, or insertAuthIdentitymanually. -
BSV wallet -- a type implementing
bsv::wallet::interfaces::WalletInterfacefrom the bsv-sdk crate. The wallet is used for nonce HMAC operations and transaction internalization. -
Client with 402 support -- the calling client must understand 402 responses, read the
x-bsv-payment-derivation-prefixnonce header, create a payment transaction, and resend the request with theX-BSV-Paymentheader.
Quick Start
use ;
use ;
// Your wallet type implementing WalletInterface
// use my_app::MyWallet;
async
async
Detailed Usage
Configuration with the Builder
PaymentMiddlewareConfigBuilder provides a fluent API for constructing the
middleware configuration:
use ;
use Arc;
let config = new
.wallet
.calculate_request_price
.build
.expect;
Builder methods:
| Method | Description |
|---|---|
wallet(W) |
Required. Set the wallet instance. |
calculate_request_price(cb) |
Optional async callback returning the price in satoshis. |
build() |
Consume the builder and return Result<Config, Error>. |
When no calculate_request_price callback is set, the middleware defaults to
DEFAULT_SATOSHIS (100).
Middleware Registration
Register the middleware with App::wrap() or Scope::wrap():
use ;
use PaymentMiddlewareFactory;
new
.wrap
.route;
Actix-web processes middleware in reverse registration order. Place
PaymentMiddlewareFactory before your auth middleware in the wrap chain so
that auth runs first at request time.
Custom Pricing with CalculateRequestPrice
The CalculateRequestPrice type alias is a boxed async closure:
type CalculateRequestPrice = ;
Return Ok(0) to allow a request through without payment. Return an error to
trigger a 500 response with ERR_PAYMENT_INTERNAL.
Extracting Payment Info in Handlers
After the middleware processes a payment, it inserts PaymentInfo into the
request extensions. Extract it in your handler using Actix-web's FromRequest:
use PaymentInfo;
async
Payment Flow (Detailed)
- The middleware reads
AuthIdentityfrom request extensions. - It calls the pricing callback (or uses
DEFAULT_SATOSHIS). - If the price is 0, the request passes through with a zero-value
PaymentInfo. - If no
X-BSV-Paymentheader is present, the middleware:- Creates a nonce via
create_nonce(). - Returns a 402 Payment Required response with headers:
x-bsv-payment-version-- protocol versionx-bsv-payment-satoshis-required-- pricex-bsv-payment-derivation-prefix-- nonce for key derivation
- Creates a nonce via
- If the
X-BSV-Paymentheader is present, the middleware:- Parses the JSON header into a
BSVPaymentstruct. - Verifies the nonce via
verify_nonce(). - Decodes the base64 transaction.
- Calls
wallet.internalize_action()to accept the payment. - Inserts
PaymentInfointo request extensions. - Adds
x-bsv-payment-satoshis-paidto the response headers. - Forwards the request to the inner handler.
- Parses the JSON header into a
API Reference
See API.md for the complete public API reference documenting all types, traits, and constants.
Online documentation is available at docs.rs/bsv-payment-actix-middleware.
Example Payment Flows
Zero-Price Passthrough
When the pricing callback returns 0, the middleware inserts a zero-value
PaymentInfo and forwards the request immediately. No 402 challenge is issued.
Client Server
| |
| GET /api/free |
|------------------------------->|
| | price = 0, skip payment
| 200 OK |
|<-------------------------------|
Paid Request (402 Challenge then Success)
Client Server
| |
| GET /api/paid |
|------------------------------->|
| | No X-BSV-Payment header
| 402 Payment Required |
| x-bsv-payment-version: 1.0 |
| x-bsv-payment-satoshis-required: 100
| x-bsv-payment-derivation-prefix: <nonce>
|<-------------------------------|
| |
| (Client creates payment tx) |
| |
| GET /api/paid |
| X-BSV-Payment: {"derivationPrefix":"<nonce>",
| "derivationSuffix":"<suffix>",
| "transaction":"<base64-tx>"}|
|------------------------------->|
| | Verify nonce
| | Internalize transaction
| 200 OK |
| x-bsv-payment-satoshis-paid: 100
|<-------------------------------|
Security Considerations
-
Run after auth middleware. The payment middleware reads the caller's identity key from request extensions. Without a preceding auth middleware, requests will receive a 500
ERR_SERVER_MISCONFIGUREDerror. -
Nonce handling. Derivation prefix nonces are created and verified using the wallet's HMAC operations. Each nonce is single-use. Replaying an old nonce will result in a 400
ERR_INVALID_DERIVATION_PREFIXerror. -
Error handling. All wallet calls are wrapped in
catch_unwindto prevent panics in the BSV SDK from crashing the server. Panics are logged and converted to appropriate error responses. -
Transaction acceptance. The
acceptedfield inPaymentInforeflects the wallet's internalization result. Handlers should check this field if they need to verify the payment was fully accepted before serving premium content.
Resources and References
- BRC-103: Mutual Authentication -- the overarching mutual auth framework
- BRC-104: HTTP Transport for Mutual Authentication -- HTTP header conventions
- BRC-29: Key Derivation -- derivation prefix/suffix scheme
- bsv-auth-actix-middleware -- companion auth middleware crate
- bsv-sdk -- BSV SDK providing WalletInterface
License
MIT