xytara 3.1.13 → 3.1.15
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/.env.example +38 -4
- package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +126 -126
- package/REAL_PAYMENT_SETUP.md +14 -7
- package/RELEASE_NOTES.md +9 -4
- package/fixtures/catalog.js +22 -0
- package/lib/a2a_rpc.js +287 -280
- package/lib/account_auth.js +585 -585
- package/lib/agent_identity.js +162 -162
- package/lib/asyncapi_contract.js +3 -3
- package/lib/bolt_adapter.js +187 -187
- package/lib/bolt_payment_front.js +224 -224
- package/lib/buyer_event_log.js +215 -0
- package/lib/buyer_safety_policy.js +132 -0
- package/lib/checkout_processor_adapter.js +217 -217
- package/lib/command_flow.js +911 -722
- package/lib/commerce_artifacts.js +591 -591
- package/lib/commerce_bridge.js +274 -274
- package/lib/commerce_cases.js +399 -399
- package/lib/commerce_client.js +7694 -7694
- package/lib/commerce_quotes.js +29 -0
- package/lib/commerce_runtime.js +322 -322
- package/lib/execution_dedupe.js +86 -86
- package/lib/external_credit_bridge.js +142 -142
- package/lib/http_transport.js +99 -81
- package/lib/l402_adapter.js +241 -241
- package/lib/l402_payment_front.js +265 -265
- package/lib/mpp_adapter.js +237 -237
- package/lib/openapi_contract.js +4 -0
- package/lib/payment_config.js +257 -253
- package/lib/payment_verification.js +357 -357
- package/lib/provider_payout_execution.js +155 -155
- package/lib/provider_registry.js +852 -842
- package/lib/rate_limiter.js +81 -81
- package/lib/runtime_state_store.js +495 -369
- package/lib/settlement_bsv_live.js +600 -600
- package/lib/settlement_bsv_teranode.js +117 -117
- package/lib/stripe_mpp_payment_front.js +258 -258
- package/lib/x402_facilitator.js +198 -191
- package/lib/x402_lane_contract.js +5 -3
- package/lib/x402_settlement_recovery.js +175 -0
- package/package.json +9 -3
- package/scripts/buyer_events_daily_report.js +79 -0
- package/scripts/state_backup_offsite.js +196 -0
- package/scripts/verify_a2a_rpc.js +131 -105
- package/scripts/verify_agent_identity.js +153 -153
- package/scripts/verify_all.js +1 -1
- package/scripts/verify_bolt_adapter.js +128 -128
- package/scripts/verify_buyer_event_log.js +123 -0
- package/scripts/verify_buyer_safety.js +141 -0
- package/scripts/verify_cdp_facilitator.js +110 -110
- package/scripts/verify_checkout_processor.js +109 -109
- package/scripts/verify_commerce_bridge.js +177 -177
- package/scripts/verify_execution_dedupe.js +189 -189
- package/scripts/verify_execution_surfacing.js +76 -76
- package/scripts/verify_final_activation.js +191 -191
- package/scripts/verify_l402_adapter.js +86 -86
- package/scripts/verify_lifecycle_spend_binding.js +88 -0
- package/scripts/verify_mpp_adapter.js +128 -128
- package/scripts/verify_production_readiness.js +268 -268
- package/scripts/verify_rate_limiter.js +52 -52
- package/scripts/verify_refund_credit.js +106 -106
- package/scripts/verify_release_candidate.js +81 -81
- package/scripts/verify_runtime_state_guard.js +117 -0
- package/scripts/verify_service.js +15421 -15421
- package/scripts/verify_verify_execute_settle.js +209 -208
- package/scripts/verify_x402_facilitator.js +307 -286
- package/scripts/verify_x402_settlement_recovery.js +153 -0
- package/server.js +12242 -11974
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// End-to-end check (real local server, mock facilitator + mock chain RPC) that a facilitator settle
|
|
4
|
+
// timeout never leaves a buyer charged with nothing:
|
|
5
|
+
// - settle times out but the authorization IS used on-chain -> the result is delivered;
|
|
6
|
+
// - settle times out and the chain does not show it yet -> held, and resending the SAME payment
|
|
7
|
+
// delivers once the chain confirms (no second settle, no re-execution);
|
|
8
|
+
// - a normally settled payment resent -> the stored result is returned (replayed), not re-run;
|
|
9
|
+
// - the same payment resent for a DIFFERENT request -> refused (on-chain data can't fetch results).
|
|
10
|
+
|
|
11
|
+
const assert = require("assert");
|
|
12
|
+
const http = require("http");
|
|
13
|
+
const { AUTHORIZATION_STATE_SELECTOR } = require("../lib/x402_settlement_recovery");
|
|
14
|
+
|
|
15
|
+
const PAYER = "0x1111111111111111111111111111111111111111";
|
|
16
|
+
const ACCEPTS = [{
|
|
17
|
+
scheme: "exact",
|
|
18
|
+
network: "eip155:84532",
|
|
19
|
+
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
20
|
+
amount: "1000",
|
|
21
|
+
payTo: "0x7cd2E0Ed2721319B20FAac4109774d030833F84D",
|
|
22
|
+
extra: { name: "USDC", version: "2" },
|
|
23
|
+
maxTimeoutSeconds: 60
|
|
24
|
+
}];
|
|
25
|
+
|
|
26
|
+
function listen(handler) {
|
|
27
|
+
const server = http.createServer((req, res) => {
|
|
28
|
+
const chunks = [];
|
|
29
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
30
|
+
req.on("end", () => {
|
|
31
|
+
let body = {};
|
|
32
|
+
try { body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); } catch (_) { body = {}; }
|
|
33
|
+
handler(req, res, body);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, port: server.address().port })));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function reply(res, payload) {
|
|
40
|
+
const text = JSON.stringify(payload);
|
|
41
|
+
res.writeHead(200, { "content-type": "application/json", "content-length": Buffer.byteLength(text) });
|
|
42
|
+
res.end(text);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function paymentHeader(signature, nonceByte) {
|
|
46
|
+
return Buffer.from(JSON.stringify({
|
|
47
|
+
x402Version: 2,
|
|
48
|
+
accepted: ACCEPTS[0],
|
|
49
|
+
payload: {
|
|
50
|
+
signature,
|
|
51
|
+
authorization: { from: PAYER, to: ACCEPTS[0].payTo, value: "1000", validAfter: "0", validBefore: String(Math.floor(Date.now() / 1000) + 600), nonce: `0x${nonceByte.repeat(32)}` }
|
|
52
|
+
}
|
|
53
|
+
})).toString("base64");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function post(baseUrl, body, header) {
|
|
57
|
+
const res = await fetch(`${baseUrl}/x402/commands/execute`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "content-type": "application/json", "payment-signature": header },
|
|
60
|
+
body: JSON.stringify(body)
|
|
61
|
+
});
|
|
62
|
+
let json = null;
|
|
63
|
+
try { json = await res.json(); } catch (_) { json = null; }
|
|
64
|
+
return { status: res.status, json };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function main() {
|
|
68
|
+
const calls = { settle: 0, rpc: 0 };
|
|
69
|
+
// Facilitator: verify always ok; settle hangs for signatures marked "hang", succeeds otherwise.
|
|
70
|
+
const facilitator = await listen((req, res, body) => {
|
|
71
|
+
const sig = String((body.paymentPayload && body.paymentPayload.payload && body.paymentPayload.payload.signature) || "");
|
|
72
|
+
if (req.url === "/verify") return reply(res, { isValid: true, payer: PAYER });
|
|
73
|
+
if (req.url === "/settle") {
|
|
74
|
+
calls.settle += 1;
|
|
75
|
+
if (sig.startsWith("hang")) return; // never answers: the client times out
|
|
76
|
+
return reply(res, { success: true, transaction: "0xmocktx", network: ACCEPTS[0].network, payer: PAYER });
|
|
77
|
+
}
|
|
78
|
+
res.writeHead(404); res.end();
|
|
79
|
+
});
|
|
80
|
+
// Chain RPC: authorizationState(PAYER, nonce) -> used for nonces in usedNonces.
|
|
81
|
+
const usedNonces = new Set();
|
|
82
|
+
const rpc = await listen((req, res, body) => {
|
|
83
|
+
calls.rpc += 1;
|
|
84
|
+
const data = String((body.params && body.params[0] && body.params[0].data) || "");
|
|
85
|
+
assert.ok(data.startsWith(AUTHORIZATION_STATE_SELECTOR), "RPC asked for authorizationState");
|
|
86
|
+
const nonce = `0x${data.slice(-64)}`;
|
|
87
|
+
reply(res, { jsonrpc: "2.0", id: body.id, result: `0x${(usedNonces.has(nonce) ? 1 : 0).toString(16).padStart(64, "0")}` });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const keys = ["XYTARA_PAYMENT_VERIFICATION_MODE", "XYTARA_X402_ACCEPTS_JSON", "XYTARA_X402_FACILITATOR_URL", "XYTARA_X402_FACILITATOR_TIMEOUT_MS", "XYTARA_RPC_URL_EIP155_84532"];
|
|
91
|
+
const previous = Object.fromEntries(keys.map((key) => [key, process.env[key]]));
|
|
92
|
+
process.env.XYTARA_PAYMENT_VERIFICATION_MODE = "x402_facilitator";
|
|
93
|
+
process.env.XYTARA_X402_ACCEPTS_JSON = JSON.stringify(ACCEPTS);
|
|
94
|
+
process.env.XYTARA_X402_FACILITATOR_URL = `http://127.0.0.1:${facilitator.port}`;
|
|
95
|
+
process.env.XYTARA_X402_FACILITATOR_TIMEOUT_MS = "1000";
|
|
96
|
+
process.env.XYTARA_RPC_URL_EIP155_84532 = `http://127.0.0.1:${rpc.port}`;
|
|
97
|
+
|
|
98
|
+
delete require.cache[require.resolve("../server")];
|
|
99
|
+
const { createServer } = require("../server");
|
|
100
|
+
const server = createServer();
|
|
101
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
102
|
+
const baseUrl = `http://127.0.0.1:${server.address().port}`;
|
|
103
|
+
const job = { account_id: "acct_recovery", task_ref: "trust.verify", body: { subject_id: "s-1" } };
|
|
104
|
+
try {
|
|
105
|
+
// A. Settle times out, but the chain shows the authorization used -> delivered.
|
|
106
|
+
usedNonces.add(`0x${"a1".repeat(32)}`);
|
|
107
|
+
const recovered = await post(baseUrl, job, paymentHeader("hang-a", "a1"));
|
|
108
|
+
assert.strictEqual(recovered.status, 200, "a settled-on-chain payment must deliver despite the facilitator timeout");
|
|
109
|
+
assert.ok(recovered.json.delivery_receipt || recovered.json.command || recovered.json.ok, "the result is delivered");
|
|
110
|
+
|
|
111
|
+
// B. Settle times out and the chain does not show it (yet) -> held, never "pay again".
|
|
112
|
+
const held = await post(baseUrl, job, paymentHeader("hang-b", "b2"));
|
|
113
|
+
assert.strictEqual(held.status, 502);
|
|
114
|
+
assert.strictEqual(held.json.reason, "settlement_not_confirmed");
|
|
115
|
+
assert.ok(/SAME payment signature/.test(held.json.detail), "the buyer is told to resend the same payment, not a new one");
|
|
116
|
+
// The transfer lands later; resending the same payment delivers the held result.
|
|
117
|
+
usedNonces.add(`0x${"b2".repeat(32)}`);
|
|
118
|
+
const settleCallsBefore = calls.settle;
|
|
119
|
+
const delivered = await post(baseUrl, job, paymentHeader("hang-b", "b2"));
|
|
120
|
+
assert.strictEqual(delivered.status, 200, "resending the same payment delivers once it is on-chain");
|
|
121
|
+
assert.strictEqual(delivered.json.replayed, true);
|
|
122
|
+
assert.strictEqual(calls.settle, settleCallsBefore, "no second settlement attempt");
|
|
123
|
+
|
|
124
|
+
// C. The same payment resent for a different request -> refused.
|
|
125
|
+
const other = await post(baseUrl, { ...job, body: { subject_id: "someone-else" } }, paymentHeader("hang-b", "b2"));
|
|
126
|
+
assert.strictEqual(other.status, 409);
|
|
127
|
+
assert.strictEqual(other.json.reason, "payment_already_used");
|
|
128
|
+
|
|
129
|
+
// D. Normal settle, then a resend (e.g. the buyer lost the response) -> stored result, no re-run.
|
|
130
|
+
const first = await post(baseUrl, job, paymentHeader("ok-d", "d4"));
|
|
131
|
+
assert.strictEqual(first.status, 200);
|
|
132
|
+
const settles = calls.settle;
|
|
133
|
+
const again = await post(baseUrl, job, paymentHeader("ok-d", "d4"));
|
|
134
|
+
assert.strictEqual(again.status, 200);
|
|
135
|
+
assert.strictEqual(again.json.replayed, true, "a resent settled payment returns the stored result");
|
|
136
|
+
assert.strictEqual(calls.settle, settles, "and is not settled again");
|
|
137
|
+
|
|
138
|
+
console.log("xytara verify:x402-settlement-recovery passed");
|
|
139
|
+
} finally {
|
|
140
|
+
await new Promise((resolve) => server.close(resolve));
|
|
141
|
+
facilitator.server.closeAllConnections && facilitator.server.closeAllConnections();
|
|
142
|
+
await new Promise((resolve) => facilitator.server.close(resolve));
|
|
143
|
+
await new Promise((resolve) => rpc.server.close(resolve));
|
|
144
|
+
for (const key of keys) {
|
|
145
|
+
if (previous[key] === undefined) delete process.env[key]; else process.env[key] = previous[key];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
main().catch((error) => {
|
|
151
|
+
console.error(error && error.stack ? error.stack : error);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|