x402z-server 0.0.19 → 0.1.12
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 -17
- package/dist/index.d.mts +43 -14
- package/dist/index.d.ts +43 -14
- package/dist/index.js +255 -67
- package/dist/index.mjs +254 -62
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# x402z-server
|
|
2
2
|
|
|
3
|
-
Server-side helpers for
|
|
3
|
+
Server-side helpers for erc7984-mind-v1 and exact x402 schemes.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -27,20 +27,25 @@ const relayer = await createRelayer({
|
|
|
27
27
|
|
|
28
28
|
const server = await createX402zServer({
|
|
29
29
|
facilitatorUrl: "http://localhost:8040",
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
confidential: {
|
|
31
|
+
asset: "0xToken",
|
|
32
|
+
eip712: { name: "FHEToken Confidential", version: "1" },
|
|
33
|
+
decimals: 6,
|
|
34
|
+
batcherAddress: "0xBatcher",
|
|
35
|
+
signer: { address, signTypedData },
|
|
36
|
+
relayer,
|
|
37
|
+
},
|
|
36
38
|
routes: {
|
|
37
39
|
"GET /demo": {
|
|
38
|
-
accepts:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
accepts: [
|
|
41
|
+
{
|
|
42
|
+
scheme: "erc7984-mind-v1",
|
|
43
|
+
payTo: "0xPayTo",
|
|
44
|
+
price: "1.0",
|
|
45
|
+
network: "eip155:11155111",
|
|
46
|
+
maxTimeoutSeconds: 300,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
44
49
|
description: "Demo content",
|
|
45
50
|
mimeType: "text/plain",
|
|
46
51
|
},
|
|
@@ -57,9 +62,10 @@ server.listen(8080);
|
|
|
57
62
|
|
|
58
63
|
- `createX402zServer(config)`
|
|
59
64
|
- `facilitatorUrl` (required): HTTP facilitator endpoint
|
|
60
|
-
- `
|
|
61
|
-
- `signer
|
|
62
|
-
- `relayer
|
|
65
|
+
- `confidential` (optional): confidential scheme config (required if any route uses `erc7984-mind-v1`)
|
|
66
|
+
- `confidential.signer`: signer used to decrypt transfer amounts
|
|
67
|
+
- `confidential.relayer`: FHEVM relayer instance used for decryption
|
|
68
|
+
- `confidential.asset`, `confidential.eip712`, `confidential.decimals`, `confidential.batcherAddress`: optional overrides; when omitted, defaults are pulled from the facilitator `/supported` response
|
|
63
69
|
- `routes`: map of `METHOD /path` to payment requirements
|
|
64
70
|
- `onPaid`: handler for successful payment (returns response body + optional headers)
|
|
65
71
|
|
|
@@ -67,9 +73,37 @@ server.listen(8080);
|
|
|
67
73
|
|
|
68
74
|
See `examples/README.md` for the full-process server + facilitator setup.
|
|
69
75
|
|
|
76
|
+
## Exact scheme (ERC20) usage
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { createX402zServer } from "x402z-server";
|
|
80
|
+
|
|
81
|
+
const server = await createX402zServer({
|
|
82
|
+
facilitatorUrl: "http://localhost:8040",
|
|
83
|
+
routes: {
|
|
84
|
+
"GET /erc20-demo": {
|
|
85
|
+
accepts: [
|
|
86
|
+
{
|
|
87
|
+
scheme: "exact",
|
|
88
|
+
payTo: "0xYourAddress",
|
|
89
|
+
price: "$0.01",
|
|
90
|
+
network: "eip155:84532",
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
description: "Exact USDC payment demo",
|
|
94
|
+
mimeType: "text/plain",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
onPaid: async () => ({ body: "paid content" }),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
server.listen(8090);
|
|
101
|
+
```
|
|
102
|
+
|
|
70
103
|
## Notes
|
|
71
104
|
|
|
72
|
-
-
|
|
105
|
+
- Confidential scheme name: `erc7984-mind-v1`
|
|
106
|
+
- Exact scheme name: `exact`
|
|
73
107
|
- `confidential.batcherAddress` is required in requirements; clients bind encrypted inputs to it.
|
|
74
108
|
|
|
75
109
|
## API surface
|
|
@@ -87,8 +121,10 @@ Types:
|
|
|
87
121
|
- `X402zServerNetworkOptions`: network config for the scheme.
|
|
88
122
|
- `X402zServerRegistrationOptions`: registration options for the scheme.
|
|
89
123
|
- `X402zRouteOptions`: route config for paywalled endpoints.
|
|
124
|
+
- `X402zRouteAccept`: accept config item for confidential or exact schemes.
|
|
90
125
|
- `X402zRouteHandler`: handler signature for paid requests.
|
|
91
126
|
- `X402zServerOptions`: options for `createX402zServer`.
|
|
127
|
+
- `X402zConfidentialServerConfig`: options for confidential server config.
|
|
92
128
|
- `CompiledRoute`: compiled route metadata.
|
|
93
129
|
- `DynamicPayTo`: callback for dynamic payee address.
|
|
94
130
|
- `DynamicPrice`: callback for dynamic pricing.
|
package/dist/index.d.mts
CHANGED
|
@@ -6,7 +6,10 @@ import * as http from 'http';
|
|
|
6
6
|
import { RelayerSigner, RelayerInstance } from 'x402z-shared';
|
|
7
7
|
export { CompiledRoute, DynamicPayTo, DynamicPrice, FacilitatorClient, FacilitatorConfig, HTTPAdapter, HTTPFacilitatorClient, HTTPProcessResult, HTTPRequestContext, HTTPResponseInstructions, PaymentOption, PaywallConfig, PaywallProvider, ProcessSettleFailureResponse, ProcessSettleResultResponse, ProcessSettleSuccessResponse, RouteConfigurationError, RouteValidationError, RoutesConfig, UnpaidResponseBody, UnpaidResponseResult, x402HTTPResourceServer } from '@x402/core/http';
|
|
8
8
|
|
|
9
|
-
type
|
|
9
|
+
type NoScheme$1 = {
|
|
10
|
+
scheme?: never;
|
|
11
|
+
};
|
|
12
|
+
type X402zServerNetworkOptions = NoScheme$1 & {
|
|
10
13
|
asset: `0x${string}`;
|
|
11
14
|
eip712: {
|
|
12
15
|
name: string;
|
|
@@ -39,13 +42,24 @@ type X402zServerRegistrationOptions = X402zServerSchemeOptions & {
|
|
|
39
42
|
};
|
|
40
43
|
declare function registerX402zEvmServerScheme(server: x402ResourceServer, config: X402zServerRegistrationOptions): x402ResourceServer;
|
|
41
44
|
|
|
42
|
-
type
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
type X402zRouteAccept = {
|
|
46
|
+
scheme: "erc7984-mind-v1";
|
|
47
|
+
payTo: string;
|
|
48
|
+
price: string;
|
|
49
|
+
network: Network;
|
|
50
|
+
maxTimeoutSeconds: number;
|
|
51
|
+
} | {
|
|
52
|
+
scheme: "exact";
|
|
53
|
+
payTo: string;
|
|
54
|
+
price: Price;
|
|
55
|
+
network: Network;
|
|
56
|
+
maxTimeoutSeconds?: number;
|
|
57
|
+
};
|
|
58
|
+
type NoScheme = {
|
|
59
|
+
scheme?: never;
|
|
60
|
+
};
|
|
61
|
+
type X402zRouteOptions = NoScheme & {
|
|
62
|
+
accepts: X402zRouteAccept[];
|
|
49
63
|
description: string;
|
|
50
64
|
mimeType: string;
|
|
51
65
|
};
|
|
@@ -57,12 +71,8 @@ type X402zRouteHandler = (args: {
|
|
|
57
71
|
headers?: Record<string, string>;
|
|
58
72
|
body: string;
|
|
59
73
|
}>;
|
|
60
|
-
type
|
|
74
|
+
type X402zBaseServerOptions = {
|
|
61
75
|
facilitatorUrl: string;
|
|
62
|
-
routes: Record<string, X402zRouteOptions>;
|
|
63
|
-
onPaid: X402zRouteHandler;
|
|
64
|
-
signer: RelayerSigner;
|
|
65
|
-
relayer: RelayerInstance;
|
|
66
76
|
debug?: boolean;
|
|
67
77
|
cors?: {
|
|
68
78
|
allowOrigin: string;
|
|
@@ -73,6 +83,25 @@ type X402zServerOptions = X402zServerRegistrationOptions & {
|
|
|
73
83
|
maxAgeSeconds?: number;
|
|
74
84
|
};
|
|
75
85
|
};
|
|
86
|
+
type X402zConfidentialServerConfig = {
|
|
87
|
+
getNetworkConfig?: (network: Network) => X402zServerNetworkOptions;
|
|
88
|
+
networks?: Network[];
|
|
89
|
+
asset?: `0x${string}`;
|
|
90
|
+
eip712?: {
|
|
91
|
+
name: string;
|
|
92
|
+
version: string;
|
|
93
|
+
};
|
|
94
|
+
decimals?: number;
|
|
95
|
+
resourceHash?: `0x${string}`;
|
|
96
|
+
batcherAddress?: `0x${string}`;
|
|
97
|
+
signer: RelayerSigner;
|
|
98
|
+
relayer: RelayerInstance;
|
|
99
|
+
} & NoScheme;
|
|
100
|
+
type X402zServerOptions = X402zBaseServerOptions & {
|
|
101
|
+
routes: Record<string, X402zRouteOptions>;
|
|
102
|
+
onPaid: X402zRouteHandler;
|
|
103
|
+
confidential?: X402zConfidentialServerConfig;
|
|
104
|
+
} & NoScheme;
|
|
76
105
|
declare function createX402zServer(config: X402zServerOptions): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
|
|
77
106
|
|
|
78
|
-
export { X402zEvmServerScheme, type X402zRouteHandler, type X402zRouteOptions, type X402zServerNetworkOptions, type X402zServerOptions, type X402zServerRegistrationOptions, type X402zServerSchemeOptions, createX402zServer, registerX402zEvmServerScheme };
|
|
107
|
+
export { type X402zConfidentialServerConfig, X402zEvmServerScheme, type X402zRouteAccept, type X402zRouteHandler, type X402zRouteOptions, type X402zServerNetworkOptions, type X402zServerOptions, type X402zServerRegistrationOptions, type X402zServerSchemeOptions, createX402zServer, registerX402zEvmServerScheme };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ import * as http from 'http';
|
|
|
6
6
|
import { RelayerSigner, RelayerInstance } from 'x402z-shared';
|
|
7
7
|
export { CompiledRoute, DynamicPayTo, DynamicPrice, FacilitatorClient, FacilitatorConfig, HTTPAdapter, HTTPFacilitatorClient, HTTPProcessResult, HTTPRequestContext, HTTPResponseInstructions, PaymentOption, PaywallConfig, PaywallProvider, ProcessSettleFailureResponse, ProcessSettleResultResponse, ProcessSettleSuccessResponse, RouteConfigurationError, RouteValidationError, RoutesConfig, UnpaidResponseBody, UnpaidResponseResult, x402HTTPResourceServer } from '@x402/core/http';
|
|
8
8
|
|
|
9
|
-
type
|
|
9
|
+
type NoScheme$1 = {
|
|
10
|
+
scheme?: never;
|
|
11
|
+
};
|
|
12
|
+
type X402zServerNetworkOptions = NoScheme$1 & {
|
|
10
13
|
asset: `0x${string}`;
|
|
11
14
|
eip712: {
|
|
12
15
|
name: string;
|
|
@@ -39,13 +42,24 @@ type X402zServerRegistrationOptions = X402zServerSchemeOptions & {
|
|
|
39
42
|
};
|
|
40
43
|
declare function registerX402zEvmServerScheme(server: x402ResourceServer, config: X402zServerRegistrationOptions): x402ResourceServer;
|
|
41
44
|
|
|
42
|
-
type
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
type X402zRouteAccept = {
|
|
46
|
+
scheme: "erc7984-mind-v1";
|
|
47
|
+
payTo: string;
|
|
48
|
+
price: string;
|
|
49
|
+
network: Network;
|
|
50
|
+
maxTimeoutSeconds: number;
|
|
51
|
+
} | {
|
|
52
|
+
scheme: "exact";
|
|
53
|
+
payTo: string;
|
|
54
|
+
price: Price;
|
|
55
|
+
network: Network;
|
|
56
|
+
maxTimeoutSeconds?: number;
|
|
57
|
+
};
|
|
58
|
+
type NoScheme = {
|
|
59
|
+
scheme?: never;
|
|
60
|
+
};
|
|
61
|
+
type X402zRouteOptions = NoScheme & {
|
|
62
|
+
accepts: X402zRouteAccept[];
|
|
49
63
|
description: string;
|
|
50
64
|
mimeType: string;
|
|
51
65
|
};
|
|
@@ -57,12 +71,8 @@ type X402zRouteHandler = (args: {
|
|
|
57
71
|
headers?: Record<string, string>;
|
|
58
72
|
body: string;
|
|
59
73
|
}>;
|
|
60
|
-
type
|
|
74
|
+
type X402zBaseServerOptions = {
|
|
61
75
|
facilitatorUrl: string;
|
|
62
|
-
routes: Record<string, X402zRouteOptions>;
|
|
63
|
-
onPaid: X402zRouteHandler;
|
|
64
|
-
signer: RelayerSigner;
|
|
65
|
-
relayer: RelayerInstance;
|
|
66
76
|
debug?: boolean;
|
|
67
77
|
cors?: {
|
|
68
78
|
allowOrigin: string;
|
|
@@ -73,6 +83,25 @@ type X402zServerOptions = X402zServerRegistrationOptions & {
|
|
|
73
83
|
maxAgeSeconds?: number;
|
|
74
84
|
};
|
|
75
85
|
};
|
|
86
|
+
type X402zConfidentialServerConfig = {
|
|
87
|
+
getNetworkConfig?: (network: Network) => X402zServerNetworkOptions;
|
|
88
|
+
networks?: Network[];
|
|
89
|
+
asset?: `0x${string}`;
|
|
90
|
+
eip712?: {
|
|
91
|
+
name: string;
|
|
92
|
+
version: string;
|
|
93
|
+
};
|
|
94
|
+
decimals?: number;
|
|
95
|
+
resourceHash?: `0x${string}`;
|
|
96
|
+
batcherAddress?: `0x${string}`;
|
|
97
|
+
signer: RelayerSigner;
|
|
98
|
+
relayer: RelayerInstance;
|
|
99
|
+
} & NoScheme;
|
|
100
|
+
type X402zServerOptions = X402zBaseServerOptions & {
|
|
101
|
+
routes: Record<string, X402zRouteOptions>;
|
|
102
|
+
onPaid: X402zRouteHandler;
|
|
103
|
+
confidential?: X402zConfidentialServerConfig;
|
|
104
|
+
} & NoScheme;
|
|
76
105
|
declare function createX402zServer(config: X402zServerOptions): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
|
|
77
106
|
|
|
78
|
-
export { X402zEvmServerScheme, type X402zRouteHandler, type X402zRouteOptions, type X402zServerNetworkOptions, type X402zServerOptions, type X402zServerRegistrationOptions, type X402zServerSchemeOptions, createX402zServer, registerX402zEvmServerScheme };
|
|
107
|
+
export { type X402zConfidentialServerConfig, X402zEvmServerScheme, type X402zRouteAccept, type X402zRouteHandler, type X402zRouteOptions, type X402zServerNetworkOptions, type X402zServerOptions, type X402zServerRegistrationOptions, type X402zServerSchemeOptions, createX402zServer, registerX402zEvmServerScheme };
|
package/dist/index.js
CHANGED
|
@@ -20,12 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
HTTPFacilitatorClient: () =>
|
|
23
|
+
HTTPFacilitatorClient: () => import_http2.HTTPFacilitatorClient,
|
|
24
24
|
X402zEvmServerScheme: () => X402zEvmServerScheme,
|
|
25
25
|
createX402zServer: () => createX402zServer,
|
|
26
26
|
registerX402zEvmServerScheme: () => registerX402zEvmServerScheme,
|
|
27
|
-
x402HTTPResourceServer: () =>
|
|
28
|
-
x402ResourceServer: () =>
|
|
27
|
+
x402HTTPResourceServer: () => import_http2.x402HTTPResourceServer,
|
|
28
|
+
x402ResourceServer: () => import_server4.x402ResourceServer
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(index_exports);
|
|
31
31
|
|
|
@@ -124,9 +124,9 @@ var import_node_http = require("http");
|
|
|
124
124
|
var import_node_url = require("url");
|
|
125
125
|
var import_server = require("@x402/core/server");
|
|
126
126
|
var import_http = require("@x402/core/http");
|
|
127
|
-
var import_http2 = require("@x402/core/http");
|
|
128
127
|
var import_x402z_shared = require("x402z-shared");
|
|
129
128
|
var import_viem = require("viem");
|
|
129
|
+
var import_server2 = require("@x402/evm/exact/server");
|
|
130
130
|
function getRelayerRpcUrl(relayer) {
|
|
131
131
|
const network = relayer.network;
|
|
132
132
|
if (typeof network === "string") {
|
|
@@ -150,24 +150,181 @@ function buildAdapter(req) {
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
function sendText(res, status, headers, body) {
|
|
153
|
-
res.writeHead(status, headers);
|
|
153
|
+
res.writeHead(status, void 0, headers);
|
|
154
154
|
res.end(body);
|
|
155
155
|
}
|
|
156
|
+
async function fetchSupported(facilitatorUrl, fetchFn) {
|
|
157
|
+
const base = facilitatorUrl.replace(/\/$/, "");
|
|
158
|
+
const response = await fetchFn(`${base}/supported`);
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw new Error(`Failed to fetch supported schemes: ${response.status}`);
|
|
161
|
+
}
|
|
162
|
+
return response.json();
|
|
163
|
+
}
|
|
164
|
+
function normalizeExactDefaults(extra) {
|
|
165
|
+
const exact = extra?.exact ?? null;
|
|
166
|
+
if (!exact?.asset) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const name = exact.eip712?.name ?? exact.name;
|
|
170
|
+
const version = exact.eip712?.version ?? exact.version;
|
|
171
|
+
if (!name || !version) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
asset: exact.asset,
|
|
176
|
+
decimals: exact.decimals ?? 6,
|
|
177
|
+
name,
|
|
178
|
+
version
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function normalizeConfidentialDefaults(extra) {
|
|
182
|
+
const confidential = extra?.confidential ?? null;
|
|
183
|
+
if (!confidential?.asset || !confidential.eip712?.name || !confidential.eip712?.version) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
if (!confidential.batcherAddress) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
asset: confidential.asset,
|
|
191
|
+
eip712: {
|
|
192
|
+
name: confidential.eip712.name,
|
|
193
|
+
version: confidential.eip712.version
|
|
194
|
+
},
|
|
195
|
+
decimals: confidential.decimals ?? 6,
|
|
196
|
+
batcherAddress: confidential.batcherAddress,
|
|
197
|
+
resourceHash: confidential.resourceHash
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function parseMoneyToDecimal(money) {
|
|
201
|
+
if (typeof money === "number") {
|
|
202
|
+
return money;
|
|
203
|
+
}
|
|
204
|
+
if (typeof money !== "string") {
|
|
205
|
+
throw new Error("Invalid price type for exact scheme");
|
|
206
|
+
}
|
|
207
|
+
const clean = money.replace(/^\$/, "").trim();
|
|
208
|
+
const amount = Number.parseFloat(clean);
|
|
209
|
+
if (Number.isNaN(amount)) {
|
|
210
|
+
throw new Error(`Invalid money format: ${money}`);
|
|
211
|
+
}
|
|
212
|
+
return amount;
|
|
213
|
+
}
|
|
214
|
+
function convertToTokenAmount(amount, decimals) {
|
|
215
|
+
const [intPart, decPart = ""] = String(amount).split(".");
|
|
216
|
+
const paddedDec = decPart.padEnd(decimals, "0").slice(0, decimals);
|
|
217
|
+
return (intPart + paddedDec).replace(/^0+/, "") || "0";
|
|
218
|
+
}
|
|
156
219
|
async function createX402zServer(config) {
|
|
157
220
|
const debugEnabled = config.debug ?? process.env.X402Z_DEBUG === "1";
|
|
158
|
-
const
|
|
221
|
+
const fetchFn = globalThis.fetch;
|
|
222
|
+
const facilitatorClient = new import_http.HTTPFacilitatorClient({ url: config.facilitatorUrl });
|
|
159
223
|
const resourceServer = new import_server.x402ResourceServer(facilitatorClient);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
224
|
+
const wantsExact = Object.values(config.routes).some(
|
|
225
|
+
(route) => route.accepts.some((accept) => accept.scheme === "exact")
|
|
226
|
+
);
|
|
227
|
+
const wantsConfidential = Object.values(config.routes).some(
|
|
228
|
+
(route) => route.accepts.some((accept) => accept.scheme === "erc7984-mind-v1")
|
|
229
|
+
);
|
|
230
|
+
const confidentialConfig = config.confidential;
|
|
231
|
+
if (wantsConfidential && !confidentialConfig) {
|
|
232
|
+
throw new Error("confidential config is required when using erc7984-mind-v1 routes");
|
|
233
|
+
}
|
|
234
|
+
const needsExactDefaults = Object.values(config.routes).some(
|
|
235
|
+
(route) => route.accepts.some((accept) => accept.scheme === "exact" && typeof accept.price !== "object")
|
|
236
|
+
);
|
|
237
|
+
let supportedExactDefaults = {};
|
|
238
|
+
if (wantsExact && needsExactDefaults) {
|
|
239
|
+
if (!fetchFn) {
|
|
240
|
+
throw new Error("fetch is required to load exact defaults from facilitator");
|
|
241
|
+
}
|
|
242
|
+
const supported = await fetchSupported(config.facilitatorUrl, fetchFn);
|
|
243
|
+
for (const kind of supported.kinds ?? []) {
|
|
244
|
+
if (kind.scheme !== "exact") continue;
|
|
245
|
+
const normalized = normalizeExactDefaults(kind.extra);
|
|
246
|
+
if (normalized) {
|
|
247
|
+
supportedExactDefaults[kind.network] = normalized;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (wantsConfidential && confidentialConfig) {
|
|
252
|
+
let supportedDefaults = {};
|
|
253
|
+
const needsSupportedDefaults = !confidentialConfig.getNetworkConfig && (!confidentialConfig.asset || !confidentialConfig.eip712 || !confidentialConfig.batcherAddress);
|
|
254
|
+
if (needsSupportedDefaults) {
|
|
255
|
+
if (!fetchFn) {
|
|
256
|
+
throw new Error("fetch is required to load confidential defaults from facilitator");
|
|
257
|
+
}
|
|
258
|
+
const supported = await fetchSupported(config.facilitatorUrl, fetchFn);
|
|
259
|
+
for (const kind of supported.kinds ?? []) {
|
|
260
|
+
if (kind.scheme !== "erc7984-mind-v1") continue;
|
|
261
|
+
const normalized = normalizeConfidentialDefaults(kind.extra);
|
|
262
|
+
if (normalized) {
|
|
263
|
+
supportedDefaults[kind.network] = normalized;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const getNetworkConfig = confidentialConfig.getNetworkConfig ?? ((network) => {
|
|
268
|
+
const base = supportedDefaults[network] ?? {};
|
|
269
|
+
const override = {
|
|
270
|
+
asset: confidentialConfig.asset,
|
|
271
|
+
eip712: confidentialConfig.eip712,
|
|
272
|
+
decimals: confidentialConfig.decimals,
|
|
273
|
+
resourceHash: confidentialConfig.resourceHash,
|
|
274
|
+
batcherAddress: confidentialConfig.batcherAddress
|
|
275
|
+
};
|
|
276
|
+
const merged = {
|
|
277
|
+
...base,
|
|
278
|
+
...Object.fromEntries(Object.entries(override).filter(([, value]) => value !== void 0))
|
|
279
|
+
};
|
|
280
|
+
if (!merged.decimals) {
|
|
281
|
+
merged.decimals = 6;
|
|
282
|
+
}
|
|
283
|
+
if (!merged.asset || !merged.eip712?.name || !merged.eip712?.version || !merged.batcherAddress) {
|
|
284
|
+
throw new Error(`Missing confidential defaults for network ${network}`);
|
|
285
|
+
}
|
|
286
|
+
return merged;
|
|
287
|
+
});
|
|
288
|
+
registerX402zEvmServerScheme(resourceServer, {
|
|
289
|
+
getNetworkConfig,
|
|
290
|
+
networks: confidentialConfig.networks
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (wantsExact) {
|
|
294
|
+
(0, import_server2.registerExactEvmScheme)(resourceServer, {});
|
|
295
|
+
}
|
|
296
|
+
const relayer = confidentialConfig?.relayer;
|
|
297
|
+
const rpcUrl = relayer ? getRelayerRpcUrl(relayer) : null;
|
|
163
298
|
const routes = {};
|
|
164
299
|
for (const [path, route] of Object.entries(config.routes)) {
|
|
300
|
+
const accepts = route.accepts.map((accept) => {
|
|
301
|
+
if (accept.scheme !== "exact") {
|
|
302
|
+
return accept;
|
|
303
|
+
}
|
|
304
|
+
if (typeof accept.price === "object") {
|
|
305
|
+
return accept;
|
|
306
|
+
}
|
|
307
|
+
const defaults = supportedExactDefaults[accept.network];
|
|
308
|
+
if (!defaults) {
|
|
309
|
+
throw new Error(`Missing exact defaults for network ${accept.network}`);
|
|
310
|
+
}
|
|
311
|
+
const decimalAmount = parseMoneyToDecimal(accept.price);
|
|
312
|
+
const amount = convertToTokenAmount(decimalAmount, defaults.decimals);
|
|
313
|
+
return {
|
|
314
|
+
...accept,
|
|
315
|
+
price: {
|
|
316
|
+
amount,
|
|
317
|
+
asset: defaults.asset,
|
|
318
|
+
extra: {
|
|
319
|
+
name: defaults.name,
|
|
320
|
+
version: defaults.version
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
});
|
|
165
325
|
routes[path] = {
|
|
166
326
|
...route,
|
|
167
|
-
accepts
|
|
168
|
-
...route.accepts,
|
|
169
|
-
scheme: "erc7984-mind-v1"
|
|
170
|
-
}
|
|
327
|
+
accepts
|
|
171
328
|
};
|
|
172
329
|
}
|
|
173
330
|
const httpServer = new import_http.x402HTTPResourceServer(resourceServer, routes);
|
|
@@ -177,7 +334,9 @@ async function createX402zServer(config) {
|
|
|
177
334
|
const method = adapter.getMethod();
|
|
178
335
|
const path = adapter.getPath();
|
|
179
336
|
const paymentHeader = adapter.getHeader("payment-signature") ?? adapter.getHeader("x402-payment") ?? adapter.getHeader("x-payment");
|
|
180
|
-
|
|
337
|
+
if (debugEnabled) {
|
|
338
|
+
console.debug(`[server] ${method} ${path}`);
|
|
339
|
+
}
|
|
181
340
|
const corsHeaders = config.cors ? {
|
|
182
341
|
"Access-Control-Allow-Origin": config.cors.allowOrigin,
|
|
183
342
|
"Access-Control-Allow-Methods": config.cors.allowMethods ?? "GET,POST,OPTIONS",
|
|
@@ -208,7 +367,9 @@ async function createX402zServer(config) {
|
|
|
208
367
|
if (result.type === "payment-error") {
|
|
209
368
|
res.writeHead(result.response.status, { ...corsHeaders, ...result.response.headers });
|
|
210
369
|
res.end(result.response.isHtml ? result.response.body : JSON.stringify(result.response.body ?? {}));
|
|
211
|
-
|
|
370
|
+
if (debugEnabled) {
|
|
371
|
+
console.debug(`[server] ${method} ${path} -> ${result.response.status}`);
|
|
372
|
+
}
|
|
212
373
|
return;
|
|
213
374
|
}
|
|
214
375
|
if (result.type === "payment-verified") {
|
|
@@ -216,68 +377,91 @@ async function createX402zServer(config) {
|
|
|
216
377
|
if (!settle.success) {
|
|
217
378
|
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
218
379
|
res.end(JSON.stringify({ error: settle.errorReason }));
|
|
219
|
-
|
|
380
|
+
if (debugEnabled) {
|
|
381
|
+
console.debug(`[server] ${method} ${path} -> 500 settlement_failed`);
|
|
382
|
+
}
|
|
220
383
|
return;
|
|
221
384
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
385
|
+
if (result.paymentRequirements.scheme === "erc7984-mind-v1") {
|
|
386
|
+
try {
|
|
387
|
+
if (!confidentialConfig || !relayer || !rpcUrl) {
|
|
388
|
+
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
389
|
+
res.end(JSON.stringify({ error: "confidential_config_missing" }));
|
|
390
|
+
if (debugEnabled) {
|
|
391
|
+
console.debug(`[server] ${method} ${path} -> 500 confidential_config_missing`);
|
|
392
|
+
}
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const observerClient = (0, import_viem.createPublicClient)({
|
|
396
|
+
transport: (0, import_viem.http)(rpcUrl)
|
|
397
|
+
});
|
|
398
|
+
const payTo = (0, import_viem.getAddress)(result.paymentRequirements.payTo);
|
|
399
|
+
const observer = await observerClient.readContract({
|
|
400
|
+
address: (0, import_viem.getAddress)(result.paymentRequirements.asset),
|
|
401
|
+
abi: import_x402z_shared.confidentialTokenAbi,
|
|
402
|
+
functionName: "observer",
|
|
403
|
+
args: [payTo]
|
|
404
|
+
});
|
|
405
|
+
const signerAddress = (0, import_viem.getAddress)(confidentialConfig.signer.address);
|
|
406
|
+
if (!observer || !(0, import_viem.isAddressEqual)(observer, signerAddress)) {
|
|
407
|
+
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
408
|
+
res.end(JSON.stringify({ error: "observer_required" }));
|
|
409
|
+
if (debugEnabled) {
|
|
410
|
+
console.debug(`[server] ${method} ${path} -> 500 observer_required`);
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const batch = settle;
|
|
415
|
+
const transferredHandle = batch.batch?.transferredHandle;
|
|
416
|
+
if (!transferredHandle) {
|
|
417
|
+
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
418
|
+
res.end(JSON.stringify({ error: "missing_transferred_handle" }));
|
|
419
|
+
if (debugEnabled) {
|
|
420
|
+
console.debug(`[server] ${method} ${path} -> 500 missing_transferred_handle`);
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const decryptedAmount = await (0, import_x402z_shared.decryptEuint64)(
|
|
425
|
+
relayer,
|
|
426
|
+
transferredHandle,
|
|
427
|
+
result.paymentRequirements.asset,
|
|
428
|
+
confidentialConfig.signer
|
|
429
|
+
);
|
|
430
|
+
const expected = BigInt(result.paymentRequirements.amount);
|
|
431
|
+
if (decryptedAmount !== expected) {
|
|
432
|
+
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
433
|
+
res.end(JSON.stringify({ error: "settlement_amount_mismatch" }));
|
|
434
|
+
if (debugEnabled) {
|
|
435
|
+
console.debug(`[server] ${method} ${path} -> 500 settlement_amount_mismatch`);
|
|
436
|
+
}
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
} catch (error) {
|
|
440
|
+
if (debugEnabled) {
|
|
441
|
+
console.error("[server] settlement verification error", error);
|
|
442
|
+
}
|
|
235
443
|
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
236
|
-
res.end(JSON.stringify({ error: "
|
|
237
|
-
|
|
444
|
+
res.end(JSON.stringify({ error: "settlement_verification_failed" }));
|
|
445
|
+
if (debugEnabled) {
|
|
446
|
+
console.debug(`[server] ${method} ${path} -> 500 settlement_verification_failed`);
|
|
447
|
+
}
|
|
238
448
|
return;
|
|
239
449
|
}
|
|
240
|
-
const batch = settle;
|
|
241
|
-
const transferredHandle = batch.batch?.transferredHandle;
|
|
242
|
-
if (!transferredHandle) {
|
|
243
|
-
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
244
|
-
res.end(JSON.stringify({ error: "missing_transferred_handle" }));
|
|
245
|
-
console.log(`[server] ${method} ${path} -> 500 missing_transferred_handle`);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
const decryptedAmount = await (0, import_x402z_shared.decryptEuint64)(
|
|
249
|
-
relayer,
|
|
250
|
-
transferredHandle,
|
|
251
|
-
result.paymentRequirements.asset,
|
|
252
|
-
config.signer
|
|
253
|
-
);
|
|
254
|
-
const expected = BigInt(result.paymentRequirements.amount);
|
|
255
|
-
if (decryptedAmount !== expected) {
|
|
256
|
-
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
257
|
-
res.end(JSON.stringify({ error: "settlement_amount_mismatch" }));
|
|
258
|
-
console.log(`[server] ${method} ${path} -> 500 settlement_amount_mismatch`);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
} catch (error) {
|
|
262
|
-
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
263
|
-
res.end(JSON.stringify({ error: "settlement_verification_failed" }));
|
|
264
|
-
console.log(`[server] ${method} ${path} -> 500 settlement_verification_failed`);
|
|
265
|
-
if (debugEnabled) {
|
|
266
|
-
console.debug("[x402z-server] settlement verification error", error);
|
|
267
|
-
}
|
|
268
|
-
return;
|
|
269
450
|
}
|
|
451
|
+
const settleHeaders = settle.headers ?? {};
|
|
270
452
|
const payload = await config.onPaid({
|
|
271
453
|
paymentRequirements: result.paymentRequirements,
|
|
272
|
-
settleHeaders
|
|
454
|
+
settleHeaders
|
|
273
455
|
});
|
|
274
456
|
const status = payload.status ?? 200;
|
|
275
457
|
const responseHeaders = {
|
|
276
458
|
...corsHeaders,
|
|
277
|
-
|
|
278
|
-
...settle.headers,
|
|
459
|
+
...settleHeaders,
|
|
279
460
|
...payload.headers ?? {}
|
|
280
461
|
};
|
|
462
|
+
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
463
|
+
responseHeaders["Content-Type"] = "text/plain";
|
|
464
|
+
}
|
|
281
465
|
if (debugEnabled) {
|
|
282
466
|
console.debug("[x402z-server] response", {
|
|
283
467
|
status,
|
|
@@ -286,7 +470,9 @@ async function createX402zServer(config) {
|
|
|
286
470
|
});
|
|
287
471
|
}
|
|
288
472
|
sendText(res, status, responseHeaders, payload.body);
|
|
289
|
-
|
|
473
|
+
if (debugEnabled) {
|
|
474
|
+
console.debug(`[server] ${method} ${path} -> ${status}`);
|
|
475
|
+
}
|
|
290
476
|
return;
|
|
291
477
|
}
|
|
292
478
|
sendText(
|
|
@@ -295,14 +481,16 @@ async function createX402zServer(config) {
|
|
|
295
481
|
{ ...corsHeaders, "Content-Type": "text/plain" },
|
|
296
482
|
"no payment required"
|
|
297
483
|
);
|
|
298
|
-
|
|
484
|
+
if (debugEnabled) {
|
|
485
|
+
console.debug(`[server] ${method} ${path} -> 200`);
|
|
486
|
+
}
|
|
299
487
|
});
|
|
300
488
|
return server;
|
|
301
489
|
}
|
|
302
490
|
|
|
303
491
|
// src/index.ts
|
|
304
|
-
var
|
|
305
|
-
var
|
|
492
|
+
var import_server4 = require("@x402/core/server");
|
|
493
|
+
var import_http2 = require("@x402/core/http");
|
|
306
494
|
// Annotate the CommonJS export names for ESM import in node:
|
|
307
495
|
0 && (module.exports = {
|
|
308
496
|
HTTPFacilitatorClient,
|
package/dist/index.mjs
CHANGED
|
@@ -92,10 +92,14 @@ function registerX402zEvmServerScheme(server, config) {
|
|
|
92
92
|
import { createServer } from "http";
|
|
93
93
|
import { URL } from "url";
|
|
94
94
|
import { x402ResourceServer } from "@x402/core/server";
|
|
95
|
-
import {
|
|
96
|
-
|
|
95
|
+
import {
|
|
96
|
+
decodePaymentSignatureHeader,
|
|
97
|
+
x402HTTPResourceServer,
|
|
98
|
+
HTTPFacilitatorClient
|
|
99
|
+
} from "@x402/core/http";
|
|
97
100
|
import { confidentialTokenAbi, decryptEuint64 } from "x402z-shared";
|
|
98
101
|
import { createPublicClient, getAddress, http, isAddressEqual } from "viem";
|
|
102
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/server";
|
|
99
103
|
function getRelayerRpcUrl(relayer) {
|
|
100
104
|
const network = relayer.network;
|
|
101
105
|
if (typeof network === "string") {
|
|
@@ -119,24 +123,181 @@ function buildAdapter(req) {
|
|
|
119
123
|
};
|
|
120
124
|
}
|
|
121
125
|
function sendText(res, status, headers, body) {
|
|
122
|
-
res.writeHead(status, headers);
|
|
126
|
+
res.writeHead(status, void 0, headers);
|
|
123
127
|
res.end(body);
|
|
124
128
|
}
|
|
129
|
+
async function fetchSupported(facilitatorUrl, fetchFn) {
|
|
130
|
+
const base = facilitatorUrl.replace(/\/$/, "");
|
|
131
|
+
const response = await fetchFn(`${base}/supported`);
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new Error(`Failed to fetch supported schemes: ${response.status}`);
|
|
134
|
+
}
|
|
135
|
+
return response.json();
|
|
136
|
+
}
|
|
137
|
+
function normalizeExactDefaults(extra) {
|
|
138
|
+
const exact = extra?.exact ?? null;
|
|
139
|
+
if (!exact?.asset) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const name = exact.eip712?.name ?? exact.name;
|
|
143
|
+
const version = exact.eip712?.version ?? exact.version;
|
|
144
|
+
if (!name || !version) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
asset: exact.asset,
|
|
149
|
+
decimals: exact.decimals ?? 6,
|
|
150
|
+
name,
|
|
151
|
+
version
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function normalizeConfidentialDefaults(extra) {
|
|
155
|
+
const confidential = extra?.confidential ?? null;
|
|
156
|
+
if (!confidential?.asset || !confidential.eip712?.name || !confidential.eip712?.version) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
if (!confidential.batcherAddress) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
asset: confidential.asset,
|
|
164
|
+
eip712: {
|
|
165
|
+
name: confidential.eip712.name,
|
|
166
|
+
version: confidential.eip712.version
|
|
167
|
+
},
|
|
168
|
+
decimals: confidential.decimals ?? 6,
|
|
169
|
+
batcherAddress: confidential.batcherAddress,
|
|
170
|
+
resourceHash: confidential.resourceHash
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function parseMoneyToDecimal(money) {
|
|
174
|
+
if (typeof money === "number") {
|
|
175
|
+
return money;
|
|
176
|
+
}
|
|
177
|
+
if (typeof money !== "string") {
|
|
178
|
+
throw new Error("Invalid price type for exact scheme");
|
|
179
|
+
}
|
|
180
|
+
const clean = money.replace(/^\$/, "").trim();
|
|
181
|
+
const amount = Number.parseFloat(clean);
|
|
182
|
+
if (Number.isNaN(amount)) {
|
|
183
|
+
throw new Error(`Invalid money format: ${money}`);
|
|
184
|
+
}
|
|
185
|
+
return amount;
|
|
186
|
+
}
|
|
187
|
+
function convertToTokenAmount(amount, decimals) {
|
|
188
|
+
const [intPart, decPart = ""] = String(amount).split(".");
|
|
189
|
+
const paddedDec = decPart.padEnd(decimals, "0").slice(0, decimals);
|
|
190
|
+
return (intPart + paddedDec).replace(/^0+/, "") || "0";
|
|
191
|
+
}
|
|
125
192
|
async function createX402zServer(config) {
|
|
126
193
|
const debugEnabled = config.debug ?? process.env.X402Z_DEBUG === "1";
|
|
194
|
+
const fetchFn = globalThis.fetch;
|
|
127
195
|
const facilitatorClient = new HTTPFacilitatorClient({ url: config.facilitatorUrl });
|
|
128
196
|
const resourceServer = new x402ResourceServer(facilitatorClient);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
197
|
+
const wantsExact = Object.values(config.routes).some(
|
|
198
|
+
(route) => route.accepts.some((accept) => accept.scheme === "exact")
|
|
199
|
+
);
|
|
200
|
+
const wantsConfidential = Object.values(config.routes).some(
|
|
201
|
+
(route) => route.accepts.some((accept) => accept.scheme === "erc7984-mind-v1")
|
|
202
|
+
);
|
|
203
|
+
const confidentialConfig = config.confidential;
|
|
204
|
+
if (wantsConfidential && !confidentialConfig) {
|
|
205
|
+
throw new Error("confidential config is required when using erc7984-mind-v1 routes");
|
|
206
|
+
}
|
|
207
|
+
const needsExactDefaults = Object.values(config.routes).some(
|
|
208
|
+
(route) => route.accepts.some((accept) => accept.scheme === "exact" && typeof accept.price !== "object")
|
|
209
|
+
);
|
|
210
|
+
let supportedExactDefaults = {};
|
|
211
|
+
if (wantsExact && needsExactDefaults) {
|
|
212
|
+
if (!fetchFn) {
|
|
213
|
+
throw new Error("fetch is required to load exact defaults from facilitator");
|
|
214
|
+
}
|
|
215
|
+
const supported = await fetchSupported(config.facilitatorUrl, fetchFn);
|
|
216
|
+
for (const kind of supported.kinds ?? []) {
|
|
217
|
+
if (kind.scheme !== "exact") continue;
|
|
218
|
+
const normalized = normalizeExactDefaults(kind.extra);
|
|
219
|
+
if (normalized) {
|
|
220
|
+
supportedExactDefaults[kind.network] = normalized;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (wantsConfidential && confidentialConfig) {
|
|
225
|
+
let supportedDefaults = {};
|
|
226
|
+
const needsSupportedDefaults = !confidentialConfig.getNetworkConfig && (!confidentialConfig.asset || !confidentialConfig.eip712 || !confidentialConfig.batcherAddress);
|
|
227
|
+
if (needsSupportedDefaults) {
|
|
228
|
+
if (!fetchFn) {
|
|
229
|
+
throw new Error("fetch is required to load confidential defaults from facilitator");
|
|
230
|
+
}
|
|
231
|
+
const supported = await fetchSupported(config.facilitatorUrl, fetchFn);
|
|
232
|
+
for (const kind of supported.kinds ?? []) {
|
|
233
|
+
if (kind.scheme !== "erc7984-mind-v1") continue;
|
|
234
|
+
const normalized = normalizeConfidentialDefaults(kind.extra);
|
|
235
|
+
if (normalized) {
|
|
236
|
+
supportedDefaults[kind.network] = normalized;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const getNetworkConfig = confidentialConfig.getNetworkConfig ?? ((network) => {
|
|
241
|
+
const base = supportedDefaults[network] ?? {};
|
|
242
|
+
const override = {
|
|
243
|
+
asset: confidentialConfig.asset,
|
|
244
|
+
eip712: confidentialConfig.eip712,
|
|
245
|
+
decimals: confidentialConfig.decimals,
|
|
246
|
+
resourceHash: confidentialConfig.resourceHash,
|
|
247
|
+
batcherAddress: confidentialConfig.batcherAddress
|
|
248
|
+
};
|
|
249
|
+
const merged = {
|
|
250
|
+
...base,
|
|
251
|
+
...Object.fromEntries(Object.entries(override).filter(([, value]) => value !== void 0))
|
|
252
|
+
};
|
|
253
|
+
if (!merged.decimals) {
|
|
254
|
+
merged.decimals = 6;
|
|
255
|
+
}
|
|
256
|
+
if (!merged.asset || !merged.eip712?.name || !merged.eip712?.version || !merged.batcherAddress) {
|
|
257
|
+
throw new Error(`Missing confidential defaults for network ${network}`);
|
|
258
|
+
}
|
|
259
|
+
return merged;
|
|
260
|
+
});
|
|
261
|
+
registerX402zEvmServerScheme(resourceServer, {
|
|
262
|
+
getNetworkConfig,
|
|
263
|
+
networks: confidentialConfig.networks
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (wantsExact) {
|
|
267
|
+
registerExactEvmScheme(resourceServer, {});
|
|
268
|
+
}
|
|
269
|
+
const relayer = confidentialConfig?.relayer;
|
|
270
|
+
const rpcUrl = relayer ? getRelayerRpcUrl(relayer) : null;
|
|
132
271
|
const routes = {};
|
|
133
272
|
for (const [path, route] of Object.entries(config.routes)) {
|
|
273
|
+
const accepts = route.accepts.map((accept) => {
|
|
274
|
+
if (accept.scheme !== "exact") {
|
|
275
|
+
return accept;
|
|
276
|
+
}
|
|
277
|
+
if (typeof accept.price === "object") {
|
|
278
|
+
return accept;
|
|
279
|
+
}
|
|
280
|
+
const defaults = supportedExactDefaults[accept.network];
|
|
281
|
+
if (!defaults) {
|
|
282
|
+
throw new Error(`Missing exact defaults for network ${accept.network}`);
|
|
283
|
+
}
|
|
284
|
+
const decimalAmount = parseMoneyToDecimal(accept.price);
|
|
285
|
+
const amount = convertToTokenAmount(decimalAmount, defaults.decimals);
|
|
286
|
+
return {
|
|
287
|
+
...accept,
|
|
288
|
+
price: {
|
|
289
|
+
amount,
|
|
290
|
+
asset: defaults.asset,
|
|
291
|
+
extra: {
|
|
292
|
+
name: defaults.name,
|
|
293
|
+
version: defaults.version
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
});
|
|
134
298
|
routes[path] = {
|
|
135
299
|
...route,
|
|
136
|
-
accepts
|
|
137
|
-
...route.accepts,
|
|
138
|
-
scheme: "erc7984-mind-v1"
|
|
139
|
-
}
|
|
300
|
+
accepts
|
|
140
301
|
};
|
|
141
302
|
}
|
|
142
303
|
const httpServer = new x402HTTPResourceServer(resourceServer, routes);
|
|
@@ -146,7 +307,9 @@ async function createX402zServer(config) {
|
|
|
146
307
|
const method = adapter.getMethod();
|
|
147
308
|
const path = adapter.getPath();
|
|
148
309
|
const paymentHeader = adapter.getHeader("payment-signature") ?? adapter.getHeader("x402-payment") ?? adapter.getHeader("x-payment");
|
|
149
|
-
|
|
310
|
+
if (debugEnabled) {
|
|
311
|
+
console.debug(`[server] ${method} ${path}`);
|
|
312
|
+
}
|
|
150
313
|
const corsHeaders = config.cors ? {
|
|
151
314
|
"Access-Control-Allow-Origin": config.cors.allowOrigin,
|
|
152
315
|
"Access-Control-Allow-Methods": config.cors.allowMethods ?? "GET,POST,OPTIONS",
|
|
@@ -177,7 +340,9 @@ async function createX402zServer(config) {
|
|
|
177
340
|
if (result.type === "payment-error") {
|
|
178
341
|
res.writeHead(result.response.status, { ...corsHeaders, ...result.response.headers });
|
|
179
342
|
res.end(result.response.isHtml ? result.response.body : JSON.stringify(result.response.body ?? {}));
|
|
180
|
-
|
|
343
|
+
if (debugEnabled) {
|
|
344
|
+
console.debug(`[server] ${method} ${path} -> ${result.response.status}`);
|
|
345
|
+
}
|
|
181
346
|
return;
|
|
182
347
|
}
|
|
183
348
|
if (result.type === "payment-verified") {
|
|
@@ -185,68 +350,91 @@ async function createX402zServer(config) {
|
|
|
185
350
|
if (!settle.success) {
|
|
186
351
|
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
187
352
|
res.end(JSON.stringify({ error: settle.errorReason }));
|
|
188
|
-
|
|
353
|
+
if (debugEnabled) {
|
|
354
|
+
console.debug(`[server] ${method} ${path} -> 500 settlement_failed`);
|
|
355
|
+
}
|
|
189
356
|
return;
|
|
190
357
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
358
|
+
if (result.paymentRequirements.scheme === "erc7984-mind-v1") {
|
|
359
|
+
try {
|
|
360
|
+
if (!confidentialConfig || !relayer || !rpcUrl) {
|
|
361
|
+
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
362
|
+
res.end(JSON.stringify({ error: "confidential_config_missing" }));
|
|
363
|
+
if (debugEnabled) {
|
|
364
|
+
console.debug(`[server] ${method} ${path} -> 500 confidential_config_missing`);
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const observerClient = createPublicClient({
|
|
369
|
+
transport: http(rpcUrl)
|
|
370
|
+
});
|
|
371
|
+
const payTo = getAddress(result.paymentRequirements.payTo);
|
|
372
|
+
const observer = await observerClient.readContract({
|
|
373
|
+
address: getAddress(result.paymentRequirements.asset),
|
|
374
|
+
abi: confidentialTokenAbi,
|
|
375
|
+
functionName: "observer",
|
|
376
|
+
args: [payTo]
|
|
377
|
+
});
|
|
378
|
+
const signerAddress = getAddress(confidentialConfig.signer.address);
|
|
379
|
+
if (!observer || !isAddressEqual(observer, signerAddress)) {
|
|
380
|
+
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
381
|
+
res.end(JSON.stringify({ error: "observer_required" }));
|
|
382
|
+
if (debugEnabled) {
|
|
383
|
+
console.debug(`[server] ${method} ${path} -> 500 observer_required`);
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const batch = settle;
|
|
388
|
+
const transferredHandle = batch.batch?.transferredHandle;
|
|
389
|
+
if (!transferredHandle) {
|
|
390
|
+
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
391
|
+
res.end(JSON.stringify({ error: "missing_transferred_handle" }));
|
|
392
|
+
if (debugEnabled) {
|
|
393
|
+
console.debug(`[server] ${method} ${path} -> 500 missing_transferred_handle`);
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const decryptedAmount = await decryptEuint64(
|
|
398
|
+
relayer,
|
|
399
|
+
transferredHandle,
|
|
400
|
+
result.paymentRequirements.asset,
|
|
401
|
+
confidentialConfig.signer
|
|
402
|
+
);
|
|
403
|
+
const expected = BigInt(result.paymentRequirements.amount);
|
|
404
|
+
if (decryptedAmount !== expected) {
|
|
405
|
+
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
406
|
+
res.end(JSON.stringify({ error: "settlement_amount_mismatch" }));
|
|
407
|
+
if (debugEnabled) {
|
|
408
|
+
console.debug(`[server] ${method} ${path} -> 500 settlement_amount_mismatch`);
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (debugEnabled) {
|
|
414
|
+
console.error("[server] settlement verification error", error);
|
|
415
|
+
}
|
|
212
416
|
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
213
|
-
res.end(JSON.stringify({ error: "
|
|
214
|
-
|
|
417
|
+
res.end(JSON.stringify({ error: "settlement_verification_failed" }));
|
|
418
|
+
if (debugEnabled) {
|
|
419
|
+
console.debug(`[server] ${method} ${path} -> 500 settlement_verification_failed`);
|
|
420
|
+
}
|
|
215
421
|
return;
|
|
216
422
|
}
|
|
217
|
-
const decryptedAmount = await decryptEuint64(
|
|
218
|
-
relayer,
|
|
219
|
-
transferredHandle,
|
|
220
|
-
result.paymentRequirements.asset,
|
|
221
|
-
config.signer
|
|
222
|
-
);
|
|
223
|
-
const expected = BigInt(result.paymentRequirements.amount);
|
|
224
|
-
if (decryptedAmount !== expected) {
|
|
225
|
-
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
226
|
-
res.end(JSON.stringify({ error: "settlement_amount_mismatch" }));
|
|
227
|
-
console.log(`[server] ${method} ${path} -> 500 settlement_amount_mismatch`);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
} catch (error) {
|
|
231
|
-
res.writeHead(500, { ...corsHeaders, "Content-Type": "application/json" });
|
|
232
|
-
res.end(JSON.stringify({ error: "settlement_verification_failed" }));
|
|
233
|
-
console.log(`[server] ${method} ${path} -> 500 settlement_verification_failed`);
|
|
234
|
-
if (debugEnabled) {
|
|
235
|
-
console.debug("[x402z-server] settlement verification error", error);
|
|
236
|
-
}
|
|
237
|
-
return;
|
|
238
423
|
}
|
|
424
|
+
const settleHeaders = settle.headers ?? {};
|
|
239
425
|
const payload = await config.onPaid({
|
|
240
426
|
paymentRequirements: result.paymentRequirements,
|
|
241
|
-
settleHeaders
|
|
427
|
+
settleHeaders
|
|
242
428
|
});
|
|
243
429
|
const status = payload.status ?? 200;
|
|
244
430
|
const responseHeaders = {
|
|
245
431
|
...corsHeaders,
|
|
246
|
-
|
|
247
|
-
...settle.headers,
|
|
432
|
+
...settleHeaders,
|
|
248
433
|
...payload.headers ?? {}
|
|
249
434
|
};
|
|
435
|
+
if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
|
|
436
|
+
responseHeaders["Content-Type"] = "text/plain";
|
|
437
|
+
}
|
|
250
438
|
if (debugEnabled) {
|
|
251
439
|
console.debug("[x402z-server] response", {
|
|
252
440
|
status,
|
|
@@ -255,7 +443,9 @@ async function createX402zServer(config) {
|
|
|
255
443
|
});
|
|
256
444
|
}
|
|
257
445
|
sendText(res, status, responseHeaders, payload.body);
|
|
258
|
-
|
|
446
|
+
if (debugEnabled) {
|
|
447
|
+
console.debug(`[server] ${method} ${path} -> ${status}`);
|
|
448
|
+
}
|
|
259
449
|
return;
|
|
260
450
|
}
|
|
261
451
|
sendText(
|
|
@@ -264,7 +454,9 @@ async function createX402zServer(config) {
|
|
|
264
454
|
{ ...corsHeaders, "Content-Type": "text/plain" },
|
|
265
455
|
"no payment required"
|
|
266
456
|
);
|
|
267
|
-
|
|
457
|
+
if (debugEnabled) {
|
|
458
|
+
console.debug(`[server] ${method} ${path} -> 200`);
|
|
459
|
+
}
|
|
268
460
|
});
|
|
269
461
|
return server;
|
|
270
462
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402z-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
"dist"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@x402/core": "^2.
|
|
11
|
+
"@x402/core": "^2.2.0",
|
|
12
|
+
"@x402/evm": "^2.2.0",
|
|
12
13
|
"viem": "^2.43.3",
|
|
13
|
-
"x402z-shared": "0.0
|
|
14
|
+
"x402z-shared": "0.1.0"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
17
|
"jest": "^29.7.0",
|