@kaspacom/mpp-provider
v0.1.2
Published
Provider-side Kaspa MPP helpers for API payment middleware.
Readme
@kaspacom/mpp-provider
Provider-side helpers for API operators.
Current starter surface:
- parse and verify
Paymentchannel vouchers - build signed provider manifests for wallet bootstrap
- enforce channel status, sequence, spend, request hash, pricing, and refund safety window checks
- verify payer-signed close/finalize requests
- authorize selected paid routes through a framework-agnostic guard
- protect selected Node/Connect/Express-style routes through drop-in middleware
- require receipt persistence before the application handler serves content
- run settlement/redeem sweeps through an injected provider redeemer
The reference KaspaCom indexer API remains the production implementation for DB storage, receipt rows, settlement authorization storage, and broadcaster jobs.
Guarding a route
The provider package does not depend on Express, Fastify, NestJS, or any one
HTTP framework. For Node/Connect/Express-style servers, use
createProviderHttpMiddleware(). For other frameworks, use
guardProviderRouteCatalog() or authorizeProviderRequest() directly.
import { createProviderHttpMiddleware } from "@kaspacom/mpp-provider";
const apiPayments = createProviderHttpMiddleware({
policies: [
{
networkId: "testnet-10",
method: "GET",
path: "/api/expensive",
pricingId: "api.expensive.v1",
route: "GET /api/expensive",
amountSompi: 300_000,
providerPubkey,
refundSafetyMarginMs: 3_600_000,
},
],
storage,
});
app.use(apiPayments);
app.get("/api/expensive", async (req, res) => {
const payment = req.apiPayment;
res.set(payment.decision.headers).json(await expensiveLookup());
});Routes not listed in policies, or listed with enabled: false, pass through
without a payment challenge. On paid success the middleware sets
Payment-Receipt and stores the full decision on req.apiPayment by default.
Use requestStateKey if your framework needs a different property name.
For custom frameworks:
import { guardProviderRouteCatalog } from "@kaspacom/mpp-provider";
const paidRoutes = [
{
networkId: "testnet-10",
method: "GET",
path: "/api/expensive",
pricingId: "api.expensive.v1",
amountSompi: 300_000,
providerPubkey,
refundSafetyMarginMs: 3_600_000,
},
];
const guarded = await guardProviderRouteCatalog(
req,
{
policies: paidRoutes,
storage,
mapRequest: (req) => ({
method: req.method,
path: req.path,
rawQuery: req.url.split("?")[1] ?? "",
headers: { authorization: req.headers.authorization },
}),
next: async (_req, decision) => {
return {
status: 200,
headers: decision.headers,
body: await expensiveLookup(),
};
},
},
);
if (!guarded.ok) {
res.status(guarded.response.status).set(guarded.response.headers).json(guarded.response.body);
return;
}
res.status(guarded.output.status).set(guarded.output.headers).json(guarded.output.body);The storage adapter must atomically re-check sequence, spend, channel status, and receipt persistence. If persistence fails, the guard fails closed and the handler must not run.
amountSompi is the per-request voucher increment. It can be below the
practical on-chain output floor, for example 300_000 sompi (0.003 KAS),
because the provider stores cumulative voucher totals and settles aggregated
usage later. Keep the channel deposit, reserve, provider payout, and payer
refund outputs sized above the practical on-chain output floor.
Publishing provider bootstrap metadata
Expose a free GET /payments/provider route with createProviderManifest()
or createProviderManifestResponse(). The signed manifest lets wallets
discover the provider key, payout key, voucher domain, channel policy, selected
paid routes, and contract artifact without manual configuration.
import { blake2bHex } from "@kaspacom/mpp-core";
import { createProviderManifestResponse } from "@kaspacom/mpp-provider";
app.get("/payments/provider", async (_req, res) => {
const manifest = await createProviderManifestResponse({
providerId: "example-provider",
networkId: "testnet-10",
providerPubkey,
providerPayoutPubkey,
voucherDomainHash,
artifactUrl: "/payments/artifacts/api-payment-channel-v1.json",
artifactHash: blake2bHex(apiPaymentChannelArtifactJson),
baseUrl: "https://api.example.test",
policies: paidRoutes,
signer: providerSigner,
});
res.set(manifest.headers).status(manifest.status).json(manifest.body);
});For v1 the manifest signer is the provider settlement pubkey. The payout pubkey can be different, so a hot operational key can finalize settlements while funds land on a colder receiver key.
Redeeming vouchers
Redeem/settlement is also framework-neutral. The package decides which channels should be redeemed and calls a provider-supplied redeemer that owns wallet access and transaction submission.
import { startSettlementWorker } from "@kaspacom/mpp-provider";
const worker = startSettlementWorker({
candidates,
redeemer,
policy: {
minUnsettledSompi: 50_000_000,
maxVoucherAgeMs: 300_000,
refundSafetyMarginMs: 3_600_000,
},
intervalMs: 60_000,
});The first production redeemer should use the same settlement authorization and wallet-lab proof path as the KaspaCom TN10 deployment until the transaction builder is promoted into a package.
