agent402-tollbooth 0.10.0 → 0.10.2

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # agent402-tollbooth
2
2
 
3
+ Part of [Agent402](https://agent402.tools), the open-source paid door for AI agents.
4
+
3
5
  **Open-source, self-hostable pay-per-crawl for x402 and MPP. Put it in front of
4
6
  any site or API: humans browse free, AI crawlers and agents pay per request** -
5
7
  in USDC over the [x402 protocol](https://x402.org), over
package/index.js CHANGED
@@ -12,7 +12,8 @@
12
12
  // The proof-of-work rail works out of the box with zero configuration. To also
13
13
  // accept USDC, set `payTo` and supply `verifyX402` (wire it to the standard
14
14
  // x402 server middleware / your facilitator — see README).
15
- import { fileURLToPath } from "node:url";
15
+ import { realpathSync } from "node:fs";
16
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
17
  import { Readable } from "node:stream";
17
18
  import { randomBytes } from "node:crypto";
18
19
  import { createPow } from "./pow.js";
@@ -910,4 +911,14 @@ async function startCli() {
910
911
  });
911
912
  }
912
913
 
913
- if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) startCli();
914
+ // THE BIN SYMLINK. npm links the bin as `.bin/agent402-tollbooth`, so argv[1]
915
+ // is the SYMLINK path while import.meta.url is the realpath - they never match,
916
+ // and the documented install path (`npx agent402-tollbooth`) started nothing at
917
+ // all while `node index.js` worked fine. Exactly the defect found and fixed in
918
+ // agent402-openclaw 0.1.2; it survived here because every test invokes
919
+ // index.js by path, which is the one way to call it that cannot see the bug.
920
+ // Compare the REALPATH, the way openclaw/cli.js does.
921
+ function invokedDirectly() {
922
+ try { return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href; } catch { return false; }
923
+ }
924
+ if (process.argv[1] && invokedDirectly()) startCli();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent402-tollbooth",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
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",
@@ -44,7 +44,7 @@
44
44
  "node": ">=20"
45
45
  },
46
46
  "scripts": {
47
- "test": "node test.js && node edge.test.js && node features.test.js",
47
+ "test": "node test.js && node edge.test.js && node features.test.js && node tempo.test.js",
48
48
  "demo": "node demo.js",
49
49
  "example": "node example.js"
50
50
  },
package/tempo.js CHANGED
@@ -323,12 +323,25 @@ export function candidateTxIds(signedTx) {
323
323
  return out;
324
324
  }
325
325
 
326
- const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
326
+ // TIP-20 TransferWithMemo(address indexed from, address indexed to, uint256 amount, bytes32 indexed memo)
327
+ const TRANSFER_WITH_MEMO_TOPIC = keccak256Hex(Buffer.from("TransferWithMemo(address,address,uint256,bytes32)"));
328
+ const MPP_TAG = keccak256Hex(Buffer.from("mpp")).slice(2, 10);
329
+
330
+ /** Is this bytes32 memo an MPP attribution memo bound to `challengeId`?
331
+ * Mirrors mppx tempo/Attribution.js verifyChallengeBinding: TAG, version
332
+ * 0x01, and bytes 25..31 = keccak256(challengeId)[0..6]. */
333
+ export function memoBoundToChallenge(memo, challengeId) {
334
+ const hex = String(memo || "").toLowerCase().replace(/^0x/, "");
335
+ if (hex.length !== 64 || typeof challengeId !== "string" || !challengeId) return false;
336
+ if (hex.slice(0, 8) !== MPP_TAG || hex.slice(8, 10) !== "01") return false;
337
+ return hex.slice(50, 64) === keccak256Hex(Buffer.from(challengeId)).slice(2, 16);
338
+ }
327
339
 
328
340
  /** Did this credential's transaction settle despite the relay's verdict?
329
341
  * Returns { txId } when a candidate receipt exists, succeeded (status 0x1),
330
- * and carries the challenge's transfer (currency + recipient + >= amount) —
331
- * else null. Polls briefly (a just-mined tx may not be indexed when the
342
+ * and carries the challenge's transfer (currency + recipient + >= amount) as
343
+ * a TransferWithMemo whose MPP memo is bound to THIS challenge's id - else
344
+ * null. Polls briefly (a just-mined tx may not be indexed when the
332
345
  * relay answers). Never throws. */
333
346
  export async function confirmTempoSettlement(credential, { rpcUrl, fetchImpl = globalThis.fetch, attempts = 4, delayMs = 2000 } = {}) {
334
347
  try {
@@ -357,8 +370,9 @@ export async function confirmTempoSettlement(credential, { rpcUrl, fetchImpl = g
357
370
  if (!receipt || receipt.status !== "0x1") continue;
358
371
  for (const log of receipt.logs || []) {
359
372
  if (String(log.address || "").toLowerCase() !== currency) continue;
360
- if ((log.topics || [])[0] !== TRANSFER_TOPIC) continue;
373
+ if ((log.topics || [])[0] !== TRANSFER_WITH_MEMO_TOPIC) continue;
361
374
  if (`0x${String(log.topics[2] || "").slice(-40)}`.toLowerCase() !== recipient) continue;
375
+ if (!memoBoundToChallenge(log.topics[3], credential?.challenge?.id)) continue;
362
376
  let value;
363
377
  try { value = BigInt(log.data); } catch { continue; }
364
378
  if (value >= minAmount) return { txId };