agent402-tollbooth 0.9.0 → 0.9.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/index.js +8 -3
- package/package.json +1 -1
- package/tempo.js +22 -5
package/index.js
CHANGED
|
@@ -349,7 +349,12 @@ export function createTollbooth(config = {}) {
|
|
|
349
349
|
if (!claimed) return reject("invalid-challenge", "Challenge is invalid: this credential was already used or is in flight. Request the resource again for a fresh challenge.");
|
|
350
350
|
const input = relayInput(cred);
|
|
351
351
|
const v = await tempoRelayClient.validate(input).catch((e) => ({ ok: false, error: String(e?.message || e) }));
|
|
352
|
-
if (!v.ok)
|
|
352
|
+
if (!v.ok) {
|
|
353
|
+
// Operator log gets the relay's full message (`detail`); the buyer's
|
|
354
|
+
// 402 problem gets status + code only (`error`) - never an upstream body.
|
|
355
|
+
console.warn(`[agent402-tollbooth] tempo credential rejected by validate (${req.method} ${req.url}): ${v.detail || v.error}`);
|
|
356
|
+
return reject("verification-failed", `Payment verification failed: ${String(v.error || "the Tempo relay rejected the credential").slice(0, 160)}.`);
|
|
357
|
+
}
|
|
353
358
|
// Buffer the handler's response (writeHead/write/end/flushHeaders) -
|
|
354
359
|
// Node's real 'finish' never fires while end is buffered, so the sync
|
|
355
360
|
// primitive is a promise resolved inside the buffered end.
|
|
@@ -387,10 +392,10 @@ export function createTollbooth(config = {}) {
|
|
|
387
392
|
// Broadcast failed AFTER a successful handler: discard the body, 402
|
|
388
393
|
// (mirrors @x402/express's settle-failure path). Loud - this is the
|
|
389
394
|
// one path that can be our latency rather than the relay's verdict.
|
|
390
|
-
console.warn(`[agent402-tollbooth] tempo broadcast failed after a successful handler (${req.method} ${req.url}) - answered 402, not charged: ${b.error}`);
|
|
395
|
+
console.warn(`[agent402-tollbooth] tempo broadcast failed after a successful handler (${req.method} ${req.url}) - answered 402, not charged: ${b.detail || b.error}`);
|
|
391
396
|
buffered = [];
|
|
392
397
|
restore();
|
|
393
|
-
return reject("verification-failed", `Payment verification failed: Tempo settlement was not accepted (${String(b.error || "no relay detail").slice(0,
|
|
398
|
+
return reject("verification-failed", `Payment verification failed: Tempo settlement was not accepted (${String(b.error || "no relay detail").slice(0, 160)}).`);
|
|
394
399
|
}
|
|
395
400
|
incr("tempoPaid");
|
|
396
401
|
restore();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent402-tollbooth",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Agent402 tollbooth: open-source, self-hostable pay-per-crawl for x402 AND MPP (the sell side of Agentic Finance): charge AI bots per request (USDC via x402, or MPP - the Machine Payments Protocol - through the same settlement, or USDG on Robinhood Chain, or free proof-of-work) while humans browse free. Drop-in Express middleware or reverse proxy that settles both wires from env alone - no Cloudflare, no Stripe, no signup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/tempo.js
CHANGED
|
@@ -167,10 +167,18 @@ export function checkTempoBinding(credential, { secretKey, realm, price, tempo,
|
|
|
167
167
|
return { ok: true, amount };
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// Buyer-facing relay failure: status + the relay's error CODE only. The
|
|
171
|
+
// relay's free-text `message` is an upstream body and goes to the operator
|
|
172
|
+
// log (console.warn at the call sites), never into the 402 problem document
|
|
173
|
+
// a buyer reads - a relay that echoes a key or account detail in an error
|
|
174
|
+
// must not hand it to every buyer. (Leak audit 2026-08-19.)
|
|
170
175
|
function relayFailure(res, body) {
|
|
171
176
|
const code = body && typeof body === "object" && body.error && typeof body.error === "object" && typeof body.error.code === "string" ? body.error.code : null;
|
|
177
|
+
return `relay HTTP ${res?.status ?? "?"}${code ? ` ${code.slice(0, 60)}` : ""}`;
|
|
178
|
+
}
|
|
179
|
+
function relayFailureDetail(res, body) {
|
|
172
180
|
const msg = body && typeof body === "object" && body.error && typeof body.error.message === "string" ? body.error.message : "";
|
|
173
|
-
return
|
|
181
|
+
return `${relayFailure(res, body)}${msg ? ` ${msg.slice(0, 160)}` : ""}`;
|
|
174
182
|
}
|
|
175
183
|
|
|
176
184
|
/** The relay client. Injectable `relay` (tests) takes precedence. */
|
|
@@ -190,17 +198,17 @@ export function tempoRelay(tempo) {
|
|
|
190
198
|
}
|
|
191
199
|
let body = null;
|
|
192
200
|
try { body = await res.json(); } catch { body = null; }
|
|
193
|
-
if (!res.ok || !body || body.success !== true) return { ok: false, error: relayFailure(res, body), body };
|
|
201
|
+
if (!res.ok || !body || body.success !== true) return { ok: false, error: relayFailure(res, body), detail: relayFailureDetail(res, body), body };
|
|
194
202
|
return { ok: true, body };
|
|
195
203
|
};
|
|
196
204
|
return {
|
|
197
205
|
async validate(input) {
|
|
198
206
|
const r = await post("validate", input);
|
|
199
|
-
return r.ok ? { ok: true } : { ok: false, error: r.error };
|
|
207
|
+
return r.ok ? { ok: true } : { ok: false, error: r.error, detail: r.detail || r.error };
|
|
200
208
|
},
|
|
201
209
|
async broadcast(input, { idempotencyKey }) {
|
|
202
210
|
const r = await post("broadcast", input, { "idempotency-key": idempotencyKey });
|
|
203
|
-
if (!r.ok) return { ok: false, error: r.error };
|
|
211
|
+
if (!r.ok) return { ok: false, error: r.error, detail: r.detail || r.error };
|
|
204
212
|
const receipt = r.body.receipt;
|
|
205
213
|
if (!receipt || typeof receipt !== "object" || typeof receipt.reference !== "string" || receipt.method !== "tempo") return { ok: false, error: "relay broadcast answered success without a tempo receipt" };
|
|
206
214
|
return { ok: true, receipt };
|
|
@@ -209,7 +217,16 @@ export function tempoRelay(tempo) {
|
|
|
209
217
|
}
|
|
210
218
|
|
|
211
219
|
export function relayInput(credential) {
|
|
212
|
-
|
|
220
|
+
// The relay's input is mppx's DESERIALIZED credential (Relay.js
|
|
221
|
+
// toRelayInput): the challenge with `request` as the decoded object, not
|
|
222
|
+
// the base64url string that rides on the wire. The first live proof
|
|
223
|
+
// (run 32295980187, 2026-08-19) sent the wire string and the relay refused
|
|
224
|
+
// every credential while the stub relay in the offline suite accepted
|
|
225
|
+
// either - the suite now pins the decoded shape.
|
|
226
|
+
const { request, ...challenge } = credential.challenge;
|
|
227
|
+
let decoded = request;
|
|
228
|
+
if (typeof request === "string") { try { decoded = JSON.parse(unb64url(request)); } catch { decoded = request; } }
|
|
229
|
+
return { challenge: { ...challenge, request: decoded }, payload: credential.payload, ...(credential.source ? { source: credential.source } : {}) };
|
|
213
230
|
}
|
|
214
231
|
export function broadcastIdempotencyKey(input) {
|
|
215
232
|
return `mpp_${createHash("sha256").update(canonicalJson(input), "utf8").digest("hex")}`;
|