@foldset/core 0.1.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/dist/api.d.ts +5 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +18 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +90 -0
- package/dist/handler.d.ts +8 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +85 -0
- package/dist/health.d.ts +3 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +11 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/mcp.d.ts +50 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +138 -0
- package/dist/paywall.d.ts +3 -0
- package/dist/paywall.d.ts.map +1 -0
- package/dist/paywall.js +87 -0
- package/dist/routes.d.ts +8 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +34 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +86 -0
- package/dist/store.d.ts +9 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +25 -0
- package/dist/telemetry.d.ts +7 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +53 -0
- package/dist/types.d.ts +113 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/web.d.ts +5 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +5 -0
- package/package.json +47 -0
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ApiRestriction, PaymentMethod, ProcessRequestResult } from "./types";
|
|
2
|
+
export declare function formatApiPaymentError(result: Extract<ProcessRequestResult, {
|
|
3
|
+
type: "payment-error";
|
|
4
|
+
}>, restriction: ApiRestriction, paymentMethods: PaymentMethod[], termsOfServiceUrl?: string): void;
|
|
5
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEnF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,EAChE,WAAW,EAAE,cAAc,EAC3B,cAAc,EAAE,aAAa,EAAE,EAC/B,iBAAiB,CAAC,EAAE,MAAM,GACzB,IAAI,CAiBN"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function formatApiPaymentError(result, restriction, paymentMethods, termsOfServiceUrl) {
|
|
2
|
+
result.response.body = JSON.stringify({
|
|
3
|
+
error: "payment_required",
|
|
4
|
+
...result.metadata,
|
|
5
|
+
message: restriction.description,
|
|
6
|
+
price: restriction.price,
|
|
7
|
+
...(termsOfServiceUrl && { terms_of_service_url: termsOfServiceUrl }),
|
|
8
|
+
payment_methods: paymentMethods.map((pm) => ({
|
|
9
|
+
network: pm.caip2_id,
|
|
10
|
+
asset: pm.contract_address,
|
|
11
|
+
decimals: pm.decimals,
|
|
12
|
+
pay_to: pm.circle_wallet_address,
|
|
13
|
+
chain: pm.chain_display_name,
|
|
14
|
+
asset_name: pm.asset_display_name,
|
|
15
|
+
})),
|
|
16
|
+
});
|
|
17
|
+
result.response.headers["Content-Type"] = "application/json";
|
|
18
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { HTTPFacilitatorClient } from "@x402/core/server";
|
|
2
|
+
import type { Bot, ConfigStore, HostConfig, PaymentMethod, ProcessRequestResult, RequestMetadata, Restriction } from "./types";
|
|
3
|
+
export declare const CACHE_TTL_MS = 30000;
|
|
4
|
+
export declare const API_BASE_URL = "https://api.foldset.com";
|
|
5
|
+
export declare function buildRequestMetadata(): RequestMetadata;
|
|
6
|
+
export declare function noPaymentRequired(metadata: RequestMetadata): ProcessRequestResult;
|
|
7
|
+
export declare class CachedConfigManager<T> {
|
|
8
|
+
protected configStore: ConfigStore;
|
|
9
|
+
protected key: string;
|
|
10
|
+
protected fallback: T;
|
|
11
|
+
protected cached: T;
|
|
12
|
+
protected cacheTimestamp: number;
|
|
13
|
+
constructor(configStore: ConfigStore, key: string, fallback: T);
|
|
14
|
+
protected isCacheValid(): boolean;
|
|
15
|
+
protected deserialize(raw: string): T;
|
|
16
|
+
get(): Promise<T>;
|
|
17
|
+
}
|
|
18
|
+
export declare class HostConfigManager extends CachedConfigManager<HostConfig | null> {
|
|
19
|
+
constructor(store: ConfigStore);
|
|
20
|
+
}
|
|
21
|
+
export declare class RestrictionsManager extends CachedConfigManager<Restriction[]> {
|
|
22
|
+
constructor(store: ConfigStore);
|
|
23
|
+
}
|
|
24
|
+
export declare class PaymentMethodsManager extends CachedConfigManager<PaymentMethod[]> {
|
|
25
|
+
constructor(store: ConfigStore);
|
|
26
|
+
}
|
|
27
|
+
export declare class BotsManager extends CachedConfigManager<Bot[]> {
|
|
28
|
+
constructor(store: ConfigStore);
|
|
29
|
+
protected deserialize(raw: string): Bot[];
|
|
30
|
+
matchBot(userAgent: string): Promise<Bot | null>;
|
|
31
|
+
}
|
|
32
|
+
export declare class FacilitatorManager extends CachedConfigManager<HTTPFacilitatorClient | null> {
|
|
33
|
+
constructor(store: ConfigStore);
|
|
34
|
+
protected deserialize(raw: string): HTTPFacilitatorClient;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAK1D,OAAO,KAAK,EACV,GAAG,EACH,WAAW,EAEX,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,eAAe,EACf,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,YAAY,QAAS,CAAC;AAEnC,eAAO,MAAM,YAAY,4BAA4B,CAAC;AAEtD,wBAAgB,oBAAoB,IAAI,eAAe,CAMtD;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,GAAG,oBAAoB,CAEjF;AAED,qBAAa,mBAAmB,CAAC,CAAC;IAK9B,SAAS,CAAC,WAAW,EAAE,WAAW;IAClC,SAAS,CAAC,GAAG,EAAE,MAAM;IACrB,SAAS,CAAC,QAAQ,EAAE,CAAC;IANvB,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IACpB,SAAS,CAAC,cAAc,SAAK;gBAGjB,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC;IAKvB,SAAS,CAAC,YAAY,IAAI,OAAO;IAIjC,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC;IAI/B,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;CAOxB;AAED,qBAAa,iBAAkB,SAAQ,mBAAmB,CAAC,UAAU,GAAG,IAAI,CAAC;gBAC/D,KAAK,EAAE,WAAW;CAG/B;AAED,qBAAa,mBAAoB,SAAQ,mBAAmB,CAAC,WAAW,EAAE,CAAC;gBAC7D,KAAK,EAAE,WAAW;CAG/B;AAED,qBAAa,qBAAsB,SAAQ,mBAAmB,CAAC,aAAa,EAAE,CAAC;gBACjE,KAAK,EAAE,WAAW;CAG/B;AAED,qBAAa,WAAY,SAAQ,mBAAmB,CAAC,GAAG,EAAE,CAAC;gBAC7C,KAAK,EAAE,WAAW;cAIX,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE;IAK5C,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;CAKvD;AAED,qBAAa,kBAAmB,SAAQ,mBAAmB,CAAC,qBAAqB,GAAG,IAAI,CAAC;gBAC3E,KAAK,EAAE,WAAW;cAIX,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB;CAiBnE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { HTTPFacilitatorClient } from "@x402/core/server";
|
|
2
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
3
|
+
const PACKAGE_VERSION = packageJson.version;
|
|
4
|
+
export const CACHE_TTL_MS = 30_000;
|
|
5
|
+
export const API_BASE_URL = "https://api.foldset.com";
|
|
6
|
+
export function buildRequestMetadata() {
|
|
7
|
+
return {
|
|
8
|
+
version: PACKAGE_VERSION,
|
|
9
|
+
request_id: crypto.randomUUID(),
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function noPaymentRequired(metadata) {
|
|
14
|
+
return { type: "no-payment-required", metadata };
|
|
15
|
+
}
|
|
16
|
+
export class CachedConfigManager {
|
|
17
|
+
configStore;
|
|
18
|
+
key;
|
|
19
|
+
fallback;
|
|
20
|
+
cached;
|
|
21
|
+
cacheTimestamp = 0;
|
|
22
|
+
constructor(configStore, key, fallback) {
|
|
23
|
+
this.configStore = configStore;
|
|
24
|
+
this.key = key;
|
|
25
|
+
this.fallback = fallback;
|
|
26
|
+
this.cached = fallback;
|
|
27
|
+
}
|
|
28
|
+
isCacheValid() {
|
|
29
|
+
return this.cacheTimestamp > 0 && Date.now() - this.cacheTimestamp < CACHE_TTL_MS;
|
|
30
|
+
}
|
|
31
|
+
deserialize(raw) {
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
async get() {
|
|
35
|
+
if (this.isCacheValid())
|
|
36
|
+
return this.cached;
|
|
37
|
+
const raw = await this.configStore.get(this.key);
|
|
38
|
+
this.cached = raw ? this.deserialize(raw) : this.fallback;
|
|
39
|
+
this.cacheTimestamp = Date.now();
|
|
40
|
+
return this.cached;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class HostConfigManager extends CachedConfigManager {
|
|
44
|
+
constructor(store) {
|
|
45
|
+
super(store, "host-config", null);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class RestrictionsManager extends CachedConfigManager {
|
|
49
|
+
constructor(store) {
|
|
50
|
+
super(store, "restrictions", []);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class PaymentMethodsManager extends CachedConfigManager {
|
|
54
|
+
constructor(store) {
|
|
55
|
+
super(store, "payment-methods", []);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class BotsManager extends CachedConfigManager {
|
|
59
|
+
constructor(store) {
|
|
60
|
+
super(store, "bots", []);
|
|
61
|
+
}
|
|
62
|
+
deserialize(raw) {
|
|
63
|
+
const parsed = JSON.parse(raw);
|
|
64
|
+
return parsed.map((b) => ({ ...b, user_agent: b.user_agent.toLowerCase() }));
|
|
65
|
+
}
|
|
66
|
+
async matchBot(userAgent) {
|
|
67
|
+
const bots = await this.get();
|
|
68
|
+
const ua = userAgent.toLowerCase();
|
|
69
|
+
return bots.find((bot) => ua.includes(bot.user_agent)) ?? null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class FacilitatorManager extends CachedConfigManager {
|
|
73
|
+
constructor(store) {
|
|
74
|
+
super(store, "facilitator", null);
|
|
75
|
+
}
|
|
76
|
+
deserialize(raw) {
|
|
77
|
+
const config = JSON.parse(raw);
|
|
78
|
+
const hasAuthHeaders = config.verifyHeaders || config.settleHeaders || config.supportedHeaders;
|
|
79
|
+
return new HTTPFacilitatorClient({
|
|
80
|
+
url: config.url,
|
|
81
|
+
...(hasAuthHeaders && {
|
|
82
|
+
createAuthHeaders: async () => ({
|
|
83
|
+
verify: config.verifyHeaders ?? {},
|
|
84
|
+
settle: config.settleHeaders ?? {},
|
|
85
|
+
supported: config.supportedHeaders ?? {},
|
|
86
|
+
}),
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProcessSettleResultResponse } from "@x402/core/server";
|
|
2
|
+
import type { PaymentPayload, PaymentRequirements } from "@x402/core/types";
|
|
3
|
+
import type { WorkerCore } from "./index";
|
|
4
|
+
import type { ProcessRequestResult, RequestAdapter, RequestMetadata } from "./types";
|
|
5
|
+
export declare function handlePaymentRequest(core: WorkerCore, adapter: RequestAdapter, metadata: RequestMetadata, pathOverride?: string): Promise<ProcessRequestResult>;
|
|
6
|
+
export declare function handleRequest(core: WorkerCore, adapter: RequestAdapter, metadata: RequestMetadata): Promise<ProcessRequestResult>;
|
|
7
|
+
export declare function handleSettlement(core: WorkerCore, adapter: RequestAdapter, paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements, upstreamStatusCode: number, requestId: string): Promise<ProcessSettleResultResponse>;
|
|
8
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAI5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAUrF,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,eAAe,EACzB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,oBAAoB,CAAC,CAiC/B;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAsC/B;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,cAAc,EAC9B,mBAAmB,EAAE,mBAAmB,EACxC,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,2BAA2B,CAAC,CAwBtC"}
|
package/dist/handler.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { formatApiPaymentError } from "./api";
|
|
2
|
+
import { noPaymentRequired } from "./config";
|
|
3
|
+
import { logEvent } from "./telemetry";
|
|
4
|
+
import { formatWebPaymentError } from "./web";
|
|
5
|
+
function settlementFailure(reason, network) {
|
|
6
|
+
return { success: false, errorReason: reason, network, transaction: "" };
|
|
7
|
+
}
|
|
8
|
+
export async function handlePaymentRequest(core, adapter, metadata, pathOverride) {
|
|
9
|
+
const httpServer = await core.httpServer.get();
|
|
10
|
+
if (!httpServer) {
|
|
11
|
+
return noPaymentRequired(metadata);
|
|
12
|
+
}
|
|
13
|
+
const path = pathOverride ?? adapter.getPath();
|
|
14
|
+
const paymentContext = {
|
|
15
|
+
adapter,
|
|
16
|
+
path,
|
|
17
|
+
method: adapter.getMethod(),
|
|
18
|
+
paymentHeader: adapter.getHeader("PAYMENT-SIGNATURE") ||
|
|
19
|
+
adapter.getHeader("X-PAYMENT"),
|
|
20
|
+
};
|
|
21
|
+
if (!httpServer.requiresPayment(paymentContext)) {
|
|
22
|
+
return noPaymentRequired(metadata);
|
|
23
|
+
}
|
|
24
|
+
const result = await httpServer.processHTTPRequest(paymentContext, undefined);
|
|
25
|
+
result.metadata = metadata;
|
|
26
|
+
if (result.type === "payment-error") {
|
|
27
|
+
if (result.restriction.price === 0) {
|
|
28
|
+
await logEvent(core, adapter, 200, metadata.request_id);
|
|
29
|
+
return noPaymentRequired(metadata);
|
|
30
|
+
}
|
|
31
|
+
await logEvent(core, adapter, result.response.status, metadata.request_id);
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
export async function handleRequest(core, adapter, metadata) {
|
|
36
|
+
const userAgent = adapter.getUserAgent();
|
|
37
|
+
const bot = userAgent ? await core.bots.matchBot(userAgent) : null;
|
|
38
|
+
const hostConfig = await core.hostConfig.get();
|
|
39
|
+
const shouldCheck = bot || hostConfig?.apiProtectionMode === "all";
|
|
40
|
+
if (!shouldCheck) {
|
|
41
|
+
return noPaymentRequired(metadata);
|
|
42
|
+
}
|
|
43
|
+
const result = await handlePaymentRequest(core, adapter, metadata);
|
|
44
|
+
if (result.type !== "payment-error") {
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
// TODO: Figure out a way to classify whether web or api sooner in the flow
|
|
48
|
+
// so we don't run handlePaymentRequest for web restrictions on non-bot requests
|
|
49
|
+
// Web restrictions are always bot-only
|
|
50
|
+
if (result.restriction.type === "web" && !bot) {
|
|
51
|
+
return noPaymentRequired(metadata);
|
|
52
|
+
}
|
|
53
|
+
const paymentMethods = await core.paymentMethods.get();
|
|
54
|
+
if (paymentMethods.length > 0) {
|
|
55
|
+
if (result.restriction.type === "api") {
|
|
56
|
+
formatApiPaymentError(result, result.restriction, paymentMethods, hostConfig?.termsOfServiceUrl);
|
|
57
|
+
}
|
|
58
|
+
else if (result.restriction.type === "web") {
|
|
59
|
+
formatWebPaymentError(result, result.restriction, paymentMethods, adapter, hostConfig?.termsOfServiceUrl);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (bot?.force_200) {
|
|
63
|
+
result.response.status = 200;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
export async function handleSettlement(core, adapter, paymentPayload, paymentRequirements, upstreamStatusCode, requestId) {
|
|
68
|
+
const httpServer = await core.httpServer.get();
|
|
69
|
+
if (!httpServer) {
|
|
70
|
+
return settlementFailure("Server not initialized", paymentRequirements.network);
|
|
71
|
+
}
|
|
72
|
+
if (upstreamStatusCode >= 400) {
|
|
73
|
+
await logEvent(core, adapter, upstreamStatusCode, requestId);
|
|
74
|
+
return settlementFailure("Upstream error", paymentRequirements.network);
|
|
75
|
+
}
|
|
76
|
+
const result = await httpServer.processSettlement(paymentPayload, paymentRequirements);
|
|
77
|
+
if (result.success) {
|
|
78
|
+
const paymentResponse = result.headers["PAYMENT-RESPONSE"];
|
|
79
|
+
await logEvent(core, adapter, upstreamStatusCode, requestId, paymentResponse);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
await logEvent(core, adapter, 402, requestId);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW,yBAAyB,CAAC;AAElD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQhF"}
|
package/dist/health.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
2
|
+
export const HEALTH_PATH = "/.well-known/foldset";
|
|
3
|
+
export function buildHealthResponse(platform, sdkVersion) {
|
|
4
|
+
return JSON.stringify({
|
|
5
|
+
status: "ok",
|
|
6
|
+
core_version: packageJson.version,
|
|
7
|
+
sdk_version: sdkVersion,
|
|
8
|
+
platform,
|
|
9
|
+
timestamp: new Date().toISOString(),
|
|
10
|
+
});
|
|
11
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ProcessSettleResultResponse } from "@x402/core/server";
|
|
2
|
+
import type { PaymentPayload, PaymentRequirements } from "@x402/core/types";
|
|
3
|
+
import { BotsManager, HostConfigManager, PaymentMethodsManager, RestrictionsManager } from "./config";
|
|
4
|
+
import { HttpServerManager } from "./server";
|
|
5
|
+
import type { ConfigStore, FoldsetOptions, ProcessRequestResult, RequestAdapter } from "./types";
|
|
6
|
+
export declare class WorkerCore {
|
|
7
|
+
readonly hostConfig: HostConfigManager;
|
|
8
|
+
readonly restrictions: RestrictionsManager;
|
|
9
|
+
readonly paymentMethods: PaymentMethodsManager;
|
|
10
|
+
readonly bots: BotsManager;
|
|
11
|
+
readonly apiKey: string;
|
|
12
|
+
readonly httpServer: HttpServerManager;
|
|
13
|
+
readonly platform: string;
|
|
14
|
+
readonly sdkVersion: string;
|
|
15
|
+
constructor(store: ConfigStore, apiKey: string, platform: string, sdkVersion: string);
|
|
16
|
+
static fromOptions(options: FoldsetOptions): Promise<WorkerCore>;
|
|
17
|
+
processRequest(adapter: RequestAdapter): Promise<ProcessRequestResult>;
|
|
18
|
+
processSettlement(adapter: RequestAdapter, paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements, upstreamStatusCode: number, requestId: string): Promise<ProcessSettleResultResponse>;
|
|
19
|
+
}
|
|
20
|
+
export type { ApiRestriction, Bot, ConfigStore, ErrorReport, EventPayload, FacilitatorConfig, FoldsetOptions, HostConfig, HttpServerResult, McpRestriction, PaymentMethod, ProcessRequestResult, RequestAdapter, RequestMetadata, Restriction, RestrictionBase, WebRestriction, } from "./types";
|
|
21
|
+
export { createRedisStore, fetchRedisCredentials } from "./store";
|
|
22
|
+
export type { RedisCredentials } from "./store";
|
|
23
|
+
export { generatePaywallHtml } from "./paywall";
|
|
24
|
+
export { buildRoutesConfig, priceToAmount } from "./routes";
|
|
25
|
+
export { BotsManager, CachedConfigManager, FacilitatorManager, HostConfigManager, PaymentMethodsManager, RestrictionsManager, } from "./config";
|
|
26
|
+
export { HttpServerManager } from "./server";
|
|
27
|
+
export { buildJsonRpcError, buildMcpRouteKey, buildMcpRoutesConfig, getMcpListPaymentRequirements, getMcpRouteKey, handleMcpRequest, isMcpListMethod, parseMcpRequest, } from "./mcp";
|
|
28
|
+
export type { JsonRpcError, JsonRpcRequest, McpPaymentRequirement } from "./mcp";
|
|
29
|
+
export { buildEventPayload, logEvent, reportError, sendEvent } from "./telemetry";
|
|
30
|
+
export { handlePaymentRequest, handleRequest, handleSettlement } from "./handler";
|
|
31
|
+
export { formatApiPaymentError } from "./api";
|
|
32
|
+
export { formatWebPaymentError } from "./web";
|
|
33
|
+
export { HEALTH_PATH, buildHealthResponse } from "./health";
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EAEpB,MAAM,UAAU,CAAC;AAIlB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,cAAc,EACf,MAAM,SAAS,CAAC;AAIjB,qBAAa,UAAU;IACrB,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;WAWvE,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAYhE,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAyBtE,iBAAiB,CACrB,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,cAAc,EAC9B,mBAAmB,EAAE,mBAAmB,EACxC,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,2BAA2B,CAAC;CAUxC;AAGD,YAAY,EACV,cAAc,EACd,GAAG,EACH,WAAW,EACX,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG5D,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,6BAA6B,EAC7B,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,OAAO,CAAC;AACf,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AAGjF,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGlF,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,OAAO,CAAC;AAG9C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { BotsManager, HostConfigManager, PaymentMethodsManager, RestrictionsManager, buildRequestMetadata, } from "./config";
|
|
2
|
+
import { handleRequest, handleSettlement } from "./handler";
|
|
3
|
+
import { HEALTH_PATH, buildHealthResponse } from "./health";
|
|
4
|
+
import { handleMcpRequest } from "./mcp";
|
|
5
|
+
import { HttpServerManager } from "./server";
|
|
6
|
+
import { createRedisStore, fetchRedisCredentials } from "./store";
|
|
7
|
+
let cachedCore = null;
|
|
8
|
+
export class WorkerCore {
|
|
9
|
+
hostConfig;
|
|
10
|
+
restrictions;
|
|
11
|
+
paymentMethods;
|
|
12
|
+
bots;
|
|
13
|
+
apiKey;
|
|
14
|
+
httpServer;
|
|
15
|
+
platform;
|
|
16
|
+
sdkVersion;
|
|
17
|
+
constructor(store, apiKey, platform, sdkVersion) {
|
|
18
|
+
this.hostConfig = new HostConfigManager(store);
|
|
19
|
+
this.restrictions = new RestrictionsManager(store);
|
|
20
|
+
this.paymentMethods = new PaymentMethodsManager(store);
|
|
21
|
+
this.bots = new BotsManager(store);
|
|
22
|
+
this.apiKey = apiKey;
|
|
23
|
+
this.httpServer = new HttpServerManager(store);
|
|
24
|
+
this.platform = platform;
|
|
25
|
+
this.sdkVersion = sdkVersion;
|
|
26
|
+
}
|
|
27
|
+
static async fromOptions(options) {
|
|
28
|
+
if (cachedCore) {
|
|
29
|
+
return cachedCore;
|
|
30
|
+
}
|
|
31
|
+
const credentials = options.redisCredentials ?? (await fetchRedisCredentials(options.apiKey));
|
|
32
|
+
const store = createRedisStore(credentials);
|
|
33
|
+
cachedCore = new WorkerCore(store, options.apiKey, options.platform ?? "unknown", options.sdkVersion ?? "unknown");
|
|
34
|
+
return cachedCore;
|
|
35
|
+
}
|
|
36
|
+
async processRequest(adapter) {
|
|
37
|
+
const metadata = buildRequestMetadata();
|
|
38
|
+
if (adapter.getPath() === HEALTH_PATH) {
|
|
39
|
+
return {
|
|
40
|
+
type: "health-check",
|
|
41
|
+
metadata,
|
|
42
|
+
response: {
|
|
43
|
+
status: 200,
|
|
44
|
+
body: buildHealthResponse(this.platform, this.sdkVersion),
|
|
45
|
+
headers: { "Content-Type": "application/json" },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const hostConfig = await this.hostConfig.get();
|
|
50
|
+
const mcpEndpoint = hostConfig?.mcpEndpoint;
|
|
51
|
+
if (mcpEndpoint && adapter.getPath() === mcpEndpoint) {
|
|
52
|
+
return handleMcpRequest(this, adapter, mcpEndpoint, metadata);
|
|
53
|
+
}
|
|
54
|
+
return handleRequest(this, adapter, metadata);
|
|
55
|
+
}
|
|
56
|
+
async processSettlement(adapter, paymentPayload, paymentRequirements, upstreamStatusCode, requestId) {
|
|
57
|
+
return handleSettlement(this, adapter, paymentPayload, paymentRequirements, upstreamStatusCode, requestId);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Store
|
|
61
|
+
export { createRedisStore, fetchRedisCredentials } from "./store";
|
|
62
|
+
// Paywall
|
|
63
|
+
export { generatePaywallHtml } from "./paywall";
|
|
64
|
+
// Routes
|
|
65
|
+
export { buildRoutesConfig, priceToAmount } from "./routes";
|
|
66
|
+
// Config managers
|
|
67
|
+
export { BotsManager, CachedConfigManager, FacilitatorManager, HostConfigManager, PaymentMethodsManager, RestrictionsManager, } from "./config";
|
|
68
|
+
// Server
|
|
69
|
+
export { HttpServerManager } from "./server";
|
|
70
|
+
// MCP
|
|
71
|
+
export { buildJsonRpcError, buildMcpRouteKey, buildMcpRoutesConfig, getMcpListPaymentRequirements, getMcpRouteKey, handleMcpRequest, isMcpListMethod, parseMcpRequest, } from "./mcp";
|
|
72
|
+
// Telemetry
|
|
73
|
+
export { buildEventPayload, logEvent, reportError, sendEvent } from "./telemetry";
|
|
74
|
+
// Handlers
|
|
75
|
+
export { handlePaymentRequest, handleRequest, handleSettlement } from "./handler";
|
|
76
|
+
export { formatApiPaymentError } from "./api";
|
|
77
|
+
export { formatWebPaymentError } from "./web";
|
|
78
|
+
// Health
|
|
79
|
+
export { HEALTH_PATH, buildHealthResponse } from "./health";
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { RoutesConfig } from "@x402/core/http";
|
|
2
|
+
import type { WorkerCore } from "./index";
|
|
3
|
+
import type { McpRestriction, PaymentMethod, ProcessRequestResult, RequestAdapter, RequestMetadata, Restriction } from "./types";
|
|
4
|
+
export interface JsonRpcRequest {
|
|
5
|
+
jsonrpc: string;
|
|
6
|
+
id?: string | number | null;
|
|
7
|
+
method: string;
|
|
8
|
+
params?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export declare function parseMcpRequest(body: unknown): JsonRpcRequest | null;
|
|
11
|
+
/**
|
|
12
|
+
* Build the route key for an MCP restriction: "endpointPath/method:name".
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildMcpRouteKey(endpointPath: string, restriction: McpRestriction): string;
|
|
15
|
+
export declare function buildMcpRoutesConfig(restrictions: Restriction[], paymentMethods: PaymentMethod[], mcpEndpoint: string, termsOfServiceUrl?: string): RoutesConfig;
|
|
16
|
+
export declare function getMcpRouteKey(endpointPath: string, method: string, params?: Record<string, unknown>): string | null;
|
|
17
|
+
export declare function isMcpListMethod(method: string): boolean;
|
|
18
|
+
export interface McpPaymentRequirement {
|
|
19
|
+
name: string;
|
|
20
|
+
method: string;
|
|
21
|
+
description: string;
|
|
22
|
+
price: number;
|
|
23
|
+
scheme: string;
|
|
24
|
+
accepts: Array<{
|
|
25
|
+
network: string;
|
|
26
|
+
chainDisplayName: string;
|
|
27
|
+
asset: string;
|
|
28
|
+
assetDisplayName: string;
|
|
29
|
+
amount: string;
|
|
30
|
+
payTo: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
export interface JsonRpcError {
|
|
34
|
+
jsonrpc: "2.0";
|
|
35
|
+
id: string | number | null;
|
|
36
|
+
error: {
|
|
37
|
+
code: number;
|
|
38
|
+
message: string;
|
|
39
|
+
data?: unknown;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare function buildJsonRpcError(id: string | number | null, code: number, message: string, data?: unknown): JsonRpcError;
|
|
43
|
+
/**
|
|
44
|
+
* Build payment requirements for all gated MCP tools/resources/prompts
|
|
45
|
+
* matching the given list method. Returns clear payment instructions
|
|
46
|
+
* built directly from Foldset restrictions and payment methods.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getMcpListPaymentRequirements(listMethod: string, restrictions: Restriction[], paymentMethods: PaymentMethod[]): McpPaymentRequirement[];
|
|
49
|
+
export declare function handleMcpRequest(core: WorkerCore, adapter: RequestAdapter, mcpEndpoint: string, metadata: RequestMetadata): Promise<ProcessRequestResult>;
|
|
50
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAIpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG1C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAQD,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAUpE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,cAAc,GAC1B,MAAM,CAER;AAED,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,EAC3B,cAAc,EAAE,aAAa,EAAE,EAC/B,WAAW,EAAE,MAAM,EACnB,iBAAiB,CAAC,EAAE,MAAM,GACzB,YAAY,CAUd;AAED,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,GAAG,IAAI,CAIf;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,gBAAgB,EAAE,MAAM,CAAC;QACzB,KAAK,EAAE,MAAM,CAAC;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1D;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO,GACb,YAAY,CAId;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,WAAW,EAAE,EAC3B,cAAc,EAAE,aAAa,EAAE,GAC9B,qBAAqB,EAAE,CAwBzB;AA+BD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAiD/B"}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { noPaymentRequired } from "./config";
|
|
2
|
+
import { handlePaymentRequest } from "./handler";
|
|
3
|
+
import { buildRouteEntry, priceToAmount } from "./routes";
|
|
4
|
+
import { logEvent } from "./telemetry";
|
|
5
|
+
const MCP_LIST_CALL_METHODS = {
|
|
6
|
+
"tools/list": "tools/call",
|
|
7
|
+
"resources/list": "resources/read",
|
|
8
|
+
"prompts/list": "prompts/get",
|
|
9
|
+
};
|
|
10
|
+
export function parseMcpRequest(body) {
|
|
11
|
+
if (typeof body !== "object" ||
|
|
12
|
+
body === null ||
|
|
13
|
+
!("jsonrpc" in body) ||
|
|
14
|
+
!("method" in body)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return body;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build the route key for an MCP restriction: "endpointPath/method:name".
|
|
21
|
+
*/
|
|
22
|
+
export function buildMcpRouteKey(endpointPath, restriction) {
|
|
23
|
+
return `${endpointPath}/${restriction.method}:${restriction.name}`;
|
|
24
|
+
}
|
|
25
|
+
export function buildMcpRoutesConfig(restrictions, paymentMethods, mcpEndpoint, termsOfServiceUrl) {
|
|
26
|
+
const routesConfig = {};
|
|
27
|
+
for (const r of restrictions) {
|
|
28
|
+
if (r.type !== "mcp")
|
|
29
|
+
continue;
|
|
30
|
+
const key = buildMcpRouteKey(mcpEndpoint, r);
|
|
31
|
+
routesConfig[key] = buildRouteEntry(r, paymentMethods, termsOfServiceUrl);
|
|
32
|
+
}
|
|
33
|
+
return routesConfig;
|
|
34
|
+
}
|
|
35
|
+
export function getMcpRouteKey(endpointPath, method, params) {
|
|
36
|
+
const identifier = params?.name ?? params?.uri;
|
|
37
|
+
if (typeof identifier !== "string")
|
|
38
|
+
return null;
|
|
39
|
+
return `${endpointPath}/${method}:${identifier}`;
|
|
40
|
+
}
|
|
41
|
+
export function isMcpListMethod(method) {
|
|
42
|
+
return method in MCP_LIST_CALL_METHODS;
|
|
43
|
+
}
|
|
44
|
+
export function buildJsonRpcError(id, code, message, data) {
|
|
45
|
+
const error = { jsonrpc: "2.0", id, error: { code, message } };
|
|
46
|
+
if (data !== undefined)
|
|
47
|
+
error.error.data = data;
|
|
48
|
+
return error;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build payment requirements for all gated MCP tools/resources/prompts
|
|
52
|
+
* matching the given list method. Returns clear payment instructions
|
|
53
|
+
* built directly from Foldset restrictions and payment methods.
|
|
54
|
+
*/
|
|
55
|
+
export function getMcpListPaymentRequirements(listMethod, restrictions, paymentMethods) {
|
|
56
|
+
const callMethod = MCP_LIST_CALL_METHODS[listMethod];
|
|
57
|
+
if (!callMethod)
|
|
58
|
+
return [];
|
|
59
|
+
const relevant = restrictions.filter((r) => r.type === "mcp" && r.method === callMethod && r.price > 0);
|
|
60
|
+
if (!relevant.length)
|
|
61
|
+
return [];
|
|
62
|
+
return relevant.map((r) => ({
|
|
63
|
+
name: r.name,
|
|
64
|
+
method: r.method,
|
|
65
|
+
description: r.description,
|
|
66
|
+
price: r.price,
|
|
67
|
+
scheme: r.scheme,
|
|
68
|
+
accepts: paymentMethods.map((pm) => ({
|
|
69
|
+
network: pm.caip2_id,
|
|
70
|
+
chainDisplayName: pm.chain_display_name,
|
|
71
|
+
asset: pm.contract_address,
|
|
72
|
+
assetDisplayName: pm.asset_display_name,
|
|
73
|
+
amount: priceToAmount(r.price, pm.decimals),
|
|
74
|
+
payTo: pm.circle_wallet_address,
|
|
75
|
+
})),
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
async function formatMcpPaymentError(core, result, rpcId) {
|
|
79
|
+
const [paymentMethods, hostConfig] = await Promise.all([
|
|
80
|
+
core.paymentMethods.get(),
|
|
81
|
+
core.hostConfig.get(),
|
|
82
|
+
]);
|
|
83
|
+
result.response.body = JSON.stringify(buildJsonRpcError(rpcId, 402, "Payment required", {
|
|
84
|
+
...result.metadata,
|
|
85
|
+
description: result.restriction.description,
|
|
86
|
+
price: result.restriction.price,
|
|
87
|
+
...(hostConfig?.termsOfServiceUrl && { terms_of_service_url: hostConfig.termsOfServiceUrl }),
|
|
88
|
+
payment_methods: paymentMethods.map((pm) => ({
|
|
89
|
+
network: pm.caip2_id,
|
|
90
|
+
asset: pm.contract_address,
|
|
91
|
+
decimals: pm.decimals,
|
|
92
|
+
pay_to: pm.circle_wallet_address,
|
|
93
|
+
chain: pm.chain_display_name,
|
|
94
|
+
asset_name: pm.asset_display_name,
|
|
95
|
+
})),
|
|
96
|
+
}));
|
|
97
|
+
result.response.headers["Content-Type"] = "application/json";
|
|
98
|
+
}
|
|
99
|
+
export async function handleMcpRequest(core, adapter, mcpEndpoint, metadata) {
|
|
100
|
+
if (adapter.getMethod() !== "POST") {
|
|
101
|
+
return noPaymentRequired(metadata);
|
|
102
|
+
}
|
|
103
|
+
const body = await adapter.getBody();
|
|
104
|
+
const rpc = parseMcpRequest(body);
|
|
105
|
+
if (!rpc) {
|
|
106
|
+
return noPaymentRequired(metadata);
|
|
107
|
+
}
|
|
108
|
+
// List methods, pass through with payment requirements header
|
|
109
|
+
if (isMcpListMethod(rpc.method)) {
|
|
110
|
+
const [restrictions, paymentMethods, hostConfig] = await Promise.all([
|
|
111
|
+
core.restrictions.get(),
|
|
112
|
+
core.paymentMethods.get(),
|
|
113
|
+
core.hostConfig.get(),
|
|
114
|
+
]);
|
|
115
|
+
// TODO rfradkin: We shouldn't really be regenerating this list each time,
|
|
116
|
+
// it should be every time requirements change
|
|
117
|
+
const requirements = getMcpListPaymentRequirements(rpc.method, restrictions, paymentMethods);
|
|
118
|
+
const headers = {};
|
|
119
|
+
if (requirements.length > 0) {
|
|
120
|
+
const payload = { requirements };
|
|
121
|
+
if (hostConfig?.termsOfServiceUrl) {
|
|
122
|
+
payload.terms_of_service_url = hostConfig.termsOfServiceUrl;
|
|
123
|
+
}
|
|
124
|
+
headers["Payment-Required"] = JSON.stringify(payload);
|
|
125
|
+
}
|
|
126
|
+
await logEvent(core, adapter, 200, metadata.request_id);
|
|
127
|
+
return { type: "no-payment-required", headers, metadata };
|
|
128
|
+
}
|
|
129
|
+
const routeKey = getMcpRouteKey(mcpEndpoint, rpc.method, rpc.params);
|
|
130
|
+
if (!routeKey) {
|
|
131
|
+
return noPaymentRequired(metadata);
|
|
132
|
+
}
|
|
133
|
+
const result = await handlePaymentRequest(core, adapter, metadata, routeKey);
|
|
134
|
+
if (result.type === "payment-error") {
|
|
135
|
+
await formatMcpPaymentError(core, result, rpc.id ?? null);
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../src/paywall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE1D,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,aAAa,EAAE,EAC/B,GAAG,EAAE,MAAM,EACX,iBAAiB,CAAC,EAAE,MAAM,GACzB,MAAM,CA2FR"}
|
package/dist/paywall.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export function generatePaywallHtml(restriction, paymentMethods, url, termsOfServiceUrl) {
|
|
2
|
+
// Group payment methods by network
|
|
3
|
+
const methodsByNetwork = new Map();
|
|
4
|
+
for (const pm of paymentMethods) {
|
|
5
|
+
const existing = methodsByNetwork.get(pm.caip2_id) ?? [];
|
|
6
|
+
existing.push(pm);
|
|
7
|
+
methodsByNetwork.set(pm.caip2_id, existing);
|
|
8
|
+
}
|
|
9
|
+
const paymentOptionsHtml = Array.from(methodsByNetwork.values()).map((methods) => {
|
|
10
|
+
const chainDisplayName = methods[0].chain_display_name;
|
|
11
|
+
const chainCaip2Id = methods[0].caip2_id;
|
|
12
|
+
const recipientAddress = methods[0].circle_wallet_address;
|
|
13
|
+
const acceptedTokensHtml = methods.map((pm) => {
|
|
14
|
+
const scheme = restriction.scheme
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/^./, c => c.toUpperCase());
|
|
17
|
+
return `
|
|
18
|
+
<div class="token-row">
|
|
19
|
+
<span class="token-name">${pm.asset_display_name}</span>
|
|
20
|
+
<span class="token-details">
|
|
21
|
+
<span class="token-scheme">${scheme}</span>
|
|
22
|
+
<span class="token-price">$${restriction.price}</span>
|
|
23
|
+
</span>
|
|
24
|
+
</div>`;
|
|
25
|
+
}).join("");
|
|
26
|
+
return `
|
|
27
|
+
<div class="card">
|
|
28
|
+
<div class="card-header">
|
|
29
|
+
<h3>${chainDisplayName}</h3>
|
|
30
|
+
<span class="chain-id">${chainCaip2Id}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="pay-to"><strong>Pay to:</strong> <code>${recipientAddress}</code></div>
|
|
33
|
+
${acceptedTokensHtml}
|
|
34
|
+
</div>`;
|
|
35
|
+
}).join("\n");
|
|
36
|
+
return `<!DOCTYPE html>
|
|
37
|
+
<html>
|
|
38
|
+
<head>
|
|
39
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
40
|
+
<title>HTTP 402 - Payment Required</title>
|
|
41
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
42
|
+
<style>
|
|
43
|
+
* { box-sizing: border-box; }
|
|
44
|
+
body { font-family: 'Inter', system-ui, sans-serif; max-width: 600px; margin: 32px auto; padding: 0 16px; background: #fff; color: #111; -webkit-font-smoothing: antialiased; font-size: 14px; }
|
|
45
|
+
h1 { font-size: 20px; margin-bottom: 4px; }
|
|
46
|
+
h2 { font-size: 15px; margin-top: 24px; margin-bottom: 8px; }
|
|
47
|
+
h3 { font-size: 14px; margin-top: 0; margin-bottom: 10px; }
|
|
48
|
+
a { color: #00aa5e; }
|
|
49
|
+
code { background: #f0f0f0; padding: 2px 5px; border-radius: 3px; font-size: 11px; font-family: 'IBM Plex Mono', monospace; word-break: break-all; }
|
|
50
|
+
.resource { margin: 12px 0; padding: 10px 12px; background: #f7f7f7; border: 1px solid #e5e5e5; border-radius: 5px; }
|
|
51
|
+
.resource-row { display: flex; gap: 6px; align-items: baseline; margin-bottom: 4px; font-size: 13px; color: #555; }
|
|
52
|
+
.resource-row:last-child { margin-bottom: 0; }
|
|
53
|
+
.resource-row strong { color: #111; font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; white-space: nowrap; }
|
|
54
|
+
.card { margin: 12px 0; padding: 12px; border: 1px solid #e5e5e5; border-radius: 5px; }
|
|
55
|
+
.card-header { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }
|
|
56
|
+
.card-header h3 { margin: 0; }
|
|
57
|
+
.card-header .chain-id { color: #888; font-size: 11px; font-weight: 400; }
|
|
58
|
+
.pay-to { font-size: 12px; color: #555; margin-bottom: 10px; }
|
|
59
|
+
.pay-to strong { color: #111; }
|
|
60
|
+
.token-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-top: 1px solid #f0f0f0; font-size: 13px; }
|
|
61
|
+
.token-name { font-weight: 500; color: #111; }
|
|
62
|
+
.token-details { display: flex; gap: 12px; align-items: center; color: #555; font-size: 12px; }
|
|
63
|
+
.token-price { font-weight: 500; color: #111; }
|
|
64
|
+
.token-scheme { font-size: 11px; color: #888; text-transform: capitalize; }
|
|
65
|
+
p { color: #555; font-size: 13px; line-height: 1.5; }
|
|
66
|
+
footer { margin-top: 24px; padding-top: 12px; border-top: 1px solid #e5e5e5; font-size: 12px; color: #888; }
|
|
67
|
+
::selection { background: #00ff88; color: #000; }
|
|
68
|
+
</style>
|
|
69
|
+
</head>
|
|
70
|
+
<body>
|
|
71
|
+
<h1>402: Payment Required</h1>
|
|
72
|
+
<p>This content requires payment via the <a href="https://github.com/coinbase/x402">x402 protocol</a>.</p>
|
|
73
|
+
|
|
74
|
+
<div class="resource">
|
|
75
|
+
<div class="resource-row"><strong>URL</strong> <code>${url}</code></div>
|
|
76
|
+
<div class="resource-row"><strong>Description</strong> ${restriction.description}</div>${termsOfServiceUrl ? `\n <div class="resource-row"><strong>Terms of Service</strong> <a href="${termsOfServiceUrl}">${termsOfServiceUrl}</a></div>` : ""}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<h2>Payment Options</h2>
|
|
80
|
+
${paymentOptionsHtml}
|
|
81
|
+
|
|
82
|
+
<footer>
|
|
83
|
+
Powered by <a href="https://www.foldset.com">Foldset</a>
|
|
84
|
+
</footer>
|
|
85
|
+
</body>
|
|
86
|
+
</html>`;
|
|
87
|
+
}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RouteConfig, RoutesConfig } from "@x402/core/http";
|
|
2
|
+
import type { PaymentMethod, Restriction } from "./types";
|
|
3
|
+
export declare function priceToAmount(priceUsd: number, decimals: number): string;
|
|
4
|
+
export declare function buildRouteEntry(restriction: Restriction, paymentMethods: PaymentMethod[], termsOfServiceUrl?: string): RouteConfig & {
|
|
5
|
+
restriction: Restriction;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildRoutesConfig(restrictions: Restriction[], paymentMethods: PaymentMethod[], termsOfServiceUrl?: string): RoutesConfig;
|
|
8
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGjE,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE1D,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED,wBAAgB,eAAe,CAC7B,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,aAAa,EAAE,EAC/B,iBAAiB,CAAC,EAAE,MAAM,GACzB,WAAW,GAAG;IAAE,WAAW,EAAE,WAAW,CAAA;CAAE,CAmB5C;AAED,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,WAAW,EAAE,EAC3B,cAAc,EAAE,aAAa,EAAE,EAC/B,iBAAiB,CAAC,EAAE,MAAM,GACzB,YAAY,CAUd"}
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function priceToAmount(priceUsd, decimals) {
|
|
2
|
+
const amount = priceUsd * Math.pow(10, decimals);
|
|
3
|
+
return Math.round(amount).toString();
|
|
4
|
+
}
|
|
5
|
+
export function buildRouteEntry(restriction, paymentMethods, termsOfServiceUrl) {
|
|
6
|
+
return {
|
|
7
|
+
accepts: paymentMethods.map((pm) => ({
|
|
8
|
+
scheme: restriction.scheme,
|
|
9
|
+
price: {
|
|
10
|
+
amount: priceToAmount(restriction.price, pm.decimals),
|
|
11
|
+
asset: pm.contract_address,
|
|
12
|
+
extra: {
|
|
13
|
+
...pm.extra,
|
|
14
|
+
...(termsOfServiceUrl && { termsOfServiceUrl }),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
network: pm.caip2_id,
|
|
18
|
+
payTo: pm.circle_wallet_address,
|
|
19
|
+
})),
|
|
20
|
+
description: restriction.description,
|
|
21
|
+
mimeType: "application/json",
|
|
22
|
+
restriction,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function buildRoutesConfig(restrictions, paymentMethods, termsOfServiceUrl) {
|
|
26
|
+
const routesConfig = {};
|
|
27
|
+
for (const r of restrictions) {
|
|
28
|
+
if (r.type === "mcp")
|
|
29
|
+
continue;
|
|
30
|
+
const key = r.type === "api" && r.httpMethod ? `${r.httpMethod.toUpperCase()} ${r.path}` : r.path;
|
|
31
|
+
routesConfig[key] = buildRouteEntry(r, paymentMethods, termsOfServiceUrl);
|
|
32
|
+
}
|
|
33
|
+
return routesConfig;
|
|
34
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HTTPRequestContext, PaywallConfig } from "@x402/core/http";
|
|
2
|
+
import { x402HTTPResourceServer } from "@x402/core/server";
|
|
3
|
+
import type { ConfigStore, HttpServerResult } from "./types";
|
|
4
|
+
export interface FoldsetX402HTTPServer extends x402HTTPResourceServer {
|
|
5
|
+
processHTTPRequest(context: HTTPRequestContext, paywallConfig?: PaywallConfig): Promise<HttpServerResult>;
|
|
6
|
+
}
|
|
7
|
+
export declare class HttpServerManager {
|
|
8
|
+
private cached;
|
|
9
|
+
private cacheTimestamp;
|
|
10
|
+
private hostConfig;
|
|
11
|
+
private restrictions;
|
|
12
|
+
private paymentMethods;
|
|
13
|
+
private facilitator;
|
|
14
|
+
constructor(store: ConfigStore);
|
|
15
|
+
get(): Promise<FoldsetX402HTTPServer | null>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAA4B,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnG,OAAO,EACL,sBAAsB,EAEvB,MAAM,mBAAmB,CAAC;AAc3B,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AA8B7D,MAAM,WAAW,qBAAsB,SAAQ,sBAAsB;IACnE,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,EAAE,aAAa,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC3G;AAmBD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,WAAW,CAAqB;gBAE5B,KAAK,EAAE,WAAW;IAOxB,GAAG,IAAI,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;CA2CnD"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { x402HTTPResourceServer, x402ResourceServer, } from "@x402/core/server";
|
|
2
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/server";
|
|
3
|
+
import { registerExactSvmScheme } from "@x402/svm/exact/server";
|
|
4
|
+
import { CACHE_TTL_MS, FacilitatorManager, HostConfigManager, PaymentMethodsManager, RestrictionsManager, } from "./config";
|
|
5
|
+
import { buildMcpRoutesConfig } from "./mcp";
|
|
6
|
+
import { buildRoutesConfig } from "./routes";
|
|
7
|
+
/**
|
|
8
|
+
* Custom parseRoutePattern that treats the path as a raw regex.
|
|
9
|
+
* Restriction paths (stored as regex in the DB) are used directly
|
|
10
|
+
* instead of being converted by x402's glob-like pattern parser.
|
|
11
|
+
*/
|
|
12
|
+
function foldsetParseRoutePattern(pattern) {
|
|
13
|
+
const [verb, path] = pattern.includes(" ") ? pattern.split(/\s+/) : ["*", pattern];
|
|
14
|
+
return { verb: verb.toUpperCase(), regex: new RegExp(path, "i") };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Always return payment-required headers regardless of browser detection.
|
|
18
|
+
* Body is left empty, web.ts and mcp.ts set their own response body.
|
|
19
|
+
*/
|
|
20
|
+
function foldsetCreatePaymentRequiredResponse(paymentRequired) {
|
|
21
|
+
// @ts-expect-error - accessing private method
|
|
22
|
+
const response = this.createHTTPPaymentRequiredResponse(paymentRequired);
|
|
23
|
+
return {
|
|
24
|
+
status: 402,
|
|
25
|
+
headers: response.headers,
|
|
26
|
+
body: "",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Attaches the matched restriction to payment-error results from the route config.
|
|
31
|
+
*/
|
|
32
|
+
const processHTTPRequestWithRestriction = async function (context, paywallConfig) {
|
|
33
|
+
const result = await x402HTTPResourceServer.prototype.processHTTPRequest.call(this, context, paywallConfig);
|
|
34
|
+
if (result.type === "payment-error") {
|
|
35
|
+
// @ts-expect-error - accessing private method
|
|
36
|
+
const routeConfig = this.getRouteConfig(context.path, context.method);
|
|
37
|
+
return { ...result, restriction: routeConfig?.restriction };
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
export class HttpServerManager {
|
|
42
|
+
cached = null;
|
|
43
|
+
cacheTimestamp = 0;
|
|
44
|
+
hostConfig;
|
|
45
|
+
restrictions;
|
|
46
|
+
paymentMethods;
|
|
47
|
+
facilitator;
|
|
48
|
+
constructor(store) {
|
|
49
|
+
this.hostConfig = new HostConfigManager(store);
|
|
50
|
+
this.restrictions = new RestrictionsManager(store);
|
|
51
|
+
this.paymentMethods = new PaymentMethodsManager(store);
|
|
52
|
+
this.facilitator = new FacilitatorManager(store);
|
|
53
|
+
}
|
|
54
|
+
async get() {
|
|
55
|
+
if (this.cached && Date.now() - this.cacheTimestamp < CACHE_TTL_MS) {
|
|
56
|
+
return this.cached;
|
|
57
|
+
}
|
|
58
|
+
const [hostConfig, restrictions, paymentMethods, facilitator] = await Promise.all([
|
|
59
|
+
this.hostConfig.get(),
|
|
60
|
+
this.restrictions.get(),
|
|
61
|
+
this.paymentMethods.get(),
|
|
62
|
+
this.facilitator.get(),
|
|
63
|
+
]);
|
|
64
|
+
if (!hostConfig || !facilitator) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const server = new x402ResourceServer(facilitator);
|
|
68
|
+
registerExactEvmScheme(server);
|
|
69
|
+
registerExactSvmScheme(server);
|
|
70
|
+
const contentRoutes = buildRoutesConfig(restrictions, paymentMethods, hostConfig.termsOfServiceUrl);
|
|
71
|
+
const mcpRoutes = hostConfig.mcpEndpoint
|
|
72
|
+
? buildMcpRoutesConfig(restrictions, paymentMethods, hostConfig.mcpEndpoint, hostConfig.termsOfServiceUrl)
|
|
73
|
+
: {};
|
|
74
|
+
const routesConfig = { ...contentRoutes, ...mcpRoutes };
|
|
75
|
+
// @ts-expect-error - overriding private method
|
|
76
|
+
x402HTTPResourceServer.prototype.parseRoutePattern = foldsetParseRoutePattern;
|
|
77
|
+
const httpServer = new x402HTTPResourceServer(server, routesConfig);
|
|
78
|
+
// @ts-expect-error - overriding private method
|
|
79
|
+
httpServer.createHTTPResponse = foldsetCreatePaymentRequiredResponse;
|
|
80
|
+
httpServer.processHTTPRequest = processHTTPRequestWithRestriction;
|
|
81
|
+
await httpServer.initialize();
|
|
82
|
+
this.cached = httpServer;
|
|
83
|
+
this.cacheTimestamp = Date.now();
|
|
84
|
+
return this.cached;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConfigStore } from "./types";
|
|
2
|
+
export interface RedisCredentials {
|
|
3
|
+
url: string;
|
|
4
|
+
token: string;
|
|
5
|
+
tenantId: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function fetchRedisCredentials(apiKey: string): Promise<RedisCredentials>;
|
|
8
|
+
export declare function createRedisStore(credentials: RedisCredentials): ConfigStore;
|
|
9
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,CAAC,CAa3B;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,gBAAgB,GAAG,WAAW,CAa3E"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Redis } from "@upstash/redis";
|
|
2
|
+
import { API_BASE_URL } from "./config";
|
|
3
|
+
export async function fetchRedisCredentials(apiKey) {
|
|
4
|
+
const response = await fetch(`${API_BASE_URL}/v1/config/redis`, {
|
|
5
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
6
|
+
});
|
|
7
|
+
if (!response.ok) {
|
|
8
|
+
throw new Error(`Failed to fetch Redis credentials: ${response.status} ${response.statusText}`);
|
|
9
|
+
}
|
|
10
|
+
const { data } = (await response.json());
|
|
11
|
+
return data;
|
|
12
|
+
}
|
|
13
|
+
export function createRedisStore(credentials) {
|
|
14
|
+
const redis = new Redis({
|
|
15
|
+
url: credentials.url,
|
|
16
|
+
token: credentials.token,
|
|
17
|
+
automaticDeserialization: false,
|
|
18
|
+
});
|
|
19
|
+
const prefix = credentials.tenantId;
|
|
20
|
+
return {
|
|
21
|
+
async get(key) {
|
|
22
|
+
return redis.get(`${prefix}:${key}`);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkerCore } from "./index";
|
|
2
|
+
import type { EventPayload, RequestAdapter } from "./types";
|
|
3
|
+
export declare function buildEventPayload(adapter: RequestAdapter, statusCode: number, requestId: string, paymentResponse?: string): EventPayload;
|
|
4
|
+
export declare function sendEvent(apiKey: string, payload: EventPayload): Promise<void>;
|
|
5
|
+
export declare function reportError(apiKey: string, error: unknown, adapter?: RequestAdapter): Promise<void>;
|
|
6
|
+
export declare function logEvent(core: WorkerCore, adapter: RequestAdapter, statusCode: number, requestId: string, paymentResponse?: string): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAe,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAOzE,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB,YAAY,CAgBd;AAED,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { API_BASE_URL } from "./config";
|
|
2
|
+
const JSON_HEADERS = {
|
|
3
|
+
"Content-Type": "application/json",
|
|
4
|
+
Accept: "application/json",
|
|
5
|
+
};
|
|
6
|
+
export function buildEventPayload(adapter, statusCode, requestId, paymentResponse) {
|
|
7
|
+
const url = new URL(adapter.getUrl());
|
|
8
|
+
return {
|
|
9
|
+
method: adapter.getMethod(),
|
|
10
|
+
status_code: statusCode,
|
|
11
|
+
user_agent: adapter.getUserAgent() || null,
|
|
12
|
+
referer: adapter.getHeader("referer") || null,
|
|
13
|
+
href: url.href,
|
|
14
|
+
hostname: url.hostname,
|
|
15
|
+
pathname: url.pathname,
|
|
16
|
+
search: url.search,
|
|
17
|
+
ip_address: adapter.getIpAddress(),
|
|
18
|
+
request_id: requestId,
|
|
19
|
+
...(paymentResponse && { payment_response: paymentResponse }),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export async function sendEvent(apiKey, payload) {
|
|
23
|
+
await fetch(`${API_BASE_URL}/v1/events`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { Authorization: `Bearer ${apiKey}`, ...JSON_HEADERS },
|
|
26
|
+
body: JSON.stringify(payload),
|
|
27
|
+
}).catch(() => { });
|
|
28
|
+
}
|
|
29
|
+
export async function reportError(apiKey, error, adapter) {
|
|
30
|
+
const payload = {
|
|
31
|
+
error: error instanceof Error ? error.message : String(error),
|
|
32
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
33
|
+
};
|
|
34
|
+
if (adapter) {
|
|
35
|
+
payload.context = {
|
|
36
|
+
method: adapter.getMethod(),
|
|
37
|
+
path: adapter.getPath(),
|
|
38
|
+
hostname: adapter.getHost(),
|
|
39
|
+
user_agent: adapter.getUserAgent() || null,
|
|
40
|
+
ip_address: adapter.getIpAddress(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Fail silently, error reporting must never break the request.
|
|
44
|
+
await fetch(`${API_BASE_URL}/v1/errors`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { Authorization: `Bearer ${apiKey}`, ...JSON_HEADERS },
|
|
47
|
+
body: JSON.stringify(payload),
|
|
48
|
+
}).catch(() => { });
|
|
49
|
+
}
|
|
50
|
+
export async function logEvent(core, adapter, statusCode, requestId, paymentResponse) {
|
|
51
|
+
const payload = buildEventPayload(adapter, statusCode, requestId, paymentResponse);
|
|
52
|
+
await sendEvent(core.apiKey, payload);
|
|
53
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { HTTPAdapter, HTTPProcessResult } from "@x402/core/server";
|
|
2
|
+
import type { RedisCredentials } from "./store";
|
|
3
|
+
export interface FoldsetOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
redisCredentials?: RedisCredentials;
|
|
6
|
+
platform?: string;
|
|
7
|
+
sdkVersion?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RequestMetadata {
|
|
10
|
+
version: string;
|
|
11
|
+
request_id: string;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
}
|
|
14
|
+
export type HttpServerResult = {
|
|
15
|
+
metadata: RequestMetadata;
|
|
16
|
+
} & ((Extract<HTTPProcessResult, {
|
|
17
|
+
type: "no-payment-required";
|
|
18
|
+
}> & {
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
}) | (Extract<HTTPProcessResult, {
|
|
21
|
+
type: "payment-error";
|
|
22
|
+
}> & {
|
|
23
|
+
restriction: Restriction;
|
|
24
|
+
}) | Extract<HTTPProcessResult, {
|
|
25
|
+
type: "payment-verified";
|
|
26
|
+
}>);
|
|
27
|
+
export type ProcessRequestResult = HttpServerResult | {
|
|
28
|
+
metadata: RequestMetadata;
|
|
29
|
+
type: "health-check";
|
|
30
|
+
response: {
|
|
31
|
+
status: 200;
|
|
32
|
+
body: string;
|
|
33
|
+
headers: Record<string, string>;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export interface RequestAdapter extends HTTPAdapter {
|
|
37
|
+
getIpAddress(): string | null;
|
|
38
|
+
getHost(): string;
|
|
39
|
+
getBody(): Promise<unknown>;
|
|
40
|
+
}
|
|
41
|
+
export interface ConfigStore {
|
|
42
|
+
get(key: string): Promise<string | null>;
|
|
43
|
+
}
|
|
44
|
+
export interface EventPayload {
|
|
45
|
+
method: string;
|
|
46
|
+
status_code: number;
|
|
47
|
+
user_agent: string | null;
|
|
48
|
+
referer?: string | null;
|
|
49
|
+
href: string;
|
|
50
|
+
hostname: string;
|
|
51
|
+
pathname: string;
|
|
52
|
+
search: string;
|
|
53
|
+
ip_address?: string | null;
|
|
54
|
+
payment_response?: string;
|
|
55
|
+
request_id: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ErrorReport {
|
|
58
|
+
error: string;
|
|
59
|
+
stack?: string;
|
|
60
|
+
context?: {
|
|
61
|
+
method?: string;
|
|
62
|
+
path?: string;
|
|
63
|
+
hostname?: string;
|
|
64
|
+
user_agent?: string | null;
|
|
65
|
+
ip_address?: string | null;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export interface HostConfig {
|
|
69
|
+
host: string;
|
|
70
|
+
mcpEndpoint?: string;
|
|
71
|
+
termsOfServiceUrl?: string;
|
|
72
|
+
apiProtectionMode: "bots" | "all";
|
|
73
|
+
}
|
|
74
|
+
export interface RestrictionBase {
|
|
75
|
+
description: string;
|
|
76
|
+
price: number;
|
|
77
|
+
scheme: string;
|
|
78
|
+
}
|
|
79
|
+
export interface WebRestriction extends RestrictionBase {
|
|
80
|
+
type: "web";
|
|
81
|
+
path: string;
|
|
82
|
+
}
|
|
83
|
+
export interface ApiRestriction extends RestrictionBase {
|
|
84
|
+
type: "api";
|
|
85
|
+
path: string;
|
|
86
|
+
httpMethod?: string;
|
|
87
|
+
}
|
|
88
|
+
export interface McpRestriction extends RestrictionBase {
|
|
89
|
+
type: "mcp";
|
|
90
|
+
method: string;
|
|
91
|
+
name: string;
|
|
92
|
+
}
|
|
93
|
+
export type Restriction = WebRestriction | ApiRestriction | McpRestriction;
|
|
94
|
+
export interface PaymentMethod {
|
|
95
|
+
caip2_id: string;
|
|
96
|
+
decimals: number;
|
|
97
|
+
contract_address: string;
|
|
98
|
+
circle_wallet_address: string;
|
|
99
|
+
chain_display_name: string;
|
|
100
|
+
asset_display_name: string;
|
|
101
|
+
extra?: Record<string, string>;
|
|
102
|
+
}
|
|
103
|
+
export interface Bot {
|
|
104
|
+
user_agent: string;
|
|
105
|
+
force_200?: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface FacilitatorConfig {
|
|
108
|
+
url: string;
|
|
109
|
+
verifyHeaders?: Record<string, string>;
|
|
110
|
+
settleHeaders?: Record<string, string>;
|
|
111
|
+
supportedHeaders?: Record<string, string>;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/web.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PaymentMethod, ProcessRequestResult, RequestAdapter, WebRestriction } from "./types";
|
|
2
|
+
export declare function formatWebPaymentError(result: Extract<ProcessRequestResult, {
|
|
3
|
+
type: "payment-error";
|
|
4
|
+
}>, restriction: WebRestriction, paymentMethods: PaymentMethod[], adapter: RequestAdapter, termsOfServiceUrl?: string): void;
|
|
5
|
+
//# sourceMappingURL=web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEnG,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,EAChE,WAAW,EAAE,cAAc,EAC3B,cAAc,EAAE,aAAa,EAAE,EAC/B,OAAO,EAAE,cAAc,EACvB,iBAAiB,CAAC,EAAE,MAAM,GACzB,IAAI,CAGN"}
|
package/dist/web.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { generatePaywallHtml } from "./paywall";
|
|
2
|
+
export function formatWebPaymentError(result, restriction, paymentMethods, adapter, termsOfServiceUrl) {
|
|
3
|
+
result.response.body = generatePaywallHtml(restriction, paymentMethods, adapter.getUrl(), termsOfServiceUrl);
|
|
4
|
+
result.response.headers["Content-Type"] = "text/html";
|
|
5
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@foldset/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core types and utilities for Foldset payment protection",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/foldset/sdks.git",
|
|
24
|
+
"directory": "typescript/core"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://docs.foldset.com",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"foldset",
|
|
29
|
+
"micropayments",
|
|
30
|
+
"stablecoin",
|
|
31
|
+
"ai",
|
|
32
|
+
"agents"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@upstash/redis": "^1.36.1",
|
|
36
|
+
"@x402/core": "^2.3.0",
|
|
37
|
+
"@x402/evm": "^2.3.0",
|
|
38
|
+
"@x402/svm": "^2.3.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
}
|
|
47
|
+
}
|