@foldset/cloudflare 0.0.2 → 0.0.3

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.
Files changed (54) hide show
  1. package/dist/ai-crawlers/index.d.ts +15 -0
  2. package/dist/ai-crawlers/index.d.ts.map +1 -0
  3. package/dist/ai-crawlers/index.js +26 -0
  4. package/dist/config.d.ts +2 -0
  5. package/dist/config.d.ts.map +1 -0
  6. package/dist/config.js +1 -0
  7. package/dist/facilitators/index.d.ts +16 -0
  8. package/dist/facilitators/index.d.ts.map +1 -0
  9. package/dist/facilitators/index.js +35 -0
  10. package/dist/hono.d.ts +2 -0
  11. package/dist/hono.d.ts.map +1 -0
  12. package/dist/hono.js +1 -0
  13. package/dist/index.d.ts +8 -8
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +1 -163
  16. package/dist/payment/adapter.d.ts +16 -0
  17. package/dist/payment/adapter.d.ts.map +1 -0
  18. package/dist/payment/adapter.js +43 -0
  19. package/dist/payment/handler.d.ts +6 -0
  20. package/dist/payment/handler.d.ts.map +1 -0
  21. package/dist/payment/handler.js +86 -0
  22. package/dist/payment/paywall.d.ts +4 -0
  23. package/dist/payment/paywall.d.ts.map +1 -0
  24. package/dist/payment/paywall.js +99 -0
  25. package/dist/payment/routes.d.ts +5 -0
  26. package/dist/payment/routes.d.ts.map +1 -0
  27. package/dist/payment/routes.js +31 -0
  28. package/dist/payment/settlement.d.ts +8 -0
  29. package/dist/payment/settlement.d.ts.map +1 -0
  30. package/dist/payment/settlement.js +27 -0
  31. package/dist/payment/setup.d.ts +7 -0
  32. package/dist/payment/setup.d.ts.map +1 -0
  33. package/dist/payment/setup.js +85 -0
  34. package/dist/payment-methods/index.d.ts +18 -0
  35. package/dist/payment-methods/index.d.ts.map +1 -0
  36. package/dist/payment-methods/index.js +18 -0
  37. package/dist/restrictions/index.d.ts +16 -0
  38. package/dist/restrictions/index.d.ts.map +1 -0
  39. package/dist/restrictions/index.js +18 -0
  40. package/dist/telemetry/logging.d.ts +18 -0
  41. package/dist/telemetry/logging.d.ts.map +1 -0
  42. package/dist/telemetry/logging.js +47 -0
  43. package/dist/telemetry/sentry.d.ts +8 -0
  44. package/dist/telemetry/sentry.d.ts.map +1 -0
  45. package/dist/telemetry/sentry.js +19 -0
  46. package/dist/types.d.ts +5 -9
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/webhooks/index.d.ts +23 -0
  49. package/dist/webhooks/index.d.ts.map +1 -0
  50. package/dist/webhooks/index.js +50 -0
  51. package/package.json +21 -18
  52. package/README.md +0 -1
  53. package/dist/adapter.d.ts +0 -71
  54. package/dist/adapter.js +0 -99
@@ -0,0 +1,15 @@
1
+ import type { Context } from "hono";
2
+ import type { Env } from "../types";
3
+ export interface AiCrawler {
4
+ user_agent: string;
5
+ }
6
+ export declare function getAiCrawlers(c: Context<{
7
+ Bindings: Env;
8
+ }>): Promise<AiCrawler[]>;
9
+ export declare function storeAiCrawlers(c: Context<{
10
+ Bindings: Env;
11
+ }>, aiCrawlers: AiCrawler[]): Promise<void>;
12
+ export declare function isAiCrawler(c: Context<{
13
+ Bindings: Env;
14
+ }>): Promise<boolean>;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai-crawlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,wBAAsB,aAAa,CACjC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAC5B,OAAO,CAAC,SAAS,EAAE,CAAC,CAWtB;AAED,wBAAsB,eAAe,CACnC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,EAC7B,UAAU,EAAE,SAAS,EAAE,GACtB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,WAAW,CAC/B,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAKjD"}
@@ -0,0 +1,26 @@
1
+ const CACHE_TTL_MS = 30_000;
2
+ let cachedAiCrawlers = [];
3
+ let cacheTimestamp = 0;
4
+ export async function getAiCrawlers(c) {
5
+ const now = Date.now();
6
+ if (cachedAiCrawlers.length > 0 && now - cacheTimestamp < CACHE_TTL_MS) {
7
+ return cachedAiCrawlers;
8
+ }
9
+ const response = await c.env.FOLDSET_CONFIG.get("ai-crawlers");
10
+ const parsed = response ? JSON.parse(response) : [];
11
+ cachedAiCrawlers = parsed.map((c) => ({ user_agent: c.user_agent.toLowerCase() }));
12
+ cacheTimestamp = now;
13
+ return cachedAiCrawlers;
14
+ }
15
+ export async function storeAiCrawlers(c, aiCrawlers) {
16
+ await c.env.FOLDSET_CONFIG.put("ai-crawlers", JSON.stringify(aiCrawlers), {
17
+ expirationTtl: 60 * 60 * 3 + 60 * 30, // 3 hours + 30 minutes
18
+ });
19
+ }
20
+ export async function isAiCrawler(c) {
21
+ const aiCrawlers = await getAiCrawlers(c);
22
+ const userAgent = c.req.header("User-Agent")?.toLowerCase();
23
+ if (!userAgent)
24
+ return false;
25
+ return aiCrawlers.some((crawler) => userAgent.includes(crawler.user_agent));
26
+ }
@@ -0,0 +1,2 @@
1
+ export declare const API_BASE_URL = "https://api.foldset.com/v1";
2
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,+BAA+B,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1 @@
1
+ export const API_BASE_URL = "https://api.foldset.com/v1";
@@ -0,0 +1,16 @@
1
+ import type { Context } from "hono";
2
+ import type { Env } from "../types";
3
+ import { HTTPFacilitatorClient } from "@x402/core/server";
4
+ export interface FacilitatorConfig {
5
+ url: string;
6
+ verifyHeaders?: Record<string, string>;
7
+ settleHeaders?: Record<string, string>;
8
+ supportedHeaders?: Record<string, string>;
9
+ }
10
+ export declare function getFacilitator(c: Context<{
11
+ Bindings: Env;
12
+ }>): Promise<HTTPFacilitatorClient | null>;
13
+ export declare function storeFacilitator(c: Context<{
14
+ Bindings: Env;
15
+ }>, facilitatorConfig: FacilitatorConfig): Promise<void>;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/facilitators/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAOD,wBAAsB,cAAc,CAClC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAC5B,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA8BvC;AAED,wBAAsB,gBAAgB,CACpC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,EAC7B,iBAAiB,EAAE,iBAAiB,GACnC,OAAO,CAAC,IAAI,CAAC,CAIf"}
@@ -0,0 +1,35 @@
1
+ import { HTTPFacilitatorClient } from "@x402/core/server";
2
+ const CACHE_TTL_MS = 30_000;
3
+ let cachedFacilitator = null;
4
+ let cacheTimestamp = 0;
5
+ export async function getFacilitator(c) {
6
+ const now = Date.now();
7
+ if (cachedFacilitator && now - cacheTimestamp < CACHE_TTL_MS) {
8
+ return cachedFacilitator;
9
+ }
10
+ const response = await c.env.FOLDSET_CONFIG.get("facilitator");
11
+ const facilitatorConfig = response ? JSON.parse(response) : null;
12
+ if (!facilitatorConfig) {
13
+ return null;
14
+ }
15
+ const hasAuthHeaders = facilitatorConfig.verifyHeaders ||
16
+ facilitatorConfig.settleHeaders ||
17
+ facilitatorConfig.supportedHeaders;
18
+ cachedFacilitator = new HTTPFacilitatorClient({
19
+ url: facilitatorConfig.url,
20
+ ...(hasAuthHeaders && {
21
+ createAuthHeaders: async () => ({
22
+ verify: facilitatorConfig.verifyHeaders ?? {},
23
+ settle: facilitatorConfig.settleHeaders ?? {},
24
+ supported: facilitatorConfig.supportedHeaders ?? {},
25
+ }),
26
+ }),
27
+ });
28
+ cacheTimestamp = now;
29
+ return cachedFacilitator;
30
+ }
31
+ export async function storeFacilitator(c, facilitatorConfig) {
32
+ await c.env.FOLDSET_CONFIG.put("facilitator", JSON.stringify(facilitatorConfig), {
33
+ expirationTtl: 60 * 60 * 3 + 60 * 30, // 3 hours + 30 minutes
34
+ });
35
+ }
package/dist/hono.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { paymentHandler } from "./payment/handler";
2
+ //# sourceMappingURL=hono.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/hono.js ADDED
@@ -0,0 +1 @@
1
+ export { paymentHandler } from "./payment/handler";
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { Hono } from 'hono';
2
- type Env = {
3
- FOLDSET_API_KEY: string;
4
- };
5
- declare const app: Hono<{
6
- Bindings: Env;
7
- }, import("hono/types").BlankSchema, "/">;
8
- export default app;
1
+ export type { Env } from "./types";
2
+ export type { Restriction } from "./restrictions";
3
+ export type { PaymentMethod } from "./payment-methods";
4
+ export type { FoldsetWebhook } from "./webhooks";
5
+ export type { EventPayload } from "./telemetry/logging";
6
+ export type { AiCrawler } from "./ai-crawlers";
7
+ export type { FacilitatorConfig } from "./facilitators";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AACnC,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,163 +1 @@
1
- // TODO rfradkin: Make a lot of these types not just functions
2
- import { Hono } from 'hono';
3
- import { x402ResourceServer, x402HTTPResourceServer } from '@x402/hono';
4
- import { registerExactEvmScheme } from '@x402/evm/exact/server';
5
- import { HTTPFacilitatorClient } from '@x402/core/server';
6
- import { HonoAdapter } from './adapter';
7
- async function fetchFoldsetAPI(endpoint, env) {
8
- const res = await fetch(`https://api.foldset.com/v1/${endpoint}`, {
9
- method: 'GET',
10
- headers: {
11
- Authorization: `Bearer ${env.FOLDSET_API_KEY}`,
12
- Accept: 'application/json',
13
- },
14
- });
15
- if (!res.ok) {
16
- throw new Error(`Foldset API (${endpoint}) failed: ${res.status}`);
17
- }
18
- return (await res.json());
19
- }
20
- async function getRestrictions(env) {
21
- const restrictionList = await fetchFoldsetAPI('restrictions', env);
22
- const restrictions = {};
23
- for (const restriction of restrictionList) {
24
- restrictions[restriction.pathname] = restriction.price;
25
- }
26
- return restrictions;
27
- }
28
- // TODO rfradkin: Should change to get addresses...
29
- async function getEVMAddress(env) {
30
- const data = await fetchFoldsetAPI('get-address', env);
31
- return data.address;
32
- }
33
- // TODO rfradkin: Add metadata for bazaar finding
34
- // https://x402.gitbook.io/x402/getting-started/quickstart-for-sellers
35
- function buildRoutesConfig(restrictions, payTo) {
36
- const routesConfig = {};
37
- for (const [pathname, price] of Object.entries(restrictions)) {
38
- routesConfig[pathname] = {
39
- accepts: [
40
- {
41
- scheme: 'exact',
42
- price: `$${price}`,
43
- network: 'eip155:84532',
44
- payTo,
45
- },
46
- ],
47
- description: 'Access to premium content',
48
- mimeType: 'application/json',
49
- };
50
- }
51
- return routesConfig;
52
- }
53
- let server = null;
54
- // Right now, load times are like 800ms, which is pretty slow since
55
- // static site is 200ms. Look into later, this call probably takes most of the time.
56
- // This is like this cause can'd do async calls at module level in cloudflare
57
- async function getHttpServer(routes, env) {
58
- // At some point allow users to dynamically choose facilitator
59
- // Setup endpoint for that
60
- if (!server) {
61
- // TODO rfradkin: Hard coded for now but consider making it configurable
62
- const facilitatorClient = new HTTPFacilitatorClient({
63
- url: 'https://x402.org/facilitator',
64
- });
65
- server = new x402ResourceServer(facilitatorClient);
66
- registerExactEvmScheme(server);
67
- }
68
- const httpServer = new x402HTTPResourceServer(server, routes);
69
- await httpServer.initialize();
70
- return httpServer;
71
- }
72
- const app = new Hono();
73
- // TODO rfradkin: This is still potentially quite slow since it is getting
74
- // each of these on each request regardless of whether the request is in
75
- // the restrictions or not. Should find a better solution down the road
76
- app.use('*', async (c, next) => {
77
- const [restrictions, payTo] = await Promise.all([
78
- getRestrictions(c.env),
79
- getEVMAddress(c.env),
80
- ]);
81
- const routes = buildRoutesConfig(restrictions, payTo);
82
- const httpServer = await getHttpServer(routes, c.env);
83
- const adapter = new HonoAdapter(c);
84
- const context = {
85
- adapter,
86
- path: c.req.path,
87
- method: c.req.method,
88
- paymentHeader: adapter.getHeader("payment-signature") || adapter.getHeader("x-payment"),
89
- };
90
- if (!httpServer.requiresPayment(context)) {
91
- return next();
92
- }
93
- const result = await httpServer.processHTTPRequest(context);
94
- switch (result.type) {
95
- case "no-payment-required":
96
- return next();
97
- case "payment-error":
98
- // Payment required but not provided or invalid
99
- const { response } = result;
100
- Object.entries(response.headers).forEach(([key, value]) => {
101
- c.header(key, value);
102
- });
103
- if (response.isHtml) {
104
- return c.html(response.body, response.status);
105
- }
106
- else {
107
- return c.json(response.body || {}, response.status);
108
- }
109
- case "payment-verified":
110
- // Payment is valid, need to wrap response for settlement
111
- const { paymentPayload, paymentRequirements } = result;
112
- await next();
113
- let res = c.res;
114
- // If the response from the protected route is >= 400, do not settle payment
115
- if (res.status >= 400) {
116
- return;
117
- }
118
- // Clear the response so we can modify headers
119
- c.res = undefined;
120
- try {
121
- const settleResult = await httpServer.processSettlement(paymentPayload, paymentRequirements);
122
- if (!settleResult.success) {
123
- // Settlement failed - do not return the protected resource
124
- res = c.json({
125
- error: "Settlement failed",
126
- details: settleResult.errorReason,
127
- }, 402);
128
- }
129
- else {
130
- // Settlement succeeded - add headers to response
131
- Object.entries(settleResult.headers).forEach(([key, value]) => {
132
- res.headers.set(key, value);
133
- });
134
- }
135
- }
136
- catch (error) {
137
- console.error(error);
138
- // If settlement fails, return an error response
139
- res = c.json({
140
- error: "Settlement failed",
141
- details: error instanceof Error ? error.message : "Unknown error",
142
- }, 402);
143
- }
144
- // Restore the response (potentially modified with settlement headers)
145
- c.res = res;
146
- return;
147
- }
148
- });
149
- app.all('*', async (c) => {
150
- const req = c.req.raw;
151
- const resp = await fetch(req);
152
- // This is cause the payment middleware needs to modify the headers to add the payment status
153
- // (since it tags the data response after the request with settlement related headers)
154
- // (ie success or failure)
155
- // and the fetch headers returned by cloudlflare is immutable, so causes an issue
156
- // TODO rfradkin: This could be cleaned up and made nicer, but works for now
157
- return new Response(resp.body, {
158
- status: resp.status,
159
- statusText: resp.statusText,
160
- headers: new Headers(resp.headers),
161
- });
162
- });
163
- export default app;
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { HTTPAdapter } from "@x402/core/server";
2
+ import { Context } from "hono";
3
+ export declare class HonoAdapter implements HTTPAdapter {
4
+ private c;
5
+ constructor(c: Context);
6
+ getHeader(name: string): string | undefined;
7
+ getMethod(): string;
8
+ getPath(): string;
9
+ getUrl(): string;
10
+ getAcceptHeader(): string;
11
+ getUserAgent(): string;
12
+ getQueryParams(): Record<string, string | string[]>;
13
+ getQueryParam(name: string): string | string[] | undefined;
14
+ getBody(): Promise<unknown>;
15
+ }
16
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/payment/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,qBAAa,WAAY,YAAW,WAAW;IACjC,OAAO,CAAC,CAAC;gBAAD,CAAC,EAAE,OAAO;IAE9B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3C,SAAS,IAAI,MAAM;IAInB,OAAO,IAAI,MAAM;IAIjB,MAAM,IAAI,MAAM;IAIhB,eAAe,IAAI,MAAM;IAIzB,YAAY,IAAI,MAAM;IAItB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IASnD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAIpD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;CAOlC"}
@@ -0,0 +1,43 @@
1
+ export class HonoAdapter {
2
+ c;
3
+ constructor(c) {
4
+ this.c = c;
5
+ }
6
+ getHeader(name) {
7
+ return this.c.req.header(name);
8
+ }
9
+ getMethod() {
10
+ return this.c.req.method;
11
+ }
12
+ getPath() {
13
+ return this.c.req.path;
14
+ }
15
+ getUrl() {
16
+ return this.c.req.url;
17
+ }
18
+ getAcceptHeader() {
19
+ return this.c.req.header("Accept") || "";
20
+ }
21
+ getUserAgent() {
22
+ return this.c.req.header("User-Agent") || "";
23
+ }
24
+ getQueryParams() {
25
+ const query = this.c.req.query();
26
+ const result = {};
27
+ for (const [key, value] of Object.entries(query)) {
28
+ result[key] = value;
29
+ }
30
+ return result;
31
+ }
32
+ getQueryParam(name) {
33
+ return this.c.req.query(name);
34
+ }
35
+ async getBody() {
36
+ try {
37
+ return await this.c.req.json();
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,6 @@
1
+ import type { Context } from "hono";
2
+ import type { Env } from "../types";
3
+ export declare function paymentHandler(c: Context<{
4
+ Bindings: Env;
5
+ }>): Promise<Response>;
6
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/payment/handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AA2GpC,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQ/E"}
@@ -0,0 +1,86 @@
1
+ import * as Sentry from "@sentry/cloudflare";
2
+ import { handleWebhook } from "../webhooks";
3
+ import { logVisitEvent } from "../telemetry/logging";
4
+ import { wrappedPaymentHandler } from "../telemetry/sentry";
5
+ import { HonoAdapter } from "./adapter";
6
+ import { getHttpServer } from "./setup";
7
+ import { handleSettlement } from "./settlement";
8
+ import { isAiCrawler } from "../ai-crawlers";
9
+ // TODO rfradkin: Open a PR in x402 github to change the handlePaymentError to protected or allow for passing parameteres to specify
10
+ // this behavior
11
+ function createPaymentContext(c) {
12
+ const adapter = new HonoAdapter(c);
13
+ return {
14
+ adapter,
15
+ path: c.req.path,
16
+ method: c.req.method,
17
+ paymentHeader: adapter.getHeader("PAYMENT-SIGNATURE") || adapter.getHeader("X-PAYMENT"),
18
+ };
19
+ }
20
+ function handlePaymentError(c, response) {
21
+ Object.entries(response.headers).forEach(([key, value]) => {
22
+ c.header(key, value);
23
+ });
24
+ // We always return a 200 even though the status code is 402
25
+ // AI crawlers tend to view the raw html only if a 200 is returned
26
+ // For now, we will return 200 errors while blocking the content even though
27
+ // we should be returning response.status as 402 here
28
+ // We also provide the headers and html to everyone, regardless of web browser status
29
+ return c.html(response.body, response.status);
30
+ // if (response.isHtml) {
31
+ // return c.html(response.body as string, response.status as 402));
32
+ // }
33
+ // return c.json(response.body || {}, response.status as 402);
34
+ }
35
+ async function paymentHandlerHelper(c) {
36
+ if (!c.env.FOLDSET_API_KEY) {
37
+ throw new Error("Missing required environment variable: FOLDSET_API_KEY. See https://docs.foldset.com/setup");
38
+ }
39
+ if (!c.env.FOLDSET_CONFIG) {
40
+ throw new Error("Missing required KV namespace binding: FOLDSET_CONFIG. See https://docs.foldset.com/setup");
41
+ }
42
+ Sentry.setTag("url", c.req.url);
43
+ Sentry.setTag("path", c.req.path);
44
+ Sentry.setTag("method", c.req.method);
45
+ if (c.req.method === "POST" && c.req.path === "/foldset/webhooks") {
46
+ return handleWebhook(c);
47
+ }
48
+ if (!(await isAiCrawler(c))) {
49
+ return await fetch(c.req.raw);
50
+ }
51
+ const httpServer = await getHttpServer(c);
52
+ if (!httpServer) {
53
+ return await fetch(c.req.raw);
54
+ }
55
+ const context = createPaymentContext(c);
56
+ if (!httpServer.requiresPayment(context)) {
57
+ return await fetch(c.req.raw);
58
+ }
59
+ const result = await httpServer.processHTTPRequest(context);
60
+ if (result.type === "no-payment-required") {
61
+ const response = await fetch(c.req.raw);
62
+ logVisitEvent(c, response);
63
+ return response;
64
+ }
65
+ if (result.type === "payment-error") {
66
+ const response = handlePaymentError(c, result.response);
67
+ logVisitEvent(c, response);
68
+ return response;
69
+ }
70
+ const { paymentPayload, paymentRequirements } = result;
71
+ const upstreamResponse = await fetch(c.req.raw);
72
+ const response = new Response(upstreamResponse.body, {
73
+ status: upstreamResponse.status,
74
+ statusText: upstreamResponse.statusText,
75
+ headers: new Headers(upstreamResponse.headers),
76
+ });
77
+ const settledResponse = await handleSettlement(c, httpServer, paymentPayload, paymentRequirements, response);
78
+ logVisitEvent(c, settledResponse);
79
+ return settledResponse;
80
+ }
81
+ export function paymentHandler(c) {
82
+ return wrappedPaymentHandler({
83
+ request: c.req.raw,
84
+ context: c.executionCtx,
85
+ }, () => paymentHandlerHelper(c));
86
+ }
@@ -0,0 +1,4 @@
1
+ import type { PaymentRequired } from "@x402/core/types";
2
+ import type { PaywallConfig } from "@x402/core/server";
3
+ export declare function generateHtml(paymentRequired: PaymentRequired, config?: PaywallConfig): string;
4
+ //# sourceMappingURL=paywall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../../src/payment/paywall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,wBAAgB,YAAY,CAAC,eAAe,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAyG7F"}
@@ -0,0 +1,99 @@
1
+ export function generateHtml(paymentRequired, config) {
2
+ const resource = paymentRequired.resource;
3
+ const description = resource?.description ?? "This resource requires payment";
4
+ const url = resource?.url ?? config?.currentUrl ?? "";
5
+ const accepts = paymentRequired.accepts ?? [];
6
+ // Group payment options by blockchain network (e.g., Base Mainnet, Solana Mainnet)
7
+ const optionsByNetwork = new Map();
8
+ for (const accept of accepts) {
9
+ const existing = optionsByNetwork.get(accept.network) ?? [];
10
+ existing.push(accept);
11
+ optionsByNetwork.set(accept.network, existing);
12
+ }
13
+ const paymentOptionsHtml = Array.from(optionsByNetwork.entries()).map(([_network, networkOptions]) => {
14
+ // Chain display name (e.g., "Base Mainnet", "Solana Mainnet")
15
+ const chainDisplayName = networkOptions[0].extra?.["chainDisplayName"] ?? "Unknown Network";
16
+ const chainCaip2Id = networkOptions[0].network;
17
+ // Recipient wallet address (where payments are sent)
18
+ const recipientAddresses = Array.from(new Set(networkOptions.map((opt) => opt.payTo)));
19
+ const recipientAddress = recipientAddresses[0] ?? "";
20
+ // Build list of accepted tokens for this network
21
+ const acceptedTokensHtml = networkOptions.map((accept) => {
22
+ // Token display name (e.g., "USD Coin", "USDC")
23
+ const tokenDisplayName = accept.extra?.["assetDisplayName"] ?? "Unknown Token";
24
+ // Token contract address (e.g., "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" for USDC on Base)
25
+ const tokenContractAddress = accept.asset;
26
+ // Amount in token's smallest unit (e.g., for USDC with 6 decimals: 1000000 = $1.00)
27
+ const rawTokenAmount = accept.amount;
28
+ const rawPrice = accept.extra?.["price"] ?? "Unknown Price";
29
+ return `
30
+ <tr>
31
+ <td>${tokenDisplayName}</td>
32
+ <td>${rawTokenAmount}</td>
33
+ <td>${rawPrice}</td>
34
+ <td><code>${tokenContractAddress}</code></td>
35
+ </tr>`;
36
+ }).join("");
37
+ return `
38
+ <section>
39
+ <h3>${chainDisplayName} (${chainCaip2Id})</h3>
40
+
41
+ <p><strong>Send payment to:</strong> <code>${recipientAddress}</code></p>
42
+
43
+ <table border="1" cellpadding="8" cellspacing="0">
44
+ <thead>
45
+ <tr>
46
+ <th>Token</th>
47
+ <th>Amount (on-chain units)</th>
48
+ <th>Price (USD)</th>
49
+ <th>Contract Address</th>
50
+ </tr>
51
+ </thead>
52
+ <tbody>${acceptedTokensHtml}
53
+ </tbody>
54
+ </table>
55
+ </section>`;
56
+ }).join("\n");
57
+ return `<!DOCTYPE html>
58
+ <html>
59
+ <head>
60
+ <title>HTTP 402 - Payment Required</title>
61
+ <style>
62
+ body { font-family: system-ui, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; }
63
+ code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }
64
+ table { border-collapse: collapse; margin: 10px 0; }
65
+ th { background: #f5f5f5; text-align: left; }
66
+ section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
67
+ h3 { margin-top: 0; }
68
+ .notice { background: #f7f7f7; border: 1px solid #e2e2e2; border-radius: 6px; padding: 10px 12px; margin: 16px 0; color: #555; font-size: 0.92em; }
69
+ .notice strong { display: block; margin-bottom: 4px; font-weight: 600; }
70
+ .notice p { margin: 0; }
71
+ footer { margin-top: 30px; padding-top: 15px; border-top: 1px solid #eee; font-size: 0.9em; color: #666; }
72
+ </style>
73
+ </head>
74
+ <body>
75
+ <h1>HTTP 402 - Payment Required</h1>
76
+
77
+ <p>This content requires a payment via the <a href="https://github.com/coinbase/x402">x402 protocol</a>.</p>
78
+
79
+ <h2>Protected Resource</h2>
80
+ <ul>
81
+ <li><strong>URL:</strong> <code>${url}</code></li>
82
+ <li><strong>Description:</strong> ${description}</li>
83
+ </ul>
84
+
85
+ <h2>Accepted Payment Options</h2>
86
+ <p>Pay using any of the following blockchain networks and tokens:</p>
87
+ ${paymentOptionsHtml}
88
+
89
+ <div class="notice">
90
+ <strong>Important for payment libraries</strong>
91
+ <p>An HTTP 200 response was returned to encourage agents to view this page. Please treat it as you would a 402 error. All required x402 headers are present. Some unmodified x402 libraries require a 402 response to initiate payment; instead of checking only the response code, check for the presence of the x402 headers.</p>
92
+ </div>
93
+
94
+ <footer>
95
+ <p><strong>Powered by:</strong> <a href="https://www.foldset.com">Foldset</a></p>
96
+ </footer>
97
+ </body>
98
+ </html>`;
99
+ }
@@ -0,0 +1,5 @@
1
+ import type { RoutesConfig } from "@x402/core/http";
2
+ import type { Restriction } from "../restrictions";
3
+ import type { PaymentMethod } from "../payment-methods";
4
+ export declare function buildRoutesConfig(restrictions: Restriction[], paymentMethods: PaymentMethod[]): RoutesConfig;
5
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/payment/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOxD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,WAAW,EAAE,EAC3B,cAAc,EAAE,aAAa,EAAE,GAC9B,YAAY,CA4Bd"}
@@ -0,0 +1,31 @@
1
+ function priceToAmount(priceUsd, decimals) {
2
+ const amount = priceUsd * Math.pow(10, decimals);
3
+ return Math.round(amount).toString();
4
+ }
5
+ export function buildRoutesConfig(restrictions, paymentMethods) {
6
+ const routesConfig = {};
7
+ for (const restriction of restrictions) {
8
+ routesConfig[restriction.path] = {
9
+ accepts: paymentMethods.map((paymentMethod) => ({
10
+ scheme: restriction.scheme,
11
+ price: {
12
+ amount: priceToAmount(restriction.price, paymentMethod.decimals),
13
+ asset: paymentMethod.contract_address,
14
+ // eip712 required extra
15
+ extra: {
16
+ ...paymentMethod.extra,
17
+ decimals: paymentMethod.decimals,
18
+ chainDisplayName: paymentMethod.chain_display_name,
19
+ assetDisplayName: paymentMethod.asset_display_name,
20
+ price: restriction.price,
21
+ },
22
+ },
23
+ network: paymentMethod.caip2_id,
24
+ payTo: paymentMethod.circle_wallet_address,
25
+ })),
26
+ description: restriction.description,
27
+ mimeType: "application/json",
28
+ };
29
+ }
30
+ return routesConfig;
31
+ }
@@ -0,0 +1,8 @@
1
+ import type { PaymentPayload, PaymentRequirements } from "@x402/core/types";
2
+ import type { x402HTTPResourceServer } from "@x402/hono";
3
+ import type { Context } from "hono";
4
+ import type { Env } from "../types";
5
+ export declare function handleSettlement(c: Context<{
6
+ Bindings: Env;
7
+ }>, httpServer: x402HTTPResourceServer, paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements, res: Response): Promise<Response>;
8
+ //# sourceMappingURL=settlement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settlement.d.ts","sourceRoot":"","sources":["../../src/payment/settlement.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,wBAAsB,gBAAgB,CACpC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,EAC7B,UAAU,EAAE,sBAAsB,EAClC,cAAc,EAAE,cAAc,EAC9B,mBAAmB,EAAE,mBAAmB,EACxC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,QAAQ,CAAC,CAmCnB"}