run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
165 lines • 5.95 kB
JavaScript
import { secp256k1 } from "@noble/curves/secp256k1.js";
import { parseBindingExtra } from "./binding.js";
import { decodeInvoice } from "./bolt11.js";
import { ASSET, ASSET_TRANSFER_METHOD, Errors, LnbtcError, PAYMENT_FLOW, SCHEME, } from "./constants.js";
const POSITIVE_INTEGER = /^[1-9][0-9]*$/;
const COMPRESSED_PUBKEY = /^0[23][0-9a-f]{64}$/;
/**
* Throws an {@link LnbtcError} with the given reason.
*
* @param reason - Stable reason
* @returns Never returns
*/
export function reject(reason) {
throw new LnbtcError(reason);
}
/**
* Validates the core requirement terms (step 2 of facilitator validation).
*
* @param req - Payment requirements
* @param networks - Supported networks mapped to BOLT11 currency
*/
export function validateCoreTerms(req, networks) {
if (req.scheme !== SCHEME)
reject(Errors.unsupportedScheme);
if (!isSupportedNetwork(req.network, networks))
reject(Errors.unsupportedNetwork);
if (req.asset !== ASSET)
reject(Errors.asset);
if (typeof req.amount !== "string" || !POSITIVE_INTEGER.test(req.amount))
reject(Errors.amount);
if (!Number.isSafeInteger(req.maxTimeoutSeconds) || req.maxTimeoutSeconds <= 0) {
reject(Errors.maxTimeout);
}
if (!isCompressedPubkey(req.payTo))
reject(Errors.payToMalformed);
}
/**
* Validates the transfer method and payment flow of an `extra` object.
*
* @param extra - Requirements `extra`
*/
export function validateMethodAndFlow(extra) {
// Only an omitted method defaults; an explicit value (even null) must be "bolt11".
const method = extra?.assetTransferMethod === undefined ? ASSET_TRANSFER_METHOD : extra.assetTransferMethod;
if (method !== ASSET_TRANSFER_METHOD)
reject(Errors.assetTransferMethod);
if (extra?.paymentFlow !== PAYMENT_FLOW)
reject(Errors.paymentFlow);
}
/**
* Validates every requirement field a client or server checks before paying or
* issuing: core terms, method, flow, and binding fields.
*
* @param req - Payment requirements
* @param networks - Supported networks mapped to BOLT11 currency
* @returns The validated binding fields
*/
export function validateRequirements(req, networks) {
validateCoreTerms(req, networks);
validateMethodAndFlow(req.extra);
return parseBindingExtra(req.extra);
}
/**
* Strictly decodes an invoice and checks it against the requirements: the
* facilitator's step 5, and the client and server pre-payment checks except
* expiry (see {@link checkExpiry}).
*
* @param invoice - BOLT11 invoice text
* @param req - Requirements whose terms the invoice must match
* @param expectedRequestHash - The expected request hash, lowercase hex
* @param networks - Supported networks mapped to BOLT11 currency
* @param options - Time and expiry policy
* @returns The decoded invoice
*/
export function validateInvoice(invoice, req, expectedRequestHash, networks, options) {
if (invoice === undefined || invoice === null || invoice === "")
reject(Errors.invoiceMissing);
if (typeof invoice !== "string")
reject(Errors.invoiceDecodeFailed);
let decoded;
try {
decoded = decodeInvoice(invoice);
}
catch {
reject(Errors.invoiceDecodeFailed);
}
if (decoded.descriptionHashes.length !== 1 || decoded.inlineDescriptionCount !== 0) {
reject(Errors.invoiceDescription);
}
if (decoded.descriptionHashes[0] !== expectedRequestHash)
reject(Errors.invoiceRequestMismatch);
if (decoded.payee !== req.payTo)
reject(Errors.invoicePayeeMismatch);
if (decoded.currency !== networks[req.network])
reject(Errors.invoiceCurrencyMismatch);
if (decoded.amountMsat !== BigInt(req.amount))
reject(Errors.invoiceAmountMismatch);
if (decoded.expirySeconds !== req.maxTimeoutSeconds)
reject(Errors.invoiceExpiryMismatch);
if (decoded.timestamp > options.now + options.skew)
reject(Errors.invoiceCreatedInFuture);
return decoded;
}
/**
* Applies an expiry policy to a decoded invoice.
*
* @param invoice - Decoded invoice
* @param options - Validation time and skew
* @param policy - `unexpired`: the invoice must not have expired at `now`
* (client, server). `settlement`: the paid-but-expired window, valid through
* `invoice_end + skew` inclusive (facilitator).
*/
export function checkExpiry(invoice, options, policy) {
const end = invoice.timestamp + invoice.expirySeconds;
const expired = policy === "settlement" ? options.now > end + options.skew : options.now >= end;
if (expired)
reject(Errors.invoiceExpired);
}
/**
* Checks that a network is one of the configured networks (own keys only, so
* names such as `constructor` never match).
*
* @param network - Candidate network
* @param networks - Supported networks mapped to BOLT11 currency
* @returns Whether the network is supported
*/
export function isSupportedNetwork(network, networks) {
return typeof network === "string" && Object.prototype.hasOwnProperty.call(networks, network);
}
/**
* Validates a clock-skew allowance.
*
* @param skew - Seconds
*/
export function validateSkew(skew) {
if (!Number.isSafeInteger(skew) || skew < 0) {
throw new RangeError("clock skew must be a non-negative integer number of seconds");
}
}
/**
* Checks for a lowercase compressed secp256k1 public key on the curve.
*
* @param value - Candidate
* @returns Whether it is valid
*/
export function isCompressedPubkey(value) {
if (typeof value !== "string" || !COMPRESSED_PUBKEY.test(value))
return false;
try {
secp256k1.Point.fromHex(value).assertValidity();
return true;
}
catch {
return false;
}
}
/**
* Current Unix time in whole seconds.
*
* @returns Seconds since the epoch
*/
export function unixNow() {
return Math.floor(Date.now() / 1000);
}
//# sourceMappingURL=validation.js.map