mrmainspring 0.1.0
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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/audit/service.d.ts +7 -0
- package/dist/audit/service.js +98 -0
- package/dist/audit/store.d.ts +7 -0
- package/dist/audit/store.js +37 -0
- package/dist/audit/supabase-store.d.ts +9 -0
- package/dist/audit/supabase-store.js +22 -0
- package/dist/audit/types.d.ts +31 -0
- package/dist/audit/types.js +1 -0
- package/dist/casper/anchorClient.d.ts +99 -0
- package/dist/casper/anchorClient.js +412 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.js +215 -0
- package/dist/env-file.d.ts +1 -0
- package/dist/env-file.js +51 -0
- package/dist/grimoire/service.d.ts +13 -0
- package/dist/grimoire/service.js +199 -0
- package/dist/grimoire/store.d.ts +10 -0
- package/dist/grimoire/store.js +64 -0
- package/dist/grimoire/supabase-store.d.ts +13 -0
- package/dist/grimoire/supabase-store.js +50 -0
- package/dist/grimoire/types.d.ts +60 -0
- package/dist/grimoire/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -0
- package/dist/mcp/auditTools.d.ts +3 -0
- package/dist/mcp/auditTools.js +13 -0
- package/dist/mcp/grimoireTools.d.ts +3 -0
- package/dist/mcp/grimoireTools.js +91 -0
- package/dist/mcp/jsonResult.d.ts +2 -0
- package/dist/mcp/jsonResult.js +10 -0
- package/dist/mcp/memoryTools.d.ts +3 -0
- package/dist/mcp/memoryTools.js +73 -0
- package/dist/mcp/paymentTools.d.ts +3 -0
- package/dist/mcp/paymentTools.js +33 -0
- package/dist/memory/canonical.d.ts +4 -0
- package/dist/memory/canonical.js +49 -0
- package/dist/memory/hash.d.ts +1 -0
- package/dist/memory/hash.js +4 -0
- package/dist/memory/service.d.ts +37 -0
- package/dist/memory/service.js +175 -0
- package/dist/memory/store.d.ts +8 -0
- package/dist/memory/store.js +49 -0
- package/dist/memory/supabase-store.d.ts +10 -0
- package/dist/memory/supabase-store.js +30 -0
- package/dist/memory/types.d.ts +56 -0
- package/dist/memory/types.js +7 -0
- package/dist/payments/service.d.ts +26 -0
- package/dist/payments/service.js +613 -0
- package/dist/payments/store.d.ts +10 -0
- package/dist/payments/store.js +64 -0
- package/dist/payments/supabase-store.d.ts +13 -0
- package/dist/payments/supabase-store.js +51 -0
- package/dist/payments/types.d.ts +101 -0
- package/dist/payments/types.js +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +68 -0
- package/dist/storage/json-file-store.d.ts +17 -0
- package/dist/storage/json-file-store.js +87 -0
- package/dist/storage/store-factory.d.ts +12 -0
- package/dist/storage/store-factory.js +26 -0
- package/dist/storage/supabase-rest.d.ts +26 -0
- package/dist/storage/supabase-rest.js +85 -0
- package/dist/x402/client.d.ts +44 -0
- package/dist/x402/client.js +95 -0
- package/dist/x402/facilitator.d.ts +84 -0
- package/dist/x402/facilitator.js +800 -0
- package/dist/x402/readiness.d.ts +55 -0
- package/dist/x402/readiness.js +433 -0
- package/dist/x402/redaction.d.ts +1 -0
- package/dist/x402/redaction.js +30 -0
- package/dist/x402/resource.d.ts +69 -0
- package/dist/x402/resource.js +325 -0
- package/dist/x402/settlement.d.ts +176 -0
- package/dist/x402/settlement.js +1210 -0
- package/dist/x402/signer.d.ts +71 -0
- package/dist/x402/signer.js +616 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1210 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { extractCasperTransactionHash } from "../casper/anchorClient.js";
|
|
3
|
+
import { canonicalizeJson, toJsonObject, toJsonValue } from "../memory/canonical.js";
|
|
4
|
+
import { sha256Hex } from "../memory/hash.js";
|
|
5
|
+
import { redactX402Value } from "./redaction.js";
|
|
6
|
+
import { validateX402PaymentPayload, verifyX402SettlementResponse } from "./readiness.js";
|
|
7
|
+
export class DisabledX402SettlementProvider {
|
|
8
|
+
blocker;
|
|
9
|
+
constructor(blocker = "x402_settlement_disabled") {
|
|
10
|
+
this.blocker = blocker;
|
|
11
|
+
}
|
|
12
|
+
async settle(input) {
|
|
13
|
+
return {
|
|
14
|
+
status: "unavailable",
|
|
15
|
+
blocker: this.blocker,
|
|
16
|
+
signed_payload_hash: null,
|
|
17
|
+
response_status: null,
|
|
18
|
+
casper_transaction_hash: null,
|
|
19
|
+
receipt_json: settlementReceiptJson({
|
|
20
|
+
status: "settlement_unavailable",
|
|
21
|
+
blocker: this.blocker,
|
|
22
|
+
payment_id: input.payment_id,
|
|
23
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
24
|
+
facilitator_url: input.facilitator_url
|
|
25
|
+
})
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class DisabledX402SigningProvider {
|
|
30
|
+
async sign() {
|
|
31
|
+
return {
|
|
32
|
+
signed: false,
|
|
33
|
+
blocker: "x402_signing_provider_not_configured"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class HttpX402SigningProvider {
|
|
38
|
+
config;
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.config = config;
|
|
41
|
+
}
|
|
42
|
+
async sign(input) {
|
|
43
|
+
if (!this.config.signerUrl) {
|
|
44
|
+
return {
|
|
45
|
+
signed: false,
|
|
46
|
+
blocker: "x402_signing_provider_not_configured"
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
response = await postSignerJson(this.config, {
|
|
52
|
+
payment_id: input.payment_id,
|
|
53
|
+
facilitator_url: input.facilitator_url,
|
|
54
|
+
method: input.method,
|
|
55
|
+
url: input.url,
|
|
56
|
+
selected_requirement: input.selected_requirement,
|
|
57
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
58
|
+
policy_hash: input.policy_hash
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return {
|
|
63
|
+
signed: false,
|
|
64
|
+
blocker: "x402_signer_request_failed"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const text = await response.text();
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
return {
|
|
70
|
+
signed: false,
|
|
71
|
+
blocker: "x402_signer_request_failed"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const body = parseJsonOrRaw(text);
|
|
75
|
+
const signedPayload = extractSignedPayload(body);
|
|
76
|
+
if (!signedPayload) {
|
|
77
|
+
return {
|
|
78
|
+
signed: false,
|
|
79
|
+
blocker: "x402_signer_response_invalid"
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const payloadApproval = validateX402PaymentPayload(signedPayload, input.selected_requirement_hash, {
|
|
83
|
+
now: this.config.now?.() ?? new Date(),
|
|
84
|
+
maxValiditySeconds: this.config.maxValiditySeconds ?? null
|
|
85
|
+
});
|
|
86
|
+
if (!payloadApproval.approved) {
|
|
87
|
+
return {
|
|
88
|
+
signed: false,
|
|
89
|
+
blocker: "x402_signer_response_invalid"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
signed: true,
|
|
94
|
+
signed_payload: signedPayload,
|
|
95
|
+
signed_payload_hash: createSignedPayloadHash(signedPayload)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export class CasperCliX402SettlementProvider {
|
|
100
|
+
signer;
|
|
101
|
+
config;
|
|
102
|
+
commandRunner;
|
|
103
|
+
constructor(signer, config, commandRunner = runCasperCommand) {
|
|
104
|
+
this.signer = signer;
|
|
105
|
+
this.config = config;
|
|
106
|
+
this.commandRunner = commandRunner;
|
|
107
|
+
}
|
|
108
|
+
async settle(input) {
|
|
109
|
+
const config = validateX402CasperCliSettlementConfig(this.config);
|
|
110
|
+
if (!config) {
|
|
111
|
+
return {
|
|
112
|
+
status: "unavailable",
|
|
113
|
+
blocker: "x402_casper_settlement_not_configured",
|
|
114
|
+
signed_payload_hash: null,
|
|
115
|
+
response_status: null,
|
|
116
|
+
casper_transaction_hash: null,
|
|
117
|
+
receipt_json: settlementReceiptJson({
|
|
118
|
+
status: "settlement_unavailable",
|
|
119
|
+
blocker: "x402_casper_settlement_not_configured",
|
|
120
|
+
payment_id: input.payment_id,
|
|
121
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
122
|
+
facilitator_url: input.facilitator_url
|
|
123
|
+
})
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (!config.submissionEnabled) {
|
|
127
|
+
return {
|
|
128
|
+
status: "unavailable",
|
|
129
|
+
blocker: "x402_casper_transaction_submission_disabled",
|
|
130
|
+
signed_payload_hash: null,
|
|
131
|
+
response_status: null,
|
|
132
|
+
casper_transaction_hash: null,
|
|
133
|
+
receipt_json: settlementReceiptJson({
|
|
134
|
+
status: "settlement_unavailable",
|
|
135
|
+
blocker: "x402_casper_transaction_submission_disabled",
|
|
136
|
+
payment_id: input.payment_id,
|
|
137
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
138
|
+
facilitator_url: input.facilitator_url
|
|
139
|
+
})
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const signed = await this.signer.sign(input);
|
|
143
|
+
if (!signed.signed) {
|
|
144
|
+
return {
|
|
145
|
+
status: "unavailable",
|
|
146
|
+
blocker: signed.blocker,
|
|
147
|
+
signed_payload_hash: null,
|
|
148
|
+
response_status: null,
|
|
149
|
+
casper_transaction_hash: null,
|
|
150
|
+
receipt_json: settlementReceiptJson({
|
|
151
|
+
status: "settlement_unavailable",
|
|
152
|
+
blocker: signed.blocker,
|
|
153
|
+
payment_id: input.payment_id,
|
|
154
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
155
|
+
facilitator_url: input.facilitator_url
|
|
156
|
+
})
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const payloadApproval = validateX402PaymentPayload(signed.signed_payload, input.selected_requirement_hash, {
|
|
160
|
+
now: config.now?.() ?? new Date(),
|
|
161
|
+
maxValiditySeconds: config.maxValiditySeconds ?? null
|
|
162
|
+
});
|
|
163
|
+
if (!payloadApproval.approved) {
|
|
164
|
+
return casperSettlementFailedOutcome({
|
|
165
|
+
input,
|
|
166
|
+
blocker: "x402_casper_payment_payload_invalid",
|
|
167
|
+
signedPayloadHash: signed.signed_payload_hash,
|
|
168
|
+
transactionHash: null,
|
|
169
|
+
receipt: {
|
|
170
|
+
status: "casper_payment_payload_invalid",
|
|
171
|
+
reason: payloadApproval.reason
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const payment = extractCasperSettlementPayment(signed.signed_payload, input.selected_requirement, input.selected_requirement_hash, config);
|
|
176
|
+
if (!payment) {
|
|
177
|
+
return casperSettlementFailedOutcome({
|
|
178
|
+
input,
|
|
179
|
+
blocker: "x402_casper_payment_payload_invalid",
|
|
180
|
+
signedPayloadHash: signed.signed_payload_hash,
|
|
181
|
+
transactionHash: null,
|
|
182
|
+
receipt: {
|
|
183
|
+
status: "casper_payment_payload_invalid"
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const invocation = buildCasperX402SettlementCommand(config, payment);
|
|
188
|
+
let putResult;
|
|
189
|
+
try {
|
|
190
|
+
putResult = await this.commandRunner(invocation.command, invocation.args);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return casperSettlementFailedOutcome({
|
|
194
|
+
input,
|
|
195
|
+
blocker: "x402_casper_transaction_submission_failed",
|
|
196
|
+
signedPayloadHash: signed.signed_payload_hash,
|
|
197
|
+
transactionHash: null,
|
|
198
|
+
receipt: {
|
|
199
|
+
status: "casper_transaction_submission_failed",
|
|
200
|
+
command: commandReceipt(invocation)
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (putResult.exitCode !== 0) {
|
|
205
|
+
return casperSettlementFailedOutcome({
|
|
206
|
+
input,
|
|
207
|
+
blocker: "x402_casper_transaction_submission_failed",
|
|
208
|
+
signedPayloadHash: signed.signed_payload_hash,
|
|
209
|
+
transactionHash: null,
|
|
210
|
+
receipt: {
|
|
211
|
+
status: "casper_transaction_submission_failed",
|
|
212
|
+
command: commandReceipt(invocation),
|
|
213
|
+
result: commandResultReceipt(putResult)
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const transactionHash = extractCasperTransactionHash(putResult);
|
|
218
|
+
if (!transactionHash) {
|
|
219
|
+
return casperSettlementFailedOutcome({
|
|
220
|
+
input,
|
|
221
|
+
blocker: "x402_casper_transaction_hash_missing",
|
|
222
|
+
signedPayloadHash: signed.signed_payload_hash,
|
|
223
|
+
transactionHash: null,
|
|
224
|
+
receipt: {
|
|
225
|
+
status: "casper_transaction_hash_missing",
|
|
226
|
+
command: commandReceipt(invocation),
|
|
227
|
+
result: commandResultReceipt(putResult)
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const execution = await this.waitForExecution(config, transactionHash);
|
|
232
|
+
if (execution.status === "lookup_failed") {
|
|
233
|
+
return casperSettlementFailedOutcome({
|
|
234
|
+
input,
|
|
235
|
+
blocker: "x402_casper_transaction_lookup_failed",
|
|
236
|
+
signedPayloadHash: signed.signed_payload_hash,
|
|
237
|
+
transactionHash,
|
|
238
|
+
receipt: {
|
|
239
|
+
status: "casper_transaction_lookup_failed",
|
|
240
|
+
transactionHash,
|
|
241
|
+
result: commandResultReceipt(execution.result)
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (execution.status === "not_executed") {
|
|
246
|
+
return casperSettlementFailedOutcome({
|
|
247
|
+
input,
|
|
248
|
+
blocker: "x402_casper_transaction_execution_unavailable",
|
|
249
|
+
signedPayloadHash: signed.signed_payload_hash,
|
|
250
|
+
transactionHash,
|
|
251
|
+
receipt: {
|
|
252
|
+
status: "casper_transaction_execution_unavailable",
|
|
253
|
+
transactionHash,
|
|
254
|
+
receipt: execution.receipt
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (execution.status === "failed") {
|
|
259
|
+
return casperSettlementFailedOutcome({
|
|
260
|
+
input,
|
|
261
|
+
blocker: "x402_casper_transaction_execution_failed",
|
|
262
|
+
signedPayloadHash: signed.signed_payload_hash,
|
|
263
|
+
transactionHash,
|
|
264
|
+
receipt: {
|
|
265
|
+
status: "casper_transaction_execution_failed",
|
|
266
|
+
transactionHash,
|
|
267
|
+
error_message: execution.errorMessage,
|
|
268
|
+
receipt: execution.receipt
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
status: "settled",
|
|
274
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
275
|
+
response_status: 200,
|
|
276
|
+
casper_transaction_hash: transactionHash,
|
|
277
|
+
receipt_json: settlementReceiptJson({
|
|
278
|
+
success: true,
|
|
279
|
+
status: "settled",
|
|
280
|
+
transactionHash,
|
|
281
|
+
transaction: transactionHash,
|
|
282
|
+
network: payment.network,
|
|
283
|
+
asset: settlementAssetReceipt(payment),
|
|
284
|
+
amount: payment.amount,
|
|
285
|
+
payer: settlementPayerReceipt(payment, signed.signed_payload),
|
|
286
|
+
payTo: settlementPayeeReceipt(payment),
|
|
287
|
+
payment_id: input.payment_id,
|
|
288
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
289
|
+
nonce_hash: sha256Hex(payment.nonce),
|
|
290
|
+
execution: execution.receipt
|
|
291
|
+
})
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async waitForExecution(config, transactionHash) {
|
|
295
|
+
const timeoutMs = config.confirmationTimeoutMs ?? 120_000;
|
|
296
|
+
const pollIntervalMs = config.confirmationPollIntervalMs ?? 2_000;
|
|
297
|
+
const deadline = Date.now() + timeoutMs;
|
|
298
|
+
for (;;) {
|
|
299
|
+
const invocation = buildCasperGetTransactionCommand(config, transactionHash);
|
|
300
|
+
let result;
|
|
301
|
+
try {
|
|
302
|
+
result = await this.commandRunner(invocation.command, invocation.args);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return {
|
|
306
|
+
status: "lookup_failed",
|
|
307
|
+
result: { exitCode: 1, stdout: "", stderr: "casper-client get-transaction failed" }
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (result.exitCode !== 0) {
|
|
311
|
+
return { status: "lookup_failed", result };
|
|
312
|
+
}
|
|
313
|
+
const execution = verifyCasperTransactionExecution(result);
|
|
314
|
+
if (execution.status !== "not_executed") {
|
|
315
|
+
return execution;
|
|
316
|
+
}
|
|
317
|
+
if (Date.now() >= deadline) {
|
|
318
|
+
return execution;
|
|
319
|
+
}
|
|
320
|
+
await delay(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export class FacilitatorX402SettlementProvider {
|
|
325
|
+
signer;
|
|
326
|
+
postJson;
|
|
327
|
+
config;
|
|
328
|
+
constructor(signer, postJson = postJsonWithFetch, config = {}) {
|
|
329
|
+
this.signer = signer;
|
|
330
|
+
this.postJson = postJson;
|
|
331
|
+
this.config = config;
|
|
332
|
+
}
|
|
333
|
+
async settle(input) {
|
|
334
|
+
const signed = await this.signer.sign(input);
|
|
335
|
+
if (!signed.signed) {
|
|
336
|
+
return {
|
|
337
|
+
status: "unavailable",
|
|
338
|
+
blocker: signed.blocker,
|
|
339
|
+
signed_payload_hash: null,
|
|
340
|
+
response_status: null,
|
|
341
|
+
casper_transaction_hash: null,
|
|
342
|
+
receipt_json: settlementReceiptJson({
|
|
343
|
+
status: "settlement_unavailable",
|
|
344
|
+
blocker: signed.blocker,
|
|
345
|
+
payment_id: input.payment_id,
|
|
346
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
347
|
+
facilitator_url: input.facilitator_url
|
|
348
|
+
})
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const payloadApproval = validateX402PaymentPayload(signed.signed_payload, input.selected_requirement_hash, {
|
|
352
|
+
now: this.config.now?.() ?? new Date(),
|
|
353
|
+
maxValiditySeconds: this.config.maxValiditySeconds ?? null
|
|
354
|
+
});
|
|
355
|
+
if (!payloadApproval.approved) {
|
|
356
|
+
return signerPayloadInvalidOutcome(input);
|
|
357
|
+
}
|
|
358
|
+
const verify = await this.postJson(facilitatorEndpoint(input.facilitator_url, "verify"), {
|
|
359
|
+
paymentPayload: signed.signed_payload,
|
|
360
|
+
paymentRequirements: input.selected_requirement
|
|
361
|
+
});
|
|
362
|
+
const verifyObject = tryJsonObject(verify.body);
|
|
363
|
+
if (!verifyObject) {
|
|
364
|
+
return {
|
|
365
|
+
status: "failed",
|
|
366
|
+
blocker: "x402_facilitator_verify_failed",
|
|
367
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
368
|
+
response_status: verify.status,
|
|
369
|
+
casper_transaction_hash: null,
|
|
370
|
+
receipt_json: settlementReceiptJson({
|
|
371
|
+
status: "verify_failed",
|
|
372
|
+
payment_id: input.payment_id,
|
|
373
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
374
|
+
facilitator_url: input.facilitator_url,
|
|
375
|
+
verify_response: verify.body
|
|
376
|
+
})
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const verifyAccepted = verify.status >= 200 &&
|
|
380
|
+
verify.status < 300 &&
|
|
381
|
+
(verifyObject.valid === true || verifyObject.success === true);
|
|
382
|
+
if (!verifyAccepted) {
|
|
383
|
+
return {
|
|
384
|
+
status: "failed",
|
|
385
|
+
blocker: "x402_facilitator_verify_failed",
|
|
386
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
387
|
+
response_status: verify.status,
|
|
388
|
+
casper_transaction_hash: null,
|
|
389
|
+
receipt_json: settlementReceiptJson({
|
|
390
|
+
status: "verify_failed",
|
|
391
|
+
payment_id: input.payment_id,
|
|
392
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
393
|
+
facilitator_url: input.facilitator_url,
|
|
394
|
+
verify_response: verifyObject
|
|
395
|
+
})
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const settle = await this.postJson(facilitatorEndpoint(input.facilitator_url, "settle"), {
|
|
399
|
+
paymentPayload: signed.signed_payload,
|
|
400
|
+
paymentRequirements: input.selected_requirement
|
|
401
|
+
});
|
|
402
|
+
const settlement = verifyX402SettlementResponse(settle.body, {
|
|
403
|
+
selectedRequirement: input.selected_requirement,
|
|
404
|
+
signedPayload: signed.signed_payload
|
|
405
|
+
});
|
|
406
|
+
if (!settlement.settled) {
|
|
407
|
+
return {
|
|
408
|
+
status: "failed",
|
|
409
|
+
blocker: settle.status >= 200 && settle.status < 300
|
|
410
|
+
? "x402_facilitator_settlement_not_verified"
|
|
411
|
+
: "x402_facilitator_settle_failed",
|
|
412
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
413
|
+
response_status: settle.status,
|
|
414
|
+
casper_transaction_hash: null,
|
|
415
|
+
receipt_json: settlement.receipt_json ??
|
|
416
|
+
settlementReceiptJson({
|
|
417
|
+
status: "settle_failed",
|
|
418
|
+
payment_id: input.payment_id,
|
|
419
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
420
|
+
facilitator_url: input.facilitator_url,
|
|
421
|
+
settle_response: settle.body
|
|
422
|
+
})
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
status: "settled",
|
|
427
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
428
|
+
response_status: settle.status,
|
|
429
|
+
casper_transaction_hash: settlement.transaction_hash,
|
|
430
|
+
receipt_json: settlement.receipt_json
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
export class ResourceRetryX402SettlementProvider {
|
|
435
|
+
signer;
|
|
436
|
+
resourceFetch;
|
|
437
|
+
paymentHeaderName;
|
|
438
|
+
now;
|
|
439
|
+
maxValiditySeconds;
|
|
440
|
+
constructor(signer, resourceFetch = fetchPaidResource, config = {}) {
|
|
441
|
+
this.signer = signer;
|
|
442
|
+
this.resourceFetch = resourceFetch;
|
|
443
|
+
this.paymentHeaderName = config.paymentHeaderName ?? "PAYMENT-SIGNATURE";
|
|
444
|
+
this.now = config.now ?? (() => new Date());
|
|
445
|
+
this.maxValiditySeconds = config.maxValiditySeconds ?? null;
|
|
446
|
+
}
|
|
447
|
+
async settle(input) {
|
|
448
|
+
const signed = await this.signer.sign(input);
|
|
449
|
+
if (!signed.signed) {
|
|
450
|
+
return {
|
|
451
|
+
status: "unavailable",
|
|
452
|
+
blocker: signed.blocker,
|
|
453
|
+
signed_payload_hash: null,
|
|
454
|
+
response_status: null,
|
|
455
|
+
casper_transaction_hash: null,
|
|
456
|
+
receipt_json: settlementReceiptJson({
|
|
457
|
+
status: "settlement_unavailable",
|
|
458
|
+
blocker: signed.blocker,
|
|
459
|
+
payment_id: input.payment_id,
|
|
460
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
461
|
+
facilitator_url: input.facilitator_url
|
|
462
|
+
})
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const payloadApproval = validateX402PaymentPayload(signed.signed_payload, input.selected_requirement_hash, {
|
|
466
|
+
now: this.now(),
|
|
467
|
+
maxValiditySeconds: this.maxValiditySeconds
|
|
468
|
+
});
|
|
469
|
+
if (!payloadApproval.approved) {
|
|
470
|
+
return signerPayloadInvalidOutcome(input);
|
|
471
|
+
}
|
|
472
|
+
const paymentHeader = encodePaymentHeader(signed.signed_payload);
|
|
473
|
+
let paidResponse;
|
|
474
|
+
try {
|
|
475
|
+
paidResponse = await this.resourceFetch(input.url, {
|
|
476
|
+
method: input.method,
|
|
477
|
+
headers: {
|
|
478
|
+
[this.paymentHeaderName]: paymentHeader
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
return {
|
|
484
|
+
status: "failed",
|
|
485
|
+
blocker: "x402_paid_resource_fetch_failed",
|
|
486
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
487
|
+
response_status: null,
|
|
488
|
+
casper_transaction_hash: null,
|
|
489
|
+
receipt_json: settlementReceiptJson({
|
|
490
|
+
status: "paid_resource_fetch_failed",
|
|
491
|
+
payment_id: input.payment_id,
|
|
492
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
493
|
+
facilitator_url: input.facilitator_url
|
|
494
|
+
})
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
const paymentResponse = parsePaymentResponseHeader(paidResponse.headers);
|
|
498
|
+
const settlement = verifyX402SettlementResponse(paymentResponse, {
|
|
499
|
+
selectedRequirement: input.selected_requirement,
|
|
500
|
+
signedPayload: signed.signed_payload
|
|
501
|
+
});
|
|
502
|
+
const bodyHash = sha256Hex(paidResponse.bodyText);
|
|
503
|
+
if (!paidResponseOk(paidResponse.status)) {
|
|
504
|
+
return {
|
|
505
|
+
status: "failed",
|
|
506
|
+
blocker: paidResponse.status === 402
|
|
507
|
+
? "x402_paid_resource_still_requires_payment"
|
|
508
|
+
: "x402_paid_resource_fetch_failed",
|
|
509
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
510
|
+
response_status: paidResponse.status,
|
|
511
|
+
casper_transaction_hash: null,
|
|
512
|
+
receipt_json: settlementReceiptJson({
|
|
513
|
+
status: "paid_resource_failed",
|
|
514
|
+
payment_id: input.payment_id,
|
|
515
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
516
|
+
facilitator_url: input.facilitator_url,
|
|
517
|
+
resource_response_status: paidResponse.status,
|
|
518
|
+
resource_response_hash: bodyHash,
|
|
519
|
+
payment_response: paymentResponse
|
|
520
|
+
})
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
if (!settlement.settled) {
|
|
524
|
+
return {
|
|
525
|
+
status: "failed",
|
|
526
|
+
blocker: "x402_paid_resource_settlement_not_verified",
|
|
527
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
528
|
+
response_status: paidResponse.status,
|
|
529
|
+
casper_transaction_hash: null,
|
|
530
|
+
receipt_json: settlementReceiptJson({
|
|
531
|
+
status: "paid_resource_settlement_not_verified",
|
|
532
|
+
reason: settlement.reason,
|
|
533
|
+
payment_id: input.payment_id,
|
|
534
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
535
|
+
facilitator_url: input.facilitator_url,
|
|
536
|
+
resource_response_status: paidResponse.status,
|
|
537
|
+
resource_response_hash: bodyHash,
|
|
538
|
+
payment_response: paymentResponse
|
|
539
|
+
})
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
status: "settled",
|
|
544
|
+
signed_payload_hash: signed.signed_payload_hash,
|
|
545
|
+
response_status: paidResponse.status,
|
|
546
|
+
casper_transaction_hash: settlement.transaction_hash,
|
|
547
|
+
receipt_json: settlementReceiptJson({
|
|
548
|
+
status: "settled",
|
|
549
|
+
payment_id: input.payment_id,
|
|
550
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
551
|
+
facilitator_url: input.facilitator_url,
|
|
552
|
+
resource_response_status: paidResponse.status,
|
|
553
|
+
resource_response_hash: bodyHash,
|
|
554
|
+
payment_response: JSON.parse(settlement.receipt_json)
|
|
555
|
+
})
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
export function createSignedPayloadHash(payload) {
|
|
560
|
+
return sha256Hex(canonicalizeJson(payload));
|
|
561
|
+
}
|
|
562
|
+
export function validateX402CasperCliSettlementConfig(config) {
|
|
563
|
+
if (!config.rpcUrl || !config.accountKeyPath) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
...config,
|
|
568
|
+
rpcUrl: config.rpcUrl,
|
|
569
|
+
accountKeyPath: config.accountKeyPath
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
export function buildCasperX402SettlementCommand(config, payment) {
|
|
573
|
+
if (payment.settlementKind === "native-transfer") {
|
|
574
|
+
return wrapCasperX402Command(config, [
|
|
575
|
+
"put-transaction",
|
|
576
|
+
"transfer",
|
|
577
|
+
"--node-address",
|
|
578
|
+
config.rpcUrl,
|
|
579
|
+
"--chain-name",
|
|
580
|
+
config.networkName,
|
|
581
|
+
"--secret-key",
|
|
582
|
+
config.accountKeyPath,
|
|
583
|
+
"--target",
|
|
584
|
+
payment.target,
|
|
585
|
+
"--transfer-amount",
|
|
586
|
+
payment.amount,
|
|
587
|
+
"--gas-price-tolerance",
|
|
588
|
+
config.gasPriceTolerance,
|
|
589
|
+
"--pricing-mode",
|
|
590
|
+
config.pricingMode,
|
|
591
|
+
"--payment-amount",
|
|
592
|
+
config.paymentAmountMotes,
|
|
593
|
+
"--standard-payment",
|
|
594
|
+
"true"
|
|
595
|
+
]);
|
|
596
|
+
}
|
|
597
|
+
return wrapCasperX402Command(config, [
|
|
598
|
+
"put-transaction",
|
|
599
|
+
"package",
|
|
600
|
+
"--node-address",
|
|
601
|
+
config.rpcUrl,
|
|
602
|
+
"--chain-name",
|
|
603
|
+
config.networkName,
|
|
604
|
+
"--contract-package-hash",
|
|
605
|
+
`hash-${payment.assetPackageHash}`,
|
|
606
|
+
"--session-entry-point",
|
|
607
|
+
"transfer_with_authorization",
|
|
608
|
+
"--gas-price-tolerance",
|
|
609
|
+
config.gasPriceTolerance,
|
|
610
|
+
"--pricing-mode",
|
|
611
|
+
config.pricingMode,
|
|
612
|
+
"--payment-amount",
|
|
613
|
+
config.paymentAmountMotes,
|
|
614
|
+
"--standard-payment",
|
|
615
|
+
"true",
|
|
616
|
+
"--secret-key",
|
|
617
|
+
config.accountKeyPath,
|
|
618
|
+
"--session-arg",
|
|
619
|
+
sessionArg("from", "key", accountKeyArg(payment.from)),
|
|
620
|
+
"--session-arg",
|
|
621
|
+
sessionArg("to", "key", accountKeyArg(payment.to)),
|
|
622
|
+
"--session-arg",
|
|
623
|
+
sessionArg("amount", "u256", payment.amount),
|
|
624
|
+
"--session-arg",
|
|
625
|
+
sessionArg("valid_after", "i64", payment.validAfter),
|
|
626
|
+
"--session-arg",
|
|
627
|
+
sessionArg("valid_before", "i64", payment.validBefore),
|
|
628
|
+
"--session-arg",
|
|
629
|
+
sessionArg("nonce", "byte_array", payment.nonce),
|
|
630
|
+
"--session-arg",
|
|
631
|
+
sessionArg("public_key", "public_key", payment.publicKey),
|
|
632
|
+
"--session-arg",
|
|
633
|
+
sessionArg("signature", "byte_array", payment.signature)
|
|
634
|
+
]);
|
|
635
|
+
}
|
|
636
|
+
export function buildCasperGetTransactionCommand(config, transactionHash) {
|
|
637
|
+
return wrapCasperX402Command(config, [
|
|
638
|
+
"get-transaction",
|
|
639
|
+
"--node-address",
|
|
640
|
+
config.rpcUrl,
|
|
641
|
+
transactionHash
|
|
642
|
+
]);
|
|
643
|
+
}
|
|
644
|
+
export function verifyCasperTransactionExecution(result) {
|
|
645
|
+
const parsed = parseCasperClientOutput(result.stdout) ?? parseCasperClientOutput(result.stderr);
|
|
646
|
+
const receipt = parsed ?? { raw: [result.stdout, result.stderr].filter(Boolean).join("\n") };
|
|
647
|
+
const executionInfo = findExecutionInfo(receipt);
|
|
648
|
+
if (!executionInfo) {
|
|
649
|
+
return {
|
|
650
|
+
status: "not_executed",
|
|
651
|
+
receipt
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const errorMessage = findExecutionErrorMessage(executionInfo);
|
|
655
|
+
if (errorMessage) {
|
|
656
|
+
return {
|
|
657
|
+
status: "failed",
|
|
658
|
+
errorMessage,
|
|
659
|
+
receipt
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
status: "success",
|
|
664
|
+
receipt
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function casperSettlementFailedOutcome(failure) {
|
|
668
|
+
return {
|
|
669
|
+
status: "failed",
|
|
670
|
+
blocker: failure.blocker,
|
|
671
|
+
signed_payload_hash: failure.signedPayloadHash,
|
|
672
|
+
response_status: null,
|
|
673
|
+
casper_transaction_hash: failure.transactionHash,
|
|
674
|
+
receipt_json: settlementReceiptJson({
|
|
675
|
+
payment_id: failure.input.payment_id,
|
|
676
|
+
selected_requirement_hash: failure.input.selected_requirement_hash,
|
|
677
|
+
facilitator_url: failure.input.facilitator_url,
|
|
678
|
+
...(asRecord(failure.receipt) ?? {})
|
|
679
|
+
})
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function signerPayloadInvalidOutcome(input) {
|
|
683
|
+
return {
|
|
684
|
+
status: "unavailable",
|
|
685
|
+
blocker: "x402_signer_response_invalid",
|
|
686
|
+
signed_payload_hash: null,
|
|
687
|
+
response_status: null,
|
|
688
|
+
casper_transaction_hash: null,
|
|
689
|
+
receipt_json: settlementReceiptJson({
|
|
690
|
+
status: "settlement_unavailable",
|
|
691
|
+
blocker: "x402_signer_response_invalid",
|
|
692
|
+
payment_id: input.payment_id,
|
|
693
|
+
selected_requirement_hash: input.selected_requirement_hash,
|
|
694
|
+
facilitator_url: input.facilitator_url
|
|
695
|
+
})
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function extractCasperSettlementPayment(signedPayload, selectedRequirement, selectedRequirementHash, config) {
|
|
699
|
+
const root = asRecord(signedPayload);
|
|
700
|
+
if (!root) {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
const paymentPayload = asRecord(root.payload) ?? root;
|
|
704
|
+
const authorization = asRecord(paymentPayload.authorization) ?? asRecord(root.authorization);
|
|
705
|
+
if (!authorization) {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
const acceptedRequirement = asRecord(root.accepted);
|
|
709
|
+
const scheme = firstString(root, ["scheme"]) ?? firstString(acceptedRequirement, ["scheme"]);
|
|
710
|
+
if (scheme !== "exact") {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
const network = firstString(root, ["network"]) ??
|
|
714
|
+
firstString(acceptedRequirement, ["network"]) ??
|
|
715
|
+
firstString(selectedRequirement, ["network", "networkId", "network_id", "caip2_chain_id"]);
|
|
716
|
+
if (!network || !networkMatches(network, config.caip2ChainId)) {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
const requirementNetwork = firstString(selectedRequirement, [
|
|
720
|
+
"network",
|
|
721
|
+
"networkId",
|
|
722
|
+
"network_id",
|
|
723
|
+
"caip2_chain_id"
|
|
724
|
+
]);
|
|
725
|
+
if (requirementNetwork && !networkMatches(network, requirementNetwork)) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
const payloadRequirementHash = firstString(root, [
|
|
729
|
+
"selectedRequirementHash",
|
|
730
|
+
"selected_requirement_hash"
|
|
731
|
+
]);
|
|
732
|
+
if (payloadRequirementHash !== selectedRequirementHash) {
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
const requirementAmount = firstString(selectedRequirement, [
|
|
736
|
+
"amount",
|
|
737
|
+
"maxAmountRequired",
|
|
738
|
+
"max_amount_required"
|
|
739
|
+
]);
|
|
740
|
+
if (!requirementAmount || !isUnsignedIntegerString(requirementAmount)) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
const assetValue = firstString(selectedRequirement, [
|
|
744
|
+
"asset",
|
|
745
|
+
"assetId",
|
|
746
|
+
"asset_id",
|
|
747
|
+
"assetPackage",
|
|
748
|
+
"asset_package"
|
|
749
|
+
]);
|
|
750
|
+
const nonce = normalizeHex(firstString(authorization, ["nonce"]) ?? firstString(root, ["nonce"]), 64);
|
|
751
|
+
if (!nonce) {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
if (assetValue?.toLowerCase() === "casper-native-cspr") {
|
|
755
|
+
const target = normalizeCasperTransferTarget(firstString(selectedRequirement, ["payTo", "pay_to", "payee", "recipient"]));
|
|
756
|
+
if (!target) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
settlementKind: "native-transfer",
|
|
761
|
+
scheme: "exact",
|
|
762
|
+
network,
|
|
763
|
+
assetId: "casper-native-cspr",
|
|
764
|
+
target,
|
|
765
|
+
amount: requirementAmount,
|
|
766
|
+
nonce
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const assetPackageHash = normalizeCasperPackageHash(assetValue);
|
|
770
|
+
if (!assetPackageHash) {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
const from = normalizeCasperAccountAddress(firstString(authorization, ["from"]));
|
|
774
|
+
const to = normalizeCasperAccountAddress(firstString(authorization, ["to"]));
|
|
775
|
+
const amount = firstString(authorization, ["value", "amount"]);
|
|
776
|
+
const validAfter = unixSecondsString(firstString(authorization, ["validAfter", "valid_after"]));
|
|
777
|
+
const validBefore = unixSecondsString(firstString(authorization, ["validBefore", "valid_before"]));
|
|
778
|
+
const publicKey = normalizePublicKey(firstString(paymentPayload, ["publicKey", "public_key"]) ??
|
|
779
|
+
firstString(authorization, ["publicKey", "public_key"]));
|
|
780
|
+
const signature = normalizeHex(firstString(paymentPayload, ["signature"]) ?? firstString(authorization, ["signature"]), 130);
|
|
781
|
+
if (!from ||
|
|
782
|
+
!to ||
|
|
783
|
+
!amount ||
|
|
784
|
+
!isUnsignedIntegerString(amount) ||
|
|
785
|
+
!validAfter ||
|
|
786
|
+
!validBefore ||
|
|
787
|
+
!nonce ||
|
|
788
|
+
!publicKey ||
|
|
789
|
+
!signature) {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
if (BigInt(validBefore) <= BigInt(validAfter)) {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
const nowSeconds = BigInt(Math.floor((config.now?.() ?? new Date()).getTime() / 1000));
|
|
796
|
+
if (BigInt(validAfter) > nowSeconds) {
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
if (BigInt(validBefore) <= nowSeconds) {
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
const payer = normalizeCasperAccountAddress(firstString(root, ["payer", "payerAccount", "payer_account"]));
|
|
803
|
+
if (payer && payer !== from) {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
if (requirementAmount && requirementAmount !== amount) {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
const requirementPayTo = normalizeCasperAccountAddress(firstString(selectedRequirement, ["payTo", "pay_to", "payee", "recipient"]));
|
|
810
|
+
if (requirementPayTo && requirementPayTo !== to) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
return {
|
|
814
|
+
settlementKind: "cep18-transfer-with-authorization",
|
|
815
|
+
scheme: "exact",
|
|
816
|
+
network,
|
|
817
|
+
assetPackageHash,
|
|
818
|
+
from,
|
|
819
|
+
to,
|
|
820
|
+
amount,
|
|
821
|
+
validAfter,
|
|
822
|
+
validBefore,
|
|
823
|
+
nonce,
|
|
824
|
+
publicKey,
|
|
825
|
+
signature
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
function runCasperCommand(command, args) {
|
|
829
|
+
return new Promise((resolve, reject) => {
|
|
830
|
+
const child = spawn(command, args, {
|
|
831
|
+
shell: false,
|
|
832
|
+
windowsHide: true
|
|
833
|
+
});
|
|
834
|
+
let stdout = "";
|
|
835
|
+
let stderr = "";
|
|
836
|
+
child.stdout.setEncoding("utf8");
|
|
837
|
+
child.stderr.setEncoding("utf8");
|
|
838
|
+
child.stdout.on("data", (chunk) => {
|
|
839
|
+
stdout += chunk;
|
|
840
|
+
});
|
|
841
|
+
child.stderr.on("data", (chunk) => {
|
|
842
|
+
stderr += chunk;
|
|
843
|
+
});
|
|
844
|
+
child.on("error", reject);
|
|
845
|
+
child.on("close", (code) => {
|
|
846
|
+
resolve({
|
|
847
|
+
exitCode: code ?? 1,
|
|
848
|
+
stdout,
|
|
849
|
+
stderr
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
function wrapCasperX402Command(config, args) {
|
|
855
|
+
if (!config.clientWslDistro) {
|
|
856
|
+
return {
|
|
857
|
+
command: config.clientBin,
|
|
858
|
+
args
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
command: "wsl",
|
|
863
|
+
args: ["-d", config.clientWslDistro, "--", config.clientBin, ...args]
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function sessionArg(name, type, value) {
|
|
867
|
+
return `${name}:${type}='${value}'`;
|
|
868
|
+
}
|
|
869
|
+
function accountKeyArg(value) {
|
|
870
|
+
const normalized = normalizeCasperAccountAddress(value);
|
|
871
|
+
if (!normalized) {
|
|
872
|
+
throw new Error("Invalid Casper account address");
|
|
873
|
+
}
|
|
874
|
+
return `account-hash-${normalized.slice(2)}`;
|
|
875
|
+
}
|
|
876
|
+
function commandReceipt(invocation) {
|
|
877
|
+
return {
|
|
878
|
+
command: invocation.command,
|
|
879
|
+
args: invocation.args.map((arg, index) => redactCommandArg(arg, invocation.args[index - 1] ?? null))
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function redactCommandArg(arg, previousArg) {
|
|
883
|
+
if (previousArg === "--secret-key") {
|
|
884
|
+
return "<redacted>";
|
|
885
|
+
}
|
|
886
|
+
if (previousArg !== "--session-arg") {
|
|
887
|
+
return arg;
|
|
888
|
+
}
|
|
889
|
+
const sessionArgMatch = arg.match(/^([^:]+):([^=]+)='(.*)'$/);
|
|
890
|
+
if (!sessionArgMatch) {
|
|
891
|
+
return arg;
|
|
892
|
+
}
|
|
893
|
+
const [, name, type, value] = sessionArgMatch;
|
|
894
|
+
if (!name || !type) {
|
|
895
|
+
return arg;
|
|
896
|
+
}
|
|
897
|
+
if (/signature|public_key|private_key|secret|authorization|nonce/i.test(name)) {
|
|
898
|
+
return `${name}:${type}='<redacted:${sha256Hex(value ?? "")}>'`;
|
|
899
|
+
}
|
|
900
|
+
return arg;
|
|
901
|
+
}
|
|
902
|
+
function commandResultReceipt(result) {
|
|
903
|
+
return {
|
|
904
|
+
exit_code: result.exitCode,
|
|
905
|
+
stdout_hash: result.stdout ? sha256Hex(result.stdout) : null,
|
|
906
|
+
stderr_hash: result.stderr ? sha256Hex(result.stderr) : null,
|
|
907
|
+
stdout_bytes: Buffer.byteLength(result.stdout, "utf8"),
|
|
908
|
+
stderr_bytes: Buffer.byteLength(result.stderr, "utf8")
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
function parseCasperClientOutput(output) {
|
|
912
|
+
const trimmed = output.trim();
|
|
913
|
+
if (!trimmed) {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
for (const candidate of [trimmed, jsonObjectSlice(trimmed)]) {
|
|
917
|
+
if (!candidate) {
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
try {
|
|
921
|
+
return JSON.parse(candidate);
|
|
922
|
+
}
|
|
923
|
+
catch {
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
function jsonObjectSlice(output) {
|
|
930
|
+
const firstBrace = output.indexOf("{");
|
|
931
|
+
const lastBrace = output.lastIndexOf("}");
|
|
932
|
+
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
return output.slice(firstBrace, lastBrace + 1);
|
|
936
|
+
}
|
|
937
|
+
function findExecutionInfo(value) {
|
|
938
|
+
if (!value || typeof value !== "object") {
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
const record = value;
|
|
942
|
+
for (const key of ["execution_info", "execution_results"]) {
|
|
943
|
+
const executionInfo = record[key];
|
|
944
|
+
if (Array.isArray(executionInfo)) {
|
|
945
|
+
if (executionInfo.length > 0) {
|
|
946
|
+
return executionInfo;
|
|
947
|
+
}
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (executionInfo && typeof executionInfo === "object") {
|
|
951
|
+
return executionInfo;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
for (const nested of Object.values(record)) {
|
|
955
|
+
const executionInfo = findExecutionInfo(nested);
|
|
956
|
+
if (executionInfo) {
|
|
957
|
+
return executionInfo;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
function findExecutionErrorMessage(value) {
|
|
963
|
+
if (!value || typeof value !== "object") {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
if (Array.isArray(value)) {
|
|
967
|
+
for (const item of value) {
|
|
968
|
+
const errorMessage = findExecutionErrorMessage(item);
|
|
969
|
+
if (errorMessage) {
|
|
970
|
+
return errorMessage;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
const record = value;
|
|
976
|
+
const direct = record.error_message;
|
|
977
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
978
|
+
return direct.trim();
|
|
979
|
+
}
|
|
980
|
+
const failure = record.Failure ?? record.failure;
|
|
981
|
+
if (failure) {
|
|
982
|
+
return typeof failure === "string" ? failure : JSON.stringify(failure);
|
|
983
|
+
}
|
|
984
|
+
for (const nested of Object.values(record)) {
|
|
985
|
+
const errorMessage = findExecutionErrorMessage(nested);
|
|
986
|
+
if (errorMessage) {
|
|
987
|
+
return errorMessage;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
function firstString(record, keys) {
|
|
993
|
+
if (!record) {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
for (const key of keys) {
|
|
997
|
+
const value = record[key];
|
|
998
|
+
if (typeof value === "string" && value.trim()) {
|
|
999
|
+
return value.trim();
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
function asRecord(value) {
|
|
1005
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
1006
|
+
? value
|
|
1007
|
+
: null;
|
|
1008
|
+
}
|
|
1009
|
+
function normalizeCasperPackageHash(value) {
|
|
1010
|
+
const normalized = value?.toLowerCase().replace(/^(hash-|package-)/, "");
|
|
1011
|
+
return normalized && /^[a-f0-9]{64}$/.test(normalized) ? normalized : null;
|
|
1012
|
+
}
|
|
1013
|
+
function normalizeCasperAccountAddress(value) {
|
|
1014
|
+
const normalized = value?.toLowerCase();
|
|
1015
|
+
if (!normalized) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
const accountHash = normalized.match(/^account-hash-([a-f0-9]{64})$/);
|
|
1019
|
+
if (accountHash) {
|
|
1020
|
+
return `00${accountHash[1]}`;
|
|
1021
|
+
}
|
|
1022
|
+
return /^(00|01)[a-f0-9]{64}$/.test(normalized) ? normalized : null;
|
|
1023
|
+
}
|
|
1024
|
+
function normalizeHex(value, length) {
|
|
1025
|
+
const normalized = value?.toLowerCase().replace(/^0x/, "");
|
|
1026
|
+
return normalized && new RegExp(`^[a-f0-9]{${length}}$`).test(normalized)
|
|
1027
|
+
? normalized
|
|
1028
|
+
: null;
|
|
1029
|
+
}
|
|
1030
|
+
function normalizePublicKey(value) {
|
|
1031
|
+
const normalized = value?.toLowerCase().replace(/^0x/, "");
|
|
1032
|
+
if (!normalized || !/^(01|02)[a-f0-9]+$/.test(normalized)) {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
return normalized.length === 66 || normalized.length === 68 ? normalized : null;
|
|
1036
|
+
}
|
|
1037
|
+
function normalizeCasperTransferTarget(value) {
|
|
1038
|
+
const publicKey = normalizePublicKey(value);
|
|
1039
|
+
if (publicKey) {
|
|
1040
|
+
return publicKey;
|
|
1041
|
+
}
|
|
1042
|
+
const accountAddress = normalizeCasperAccountAddress(value);
|
|
1043
|
+
return accountAddress ? `account-hash-${accountAddress.slice(2)}` : null;
|
|
1044
|
+
}
|
|
1045
|
+
function settlementAssetReceipt(payment) {
|
|
1046
|
+
return payment.settlementKind === "native-transfer"
|
|
1047
|
+
? payment.assetId
|
|
1048
|
+
: payment.assetPackageHash;
|
|
1049
|
+
}
|
|
1050
|
+
function settlementPayeeReceipt(payment) {
|
|
1051
|
+
return payment.settlementKind === "native-transfer" ? payment.target : payment.to;
|
|
1052
|
+
}
|
|
1053
|
+
function settlementPayerReceipt(payment, signedPayload) {
|
|
1054
|
+
if (payment.settlementKind !== "native-transfer") {
|
|
1055
|
+
return payment.from;
|
|
1056
|
+
}
|
|
1057
|
+
return firstString(signedPayload, ["payer", "payerAccount", "payer_account"]);
|
|
1058
|
+
}
|
|
1059
|
+
function unixSecondsString(value) {
|
|
1060
|
+
if (!value) {
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
if (isUnsignedIntegerString(value)) {
|
|
1064
|
+
return value;
|
|
1065
|
+
}
|
|
1066
|
+
const timestampMs = Date.parse(value);
|
|
1067
|
+
if (!Number.isFinite(timestampMs)) {
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
return String(Math.floor(timestampMs / 1000));
|
|
1071
|
+
}
|
|
1072
|
+
function isUnsignedIntegerString(value) {
|
|
1073
|
+
return /^(0|[1-9]\d*)$/.test(value);
|
|
1074
|
+
}
|
|
1075
|
+
function networkMatches(left, right) {
|
|
1076
|
+
const normalizedLeft = left.toLowerCase();
|
|
1077
|
+
const normalizedRight = right.toLowerCase();
|
|
1078
|
+
if (normalizedLeft === normalizedRight) {
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
const leftSuffix = normalizedLeft.split(":").at(-1);
|
|
1082
|
+
const rightSuffix = normalizedRight.split(":").at(-1);
|
|
1083
|
+
return Boolean(leftSuffix && rightSuffix && leftSuffix === rightSuffix);
|
|
1084
|
+
}
|
|
1085
|
+
function delay(ms) {
|
|
1086
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1087
|
+
}
|
|
1088
|
+
function facilitatorEndpoint(facilitatorUrl, path) {
|
|
1089
|
+
if (!facilitatorUrl) {
|
|
1090
|
+
throw new Error("X402_FACILITATOR_URL is required for facilitator settlement");
|
|
1091
|
+
}
|
|
1092
|
+
return new URL(path, ensureTrailingSlash(facilitatorUrl)).toString();
|
|
1093
|
+
}
|
|
1094
|
+
function ensureTrailingSlash(value) {
|
|
1095
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
1096
|
+
}
|
|
1097
|
+
function settlementReceiptJson(value) {
|
|
1098
|
+
return JSON.stringify(redactX402Value(toJsonValue(value, "x402_settlement_receipt")));
|
|
1099
|
+
}
|
|
1100
|
+
async function postSignerJson(config, body) {
|
|
1101
|
+
const controller = new AbortController();
|
|
1102
|
+
const timeout = setTimeout(() => controller.abort(), config.timeoutMs ?? 10_000);
|
|
1103
|
+
try {
|
|
1104
|
+
const headers = {
|
|
1105
|
+
"content-type": "application/json"
|
|
1106
|
+
};
|
|
1107
|
+
if (config.authToken) {
|
|
1108
|
+
headers.authorization = `Bearer ${config.authToken}`;
|
|
1109
|
+
}
|
|
1110
|
+
return await fetch(config.signerUrl, {
|
|
1111
|
+
method: "POST",
|
|
1112
|
+
headers,
|
|
1113
|
+
body: JSON.stringify(body),
|
|
1114
|
+
signal: controller.signal
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
finally {
|
|
1118
|
+
clearTimeout(timeout);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
function extractSignedPayload(value) {
|
|
1122
|
+
const response = tryJsonObject(value);
|
|
1123
|
+
if (!response) {
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
for (const key of [
|
|
1127
|
+
"signed_payload",
|
|
1128
|
+
"signedPayload",
|
|
1129
|
+
"paymentPayload",
|
|
1130
|
+
"payment_payload"
|
|
1131
|
+
]) {
|
|
1132
|
+
const candidate = response[key];
|
|
1133
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
|
|
1134
|
+
return toJsonObject(candidate, key);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
function encodePaymentHeader(payload) {
|
|
1140
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
1141
|
+
}
|
|
1142
|
+
async function fetchPaidResource(url, init) {
|
|
1143
|
+
const response = await fetch(url, {
|
|
1144
|
+
method: init.method,
|
|
1145
|
+
headers: init.headers
|
|
1146
|
+
});
|
|
1147
|
+
return {
|
|
1148
|
+
status: response.status,
|
|
1149
|
+
headers: response.headers,
|
|
1150
|
+
bodyText: await response.text()
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
function parsePaymentResponseHeader(headers) {
|
|
1154
|
+
for (const name of ["PAYMENT-RESPONSE", "X-PAYMENT-RESPONSE"]) {
|
|
1155
|
+
const header = headers.get(name);
|
|
1156
|
+
if (!header) {
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
return parsePaymentResponseHeaderValue(header);
|
|
1160
|
+
}
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
function parsePaymentResponseHeaderValue(value) {
|
|
1164
|
+
const trimmed = value.trim();
|
|
1165
|
+
for (const candidate of [
|
|
1166
|
+
() => Buffer.from(trimmed, "base64").toString("utf8"),
|
|
1167
|
+
() => trimmed
|
|
1168
|
+
]) {
|
|
1169
|
+
try {
|
|
1170
|
+
return JSON.parse(candidate());
|
|
1171
|
+
}
|
|
1172
|
+
catch {
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return { raw: trimmed };
|
|
1177
|
+
}
|
|
1178
|
+
function paidResponseOk(status) {
|
|
1179
|
+
return status >= 200 && status < 300;
|
|
1180
|
+
}
|
|
1181
|
+
async function postJsonWithFetch(url, body) {
|
|
1182
|
+
const response = await fetch(url, {
|
|
1183
|
+
method: "POST",
|
|
1184
|
+
headers: {
|
|
1185
|
+
"content-type": "application/json"
|
|
1186
|
+
},
|
|
1187
|
+
body: JSON.stringify(body)
|
|
1188
|
+
});
|
|
1189
|
+
const text = await response.text();
|
|
1190
|
+
return {
|
|
1191
|
+
status: response.status,
|
|
1192
|
+
body: text ? parseJsonOrRaw(text) : {}
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
function tryJsonObject(value) {
|
|
1196
|
+
try {
|
|
1197
|
+
return toJsonObject(value, "x402_response");
|
|
1198
|
+
}
|
|
1199
|
+
catch {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function parseJsonOrRaw(text) {
|
|
1204
|
+
try {
|
|
1205
|
+
return JSON.parse(text);
|
|
1206
|
+
}
|
|
1207
|
+
catch {
|
|
1208
|
+
return { raw: text };
|
|
1209
|
+
}
|
|
1210
|
+
}
|