@bsv/payment-express-middleware v1.0.6
@bsv/payment-express-middleware
Accept BSV micropayments in your Express.js API by seamlessly integrating 402 Payment Required flows with BRC-103 and BRC-104 mutual authentication. This middleware builds upon the Auth middleware—thus letting you identify and authenticate the payer before receiving BSV to monetize your services.
- Monetize your APIs via BSV micropayments.
- Automatically handle
402 Payment Requiredlogic by providing the amount owed, plus a derivation prefix for the payer to build the transaction. - Integrates seamlessly after the BRC-103 Auth middleware to ensure the user’s identity is established.
- Extensible pricing logic via a user-defined function.
Table of Contents
- Background
- Features
- Installation
- Pre-requisites
- Quick Start
- Detailed Usage
- API Reference
- Example Payment Flows
- Security Considerations
- Resources & References
- License
Background
The BRC-103 authentication framework and its BRC-104 HTTP transport extension provide mutual authentication and selective field disclosure. Building on top of these, we can now monetize interactions by requiring micropayments for certain requests. By layering a Payment Middleware after the Auth middleware, your service can signal “402 Payment Required” to the client, prompting them to respond with a BSV transaction that pays for that request.
Features
- Simple 402 Payment Flows: Easily return a
402status if payment is required. - Configurable Pricing: Provide a
calculateRequestPricefunction to dynamically determine how many satoshis are owed for each request. - Nonce-Based: Uses a derivation prefix to ensure the final payment is bound to your session, preventing replay attacks.
- Auth Integration: Leverages the user’s identity key from the preceding Auth middleware to track who is paying.
- Automatic Transaction Handling: On the server side, calls your
walletinstance’sinternalizeAction()to process the transaction.
Installation
npm i @bsv/payment-express-middlewareNote: You must also have @bsv/auth-express-middleware installed and set up before using this payment middleware.
Pre-requisites
BRC-103 / BRC-104–based Auth Middleware
You must install and configure@bsv/auth-express-middlewarefirst. This ensures every request has a validreq.auth.identityKey.BSV Wallet
A wallet capable of receiving, verifying, and broadcasting transactions. This middleware leverages the standard, bRC-100wallet.internalizeAction()to handle submitting the payment transaction.- This can be your own custom wallet logic that implements these interfaces.
- The wallet should also be able to verify that the
derivationPrefixandderivationSuffixproperly correspond to keys in the output script, as per BRC-29. - If you use the wallet implementation from
@bsv/sdk, these details are handled automatically.
Client with 402 Support
On the client side, you need a user agent (e.g., AuthFetch from@bsv/sdk, or a custom approach) that automatically responds to402challenges by constructing a BSV transaction to make the payment.
Quick Start
Below is the minimal example integrating the payment middleware with the Auth middleware:
import express from 'express'
import bodyParser from 'body-parser'
import { createAuthMiddleware } from '@bsv/auth-express-middleware'
import { createPaymentMiddleware } from '@bsv/payment-express-middleware'
import { Wallet } from '@your/bsv-wallet'
// 1. Create a BSV wallet that can manage transactions
const wallet = new Wallet({ /* config */ })
// 2. Create the Auth middleware (BRC-103/104)
const authMiddleware = createAuthMiddleware({ wallet })
// 3. Create the Payment middleware
const paymentMiddleware = createPaymentMiddleware({
wallet,
calculateRequestPrice: async (req) => {
// e.g., 50 satoshis per request
return 50
}
})
const app = express()
app.use(bodyParser.json())
// 4. Place Auth middleware first, then Payment middleware
app.use(authMiddleware)
app.use(paymentMiddleware)
// 5. Define your routes as normal
app.post('/somePaidEndpoint', (req, res) => {
// If we got here, the request is authenticated and the payment (if required) was accepted.
res.json({ message: 'Payment received, request authorized', amount: req.payment.satoshisPaid })
})
app.listen(3000, () => {
console.log('Payment-enabled server is listening on port 3000')
})In this setup:
Auth middlewareensuresreq.authis set.Payment middlewarechecks if payment is required (based oncalculateRequestPrice). If yes, the client must supply a validx-bsv-paymentheader with a BSV transaction referencing the specified derivation prefix. Otherwise, the middleware returns a402 Payment Requiredresponse, prompting the client to pay.
Detailed Usage
Creating the Payment Middleware
import { createPaymentMiddleware } from '@bsv/payment-express-middleware'
const paymentMiddleware = createPaymentMiddleware({
wallet: myWallet,
calculateRequestPrice: (req) => {
// Your logic to return satoshis required for this request
return 100 // e.g. 100 satoshis
}
})Options:
wallet(required): A wallet object that can process and broadcast BSV transactions. Must expose aninternalizeActionmethod.calculateRequestPrice(optional): A function(req) => number | Promise<number>that returns how many satoshis the request should cost. Defaults to100.
Installing the Payment Middleware in Express
- Order: Must run after the Auth middleware.
Usage:
app.use(authMiddleware) // from @bsv/auth-express-middleware app.use(paymentMiddleware) // from @bsv/payment-express-middlewareEffects:
- On each request, it first checks
req.auth.identityKey. If undefined, returns an error (the Payment middleware requires you to be authenticated). - Determines the price. If
0, no payment is required—proceeds immediately. - Otherwise, checks the
x-bsv-paymentheader from the client. - If the header is missing or invalid, responds with
402 Payment Requiredalong with thex-bsv-payment-satoshis-requiredand a nonce inx-bsv-payment-derivation-prefix. - If the header is present, tries to finalize the transaction via
wallet.internalizeAction(). - On success, sets
req.paymentwith the transaction details and callsnext().
- On each request, it first checks
Custom Pricing Logic
You can define any logic for calculating the cost of each request, such as:
- A flat fee for all requests (
return 100) - Per-endpoint pricing
- Different costs based on request size or complexity
- Free requests (return
0) for certain routes or conditions
const paymentMiddleware = createPaymentMiddleware({
wallet,
calculateRequestPrice: async (req) => {
if (req.path === '/premium') return 500 // cost 500 satoshis
return 0 // free for everything else
}
})Detailed Flow
- Authenticated request arrives.
- Payment middleware calls
calculateRequestPrice(req). - If
price = 0, continue. - Else check
x-bsv-paymentheader:- If missing → respond with
402 Payment Required+ nonce (derivation prefix). - If present → parse JSON, verify the nonce, call
wallet.internalizeAction().- If successful, sets
req.payment.satoshisPaid = price. - Continue to your route handler.
- If successful, sets
- If missing → respond with
API Reference
createPaymentMiddleware(options: PaymentMiddlewareOptions)
Returns an Express middleware function that:
- Checks for
req.auth.identityKey. - Calculates the request’s price.
- Enforces payment via
x-bsv-paymentifprice > 0. - On success, attaches
req.payment = { satoshisPaid, accepted, tx }.
PaymentMiddlewareOptions:
| Property | Type | Required | Description |
|---|---|---|---|
calculateRequestPrice | (req: Request) => number \| Promise<number> | No | Determines how many satoshis are needed to serve the request. Defaults to 100. |
wallet | Wallet | Yes | A wallet instance with a internalizeAction() function to finalize the BSV transaction. |
PaymentMiddleware
Once invoked:
- If
price = 0, setsreq.payment = { satoshisPaid: 0 }and callsnext(). - If
price > 0, requires thex-bsv-paymentheader containing a valid BSV transaction (plus a derivation prefix & suffix). - If successful, sets
req.payment = { satoshisPaid: <price>, accepted: true, tx: <transactionData> }.
Example Payment Flows
0 Satoshis (Free Request)
const paymentMiddleware = createPaymentMiddleware({
wallet,
calculateRequestPrice: () => 0
})
app.use(authMiddleware)
app.use(paymentMiddleware)
// => All requests are free, effectively ignoring payment logic, but the pipeline remains consistent.Paid Request
const paymentMiddleware = createPaymentMiddleware({
wallet,
calculateRequestPrice: async (req) => {
// Example: cost is 100 satoshis unless "POST" method, which costs 200
return req.method === 'POST' ? 200 : 100
}
})When the client tries to call a route, the server may respond with:
{
"status": "error",
"code": "ERR_PAYMENT_REQUIRED",
"satoshisRequired": 200,
"description": "A BSV payment is required to complete this request."
}along with the header:
x-bsv-payment-satoshis-required: 200
x-bsv-payment-derivation-prefix: <random-nonce-base64>The client then constructs a BSV transaction paying the appropriate amount to the server’s wallet, referencing the derivation prefix in the transaction metadata. Once complete, the client re-sends the request including:
"x-bsv-payment": JSON.stringify({
derivationPrefix: <same-derivation-prefix>,
derivationSuffix: <some-other-data>,
transaction: <serialized-tx>
})If accepted, the request proceeds.
Security Considerations
Run after Auth
This middleware relies onreq.auth.identityKeyfrom the preceding BRC-103 authentication. If you skip Auth, the identity is unknown, which can break the payment system.Nonce Handling
Uses aderivationPrefixto ensure each payment is unique to the request context. The library verifies the prefix is bound to the server private key.- You should ensure your wallet is robust to replay attacks, e.g., by only accepting each prefix once inside of
internalizeAction(). - Don't accept the same transaction twice, even if it's still valid! Ensure your wallet throws an error if
internalizeAction()is called with the same payment multiple times.
- You should ensure your wallet is robust to replay attacks, e.g., by only accepting each prefix once inside of
Error Handling
Non-compliant or missingx-bsv-paymentdata results in a4xxerror (often402 Payment Requiredor400 Bad Request).Transaction Acceptance
The final acceptance or rejection of a transaction is performed by yourwallet.internalizeAction(). Ensure your wallet’s logic is secure and robust.
Resources & References
- BRC-103 Spec – Mutual authentication & certificate exchange.
- BRC-104 Spec – HTTP transport for BRC-103.
- @bsv/auth-express-middleware – The prerequisite middleware for authentication.
- BRC-29 key derivation protocol – The specification covering
derivationPrefixandderivationSuffixas related to the exchange of BSV payments. - 402 Payment Required – The HTTP status code used to signal that payment is required.
License
Happy Building! If you have questions, run into issues, or want to contribute improvements, feel free to open issues or PRs in our repository.