{"version":3,"file":"index.cjs","names":["Utils","PublicKey","Beef"],"sources":["../../src/index.ts"],"sourcesContent":["import { Beef, createNonce, PublicKey, Utils, verifyNonce, type AtomicBEEF } from '@bsv/sdk'\nimport type { RequestHandler, Response } from 'express'\nimport type {\n  BSVPayment,\n  PaymentMiddlewareOptions,\n  PaymentReplayStore,\n  PaymentRequest\n} from './types.js'\n\nconst PAYMENT_VERSION = '1.0'\nconst DEFAULT_MAX_PAYMENT_HEADER_BYTES = 64 * 1024\nconst DEFAULT_REPLAY_CAPACITY = 100_000\nconst MAX_NONCE_LENGTH = 512\n\ninterface ParsedPayment {\n  payment: BSVPayment\n  transaction: AtomicBEEF\n  transactionId: string\n  satoshis: number\n}\n\ninterface InternalizeResult {\n  accepted?: boolean\n  isMerge?: boolean\n}\n\nexport class InMemoryPaymentReplayStore implements PaymentReplayStore {\n  private readonly claimed = new Set<string>()\n\n  constructor(private readonly maxEntries: number = DEFAULT_REPLAY_CAPACITY) {\n    if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {\n      throw new RangeError('Replay-store capacity must be a positive safe integer.')\n    }\n  }\n\n  claim(transactionId: string): boolean {\n    if (this.claimed.has(transactionId)) return false\n    if (this.claimed.size >= this.maxEntries) {\n      throw new Error('Payment replay store capacity exceeded.')\n    }\n    this.claimed.add(transactionId)\n    return true\n  }\n}\n\nfunction isPositiveSafeInteger(value: number): boolean {\n  return Number.isSafeInteger(value) && value > 0\n}\n\nfunction isCanonicalBase64(value: string): boolean {\n  if (\n    value.length === 0 ||\n    !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)\n  ) {\n    return false\n  }\n  try {\n    return Utils.toBase64(Utils.toArray(value, 'base64')) === value\n  } catch {\n    return false\n  }\n}\n\nfunction isCompressedPublicKey(value: string): boolean {\n  if (!/^(02|03)[0-9a-fA-F]{64}$/.test(value)) return false\n  try {\n    return PublicKey.fromString(value).toString() === value.toLowerCase()\n  } catch {\n    return false\n  }\n}\n\nfunction paymentHeader(req: PaymentRequest): string | null | undefined {\n  const value = req.headers['x-bsv-payment']\n  if (value === undefined) return undefined\n  return typeof value === 'string' ? value : null\n}\n\nfunction parsePaymentHeader(raw: string, maxBytes: number): BSVPayment | undefined {\n  if (Buffer.byteLength(raw, 'utf8') > maxBytes) return undefined\n  let value: unknown\n  try {\n    value = JSON.parse(raw)\n  } catch {\n    return undefined\n  }\n  if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined\n  const record = value as Record<string, unknown>\n  if (\n    typeof record.derivationPrefix !== 'string' ||\n    typeof record.derivationSuffix !== 'string' ||\n    typeof record.transaction !== 'string' ||\n    record.derivationPrefix.length > MAX_NONCE_LENGTH ||\n    record.derivationSuffix.length > MAX_NONCE_LENGTH ||\n    !isCanonicalBase64(record.derivationPrefix) ||\n    !isCanonicalBase64(record.derivationSuffix) ||\n    !isCanonicalBase64(record.transaction)\n  ) {\n    return undefined\n  }\n  return {\n    derivationPrefix: record.derivationPrefix,\n    derivationSuffix: record.derivationSuffix,\n    transaction: record.transaction\n  }\n}\n\nfunction parseAtomicPayment(\n  payment: BSVPayment,\n  requiredSatoshis: number\n): ParsedPayment | undefined {\n  try {\n    const transaction = Utils.toArray(payment.transaction, 'base64') as AtomicBEEF\n    const beef = Beef.fromBinary(transaction)\n    const transactionId = beef.atomicTxid\n    if (typeof transactionId !== 'string') return undefined\n    const atomicTransaction = beef.findTxid(transactionId)?.tx\n    const satoshis = atomicTransaction?.outputs[0]?.satoshis\n    if (typeof satoshis !== 'number' || satoshis < requiredSatoshis) return undefined\n    return { payment, transaction, transactionId, satoshis }\n  } catch {\n    return undefined\n  }\n}\n\nfunction sendError(\n  res: Response,\n  status: number,\n  code: string,\n  description: string,\n  details: Record<string, unknown> = {}\n): void {\n  res.status(status).json({\n    status: 'error',\n    code,\n    ...details,\n    description\n  })\n}\n\nfunction safeErrorContext(error: unknown): Record<string, unknown> {\n  return error instanceof Error ? { errorName: error.name } : { errorType: typeof error }\n}\n\nfunction isPaymentLogger(value: unknown): boolean {\n  if (value === undefined) return true\n  if (value === null || typeof value !== 'object') return false\n  const logger = value as Record<string, unknown>\n  return (\n    (logger.error === undefined || typeof logger.error === 'function') &&\n    (logger.warn === undefined || typeof logger.warn === 'function')\n  )\n}\n\nasync function issuePaymentChallenge(\n  wallet: PaymentMiddlewareOptions['wallet'],\n  res: Response,\n  requestPrice: number,\n  logger: PaymentMiddlewareOptions['logger']\n): Promise<void> {\n  try {\n    const derivationPrefix = await createNonce(wallet)\n    res\n      .status(402)\n      .set({\n        'x-bsv-payment-version': PAYMENT_VERSION,\n        'x-bsv-payment-satoshis-required': String(requestPrice),\n        'x-bsv-payment-derivation-prefix': derivationPrefix\n      })\n      .json({\n        status: 'error',\n        code: 'ERR_PAYMENT_REQUIRED',\n        satoshisRequired: requestPrice,\n        description: 'A BSV payment is required. Provide the X-BSV-Payment header.'\n      })\n  } catch (error) {\n    logger?.error?.('Failed to create a payment challenge.', safeErrorContext(error))\n    sendError(res, 503, 'ERR_PAYMENT_UNAVAILABLE', 'Payment processing is temporarily unavailable.')\n  }\n}\n\n/**\n * Creates middleware that enforces a BRC-29 wallet payment after BRC-103 auth.\n */\nexport function createPaymentMiddleware(options: PaymentMiddlewareOptions): RequestHandler {\n  if (options === null || typeof options !== 'object') {\n    throw new TypeError('Payment middleware options are required.')\n  }\n\n  const {\n    calculateRequestPrice = () => 100,\n    wallet,\n    replayStore = new InMemoryPaymentReplayStore(),\n    maxPaymentHeaderBytes = DEFAULT_MAX_PAYMENT_HEADER_BYTES,\n    logger\n  } = options\n\n  if (typeof calculateRequestPrice !== 'function') {\n    throw new TypeError('The calculateRequestPrice option must be a function.')\n  }\n  if (\n    wallet === null ||\n    typeof wallet !== 'object' ||\n    typeof wallet.internalizeAction !== 'function'\n  ) {\n    throw new TypeError('A valid wallet instance must be supplied to the payment middleware.')\n  }\n  if (replayStore === null || typeof replayStore.claim !== 'function') {\n    throw new TypeError('A replay store with an atomic claim method is required.')\n  }\n  if (!Number.isSafeInteger(maxPaymentHeaderBytes) || maxPaymentHeaderBytes < 1) {\n    throw new RangeError('maxPaymentHeaderBytes must be a positive safe integer.')\n  }\n  if (!isPaymentLogger(logger)) {\n    throw new TypeError('logger error and warn properties must be functions when provided.')\n  }\n\n  return async (req, res, next): Promise<void> => {\n    const paymentRequest: PaymentRequest = req\n    const identityKey = paymentRequest.auth?.identityKey\n    if (typeof identityKey !== 'string' || !isCompressedPublicKey(identityKey)) {\n      sendError(\n        res,\n        500,\n        'ERR_SERVER_MISCONFIGURED',\n        'The payment middleware must run after successful Auth middleware.'\n      )\n      return\n    }\n\n    let requestPrice: number\n    try {\n      requestPrice = await calculateRequestPrice(paymentRequest)\n    } catch (error) {\n      logger?.error?.('Payment pricing failed.', safeErrorContext(error))\n      sendError(\n        res,\n        500,\n        'ERR_PAYMENT_INTERNAL',\n        'An internal error occurred while determining the payment required for this request.'\n      )\n      return\n    }\n\n    if (requestPrice === 0) {\n      paymentRequest.payment = { satoshisPaid: 0, accepted: true, tx: '', txid: '' }\n      next()\n      return\n    }\n    if (!isPositiveSafeInteger(requestPrice)) {\n      logger?.error?.('Payment pricing returned an invalid value.', { requestPrice })\n      sendError(res, 500, 'ERR_PAYMENT_INTERNAL', 'The configured payment price is invalid.')\n      return\n    }\n\n    const rawPayment = paymentHeader(paymentRequest)\n    if (rawPayment === undefined) {\n      await issuePaymentChallenge(wallet, res, requestPrice, logger)\n      return\n    }\n\n    if (rawPayment === null) {\n      sendError(res, 400, 'ERR_MALFORMED_PAYMENT', 'The X-BSV-Payment header is malformed.')\n      return\n    }\n\n    const payment = parsePaymentHeader(rawPayment, maxPaymentHeaderBytes)\n    if (payment === undefined) {\n      sendError(res, 400, 'ERR_MALFORMED_PAYMENT', 'The X-BSV-Payment header is malformed.')\n      return\n    }\n\n    let validPrefix = false\n    try {\n      validPrefix = await verifyNonce(payment.derivationPrefix, wallet)\n    } catch (error) {\n      logger?.warn?.('Payment derivation-prefix verification failed.', safeErrorContext(error))\n    }\n    if (!validPrefix) {\n      sendError(\n        res,\n        400,\n        'ERR_INVALID_DERIVATION_PREFIX',\n        'The payment derivation prefix is invalid.'\n      )\n      return\n    }\n\n    const parsed = parseAtomicPayment(payment, requestPrice)\n    if (parsed === undefined) {\n      sendError(\n        res,\n        400,\n        'ERR_INVALID_PAYMENT',\n        'The payment transaction is invalid or does not cover the required amount.'\n      )\n      return\n    }\n\n    let claimed: boolean\n    try {\n      const claimResult: unknown = await replayStore.claim(parsed.transactionId)\n      if (typeof claimResult !== 'boolean') {\n        throw new TypeError('The replay store returned an invalid claim result.')\n      }\n      claimed = claimResult\n    } catch (error) {\n      logger?.error?.('Payment replay claim failed.', safeErrorContext(error))\n      sendError(\n        res,\n        503,\n        'ERR_PAYMENT_UNAVAILABLE',\n        'Payment processing is temporarily unavailable.'\n      )\n      return\n    }\n    if (!claimed) {\n      sendError(res, 409, 'ERR_PAYMENT_REPLAYED', 'This payment was already used.')\n      return\n    }\n\n    try {\n      const result = (await wallet.internalizeAction({\n        tx: parsed.transaction,\n        outputs: [\n          {\n            paymentRemittance: {\n              derivationPrefix: payment.derivationPrefix,\n              derivationSuffix: payment.derivationSuffix,\n              senderIdentityKey: identityKey\n            },\n            outputIndex: 0,\n            protocol: 'wallet payment'\n          }\n        ],\n        description: 'Payment for request'\n      })) as InternalizeResult\n\n      if (result.accepted !== true || result.isMerge === true) {\n        sendError(res, 409, 'ERR_PAYMENT_REPLAYED', 'This payment was not newly accepted.')\n        return\n      }\n\n      paymentRequest.payment = {\n        satoshisPaid: parsed.satoshis,\n        accepted: true,\n        tx: payment.transaction,\n        txid: parsed.transactionId\n      }\n      res.set({\n        'x-bsv-payment-satoshis-paid': String(parsed.satoshis)\n      })\n      next()\n    } catch (error) {\n      logger?.warn?.('Payment internalization failed.', safeErrorContext(error))\n      sendError(res, 400, 'ERR_PAYMENT_FAILED', 'The payment could not be accepted.')\n    }\n  }\n}\n"],"mappings":"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"}