x402z-server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/index.d.mts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +229 -0
- package/dist/index.mjs +200 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# x402z-server
|
|
2
|
+
|
|
3
|
+
Server-side helpers for the erc7984-mind-v1 x402 scheme.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add x402z-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createX402zServer } from "x402z-server";
|
|
15
|
+
|
|
16
|
+
const server = await createX402zServer({
|
|
17
|
+
facilitatorUrl: "http://localhost:8040",
|
|
18
|
+
asset: "0xToken",
|
|
19
|
+
eip712: { name: "FHEToken Confidential", version: "1" },
|
|
20
|
+
decimals: 6,
|
|
21
|
+
facilitatorAddress: "0xFacilitator",
|
|
22
|
+
routes: {
|
|
23
|
+
"GET /demo": {
|
|
24
|
+
accepts: {
|
|
25
|
+
payTo: "0xPayTo",
|
|
26
|
+
price: "1.0",
|
|
27
|
+
network: "eip155:11155111",
|
|
28
|
+
maxTimeoutSeconds: 300,
|
|
29
|
+
},
|
|
30
|
+
description: "Demo content",
|
|
31
|
+
mimeType: "text/plain",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
onPaid: async () => ({
|
|
35
|
+
body: "demo content from server",
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
server.listen(8080);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
- `createX402zServer(config)`
|
|
45
|
+
- `facilitatorUrl` (required): HTTP facilitator endpoint
|
|
46
|
+
- `asset`, `eip712`, `decimals`, `facilitatorAddress`: scheme config
|
|
47
|
+
- `routes`: map of `METHOD /path` to payment requirements
|
|
48
|
+
- `onPaid`: handler for successful payment (returns response body + optional headers)
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
- Scheme name: `erc7984-mind-v1`
|
|
53
|
+
- `confidential.facilitatorAddress` is included in requirements so clients can bind encrypted inputs.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { SchemeNetworkServer, Network, Price, AssetAmount, PaymentRequirements } from '@x402/core/types';
|
|
2
|
+
export * from '@x402/core/types';
|
|
3
|
+
import { x402ResourceServer } from '@x402/core/server';
|
|
4
|
+
export { x402ResourceServer } from '@x402/core/server';
|
|
5
|
+
import * as http from 'http';
|
|
6
|
+
export { CompiledRoute, DynamicPayTo, DynamicPrice, FacilitatorClient, FacilitatorConfig, HTTPAdapter, HTTPFacilitatorClient, HTTPProcessResult, HTTPRequestContext, HTTPResponseInstructions, PaymentOption, PaywallConfig, PaywallProvider, ProcessSettleFailureResponse, ProcessSettleResultResponse, ProcessSettleSuccessResponse, RouteConfig, RouteConfigurationError, RouteValidationError, RoutesConfig, UnpaidResponseBody, UnpaidResponseResult, x402HTTPResourceServer } from '@x402/core/http';
|
|
7
|
+
|
|
8
|
+
type ConfidentialServerNetworkConfig = {
|
|
9
|
+
asset: `0x${string}`;
|
|
10
|
+
eip712: {
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
};
|
|
14
|
+
decimals?: number;
|
|
15
|
+
resourceHash?: `0x${string}`;
|
|
16
|
+
facilitatorAddress?: `0x${string}`;
|
|
17
|
+
};
|
|
18
|
+
type ConfidentialServerConfig = ConfidentialServerNetworkConfig | {
|
|
19
|
+
getNetworkConfig: (network: Network) => ConfidentialServerNetworkConfig;
|
|
20
|
+
};
|
|
21
|
+
declare class ConfidentialEvmScheme implements SchemeNetworkServer {
|
|
22
|
+
readonly scheme = "erc7984-mind-v1";
|
|
23
|
+
private readonly getConfig;
|
|
24
|
+
constructor(config: ConfidentialServerConfig);
|
|
25
|
+
parsePrice(price: Price, network: Network): Promise<AssetAmount>;
|
|
26
|
+
enhancePaymentRequirements(paymentRequirements: PaymentRequirements, supportedKind: {
|
|
27
|
+
x402Version: number;
|
|
28
|
+
scheme: string;
|
|
29
|
+
network: Network;
|
|
30
|
+
extra?: Record<string, unknown>;
|
|
31
|
+
}, extensionKeys: string[]): Promise<PaymentRequirements>;
|
|
32
|
+
private parseMoneyToDecimal;
|
|
33
|
+
private convertToTokenAmount;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ConfidentialServerRegisterConfig = ConfidentialServerConfig & {
|
|
37
|
+
networks?: Network[];
|
|
38
|
+
};
|
|
39
|
+
declare function registerConfidentialEvmScheme(server: x402ResourceServer, config: ConfidentialServerRegisterConfig): x402ResourceServer;
|
|
40
|
+
|
|
41
|
+
type X402zRouteConfig = {
|
|
42
|
+
accepts: {
|
|
43
|
+
payTo: string;
|
|
44
|
+
price: string;
|
|
45
|
+
network: Network;
|
|
46
|
+
maxTimeoutSeconds: number;
|
|
47
|
+
};
|
|
48
|
+
description: string;
|
|
49
|
+
mimeType: string;
|
|
50
|
+
};
|
|
51
|
+
type X402zRouteHandler = (args: {
|
|
52
|
+
paymentRequirements: PaymentRequirements;
|
|
53
|
+
settleHeaders: Record<string, string>;
|
|
54
|
+
}) => Promise<{
|
|
55
|
+
status?: number;
|
|
56
|
+
headers?: Record<string, string>;
|
|
57
|
+
body: string;
|
|
58
|
+
}>;
|
|
59
|
+
type X402zServerConfig = ConfidentialServerRegisterConfig & {
|
|
60
|
+
facilitatorUrl: string;
|
|
61
|
+
routes: Record<string, X402zRouteConfig>;
|
|
62
|
+
onPaid: X402zRouteHandler;
|
|
63
|
+
debug?: boolean;
|
|
64
|
+
};
|
|
65
|
+
declare function createX402zServer(config: X402zServerConfig): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
|
|
66
|
+
|
|
67
|
+
export { ConfidentialEvmScheme, type ConfidentialServerConfig, type ConfidentialServerNetworkConfig, type ConfidentialServerRegisterConfig, type X402zRouteConfig, type X402zRouteHandler, type X402zServerConfig, createX402zServer, registerConfidentialEvmScheme };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { SchemeNetworkServer, Network, Price, AssetAmount, PaymentRequirements } from '@x402/core/types';
|
|
2
|
+
export * from '@x402/core/types';
|
|
3
|
+
import { x402ResourceServer } from '@x402/core/server';
|
|
4
|
+
export { x402ResourceServer } from '@x402/core/server';
|
|
5
|
+
import * as http from 'http';
|
|
6
|
+
export { CompiledRoute, DynamicPayTo, DynamicPrice, FacilitatorClient, FacilitatorConfig, HTTPAdapter, HTTPFacilitatorClient, HTTPProcessResult, HTTPRequestContext, HTTPResponseInstructions, PaymentOption, PaywallConfig, PaywallProvider, ProcessSettleFailureResponse, ProcessSettleResultResponse, ProcessSettleSuccessResponse, RouteConfig, RouteConfigurationError, RouteValidationError, RoutesConfig, UnpaidResponseBody, UnpaidResponseResult, x402HTTPResourceServer } from '@x402/core/http';
|
|
7
|
+
|
|
8
|
+
type ConfidentialServerNetworkConfig = {
|
|
9
|
+
asset: `0x${string}`;
|
|
10
|
+
eip712: {
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
};
|
|
14
|
+
decimals?: number;
|
|
15
|
+
resourceHash?: `0x${string}`;
|
|
16
|
+
facilitatorAddress?: `0x${string}`;
|
|
17
|
+
};
|
|
18
|
+
type ConfidentialServerConfig = ConfidentialServerNetworkConfig | {
|
|
19
|
+
getNetworkConfig: (network: Network) => ConfidentialServerNetworkConfig;
|
|
20
|
+
};
|
|
21
|
+
declare class ConfidentialEvmScheme implements SchemeNetworkServer {
|
|
22
|
+
readonly scheme = "erc7984-mind-v1";
|
|
23
|
+
private readonly getConfig;
|
|
24
|
+
constructor(config: ConfidentialServerConfig);
|
|
25
|
+
parsePrice(price: Price, network: Network): Promise<AssetAmount>;
|
|
26
|
+
enhancePaymentRequirements(paymentRequirements: PaymentRequirements, supportedKind: {
|
|
27
|
+
x402Version: number;
|
|
28
|
+
scheme: string;
|
|
29
|
+
network: Network;
|
|
30
|
+
extra?: Record<string, unknown>;
|
|
31
|
+
}, extensionKeys: string[]): Promise<PaymentRequirements>;
|
|
32
|
+
private parseMoneyToDecimal;
|
|
33
|
+
private convertToTokenAmount;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ConfidentialServerRegisterConfig = ConfidentialServerConfig & {
|
|
37
|
+
networks?: Network[];
|
|
38
|
+
};
|
|
39
|
+
declare function registerConfidentialEvmScheme(server: x402ResourceServer, config: ConfidentialServerRegisterConfig): x402ResourceServer;
|
|
40
|
+
|
|
41
|
+
type X402zRouteConfig = {
|
|
42
|
+
accepts: {
|
|
43
|
+
payTo: string;
|
|
44
|
+
price: string;
|
|
45
|
+
network: Network;
|
|
46
|
+
maxTimeoutSeconds: number;
|
|
47
|
+
};
|
|
48
|
+
description: string;
|
|
49
|
+
mimeType: string;
|
|
50
|
+
};
|
|
51
|
+
type X402zRouteHandler = (args: {
|
|
52
|
+
paymentRequirements: PaymentRequirements;
|
|
53
|
+
settleHeaders: Record<string, string>;
|
|
54
|
+
}) => Promise<{
|
|
55
|
+
status?: number;
|
|
56
|
+
headers?: Record<string, string>;
|
|
57
|
+
body: string;
|
|
58
|
+
}>;
|
|
59
|
+
type X402zServerConfig = ConfidentialServerRegisterConfig & {
|
|
60
|
+
facilitatorUrl: string;
|
|
61
|
+
routes: Record<string, X402zRouteConfig>;
|
|
62
|
+
onPaid: X402zRouteHandler;
|
|
63
|
+
debug?: boolean;
|
|
64
|
+
};
|
|
65
|
+
declare function createX402zServer(config: X402zServerConfig): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
|
|
66
|
+
|
|
67
|
+
export { ConfidentialEvmScheme, type ConfidentialServerConfig, type ConfidentialServerNetworkConfig, type ConfidentialServerRegisterConfig, type X402zRouteConfig, type X402zRouteHandler, type X402zServerConfig, createX402zServer, registerConfidentialEvmScheme };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ConfidentialEvmScheme: () => ConfidentialEvmScheme,
|
|
24
|
+
HTTPFacilitatorClient: () => import_http4.HTTPFacilitatorClient,
|
|
25
|
+
createX402zServer: () => createX402zServer,
|
|
26
|
+
registerConfidentialEvmScheme: () => registerConfidentialEvmScheme,
|
|
27
|
+
x402HTTPResourceServer: () => import_http4.x402HTTPResourceServer,
|
|
28
|
+
x402ResourceServer: () => import_server2.x402ResourceServer
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/scheme.ts
|
|
33
|
+
var ConfidentialEvmScheme = class {
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.scheme = "erc7984-mind-v1";
|
|
36
|
+
if ("getNetworkConfig" in config) {
|
|
37
|
+
this.getConfig = config.getNetworkConfig;
|
|
38
|
+
} else {
|
|
39
|
+
this.getConfig = () => config;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async parsePrice(price, network) {
|
|
43
|
+
if (typeof price === "object" && price !== null && "amount" in price) {
|
|
44
|
+
if (!price.asset) {
|
|
45
|
+
throw new Error(`Asset address must be specified for AssetAmount on network ${network}`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
amount: price.amount,
|
|
49
|
+
asset: price.asset,
|
|
50
|
+
extra: price.extra || {}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const amount = this.parseMoneyToDecimal(price);
|
|
54
|
+
const config = this.getConfig(network);
|
|
55
|
+
return {
|
|
56
|
+
amount: this.convertToTokenAmount(amount.toString(), config.decimals ?? 6),
|
|
57
|
+
asset: config.asset,
|
|
58
|
+
extra: {
|
|
59
|
+
eip712: config.eip712,
|
|
60
|
+
confidential: {
|
|
61
|
+
maxClearAmount: amount.toString(),
|
|
62
|
+
resourceHash: config.resourceHash,
|
|
63
|
+
facilitatorAddress: config.facilitatorAddress
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {
|
|
69
|
+
void supportedKind;
|
|
70
|
+
void extensionKeys;
|
|
71
|
+
const config = this.getConfig(paymentRequirements.network);
|
|
72
|
+
const extra = paymentRequirements.extra;
|
|
73
|
+
const nextExtra = {
|
|
74
|
+
...paymentRequirements.extra || {},
|
|
75
|
+
eip712: extra?.eip712 ?? config.eip712,
|
|
76
|
+
confidential: {
|
|
77
|
+
maxClearAmount: extra?.confidential?.maxClearAmount ?? paymentRequirements.amount,
|
|
78
|
+
resourceHash: extra?.confidential?.resourceHash ?? config.resourceHash,
|
|
79
|
+
facilitatorAddress: extra?.confidential?.facilitatorAddress ?? config.facilitatorAddress
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
...paymentRequirements,
|
|
84
|
+
extra: nextExtra
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
parseMoneyToDecimal(money) {
|
|
88
|
+
if (typeof money === "number") {
|
|
89
|
+
return money;
|
|
90
|
+
}
|
|
91
|
+
const cleanMoney = money.replace(/^\$/, "").trim();
|
|
92
|
+
const amount = parseFloat(cleanMoney);
|
|
93
|
+
if (Number.isNaN(amount)) {
|
|
94
|
+
throw new Error(`Invalid money format: ${money}`);
|
|
95
|
+
}
|
|
96
|
+
return amount;
|
|
97
|
+
}
|
|
98
|
+
convertToTokenAmount(decimalAmount, decimals) {
|
|
99
|
+
const amount = parseFloat(decimalAmount);
|
|
100
|
+
if (Number.isNaN(amount)) {
|
|
101
|
+
throw new Error(`Invalid amount: ${decimalAmount}`);
|
|
102
|
+
}
|
|
103
|
+
const [intPart, decPart = ""] = String(amount).split(".");
|
|
104
|
+
const paddedDec = decPart.padEnd(decimals, "0").slice(0, decimals);
|
|
105
|
+
return (intPart + paddedDec).replace(/^0+/, "") || "0";
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/register.ts
|
|
110
|
+
function registerConfidentialEvmScheme(server, config) {
|
|
111
|
+
if ("networks" in config && config.networks && config.networks.length > 0) {
|
|
112
|
+
for (const network of config.networks) {
|
|
113
|
+
server.register(network, new ConfidentialEvmScheme(config));
|
|
114
|
+
}
|
|
115
|
+
return server;
|
|
116
|
+
}
|
|
117
|
+
server.register("eip155:*", new ConfidentialEvmScheme(config));
|
|
118
|
+
return server;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/http.ts
|
|
122
|
+
var import_node_http = require("http");
|
|
123
|
+
var import_node_url = require("url");
|
|
124
|
+
var import_server = require("@x402/core/server");
|
|
125
|
+
var import_http = require("@x402/core/http");
|
|
126
|
+
var import_http2 = require("@x402/core/http");
|
|
127
|
+
function buildAdapter(req) {
|
|
128
|
+
const url = new import_node_url.URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
129
|
+
return {
|
|
130
|
+
getHeader: (name) => {
|
|
131
|
+
const value = req.headers[name.toLowerCase()];
|
|
132
|
+
if (!value) return void 0;
|
|
133
|
+
return Array.isArray(value) ? value.join(",") : value;
|
|
134
|
+
},
|
|
135
|
+
getMethod: () => req.method ?? "GET",
|
|
136
|
+
getPath: () => url.pathname,
|
|
137
|
+
getUrl: () => url.toString(),
|
|
138
|
+
getAcceptHeader: () => req.headers.accept ?? "",
|
|
139
|
+
getUserAgent: () => req.headers["user-agent"] ?? ""
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function sendText(res, status, headers, body) {
|
|
143
|
+
res.writeHead(status, headers);
|
|
144
|
+
res.end(body);
|
|
145
|
+
}
|
|
146
|
+
async function createX402zServer(config) {
|
|
147
|
+
const debugEnabled = config.debug ?? process.env.X402Z_DEBUG === "1";
|
|
148
|
+
const facilitatorClient = new import_http2.HTTPFacilitatorClient({ url: config.facilitatorUrl });
|
|
149
|
+
const resourceServer = new import_server.x402ResourceServer(facilitatorClient);
|
|
150
|
+
registerConfidentialEvmScheme(resourceServer, config);
|
|
151
|
+
const routes = {};
|
|
152
|
+
for (const [path, route] of Object.entries(config.routes)) {
|
|
153
|
+
routes[path] = {
|
|
154
|
+
...route,
|
|
155
|
+
accepts: {
|
|
156
|
+
...route.accepts,
|
|
157
|
+
scheme: "erc7984-mind-v1"
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const httpServer = new import_http.x402HTTPResourceServer(resourceServer, routes);
|
|
162
|
+
await httpServer.initialize();
|
|
163
|
+
const server = (0, import_node_http.createServer)(async (req, res) => {
|
|
164
|
+
const adapter = buildAdapter(req);
|
|
165
|
+
const method = adapter.getMethod();
|
|
166
|
+
const path = adapter.getPath();
|
|
167
|
+
const paymentHeader = adapter.getHeader("x402-payment") ?? adapter.getHeader("x-payment");
|
|
168
|
+
console.log(`[server] ${method} ${path}`);
|
|
169
|
+
if (debugEnabled && paymentHeader) {
|
|
170
|
+
console.debug("[x402z-server] payment header", paymentHeader);
|
|
171
|
+
}
|
|
172
|
+
const result = await httpServer.processHTTPRequest({
|
|
173
|
+
adapter,
|
|
174
|
+
path: adapter.getPath(),
|
|
175
|
+
method: adapter.getMethod()
|
|
176
|
+
});
|
|
177
|
+
if (result.type === "payment-error") {
|
|
178
|
+
res.writeHead(result.response.status, result.response.headers);
|
|
179
|
+
res.end(result.response.isHtml ? result.response.body : JSON.stringify(result.response.body ?? {}));
|
|
180
|
+
console.log(`[server] ${method} ${path} -> ${result.response.status}`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (result.type === "payment-verified") {
|
|
184
|
+
const settle = await httpServer.processSettlement(result.paymentPayload, result.paymentRequirements);
|
|
185
|
+
if (!settle.success) {
|
|
186
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
187
|
+
res.end(JSON.stringify({ error: settle.errorReason }));
|
|
188
|
+
console.log(`[server] ${method} ${path} -> 500 settlement_failed`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const payload = await config.onPaid({
|
|
192
|
+
paymentRequirements: result.paymentRequirements,
|
|
193
|
+
settleHeaders: settle.headers
|
|
194
|
+
});
|
|
195
|
+
const status = payload.status ?? 200;
|
|
196
|
+
const responseHeaders = {
|
|
197
|
+
"Content-Type": "text/plain",
|
|
198
|
+
...settle.headers,
|
|
199
|
+
...payload.headers ?? {}
|
|
200
|
+
};
|
|
201
|
+
if (debugEnabled) {
|
|
202
|
+
console.debug("[x402z-server] response", {
|
|
203
|
+
status,
|
|
204
|
+
headers: responseHeaders,
|
|
205
|
+
body: payload.body
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
sendText(res, status, responseHeaders, payload.body);
|
|
209
|
+
console.log(`[server] ${method} ${path} -> ${status}`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
sendText(res, 200, { "Content-Type": "text/plain" }, "no payment required");
|
|
213
|
+
console.log(`[server] ${method} ${path} -> 200`);
|
|
214
|
+
});
|
|
215
|
+
return server;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/index.ts
|
|
219
|
+
var import_server2 = require("@x402/core/server");
|
|
220
|
+
var import_http4 = require("@x402/core/http");
|
|
221
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
222
|
+
0 && (module.exports = {
|
|
223
|
+
ConfidentialEvmScheme,
|
|
224
|
+
HTTPFacilitatorClient,
|
|
225
|
+
createX402zServer,
|
|
226
|
+
registerConfidentialEvmScheme,
|
|
227
|
+
x402HTTPResourceServer,
|
|
228
|
+
x402ResourceServer
|
|
229
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// src/scheme.ts
|
|
2
|
+
var ConfidentialEvmScheme = class {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.scheme = "erc7984-mind-v1";
|
|
5
|
+
if ("getNetworkConfig" in config) {
|
|
6
|
+
this.getConfig = config.getNetworkConfig;
|
|
7
|
+
} else {
|
|
8
|
+
this.getConfig = () => config;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
async parsePrice(price, network) {
|
|
12
|
+
if (typeof price === "object" && price !== null && "amount" in price) {
|
|
13
|
+
if (!price.asset) {
|
|
14
|
+
throw new Error(`Asset address must be specified for AssetAmount on network ${network}`);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
amount: price.amount,
|
|
18
|
+
asset: price.asset,
|
|
19
|
+
extra: price.extra || {}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const amount = this.parseMoneyToDecimal(price);
|
|
23
|
+
const config = this.getConfig(network);
|
|
24
|
+
return {
|
|
25
|
+
amount: this.convertToTokenAmount(amount.toString(), config.decimals ?? 6),
|
|
26
|
+
asset: config.asset,
|
|
27
|
+
extra: {
|
|
28
|
+
eip712: config.eip712,
|
|
29
|
+
confidential: {
|
|
30
|
+
maxClearAmount: amount.toString(),
|
|
31
|
+
resourceHash: config.resourceHash,
|
|
32
|
+
facilitatorAddress: config.facilitatorAddress
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {
|
|
38
|
+
void supportedKind;
|
|
39
|
+
void extensionKeys;
|
|
40
|
+
const config = this.getConfig(paymentRequirements.network);
|
|
41
|
+
const extra = paymentRequirements.extra;
|
|
42
|
+
const nextExtra = {
|
|
43
|
+
...paymentRequirements.extra || {},
|
|
44
|
+
eip712: extra?.eip712 ?? config.eip712,
|
|
45
|
+
confidential: {
|
|
46
|
+
maxClearAmount: extra?.confidential?.maxClearAmount ?? paymentRequirements.amount,
|
|
47
|
+
resourceHash: extra?.confidential?.resourceHash ?? config.resourceHash,
|
|
48
|
+
facilitatorAddress: extra?.confidential?.facilitatorAddress ?? config.facilitatorAddress
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
...paymentRequirements,
|
|
53
|
+
extra: nextExtra
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
parseMoneyToDecimal(money) {
|
|
57
|
+
if (typeof money === "number") {
|
|
58
|
+
return money;
|
|
59
|
+
}
|
|
60
|
+
const cleanMoney = money.replace(/^\$/, "").trim();
|
|
61
|
+
const amount = parseFloat(cleanMoney);
|
|
62
|
+
if (Number.isNaN(amount)) {
|
|
63
|
+
throw new Error(`Invalid money format: ${money}`);
|
|
64
|
+
}
|
|
65
|
+
return amount;
|
|
66
|
+
}
|
|
67
|
+
convertToTokenAmount(decimalAmount, decimals) {
|
|
68
|
+
const amount = parseFloat(decimalAmount);
|
|
69
|
+
if (Number.isNaN(amount)) {
|
|
70
|
+
throw new Error(`Invalid amount: ${decimalAmount}`);
|
|
71
|
+
}
|
|
72
|
+
const [intPart, decPart = ""] = String(amount).split(".");
|
|
73
|
+
const paddedDec = decPart.padEnd(decimals, "0").slice(0, decimals);
|
|
74
|
+
return (intPart + paddedDec).replace(/^0+/, "") || "0";
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/register.ts
|
|
79
|
+
function registerConfidentialEvmScheme(server, config) {
|
|
80
|
+
if ("networks" in config && config.networks && config.networks.length > 0) {
|
|
81
|
+
for (const network of config.networks) {
|
|
82
|
+
server.register(network, new ConfidentialEvmScheme(config));
|
|
83
|
+
}
|
|
84
|
+
return server;
|
|
85
|
+
}
|
|
86
|
+
server.register("eip155:*", new ConfidentialEvmScheme(config));
|
|
87
|
+
return server;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/http.ts
|
|
91
|
+
import { createServer } from "http";
|
|
92
|
+
import { URL } from "url";
|
|
93
|
+
import { x402ResourceServer } from "@x402/core/server";
|
|
94
|
+
import { x402HTTPResourceServer } from "@x402/core/http";
|
|
95
|
+
import { HTTPFacilitatorClient } from "@x402/core/http";
|
|
96
|
+
function buildAdapter(req) {
|
|
97
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
98
|
+
return {
|
|
99
|
+
getHeader: (name) => {
|
|
100
|
+
const value = req.headers[name.toLowerCase()];
|
|
101
|
+
if (!value) return void 0;
|
|
102
|
+
return Array.isArray(value) ? value.join(",") : value;
|
|
103
|
+
},
|
|
104
|
+
getMethod: () => req.method ?? "GET",
|
|
105
|
+
getPath: () => url.pathname,
|
|
106
|
+
getUrl: () => url.toString(),
|
|
107
|
+
getAcceptHeader: () => req.headers.accept ?? "",
|
|
108
|
+
getUserAgent: () => req.headers["user-agent"] ?? ""
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function sendText(res, status, headers, body) {
|
|
112
|
+
res.writeHead(status, headers);
|
|
113
|
+
res.end(body);
|
|
114
|
+
}
|
|
115
|
+
async function createX402zServer(config) {
|
|
116
|
+
const debugEnabled = config.debug ?? process.env.X402Z_DEBUG === "1";
|
|
117
|
+
const facilitatorClient = new HTTPFacilitatorClient({ url: config.facilitatorUrl });
|
|
118
|
+
const resourceServer = new x402ResourceServer(facilitatorClient);
|
|
119
|
+
registerConfidentialEvmScheme(resourceServer, config);
|
|
120
|
+
const routes = {};
|
|
121
|
+
for (const [path, route] of Object.entries(config.routes)) {
|
|
122
|
+
routes[path] = {
|
|
123
|
+
...route,
|
|
124
|
+
accepts: {
|
|
125
|
+
...route.accepts,
|
|
126
|
+
scheme: "erc7984-mind-v1"
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const httpServer = new x402HTTPResourceServer(resourceServer, routes);
|
|
131
|
+
await httpServer.initialize();
|
|
132
|
+
const server = createServer(async (req, res) => {
|
|
133
|
+
const adapter = buildAdapter(req);
|
|
134
|
+
const method = adapter.getMethod();
|
|
135
|
+
const path = adapter.getPath();
|
|
136
|
+
const paymentHeader = adapter.getHeader("x402-payment") ?? adapter.getHeader("x-payment");
|
|
137
|
+
console.log(`[server] ${method} ${path}`);
|
|
138
|
+
if (debugEnabled && paymentHeader) {
|
|
139
|
+
console.debug("[x402z-server] payment header", paymentHeader);
|
|
140
|
+
}
|
|
141
|
+
const result = await httpServer.processHTTPRequest({
|
|
142
|
+
adapter,
|
|
143
|
+
path: adapter.getPath(),
|
|
144
|
+
method: adapter.getMethod()
|
|
145
|
+
});
|
|
146
|
+
if (result.type === "payment-error") {
|
|
147
|
+
res.writeHead(result.response.status, result.response.headers);
|
|
148
|
+
res.end(result.response.isHtml ? result.response.body : JSON.stringify(result.response.body ?? {}));
|
|
149
|
+
console.log(`[server] ${method} ${path} -> ${result.response.status}`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (result.type === "payment-verified") {
|
|
153
|
+
const settle = await httpServer.processSettlement(result.paymentPayload, result.paymentRequirements);
|
|
154
|
+
if (!settle.success) {
|
|
155
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
156
|
+
res.end(JSON.stringify({ error: settle.errorReason }));
|
|
157
|
+
console.log(`[server] ${method} ${path} -> 500 settlement_failed`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const payload = await config.onPaid({
|
|
161
|
+
paymentRequirements: result.paymentRequirements,
|
|
162
|
+
settleHeaders: settle.headers
|
|
163
|
+
});
|
|
164
|
+
const status = payload.status ?? 200;
|
|
165
|
+
const responseHeaders = {
|
|
166
|
+
"Content-Type": "text/plain",
|
|
167
|
+
...settle.headers,
|
|
168
|
+
...payload.headers ?? {}
|
|
169
|
+
};
|
|
170
|
+
if (debugEnabled) {
|
|
171
|
+
console.debug("[x402z-server] response", {
|
|
172
|
+
status,
|
|
173
|
+
headers: responseHeaders,
|
|
174
|
+
body: payload.body
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
sendText(res, status, responseHeaders, payload.body);
|
|
178
|
+
console.log(`[server] ${method} ${path} -> ${status}`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
sendText(res, 200, { "Content-Type": "text/plain" }, "no payment required");
|
|
182
|
+
console.log(`[server] ${method} ${path} -> 200`);
|
|
183
|
+
});
|
|
184
|
+
return server;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/index.ts
|
|
188
|
+
import { x402ResourceServer as x402ResourceServer2 } from "@x402/core/server";
|
|
189
|
+
import {
|
|
190
|
+
HTTPFacilitatorClient as HTTPFacilitatorClient2,
|
|
191
|
+
x402HTTPResourceServer as x402HTTPResourceServer2
|
|
192
|
+
} from "@x402/core/http";
|
|
193
|
+
export {
|
|
194
|
+
ConfidentialEvmScheme,
|
|
195
|
+
HTTPFacilitatorClient2 as HTTPFacilitatorClient,
|
|
196
|
+
createX402zServer,
|
|
197
|
+
registerConfidentialEvmScheme,
|
|
198
|
+
x402HTTPResourceServer2 as x402HTTPResourceServer,
|
|
199
|
+
x402ResourceServer2 as x402ResourceServer
|
|
200
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "x402z-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"module": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@x402/core": "^2.0.0",
|
|
12
|
+
"x402z-shared": "0.0.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"jest": "^29.7.0",
|
|
16
|
+
"ts-jest": "^29.2.5",
|
|
17
|
+
"@types/jest": "^29.5.12"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
21
|
+
"test": "jest"
|
|
22
|
+
}
|
|
23
|
+
}
|