x402z-facilitator 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 +31 -0
- package/dist/bootstrap.d.mts +8 -0
- package/dist/bootstrap.d.ts +8 -0
- package/dist/bootstrap.js +357 -0
- package/dist/bootstrap.mjs +105 -0
- package/dist/chunk-4LCQ4MN3.mjs +253 -0
- package/dist/chunk-H6QEA2EB.mjs +352 -0
- package/dist/index.d.mts +49 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +281 -0
- package/dist/index.mjs +21 -0
- package/package.json +27 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/service.ts
|
|
9
|
+
import { createServer } from "http";
|
|
10
|
+
async function readJson(req) {
|
|
11
|
+
const chunks = [];
|
|
12
|
+
for await (const chunk of req) {
|
|
13
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
14
|
+
}
|
|
15
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
16
|
+
return JSON.parse(body);
|
|
17
|
+
}
|
|
18
|
+
function sendJson(res, status, payload) {
|
|
19
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
20
|
+
res.end(JSON.stringify(payload));
|
|
21
|
+
}
|
|
22
|
+
function createFacilitatorService(config) {
|
|
23
|
+
const server = createServer(async (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const method = req.method ?? "GET";
|
|
26
|
+
const url = req.url ?? "/";
|
|
27
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
28
|
+
console.debug(`[x402z-facilitator] ${method} ${url}`);
|
|
29
|
+
}
|
|
30
|
+
if (!req.url) {
|
|
31
|
+
sendJson(res, 404, { error: "not_found" });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (req.method === "GET" && req.url === "/supported") {
|
|
35
|
+
sendJson(res, 200, config.facilitator.getSupported());
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (req.method === "POST" && req.url === "/verify") {
|
|
39
|
+
const body = await readJson(req);
|
|
40
|
+
const result = await config.facilitator.verify(body.paymentPayload, body.paymentRequirements);
|
|
41
|
+
sendJson(res, 200, result);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (req.method === "POST" && req.url === "/settle") {
|
|
45
|
+
const body = await readJson(req);
|
|
46
|
+
const result = await config.facilitator.settle(body.paymentPayload, body.paymentRequirements);
|
|
47
|
+
sendJson(res, 200, result);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
sendJson(res, 404, { error: "not_found" });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const message = error instanceof Error ? error.message : "internal_error";
|
|
53
|
+
sendJson(res, 500, { error: message });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return server;
|
|
57
|
+
}
|
|
58
|
+
function startFacilitatorService(config) {
|
|
59
|
+
const port = config.port ?? 8040;
|
|
60
|
+
const server = createFacilitatorService(config);
|
|
61
|
+
server.listen(port);
|
|
62
|
+
return { port };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/scheme.ts
|
|
66
|
+
import { getAddress, isAddressEqual } from "viem";
|
|
67
|
+
import {
|
|
68
|
+
confidentialPaymentTypes,
|
|
69
|
+
confidentialTokenAbi,
|
|
70
|
+
hashEncryptedAmountInput
|
|
71
|
+
} from "x402z-shared";
|
|
72
|
+
var ConfidentialEvmFacilitator = class {
|
|
73
|
+
constructor(config) {
|
|
74
|
+
this.config = config;
|
|
75
|
+
this.scheme = "erc7984-mind-v1";
|
|
76
|
+
this.caipFamily = "eip155:*";
|
|
77
|
+
this.hashFn = config.hashEncryptedAmountInput ?? hashEncryptedAmountInput;
|
|
78
|
+
this.clock = config.clock ?? (() => Math.floor(Date.now() / 1e3));
|
|
79
|
+
this.checkUsedNonces = config.checkUsedNonces ?? true;
|
|
80
|
+
this.waitForReceipt = config.waitForReceipt ?? true;
|
|
81
|
+
this.receiptOptions = config.receipt;
|
|
82
|
+
}
|
|
83
|
+
getExtra(_) {
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
getSigners(_) {
|
|
87
|
+
return [...this.config.signer.getAddresses()];
|
|
88
|
+
}
|
|
89
|
+
async verify(payload, requirements) {
|
|
90
|
+
const confidentialPayload = payload.payload;
|
|
91
|
+
if (payload.accepted.scheme !== this.scheme || requirements.scheme !== this.scheme) {
|
|
92
|
+
return {
|
|
93
|
+
isValid: false,
|
|
94
|
+
invalidReason: "unsupported_scheme",
|
|
95
|
+
payer: confidentialPayload?.authorization?.holder
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (payload.accepted.network !== requirements.network) {
|
|
99
|
+
return {
|
|
100
|
+
isValid: false,
|
|
101
|
+
invalidReason: "network_mismatch",
|
|
102
|
+
payer: confidentialPayload.authorization.holder
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const extra = requirements.extra;
|
|
106
|
+
const eip712 = extra?.eip712;
|
|
107
|
+
if (!eip712?.name || !eip712?.version) {
|
|
108
|
+
return {
|
|
109
|
+
isValid: false,
|
|
110
|
+
invalidReason: "missing_eip712_domain",
|
|
111
|
+
payer: confidentialPayload.authorization.holder
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const now = this.clock();
|
|
115
|
+
const validAfter = Number(confidentialPayload.authorization.validAfter);
|
|
116
|
+
const validBefore = Number(confidentialPayload.authorization.validBefore);
|
|
117
|
+
if (Number.isNaN(validAfter) || Number.isNaN(validBefore)) {
|
|
118
|
+
return {
|
|
119
|
+
isValid: false,
|
|
120
|
+
invalidReason: "invalid_validity_window",
|
|
121
|
+
payer: confidentialPayload.authorization.holder
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (now < validAfter || now > validBefore) {
|
|
125
|
+
return {
|
|
126
|
+
isValid: false,
|
|
127
|
+
invalidReason: "authorization_expired",
|
|
128
|
+
payer: confidentialPayload.authorization.holder
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (!isAddressEqual(getAddress(confidentialPayload.authorization.payee), getAddress(requirements.payTo))) {
|
|
132
|
+
return {
|
|
133
|
+
isValid: false,
|
|
134
|
+
invalidReason: "recipient_mismatch",
|
|
135
|
+
payer: confidentialPayload.authorization.holder
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const computedHash = this.hashFn(confidentialPayload.encryptedAmountInput);
|
|
139
|
+
if (computedHash !== confidentialPayload.authorization.encryptedAmountHash) {
|
|
140
|
+
return {
|
|
141
|
+
isValid: false,
|
|
142
|
+
invalidReason: "encrypted_amount_mismatch",
|
|
143
|
+
payer: confidentialPayload.authorization.holder
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const chainId = parseInt(requirements.network.split(":")[1]);
|
|
147
|
+
const isValidSignature = await this.config.signer.verifyTypedData({
|
|
148
|
+
address: confidentialPayload.authorization.holder,
|
|
149
|
+
domain: {
|
|
150
|
+
name: eip712.name,
|
|
151
|
+
version: eip712.version,
|
|
152
|
+
chainId,
|
|
153
|
+
verifyingContract: getAddress(requirements.asset)
|
|
154
|
+
},
|
|
155
|
+
types: confidentialPaymentTypes,
|
|
156
|
+
primaryType: "ConfidentialPayment",
|
|
157
|
+
message: {
|
|
158
|
+
holder: getAddress(confidentialPayload.authorization.holder),
|
|
159
|
+
payee: getAddress(confidentialPayload.authorization.payee),
|
|
160
|
+
maxClearAmount: BigInt(confidentialPayload.authorization.maxClearAmount),
|
|
161
|
+
resourceHash: confidentialPayload.authorization.resourceHash,
|
|
162
|
+
validAfter: BigInt(confidentialPayload.authorization.validAfter),
|
|
163
|
+
validBefore: BigInt(confidentialPayload.authorization.validBefore),
|
|
164
|
+
nonce: confidentialPayload.authorization.nonce,
|
|
165
|
+
encryptedAmountHash: confidentialPayload.authorization.encryptedAmountHash
|
|
166
|
+
},
|
|
167
|
+
signature: confidentialPayload.signature
|
|
168
|
+
});
|
|
169
|
+
if (!isValidSignature) {
|
|
170
|
+
return {
|
|
171
|
+
isValid: false,
|
|
172
|
+
invalidReason: "invalid_signature",
|
|
173
|
+
payer: confidentialPayload.authorization.holder
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (this.checkUsedNonces) {
|
|
177
|
+
const used = await this.config.signer.readContract({
|
|
178
|
+
address: getAddress(requirements.asset),
|
|
179
|
+
abi: confidentialTokenAbi,
|
|
180
|
+
functionName: "usedNonces",
|
|
181
|
+
args: [confidentialPayload.authorization.holder, confidentialPayload.authorization.nonce]
|
|
182
|
+
});
|
|
183
|
+
if (used) {
|
|
184
|
+
return {
|
|
185
|
+
isValid: false,
|
|
186
|
+
invalidReason: "nonce_already_used",
|
|
187
|
+
payer: confidentialPayload.authorization.holder
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
isValid: true,
|
|
193
|
+
payer: confidentialPayload.authorization.holder
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async settle(payload, requirements) {
|
|
197
|
+
const valid = await this.verify(payload, requirements);
|
|
198
|
+
const confidentialPayload = payload.payload;
|
|
199
|
+
if (!valid.isValid) {
|
|
200
|
+
return {
|
|
201
|
+
success: false,
|
|
202
|
+
errorReason: valid.invalidReason ?? "invalid_payment",
|
|
203
|
+
payer: confidentialPayload.authorization.holder,
|
|
204
|
+
transaction: "",
|
|
205
|
+
network: requirements.network
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const txHash = await this.config.signer.writeContract({
|
|
209
|
+
address: getAddress(requirements.asset),
|
|
210
|
+
abi: confidentialTokenAbi,
|
|
211
|
+
functionName: "confidentialTransferWithAuthorization",
|
|
212
|
+
args: [
|
|
213
|
+
confidentialPayload.authorization,
|
|
214
|
+
confidentialPayload.encryptedAmountInput,
|
|
215
|
+
confidentialPayload.inputProof,
|
|
216
|
+
confidentialPayload.signature
|
|
217
|
+
]
|
|
218
|
+
});
|
|
219
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
220
|
+
console.debug("[x402z-facilitator] tx submitted", txHash);
|
|
221
|
+
}
|
|
222
|
+
if (this.waitForReceipt) {
|
|
223
|
+
const receipt = await this.config.signer.waitForTransactionReceipt({
|
|
224
|
+
hash: txHash
|
|
225
|
+
});
|
|
226
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
227
|
+
console.debug("[x402z-facilitator] tx receipt", receipt);
|
|
228
|
+
}
|
|
229
|
+
if (receipt.status !== "success") {
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
errorReason: "settlement_failed",
|
|
233
|
+
payer: confidentialPayload.authorization.holder,
|
|
234
|
+
transaction: txHash,
|
|
235
|
+
network: requirements.network
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
success: true,
|
|
241
|
+
payer: confidentialPayload.authorization.holder,
|
|
242
|
+
transaction: txHash,
|
|
243
|
+
network: requirements.network
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export {
|
|
249
|
+
__require,
|
|
250
|
+
createFacilitatorService,
|
|
251
|
+
startFacilitatorService,
|
|
252
|
+
ConfidentialEvmFacilitator
|
|
253
|
+
};
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/bootstrap.ts
|
|
9
|
+
import { x402Facilitator } from "@x402/core/facilitator";
|
|
10
|
+
import { toFacilitatorEvmSigner } from "@x402/evm";
|
|
11
|
+
import { createWalletClient, http, publicActions } from "viem";
|
|
12
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
13
|
+
|
|
14
|
+
// src/service.ts
|
|
15
|
+
import { createServer } from "http";
|
|
16
|
+
async function readJson(req) {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
for await (const chunk of req) {
|
|
19
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
20
|
+
}
|
|
21
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
22
|
+
return JSON.parse(body);
|
|
23
|
+
}
|
|
24
|
+
function sendJson(res, status, payload) {
|
|
25
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
26
|
+
res.end(JSON.stringify(payload));
|
|
27
|
+
}
|
|
28
|
+
function createFacilitatorService(config) {
|
|
29
|
+
const server = createServer(async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const method = req.method ?? "GET";
|
|
32
|
+
const url = req.url ?? "/";
|
|
33
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
34
|
+
console.debug(`[x402z-facilitator] ${method} ${url}`);
|
|
35
|
+
}
|
|
36
|
+
if (!req.url) {
|
|
37
|
+
sendJson(res, 404, { error: "not_found" });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (req.method === "GET" && req.url === "/supported") {
|
|
41
|
+
sendJson(res, 200, config.facilitator.getSupported());
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (req.method === "POST" && req.url === "/verify") {
|
|
45
|
+
const body = await readJson(req);
|
|
46
|
+
const result = await config.facilitator.verify(body.paymentPayload, body.paymentRequirements);
|
|
47
|
+
sendJson(res, 200, result);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (req.method === "POST" && req.url === "/settle") {
|
|
51
|
+
const body = await readJson(req);
|
|
52
|
+
const result = await config.facilitator.settle(body.paymentPayload, body.paymentRequirements);
|
|
53
|
+
sendJson(res, 200, result);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
sendJson(res, 404, { error: "not_found" });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : "internal_error";
|
|
59
|
+
sendJson(res, 500, { error: message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return server;
|
|
63
|
+
}
|
|
64
|
+
function startFacilitatorService(config) {
|
|
65
|
+
const port = config.port ?? 8040;
|
|
66
|
+
const server = createFacilitatorService(config);
|
|
67
|
+
server.listen(port);
|
|
68
|
+
return { port };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/scheme.ts
|
|
72
|
+
import { getAddress, isAddressEqual } from "viem";
|
|
73
|
+
import {
|
|
74
|
+
confidentialPaymentTypes,
|
|
75
|
+
confidentialTokenAbi,
|
|
76
|
+
hashEncryptedAmountInput
|
|
77
|
+
} from "x402z-shared";
|
|
78
|
+
var ConfidentialEvmFacilitator = class {
|
|
79
|
+
constructor(config) {
|
|
80
|
+
this.config = config;
|
|
81
|
+
this.scheme = "erc7984-mind-v1";
|
|
82
|
+
this.caipFamily = "eip155:*";
|
|
83
|
+
this.hashFn = config.hashEncryptedAmountInput ?? hashEncryptedAmountInput;
|
|
84
|
+
this.clock = config.clock ?? (() => Math.floor(Date.now() / 1e3));
|
|
85
|
+
this.checkUsedNonces = config.checkUsedNonces ?? true;
|
|
86
|
+
this.waitForReceipt = config.waitForReceipt ?? true;
|
|
87
|
+
this.receiptOptions = config.receipt;
|
|
88
|
+
}
|
|
89
|
+
getExtra(_) {
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
getSigners(_) {
|
|
93
|
+
return [...this.config.signer.getAddresses()];
|
|
94
|
+
}
|
|
95
|
+
async verify(payload, requirements) {
|
|
96
|
+
const confidentialPayload = payload.payload;
|
|
97
|
+
if (payload.accepted.scheme !== this.scheme || requirements.scheme !== this.scheme) {
|
|
98
|
+
return {
|
|
99
|
+
isValid: false,
|
|
100
|
+
invalidReason: "unsupported_scheme",
|
|
101
|
+
payer: confidentialPayload?.authorization?.holder
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (payload.accepted.network !== requirements.network) {
|
|
105
|
+
return {
|
|
106
|
+
isValid: false,
|
|
107
|
+
invalidReason: "network_mismatch",
|
|
108
|
+
payer: confidentialPayload.authorization.holder
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const extra = requirements.extra;
|
|
112
|
+
const eip712 = extra?.eip712;
|
|
113
|
+
if (!eip712?.name || !eip712?.version) {
|
|
114
|
+
return {
|
|
115
|
+
isValid: false,
|
|
116
|
+
invalidReason: "missing_eip712_domain",
|
|
117
|
+
payer: confidentialPayload.authorization.holder
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const now = this.clock();
|
|
121
|
+
const validAfter = Number(confidentialPayload.authorization.validAfter);
|
|
122
|
+
const validBefore = Number(confidentialPayload.authorization.validBefore);
|
|
123
|
+
if (Number.isNaN(validAfter) || Number.isNaN(validBefore)) {
|
|
124
|
+
return {
|
|
125
|
+
isValid: false,
|
|
126
|
+
invalidReason: "invalid_validity_window",
|
|
127
|
+
payer: confidentialPayload.authorization.holder
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (now < validAfter || now > validBefore) {
|
|
131
|
+
return {
|
|
132
|
+
isValid: false,
|
|
133
|
+
invalidReason: "authorization_expired",
|
|
134
|
+
payer: confidentialPayload.authorization.holder
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (!isAddressEqual(getAddress(confidentialPayload.authorization.payee), getAddress(requirements.payTo))) {
|
|
138
|
+
return {
|
|
139
|
+
isValid: false,
|
|
140
|
+
invalidReason: "recipient_mismatch",
|
|
141
|
+
payer: confidentialPayload.authorization.holder
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const computedHash = this.hashFn(confidentialPayload.encryptedAmountInput);
|
|
145
|
+
if (computedHash !== confidentialPayload.authorization.encryptedAmountHash) {
|
|
146
|
+
return {
|
|
147
|
+
isValid: false,
|
|
148
|
+
invalidReason: "encrypted_amount_mismatch",
|
|
149
|
+
payer: confidentialPayload.authorization.holder
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const chainId = parseInt(requirements.network.split(":")[1]);
|
|
153
|
+
const isValidSignature = await this.config.signer.verifyTypedData({
|
|
154
|
+
address: confidentialPayload.authorization.holder,
|
|
155
|
+
domain: {
|
|
156
|
+
name: eip712.name,
|
|
157
|
+
version: eip712.version,
|
|
158
|
+
chainId,
|
|
159
|
+
verifyingContract: getAddress(requirements.asset)
|
|
160
|
+
},
|
|
161
|
+
types: confidentialPaymentTypes,
|
|
162
|
+
primaryType: "ConfidentialPayment",
|
|
163
|
+
message: {
|
|
164
|
+
holder: getAddress(confidentialPayload.authorization.holder),
|
|
165
|
+
payee: getAddress(confidentialPayload.authorization.payee),
|
|
166
|
+
maxClearAmount: BigInt(confidentialPayload.authorization.maxClearAmount),
|
|
167
|
+
resourceHash: confidentialPayload.authorization.resourceHash,
|
|
168
|
+
validAfter: BigInt(confidentialPayload.authorization.validAfter),
|
|
169
|
+
validBefore: BigInt(confidentialPayload.authorization.validBefore),
|
|
170
|
+
nonce: confidentialPayload.authorization.nonce,
|
|
171
|
+
encryptedAmountHash: confidentialPayload.authorization.encryptedAmountHash
|
|
172
|
+
},
|
|
173
|
+
signature: confidentialPayload.signature
|
|
174
|
+
});
|
|
175
|
+
if (!isValidSignature) {
|
|
176
|
+
return {
|
|
177
|
+
isValid: false,
|
|
178
|
+
invalidReason: "invalid_signature",
|
|
179
|
+
payer: confidentialPayload.authorization.holder
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (this.checkUsedNonces) {
|
|
183
|
+
const used = await this.config.signer.readContract({
|
|
184
|
+
address: getAddress(requirements.asset),
|
|
185
|
+
abi: confidentialTokenAbi,
|
|
186
|
+
functionName: "usedNonces",
|
|
187
|
+
args: [confidentialPayload.authorization.holder, confidentialPayload.authorization.nonce]
|
|
188
|
+
});
|
|
189
|
+
if (used) {
|
|
190
|
+
return {
|
|
191
|
+
isValid: false,
|
|
192
|
+
invalidReason: "nonce_already_used",
|
|
193
|
+
payer: confidentialPayload.authorization.holder
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
isValid: true,
|
|
199
|
+
payer: confidentialPayload.authorization.holder
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async settle(payload, requirements) {
|
|
203
|
+
const valid = await this.verify(payload, requirements);
|
|
204
|
+
const confidentialPayload = payload.payload;
|
|
205
|
+
if (!valid.isValid) {
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
errorReason: valid.invalidReason ?? "invalid_payment",
|
|
209
|
+
payer: confidentialPayload.authorization.holder,
|
|
210
|
+
transaction: "",
|
|
211
|
+
network: requirements.network
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const txHash = await this.config.signer.writeContract({
|
|
215
|
+
address: getAddress(requirements.asset),
|
|
216
|
+
abi: confidentialTokenAbi,
|
|
217
|
+
functionName: "confidentialTransferWithAuthorization",
|
|
218
|
+
args: [
|
|
219
|
+
confidentialPayload.authorization,
|
|
220
|
+
confidentialPayload.encryptedAmountInput,
|
|
221
|
+
confidentialPayload.inputProof,
|
|
222
|
+
confidentialPayload.signature
|
|
223
|
+
]
|
|
224
|
+
});
|
|
225
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
226
|
+
console.debug("[x402z-facilitator] tx submitted", txHash);
|
|
227
|
+
}
|
|
228
|
+
if (this.waitForReceipt) {
|
|
229
|
+
const receipt = await this.config.signer.waitForTransactionReceipt({
|
|
230
|
+
hash: txHash
|
|
231
|
+
});
|
|
232
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
233
|
+
console.debug("[x402z-facilitator] tx receipt", receipt);
|
|
234
|
+
}
|
|
235
|
+
if (receipt.status !== "success") {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
errorReason: "settlement_failed",
|
|
239
|
+
payer: confidentialPayload.authorization.holder,
|
|
240
|
+
transaction: txHash,
|
|
241
|
+
network: requirements.network
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
success: true,
|
|
247
|
+
payer: confidentialPayload.authorization.holder,
|
|
248
|
+
transaction: txHash,
|
|
249
|
+
network: requirements.network
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/bootstrap.ts
|
|
255
|
+
function requireEnv(key) {
|
|
256
|
+
const value = process.env[key];
|
|
257
|
+
if (!value) {
|
|
258
|
+
throw new Error(`Missing required env var: ${key}`);
|
|
259
|
+
}
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
function createConfidentialFacilitatorFromEnv() {
|
|
263
|
+
const privateKey = requireEnv("FACILITATOR_EVM_PRIVATE_KEY");
|
|
264
|
+
const chainId = Number(process.env.FACILITATOR_EVM_CHAIN_ID ?? "11155111");
|
|
265
|
+
const rpcUrl = requireEnv("FACILITATOR_EVM_RPC_URL");
|
|
266
|
+
const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
|
|
267
|
+
const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
|
|
268
|
+
const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
|
|
269
|
+
const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
|
|
270
|
+
const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
|
|
271
|
+
const debugEnabled = process.env.X402Z_DEBUG === "1";
|
|
272
|
+
const account = privateKeyToAccount(privateKey);
|
|
273
|
+
if (debugEnabled) {
|
|
274
|
+
console.debug("[x402z-facilitator] config", {
|
|
275
|
+
chainId,
|
|
276
|
+
networks,
|
|
277
|
+
rpcUrl,
|
|
278
|
+
waitForReceipt,
|
|
279
|
+
receipt: {
|
|
280
|
+
confirmations: receiptConfirmations,
|
|
281
|
+
timeoutMs: receiptTimeoutMs,
|
|
282
|
+
pollingIntervalMs: receiptPollingIntervalMs
|
|
283
|
+
},
|
|
284
|
+
address: account.address
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const client = createWalletClient({
|
|
288
|
+
account,
|
|
289
|
+
chain: {
|
|
290
|
+
id: chainId,
|
|
291
|
+
name: "custom",
|
|
292
|
+
nativeCurrency: { name: "native", symbol: "NATIVE", decimals: 18 },
|
|
293
|
+
rpcUrls: { default: { http: [rpcUrl] } }
|
|
294
|
+
},
|
|
295
|
+
transport: http(rpcUrl)
|
|
296
|
+
}).extend(publicActions);
|
|
297
|
+
const signer = toFacilitatorEvmSigner({
|
|
298
|
+
address: account.address,
|
|
299
|
+
readContract: (args) => client.readContract({
|
|
300
|
+
...args,
|
|
301
|
+
args: args.args || []
|
|
302
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
303
|
+
}),
|
|
304
|
+
verifyTypedData: (args) => client.verifyTypedData(args),
|
|
305
|
+
writeContract: (args) => client.writeContract({
|
|
306
|
+
...args,
|
|
307
|
+
args: args.args || []
|
|
308
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
309
|
+
}),
|
|
310
|
+
sendTransaction: (args) => (
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
312
|
+
client.sendTransaction({ to: args.to, data: args.data })
|
|
313
|
+
),
|
|
314
|
+
waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
|
|
315
|
+
getCode: (args) => client.getCode(args)
|
|
316
|
+
});
|
|
317
|
+
const facilitator = new x402Facilitator();
|
|
318
|
+
for (const network of networks) {
|
|
319
|
+
facilitator.register(
|
|
320
|
+
network,
|
|
321
|
+
new ConfidentialEvmFacilitator({
|
|
322
|
+
signer,
|
|
323
|
+
waitForReceipt,
|
|
324
|
+
receipt: {
|
|
325
|
+
confirmations: receiptConfirmations,
|
|
326
|
+
timeoutMs: receiptTimeoutMs,
|
|
327
|
+
pollingIntervalMs: receiptPollingIntervalMs
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return facilitator;
|
|
333
|
+
}
|
|
334
|
+
function startConfidentialFacilitator() {
|
|
335
|
+
const facilitator = createConfidentialFacilitatorFromEnv();
|
|
336
|
+
const port = Number(process.env.FACILITATOR_PORT ?? "8040");
|
|
337
|
+
const server = createFacilitatorService({ facilitator, port });
|
|
338
|
+
server.listen(port);
|
|
339
|
+
return { port };
|
|
340
|
+
}
|
|
341
|
+
if (__require.main === module) {
|
|
342
|
+
const { port } = startConfidentialFacilitator();
|
|
343
|
+
console.log(`Confidential facilitator listening on :${port}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export {
|
|
347
|
+
createFacilitatorService,
|
|
348
|
+
startFacilitatorService,
|
|
349
|
+
ConfidentialEvmFacilitator,
|
|
350
|
+
createConfidentialFacilitatorFromEnv,
|
|
351
|
+
startConfidentialFacilitator
|
|
352
|
+
};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { SchemeNetworkFacilitator, PaymentPayload, PaymentRequirements, VerifyResponse, SettleResponse, Network } from '@x402/core/types';
|
|
2
|
+
import { FacilitatorEvmSigner } from '@x402/evm';
|
|
3
|
+
import { x402Facilitator } from '@x402/core/facilitator';
|
|
4
|
+
import * as http from 'http';
|
|
5
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
|
+
|
|
7
|
+
type ConfidentialFacilitatorConfig = {
|
|
8
|
+
signer: FacilitatorEvmSigner;
|
|
9
|
+
hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
|
|
10
|
+
checkUsedNonces?: boolean;
|
|
11
|
+
clock?: () => number;
|
|
12
|
+
waitForReceipt?: boolean;
|
|
13
|
+
receipt?: {
|
|
14
|
+
confirmations?: number;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
pollingIntervalMs?: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
declare class ConfidentialEvmFacilitator implements SchemeNetworkFacilitator {
|
|
20
|
+
private readonly config;
|
|
21
|
+
readonly scheme = "erc7984-mind-v1";
|
|
22
|
+
readonly caipFamily = "eip155:*";
|
|
23
|
+
private readonly hashFn;
|
|
24
|
+
private readonly clock;
|
|
25
|
+
private readonly checkUsedNonces;
|
|
26
|
+
private readonly waitForReceipt;
|
|
27
|
+
private readonly receiptOptions?;
|
|
28
|
+
constructor(config: ConfidentialFacilitatorConfig);
|
|
29
|
+
getExtra(_: string): Record<string, unknown> | undefined;
|
|
30
|
+
getSigners(_: string): string[];
|
|
31
|
+
verify(payload: PaymentPayload, requirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
32
|
+
settle(payload: PaymentPayload, requirements: PaymentRequirements): Promise<SettleResponse>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type ConfidentialFacilitatorRegisterConfig = ConfidentialFacilitatorConfig & {
|
|
36
|
+
networks?: Network[];
|
|
37
|
+
};
|
|
38
|
+
declare function registerConfidentialEvmScheme(facilitator: x402Facilitator, config: ConfidentialFacilitatorRegisterConfig): x402Facilitator;
|
|
39
|
+
|
|
40
|
+
type FacilitatorServiceConfig = {
|
|
41
|
+
facilitator: x402Facilitator;
|
|
42
|
+
port?: number;
|
|
43
|
+
};
|
|
44
|
+
declare function createFacilitatorService(config: FacilitatorServiceConfig): http.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
45
|
+
declare function startFacilitatorService(config: FacilitatorServiceConfig): {
|
|
46
|
+
port: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { ConfidentialEvmFacilitator, type ConfidentialFacilitatorConfig, type ConfidentialFacilitatorRegisterConfig, type FacilitatorServiceConfig, createFacilitatorService, registerConfidentialEvmScheme, startFacilitatorService };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { SchemeNetworkFacilitator, PaymentPayload, PaymentRequirements, VerifyResponse, SettleResponse, Network } from '@x402/core/types';
|
|
2
|
+
import { FacilitatorEvmSigner } from '@x402/evm';
|
|
3
|
+
import { x402Facilitator } from '@x402/core/facilitator';
|
|
4
|
+
import * as http from 'http';
|
|
5
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
|
+
|
|
7
|
+
type ConfidentialFacilitatorConfig = {
|
|
8
|
+
signer: FacilitatorEvmSigner;
|
|
9
|
+
hashEncryptedAmountInput?: (encryptedAmountInput: `0x${string}`) => `0x${string}`;
|
|
10
|
+
checkUsedNonces?: boolean;
|
|
11
|
+
clock?: () => number;
|
|
12
|
+
waitForReceipt?: boolean;
|
|
13
|
+
receipt?: {
|
|
14
|
+
confirmations?: number;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
pollingIntervalMs?: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
declare class ConfidentialEvmFacilitator implements SchemeNetworkFacilitator {
|
|
20
|
+
private readonly config;
|
|
21
|
+
readonly scheme = "erc7984-mind-v1";
|
|
22
|
+
readonly caipFamily = "eip155:*";
|
|
23
|
+
private readonly hashFn;
|
|
24
|
+
private readonly clock;
|
|
25
|
+
private readonly checkUsedNonces;
|
|
26
|
+
private readonly waitForReceipt;
|
|
27
|
+
private readonly receiptOptions?;
|
|
28
|
+
constructor(config: ConfidentialFacilitatorConfig);
|
|
29
|
+
getExtra(_: string): Record<string, unknown> | undefined;
|
|
30
|
+
getSigners(_: string): string[];
|
|
31
|
+
verify(payload: PaymentPayload, requirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
32
|
+
settle(payload: PaymentPayload, requirements: PaymentRequirements): Promise<SettleResponse>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type ConfidentialFacilitatorRegisterConfig = ConfidentialFacilitatorConfig & {
|
|
36
|
+
networks?: Network[];
|
|
37
|
+
};
|
|
38
|
+
declare function registerConfidentialEvmScheme(facilitator: x402Facilitator, config: ConfidentialFacilitatorRegisterConfig): x402Facilitator;
|
|
39
|
+
|
|
40
|
+
type FacilitatorServiceConfig = {
|
|
41
|
+
facilitator: x402Facilitator;
|
|
42
|
+
port?: number;
|
|
43
|
+
};
|
|
44
|
+
declare function createFacilitatorService(config: FacilitatorServiceConfig): http.Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
45
|
+
declare function startFacilitatorService(config: FacilitatorServiceConfig): {
|
|
46
|
+
port: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { ConfidentialEvmFacilitator, type ConfidentialFacilitatorConfig, type ConfidentialFacilitatorRegisterConfig, type FacilitatorServiceConfig, createFacilitatorService, registerConfidentialEvmScheme, startFacilitatorService };
|