x402z-server 0.0.2 → 0.0.4

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 CHANGED
@@ -24,7 +24,7 @@ const server = await createX402zServer({
24
24
  asset: "0xToken",
25
25
  eip712: { name: "FHEToken Confidential", version: "1" },
26
26
  decimals: 6,
27
- facilitatorAddress: "0xFacilitator",
27
+ batcherAddress: "0xBatcher",
28
28
  signer: { address, signTypedData },
29
29
  relayer,
30
30
  routes: {
@@ -51,7 +51,7 @@ server.listen(8080);
51
51
 
52
52
  - `createX402zServer(config)`
53
53
  - `facilitatorUrl` (required): HTTP facilitator endpoint
54
- - `asset`, `eip712`, `decimals`, `facilitatorAddress`: scheme config
54
+ - `asset`, `eip712`, `decimals`, `batcherAddress`: scheme config
55
55
  - `signer` (required): signer used to decrypt transfer amounts
56
56
  - `relayer` (required): FHEVM relayer instance used for decryption
57
57
  - `routes`: map of `METHOD /path` to payment requirements
@@ -60,4 +60,4 @@ server.listen(8080);
60
60
  ## Notes
61
61
 
62
62
  - Scheme name: `erc7984-mind-v1`
63
- - `confidential.facilitatorAddress` is included in requirements so clients can bind encrypted inputs.
63
+ - `confidential.batcherAddress` is required in requirements; clients bind encrypted inputs to it.
package/dist/index.d.mts CHANGED
@@ -3,6 +3,7 @@ export * from '@x402/core/types';
3
3
  import { x402ResourceServer } from '@x402/core/server';
4
4
  export { x402ResourceServer } from '@x402/core/server';
5
5
  import * as http from 'http';
6
+ import { RelayerSigner, RelayerInstance } from 'x402z-shared';
6
7
  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
 
8
9
  type ConfidentialServerNetworkConfig = {
@@ -13,7 +14,7 @@ type ConfidentialServerNetworkConfig = {
13
14
  };
14
15
  decimals?: number;
15
16
  resourceHash?: `0x${string}`;
16
- facilitatorAddress?: `0x${string}`;
17
+ batcherAddress: `0x${string}`;
17
18
  };
18
19
  type ConfidentialServerConfig = ConfidentialServerNetworkConfig | {
19
20
  getNetworkConfig: (network: Network) => ConfidentialServerNetworkConfig;
@@ -60,6 +61,8 @@ type X402zServerConfig = ConfidentialServerRegisterConfig & {
60
61
  facilitatorUrl: string;
61
62
  routes: Record<string, X402zRouteConfig>;
62
63
  onPaid: X402zRouteHandler;
64
+ signer: RelayerSigner;
65
+ relayer: RelayerInstance;
63
66
  debug?: boolean;
64
67
  };
65
68
  declare function createX402zServer(config: X402zServerConfig): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from '@x402/core/types';
3
3
  import { x402ResourceServer } from '@x402/core/server';
4
4
  export { x402ResourceServer } from '@x402/core/server';
5
5
  import * as http from 'http';
6
+ import { RelayerSigner, RelayerInstance } from 'x402z-shared';
6
7
  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
 
8
9
  type ConfidentialServerNetworkConfig = {
@@ -13,7 +14,7 @@ type ConfidentialServerNetworkConfig = {
13
14
  };
14
15
  decimals?: number;
15
16
  resourceHash?: `0x${string}`;
16
- facilitatorAddress?: `0x${string}`;
17
+ batcherAddress: `0x${string}`;
17
18
  };
18
19
  type ConfidentialServerConfig = ConfidentialServerNetworkConfig | {
19
20
  getNetworkConfig: (network: Network) => ConfidentialServerNetworkConfig;
@@ -60,6 +61,8 @@ type X402zServerConfig = ConfidentialServerRegisterConfig & {
60
61
  facilitatorUrl: string;
61
62
  routes: Record<string, X402zRouteConfig>;
62
63
  onPaid: X402zRouteHandler;
64
+ signer: RelayerSigner;
65
+ relayer: RelayerInstance;
63
66
  debug?: boolean;
64
67
  };
65
68
  declare function createX402zServer(config: X402zServerConfig): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
package/dist/index.js CHANGED
@@ -52,15 +52,16 @@ var ConfidentialEvmScheme = class {
52
52
  }
53
53
  const amount = this.parseMoneyToDecimal(price);
54
54
  const config = this.getConfig(network);
55
+ const tokenAmount = this.convertToTokenAmount(amount.toString(), config.decimals ?? 6);
55
56
  return {
56
- amount: this.convertToTokenAmount(amount.toString(), config.decimals ?? 6),
57
+ amount: tokenAmount,
57
58
  asset: config.asset,
58
59
  extra: {
59
60
  eip712: config.eip712,
60
61
  confidential: {
61
- maxClearAmount: amount.toString(),
62
+ maxClearAmount: tokenAmount,
62
63
  resourceHash: config.resourceHash,
63
- facilitatorAddress: config.facilitatorAddress
64
+ batcherAddress: config.batcherAddress
64
65
  }
65
66
  }
66
67
  };
@@ -76,7 +77,7 @@ var ConfidentialEvmScheme = class {
76
77
  confidential: {
77
78
  maxClearAmount: extra?.confidential?.maxClearAmount ?? paymentRequirements.amount,
78
79
  resourceHash: extra?.confidential?.resourceHash ?? config.resourceHash,
79
- facilitatorAddress: extra?.confidential?.facilitatorAddress ?? config.facilitatorAddress
80
+ batcherAddress: extra?.confidential?.batcherAddress ?? config.batcherAddress
80
81
  }
81
82
  };
82
83
  return {
@@ -124,6 +125,15 @@ var import_node_url = require("url");
124
125
  var import_server = require("@x402/core/server");
125
126
  var import_http = require("@x402/core/http");
126
127
  var import_http2 = require("@x402/core/http");
128
+ var import_x402z_shared = require("x402z-shared");
129
+ var import_viem = require("viem");
130
+ function getRelayerRpcUrl(relayer) {
131
+ const network = relayer.network;
132
+ if (typeof network === "string") {
133
+ return network;
134
+ }
135
+ throw new Error("relayer.network must be a string RPC URL");
136
+ }
127
137
  function buildAdapter(req) {
128
138
  const url = new import_node_url.URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
129
139
  return {
@@ -148,6 +158,8 @@ async function createX402zServer(config) {
148
158
  const facilitatorClient = new import_http2.HTTPFacilitatorClient({ url: config.facilitatorUrl });
149
159
  const resourceServer = new import_server.x402ResourceServer(facilitatorClient);
150
160
  registerConfidentialEvmScheme(resourceServer, config);
161
+ const relayer = config.relayer;
162
+ const rpcUrl = getRelayerRpcUrl(relayer);
151
163
  const routes = {};
152
164
  for (const [path, route] of Object.entries(config.routes)) {
153
165
  routes[path] = {
@@ -188,6 +200,54 @@ async function createX402zServer(config) {
188
200
  console.log(`[server] ${method} ${path} -> 500 settlement_failed`);
189
201
  return;
190
202
  }
203
+ try {
204
+ const observerClient = (0, import_viem.createPublicClient)({
205
+ transport: (0, import_viem.http)(rpcUrl)
206
+ });
207
+ const payTo = (0, import_viem.getAddress)(result.paymentRequirements.payTo);
208
+ const observer = await observerClient.readContract({
209
+ address: (0, import_viem.getAddress)(result.paymentRequirements.asset),
210
+ abi: import_x402z_shared.confidentialTokenAbi,
211
+ functionName: "observer",
212
+ args: [payTo]
213
+ });
214
+ const signerAddress = (0, import_viem.getAddress)(config.signer.address);
215
+ if (!observer || !(0, import_viem.isAddressEqual)(observer, signerAddress)) {
216
+ res.writeHead(500, { "Content-Type": "application/json" });
217
+ res.end(JSON.stringify({ error: "observer_required" }));
218
+ console.log(`[server] ${method} ${path} -> 500 observer_required`);
219
+ return;
220
+ }
221
+ const batch = settle;
222
+ const transferredHandle = batch.batch?.transferredHandle;
223
+ if (!transferredHandle) {
224
+ res.writeHead(500, { "Content-Type": "application/json" });
225
+ res.end(JSON.stringify({ error: "missing_transferred_handle" }));
226
+ console.log(`[server] ${method} ${path} -> 500 missing_transferred_handle`);
227
+ return;
228
+ }
229
+ const decryptedAmount = await (0, import_x402z_shared.userDecryptEuint64)(
230
+ relayer,
231
+ transferredHandle,
232
+ result.paymentRequirements.asset,
233
+ config.signer
234
+ );
235
+ const expected = BigInt(result.paymentRequirements.amount);
236
+ if (decryptedAmount !== expected) {
237
+ res.writeHead(500, { "Content-Type": "application/json" });
238
+ res.end(JSON.stringify({ error: "settlement_amount_mismatch" }));
239
+ console.log(`[server] ${method} ${path} -> 500 settlement_amount_mismatch`);
240
+ return;
241
+ }
242
+ } catch (error) {
243
+ res.writeHead(500, { "Content-Type": "application/json" });
244
+ res.end(JSON.stringify({ error: "settlement_verification_failed" }));
245
+ console.log(`[server] ${method} ${path} -> 500 settlement_verification_failed`);
246
+ if (debugEnabled) {
247
+ console.debug("[x402z-server] settlement verification error", error);
248
+ }
249
+ return;
250
+ }
191
251
  const payload = await config.onPaid({
192
252
  paymentRequirements: result.paymentRequirements,
193
253
  settleHeaders: settle.headers
package/dist/index.mjs CHANGED
@@ -21,15 +21,16 @@ var ConfidentialEvmScheme = class {
21
21
  }
22
22
  const amount = this.parseMoneyToDecimal(price);
23
23
  const config = this.getConfig(network);
24
+ const tokenAmount = this.convertToTokenAmount(amount.toString(), config.decimals ?? 6);
24
25
  return {
25
- amount: this.convertToTokenAmount(amount.toString(), config.decimals ?? 6),
26
+ amount: tokenAmount,
26
27
  asset: config.asset,
27
28
  extra: {
28
29
  eip712: config.eip712,
29
30
  confidential: {
30
- maxClearAmount: amount.toString(),
31
+ maxClearAmount: tokenAmount,
31
32
  resourceHash: config.resourceHash,
32
- facilitatorAddress: config.facilitatorAddress
33
+ batcherAddress: config.batcherAddress
33
34
  }
34
35
  }
35
36
  };
@@ -45,7 +46,7 @@ var ConfidentialEvmScheme = class {
45
46
  confidential: {
46
47
  maxClearAmount: extra?.confidential?.maxClearAmount ?? paymentRequirements.amount,
47
48
  resourceHash: extra?.confidential?.resourceHash ?? config.resourceHash,
48
- facilitatorAddress: extra?.confidential?.facilitatorAddress ?? config.facilitatorAddress
49
+ batcherAddress: extra?.confidential?.batcherAddress ?? config.batcherAddress
49
50
  }
50
51
  };
51
52
  return {
@@ -93,6 +94,15 @@ import { URL } from "url";
93
94
  import { x402ResourceServer } from "@x402/core/server";
94
95
  import { x402HTTPResourceServer } from "@x402/core/http";
95
96
  import { HTTPFacilitatorClient } from "@x402/core/http";
97
+ import { confidentialTokenAbi, userDecryptEuint64 } from "x402z-shared";
98
+ import { createPublicClient, getAddress, http, isAddressEqual } from "viem";
99
+ function getRelayerRpcUrl(relayer) {
100
+ const network = relayer.network;
101
+ if (typeof network === "string") {
102
+ return network;
103
+ }
104
+ throw new Error("relayer.network must be a string RPC URL");
105
+ }
96
106
  function buildAdapter(req) {
97
107
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
98
108
  return {
@@ -117,6 +127,8 @@ async function createX402zServer(config) {
117
127
  const facilitatorClient = new HTTPFacilitatorClient({ url: config.facilitatorUrl });
118
128
  const resourceServer = new x402ResourceServer(facilitatorClient);
119
129
  registerConfidentialEvmScheme(resourceServer, config);
130
+ const relayer = config.relayer;
131
+ const rpcUrl = getRelayerRpcUrl(relayer);
120
132
  const routes = {};
121
133
  for (const [path, route] of Object.entries(config.routes)) {
122
134
  routes[path] = {
@@ -157,6 +169,54 @@ async function createX402zServer(config) {
157
169
  console.log(`[server] ${method} ${path} -> 500 settlement_failed`);
158
170
  return;
159
171
  }
172
+ try {
173
+ const observerClient = createPublicClient({
174
+ transport: http(rpcUrl)
175
+ });
176
+ const payTo = getAddress(result.paymentRequirements.payTo);
177
+ const observer = await observerClient.readContract({
178
+ address: getAddress(result.paymentRequirements.asset),
179
+ abi: confidentialTokenAbi,
180
+ functionName: "observer",
181
+ args: [payTo]
182
+ });
183
+ const signerAddress = getAddress(config.signer.address);
184
+ if (!observer || !isAddressEqual(observer, signerAddress)) {
185
+ res.writeHead(500, { "Content-Type": "application/json" });
186
+ res.end(JSON.stringify({ error: "observer_required" }));
187
+ console.log(`[server] ${method} ${path} -> 500 observer_required`);
188
+ return;
189
+ }
190
+ const batch = settle;
191
+ const transferredHandle = batch.batch?.transferredHandle;
192
+ if (!transferredHandle) {
193
+ res.writeHead(500, { "Content-Type": "application/json" });
194
+ res.end(JSON.stringify({ error: "missing_transferred_handle" }));
195
+ console.log(`[server] ${method} ${path} -> 500 missing_transferred_handle`);
196
+ return;
197
+ }
198
+ const decryptedAmount = await userDecryptEuint64(
199
+ relayer,
200
+ transferredHandle,
201
+ result.paymentRequirements.asset,
202
+ config.signer
203
+ );
204
+ const expected = BigInt(result.paymentRequirements.amount);
205
+ if (decryptedAmount !== expected) {
206
+ res.writeHead(500, { "Content-Type": "application/json" });
207
+ res.end(JSON.stringify({ error: "settlement_amount_mismatch" }));
208
+ console.log(`[server] ${method} ${path} -> 500 settlement_amount_mismatch`);
209
+ return;
210
+ }
211
+ } catch (error) {
212
+ res.writeHead(500, { "Content-Type": "application/json" });
213
+ res.end(JSON.stringify({ error: "settlement_verification_failed" }));
214
+ console.log(`[server] ${method} ${path} -> 500 settlement_verification_failed`);
215
+ if (debugEnabled) {
216
+ console.debug("[x402z-server] settlement verification error", error);
217
+ }
218
+ return;
219
+ }
160
220
  const payload = await config.onPaid({
161
221
  paymentRequirements: result.paymentRequirements,
162
222
  settleHeaders: settle.headers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402z-server",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "dependencies": {
11
11
  "@x402/core": "^2.0.0",
12
12
  "viem": "^2.43.3",
13
- "x402z-shared": "0.0.2"
13
+ "x402z-shared": "0.0.4"
14
14
  },
15
15
  "devDependencies": {
16
16
  "jest": "^29.7.0",