@foldset/cloudflare 0.0.1 → 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 -37
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +1 -237
  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,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"}
@@ -0,0 +1,27 @@
1
+ import * as Sentry from "@sentry/cloudflare";
2
+ export async function handleSettlement(c, httpServer, paymentPayload, paymentRequirements, res) {
3
+ if (res.status >= 400) {
4
+ return res;
5
+ }
6
+ try {
7
+ const settleResult = await httpServer.processSettlement(paymentPayload, paymentRequirements);
8
+ if (!settleResult.success) {
9
+ Sentry.captureException(new Error(`Settlement failed: ${settleResult.errorReason}`));
10
+ return c.json({
11
+ error: "Settlement failed",
12
+ details: settleResult.errorReason,
13
+ }, 402);
14
+ }
15
+ Object.entries(settleResult.headers).forEach(([key, value]) => {
16
+ res.headers.set(key, value);
17
+ });
18
+ return res;
19
+ }
20
+ catch (error) {
21
+ Sentry.captureException(error);
22
+ return c.json({
23
+ error: "Settlement failed",
24
+ details: error instanceof Error ? error.message : "Unknown error",
25
+ }, 402);
26
+ }
27
+ }
@@ -0,0 +1,7 @@
1
+ import type { Context } from "hono";
2
+ import { x402HTTPResourceServer } from "@x402/hono";
3
+ import type { Env } from "../types";
4
+ export declare function getHttpServer(c: Context<{
5
+ Bindings: Env;
6
+ }>): Promise<x402HTTPResourceServer | null>;
7
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/payment/setup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAEL,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAMpB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AA+DpC,wBAAsB,aAAa,CACjC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAC5B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CA0CxC"}
@@ -0,0 +1,85 @@
1
+ import { x402ResourceServer, x402HTTPResourceServer, } from "@x402/hono";
2
+ import { registerExactEvmScheme } from "@x402/evm/exact/server";
3
+ import { registerExactSvmScheme } from "@x402/svm/exact/server";
4
+ import { getRestrictions } from "../restrictions";
5
+ import { getPaymentMethods } from "../payment-methods";
6
+ import { buildRoutesConfig } from "./routes";
7
+ import { getFacilitator } from "../facilitators";
8
+ import { generateHtml } from "./paywall";
9
+ const paywallProvider = {
10
+ generateHtml
11
+ };
12
+ let cachedHttpServer = null;
13
+ let cachedRestrictions = null;
14
+ let cachedPaymentMethods = null;
15
+ /**
16
+ * Custom createHTTPResponse implementation for Foldset
17
+ * Monkey-patched onto x402HTTPResourceServer instances
18
+ */
19
+ function createFoldsetHTTPResponse(paymentRequired, isWebBrowser, paywallConfig, customHtml, unpaidResponse) {
20
+ // @ts-expect-error - accessing private method
21
+ const html = this.generatePaywallHTML(paymentRequired, paywallConfig, customHtml);
22
+ // Always generate and provide
23
+ // if (isWebBrowser) {
24
+ // // @ts-expect-error - accessing private method
25
+ // const html = this.generatePaywallHTML(paymentRequired, paywallConfig, customHtml);
26
+ // return {
27
+ // status: 402,
28
+ // headers: { "Content-Type": "text/html" },
29
+ // body: html,
30
+ // isHtml: true,
31
+ // };
32
+ // }
33
+ // @ts-expect-error - accessing private method
34
+ const response = this.createHTTPPaymentRequiredResponse(paymentRequired);
35
+ // We don't provide a callback
36
+ // Use callback result if provided, otherwise default to JSON with empty object
37
+ // const contentType = unpaidResponse ? unpaidResponse.contentType : "application/json";
38
+ // const body = unpaidResponse ? unpaidResponse.body : {};
39
+ // Status should be 402 but we return 200 so AI crawlers view the payment instructions
40
+ return {
41
+ status: 200,
42
+ headers: {
43
+ "Content-Type": "text/html",
44
+ ...response.headers,
45
+ },
46
+ body: html,
47
+ isHtml: true,
48
+ };
49
+ }
50
+ // Maybe this function is never needed and can just updated cachedHttpServer on changes to upstream configs
51
+ // ie consider changing to a chained pattern
52
+ export async function getHttpServer(c) {
53
+ const restrictions = await getRestrictions(c);
54
+ const paymentMethods = await getPaymentMethods(c);
55
+ if (restrictions === null || paymentMethods === null) {
56
+ return null;
57
+ }
58
+ if (cachedHttpServer &&
59
+ restrictions === cachedRestrictions &&
60
+ paymentMethods === cachedPaymentMethods) {
61
+ return cachedHttpServer;
62
+ }
63
+ const facilitator = await getFacilitator(c);
64
+ if (facilitator === null) {
65
+ return null;
66
+ }
67
+ // This could be pulled out
68
+ const server = new x402ResourceServer(facilitator);
69
+ registerExactEvmScheme(server);
70
+ registerExactSvmScheme(server);
71
+ const routesConfig = buildRoutesConfig(restrictions, paymentMethods);
72
+ const httpServer = new x402HTTPResourceServer(server, routesConfig);
73
+ // Monkey-patch createHTTPResponse with our custom implementation
74
+ // @ts-expect-error - overriding private method
75
+ httpServer.createHTTPResponse = createFoldsetHTTPResponse;
76
+ // TODO rfradkin: This is probably the slowest part of the cold start. Figure out how to speed this up.
77
+ // Also this is slow on every request where its updated.
78
+ // Consider looking into creating a serverless facilitator so don't have to request outside the serverless environment
79
+ await httpServer.initialize();
80
+ httpServer.registerPaywallProvider(paywallProvider);
81
+ cachedHttpServer = httpServer;
82
+ cachedRestrictions = restrictions;
83
+ cachedPaymentMethods = paymentMethods;
84
+ return httpServer;
85
+ }
@@ -0,0 +1,18 @@
1
+ import type { Context } from "hono";
2
+ import type { Env } from "../types";
3
+ export interface PaymentMethod {
4
+ caip2_id: string;
5
+ decimals: number;
6
+ contract_address: string;
7
+ circle_wallet_address: string;
8
+ extra?: Record<string, string>;
9
+ chain_display_name: string;
10
+ asset_display_name: string;
11
+ }
12
+ export declare function getPaymentMethods(c: Context<{
13
+ Bindings: Env;
14
+ }>): Promise<PaymentMethod[] | null>;
15
+ export declare function storePaymentMethods(c: Context<{
16
+ Bindings: Env;
17
+ }>, paymentMethods: PaymentMethod[]): Promise<void>;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/payment-methods/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,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAOD,wBAAsB,iBAAiB,CACrC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAC5B,OAAO,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,CAUjC;AAED,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,EAC7B,cAAc,EAAE,aAAa,EAAE,GAC9B,OAAO,CAAC,IAAI,CAAC,CAIf"}
@@ -0,0 +1,18 @@
1
+ const CACHE_TTL_MS = 30_000;
2
+ let cachedPaymentMethods = null;
3
+ let cacheTimestamp = 0;
4
+ export async function getPaymentMethods(c) {
5
+ const now = Date.now();
6
+ if (cachedPaymentMethods !== null && now - cacheTimestamp < CACHE_TTL_MS) {
7
+ return cachedPaymentMethods;
8
+ }
9
+ const response = await c.env.FOLDSET_CONFIG.get("payment-methods");
10
+ cachedPaymentMethods = response ? JSON.parse(response) : null;
11
+ cacheTimestamp = now;
12
+ return cachedPaymentMethods;
13
+ }
14
+ export async function storePaymentMethods(c, paymentMethods) {
15
+ await c.env.FOLDSET_CONFIG.put("payment-methods", JSON.stringify(paymentMethods), {
16
+ expirationTtl: 60 * 60 * 3 + 60 * 30, // 3 hours + 30 minutes
17
+ });
18
+ }
@@ -0,0 +1,16 @@
1
+ import type { Context } from "hono";
2
+ import type { Env } from "../types";
3
+ export interface Restriction {
4
+ host: string;
5
+ path: string;
6
+ description: string;
7
+ price: number;
8
+ scheme: string;
9
+ }
10
+ export declare function getRestrictions(c: Context<{
11
+ Bindings: Env;
12
+ }>): Promise<Restriction[] | null>;
13
+ export declare function storeRestrictions(c: Context<{
14
+ Bindings: Env;
15
+ }>, restrictions: Restriction[]): Promise<void>;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/restrictions/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,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAOD,wBAAsB,eAAe,CACnC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAC5B,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAU/B;AAED,wBAAsB,iBAAiB,CACrC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,EAC7B,YAAY,EAAE,WAAW,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAIf"}
@@ -0,0 +1,18 @@
1
+ const CACHE_TTL_MS = 30_000;
2
+ let cachedRestrictions = null;
3
+ let cacheTimestamp = 0;
4
+ export async function getRestrictions(c) {
5
+ const now = Date.now();
6
+ if (cachedRestrictions !== null && now - cacheTimestamp < CACHE_TTL_MS) {
7
+ return cachedRestrictions;
8
+ }
9
+ const response = await c.env.FOLDSET_CONFIG.get("restrictions");
10
+ cachedRestrictions = response ? JSON.parse(response) : null;
11
+ cacheTimestamp = now;
12
+ return cachedRestrictions;
13
+ }
14
+ export async function storeRestrictions(c, restrictions) {
15
+ await c.env.FOLDSET_CONFIG.put("restrictions", JSON.stringify(restrictions), {
16
+ expirationTtl: 60 * 60 * 3 + 60 * 30, // 3 hours + 30 minutes
17
+ });
18
+ }
@@ -0,0 +1,18 @@
1
+ import type { Context } from "hono";
2
+ import type { Env } from "../types";
3
+ export type EventPayload = {
4
+ method: string;
5
+ status_code: number;
6
+ user_agent: string | null;
7
+ referer?: string | null;
8
+ href: string;
9
+ hostname: string;
10
+ pathname: string;
11
+ search: string;
12
+ ip_address?: string | null;
13
+ payment_response?: string;
14
+ };
15
+ export declare function logVisitEvent(c: Context<{
16
+ Bindings: Env;
17
+ }>, response: Response): void;
18
+ //# sourceMappingURL=logging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/telemetry/logging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAIpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAIpC,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,QA8C9E"}
@@ -0,0 +1,47 @@
1
+ import * as Sentry from "@sentry/cloudflare";
2
+ import { API_BASE_URL } from "../config";
3
+ const PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
4
+ export function logVisitEvent(c, response) {
5
+ const url = new URL(c.req.url);
6
+ const ipAddressHeader = c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for");
7
+ const ipAddress = ipAddressHeader?.split(",")[0]?.trim() || null;
8
+ const paymentResponse = response.headers.get(PAYMENT_RESPONSE_HEADER) || undefined;
9
+ const payload = {
10
+ method: c.req.method,
11
+ status_code: response.status,
12
+ user_agent: c.req.header("user-agent") || null,
13
+ referer: c.req.header("referer") || null,
14
+ href: url.href,
15
+ hostname: url.hostname,
16
+ pathname: url.pathname,
17
+ search: url.search,
18
+ ip_address: ipAddress,
19
+ ...(paymentResponse ? { payment_response: paymentResponse } : {}),
20
+ };
21
+ const requestPromise = fetch(`${API_BASE_URL}/events`, {
22
+ method: "POST",
23
+ headers: {
24
+ Authorization: `Bearer ${c.env.FOLDSET_API_KEY}`,
25
+ "Content-Type": "application/json",
26
+ Accept: "application/json",
27
+ },
28
+ body: JSON.stringify(payload),
29
+ }).catch((error) => {
30
+ Sentry.captureException(error, {
31
+ extra: {
32
+ method: "POST",
33
+ url: `${API_BASE_URL}/events`,
34
+ headers: {
35
+ Authorization: `Bearer ${c.env.FOLDSET_API_KEY}`,
36
+ "Content-Type": "application/json",
37
+ Accept: "application/json",
38
+ },
39
+ body: JSON.stringify(payload),
40
+ },
41
+ tags: {
42
+ endpoint: "/events",
43
+ },
44
+ });
45
+ });
46
+ c.executionCtx.waitUntil(requestPromise);
47
+ }
@@ -0,0 +1,8 @@
1
+ import type { ExecutionContext } from "@cloudflare/workers-types";
2
+ type SentryWrapperArgs = {
3
+ request: Request;
4
+ context: ExecutionContext;
5
+ };
6
+ export declare const wrappedPaymentHandler: (wrapperArgs: SentryWrapperArgs, handler: () => Promise<Response>) => Promise<Response>;
7
+ export {};
8
+ //# sourceMappingURL=sentry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/telemetry/sentry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAIlE,KAAK,iBAAiB,GAAG;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,gBAAgB,CAAC;CAC3B,CAAC;AAuBF,eAAO,MAAM,qBAAqB,gBAnBX,iBAAiB,WAAW,MAAM,OAAO,CAAC,QAAQ,CAAC,sBAmBE,CAAC"}
@@ -0,0 +1,19 @@
1
+ import * as Sentry from "@sentry/cloudflare";
2
+ const SENTRY_DSN = "https://f68380669974dab9bbcf5aec9414bcb8@o4510648631296000.ingest.us.sentry.io/4510718170038272";
3
+ // TODO rfradkin: Add webhook support for updating this
4
+ function createWrappedPaymentHandler(dsn) {
5
+ return (wrapperArgs, handler) => {
6
+ if (!dsn) {
7
+ return handler();
8
+ }
9
+ return Sentry.wrapRequestHandler({
10
+ options: {
11
+ dsn,
12
+ sendDefaultPii: true,
13
+ },
14
+ request: wrapperArgs.request,
15
+ context: wrapperArgs.context,
16
+ }, handler);
17
+ };
18
+ }
19
+ export const wrappedPaymentHandler = createWrappedPaymentHandler(SENTRY_DSN);
package/dist/types.d.ts CHANGED
@@ -1,10 +1,6 @@
1
- export type Price = number;
2
- export type Pathname = string;
3
- export type Restriction = {
4
- price: Price;
5
- pathname: Pathname;
6
- };
7
- export type Restrictions = Record<Pathname, Price>;
8
- export type AddressResponse = {
9
- address: string;
1
+ import type { KVNamespace } from "@cloudflare/workers-types";
2
+ export type Env = {
3
+ FOLDSET_API_KEY: string;
4
+ FOLDSET_CONFIG: KVNamespace;
10
5
  };
6
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,MAAM,GAAG,GAAG;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,WAAW,CAAC;CAC7B,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { Context } from "hono";
2
+ import type { Env } from "../types";
3
+ import { type Restriction } from "../restrictions";
4
+ import { type PaymentMethod } from "../payment-methods";
5
+ import { type AiCrawler } from "../ai-crawlers";
6
+ import { type FacilitatorConfig } from "../facilitators";
7
+ export type FoldsetWebhook = {
8
+ event_type: "restrictions";
9
+ event_object: Restriction[];
10
+ } | {
11
+ event_type: "payment-methods";
12
+ event_object: PaymentMethod[];
13
+ } | {
14
+ event_type: "ai-crawlers";
15
+ event_object: AiCrawler[];
16
+ } | {
17
+ event_type: "facilitator";
18
+ event_object: FacilitatorConfig;
19
+ };
20
+ export declare function handleWebhook(c: Context<{
21
+ Bindings: Env;
22
+ }>): Promise<Response>;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,KAAK,aAAa,EAAuB,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,KAAK,iBAAiB,EAAoB,MAAM,iBAAiB,CAAC;AAE3E,MAAM,MAAM,cAAc,GACtB;IAAE,UAAU,EAAE,cAAc,CAAC;IAAC,YAAY,EAAE,WAAW,EAAE,CAAA;CAAE,GAC3D;IAAE,UAAU,EAAE,iBAAiB,CAAC;IAAC,YAAY,EAAE,aAAa,EAAE,CAAA;CAAE,GAChE;IAAE,UAAU,EAAE,aAAa,CAAC;IAAC,YAAY,EAAE,SAAS,EAAE,CAAA;CAAE,GACxD;IAAE,UAAU,EAAE,aAAa,CAAC;IAAC,YAAY,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAgCnE,wBAAsB,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0BpF"}
@@ -0,0 +1,50 @@
1
+ import { storeRestrictions } from "../restrictions";
2
+ import { storePaymentMethods } from "../payment-methods";
3
+ import { storeAiCrawlers } from "../ai-crawlers";
4
+ import { storeFacilitator } from "../facilitators";
5
+ async function verifySignature(body, signature, apiKey) {
6
+ const encoder = new TextEncoder();
7
+ const keyHashBuffer = await crypto.subtle.digest("SHA-256", encoder.encode(apiKey));
8
+ const hashedKeyHex = Array.from(new Uint8Array(keyHashBuffer))
9
+ .map((b) => b.toString(16).padStart(2, "0"))
10
+ .join("");
11
+ const hmacKey = await crypto.subtle.importKey("raw", encoder.encode(hashedKeyHex), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
12
+ const expectedSig = await crypto.subtle.sign("HMAC", hmacKey, encoder.encode(body));
13
+ const expectedHex = Array.from(new Uint8Array(expectedSig))
14
+ .map((b) => b.toString(16).padStart(2, "0"))
15
+ .join("");
16
+ // Timing-safe comparison
17
+ if (signature.length !== expectedHex.length)
18
+ return false;
19
+ let result = 0;
20
+ for (let i = 0; i < signature.length; i++) {
21
+ result |= signature.charCodeAt(i) ^ expectedHex.charCodeAt(i);
22
+ }
23
+ return result === 0;
24
+ }
25
+ export async function handleWebhook(c) {
26
+ const signature = c.req.header("X-Foldset-Signature");
27
+ if (!signature) {
28
+ return new Response("Missing signature", { status: 401 });
29
+ }
30
+ const body = await c.req.text();
31
+ const isValid = await verifySignature(body, signature, c.env.FOLDSET_API_KEY);
32
+ if (!isValid) {
33
+ return new Response("Invalid signature", { status: 401 });
34
+ }
35
+ const webhook = JSON.parse(body);
36
+ // Fails the entire webhook if the put fails. Not standard but think it makes sense for now
37
+ if (webhook.event_type === "restrictions") {
38
+ await storeRestrictions(c, webhook.event_object);
39
+ }
40
+ else if (webhook.event_type === "payment-methods") {
41
+ await storePaymentMethods(c, webhook.event_object);
42
+ }
43
+ else if (webhook.event_type === "ai-crawlers") {
44
+ await storeAiCrawlers(c, webhook.event_object);
45
+ }
46
+ else if (webhook.event_type === "facilitator") {
47
+ await storeFacilitator(c, webhook.event_object);
48
+ }
49
+ return new Response("Ok", { status: 200 });
50
+ }
package/package.json CHANGED
@@ -1,34 +1,37 @@
1
1
  {
2
2
  "name": "@foldset/cloudflare",
3
- "version": "0.0.1",
4
- "description": "SDK for creating Foldset-enabled Cloudflare Workers",
5
- "type": "module",
3
+ "version": "0.0.3",
4
+ "private": false,
6
5
  "main": "./dist/index.js",
7
6
  "types": "./dist/index.d.ts",
8
7
  "exports": {
9
8
  ".": {
10
9
  "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./hono": {
13
+ "types": "./dist/hono.d.ts",
14
+ "default": "./dist/hono.js"
12
15
  }
13
16
  },
14
17
  "files": [
15
18
  "dist"
16
19
  ],
17
- "keywords": [
18
- "foldset",
19
- "cloudflare",
20
- "workers",
21
- "x402"
22
- ],
23
- "license": "MIT",
20
+ "scripts": {
21
+ "typegen": "wrangler types --path types/worker-configuration.d.ts",
22
+ "build": "tsc -p tsconfig.json --noEmit false --outDir dist --rootDir src"
23
+ },
24
24
  "dependencies": {
25
- "@x402/core": "^2.1.0",
26
- "@x402/evm": "^2.1.0",
27
- "@x402/hono": "^2.1.0",
28
- "hono": "^4.11.3",
29
- "p-memoize": "^8.0.0"
25
+ "@cloudflare/workers-types": "^4.20260117.0",
26
+ "@sentry/cloudflare": "^10.34.0",
27
+ "@x402/core": "^2.2.0",
28
+ "@x402/evm": "^2.2.0",
29
+ "@x402/hono": "^2.2.0",
30
+ "@x402/svm": "^2.2.0",
31
+ "hono": "^4.11.4"
30
32
  },
31
- "scripts": {
32
- "build": "tsc"
33
+ "devDependencies": {
34
+ "typescript": "^5.9.3",
35
+ "wrangler": "^4.59.1"
33
36
  }
34
37
  }
package/README.md DELETED
@@ -1 +0,0 @@
1
- # Typescript library for foldset?