1.0.6 • Published 7 months ago

@bsv/payment-express-middleware v1.0.6

Weekly downloads
-
License
SEE LICENSE IN LI...
Repository
-
Last release
7 months ago

@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:

PropertyTypeRequiredDescription
calculateRequestPrice(req: Request) => number \| Promise<number>NoDetermines how many satoshis are needed to serve the request. Defaults to 100.
walletWalletYesA 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.

1.0.6

7 months ago

1.0.5

7 months ago

1.0.4

8 months ago

1.0.3

8 months ago

1.0.2

8 months ago

1.0.1

10 months ago

1.0.0

10 months ago