Skip to content

bitcoin-sv/payment-express-middleware

Repository files navigation

@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 Required logic 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

  1. Background
  2. Features
  3. Installation
  4. Pre-requisites
  5. Quick Start
  6. Detailed Usage
  7. API Reference
  8. Example Payment Flows
  9. Security Considerations
  10. Resources & References
  11. 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 402 status if payment is required.
  • Configurable Pricing: Provide a calculateRequestPrice function 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 wallet instance’s internalizeAction() to process the transaction.

Installation

npm i @bsv/payment-express-middleware

Note: You must also have @bsv/auth-express-middleware installed and set up before using this payment middleware.


Pre-requisites

  1. BRC-103 / BRC-104–based Auth Middleware
    You must install and configure @bsv/auth-express-middleware first. This ensures every request has a valid req.auth.identityKey.

  2. BSV Wallet
    A wallet capable of receiving, verifying, and broadcasting transactions. This middleware leverages the standard, bRC-100 wallet.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 derivationPrefix and derivationSuffix properly 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.
  3. 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 to 402 challenges 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 middleware ensures req.auth is set.
  • Payment middleware checks if payment is required (based on calculateRequestPrice). If yes, the client must supply a valid x-bsv-payment header with a BSV transaction referencing the specified derivation prefix. Otherwise, the middleware returns a 402 Payment Required response, 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 an internalizeAction method.
  • calculateRequestPrice (optional): A function (req) => number | Promise<number> that returns how many satoshis the request should cost. Defaults to 100.

Installing the Payment Middleware in Express

  1. Order: Must run after the Auth middleware.

  2. Usage:

    app.use(authMiddleware)       // from @bsv/auth-express-middleware
    app.use(paymentMiddleware)    // from @bsv/payment-express-middleware
  3. Effects:

    • 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-payment header from the client.
    • If the header is missing or invalid, responds with 402 Payment Required along with the x-bsv-payment-satoshis-required and a nonce in x-bsv-payment-derivation-prefix.
    • If the header is present, tries to finalize the transaction via wallet.internalizeAction().
    • On success, sets req.payment with the transaction details and calls next().

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

  1. Authenticated request arrives.
  2. Payment middleware calls calculateRequestPrice(req).
  3. If price = 0, continue.
  4. Else check x-bsv-payment header:
    • 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.

API Reference

createPaymentMiddleware(options: PaymentMiddlewareOptions)

Returns an Express middleware function that:

  1. Checks for req.auth.identityKey.
  2. Calculates the request’s price.
  3. Enforces payment via x-bsv-payment if price > 0.
  4. 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, sets req.payment = { satoshisPaid: 0 } and calls next().
  • If price > 0, requires the x-bsv-payment header 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

  1. Run after Auth
    This middleware relies on req.auth.identityKey from the preceding BRC-103 authentication. If you skip Auth, the identity is unknown, which can break the payment system.

  2. Nonce Handling
    Uses a derivationPrefix to 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.
  3. Error Handling
    Non-compliant or missing x-bsv-payment data results in a 4xx error (often 402 Payment Required or 400 Bad Request).

  4. Transaction Acceptance
    The final acceptance or rejection of a transaction is performed by your wallet.internalizeAction(). Ensure your wallet’s logic is secure and robust.


Resources & References


License

Open BSV 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.

About

BSV Blockchain service monetization express middleware

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published