@emdash-cms/x402 0.1.0 → 0.3.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/LICENSE +9 -0
- package/dist/index.mjs +13 -9
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +13 -0
- package/dist/middleware.d.mts.map +1 -0
- package/dist/middleware.mjs +161 -0
- package/dist/middleware.mjs.map +1 -0
- package/dist/server-BAHg4BMb.mjs +142 -0
- package/dist/server-BAHg4BMb.mjs.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +11 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Cloudflare Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/index.mjs
CHANGED
|
@@ -8,15 +8,19 @@ function x402(config) {
|
|
|
8
8
|
return {
|
|
9
9
|
name: "@emdash-cms/x402",
|
|
10
10
|
hooks: { "astro:config:setup": ({ addMiddleware, updateConfig }) => {
|
|
11
|
-
updateConfig({ vite: {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
updateConfig({ vite: {
|
|
12
|
+
plugins: [{
|
|
13
|
+
name: "x402-virtual-config",
|
|
14
|
+
resolveId(id) {
|
|
15
|
+
if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;
|
|
16
|
+
},
|
|
17
|
+
load(id) {
|
|
18
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) return `export default ${JSON.stringify(config)}`;
|
|
19
|
+
}
|
|
20
|
+
}],
|
|
21
|
+
optimizeDeps: { exclude: ["@emdash-cms/x402"] },
|
|
22
|
+
ssr: { optimizeDeps: { exclude: ["@emdash-cms/x402"] } }
|
|
23
|
+
} });
|
|
20
24
|
addMiddleware({
|
|
21
25
|
entrypoint: "@emdash-cms/x402/middleware",
|
|
22
26
|
order: "pre"
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @emdash-cms/x402 -- x402 Payment Integration for Astro\n *\n * An Astro integration that provides x402 payment enforcement via\n * Astro.locals.x402. Supports bot-only mode using Cloudflare Bot Management.\n *\n * @example\n * ```ts\n * // astro.config.mjs\n * import { x402 } from \"@emdash-cms/x402\";\n *\n * export default defineConfig({\n * integrations: [\n * x402({\n * payTo: \"0xYourWallet\",\n * network: \"eip155:8453\",\n * defaultPrice: \"$0.01\",\n * botOnly: true,\n * }),\n * ],\n * });\n * ```\n *\n * ```astro\n * ---\n * const { x402 } = Astro.locals;\n *\n * const result = await x402.enforce(Astro.request, { price: \"$0.05\" });\n * if (result instanceof Response) return result;\n *\n * x402.applyHeaders(result, Astro.response);\n * ---\n * <article>Premium content here</article>\n * ```\n */\n\nimport type { AstroIntegration } from \"astro\";\n\nimport type { X402Config } from \"./types.js\";\n\nconst VIRTUAL_MODULE_ID = \"virtual:x402/config\";\nconst RESOLVED_VIRTUAL_MODULE_ID = \"\\0\" + VIRTUAL_MODULE_ID;\n\n/**\n * Create the x402 Astro integration.\n */\nexport function x402(config: X402Config): AstroIntegration {\n\treturn {\n\t\tname: \"@emdash-cms/x402\",\n\t\thooks: {\n\t\t\t\"astro:config:setup\": ({ addMiddleware, updateConfig }) => {\n\t\t\t\t// Inject the virtual module that provides config to the middleware\n\t\t\t\tupdateConfig({\n\t\t\t\t\tvite: {\n\t\t\t\t\t\tplugins: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: \"x402-virtual-config\",\n\t\t\t\t\t\t\t\tresolveId(id: string) {\n\t\t\t\t\t\t\t\t\tif (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tload(id: string) {\n\t\t\t\t\t\t\t\t\tif (id === RESOLVED_VIRTUAL_MODULE_ID) {\n\t\t\t\t\t\t\t\t\t\treturn `export default ${JSON.stringify(config)}`;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\t// Register the middleware that puts the enforcer on locals\n\t\t\t\taddMiddleware({\n\t\t\t\t\tentrypoint: \"@emdash-cms/x402/middleware\",\n\t\t\t\t\torder: \"pre\",\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t};\n}\n\n// Re-export types for convenience\nexport type {\n\tEnforceOptions,\n\tEnforceResult,\n\tNetwork,\n\tPrice,\n\tX402Config,\n\tX402Enforcer,\n} from \"./types.js\";\n"],"mappings":";AAwCA,MAAM,oBAAoB;AAC1B,MAAM,6BAA6B,OAAO;;;;AAK1C,SAAgB,KAAK,QAAsC;AAC1D,QAAO;EACN,MAAM;EACN,OAAO,EACN,uBAAuB,EAAE,eAAe,mBAAmB;
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @emdash-cms/x402 -- x402 Payment Integration for Astro\n *\n * An Astro integration that provides x402 payment enforcement via\n * Astro.locals.x402. Supports bot-only mode using Cloudflare Bot Management.\n *\n * @example\n * ```ts\n * // astro.config.mjs\n * import { x402 } from \"@emdash-cms/x402\";\n *\n * export default defineConfig({\n * integrations: [\n * x402({\n * payTo: \"0xYourWallet\",\n * network: \"eip155:8453\",\n * defaultPrice: \"$0.01\",\n * botOnly: true,\n * }),\n * ],\n * });\n * ```\n *\n * ```astro\n * ---\n * const { x402 } = Astro.locals;\n *\n * const result = await x402.enforce(Astro.request, { price: \"$0.05\" });\n * if (result instanceof Response) return result;\n *\n * x402.applyHeaders(result, Astro.response);\n * ---\n * <article>Premium content here</article>\n * ```\n */\n\nimport type { AstroIntegration } from \"astro\";\n\nimport type { X402Config } from \"./types.js\";\n\nconst VIRTUAL_MODULE_ID = \"virtual:x402/config\";\nconst RESOLVED_VIRTUAL_MODULE_ID = \"\\0\" + VIRTUAL_MODULE_ID;\n\n/**\n * Create the x402 Astro integration.\n */\nexport function x402(config: X402Config): AstroIntegration {\n\treturn {\n\t\tname: \"@emdash-cms/x402\",\n\t\thooks: {\n\t\t\t\"astro:config:setup\": ({ addMiddleware, updateConfig }) => {\n\t\t\t\t// Inject the virtual module that provides config to the middleware.\n\t\t\t\t// The middleware must be excluded from Vite's SSR dependency optimizer\n\t\t\t\t// because esbuild cannot resolve virtual modules — only Vite plugins can.\n\t\t\t\tupdateConfig({\n\t\t\t\t\tvite: {\n\t\t\t\t\t\tplugins: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tname: \"x402-virtual-config\",\n\t\t\t\t\t\t\t\tresolveId(id: string) {\n\t\t\t\t\t\t\t\t\tif (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tload(id: string) {\n\t\t\t\t\t\t\t\t\tif (id === RESOLVED_VIRTUAL_MODULE_ID) {\n\t\t\t\t\t\t\t\t\t\treturn `export default ${JSON.stringify(config)}`;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\toptimizeDeps: {\n\t\t\t\t\t\t\texclude: [\"@emdash-cms/x402\"],\n\t\t\t\t\t\t},\n\t\t\t\t\t\tssr: {\n\t\t\t\t\t\t\toptimizeDeps: {\n\t\t\t\t\t\t\t\texclude: [\"@emdash-cms/x402\"],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\t// Register the middleware that puts the enforcer on locals\n\t\t\t\taddMiddleware({\n\t\t\t\t\tentrypoint: \"@emdash-cms/x402/middleware\",\n\t\t\t\t\torder: \"pre\",\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t};\n}\n\n// Re-export types for convenience\nexport type {\n\tEnforceOptions,\n\tEnforceResult,\n\tNetwork,\n\tPrice,\n\tX402Config,\n\tX402Enforcer,\n} from \"./types.js\";\n"],"mappings":";AAwCA,MAAM,oBAAoB;AAC1B,MAAM,6BAA6B,OAAO;;;;AAK1C,SAAgB,KAAK,QAAsC;AAC1D,QAAO;EACN,MAAM;EACN,OAAO,EACN,uBAAuB,EAAE,eAAe,mBAAmB;AAI1D,gBAAa,EACZ,MAAM;IACL,SAAS,CACR;KACC,MAAM;KACN,UAAU,IAAY;AACrB,UAAI,OAAO,kBAAmB,QAAO;;KAEtC,KAAK,IAAY;AAChB,UAAI,OAAO,2BACV,QAAO,kBAAkB,KAAK,UAAU,OAAO;;KAGjD,CACD;IACD,cAAc,EACb,SAAS,CAAC,mBAAmB,EAC7B;IACD,KAAK,EACJ,cAAc,EACb,SAAS,CAAC,mBAAmB,EAC7B,EACD;IACD,EACD,CAAC;AAGF,iBAAc;IACb,YAAY;IACZ,OAAO;IACP,CAAC;KAEH;EACD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/middleware.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* x402 Astro Middleware
|
|
4
|
+
*
|
|
5
|
+
* Injected by the x402 integration. Creates the enforcer and
|
|
6
|
+
* places it on Astro.locals.x402 for use in page frontmatter.
|
|
7
|
+
*
|
|
8
|
+
* The config is passed via the virtual module resolved by the integration.
|
|
9
|
+
*/
|
|
10
|
+
declare const onRequest: any;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { onRequest };
|
|
13
|
+
//# sourceMappingURL=middleware.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.mts","names":[],"sources":["../src/middleware.ts"],"mappings":";;AAqBA;;;;;;;cAAa,SAAA"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { defineMiddleware } from "astro:middleware";
|
|
2
|
+
import x402Config from "virtual:x402/config";
|
|
3
|
+
import { decodePaymentSignatureHeader, encodePaymentRequiredHeader, encodePaymentResponseHeader } from "@x402/core/http";
|
|
4
|
+
import { HTTPFacilitatorClient, x402ResourceServer } from "@x402/core/server";
|
|
5
|
+
|
|
6
|
+
//#region src/enforcer.ts
|
|
7
|
+
/**
|
|
8
|
+
* x402 Payment Enforcer
|
|
9
|
+
*
|
|
10
|
+
* Creates the x402 enforcement interface. Uses the @x402/core SDK
|
|
11
|
+
* to handle the payment protocol negotiation.
|
|
12
|
+
*/
|
|
13
|
+
const PAYMENT_SIGNATURE_HEADER = "payment-signature";
|
|
14
|
+
const PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
15
|
+
const PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
|
|
16
|
+
const DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
|
|
17
|
+
const DEFAULT_SCHEME = "exact";
|
|
18
|
+
const DEFAULT_MAX_TIMEOUT_SECONDS = 60;
|
|
19
|
+
const DEFAULT_BOT_SCORE_THRESHOLD = 30;
|
|
20
|
+
/**
|
|
21
|
+
* Cached resource server instance.
|
|
22
|
+
* Initialized once per process, reused across requests.
|
|
23
|
+
*/
|
|
24
|
+
let _resourceServer = null;
|
|
25
|
+
let _initPromise = null;
|
|
26
|
+
/**
|
|
27
|
+
* Get or create the x402ResourceServer singleton.
|
|
28
|
+
*/
|
|
29
|
+
async function getResourceServer(config) {
|
|
30
|
+
if (!_resourceServer) {
|
|
31
|
+
const server = new x402ResourceServer(new HTTPFacilitatorClient({ url: config.facilitatorUrl ?? DEFAULT_FACILITATOR_URL }));
|
|
32
|
+
if (config.evm !== false) try {
|
|
33
|
+
const evmScheme = new (await (import("@x402/evm/exact/server"))).ExactEvmScheme();
|
|
34
|
+
server.register("eip155:*", evmScheme);
|
|
35
|
+
} catch {}
|
|
36
|
+
if (config.svm) try {
|
|
37
|
+
const svmScheme = new (await (import("./server-BAHg4BMb.mjs"))).ExactSvmScheme();
|
|
38
|
+
server.register("solana:*", svmScheme);
|
|
39
|
+
} catch {}
|
|
40
|
+
_resourceServer = server;
|
|
41
|
+
_initPromise = server.initialize();
|
|
42
|
+
}
|
|
43
|
+
if (_initPromise) {
|
|
44
|
+
await _initPromise;
|
|
45
|
+
_initPromise = null;
|
|
46
|
+
}
|
|
47
|
+
return _resourceServer;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if a request is from a bot using Cloudflare Bot Management.
|
|
51
|
+
* Returns true if the request is likely from a bot, false otherwise.
|
|
52
|
+
* When bot management data is unavailable (local dev, non-CF deployment),
|
|
53
|
+
* returns false (treat as human).
|
|
54
|
+
*/
|
|
55
|
+
function isBot(request, threshold) {
|
|
56
|
+
const cf = Reflect.get(request, "cf");
|
|
57
|
+
if (cf == null || typeof cf !== "object") return false;
|
|
58
|
+
const bm = Reflect.get(cf, "botManagement");
|
|
59
|
+
if (bm == null || typeof bm !== "object") return false;
|
|
60
|
+
const score = Reflect.get(bm, "score");
|
|
61
|
+
if (typeof score !== "number") return false;
|
|
62
|
+
return score < threshold;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create an X402Enforcer for the given configuration.
|
|
66
|
+
* Called once by the middleware, reused across requests.
|
|
67
|
+
*/
|
|
68
|
+
function createEnforcer(config) {
|
|
69
|
+
const botScoreThreshold = config.botScoreThreshold ?? DEFAULT_BOT_SCORE_THRESHOLD;
|
|
70
|
+
return {
|
|
71
|
+
async enforce(request, options) {
|
|
72
|
+
if (config.botOnly && !isBot(request, botScoreThreshold)) return {
|
|
73
|
+
paid: false,
|
|
74
|
+
skipped: true,
|
|
75
|
+
responseHeaders: {}
|
|
76
|
+
};
|
|
77
|
+
const server = await getResourceServer(config);
|
|
78
|
+
const price = options?.price ?? config.defaultPrice;
|
|
79
|
+
if (price == null) throw new Error("x402: No price specified. Pass a price in enforce() options or set defaultPrice in the config.");
|
|
80
|
+
const payTo = options?.payTo ?? config.payTo;
|
|
81
|
+
const network = options?.network ?? config.network;
|
|
82
|
+
const scheme = options?.scheme ?? config.scheme ?? DEFAULT_SCHEME;
|
|
83
|
+
const maxTimeoutSeconds = config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
84
|
+
const resourceConfig = {
|
|
85
|
+
scheme,
|
|
86
|
+
payTo,
|
|
87
|
+
price: normalizePrice(price),
|
|
88
|
+
network,
|
|
89
|
+
maxTimeoutSeconds
|
|
90
|
+
};
|
|
91
|
+
const resourceInfo = {
|
|
92
|
+
url: new URL(request.url).pathname,
|
|
93
|
+
description: options?.description,
|
|
94
|
+
mimeType: options?.mimeType
|
|
95
|
+
};
|
|
96
|
+
const paymentHeader = request.headers.get(PAYMENT_SIGNATURE_HEADER) || request.headers.get("PAYMENT-SIGNATURE");
|
|
97
|
+
if (!paymentHeader) return make402(server, resourceConfig, resourceInfo, "Payment required");
|
|
98
|
+
const paymentPayload = decodePaymentSignatureHeader(paymentHeader);
|
|
99
|
+
const requirements = await server.buildPaymentRequirements(resourceConfig);
|
|
100
|
+
const matchingReqs = server.findMatchingRequirements(requirements, paymentPayload);
|
|
101
|
+
if (!matchingReqs) return make402(server, resourceConfig, resourceInfo, "Payment does not match accepted requirements");
|
|
102
|
+
const verifyResult = await server.verifyPayment(paymentPayload, matchingReqs);
|
|
103
|
+
if (!verifyResult.isValid) return make402(server, resourceConfig, resourceInfo, verifyResult.invalidReason ?? "Payment verification failed");
|
|
104
|
+
const settleResult = await server.settlePayment(paymentPayload, matchingReqs);
|
|
105
|
+
const responseHeaders = {};
|
|
106
|
+
if (settleResult) responseHeaders[PAYMENT_RESPONSE_HEADER] = encodePaymentResponseHeader(settleResult);
|
|
107
|
+
return {
|
|
108
|
+
paid: true,
|
|
109
|
+
skipped: false,
|
|
110
|
+
payer: verifyResult.payer,
|
|
111
|
+
settlement: settleResult,
|
|
112
|
+
responseHeaders
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
applyHeaders(result, response) {
|
|
116
|
+
for (const [key, value] of Object.entries(result.responseHeaders)) response.headers.set(key, value);
|
|
117
|
+
},
|
|
118
|
+
hasPayment(request) {
|
|
119
|
+
return !!(request.headers.get(PAYMENT_SIGNATURE_HEADER) || request.headers.get("PAYMENT-SIGNATURE"));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Build and return a 402 Response */
|
|
124
|
+
async function make402(server, resourceConfig, resourceInfo, error) {
|
|
125
|
+
const requirements = await server.buildPaymentRequirements(resourceConfig);
|
|
126
|
+
const paymentRequired = await server.createPaymentRequiredResponse(requirements, resourceInfo, error);
|
|
127
|
+
return new Response(JSON.stringify(paymentRequired), {
|
|
128
|
+
status: 402,
|
|
129
|
+
headers: {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
[PAYMENT_REQUIRED_HEADER]: encodePaymentRequiredHeader(paymentRequired)
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Normalize a user-friendly price into the format expected by x402 SDK.
|
|
137
|
+
*/
|
|
138
|
+
function normalizePrice(price) {
|
|
139
|
+
if (typeof price === "string" && price.startsWith("$")) return price.slice(1);
|
|
140
|
+
return price;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/middleware.ts
|
|
145
|
+
/**
|
|
146
|
+
* x402 Astro Middleware
|
|
147
|
+
*
|
|
148
|
+
* Injected by the x402 integration. Creates the enforcer and
|
|
149
|
+
* places it on Astro.locals.x402 for use in page frontmatter.
|
|
150
|
+
*
|
|
151
|
+
* The config is passed via the virtual module resolved by the integration.
|
|
152
|
+
*/
|
|
153
|
+
const enforcer = createEnforcer(x402Config);
|
|
154
|
+
const onRequest = defineMiddleware(async (context, next) => {
|
|
155
|
+
context.locals.x402 = enforcer;
|
|
156
|
+
return next();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
export { onRequest };
|
|
161
|
+
//# sourceMappingURL=middleware.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.mjs","names":[],"sources":["../src/enforcer.ts","../src/middleware.ts"],"sourcesContent":["/**\n * x402 Payment Enforcer\n *\n * Creates the x402 enforcement interface. Uses the @x402/core SDK\n * to handle the payment protocol negotiation.\n */\n\nimport {\n\tdecodePaymentSignatureHeader,\n\tencodePaymentRequiredHeader,\n\tencodePaymentResponseHeader,\n} from \"@x402/core/http\";\nimport { HTTPFacilitatorClient, x402ResourceServer, type ResourceConfig } from \"@x402/core/server\";\n\nimport type { EnforceOptions, EnforceResult, X402Config, X402Enforcer } from \"./types.js\";\n\nconst PAYMENT_SIGNATURE_HEADER = \"payment-signature\";\nconst PAYMENT_REQUIRED_HEADER = \"PAYMENT-REQUIRED\";\nconst PAYMENT_RESPONSE_HEADER = \"PAYMENT-RESPONSE\";\n\nconst DEFAULT_FACILITATOR_URL = \"https://x402.org/facilitator\";\nconst DEFAULT_SCHEME = \"exact\";\nconst DEFAULT_MAX_TIMEOUT_SECONDS = 60;\nconst DEFAULT_BOT_SCORE_THRESHOLD = 30;\n\n/**\n * Cached resource server instance.\n * Initialized once per process, reused across requests.\n */\nlet _resourceServer: x402ResourceServer | null = null;\nlet _initPromise: Promise<void> | null = null;\n\n/**\n * Get or create the x402ResourceServer singleton.\n */\nasync function getResourceServer(config: X402Config): Promise<x402ResourceServer> {\n\tif (!_resourceServer) {\n\t\tconst facilitatorUrl = config.facilitatorUrl ?? DEFAULT_FACILITATOR_URL;\n\t\tconst facilitator = new HTTPFacilitatorClient({ url: facilitatorUrl });\n\t\tconst server = new x402ResourceServer(facilitator);\n\n\t\t// Register EVM scheme (default)\n\t\tif (config.evm !== false) {\n\t\t\ttry {\n\t\t\t\tconst evmMod = await import(\"@x402/evm/exact/server\");\n\t\t\t\tconst evmScheme = new evmMod.ExactEvmScheme();\n\t\t\t\tserver.register(\"eip155:*\" as `${string}:${string}`, evmScheme);\n\t\t\t} catch {\n\t\t\t\t// @x402/evm not installed -- skip EVM support\n\t\t\t}\n\t\t}\n\n\t\t// Register SVM scheme (opt-in)\n\t\tif (config.svm) {\n\t\t\ttry {\n\t\t\t\tconst svmMod = await import(\"@x402/svm/exact/server\");\n\t\t\t\tconst svmScheme = new svmMod.ExactSvmScheme();\n\t\t\t\tserver.register(\"solana:*\" as `${string}:${string}`, svmScheme);\n\t\t\t} catch {\n\t\t\t\t// @x402/svm not installed -- skip Solana support\n\t\t\t}\n\t\t}\n\n\t\t_resourceServer = server;\n\t\t_initPromise = server.initialize();\n\t}\n\n\tif (_initPromise) {\n\t\tawait _initPromise;\n\t\t_initPromise = null;\n\t}\n\n\treturn _resourceServer;\n}\n\n/**\n * Check if a request is from a bot using Cloudflare Bot Management.\n * Returns true if the request is likely from a bot, false otherwise.\n * When bot management data is unavailable (local dev, non-CF deployment),\n * returns false (treat as human).\n */\nfunction isBot(request: Request, threshold: number): boolean {\n\t// Cloudflare Workers expose cf properties on the request\n\tconst cf: unknown = Reflect.get(request, \"cf\");\n\tif (cf == null || typeof cf !== \"object\") return false;\n\tconst bm: unknown = Reflect.get(cf, \"botManagement\");\n\tif (bm == null || typeof bm !== \"object\") return false;\n\tconst score: unknown = Reflect.get(bm, \"score\");\n\tif (typeof score !== \"number\") return false;\n\treturn score < threshold;\n}\n\n/**\n * Create an X402Enforcer for the given configuration.\n * Called once by the middleware, reused across requests.\n */\nexport function createEnforcer(config: X402Config): X402Enforcer {\n\tconst botScoreThreshold = config.botScoreThreshold ?? DEFAULT_BOT_SCORE_THRESHOLD;\n\n\treturn {\n\t\tasync enforce(request: Request, options?: EnforceOptions): Promise<Response | EnforceResult> {\n\t\t\t// In botOnly mode, skip enforcement for humans\n\t\t\tif (config.botOnly && !isBot(request, botScoreThreshold)) {\n\t\t\t\treturn { paid: false, skipped: true, responseHeaders: {} };\n\t\t\t}\n\n\t\t\tconst server = await getResourceServer(config);\n\n\t\t\tconst price = options?.price ?? config.defaultPrice;\n\t\t\tif (price == null) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"x402: No price specified. Pass a price in enforce() options or set defaultPrice in the config.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst payTo = options?.payTo ?? config.payTo;\n\t\t\tconst network = options?.network ?? config.network;\n\t\t\tconst scheme = options?.scheme ?? config.scheme ?? DEFAULT_SCHEME;\n\t\t\tconst maxTimeoutSeconds = config.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;\n\n\t\t\tconst resourceConfig: ResourceConfig = {\n\t\t\t\tscheme,\n\t\t\t\tpayTo,\n\t\t\t\tprice: normalizePrice(price),\n\t\t\t\tnetwork,\n\t\t\t\tmaxTimeoutSeconds,\n\t\t\t};\n\n\t\t\tconst url = new URL(request.url);\n\t\t\tconst resourceInfo = {\n\t\t\t\turl: url.pathname,\n\t\t\t\tdescription: options?.description,\n\t\t\t\tmimeType: options?.mimeType,\n\t\t\t};\n\n\t\t\t// Check for payment signature header\n\t\t\tconst paymentHeader =\n\t\t\t\trequest.headers.get(PAYMENT_SIGNATURE_HEADER) || request.headers.get(\"PAYMENT-SIGNATURE\");\n\n\t\t\tif (!paymentHeader) {\n\t\t\t\treturn make402(server, resourceConfig, resourceInfo, \"Payment required\");\n\t\t\t}\n\n\t\t\t// Payment present -- decode and verify\n\t\t\tconst paymentPayload = decodePaymentSignatureHeader(paymentHeader);\n\t\t\tconst requirements = await server.buildPaymentRequirements(resourceConfig);\n\t\t\tconst matchingReqs = server.findMatchingRequirements(requirements, paymentPayload);\n\n\t\t\tif (!matchingReqs) {\n\t\t\t\treturn make402(\n\t\t\t\t\tserver,\n\t\t\t\t\tresourceConfig,\n\t\t\t\t\tresourceInfo,\n\t\t\t\t\t\"Payment does not match accepted requirements\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Verify with facilitator\n\t\t\tconst verifyResult = await server.verifyPayment(paymentPayload, matchingReqs);\n\n\t\t\tif (!verifyResult.isValid) {\n\t\t\t\treturn make402(\n\t\t\t\t\tserver,\n\t\t\t\t\tresourceConfig,\n\t\t\t\t\tresourceInfo,\n\t\t\t\t\tverifyResult.invalidReason ?? \"Payment verification failed\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Settle\n\t\t\tconst settleResult = await server.settlePayment(paymentPayload, matchingReqs);\n\n\t\t\tconst responseHeaders: Record<string, string> = {};\n\t\t\tif (settleResult) {\n\t\t\t\tresponseHeaders[PAYMENT_RESPONSE_HEADER] = encodePaymentResponseHeader(settleResult);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tpaid: true,\n\t\t\t\tskipped: false,\n\t\t\t\tpayer: verifyResult.payer,\n\t\t\t\tsettlement: settleResult,\n\t\t\t\tresponseHeaders,\n\t\t\t};\n\t\t},\n\n\t\tapplyHeaders(result: EnforceResult, response: { headers: Headers }): void {\n\t\t\tfor (const [key, value] of Object.entries(result.responseHeaders)) {\n\t\t\t\tresponse.headers.set(key, value);\n\t\t\t}\n\t\t},\n\n\t\thasPayment(request: Request): boolean {\n\t\t\treturn !!(\n\t\t\t\trequest.headers.get(PAYMENT_SIGNATURE_HEADER) || request.headers.get(\"PAYMENT-SIGNATURE\")\n\t\t\t);\n\t\t},\n\t};\n}\n\n/** Build and return a 402 Response */\nasync function make402(\n\tserver: x402ResourceServer,\n\tresourceConfig: ResourceConfig,\n\tresourceInfo: { url: string; description?: string; mimeType?: string },\n\terror: string,\n): Promise<Response> {\n\tconst requirements = await server.buildPaymentRequirements(resourceConfig);\n\tconst paymentRequired = await server.createPaymentRequiredResponse(\n\t\trequirements,\n\t\tresourceInfo,\n\t\terror,\n\t);\n\n\treturn new Response(JSON.stringify(paymentRequired), {\n\t\tstatus: 402,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t[PAYMENT_REQUIRED_HEADER]: encodePaymentRequiredHeader(paymentRequired),\n\t\t},\n\t});\n}\n\n/**\n * Normalize a user-friendly price into the format expected by x402 SDK.\n */\nfunction normalizePrice(\n\tprice: string | number | { amount: string; asset: string; extra?: Record<string, unknown> },\n): string | number | { amount: string; asset: string; extra?: Record<string, unknown> } {\n\tif (typeof price === \"string\" && price.startsWith(\"$\")) {\n\t\treturn price.slice(1);\n\t}\n\treturn price;\n}\n","/**\n * x402 Astro Middleware\n *\n * Injected by the x402 integration. Creates the enforcer and\n * places it on Astro.locals.x402 for use in page frontmatter.\n *\n * The config is passed via the virtual module resolved by the integration.\n */\n\nimport { defineMiddleware } from \"astro:middleware\";\n// The integration injects config via a virtual module.\n// @ts-ignore -- virtual module, resolved at build time\nimport x402Config from \"virtual:x402/config\";\n\nimport { createEnforcer } from \"./enforcer.js\";\nimport type { X402Config } from \"./types.js\";\n\n// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- virtual module import has no type info\nconst config: X402Config = x402Config as X402Config;\nconst enforcer = createEnforcer(config);\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n\tcontext.locals.x402 = enforcer;\n\treturn next();\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/@x402+svm@2.8.0_@solana+kit@5.5.1_typescript@5.9.3__@solana+sysvars@5.5.1_typescript@5.9.3_/node_modules/@x402/svm/dist/esm/chunk-TZGEGCWJ.mjs
|
|
2
|
+
var USDC_MAINNET_ADDRESS = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
3
|
+
var USDC_DEVNET_ADDRESS = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
|
|
4
|
+
var USDC_TESTNET_ADDRESS = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
|
|
5
|
+
var SOLANA_MAINNET_CAIP2 = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
6
|
+
var SOLANA_DEVNET_CAIP2 = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
7
|
+
var SOLANA_TESTNET_CAIP2 = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
|
|
8
|
+
var V1_TO_V2_NETWORK_MAP = {
|
|
9
|
+
solana: SOLANA_MAINNET_CAIP2,
|
|
10
|
+
"solana-devnet": SOLANA_DEVNET_CAIP2,
|
|
11
|
+
"solana-testnet": SOLANA_TESTNET_CAIP2
|
|
12
|
+
};
|
|
13
|
+
function normalizeNetwork(network) {
|
|
14
|
+
if (network.includes(":")) {
|
|
15
|
+
if (![
|
|
16
|
+
SOLANA_MAINNET_CAIP2,
|
|
17
|
+
SOLANA_DEVNET_CAIP2,
|
|
18
|
+
SOLANA_TESTNET_CAIP2
|
|
19
|
+
].includes(network)) throw new Error(`Unsupported SVM network: ${network}`);
|
|
20
|
+
return network;
|
|
21
|
+
}
|
|
22
|
+
const caip2Network = V1_TO_V2_NETWORK_MAP[network];
|
|
23
|
+
if (!caip2Network) throw new Error(`Unsupported SVM network: ${network}`);
|
|
24
|
+
return caip2Network;
|
|
25
|
+
}
|
|
26
|
+
function getUsdcAddress(network) {
|
|
27
|
+
switch (normalizeNetwork(network)) {
|
|
28
|
+
case SOLANA_MAINNET_CAIP2: return USDC_MAINNET_ADDRESS;
|
|
29
|
+
case SOLANA_DEVNET_CAIP2: return USDC_DEVNET_ADDRESS;
|
|
30
|
+
case SOLANA_TESTNET_CAIP2: return USDC_TESTNET_ADDRESS;
|
|
31
|
+
default: throw new Error(`No USDC address configured for network: ${network}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function convertToTokenAmount(decimalAmount, decimals) {
|
|
35
|
+
const amount = parseFloat(decimalAmount);
|
|
36
|
+
if (isNaN(amount)) throw new Error(`Invalid amount: ${decimalAmount}`);
|
|
37
|
+
const [intPart, decPart = ""] = String(amount).split(".");
|
|
38
|
+
return (intPart + decPart.padEnd(decimals, "0").slice(0, decimals)).replace(/^0+/, "") || "0";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region ../../node_modules/.pnpm/@x402+svm@2.8.0_@solana+kit@5.5.1_typescript@5.9.3__@solana+sysvars@5.5.1_typescript@5.9.3_/node_modules/@x402/svm/dist/esm/exact/server/index.mjs
|
|
43
|
+
var ExactSvmScheme = class {
|
|
44
|
+
constructor() {
|
|
45
|
+
this.scheme = "exact";
|
|
46
|
+
this.moneyParsers = [];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Register a custom money parser in the parser chain.
|
|
50
|
+
* Multiple parsers can be registered - they will be tried in registration order.
|
|
51
|
+
* Each parser receives a decimal amount (e.g., 1.50 for $1.50).
|
|
52
|
+
* If a parser returns null, the next parser in the chain will be tried.
|
|
53
|
+
* The default parser is always the final fallback.
|
|
54
|
+
*
|
|
55
|
+
* @param parser - Custom function to convert amount to AssetAmount (or null to skip)
|
|
56
|
+
* @returns The service instance for chaining
|
|
57
|
+
*/
|
|
58
|
+
registerMoneyParser(parser) {
|
|
59
|
+
this.moneyParsers.push(parser);
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parses a price into an asset amount.
|
|
64
|
+
* If price is already an AssetAmount, returns it directly.
|
|
65
|
+
* If price is Money (string | number), parses to decimal and tries custom parsers.
|
|
66
|
+
* Falls back to default conversion if all custom parsers return null.
|
|
67
|
+
*
|
|
68
|
+
* @param price - The price to parse
|
|
69
|
+
* @param network - The network to use
|
|
70
|
+
* @returns Promise that resolves to the parsed asset amount
|
|
71
|
+
*/
|
|
72
|
+
async parsePrice(price, network) {
|
|
73
|
+
if (typeof price === "object" && price !== null && "amount" in price) {
|
|
74
|
+
if (!price.asset) throw new Error(`Asset address must be specified for AssetAmount on network ${network}`);
|
|
75
|
+
return {
|
|
76
|
+
amount: price.amount,
|
|
77
|
+
asset: price.asset,
|
|
78
|
+
extra: price.extra || {}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const amount = this.parseMoneyToDecimal(price);
|
|
82
|
+
for (const parser of this.moneyParsers) {
|
|
83
|
+
const result = await parser(amount, network);
|
|
84
|
+
if (result !== null) return result;
|
|
85
|
+
}
|
|
86
|
+
return this.defaultMoneyConversion(amount, network);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build payment requirements for this scheme/network combination
|
|
90
|
+
*
|
|
91
|
+
* @param paymentRequirements - The base payment requirements
|
|
92
|
+
* @param supportedKind - The supported kind configuration
|
|
93
|
+
* @param supportedKind.x402Version - The x402 protocol version
|
|
94
|
+
* @param supportedKind.scheme - The payment scheme
|
|
95
|
+
* @param supportedKind.network - The network identifier
|
|
96
|
+
* @param supportedKind.extra - Extra metadata including feePayer address
|
|
97
|
+
* @param extensionKeys - Extension keys supported by the facilitator
|
|
98
|
+
* @returns Enhanced payment requirements with feePayer in extra
|
|
99
|
+
*/
|
|
100
|
+
enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {
|
|
101
|
+
return Promise.resolve({
|
|
102
|
+
...paymentRequirements,
|
|
103
|
+
extra: {
|
|
104
|
+
...paymentRequirements.extra,
|
|
105
|
+
feePayer: supportedKind.extra?.feePayer
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Parse Money (string | number) to a decimal number.
|
|
111
|
+
* Handles formats like "$1.50", "1.50", 1.50, etc.
|
|
112
|
+
*
|
|
113
|
+
* @param money - The money value to parse
|
|
114
|
+
* @returns Decimal number
|
|
115
|
+
*/
|
|
116
|
+
parseMoneyToDecimal(money) {
|
|
117
|
+
if (typeof money === "number") return money;
|
|
118
|
+
const cleanMoney = money.replace(/^\$/, "").trim();
|
|
119
|
+
const amount = parseFloat(cleanMoney);
|
|
120
|
+
if (isNaN(amount)) throw new Error(`Invalid money format: ${money}`);
|
|
121
|
+
return amount;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Default money conversion implementation.
|
|
125
|
+
* Converts decimal amount to USDC on the specified network.
|
|
126
|
+
*
|
|
127
|
+
* @param amount - The decimal amount (e.g., 1.50)
|
|
128
|
+
* @param network - The network to use
|
|
129
|
+
* @returns The parsed asset amount in USDC
|
|
130
|
+
*/
|
|
131
|
+
defaultMoneyConversion(amount, network) {
|
|
132
|
+
return {
|
|
133
|
+
amount: convertToTokenAmount(amount.toString(), 6),
|
|
134
|
+
asset: getUsdcAddress(network),
|
|
135
|
+
extra: {}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
//#endregion
|
|
141
|
+
export { ExactSvmScheme };
|
|
142
|
+
//# sourceMappingURL=server-BAHg4BMb.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-BAHg4BMb.mjs","names":[],"sources":["../../../node_modules/.pnpm/@x402+svm@2.8.0_@solana+kit@5.5.1_typescript@5.9.3__@solana+sysvars@5.5.1_typescript@5.9.3_/node_modules/@x402/svm/dist/esm/chunk-TZGEGCWJ.mjs","../../../node_modules/.pnpm/@x402+svm@2.8.0_@solana+kit@5.5.1_typescript@5.9.3__@solana+sysvars@5.5.1_typescript@5.9.3_/node_modules/@x402/svm/dist/esm/exact/server/index.mjs"],"sourcesContent":["// src/constants.ts\nvar TOKEN_PROGRAM_ADDRESS = \"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\";\nvar TOKEN_2022_PROGRAM_ADDRESS = \"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb\";\nvar COMPUTE_BUDGET_PROGRAM_ADDRESS = \"ComputeBudget111111111111111111111111111111\";\nvar MEMO_PROGRAM_ADDRESS = \"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr\";\nvar LIGHTHOUSE_PROGRAM_ADDRESS = \"L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95\";\nvar DEVNET_RPC_URL = \"https://api.devnet.solana.com\";\nvar TESTNET_RPC_URL = \"https://api.testnet.solana.com\";\nvar MAINNET_RPC_URL = \"https://api.mainnet-beta.solana.com\";\nvar DEVNET_WS_URL = \"wss://api.devnet.solana.com\";\nvar TESTNET_WS_URL = \"wss://api.testnet.solana.com\";\nvar MAINNET_WS_URL = \"wss://api.mainnet-beta.solana.com\";\nvar USDC_MAINNET_ADDRESS = \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\";\nvar USDC_DEVNET_ADDRESS = \"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU\";\nvar USDC_TESTNET_ADDRESS = \"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU\";\nvar DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;\nvar MAX_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 5e6;\nvar DEFAULT_COMPUTE_UNIT_LIMIT = 2e4;\nvar SETTLEMENT_TTL_MS = 12e4;\nvar SVM_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\nvar SOLANA_MAINNET_CAIP2 = \"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\";\nvar SOLANA_DEVNET_CAIP2 = \"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1\";\nvar SOLANA_TESTNET_CAIP2 = \"solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z\";\nvar V1_TO_V2_NETWORK_MAP = {\n solana: SOLANA_MAINNET_CAIP2,\n \"solana-devnet\": SOLANA_DEVNET_CAIP2,\n \"solana-testnet\": SOLANA_TESTNET_CAIP2\n};\n\n// src/utils.ts\nimport {\n getBase64Encoder,\n getTransactionDecoder,\n getCompiledTransactionMessageDecoder,\n createSolanaRpc,\n devnet,\n testnet,\n mainnet\n} from \"@solana/kit\";\nimport { TOKEN_PROGRAM_ADDRESS as TOKEN_PROGRAM_ADDRESS2 } from \"@solana-program/token\";\nimport { TOKEN_2022_PROGRAM_ADDRESS as TOKEN_2022_PROGRAM_ADDRESS2 } from \"@solana-program/token-2022\";\nfunction normalizeNetwork(network) {\n if (network.includes(\":\")) {\n const supported = [SOLANA_MAINNET_CAIP2, SOLANA_DEVNET_CAIP2, SOLANA_TESTNET_CAIP2];\n if (!supported.includes(network)) {\n throw new Error(`Unsupported SVM network: ${network}`);\n }\n return network;\n }\n const caip2Network = V1_TO_V2_NETWORK_MAP[network];\n if (!caip2Network) {\n throw new Error(`Unsupported SVM network: ${network}`);\n }\n return caip2Network;\n}\nfunction validateSvmAddress(address) {\n return SVM_ADDRESS_REGEX.test(address);\n}\nfunction decodeTransactionFromPayload(svmPayload) {\n try {\n const base64Encoder = getBase64Encoder();\n const transactionBytes = base64Encoder.encode(svmPayload.transaction);\n const transactionDecoder = getTransactionDecoder();\n return transactionDecoder.decode(transactionBytes);\n } catch (error) {\n console.error(\"Error decoding transaction:\", error);\n throw new Error(\"invalid_exact_svm_payload_transaction\");\n }\n}\nfunction getTokenPayerFromTransaction(transaction) {\n const compiled = getCompiledTransactionMessageDecoder().decode(transaction.messageBytes);\n const staticAccounts = compiled.staticAccounts ?? [];\n const instructions = compiled.instructions ?? [];\n for (const ix of instructions) {\n const programIndex = ix.programAddressIndex;\n const programAddress = staticAccounts[programIndex].toString();\n if (programAddress === TOKEN_PROGRAM_ADDRESS2.toString() || programAddress === TOKEN_2022_PROGRAM_ADDRESS2.toString()) {\n const accountIndices = ix.accountIndices ?? [];\n if (accountIndices.length >= 4) {\n const ownerIndex = accountIndices[3];\n const ownerAddress = staticAccounts[ownerIndex].toString();\n if (ownerAddress) return ownerAddress;\n }\n }\n }\n return \"\";\n}\nfunction createRpcClient(network, customRpcUrl) {\n const caip2Network = normalizeNetwork(network);\n switch (caip2Network) {\n case SOLANA_DEVNET_CAIP2: {\n const url = customRpcUrl || DEVNET_RPC_URL;\n return createSolanaRpc(devnet(url));\n }\n case SOLANA_TESTNET_CAIP2: {\n const url = customRpcUrl || TESTNET_RPC_URL;\n return createSolanaRpc(testnet(url));\n }\n case SOLANA_MAINNET_CAIP2: {\n const url = customRpcUrl || MAINNET_RPC_URL;\n return createSolanaRpc(mainnet(url));\n }\n default:\n throw new Error(`Unsupported network: ${network}`);\n }\n}\nfunction getUsdcAddress(network) {\n const caip2Network = normalizeNetwork(network);\n switch (caip2Network) {\n case SOLANA_MAINNET_CAIP2:\n return USDC_MAINNET_ADDRESS;\n case SOLANA_DEVNET_CAIP2:\n return USDC_DEVNET_ADDRESS;\n case SOLANA_TESTNET_CAIP2:\n return USDC_TESTNET_ADDRESS;\n default:\n throw new Error(`No USDC address configured for network: ${network}`);\n }\n}\nfunction convertToTokenAmount(decimalAmount, decimals) {\n const amount = parseFloat(decimalAmount);\n if (isNaN(amount)) {\n throw new Error(`Invalid amount: ${decimalAmount}`);\n }\n const [intPart, decPart = \"\"] = String(amount).split(\".\");\n const paddedDec = decPart.padEnd(decimals, \"0\").slice(0, decimals);\n const tokenAmount = (intPart + paddedDec).replace(/^0+/, \"\") || \"0\";\n return tokenAmount;\n}\n\nexport {\n TOKEN_PROGRAM_ADDRESS,\n TOKEN_2022_PROGRAM_ADDRESS,\n COMPUTE_BUDGET_PROGRAM_ADDRESS,\n MEMO_PROGRAM_ADDRESS,\n LIGHTHOUSE_PROGRAM_ADDRESS,\n DEVNET_RPC_URL,\n TESTNET_RPC_URL,\n MAINNET_RPC_URL,\n DEVNET_WS_URL,\n TESTNET_WS_URL,\n MAINNET_WS_URL,\n USDC_MAINNET_ADDRESS,\n USDC_DEVNET_ADDRESS,\n USDC_TESTNET_ADDRESS,\n DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS,\n MAX_COMPUTE_UNIT_PRICE_MICROLAMPORTS,\n DEFAULT_COMPUTE_UNIT_LIMIT,\n SETTLEMENT_TTL_MS,\n SVM_ADDRESS_REGEX,\n SOLANA_MAINNET_CAIP2,\n SOLANA_DEVNET_CAIP2,\n SOLANA_TESTNET_CAIP2,\n V1_TO_V2_NETWORK_MAP,\n normalizeNetwork,\n validateSvmAddress,\n decodeTransactionFromPayload,\n getTokenPayerFromTransaction,\n createRpcClient,\n getUsdcAddress,\n convertToTokenAmount\n};\n//# sourceMappingURL=chunk-TZGEGCWJ.mjs.map","import {\n convertToTokenAmount,\n getUsdcAddress\n} from \"../../chunk-TZGEGCWJ.mjs\";\n\n// src/exact/server/scheme.ts\nvar ExactSvmScheme = class {\n constructor() {\n this.scheme = \"exact\";\n this.moneyParsers = [];\n }\n /**\n * Register a custom money parser in the parser chain.\n * Multiple parsers can be registered - they will be tried in registration order.\n * Each parser receives a decimal amount (e.g., 1.50 for $1.50).\n * If a parser returns null, the next parser in the chain will be tried.\n * The default parser is always the final fallback.\n *\n * @param parser - Custom function to convert amount to AssetAmount (or null to skip)\n * @returns The service instance for chaining\n */\n registerMoneyParser(parser) {\n this.moneyParsers.push(parser);\n return this;\n }\n /**\n * Parses a price into an asset amount.\n * If price is already an AssetAmount, returns it directly.\n * If price is Money (string | number), parses to decimal and tries custom parsers.\n * Falls back to default conversion if all custom parsers return null.\n *\n * @param price - The price to parse\n * @param network - The network to use\n * @returns Promise that resolves to the parsed asset amount\n */\n async parsePrice(price, network) {\n if (typeof price === \"object\" && price !== null && \"amount\" in price) {\n if (!price.asset) {\n throw new Error(`Asset address must be specified for AssetAmount on network ${network}`);\n }\n return {\n amount: price.amount,\n asset: price.asset,\n extra: price.extra || {}\n };\n }\n const amount = this.parseMoneyToDecimal(price);\n for (const parser of this.moneyParsers) {\n const result = await parser(amount, network);\n if (result !== null) {\n return result;\n }\n }\n return this.defaultMoneyConversion(amount, network);\n }\n /**\n * Build payment requirements for this scheme/network combination\n *\n * @param paymentRequirements - The base payment requirements\n * @param supportedKind - The supported kind configuration\n * @param supportedKind.x402Version - The x402 protocol version\n * @param supportedKind.scheme - The payment scheme\n * @param supportedKind.network - The network identifier\n * @param supportedKind.extra - Extra metadata including feePayer address\n * @param extensionKeys - Extension keys supported by the facilitator\n * @returns Enhanced payment requirements with feePayer in extra\n */\n enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {\n void extensionKeys;\n return Promise.resolve({\n ...paymentRequirements,\n extra: {\n ...paymentRequirements.extra,\n feePayer: supportedKind.extra?.feePayer\n }\n });\n }\n /**\n * Parse Money (string | number) to a decimal number.\n * Handles formats like \"$1.50\", \"1.50\", 1.50, etc.\n *\n * @param money - The money value to parse\n * @returns Decimal number\n */\n parseMoneyToDecimal(money) {\n if (typeof money === \"number\") {\n return money;\n }\n const cleanMoney = money.replace(/^\\$/, \"\").trim();\n const amount = parseFloat(cleanMoney);\n if (isNaN(amount)) {\n throw new Error(`Invalid money format: ${money}`);\n }\n return amount;\n }\n /**\n * Default money conversion implementation.\n * Converts decimal amount to USDC on the specified network.\n *\n * @param amount - The decimal amount (e.g., 1.50)\n * @param network - The network to use\n * @returns The parsed asset amount in USDC\n */\n defaultMoneyConversion(amount, network) {\n const tokenAmount = convertToTokenAmount(amount.toString(), 6);\n return {\n amount: tokenAmount,\n asset: getUsdcAddress(network),\n extra: {}\n };\n }\n};\n\n// src/exact/server/register.ts\nfunction registerExactSvmScheme(server, config = {}) {\n if (config.networks && config.networks.length > 0) {\n config.networks.forEach((network) => {\n server.register(network, new ExactSvmScheme());\n });\n } else {\n server.register(\"solana:*\", new ExactSvmScheme());\n }\n return server;\n}\nexport {\n ExactSvmScheme,\n registerExactSvmScheme\n};\n//# sourceMappingURL=index.mjs.map"],"x_google_ignoreList":[0,1],"mappings":";AAYA,IAAI,uBAAuB;AAC3B,IAAI,sBAAsB;AAC1B,IAAI,uBAAuB;AAM3B,IAAI,uBAAuB;AAC3B,IAAI,sBAAsB;AAC1B,IAAI,uBAAuB;AAC3B,IAAI,uBAAuB;CACzB,QAAQ;CACR,iBAAiB;CACjB,kBAAkB;CACnB;AAcD,SAAS,iBAAiB,SAAS;AACjC,KAAI,QAAQ,SAAS,IAAI,EAAE;AAEzB,MAAI,CADc;GAAC;GAAsB;GAAqB;GAAqB,CACpE,SAAS,QAAQ,CAC9B,OAAM,IAAI,MAAM,4BAA4B,UAAU;AAExD,SAAO;;CAET,MAAM,eAAe,qBAAqB;AAC1C,KAAI,CAAC,aACH,OAAM,IAAI,MAAM,4BAA4B,UAAU;AAExD,QAAO;;AAqDT,SAAS,eAAe,SAAS;AAE/B,SADqB,iBAAiB,QAAQ,EAC9C;EACE,KAAK,qBACH,QAAO;EACT,KAAK,oBACH,QAAO;EACT,KAAK,qBACH,QAAO;EACT,QACE,OAAM,IAAI,MAAM,2CAA2C,UAAU;;;AAG3E,SAAS,qBAAqB,eAAe,UAAU;CACrD,MAAM,SAAS,WAAW,cAAc;AACxC,KAAI,MAAM,OAAO,CACf,OAAM,IAAI,MAAM,mBAAmB,gBAAgB;CAErD,MAAM,CAAC,SAAS,UAAU,MAAM,OAAO,OAAO,CAAC,MAAM,IAAI;AAGzD,SADqB,UADH,QAAQ,OAAO,UAAU,IAAI,CAAC,MAAM,GAAG,SAAS,EACxB,QAAQ,OAAO,GAAG,IAAI;;;;;ACxHlE,IAAI,iBAAiB,MAAM;CACzB,cAAc;AACZ,OAAK,SAAS;AACd,OAAK,eAAe,EAAE;;;;;;;;;;;;CAYxB,oBAAoB,QAAQ;AAC1B,OAAK,aAAa,KAAK,OAAO;AAC9B,SAAO;;;;;;;;;;;;CAYT,MAAM,WAAW,OAAO,SAAS;AAC/B,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,YAAY,OAAO;AACpE,OAAI,CAAC,MAAM,MACT,OAAM,IAAI,MAAM,8DAA8D,UAAU;AAE1F,UAAO;IACL,QAAQ,MAAM;IACd,OAAO,MAAM;IACb,OAAO,MAAM,SAAS,EAAE;IACzB;;EAEH,MAAM,SAAS,KAAK,oBAAoB,MAAM;AAC9C,OAAK,MAAM,UAAU,KAAK,cAAc;GACtC,MAAM,SAAS,MAAM,OAAO,QAAQ,QAAQ;AAC5C,OAAI,WAAW,KACb,QAAO;;AAGX,SAAO,KAAK,uBAAuB,QAAQ,QAAQ;;;;;;;;;;;;;;CAcrD,2BAA2B,qBAAqB,eAAe,eAAe;AAE5E,SAAO,QAAQ,QAAQ;GACrB,GAAG;GACH,OAAO;IACL,GAAG,oBAAoB;IACvB,UAAU,cAAc,OAAO;IAChC;GACF,CAAC;;;;;;;;;CASJ,oBAAoB,OAAO;AACzB,MAAI,OAAO,UAAU,SACnB,QAAO;EAET,MAAM,aAAa,MAAM,QAAQ,OAAO,GAAG,CAAC,MAAM;EAClD,MAAM,SAAS,WAAW,WAAW;AACrC,MAAI,MAAM,OAAO,CACf,OAAM,IAAI,MAAM,yBAAyB,QAAQ;AAEnD,SAAO;;;;;;;;;;CAUT,uBAAuB,QAAQ,SAAS;AAEtC,SAAO;GACL,QAFkB,qBAAqB,OAAO,UAAU,EAAE,EAAE;GAG5D,OAAO,eAAe,QAAQ;GAC9B,OAAO,EAAE;GACV"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -49,7 +49,9 @@ export function x402(config: X402Config): AstroIntegration {
|
|
|
49
49
|
name: "@emdash-cms/x402",
|
|
50
50
|
hooks: {
|
|
51
51
|
"astro:config:setup": ({ addMiddleware, updateConfig }) => {
|
|
52
|
-
// Inject the virtual module that provides config to the middleware
|
|
52
|
+
// Inject the virtual module that provides config to the middleware.
|
|
53
|
+
// The middleware must be excluded from Vite's SSR dependency optimizer
|
|
54
|
+
// because esbuild cannot resolve virtual modules — only Vite plugins can.
|
|
53
55
|
updateConfig({
|
|
54
56
|
vite: {
|
|
55
57
|
plugins: [
|
|
@@ -65,6 +67,14 @@ export function x402(config: X402Config): AstroIntegration {
|
|
|
65
67
|
},
|
|
66
68
|
},
|
|
67
69
|
],
|
|
70
|
+
optimizeDeps: {
|
|
71
|
+
exclude: ["@emdash-cms/x402"],
|
|
72
|
+
},
|
|
73
|
+
ssr: {
|
|
74
|
+
optimizeDeps: {
|
|
75
|
+
exclude: ["@emdash-cms/x402"],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
68
78
|
},
|
|
69
79
|
});
|
|
70
80
|
|