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 +3 -3
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +64 -4
- package/dist/index.mjs +64 -4
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
57
|
+
amount: tokenAmount,
|
|
57
58
|
asset: config.asset,
|
|
58
59
|
extra: {
|
|
59
60
|
eip712: config.eip712,
|
|
60
61
|
confidential: {
|
|
61
|
-
maxClearAmount:
|
|
62
|
+
maxClearAmount: tokenAmount,
|
|
62
63
|
resourceHash: config.resourceHash,
|
|
63
|
-
|
|
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
|
-
|
|
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:
|
|
26
|
+
amount: tokenAmount,
|
|
26
27
|
asset: config.asset,
|
|
27
28
|
extra: {
|
|
28
29
|
eip712: config.eip712,
|
|
29
30
|
confidential: {
|
|
30
|
-
maxClearAmount:
|
|
31
|
+
maxClearAmount: tokenAmount,
|
|
31
32
|
resourceHash: config.resourceHash,
|
|
32
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
13
|
+
"x402z-shared": "0.0.4"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"jest": "^29.7.0",
|