@faremeter/middleware 0.12.0 → 0.14.0

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 ADDED
@@ -0,0 +1,41 @@
1
+ # @faremeter/middleware
2
+
3
+ Server middleware for adding payment walls to API endpoints using the x402 protocol.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm install @faremeter/middleware
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Paywall any endpoint - Add `middleware` to any route
14
+ - Framework agnostic - Express, Hono, or custom
15
+ - Multi-chain support - Solana, EVM, extensible
16
+ - Fast validation - Payment requirements caching
17
+ - Facilitator integration - Handles settlement verification
18
+
19
+ ## API Reference
20
+
21
+ <!-- TSDOC_START -->
22
+
23
+ <!-- TSDOC_END -->
24
+
25
+ ## Examples
26
+
27
+ See working examples in the [faremeter repository](https://github.com/faremeter/faremeter/tree/main/scripts):
28
+
29
+ - [Express + Solana](https://github.com/faremeter/faremeter/blob/main/scripts/solana-example/server-express.ts)
30
+ - [Express + EVM](https://github.com/faremeter/faremeter/blob/main/scripts/evm-example/server-express.ts)
31
+ - [Hono + Solana](https://github.com/faremeter/faremeter/blob/main/scripts/solana-example/server-hono.ts)
32
+
33
+ ## Related Packages
34
+
35
+ - [@faremeter/fetch](https://www.npmjs.com/package/@faremeter/fetch) - Client-side fetch wrapper
36
+ - [@faremeter/info](https://www.npmjs.com/package/@faremeter/info) - Network/asset configuration helpers
37
+ - [@faremeter/facilitator](https://www.npmjs.com/package/@faremeter/facilitator) - Payment facilitator service
38
+
39
+ ## License
40
+
41
+ LGPL-3.0-only
@@ -49,6 +49,12 @@ export type HandleMiddlewareRequestArgs<MiddlewareResponse = unknown> = CommonMi
49
49
  getHeader: (key: string) => string | undefined;
50
50
  getPaymentRequiredResponse: typeof getPaymentRequiredResponse;
51
51
  sendJSONResponse: (status: PossibleStatusCodes, obj: PossibleJSONResponse) => MiddlewareResponse;
52
+ body: (context: {
53
+ paymentRequirements: x402PaymentRequirements;
54
+ paymentPayload: x402PaymentPayload;
55
+ settle: () => Promise<MiddlewareResponse | undefined>;
56
+ verify: () => Promise<MiddlewareResponse | undefined>;
57
+ }) => Promise<MiddlewareResponse | undefined>;
52
58
  };
53
59
  export declare function handleMiddlewareRequest<MiddlewareResponse>(args: HandleMiddlewareRequestArgs<MiddlewareResponse>): Promise<MiddlewareResponse | undefined>;
54
60
  export type createPaymentRequiredResponseCacheOpts = AgedLRUCacheOpts & {
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/common.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EAKxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,KAAK,gBAAgB,EAAgB,MAAM,SAAS,CAAC;AAI9D,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,uBAAuB,EAAE,EAClC,OAAO,EAAE,kBAAkB;;;;;;;;;;;;cA6B5B;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,QAAQ,QAS3D;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEnE,KAAK,8BAA8B,GAAG;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,8BAA8B;;;;;;;;;;;;oBApC5B,CAAA;aAAuB,CAAC;;;GAkEjC;AAED,KAAK,mBAAmB,GAAG,GAAG,CAAC;AAC/B,KAAK,oBAAoB,GAAG,MAAM,CAAC;AAEnC,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,CAAC,mBAAmB,GAAG,mBAAmB,EAAE,CAAC,EAAE,CAAC;IACzD,WAAW,CAAC,EAAE,sCAAsC,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,kBAAkB,GAAG,OAAO,IAClE,oBAAoB,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/C,0BAA0B,EAAE,OAAO,0BAA0B,CAAC;IAC9D,gBAAgB,EAAE,CAChB,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,oBAAoB,KACtB,kBAAkB,CAAC;CACzB,CAAC;AAEJ,wBAAsB,uBAAuB,CAAC,kBAAkB,EAC9D,IAAI,EAAE,2BAA2B,CAAC,kBAAkB,CAAC,2CAiEtD;AAED,MAAM,MAAM,sCAAsC,GAAG,gBAAgB,GAAG;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AACF,wBAAgB,kCAAkC,CAChD,IAAI,GAAE,sCAA2C;;EA8BlD"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/common.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EAOxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,KAAK,gBAAgB,EAAgB,MAAM,SAAS,CAAC;AAI9D,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,uBAAuB,EAAE,EAClC,OAAO,EAAE,kBAAkB;;;;;;;;;;;;cA6B5B;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,QAAQ,QAS3D;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEnE,KAAK,8BAA8B,GAAG;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,8BAA8B;;;;;;;;;;;;oBAtCoB,CAAC;aACtC,CAAC;;;GAmErB;AAED,KAAK,mBAAmB,GAAG,GAAG,CAAC;AAC/B,KAAK,oBAAoB,GAAG,MAAM,CAAC;AAEnC,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,CAAC,mBAAmB,GAAG,mBAAmB,EAAE,CAAC,EAAE,CAAC;IACzD,WAAW,CAAC,EAAE,sCAAsC,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,kBAAkB,GAAG,OAAO,IAClE,oBAAoB,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/C,0BAA0B,EAAE,OAAO,0BAA0B,CAAC;IAC9D,gBAAgB,EAAE,CAChB,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,oBAAoB,KACtB,kBAAkB,CAAC;IACxB,IAAI,EAAE,CAAC,OAAO,EAAE;QACd,mBAAmB,EAAE,uBAAuB,CAAC;QAC7C,cAAc,EAAE,kBAAkB,CAAC;QACnC,MAAM,EAAE,MAAM,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;QACtD,MAAM,EAAE,MAAM,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;KACvD,KAAK,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;CAC/C,CAAC;AAEJ,wBAAsB,uBAAuB,CAAC,kBAAkB,EAC9D,IAAI,EAAE,2BAA2B,CAAC,kBAAkB,CAAC,2CA4GtD;AAED,MAAM,MAAM,sCAAsC,GAAG,gBAAgB,GAAG;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AACF,wBAAgB,kCAAkC,CAChD,IAAI,GAAE,sCAA2C;;EA8BlD"}
@@ -1,5 +1,5 @@
1
1
  import { isValidationError } from "@faremeter/types";
2
- import { x402PaymentRequiredResponse, x402PaymentHeaderToPayload, x402SettleRequest, x402SettleResponse, } from "@faremeter/types/x402";
2
+ import { x402PaymentRequiredResponse, x402PaymentHeaderToPayload, x402VerifyRequest, x402VerifyResponse, x402SettleRequest, x402SettleResponse, } from "@faremeter/types/x402";
3
3
  import { AgedLRUCache } from "./cache.js";
4
4
  import { logger } from "./logger.js";
5
5
  export function findMatchingPaymentRequirements(accepts, payload) {
@@ -64,39 +64,74 @@ export async function handleMiddlewareRequest(args) {
64
64
  if (!paymentHeader) {
65
65
  return sendPaymentRequired();
66
66
  }
67
- const payload = x402PaymentHeaderToPayload(paymentHeader);
68
- if (isValidationError(payload)) {
69
- logger.debug(`couldn't validate client payload: ${payload.summary}`);
67
+ const paymentPayload = x402PaymentHeaderToPayload(paymentHeader);
68
+ if (isValidationError(paymentPayload)) {
69
+ logger.debug(`couldn't validate client payload: ${paymentPayload.summary}`);
70
70
  return sendPaymentRequired();
71
71
  }
72
- const paymentRequirements = findMatchingPaymentRequirements(paymentRequiredResponse.accepts, payload);
72
+ const paymentRequirements = findMatchingPaymentRequirements(paymentRequiredResponse.accepts, paymentPayload);
73
73
  if (!paymentRequirements) {
74
- logger.warning(`couldn't find matching payment requirements for payload`, payload);
74
+ logger.warning(`couldn't find matching payment requirements for payload`, paymentPayload);
75
75
  return sendPaymentRequired();
76
76
  }
77
- const settleRequest = {
78
- x402Version: 1,
79
- paymentHeader,
80
- paymentRequirements,
77
+ const settle = async () => {
78
+ const settleRequest = {
79
+ x402Version: 1,
80
+ paymentHeader,
81
+ paymentPayload,
82
+ paymentRequirements,
83
+ };
84
+ const t = await fetch(`${args.facilitatorURL}/settle`, {
85
+ method: "POST",
86
+ headers: {
87
+ Accept: "application/json",
88
+ "Content-Type": "application/json",
89
+ },
90
+ body: JSON.stringify(settleRequest),
91
+ });
92
+ const settlementResponse = x402SettleResponse(await t.json());
93
+ if (isValidationError(settlementResponse)) {
94
+ const msg = `error getting response from facilitator for settlement: ${settlementResponse.summary}`;
95
+ logger.error(msg);
96
+ throw new Error(msg);
97
+ }
98
+ if (!settlementResponse.success) {
99
+ logger.warning("failed to settle payment: {error}", settlementResponse);
100
+ return sendPaymentRequired();
101
+ }
81
102
  };
82
- const t = await fetch(`${args.facilitatorURL}/settle`, {
83
- method: "POST",
84
- headers: {
85
- Accept: "application/json",
86
- "Content-Type": "application/json",
87
- },
88
- body: JSON.stringify(settleRequest),
103
+ const verify = async () => {
104
+ const verifyRequest = {
105
+ x402Version: 1,
106
+ paymentHeader,
107
+ paymentPayload,
108
+ paymentRequirements,
109
+ };
110
+ const t = await fetch(`${args.facilitatorURL}/verify`, {
111
+ method: "POST",
112
+ headers: {
113
+ Accept: "application/json",
114
+ "Content-Type": "application/json",
115
+ },
116
+ body: JSON.stringify(verifyRequest),
117
+ });
118
+ const verifyResponse = x402VerifyResponse(await t.json());
119
+ if (isValidationError(verifyResponse)) {
120
+ const msg = `error getting response from facilitator for verification: ${verifyResponse.summary}`;
121
+ logger.error(msg);
122
+ throw new Error(msg);
123
+ }
124
+ if (!verifyResponse.isValid) {
125
+ logger.warning("failed to settle payment: {invalidReason}", verifyResponse);
126
+ return sendPaymentRequired();
127
+ }
128
+ };
129
+ return await args.body({
130
+ paymentRequirements,
131
+ paymentPayload,
132
+ settle,
133
+ verify,
89
134
  });
90
- const settlementResponse = x402SettleResponse(await t.json());
91
- if (isValidationError(settlementResponse)) {
92
- const msg = `error getting response from facilitator for settlement: ${settlementResponse.summary}`;
93
- logger.error(msg);
94
- throw new Error(msg);
95
- }
96
- if (!settlementResponse.success) {
97
- logger.warning("failed to settle payment: {error}", settlementResponse);
98
- return sendPaymentRequired();
99
- }
100
135
  }
101
136
  export function createPaymentRequiredResponseCache(opts = {}) {
102
137
  if (opts.disable) {
@@ -1,6 +1,6 @@
1
1
  import { type CommonMiddlewareArgs } from "./common.js";
2
2
  import type { NextFunction, Request, Response } from "express";
3
3
  type createMiddlewareArgs = CommonMiddlewareArgs;
4
- export declare function createMiddleware(args: createMiddlewareArgs): Promise<(req: Request, res: Response, next: NextFunction) => Promise<void>>;
4
+ export declare function createMiddleware(args: createMiddlewareArgs): Promise<(req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>>;
5
5
  export {};
6
6
  //# sourceMappingURL=express.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/D,KAAK,oBAAoB,GAAG,oBAAoB,CAAC;AAEjD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,iBAK5C,OAAO,OAAO,QAAQ,QAAQ,YAAY,oBAe9D"}
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/D,KAAK,oBAAoB,GAAG,oBAAoB,CAAC;AAEjD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,iBAK5C,OAAO,OAAO,QAAQ,QAAQ,YAAY,8DAiB9D"}
@@ -2,16 +2,19 @@ import { handleMiddlewareRequest, createPaymentRequiredResponseCache, } from "./
2
2
  export async function createMiddleware(args) {
3
3
  const { getPaymentRequiredResponse } = createPaymentRequiredResponseCache(args.cacheConfig);
4
4
  return async (req, res, next) => {
5
- const response = await handleMiddlewareRequest({
5
+ return await handleMiddlewareRequest({
6
6
  ...args,
7
7
  resource: `${req.protocol}://${req.headers.host}${req.path}`,
8
8
  getPaymentRequiredResponse,
9
9
  getHeader: (key) => req.header(key),
10
10
  sendJSONResponse: (status, body) => res.status(status).json(body),
11
+ body: async ({ settle }) => {
12
+ const response = await settle();
13
+ if (response !== undefined) {
14
+ return response;
15
+ }
16
+ next();
17
+ },
11
18
  });
12
- if (response) {
13
- return;
14
- }
15
- next();
16
19
  };
17
20
  }
@@ -1,6 +1,8 @@
1
1
  import { type CommonMiddlewareArgs } from "./common.js";
2
2
  import type { MiddlewareHandler } from "hono";
3
- type CreateMiddlewareArgs = CommonMiddlewareArgs;
3
+ type CreateMiddlewareArgs = {
4
+ verifyBeforeSettle?: boolean;
5
+ } & CommonMiddlewareArgs;
4
6
  export declare function createMiddleware(args: CreateMiddlewareArgs): Promise<MiddlewareHandler>;
5
7
  export {};
6
8
  //# sourceMappingURL=hono.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,KAAK,oBAAoB,GAAG,oBAAoB,CAAC;AAEjD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAuB5B"}
1
+ {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,KAAK,oBAAoB,GAAG;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GAAG,oBAAoB,CAAC;AAEzB,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAoD5B"}
package/dist/src/hono.js CHANGED
@@ -2,7 +2,7 @@ import { handleMiddlewareRequest, createPaymentRequiredResponseCache, } from "./
2
2
  export async function createMiddleware(args) {
3
3
  const { getPaymentRequiredResponse } = createPaymentRequiredResponseCache(args.cacheConfig);
4
4
  return async (c, next) => {
5
- const response = await handleMiddlewareRequest({
5
+ return await handleMiddlewareRequest({
6
6
  ...args,
7
7
  resource: c.req.url,
8
8
  getHeader: (key) => c.req.header(key),
@@ -11,10 +11,39 @@ export async function createMiddleware(args) {
11
11
  c.status(status);
12
12
  return c.json(body);
13
13
  },
14
+ body: async ({ verify, settle }) => {
15
+ if (args.verifyBeforeSettle) {
16
+ // If configured, try to verify the transaction before running
17
+ // the next operation.
18
+ const verifyResult = await verify();
19
+ if (verifyResult !== undefined) {
20
+ return verifyResult;
21
+ }
22
+ }
23
+ else {
24
+ // Otherwise just settle the payment beforehand, like we've
25
+ // done historically.
26
+ const settleResult = await settle();
27
+ if (settleResult !== undefined) {
28
+ return settleResult;
29
+ }
30
+ }
31
+ await next();
32
+ if (args.verifyBeforeSettle) {
33
+ // Close out the verification, by actually settling the
34
+ // payment.
35
+ const settleResult = await settle();
36
+ if (settleResult !== undefined) {
37
+ // If the settlement fails, we need to explicitly
38
+ // overwrite the downstream result. See:
39
+ //
40
+ // https://hono.dev/docs/guides/middleware#modify-the-response-after-next
41
+ //
42
+ c.res = undefined;
43
+ c.res = settleResult;
44
+ }
45
+ }
46
+ },
14
47
  });
15
- if (response) {
16
- return response;
17
- }
18
- await next();
19
48
  };
20
49
  }