@fiber-pay/sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,7 +48,17 @@ console.log(facts);
48
48
  This helper aligns with upstream Fiber Biscuit permission mapping (method -> read/write resource),
49
49
  and can be used to prepare `permissions.bc` inputs before signing tokens.
50
50
 
51
+ ## L402 Protocol
52
+
53
+ The SDK includes L402 payment-gating primitives for Express APIs:
54
+
55
+ - `MacaroonService` — mint and verify L402 tokens
56
+ - `createL402Middleware()` — Express middleware for 402 challenge-response flow
57
+
58
+ See [docs/l402-agent-guide.md](../../docs/l402-agent-guide.md) for usage.
59
+
51
60
  ## Compatibility
52
61
 
53
62
  - Node.js `>=20`
54
63
  - Fiber target: `v0.7.1`
64
+
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Request, Response, NextFunction } from 'express';
1
2
  import { z } from 'zod';
2
3
 
3
4
  /**
@@ -1028,6 +1029,231 @@ declare class LiquidityAnalyzer {
1028
1029
  }>;
1029
1030
  }
1030
1031
 
1032
+ /**
1033
+ * MacaroonService
1034
+ *
1035
+ * Handles L402 macaroon minting, verification, and caveat management.
1036
+ * Uses the `macaroon` npm package for cryptographic operations and
1037
+ * Node.js built-in `crypto` for SHA-256 hashing.
1038
+ */
1039
+ interface MacaroonCaveat {
1040
+ condition: string;
1041
+ value: string;
1042
+ }
1043
+ interface MintParams {
1044
+ identifier: string;
1045
+ paymentHash: string;
1046
+ resourceId?: string;
1047
+ resourceType?: string;
1048
+ expirySeconds?: number;
1049
+ location?: string;
1050
+ }
1051
+ interface VerifyResult {
1052
+ valid: boolean;
1053
+ error?: string;
1054
+ caveats?: Record<string, string>;
1055
+ }
1056
+ declare class MacaroonService {
1057
+ private rootKey;
1058
+ constructor(rootKey?: string);
1059
+ /**
1060
+ * Mint a new macaroon with embedded caveats.
1061
+ *
1062
+ * The macaroon identifier encodes the payment hash, resource info, and
1063
+ * version. First-party caveats are added for payment_hash, expiry, and
1064
+ * optionally resource_id / resource_type.
1065
+ */
1066
+ mint(params: MintParams): {
1067
+ macaroon: string;
1068
+ caveats: MacaroonCaveat[];
1069
+ };
1070
+ /**
1071
+ * Verify a macaroon with a payment preimage.
1072
+ *
1073
+ * Validates:
1074
+ * 1. SHA-256(preimage) === payment_hash in the macaroon identifier
1075
+ * 2. Macaroon signature against root key
1076
+ * 3. Expiry caveat is not past
1077
+ */
1078
+ verify(macaroonB64: string, preimage: string): VerifyResult;
1079
+ /**
1080
+ * Verify macaroon signature and caveats without requiring a preimage.
1081
+ *
1082
+ * Used in the "connected-node" flow where the middleware verifies
1083
+ * invoice settlement via Fiber RPC instead of requiring the client
1084
+ * to provide the preimage directly.
1085
+ */
1086
+ verifyWithoutPreimage(macaroonB64: string): VerifyResult;
1087
+ /** Extract caveats from a macaroon without full verification. */
1088
+ extractCaveats(macaroonB64: string): Record<string, string>;
1089
+ private generateRootKey;
1090
+ }
1091
+
1092
+ /**
1093
+ * L402 Protocol Types
1094
+ *
1095
+ * Type definitions for the L402 (HTTP 402 + Macaroon + Lightning Invoice)
1096
+ * payment protocol. These types are framework-agnostic where possible;
1097
+ * Express-specific types are isolated behind optional peer dependency.
1098
+ */
1099
+
1100
+ /** Fiber Network invoice used in L402 challenges. */
1101
+ interface L402Invoice {
1102
+ paymentHash: string;
1103
+ invoiceAddress: string;
1104
+ amount: string;
1105
+ description: string;
1106
+ expiry: number;
1107
+ createdAt: number;
1108
+ }
1109
+ /** Authorization token combining macaroon + payment preimage. */
1110
+ interface L402Token {
1111
+ macaroon: string;
1112
+ preimage: string;
1113
+ }
1114
+ /** Data returned in a 402 response. */
1115
+ interface L402Challenge {
1116
+ macaroon: string;
1117
+ invoice: string;
1118
+ }
1119
+ /** Core L402 configuration. */
1120
+ interface L402Config {
1121
+ /** Hex string for macaroon signing (32 bytes / 64 hex chars). Falls back to L402_ROOT_KEY env or a secure random key. */
1122
+ rootKey?: string;
1123
+ /** Default expiry for macaroon + invoice in seconds. Default: 3600. */
1124
+ expirySeconds: number;
1125
+ /** Default price in CKB. */
1126
+ priceCkb: number;
1127
+ }
1128
+ /** Express request augmented with L402 validation result. */
1129
+ interface L402Request extends Request {
1130
+ l402?: {
1131
+ valid: boolean;
1132
+ preimage?: string;
1133
+ paymentHash?: string;
1134
+ token?: L402Token;
1135
+ };
1136
+ }
1137
+ /** Full middleware configuration including rate limiting. */
1138
+ interface L402MiddlewareConfig extends L402Config {
1139
+ rateLimitWindowMs: number;
1140
+ rateLimitMaxRequests: number;
1141
+ }
1142
+ /** In-memory challenge store interface. */
1143
+ interface ChallengeStore {
1144
+ get(key: string): L402Challenge | undefined;
1145
+ set(key: string, value: L402Challenge): void;
1146
+ delete(key: string): void;
1147
+ has(key: string): boolean;
1148
+ }
1149
+ /** Metadata about a protected resource (used for dynamic pricing). */
1150
+ interface ProtectedResourceInfo {
1151
+ id?: string;
1152
+ type?: string;
1153
+ priceCkb?: number;
1154
+ }
1155
+ /** Resolves request → resource info for dynamic pricing. */
1156
+ interface ResourceResolver {
1157
+ name: string;
1158
+ matches(req: Request): boolean;
1159
+ resolve(req: Request): Promise<ProtectedResourceInfo | undefined>;
1160
+ }
1161
+ /** Registry that matches requests to the appropriate resolver. */
1162
+ interface ResourceResolverRegistry {
1163
+ register(resolver: ResourceResolver): void;
1164
+ resolve(req: Request): Promise<ProtectedResourceInfo | undefined>;
1165
+ }
1166
+
1167
+ /**
1168
+ * L402 Middleware for Express
1169
+ *
1170
+ * Protects routes with the L402 payment protocol. On unauthenticated
1171
+ * requests it issues a 402 challenge (macaroon + Fiber invoice); on
1172
+ * subsequent requests it verifies the L402 token.
1173
+ *
1174
+ * Two verification paths are supported:
1175
+ * Path A — client provides macaroon:preimage (legacy / manual flow)
1176
+ * Path B — client provides macaroon only; middleware checks invoice
1177
+ * settlement via Fiber RPC (connected-node flow)
1178
+ *
1179
+ * Uses `FiberRpcClient` from `@fiber-pay/sdk` directly for invoice
1180
+ * creation and settlement checks, eliminating the intermediate
1181
+ * InvoiceService abstraction from the upstream fiber-l402 SDK.
1182
+ */
1183
+
1184
+ interface L402ResourceResolver {
1185
+ resolve(req: Request): Promise<ProtectedResourceInfo | undefined>;
1186
+ }
1187
+ type LegacyResourceProvider = (req: Request) => Promise<ProtectedResourceInfo | undefined> | ProtectedResourceInfo | undefined;
1188
+ declare class L402Middleware {
1189
+ private config;
1190
+ private rateLimitStore;
1191
+ private macaroonService;
1192
+ private rpcClient;
1193
+ private resourceResolver?;
1194
+ private currency;
1195
+ constructor(config?: Partial<L402MiddlewareConfig> & {
1196
+ /** Pre-configured RPC client. Takes precedence over rpcUrl. */
1197
+ rpcClient?: FiberRpcClient;
1198
+ /** Fiber node RPC URL. Used when rpcClient is not provided. */
1199
+ rpcUrl?: string;
1200
+ /** Biscuit token for Fiber RPC authentication. */
1201
+ biscuitToken?: string;
1202
+ /** Invoice currency. Default: 'Fibt' (testnet). */
1203
+ currency?: Currency;
1204
+ /** Resource resolver registry for dynamic pricing. */
1205
+ resourceResolver?: L402ResourceResolver;
1206
+ /** @deprecated Use resourceResolver instead. */
1207
+ resourceProvider?: LegacyResourceProvider;
1208
+ });
1209
+ handle(req: L402Request, res: Response, next: NextFunction): Promise<void>;
1210
+ private validateResourceCaveats;
1211
+ private validatePaymentHashHeader;
1212
+ private checkRateLimit;
1213
+ private issueChallenge;
1214
+ private createChallenge;
1215
+ }
1216
+ /**
1217
+ * Create an L402 middleware function for Express.
1218
+ *
1219
+ * @example
1220
+ * ```ts
1221
+ * import express from 'express';
1222
+ * import { createL402Middleware } from '@fiber-pay/sdk';
1223
+ *
1224
+ * const app = express();
1225
+ *
1226
+ * app.get('/api/premium/*', createL402Middleware({
1227
+ * rootKey: process.env.L402_ROOT_KEY,
1228
+ * priceCkb: 0.1,
1229
+ * expirySeconds: 3600,
1230
+ * }));
1231
+ * ```
1232
+ */
1233
+ declare function createL402Middleware(config?: Partial<L402MiddlewareConfig> & {
1234
+ rpcClient?: FiberRpcClient;
1235
+ rpcUrl?: string;
1236
+ biscuitToken?: string;
1237
+ currency?: Currency;
1238
+ resourceResolver?: L402ResourceResolver;
1239
+ resourceProvider?: LegacyResourceProvider;
1240
+ }): (req: L402Request, res: Response, next: NextFunction) => Promise<void>;
1241
+
1242
+ /**
1243
+ * Default Resource Resolver Registry
1244
+ *
1245
+ * Iterates registered resolvers to match incoming requests to protected
1246
+ * resource metadata (id, type, price). Used by L402Middleware for
1247
+ * dynamic per-resource pricing.
1248
+ */
1249
+
1250
+ declare class DefaultResourceResolverRegistry implements ResourceResolverRegistry {
1251
+ private resolvers;
1252
+ constructor(resolvers?: ResourceResolver[]);
1253
+ register(resolver: ResourceResolver): void;
1254
+ resolve(req: Request): Promise<ProtectedResourceInfo | undefined>;
1255
+ }
1256
+
1031
1257
  /**
1032
1258
  * Biscuit policy helpers for Fiber RPC.
1033
1259
  *
@@ -1251,4 +1477,4 @@ declare class InvoiceVerifier {
1251
1477
  private calculateTrustScore;
1252
1478
  }
1253
1479
 
1254
- export { AUTH_TAG_LENGTH, type AbandonChannelParams, type AcceptChannelParams, type AcceptChannelResult, type AgentSession, type Attribute, type AuditAction, type AuditLogEntry, type BiscuitAction, type BiscuitMethodRule, type BiscuitPermission, type BuildRouterParams, type BuildRouterResult, type CancelInvoiceParams, type CancelInvoiceResult, type CchInvoice, type CchOrderStatus, type CellOutput, type Channel, type ChannelId, type ChannelPolicy, ChannelPolicySchema, ChannelState, type ChannelStateFlags, type ChannelUpdateInfo, type CkbInvoice, type CkbInvoiceStatus, type ConnectPeerParams, type ConnectPeerResult, type Currency, DEFAULT_SECURITY_POLICY, type DisconnectPeerParams, ENCRYPTED_MAGIC, FiberRpcClient, FiberRpcError, type GetInvoiceParams, type GetInvoiceResult, type GetPaymentParams, type GetPaymentResult, type GraphChannelInfo, type GraphChannelsParams, type GraphChannelsResult, type GraphNodeInfo, type GraphNodesParams, type GraphNodesResult, type Hash256, type HashAlgorithm, type HexString, type HopHint, type HopRequire, type Htlc, IV_LENGTH, type InvoiceData, type InvoiceSignature, type InvoiceVerificationResult, InvoiceVerifier, type JsonRpcError, type JsonRpcRequest, type JsonRpcResponse, KEY_LENGTH, type KeyConfig, type KeyInfo, LiquidityAnalyzer, type LiquidityReport, type ListChannelsParams, type ListChannelsResult, type ListPeersResult, type Multiaddr, type NewInvoiceParams, type NewInvoiceResult, type NodeInfo, type NodeInfoResult, type OpenChannelParams, type OpenChannelResult, type OutPoint, type ParseInvoiceParams, type ParseInvoiceResult, type PaymentCustomRecords, type PaymentHash, type PaymentInfo, type PaymentStatus, type PeerId, type PeerInfo, type PolicyCheckResult, PolicyEngine, type PolicyViolation, type Privkey, type Pubkey, type RateLimit, RateLimitSchema, type RecipientPolicy, RecipientPolicySchema, type RemoveTlcReason, type RevocationData, type RouterHop, SALT_LENGTH, SCRYPT_N, SCRYPT_P, SCRYPT_R, type Script$1 as Script, type SecurityPolicy, SecurityPolicySchema, type SendPaymentParams, type SendPaymentResult, type SendPaymentWithRouterParams, type SessionRoute, type SessionRouteNode, type SettleInvoiceParams, type SettlementData, type SettlementTlc, type ShutdownChannelParams, type SpendingLimit, SpendingLimitSchema, type TLCId, type TlcStatus, type UdtArgInfo, type UdtCellDep, type UdtCfgInfos, type UdtDep, type UdtScript, type UpdateChannelParams, type ViolationType, buildMultiaddr, buildMultiaddrFromNodeId, buildMultiaddrFromRpcUrl, ckbHash, ckbToShannons, collectBiscuitPermissions, decryptKey, derivePublicKey, fromHex, generatePreimage, generatePrivateKey, getBiscuitRuleForMethod, hashPreimage, isEncryptedKey, listSupportedBiscuitMethods, nodeIdToPeerId, randomBytes32, renderBiscuitFactsForMethods, renderBiscuitPermissionFacts, scriptToAddress, sha256Hash, shannonsToCkb, toHex, verifyPreimageHash };
1480
+ export { AUTH_TAG_LENGTH, type AbandonChannelParams, type AcceptChannelParams, type AcceptChannelResult, type AgentSession, type Attribute, type AuditAction, type AuditLogEntry, type BiscuitAction, type BiscuitMethodRule, type BiscuitPermission, type BuildRouterParams, type BuildRouterResult, type CancelInvoiceParams, type CancelInvoiceResult, type CchInvoice, type CchOrderStatus, type CellOutput, type ChallengeStore, type Channel, type ChannelId, type ChannelPolicy, ChannelPolicySchema, ChannelState, type ChannelStateFlags, type ChannelUpdateInfo, type CkbInvoice, type CkbInvoiceStatus, type ConnectPeerParams, type ConnectPeerResult, type Currency, DEFAULT_SECURITY_POLICY, DefaultResourceResolverRegistry, type DisconnectPeerParams, ENCRYPTED_MAGIC, FiberRpcClient, FiberRpcError, type GetInvoiceParams, type GetInvoiceResult, type GetPaymentParams, type GetPaymentResult, type GraphChannelInfo, type GraphChannelsParams, type GraphChannelsResult, type GraphNodeInfo, type GraphNodesParams, type GraphNodesResult, type Hash256, type HashAlgorithm, type HexString, type HopHint, type HopRequire, type Htlc, IV_LENGTH, type InvoiceData, type InvoiceSignature, type InvoiceVerificationResult, InvoiceVerifier, type JsonRpcError, type JsonRpcRequest, type JsonRpcResponse, KEY_LENGTH, type KeyConfig, type KeyInfo, type L402Challenge, type L402Config, type L402Invoice, L402Middleware, type L402MiddlewareConfig, type L402Request, type L402Token, LiquidityAnalyzer, type LiquidityReport, type ListChannelsParams, type ListChannelsResult, type ListPeersResult, type MacaroonCaveat, MacaroonService, type MintParams, type Multiaddr, type NewInvoiceParams, type NewInvoiceResult, type NodeInfo, type NodeInfoResult, type OpenChannelParams, type OpenChannelResult, type OutPoint, type ParseInvoiceParams, type ParseInvoiceResult, type PaymentCustomRecords, type PaymentHash, type PaymentInfo, type PaymentStatus, type PeerId, type PeerInfo, type PolicyCheckResult, PolicyEngine, type PolicyViolation, type Privkey, type ProtectedResourceInfo, type Pubkey, type RateLimit, RateLimitSchema, type RecipientPolicy, RecipientPolicySchema, type RemoveTlcReason, type ResourceResolver, type ResourceResolverRegistry, type RevocationData, type RouterHop, SALT_LENGTH, SCRYPT_N, SCRYPT_P, SCRYPT_R, type Script$1 as Script, type SecurityPolicy, SecurityPolicySchema, type SendPaymentParams, type SendPaymentResult, type SendPaymentWithRouterParams, type SessionRoute, type SessionRouteNode, type SettleInvoiceParams, type SettlementData, type SettlementTlc, type ShutdownChannelParams, type SpendingLimit, SpendingLimitSchema, type TLCId, type TlcStatus, type UdtArgInfo, type UdtCellDep, type UdtCfgInfos, type UdtDep, type UdtScript, type UpdateChannelParams, type VerifyResult, type ViolationType, buildMultiaddr, buildMultiaddrFromNodeId, buildMultiaddrFromRpcUrl, ckbHash, ckbToShannons, collectBiscuitPermissions, createL402Middleware, decryptKey, derivePublicKey, fromHex, generatePreimage, generatePrivateKey, getBiscuitRuleForMethod, hashPreimage, isEncryptedKey, listSupportedBiscuitMethods, nodeIdToPeerId, randomBytes32, renderBiscuitFactsForMethods, renderBiscuitPermissionFacts, scriptToAddress, sha256Hash, shannonsToCkb, toHex, verifyPreimageHash };
package/dist/index.js CHANGED
@@ -534,6 +534,166 @@ var LiquidityAnalyzer = class {
534
534
  }
535
535
  };
536
536
 
537
+ // src/l402/macaroon.ts
538
+ import { createHash } from "crypto";
539
+ import * as macaroon from "macaroon";
540
+ var MacaroonService = class {
541
+ rootKey;
542
+ constructor(rootKey) {
543
+ const keyHex = rootKey || process.env.L402_ROOT_KEY || this.generateRootKey();
544
+ this.rootKey = Buffer.from(keyHex.replace(/^0x/, ""), "hex");
545
+ if (this.rootKey.length !== 32) {
546
+ throw new Error("Root key must be 32 bytes (64 hex characters)");
547
+ }
548
+ }
549
+ /**
550
+ * Mint a new macaroon with embedded caveats.
551
+ *
552
+ * The macaroon identifier encodes the payment hash, resource info, and
553
+ * version. First-party caveats are added for payment_hash, expiry, and
554
+ * optionally resource_id / resource_type.
555
+ */
556
+ mint(params) {
557
+ const expiryTimestamp = Math.floor(Date.now() / 1e3) + (params.expirySeconds || 3600);
558
+ const caveats = [
559
+ { condition: "payment_hash", value: params.paymentHash },
560
+ { condition: "expiry", value: expiryTimestamp.toString() }
561
+ ];
562
+ if (params.resourceId) {
563
+ caveats.push({ condition: "resource_id", value: params.resourceId });
564
+ }
565
+ if (params.resourceType) {
566
+ caveats.push({ condition: "resource_type", value: params.resourceType });
567
+ }
568
+ const identifier = JSON.stringify({
569
+ v: 0,
570
+ pid: params.paymentHash,
571
+ rid: params.resourceId,
572
+ rtype: params.resourceType,
573
+ loc: params.location || "fiber-l402"
574
+ });
575
+ const m = macaroon.newMacaroon({
576
+ rootKey: this.rootKey,
577
+ identifier,
578
+ location: params.location || "fiber-l402"
579
+ });
580
+ for (const caveat of caveats) {
581
+ m.addFirstPartyCaveat(`${caveat.condition}=${caveat.value}`);
582
+ }
583
+ const exported = m.exportJSON();
584
+ const macaroonB64 = Buffer.from(JSON.stringify(exported)).toString("base64");
585
+ return { macaroon: macaroonB64, caveats };
586
+ }
587
+ /**
588
+ * Verify a macaroon with a payment preimage.
589
+ *
590
+ * Validates:
591
+ * 1. SHA-256(preimage) === payment_hash in the macaroon identifier
592
+ * 2. Macaroon signature against root key
593
+ * 3. Expiry caveat is not past
594
+ */
595
+ verify(macaroonB64, preimage) {
596
+ try {
597
+ const exported = JSON.parse(Buffer.from(macaroonB64, "base64").toString());
598
+ const m = macaroon.importMacaroon(exported);
599
+ const identifierBytes = m.identifier;
600
+ const identifier = Buffer.from(identifierBytes).toString("utf-8");
601
+ const idData = JSON.parse(identifier);
602
+ const paymentHash = idData.pid;
603
+ const preimageHash = createHash("sha256").update(Buffer.from(preimage.replace(/^0x/, ""), "hex")).digest("hex");
604
+ if (preimageHash !== paymentHash.replace(/^0x/, "")) {
605
+ return { valid: false, error: "Invalid preimage: hash mismatch" };
606
+ }
607
+ const caveatCheck = (caveat) => {
608
+ if (caveat.startsWith("expiry=")) {
609
+ const expiry = parseInt(caveat.split("=")[1], 10);
610
+ if (expiry < Math.floor(Date.now() / 1e3)) {
611
+ return "Macaroon expired";
612
+ }
613
+ }
614
+ return null;
615
+ };
616
+ m.verify(this.rootKey, caveatCheck, []);
617
+ const caveats = {};
618
+ for (const caveat of m.caveats) {
619
+ const caveatStr = Buffer.from(caveat.identifier).toString("utf-8");
620
+ const [key, value] = caveatStr.split("=");
621
+ if (key && value) {
622
+ caveats[key] = value;
623
+ }
624
+ }
625
+ return { valid: true, caveats };
626
+ } catch (error) {
627
+ return {
628
+ valid: false,
629
+ error: error instanceof Error ? error.message : "Verification failed"
630
+ };
631
+ }
632
+ }
633
+ /**
634
+ * Verify macaroon signature and caveats without requiring a preimage.
635
+ *
636
+ * Used in the "connected-node" flow where the middleware verifies
637
+ * invoice settlement via Fiber RPC instead of requiring the client
638
+ * to provide the preimage directly.
639
+ */
640
+ verifyWithoutPreimage(macaroonB64) {
641
+ try {
642
+ const exported = JSON.parse(Buffer.from(macaroonB64, "base64").toString());
643
+ const m = macaroon.importMacaroon(exported);
644
+ const caveatCheck = (caveat) => {
645
+ if (caveat.startsWith("expiry=")) {
646
+ const expiry = parseInt(caveat.split("=")[1], 10);
647
+ if (expiry < Math.floor(Date.now() / 1e3)) {
648
+ return "Macaroon expired";
649
+ }
650
+ }
651
+ return null;
652
+ };
653
+ m.verify(this.rootKey, caveatCheck, []);
654
+ const caveats = {};
655
+ for (const caveat of m.caveats) {
656
+ const caveatStr = Buffer.from(caveat.identifier).toString("utf-8");
657
+ const [key, value] = caveatStr.split("=");
658
+ if (key && value) {
659
+ caveats[key] = value;
660
+ }
661
+ }
662
+ return { valid: true, caveats };
663
+ } catch (error) {
664
+ return {
665
+ valid: false,
666
+ error: error instanceof Error ? error.message : "Verification failed"
667
+ };
668
+ }
669
+ }
670
+ /** Extract caveats from a macaroon without full verification. */
671
+ extractCaveats(macaroonB64) {
672
+ try {
673
+ const exported = JSON.parse(Buffer.from(macaroonB64, "base64").toString());
674
+ const m = macaroon.importMacaroon(exported);
675
+ const caveats = {};
676
+ for (const caveat of m.caveats) {
677
+ const caveatStr = Buffer.from(caveat.identifier).toString("utf-8");
678
+ const parts = caveatStr.split("=");
679
+ if (parts.length === 2) {
680
+ caveats[parts[0]] = parts[1];
681
+ }
682
+ }
683
+ return caveats;
684
+ } catch {
685
+ return {};
686
+ }
687
+ }
688
+ generateRootKey() {
689
+ const bytes = new Uint8Array(32);
690
+ for (let i = 0; i < 32; i++) {
691
+ bytes[i] = Math.floor(Math.random() * 256);
692
+ }
693
+ return Buffer.from(bytes).toString("hex");
694
+ }
695
+ };
696
+
537
697
  // src/rpc/client.ts
538
698
  var HASH_ALGORITHM_MAP = {
539
699
  CkbHash: "ckb_hash",
@@ -991,6 +1151,237 @@ var FiberRpcClient = class {
991
1151
  }
992
1152
  };
993
1153
 
1154
+ // src/l402/middleware.ts
1155
+ var L402Middleware = class {
1156
+ config;
1157
+ rateLimitStore;
1158
+ macaroonService;
1159
+ rpcClient;
1160
+ resourceResolver;
1161
+ currency;
1162
+ constructor(config = {}) {
1163
+ this.config = {
1164
+ rootKey: config.rootKey || process.env.L402_ROOT_KEY,
1165
+ expirySeconds: config.expirySeconds || 3600,
1166
+ priceCkb: config.priceCkb || 0.1,
1167
+ rateLimitWindowMs: config.rateLimitWindowMs || 6e4,
1168
+ rateLimitMaxRequests: config.rateLimitMaxRequests || 100
1169
+ };
1170
+ this.rateLimitStore = /* @__PURE__ */ new Map();
1171
+ this.macaroonService = new MacaroonService(this.config.rootKey);
1172
+ this.currency = config.currency || "Fibt";
1173
+ this.rpcClient = config.rpcClient || new FiberRpcClient({
1174
+ url: config.rpcUrl || process.env.FIBER_RPC_URL || "http://127.0.0.1:8227",
1175
+ biscuitToken: config.biscuitToken || process.env.FIBER_BISCUIT_TOKEN
1176
+ });
1177
+ if (config.resourceResolver) {
1178
+ this.resourceResolver = config.resourceResolver;
1179
+ } else if (config.resourceProvider) {
1180
+ this.resourceResolver = {
1181
+ resolve: async (req) => config.resourceProvider?.(req)
1182
+ };
1183
+ }
1184
+ }
1185
+ async handle(req, res, next) {
1186
+ const clientIp = req.ip || req.socket.remoteAddress || "unknown";
1187
+ if (!this.checkRateLimit(clientIp)) {
1188
+ res.status(429).json({
1189
+ error: "Rate limit exceeded",
1190
+ retryAfter: Math.ceil(this.config.rateLimitWindowMs / 1e3)
1191
+ });
1192
+ return;
1193
+ }
1194
+ const resource = this.resourceResolver ? await this.resourceResolver.resolve(req) : void 0;
1195
+ const authHeader = req.headers.authorization;
1196
+ if (!authHeader?.startsWith("L402 ")) {
1197
+ return this.issueChallenge(req, res, 402, void 0, resource);
1198
+ }
1199
+ const [, token] = authHeader.split(" ");
1200
+ if (!token) {
1201
+ return this.issueChallenge(req, res);
1202
+ }
1203
+ const splitIndex = token.indexOf(":");
1204
+ const macaroonToken = splitIndex >= 0 ? token.slice(0, splitIndex) : token;
1205
+ const preimage = splitIndex >= 0 ? token.slice(splitIndex + 1) : void 0;
1206
+ if (!macaroonToken) {
1207
+ return this.issueChallenge(req, res);
1208
+ }
1209
+ if (preimage) {
1210
+ const result = this.macaroonService.verify(macaroonToken, preimage);
1211
+ if (!result.valid) {
1212
+ return this.issueChallenge(req, res, 401, result.error, resource);
1213
+ }
1214
+ const headerError2 = this.validatePaymentHashHeader(req, result.caveats || {});
1215
+ if (headerError2) {
1216
+ return this.issueChallenge(req, res, 401, headerError2, resource);
1217
+ }
1218
+ const validateError2 = this.validateResourceCaveats(result.caveats || {}, resource);
1219
+ if (validateError2) {
1220
+ return this.issueChallenge(req, res, 401, validateError2, resource);
1221
+ }
1222
+ req.l402 = { valid: true, preimage };
1223
+ next();
1224
+ return;
1225
+ }
1226
+ const verifyResult = this.macaroonService.verifyWithoutPreimage(macaroonToken);
1227
+ if (!verifyResult.valid) {
1228
+ return this.issueChallenge(req, res, 401, verifyResult.error, resource);
1229
+ }
1230
+ const caveats = verifyResult.caveats || {};
1231
+ const paymentHash = caveats.payment_hash;
1232
+ if (!paymentHash) {
1233
+ return this.issueChallenge(req, res, 401, "Missing payment hash caveat", resource);
1234
+ }
1235
+ const headerError = this.validatePaymentHashHeader(req, caveats);
1236
+ if (headerError) {
1237
+ return this.issueChallenge(req, res, 401, headerError, resource);
1238
+ }
1239
+ const validateError = this.validateResourceCaveats(caveats, resource);
1240
+ if (validateError) {
1241
+ return this.issueChallenge(req, res, 401, validateError, resource);
1242
+ }
1243
+ try {
1244
+ const invoiceResult = await this.rpcClient.getInvoice({
1245
+ payment_hash: paymentHash
1246
+ });
1247
+ if (invoiceResult.status !== "Paid") {
1248
+ return this.issueChallenge(
1249
+ req,
1250
+ res,
1251
+ 401,
1252
+ `Invoice not settled (status: ${invoiceResult.status})`,
1253
+ resource
1254
+ );
1255
+ }
1256
+ } catch {
1257
+ return this.issueChallenge(req, res, 401, "Failed to verify invoice status", resource);
1258
+ }
1259
+ req.l402 = { valid: true, paymentHash };
1260
+ next();
1261
+ }
1262
+ // ─── Private Helpers ───────────────────────────────────────
1263
+ validateResourceCaveats(caveats, resource) {
1264
+ if (!resource) {
1265
+ return void 0;
1266
+ }
1267
+ if (resource.id && caveats.resource_id && caveats.resource_id !== resource.id) {
1268
+ return "Resource id mismatch";
1269
+ }
1270
+ if (resource.type && caveats.resource_type && caveats.resource_type !== resource.type) {
1271
+ return "Resource type mismatch";
1272
+ }
1273
+ if (resource.id && !caveats.resource_id) {
1274
+ return "Missing resource_id in macaroon";
1275
+ }
1276
+ if (resource.type && !caveats.resource_type) {
1277
+ return "Missing resource_type in macaroon";
1278
+ }
1279
+ return void 0;
1280
+ }
1281
+ validatePaymentHashHeader(req, caveats) {
1282
+ const rawHeader = req.headers["x-l402-payment-hash"];
1283
+ const headerPaymentHash = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
1284
+ if (!headerPaymentHash) {
1285
+ return void 0;
1286
+ }
1287
+ const caveatPaymentHash = caveats.payment_hash;
1288
+ if (!caveatPaymentHash) {
1289
+ return "Missing payment hash caveat";
1290
+ }
1291
+ if (headerPaymentHash !== caveatPaymentHash) {
1292
+ return "Payment hash header mismatch";
1293
+ }
1294
+ return void 0;
1295
+ }
1296
+ checkRateLimit(ip) {
1297
+ const now = Date.now();
1298
+ const record = this.rateLimitStore.get(ip);
1299
+ if (!record) {
1300
+ this.rateLimitStore.set(ip, {
1301
+ count: 1,
1302
+ resetTime: now + this.config.rateLimitWindowMs
1303
+ });
1304
+ return true;
1305
+ }
1306
+ if (now > record.resetTime) {
1307
+ this.rateLimitStore.set(ip, {
1308
+ count: 1,
1309
+ resetTime: now + this.config.rateLimitWindowMs
1310
+ });
1311
+ return true;
1312
+ }
1313
+ if (record.count >= this.config.rateLimitMaxRequests) {
1314
+ return false;
1315
+ }
1316
+ record.count++;
1317
+ return true;
1318
+ }
1319
+ async issueChallenge(req, res, statusCode = 402, errorMessage, resource) {
1320
+ try {
1321
+ const { macaroon: macaroon2, invoice } = await this.createChallenge(req, resource);
1322
+ const wwwAuthenticate = `L402 macaroon="${macaroon2}", invoice="${invoice}"`;
1323
+ res.status(statusCode).set("WWW-Authenticate", wwwAuthenticate).json({
1324
+ error: errorMessage || "Payment Required",
1325
+ macaroon: macaroon2,
1326
+ invoice
1327
+ });
1328
+ } catch (error) {
1329
+ res.status(500).json({
1330
+ error: "Failed to create payment challenge",
1331
+ message: error instanceof Error ? error.message : "Unknown error"
1332
+ });
1333
+ }
1334
+ }
1335
+ async createChallenge(req, resource) {
1336
+ const priceCkb = resource?.priceCkb ?? this.config.priceCkb;
1337
+ const amountShannons = `0x${(priceCkb * 1e8).toString(16)}`;
1338
+ const invoiceResult = await this.rpcClient.newInvoice({
1339
+ amount: amountShannons,
1340
+ description: `L402 Payment ${resource?.type ?? "resource"}`,
1341
+ currency: this.currency,
1342
+ expiry: `0x${this.config.expirySeconds.toString(16)}`,
1343
+ hash_algorithm: "Sha256"
1344
+ });
1345
+ const paymentHash = invoiceResult.invoice.data.payment_hash;
1346
+ const { macaroon: macaroon2 } = this.macaroonService.mint({
1347
+ identifier: `l402-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1348
+ paymentHash,
1349
+ expirySeconds: this.config.expirySeconds,
1350
+ resourceId: resource?.id,
1351
+ resourceType: resource?.type,
1352
+ location: req.headers.host || "fiber-l402"
1353
+ });
1354
+ return { macaroon: macaroon2, invoice: invoiceResult.invoice_address };
1355
+ }
1356
+ };
1357
+ function createL402Middleware(config = {}) {
1358
+ const middleware = new L402Middleware(config);
1359
+ return middleware.handle.bind(middleware);
1360
+ }
1361
+
1362
+ // src/l402/resources.ts
1363
+ var DefaultResourceResolverRegistry = class {
1364
+ resolvers;
1365
+ constructor(resolvers = []) {
1366
+ this.resolvers = [...resolvers];
1367
+ }
1368
+ register(resolver) {
1369
+ this.resolvers.push(resolver);
1370
+ }
1371
+ async resolve(req) {
1372
+ for (const resolver of this.resolvers) {
1373
+ if (!resolver.matches(req)) {
1374
+ continue;
1375
+ }
1376
+ const resource = await resolver.resolve(req);
1377
+ if (resource) {
1378
+ return resource;
1379
+ }
1380
+ }
1381
+ return void 0;
1382
+ }
1383
+ };
1384
+
994
1385
  // src/security/biscuit-policy.ts
995
1386
  var RULES = {
996
1387
  // Cch
@@ -1863,13 +2254,16 @@ var InvoiceVerifier = class {
1863
2254
  export {
1864
2255
  AUTH_TAG_LENGTH,
1865
2256
  ChannelState,
2257
+ DefaultResourceResolverRegistry,
1866
2258
  ENCRYPTED_MAGIC,
1867
2259
  FiberRpcClient,
1868
2260
  FiberRpcError,
1869
2261
  IV_LENGTH,
1870
2262
  InvoiceVerifier,
1871
2263
  KEY_LENGTH,
2264
+ L402Middleware,
1872
2265
  LiquidityAnalyzer,
2266
+ MacaroonService,
1873
2267
  PolicyEngine,
1874
2268
  SALT_LENGTH,
1875
2269
  SCRYPT_N,
@@ -1881,6 +2275,7 @@ export {
1881
2275
  ckbHash,
1882
2276
  ckbToShannons,
1883
2277
  collectBiscuitPermissions,
2278
+ createL402Middleware,
1884
2279
  decryptKey,
1885
2280
  derivePublicKey,
1886
2281
  fromHex,