@absol-labs/agent 0.11.0 → 0.12.0-rc.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.
Files changed (366) hide show
  1. package/README.md +43 -51
  2. package/dist/capability/invocation-capability.d.ts +2 -2
  3. package/dist/capability/probe-capability.d.ts +225 -0
  4. package/dist/capability/probe-capability.d.ts.map +1 -0
  5. package/dist/capability/probe-capability.js +248 -0
  6. package/dist/capability/probe-capability.js.map +1 -0
  7. package/dist/cli/args.d.ts +11 -0
  8. package/dist/cli/args.d.ts.map +1 -1
  9. package/dist/cli/args.js +23 -0
  10. package/dist/cli/args.js.map +1 -1
  11. package/dist/cli/commands/claim.d.ts +32 -0
  12. package/dist/cli/commands/claim.d.ts.map +1 -1
  13. package/dist/cli/commands/claim.js +116 -28
  14. package/dist/cli/commands/claim.js.map +1 -1
  15. package/dist/cli/commands/close.d.ts.map +1 -1
  16. package/dist/cli/commands/close.js +35 -3
  17. package/dist/cli/commands/close.js.map +1 -1
  18. package/dist/cli/commands/invoke.d.ts +2 -0
  19. package/dist/cli/commands/invoke.d.ts.map +1 -1
  20. package/dist/cli/commands/invoke.js +2 -2
  21. package/dist/cli/commands/invoke.js.map +1 -1
  22. package/dist/cli/commands/mcp.d.ts.map +1 -1
  23. package/dist/cli/commands/mcp.js +15 -2
  24. package/dist/cli/commands/mcp.js.map +1 -1
  25. package/dist/cli/commands/open.d.ts.map +1 -1
  26. package/dist/cli/commands/open.js +58 -4
  27. package/dist/cli/commands/open.js.map +1 -1
  28. package/dist/cli/commands/pay.d.ts +34 -0
  29. package/dist/cli/commands/pay.d.ts.map +1 -0
  30. package/dist/cli/commands/pay.js +479 -0
  31. package/dist/cli/commands/pay.js.map +1 -0
  32. package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -1
  33. package/dist/cli/commands/reclaim-unverified.js +3 -2
  34. package/dist/cli/commands/reclaim-unverified.js.map +1 -1
  35. package/dist/cli/commands/reclaim.d.ts +23 -2
  36. package/dist/cli/commands/reclaim.d.ts.map +1 -1
  37. package/dist/cli/commands/reclaim.js +204 -20
  38. package/dist/cli/commands/reclaim.js.map +1 -1
  39. package/dist/cli/commands/refund.d.ts +31 -0
  40. package/dist/cli/commands/refund.d.ts.map +1 -0
  41. package/dist/cli/commands/refund.js +397 -0
  42. package/dist/cli/commands/refund.js.map +1 -0
  43. package/dist/cli/commands/status.d.ts.map +1 -1
  44. package/dist/cli/commands/status.js +21 -2
  45. package/dist/cli/commands/status.js.map +1 -1
  46. package/dist/cli/commands/streams.js +8 -12
  47. package/dist/cli/commands/streams.js.map +1 -1
  48. package/dist/cli/commands/watch.d.ts.map +1 -1
  49. package/dist/cli/commands/watch.js +4 -1
  50. package/dist/cli/commands/watch.js.map +1 -1
  51. package/dist/cli/config.d.ts +44 -26
  52. package/dist/cli/config.d.ts.map +1 -1
  53. package/dist/cli/config.js +88 -38
  54. package/dist/cli/config.js.map +1 -1
  55. package/dist/cli/context.d.ts +19 -0
  56. package/dist/cli/context.d.ts.map +1 -1
  57. package/dist/cli/deps.d.ts +53 -0
  58. package/dist/cli/deps.d.ts.map +1 -1
  59. package/dist/cli/deps.js +51 -1
  60. package/dist/cli/deps.js.map +1 -1
  61. package/dist/cli/errors.d.ts.map +1 -1
  62. package/dist/cli/errors.js +21 -1
  63. package/dist/cli/errors.js.map +1 -1
  64. package/dist/cli/escape-hatch.d.ts +6 -4
  65. package/dist/cli/escape-hatch.d.ts.map +1 -1
  66. package/dist/cli/escape-hatch.js +6 -2
  67. package/dist/cli/escape-hatch.js.map +1 -1
  68. package/dist/cli/listings.d.ts +18 -0
  69. package/dist/cli/listings.d.ts.map +1 -1
  70. package/dist/cli/listings.js +22 -12
  71. package/dist/cli/listings.js.map +1 -1
  72. package/dist/cli/preflight.d.ts +20 -14
  73. package/dist/cli/preflight.d.ts.map +1 -1
  74. package/dist/cli/preflight.js +34 -20
  75. package/dist/cli/preflight.js.map +1 -1
  76. package/dist/cli/privy.d.ts.map +1 -1
  77. package/dist/cli/privy.js +4 -2
  78. package/dist/cli/privy.js.map +1 -1
  79. package/dist/cli/progress.d.ts +13 -1
  80. package/dist/cli/progress.d.ts.map +1 -1
  81. package/dist/cli/progress.js +7 -1
  82. package/dist/cli/progress.js.map +1 -1
  83. package/dist/cli/reclaimability.d.ts +82 -0
  84. package/dist/cli/reclaimability.d.ts.map +1 -0
  85. package/dist/cli/reclaimability.js +80 -0
  86. package/dist/cli/reclaimability.js.map +1 -0
  87. package/dist/cli/run.d.ts +2 -1
  88. package/dist/cli/run.d.ts.map +1 -1
  89. package/dist/cli/run.js +62 -21
  90. package/dist/cli/run.js.map +1 -1
  91. package/dist/cli/scan-range.d.ts +2 -2
  92. package/dist/cli/scan-range.js +2 -2
  93. package/dist/cli/status.d.ts +106 -1
  94. package/dist/cli/status.d.ts.map +1 -1
  95. package/dist/cli/status.js +325 -44
  96. package/dist/cli/status.js.map +1 -1
  97. package/dist/cli/wallet.d.ts +2 -2
  98. package/dist/cli/wallet.d.ts.map +1 -1
  99. package/dist/cli/wallet.js +29 -5
  100. package/dist/cli/wallet.js.map +1 -1
  101. package/dist/discovery/rate-band.d.ts +74 -0
  102. package/dist/discovery/rate-band.d.ts.map +1 -0
  103. package/dist/discovery/rate-band.js +114 -0
  104. package/dist/discovery/rate-band.js.map +1 -0
  105. package/dist/escrow-generation.d.ts +81 -0
  106. package/dist/escrow-generation.d.ts.map +1 -0
  107. package/dist/escrow-generation.js +76 -0
  108. package/dist/escrow-generation.js.map +1 -0
  109. package/dist/frameworks/agentkit.d.ts +8 -4
  110. package/dist/frameworks/agentkit.d.ts.map +1 -1
  111. package/dist/frameworks/agentkit.js +27 -10
  112. package/dist/frameworks/agentkit.js.map +1 -1
  113. package/dist/frameworks/eliza.d.ts +3 -3
  114. package/dist/frameworks/eliza.d.ts.map +1 -1
  115. package/dist/frameworks/eliza.js +15 -6
  116. package/dist/frameworks/eliza.js.map +1 -1
  117. package/dist/frameworks/langchain.d.ts.map +1 -1
  118. package/dist/frameworks/langchain.js +8 -0
  119. package/dist/frameworks/langchain.js.map +1 -1
  120. package/dist/gateway/caller-auth-gateway.d.ts +83 -31
  121. package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
  122. package/dist/gateway/caller-auth-gateway.js +180 -27
  123. package/dist/gateway/caller-auth-gateway.js.map +1 -1
  124. package/dist/gateway/http-server.d.ts +128 -44
  125. package/dist/gateway/http-server.d.ts.map +1 -1
  126. package/dist/gateway/http-server.js +506 -155
  127. package/dist/gateway/http-server.js.map +1 -1
  128. package/dist/gateway/probe-auth.d.ts +112 -0
  129. package/dist/gateway/probe-auth.d.ts.map +1 -0
  130. package/dist/gateway/probe-auth.js +127 -0
  131. package/dist/gateway/probe-auth.js.map +1 -0
  132. package/dist/gateway/signed-rate-bounds.d.ts +51 -0
  133. package/dist/gateway/signed-rate-bounds.d.ts.map +1 -0
  134. package/dist/gateway/signed-rate-bounds.js +94 -0
  135. package/dist/gateway/signed-rate-bounds.js.map +1 -0
  136. package/dist/gateway/x402-opener-mode.d.ts +252 -0
  137. package/dist/gateway/x402-opener-mode.d.ts.map +1 -0
  138. package/dist/gateway/x402-opener-mode.js +772 -0
  139. package/dist/gateway/x402-opener-mode.js.map +1 -0
  140. package/dist/index.d.ts +6 -2
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +10 -5
  143. package/dist/index.js.map +1 -1
  144. package/dist/mcp/server.d.ts +64 -1
  145. package/dist/mcp/server.d.ts.map +1 -1
  146. package/dist/mcp/server.js +247 -37
  147. package/dist/mcp/server.js.map +1 -1
  148. package/dist/mcp/x402-tools.d.ts +109 -0
  149. package/dist/mcp/x402-tools.d.ts.map +1 -0
  150. package/dist/mcp/x402-tools.js +554 -0
  151. package/dist/mcp/x402-tools.js.map +1 -0
  152. package/dist/network.d.ts +119 -0
  153. package/dist/network.d.ts.map +1 -0
  154. package/dist/network.js +199 -0
  155. package/dist/network.js.map +1 -0
  156. package/dist/sdk/client.d.ts +13 -0
  157. package/dist/sdk/client.d.ts.map +1 -1
  158. package/dist/sdk/client.js +15 -0
  159. package/dist/sdk/client.js.map +1 -1
  160. package/dist/sdk/invoke.d.ts +4 -0
  161. package/dist/sdk/invoke.d.ts.map +1 -1
  162. package/dist/sdk/invoke.js +2 -5
  163. package/dist/sdk/invoke.js.map +1 -1
  164. package/dist/wallet/autonomous-wallet-broker.d.ts +3 -0
  165. package/dist/wallet/autonomous-wallet-broker.d.ts.map +1 -1
  166. package/dist/wallet/autonomous-wallet-broker.js +27 -23
  167. package/dist/wallet/autonomous-wallet-broker.js.map +1 -1
  168. package/dist/wallet/autonomous-wallet-protocol.d.ts +8 -2
  169. package/dist/wallet/autonomous-wallet-protocol.d.ts.map +1 -1
  170. package/dist/wallet/autonomous-wallet-protocol.js +9 -2
  171. package/dist/wallet/autonomous-wallet-protocol.js.map +1 -1
  172. package/dist/wallet/autonomous-wallet.d.ts.map +1 -1
  173. package/dist/wallet/autonomous-wallet.js +3 -2
  174. package/dist/wallet/autonomous-wallet.js.map +1 -1
  175. package/dist/wallet/broker-escrows.d.ts +222 -0
  176. package/dist/wallet/broker-escrows.d.ts.map +1 -0
  177. package/dist/wallet/broker-escrows.js +36 -0
  178. package/dist/wallet/broker-escrows.js.map +1 -0
  179. package/dist/wallet/lifecycle.d.ts +21 -5
  180. package/dist/wallet/lifecycle.d.ts.map +1 -1
  181. package/dist/wallet/lifecycle.js +32 -12
  182. package/dist/wallet/lifecycle.js.map +1 -1
  183. package/dist/wallet/privy-broker-server.d.ts.map +1 -1
  184. package/dist/wallet/privy-broker-server.js +31 -26
  185. package/dist/wallet/privy-broker-server.js.map +1 -1
  186. package/dist/wallet/privy-session-broker.d.ts +14 -5
  187. package/dist/wallet/privy-session-broker.d.ts.map +1 -1
  188. package/dist/wallet/privy-session-broker.js +29 -17
  189. package/dist/wallet/privy-session-broker.js.map +1 -1
  190. package/dist/wallet/privy-session-provider.d.ts +3 -1
  191. package/dist/wallet/privy-session-provider.d.ts.map +1 -1
  192. package/dist/wallet/privy-session-provider.js +5 -3
  193. package/dist/wallet/privy-session-provider.js.map +1 -1
  194. package/dist/wallet/provider.d.ts.map +1 -1
  195. package/dist/wallet/provider.js +15 -8
  196. package/dist/wallet/provider.js.map +1 -1
  197. package/dist/x402/facilitator.d.ts +3 -30
  198. package/dist/x402/facilitator.d.ts.map +1 -1
  199. package/dist/x402/facilitator.js +0 -27
  200. package/dist/x402/facilitator.js.map +1 -1
  201. package/dist/x402-buyer/guarded-fetch.d.ts +51 -0
  202. package/dist/x402-buyer/guarded-fetch.d.ts.map +1 -0
  203. package/dist/x402-buyer/guarded-fetch.js +147 -0
  204. package/dist/x402-buyer/guarded-fetch.js.map +1 -0
  205. package/dist/x402-buyer/index.d.ts +10 -0
  206. package/dist/x402-buyer/index.d.ts.map +1 -0
  207. package/dist/x402-buyer/index.js +10 -0
  208. package/dist/x402-buyer/index.js.map +1 -0
  209. package/dist/x402-buyer/pay.d.ts +157 -0
  210. package/dist/x402-buyer/pay.d.ts.map +1 -0
  211. package/dist/x402-buyer/pay.js +366 -0
  212. package/dist/x402-buyer/pay.js.map +1 -0
  213. package/dist/x402-opener/abi.d.ts +684 -0
  214. package/dist/x402-opener/abi.d.ts.map +1 -0
  215. package/dist/x402-opener/abi.js +880 -0
  216. package/dist/x402-opener/abi.js.map +1 -0
  217. package/dist/x402-opener/facilitator-entry.d.ts +3 -0
  218. package/dist/x402-opener/facilitator-entry.d.ts.map +1 -0
  219. package/dist/x402-opener/facilitator-entry.js +40 -0
  220. package/dist/x402-opener/facilitator-entry.js.map +1 -0
  221. package/dist/x402-opener/facilitator-server.d.ts +209 -0
  222. package/dist/x402-opener/facilitator-server.d.ts.map +1 -0
  223. package/dist/x402-opener/facilitator-server.js +762 -0
  224. package/dist/x402-opener/facilitator-server.js.map +1 -0
  225. package/dist/x402-opener/index.d.ts +14 -0
  226. package/dist/x402-opener/index.d.ts.map +1 -0
  227. package/dist/x402-opener/index.js +14 -0
  228. package/dist/x402-opener/index.js.map +1 -0
  229. package/dist/x402-opener/listing-resolver.d.ts +55 -0
  230. package/dist/x402-opener/listing-resolver.d.ts.map +1 -0
  231. package/dist/x402-opener/listing-resolver.js +125 -0
  232. package/dist/x402-opener/listing-resolver.js.map +1 -0
  233. package/dist/x402-opener/refund-relay.d.ts +266 -0
  234. package/dist/x402-opener/refund-relay.d.ts.map +1 -0
  235. package/dist/x402-opener/refund-relay.js +1341 -0
  236. package/dist/x402-opener/refund-relay.js.map +1 -0
  237. package/dist/x402-opener/relayer-queue.d.ts +14 -0
  238. package/dist/x402-opener/relayer-queue.d.ts.map +1 -0
  239. package/dist/x402-opener/relayer-queue.js +18 -0
  240. package/dist/x402-opener/relayer-queue.js.map +1 -0
  241. package/dist/x402-opener/scheme.d.ts +296 -0
  242. package/dist/x402-opener/scheme.d.ts.map +1 -0
  243. package/dist/x402-opener/scheme.js +1035 -0
  244. package/dist/x402-opener/scheme.js.map +1 -0
  245. package/dist/x402-opener/terms.d.ts +165 -0
  246. package/dist/x402-opener/terms.d.ts.map +1 -0
  247. package/dist/x402-opener/terms.js +304 -0
  248. package/dist/x402-opener/terms.js.map +1 -0
  249. package/dist/zktls/reclaim.d.ts +2 -2
  250. package/dist/zktls/t2-delivery-proof.d.ts +6 -6
  251. package/docs/agent-layer.md +3 -2
  252. package/docs/autonomous-privy-wallet.md +188 -0
  253. package/docs/cli.md +265 -34
  254. package/docs/langchain.md +5 -0
  255. package/docs/mcp-reference.md +91 -5
  256. package/docs/probe-capability.md +191 -0
  257. package/docs/quickstart.md +39 -4
  258. package/docs/threat-model.md +20 -0
  259. package/docs/x402.md +766 -593
  260. package/package.json +19 -17
  261. package/src/capability/probe-capability.ts +355 -0
  262. package/src/cli/args.ts +29 -0
  263. package/src/cli/commands/claim.ts +168 -33
  264. package/src/cli/commands/close.ts +36 -3
  265. package/src/cli/commands/invoke.ts +5 -2
  266. package/src/cli/commands/mcp.ts +18 -2
  267. package/src/cli/commands/open.ts +75 -4
  268. package/src/cli/commands/pay.ts +584 -0
  269. package/src/cli/commands/reclaim-unverified.ts +8 -2
  270. package/src/cli/commands/reclaim.ts +277 -19
  271. package/src/cli/commands/refund.ts +475 -0
  272. package/src/cli/commands/status.ts +28 -2
  273. package/src/cli/commands/streams.ts +10 -14
  274. package/src/cli/commands/watch.ts +11 -1
  275. package/src/cli/config.ts +139 -52
  276. package/src/cli/context.ts +16 -0
  277. package/src/cli/deps.ts +111 -1
  278. package/src/cli/errors.ts +24 -1
  279. package/src/cli/escape-hatch.ts +19 -5
  280. package/src/cli/listings.ts +43 -10
  281. package/src/cli/preflight.ts +36 -21
  282. package/src/cli/privy.ts +4 -2
  283. package/src/cli/progress.ts +21 -2
  284. package/src/cli/reclaimability.ts +154 -0
  285. package/src/cli/run.ts +77 -21
  286. package/src/cli/scan-range.ts +2 -2
  287. package/src/cli/status.ts +454 -44
  288. package/src/cli/wallet.ts +45 -8
  289. package/src/discovery/rate-band.ts +172 -0
  290. package/src/escrow-generation.ts +135 -0
  291. package/src/frameworks/agentkit.ts +45 -11
  292. package/src/frameworks/eliza.ts +27 -5
  293. package/src/frameworks/langchain.ts +14 -0
  294. package/src/gateway/caller-auth-gateway.ts +320 -68
  295. package/src/gateway/http-server.ts +717 -220
  296. package/src/gateway/probe-auth.ts +191 -0
  297. package/src/gateway/signed-rate-bounds.ts +164 -0
  298. package/src/gateway/x402-opener-mode.ts +1171 -0
  299. package/src/index.ts +82 -19
  300. package/src/mcp/server.ts +351 -42
  301. package/src/mcp/x402-tools.ts +785 -0
  302. package/src/network.ts +302 -0
  303. package/src/sdk/client.ts +25 -0
  304. package/src/sdk/invoke.ts +6 -5
  305. package/src/wallet/autonomous-wallet-broker.ts +46 -20
  306. package/src/wallet/autonomous-wallet-protocol.ts +11 -2
  307. package/src/wallet/autonomous-wallet.ts +4 -2
  308. package/src/wallet/broker-escrows.ts +66 -0
  309. package/src/wallet/lifecycle.ts +53 -18
  310. package/src/wallet/privy-broker-server.ts +35 -25
  311. package/src/wallet/privy-session-broker.ts +58 -25
  312. package/src/wallet/privy-session-provider.ts +7 -3
  313. package/src/wallet/provider.ts +20 -15
  314. package/src/x402/facilitator.ts +3 -43
  315. package/src/x402-buyer/guarded-fetch.ts +213 -0
  316. package/src/x402-buyer/index.ts +9 -0
  317. package/src/x402-buyer/pay.ts +555 -0
  318. package/src/x402-opener/abi.ts +881 -0
  319. package/src/x402-opener/facilitator-entry.ts +48 -0
  320. package/src/x402-opener/facilitator-server.ts +1114 -0
  321. package/src/x402-opener/index.ts +13 -0
  322. package/src/x402-opener/listing-resolver.ts +202 -0
  323. package/src/x402-opener/refund-relay.ts +1873 -0
  324. package/src/x402-opener/relayer-queue.ts +18 -0
  325. package/src/x402-opener/scheme.ts +1551 -0
  326. package/src/x402-opener/terms.ts +453 -0
  327. package/dist/gateway/x402-mode.d.ts +0 -123
  328. package/dist/gateway/x402-mode.d.ts.map +0 -1
  329. package/dist/gateway/x402-mode.js +0 -212
  330. package/dist/gateway/x402-mode.js.map +0 -1
  331. package/dist/x402/delivery-oracle.d.ts +0 -108
  332. package/dist/x402/delivery-oracle.d.ts.map +0 -1
  333. package/dist/x402/delivery-oracle.js +0 -174
  334. package/dist/x402/delivery-oracle.js.map +0 -1
  335. package/dist/x402/facilitator-server-entry.d.ts +0 -3
  336. package/dist/x402/facilitator-server-entry.d.ts.map +0 -1
  337. package/dist/x402/facilitator-server-entry.js +0 -27
  338. package/dist/x402/facilitator-server-entry.js.map +0 -1
  339. package/dist/x402/facilitator-server.d.ts +0 -73
  340. package/dist/x402/facilitator-server.d.ts.map +0 -1
  341. package/dist/x402/facilitator-server.js +0 -305
  342. package/dist/x402/facilitator-server.js.map +0 -1
  343. package/dist/x402/payment-requirements.d.ts +0 -106
  344. package/dist/x402/payment-requirements.d.ts.map +0 -1
  345. package/dist/x402/payment-requirements.js +0 -138
  346. package/dist/x402/payment-requirements.js.map +0 -1
  347. package/dist/x402/relayer.d.ts +0 -41
  348. package/dist/x402/relayer.d.ts.map +0 -1
  349. package/dist/x402/relayer.js +0 -64
  350. package/dist/x402/relayer.js.map +0 -1
  351. package/dist/x402/resource-index.d.ts +0 -75
  352. package/dist/x402/resource-index.d.ts.map +0 -1
  353. package/dist/x402/resource-index.js +0 -126
  354. package/dist/x402/resource-index.js.map +0 -1
  355. package/dist/x402/verified-delivery-facilitator.d.ts +0 -285
  356. package/dist/x402/verified-delivery-facilitator.d.ts.map +0 -1
  357. package/dist/x402/verified-delivery-facilitator.js +0 -423
  358. package/dist/x402/verified-delivery-facilitator.js.map +0 -1
  359. package/src/gateway/x402-mode.ts +0 -350
  360. package/src/x402/delivery-oracle.ts +0 -285
  361. package/src/x402/facilitator-server-entry.ts +0 -35
  362. package/src/x402/facilitator-server.ts +0 -445
  363. package/src/x402/payment-requirements.ts +0 -231
  364. package/src/x402/relayer.ts +0 -122
  365. package/src/x402/resource-index.ts +0 -228
  366. package/src/x402/verified-delivery-facilitator.ts +0 -744
@@ -36,11 +36,16 @@ an economic question, not a cryptographic one.
36
36
  | `reclaim_unspent` | **yes** | Closes the stream (**stops the meter**) and returns unspent USDC to the buyer. |
37
37
  | `list_streams` | no | Lists the streams this server opened. |
38
38
  | `prove_https_response` | no | Buyer-side zkTLS proof of a response. Registered only with Reclaim credentials. |
39
+ | `x402_pay` | **yes** | Pays an x402 endpoint (opener path, docs/x402.md) under the spend mandate. |
40
+ | `refund_status` | no | Refund state of an x402 stream, from the seller's gasless refund relay. |
41
+ | `refund` | **yes** | Gasless close (payer-signed) and reclaim of an opener stream via the relay. |
39
42
 
40
43
  ### `discover_services`
41
44
 
42
45
  `category?`, `limit?`. Returns `serviceRef`, `operator`, `publicUrl`, `access`,
43
- `accessUrl` per listing.
46
+ `accessUrl` per listing, plus `ratePerSecondBand` (`minRatePerSecondUsdc` /
47
+ `maxRatePerSecondUsdc`, atomic units, `null` for an unsigned side) when the
48
+ operator's signed descriptor carries one.
44
49
 
45
50
  The registry is an **untrusted transport**: every `serviceRef` is re-derived
46
51
  from the signed record and the operator's signature recovered, so a compromised
@@ -80,6 +85,16 @@ warning on the result. It is operator-set input only — never a tool argument,
80
85
  because the caller being asked to relax a fund-safety check is the LLM the check
81
86
  exists to constrain — and it never overrides a positive contradiction.
82
87
 
88
+ `ratePerSecondUsdc` must sit inside the listing's operator-signed rate band
89
+ (inclusive). The descriptor is re-verified with the gateway's own check, and an
90
+ out-of-band rate is refused before any approval or open is sent: the escrow
91
+ accepts any non-zero rate, but the seller's gateway answers every request on
92
+ such a stream with `403 stream-rate-out-of-bounds`. A descriptor that does not
93
+ verify is a refusal. Under `METRIK_AGENT_ALLOW_UNVERIFIED_OPERATOR=true` an
94
+ unverifiable pairing has no listing to read a band from, and the result's
95
+ warning says the rate was not checked; the override never widens a band the
96
+ server can read.
97
+
83
98
  The result carries a `metering` block: the rate, the budget, the maximum this
84
99
  stream can ever spend, and what starts and stops the clock.
85
100
 
@@ -151,6 +166,72 @@ evidence of a delivery that did not happen here.
151
166
  This is an L2 signal to be cross-checked with oracle/L1 evidence — not a
152
167
  substitute for it.
153
168
 
169
+ ### `x402_pay`
170
+
171
+ Arguments: `url` (https; http only for localhost), optional `method`, `headers`,
172
+ `body`, `maxAmountUsdc`, `maxRatePerSecondUsdc` (base units; these can only TIGHTEN
173
+ the server mandate), `minDurationSeconds` and `maxUpfrontAmountUsdc` (the cap on the
174
+ non-refundable part of an upfront quote, passed to the SDK as `maxUpfrontAmount`;
175
+ default 100000 = 0.10 USDC, `"0"` refuses any upfront charge). Pays through `@absol-labs/sdk/x402`'s
176
+ `payWithX402` with every SDK protection intact, and is gated exactly like
177
+ `hire_verified_service`: the same server mandate, the same cumulative spend, the
178
+ same per-owner serialization. The payment's opener terms are checked by
179
+ `checkMandate()` before anything is signed — `amount` (the deposit) against the
180
+ per-stream and total caps, `ratePerSecond`, `maxDuration` and `operator` against
181
+ theirs — and the stream it opens is recorded, so it counts against `maxTotalUsdc`.
182
+ A caller-supplied `PAYMENT-SIGNATURE` / `X-PAYMENT` header is refused. The result
183
+ carries the response (bounded), `streamId`, `opener`, `refundUrl`, the deposit and
184
+ rate, and `refundIsAutomatic: false`.
185
+
186
+ **Upfront ("minimum charge") quotes.** The price splits into a non-refundable per-call
187
+ charge (`upfrontAmount`, paid to the seller at settlement) and a metered, refundable
188
+ deposit (`price - upfrontAmount`). The mandate counts the WHOLE price as spend; the
189
+ upfront part is non-recoverable. The result describes the split (`openerKind`,
190
+ `priceUsdc`, `nonRefundableUsdc`, `refundableUsdc`, `upfrontFeeUsdc`, `upfrontOnly`) and
191
+ the stream record carries `nonRefundableUsdc`. An **upfront-only** payment
192
+ (`upfrontAmount == price`) opens no stream: `streamId` is `null`, no stream (and no
193
+ reservation) is recorded — only a non-stream charge that keeps counting against
194
+ `maxTotalUsdc` and shows in `list_streams` as such — and the payment is final.
195
+
196
+ ### `refund_status` / `refund`
197
+
198
+ Arguments: `streamId`, optional `refundUrl` (not needed for a stream this server
199
+ paid for — it was recorded); `refund` also takes `close` (stop the stream first;
200
+ this wallet signs the opener's `Close`, authorized on the mandate owner's signature
201
+ alone like `reclaim_unspent`) and `reclaim` (default `true`; the permissionless
202
+ reclaim, the refund always goes to the payer). Every relay success is verified
203
+ against the transaction receipt. A settlement window that is still open is reported
204
+ with when to retry. Nothing is refunded automatically. For an upfront-only payment —
205
+ `streamId` 0, or the charge key `x402_pay` recorded — both tools answer "nothing to
206
+ refund" (not an error) without contacting a relay or signing anything. The default
207
+ refund client is given read-only chain access, so it can prove an opener's kind
208
+ on-chain and sign an upfront opener's `Close` under its own EIP-712 domain.
209
+
210
+ The three x402 tools are **opt-in**: set `METRIK_AGENT_X402=1` to register them
211
+ (any other value, or none, leaves them off).
212
+
213
+ **Spend is reserved before anything is signed.** When the mandate approves a
214
+ quote, `x402_pay` records a reservation for its amount in the stream registry
215
+ — inside the same per-owner queue as `hire_verified_service` — before the SDK
216
+ signs the EIP-3009 authorization. If anything fails after the signature has
217
+ been handed out (a malformed or missing `PAYMENT-RESPONSE`, an unconfirmed
218
+ settlement, a dropped connection), the outcome is established from the chain,
219
+ never from the seller: USDC `authorizationState(from, nonce)` and the opener's
220
+ `StreamOpenedFor(streamId, payer, authorizationNonce, …)` log. The reservation
221
+ is released only if the authorization was never used AND has expired; a stream
222
+ found on-chain replaces it under its real `streamId`; otherwise it stays
223
+ counted, and the tool says the money may have moved — never "not settled".
224
+
225
+ **Outbound requests are guarded.** The URLs the payment tool, `refund` and
226
+ `refund_status` contact come from the model, so every request must be `https:`
227
+ to a host whose every resolved address is public (loopback, private,
228
+ link-local, unique-local, CGNAT, multicast and reserved ranges are refused),
229
+ redirects are never followed, and `METRIK_AGENT_X402_ALLOWED_HOSTS`
230
+ (comma-separated, exact or `*.domain`) restricts it further — list the seller
231
+ AND its refund relay. A response that did not ask for payment is not returned
232
+ (status only). The address is checked at lookup time; use the allowlist where
233
+ DNS rebinding matters.
234
+
154
235
  ## Configuration
155
236
 
156
237
  `metrik mcp --print-config` prints the `mcpServers` block to paste into an MCP
@@ -159,11 +240,15 @@ in it are placeholders by design.
159
240
 
160
241
  | Variable | Required | Meaning |
161
242
  | ---------------------------------------- | -------- | ----------------------------------------------------------------------------------------------- |
162
- | `METRIK_AGENT_RPC_URL` | **yes** | Base Sepolia RPC. |
163
- | `METRIK_AGENT_ESCROW` | **yes** | The **StreamEscrowV2** address. The retired V1 escrow is refused. |
243
+ | `METRIK_AGENT_RPC_URL` | **yes** | RPC of the selected network. |
244
+ | `METRIK_AGENT_ESCROW` | **yes** | The network's registered **StreamEscrowV2**. Anything else is refused (see below). |
164
245
  | `METRIK_AGENT_USDC` | **yes** | Settlement token. |
165
246
  | `METRIK_AGENT_ESCROW_GENERATION` | no | `v2` (default, and the only supported value). Declared, not probed. |
166
- | `METRIK_AGENT_CHAIN_ID` | no | Defaults to `84532`; nothing else is accepted. |
247
+ | `METRIK_NETWORK` | no | Registry id, chain id or CAIP-2 id; default the registry's default network. Testnets only. |
248
+ | `METRIK_AGENT_CHAIN_ID` | no | Alternative selector; must agree with `METRIK_NETWORK` when both are set. |
249
+ | `METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW` | no | `true` accepts an escrow the registry does not record (a deliberate private deployment). |
250
+ | `METRIK_AGENT_X402` | no | `1` registers `x402_pay`, `refund_status`, `refund`. **Off by default.** |
251
+ | `METRIK_AGENT_X402_ALLOWED_HOSTS` | no | Comma-separated hosts (`*.domain` allowed) the x402 tools may contact. Unset = any public host. |
167
252
  | `METRIK_AGENT_REGISTRY_URL` | no | Registry the invocation origin is verified against. |
168
253
  | `METRIK_AGENT_ALLOW_UNVERIFIED_OPERATOR` | no | `true` downgrades an unverifiable operator pairing from a refusal to a warning. Off by default. |
169
254
  | `METRIK_AGENT_MCP_STATE_FILE` | no | Persists the stream registry across restarts. |
@@ -171,5 +256,6 @@ in it are placeholders by design.
171
256
  | wallet vars | **yes** | `METRIK_AGENT_PRIVATE_KEY`, or the CDP/Privy set. |
172
257
 
173
258
  The escrow generation is a **declared fact, not an inference**: the server
174
- refuses the retired V1 address by name, because a wrong-generation read does not
259
+ refuses any escrow that is not the network's registered `StreamEscrowV2` (the
260
+ retired V1 escrow included) unless `METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW=true`, because a wrong-generation read does not
175
261
  revert — it returns confident nonsense about someone's money.
@@ -0,0 +1,191 @@
1
+ # `ProbeCapability` — letting the verifier check a gated paid route
2
+
3
+ > **What this changes for you as a seller:** if your listing is `gated`, the
4
+ > Metrik oracle currently cannot call your paid route at all. It can only reach
5
+ > your health route. Adopting `ProbeCapability` is what turns "the health
6
+ > endpoint answered" into "the paid route delivered". It is **opt-in**, and a
7
+ > gateway that does not adopt it is reported by the oracle as **unverifiable**
8
+ > for the paid route — never as passing.
9
+
10
+ ## The problem
11
+
12
+ A gated listing's paid route requires an `InvocationCapability` (see
13
+ [`../src/capability/invocation-capability.ts`](../src/capability/invocation-capability.ts))
14
+ signed by **the stream's buyer**: 60s TTL, single-use nonce, bound to one method
15
+ and one path. The gateway recovers that signature and checks it against
16
+ `stream.buyer` on-chain.
17
+
18
+ The oracle is not a buyer. It holds no buyer key and has no stream. So for a
19
+ gated listing there is no credential a regional probe worker could safely
20
+ present, and the paid route goes unprobed by anybody — not remotely, and not
21
+ even by the coordinator.
22
+
23
+ That is a real gap in a claim the product makes. "The seller earns only for
24
+ intervals that verified" must not quietly mean "the health endpoint answered".
25
+
26
+ ## The mechanism
27
+
28
+ A second EIP-712 type, `ProbeCapability`, signed by an **oracle signer**:
29
+
30
+ ```
31
+ ProbeCapability {
32
+ bytes32 serviceRef; // the service being probed
33
+ address oracle; // the minting signer; must equal the recovered signer
34
+ string method; // exactly one HTTP method
35
+ bytes32 pathHash; // keccak256 of exactly one request path
36
+ bytes32 nonce; // single-use
37
+ uint64 expiry; // ~30s
38
+ }
39
+ ```
40
+
41
+ EIP-712 domain: `name = "Metrik Probe Capability"`, `version = "1"`, the
42
+ gateway's `chainId`, and `verifyingContract` = the `StreamEscrowV2` the gateway
43
+ already checks streams against. The domain name is deliberately **different**
44
+ from `"Metrik Invocation Capability"`, so a buyer's invocation signature can
45
+ never be replayed as a free probe and vice versa.
46
+
47
+ Transport: base64url in the `x-metrik-probe-capability` request header.
48
+
49
+ ### Authority is public, not a shared secret
50
+
51
+ The gateway authorizes the probe iff the recovered signer is in
52
+ `StreamEscrowV2.getOracleSigners()` — a **public `view`** on the escrow. On the
53
+ live Base Sepolia deployment (`0x0f09f36Ccc05A7c9882F438721C08De314dFd46C`) that
54
+ returns three addresses today.
55
+
56
+ This is the whole reason the design beats "give the workers a credential":
57
+
58
+ - **Nothing to distribute.** No API key, no registration, no onboarding call.
59
+ A third-party seller performs the identical on-chain read and reaches the
60
+ identical verdict, with no trust in the oracle's say-so.
61
+ - **Nothing to leak.** The only secret is the oracle coordinator's signing key,
62
+ which never leaves it.
63
+ - **Revocation is already solved.** Remove a signer on the escrow and it stops
64
+ being able to probe within one signer-cache TTL (60s by default).
65
+
66
+ ### No signing key leaves the coordinator
67
+
68
+ The oracle coordinator mints **one fresh capability per probe, per vantage** and
69
+ hands the encoded header to the worker alongside the nonce. Workers hold a
70
+ bearer token for the coordinator and nothing else. Compromising every regional
71
+ worker yields no ability to mint a capability, and no ability to probe anything
72
+ the coordinator did not already ask for — the capability is bound to one
73
+ service, one method, one path, one nonce and ~30 seconds.
74
+
75
+ One capability **per vantage**, not one per probe, because the nonce is
76
+ single-use: two vantages sharing a capability would see the second rejected as a
77
+ replay.
78
+
79
+ ### A probe never consumes a buyer's budget
80
+
81
+ Three independent reasons, all structural rather than conventional:
82
+
83
+ 1. A `ProbeCapability` carries **no `streamId` and no `buyer`**. There is no
84
+ stream identified for it to be billed against.
85
+ 2. Accrual is only ever produced by an **oracle checkpoint keyed to a stream**.
86
+ A gateway call produces none, ever — the gateway is an authorizer and proxy,
87
+ not a meter.
88
+ 3. In the reference gateway the probe branch is evaluated **first and returns
89
+ unconditionally**, so it can never fall through into the stream branch. An
90
+ authorized probe decision carries only the probe — no `stream`, no
91
+ capability, nothing billable. A test asserts the stream reader is never even
92
+ called.
93
+
94
+ The gateway additionally sets `x-metrik-probe: 1` on the **upstream** request so
95
+ your own metering and analytics can exclude verification traffic. It is
96
+ gateway-asserted: any inbound copy from a client is stripped first, so a paying
97
+ caller cannot mark its own billable request as a probe.
98
+
99
+ ### Rate limiting: why 60/minute
100
+
101
+ Without a bound, an oracle-signed capability is a licence to consume your
102
+ service indefinitely for free.
103
+
104
+ The oracle checkpoints every ~15–22 seconds, so **one** vantage probes a given
105
+ service at most ~4 times a minute. T1 multi-vantage fans that out across at most
106
+ 9 vantages (the coordinator plus the oracle's 8-worker cap), giving a worst-case
107
+ honest load of **~36 probes/minute**. The default budget is **60 per minute**:
108
+ comfortable headroom for a re-execution sample and clock jitter, while capping
109
+ the free-service surface at one request per second — far below anything
110
+ economically interesting as a way to get the service for nothing.
111
+
112
+ Tune it with `METRIK_GATEWAY_PROBE_MAX_PER_MINUTE`. A useful side effect: since
113
+ every capability is single-use and ~30s-lived, the rate limit is also what
114
+ bounds the replay cache — at most `maxPerMinute × ttl/60` probe nonces live at
115
+ once.
116
+
117
+ ### Replay protection reuses the existing cache
118
+
119
+ The probe path calls the **same** `NonceReplayCache` the buyer path uses, so a
120
+ nonce can never be spent once as a probe and once as an invocation. The nonce is
121
+ consumed _before_ the rate-limit check, so a captured capability replayed at
122
+ volume is rejected as a replay rather than eating the honest probe budget, and
123
+ _before_ the on-chain signer read, so a flood cannot amplify into RPC load.
124
+
125
+ ## Enabling it
126
+
127
+ ```bash
128
+ METRIK_GATEWAY_PROBE_CAPABILITY=true # off by default
129
+ METRIK_GATEWAY_PROBE_MAX_PER_MINUTE=60 # optional
130
+ METRIK_GATEWAY_PROBE_SIGNER_CACHE_SECONDS=60 # optional
131
+ ```
132
+
133
+ There is **no credential to configure**. The signer set is read from the
134
+ `METRIK_GATEWAY_RPC_URL` and escrow the gateway already uses.
135
+
136
+ Not using the reference gateway? Implement the verification in this exact order
137
+ — cheapest and most local first, so an unsigned or replayed flood cannot amplify
138
+ into RPC load:
139
+
140
+ 1. Decode the `x-metrik-probe-capability` header; malformed → `403`.
141
+ 2. `now >= expiry` → `403`.
142
+ 3. Recover the EIP-712 signer; it must equal `capability.oracle` → else `403`.
143
+ 4. `capability.method` must equal the request method → else `403`.
144
+ 5. `keccak256(path)` must equal `capability.pathHash` (path only — no query, no
145
+ fragment, no trailing slash except root) → else `403`.
146
+ 6. `capability.serviceRef` must be one you front → else `403`.
147
+ 7. Consume `capability.nonce` single-use → replay is `403`.
148
+ 8. Rate-limit → `429`.
149
+ 9. `capability.oracle` must be in `getOracleSigners()` → else `403`. If you
150
+ cannot read the set, **deny** (`403`); do not serve on a stale set.
151
+
152
+ Then proxy upstream and **do not meter, bill or settle the call**.
153
+
154
+ ## Failure modes, and what each one means to the oracle
155
+
156
+ Every denial is a distinct machine-readable `reason` in the JSON body:
157
+
158
+ | Reason | Status | What it means |
159
+ | ----------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------- |
160
+ | `probe-not-enabled` | 403 | This gateway has not adopted `ProbeCapability`. The oracle reports the paid route **unverifiable**. |
161
+ | `malformed-probe-capability` | 403 | Header was not a decodable signed capability. |
162
+ | `probe-capability-expired` | 403 | Past `expiry`. |
163
+ | `probe-invalid-signature` | 403 | Signature does not recover to the claimed `oracle`. |
164
+ | `probe-method-mismatch` / `probe-path-mismatch` | 403 | Capability was not minted for this route. |
165
+ | `probe-service-ref-mismatch` | 403 | Minted for another service. |
166
+ | `probe-nonce-replayed` | 403 | Nonce already spent. |
167
+ | `probe-rate-limited` | 429 | Free-probe budget for this window is spent. |
168
+ | `probe-signer-not-authorized` | 403 | Signer is not in `getOracleSigners()`. |
169
+ | `probe-signer-set-unavailable` | 403 | The set could not be read fresh enough. Fail closed. |
170
+ | `ambiguous-probe-mode` | 400 | Request carried a probe capability _and_ a buyer capability or `X-PAYMENT`. Refused rather than guessed. |
171
+
172
+ ## Honest limitations
173
+
174
+ - **This is an adoption ask.** We ship the reference implementation because we
175
+ run the reference gateway, but a third-party seller has to adopt it. Until
176
+ they do, their gated paid route stays health-route-only and the oracle says so
177
+ out loud rather than implying coverage it does not have.
178
+ - **A gated seller can stay unverifiable on purpose.** A hostile gateway can
179
+ simply answer `403` to every probe and remain in the "unverifiable" bucket.
180
+ That gains it nothing it does not already have today — under the oracle's
181
+ `strict` enforcement an unverifiable paid route accrues nothing, and under
182
+ `eligible` it is exactly today's health-route-only behaviour, counted and
183
+ surfaced. What it can never do is be counted as _passing_.
184
+ - **Delivery, not correctness.** A passing probe is evidence the paid route
185
+ produced a fresh, spec-conforming response for _this_ challenge. It is not a
186
+ proof the output is correct. See
187
+ [`../../streamproof-protocol/docs/verification-model.md`](https://github.com/Absol-Labs/metrik-protocol/blob/main/docs/verification-model.md).
188
+ - **The signer set is federated today.** All three addresses in
189
+ `getOracleSigners()` are keys held by one process, and one of them operates
190
+ the live listings. `ProbeCapability` inherits that posture exactly — it makes
191
+ the _paid route_ checkable, not the verifier independent.
@@ -111,7 +111,7 @@ const account = wallet.account;
111
111
  const mandate = spendMandateSchema.parse({
112
112
  maxPerStreamUsdc: 100_000n, // 0.1 USDC (6 decimals) — max deposit per stream
113
113
  maxTotalUsdc: 100_000n, // cumulative cap across all streams
114
- maxRatePerSecondUsdc: 1n, // max accrual rate the agent may agree to
114
+ maxRatePerSecondUsdc: 100n, // max accrual rate the agent may agree to (0.0001 USDC/s)
115
115
  maxDurationSeconds: 3600, // max stream lifetime
116
116
  allowedOperators: [account.address as `0x${string}`], // omit/empty ⇒ any operator
117
117
  expiresAt: nowSeconds + 365 * 24 * 60 * 60,
@@ -228,7 +228,7 @@ const hire = await agent.openVerifiedStream({
228
228
  operator: target.operator, // seller payout wallet from discovery
229
229
  serviceRef: target.serviceRef, // bytes32 the oracle verifies delivery against
230
230
  budgetUsdc: 100_000n,
231
- ratePerSecondUsdc: 1n,
231
+ ratePerSecondUsdc: 100n, // must sit inside the listing's signed rate band
232
232
  maxDurationSeconds: 3600,
233
233
  signedMandate,
234
234
  spentSoFarUsdc: 0n,
@@ -293,6 +293,40 @@ const { reclaimResult } = await agent.reclaimStream({
293
293
  escape-window rules, and `claimed + fees + reclaimed == deposit` conserves the escrowed
294
294
  funds exactly.
295
295
 
296
+ ### 7b. Or pay a seller over x402 (no gas needed)
297
+
298
+ A Metrik x402 seller (an HTTP `402` with an x402 v2 `exact` quote) can be paid in one
299
+ call. The payment opens the same kind of stream — `deposit = price`, earned per verified
300
+ second — and the same spend mandate bounds it before anything is signed:
301
+
302
+ ```ts
303
+ import { defaultNetwork } from "@absol-labs/agent";
304
+ import { payX402WithMandate } from "@absol-labs/agent/x402-buyer";
305
+
306
+ const paid = await payX402WithMandate({
307
+ url: "https://x402.137.23.50.249.sslip.io/transform",
308
+ init: {
309
+ method: "POST",
310
+ headers: { "content-type": "application/json" },
311
+ body: '{"text":"# hi"}',
312
+ },
313
+ signer: account, // USDC only, no ETH
314
+ network: defaultNetwork(),
315
+ signedMandate,
316
+ spentSoFarUsdc: 0n,
317
+ nowSeconds: Math.floor(Date.now() / 1000),
318
+ });
319
+ console.log(paid.metrikStreamOpener); // streamId, opener, refundUrl, deposit, rate
320
+ ```
321
+
322
+ Getting the unearned part back is not automatic: close, then reclaim, through the seller's
323
+ gasless relay (the `metrik refund` command below, or the SDK's `refundClient`).
324
+
325
+ Full details, including the mandate mapping: [the buyer section](./x402.md).
326
+
327
+ From a terminal: `metrik pay <url> --json '{"text":"# hi"}' --max-amount 0.1`, then
328
+ `metrik refund <streamId> --refund-url <url> --close --reclaim`.
329
+
296
330
  ---
297
331
 
298
332
  ## 8. Five ways to integrate
@@ -306,7 +340,7 @@ loop (`discover -> open -> invoke -> watch -> close -> reclaim`):
306
340
  ```bash
307
341
  npx --package @absol-labs/agent metrik discover --min-tier 2
308
342
  npx --package @absol-labs/agent metrik open --service <serviceRef> \
309
- --rate 0.000001 --budget 0.27 --duration 1h --dry-run
343
+ --rate 0.0001 --budget 0.36 --duration 1h --dry-run
310
344
  ```
311
345
 
312
346
  Testnet-only, key read from `METRIK_AGENT_PRIVATE_KEY`, `--json` for scripting. Full
@@ -394,4 +428,5 @@ Metrik proves **verified delivery** (responded, correctly-shaped, within SLA to
394
428
  traffic) — **not output correctness** (that the model's answer is right). Pitch and treat
395
429
  it accordingly. The full journey, run live against the real deployed escrow on Base
396
430
  Sepolia, is [`scripts/e2e-cdp.ts`](../scripts/e2e-cdp.ts) (`pnpm build:e2e && pnpm
397
- e2e:cdp`) — the working reference for everything above.
431
+ e2e:cdp`) — the working reference for everything above; its x402 leg is
432
+ [`scripts/e2e-x402.ts`](../scripts/e2e-x402.ts) (`pnpm e2e:x402`).
@@ -22,6 +22,9 @@ In scope:
22
22
  - the mandate-gated SDK wrapper in [`src/sdk/client.ts`](../src/sdk/client.ts)
23
23
  - Metrik `402` verified-stream opens in [`src/x402/facilitator.ts`](../src/x402/facilitator.ts)
24
24
  (a Metrik-native challenge flow, not the x402 protocol — see [docs/x402.md](./x402.md))
25
+ - the x402 v2 opener facilitator in [`src/x402-opener/`](../src/x402-opener/) and the
26
+ gateway's x402 v2 mode in [`src/gateway/x402-opener-mode.ts`](../src/gateway/x402-opener-mode.ts)
27
+ (both off by default — see [docs/x402.md](./x402.md))
25
28
  - MCP fund-moving tools in [`src/mcp/server.ts`](../src/mcp/server.ts)
26
29
  - zkTLS consumer delivery proofs in [`src/zktls/reclaim.ts`](../src/zktls/reclaim.ts)
27
30
  - wallet custody in [`src/wallet/provider.ts`](../src/wallet/provider.ts)
@@ -43,6 +46,23 @@ Out of scope:
43
46
  | Mandate replay | A previously valid signed mandate is replayed after revocation or outside the intended lifetime. | Mandates are EIP-712 signed over `mandateId`, `owner`, `chainId`, `issuedAt`, expiry, and policy hashes. `checkMandate()` verifies signature, revocation, expiry, then policy. Integrators can revoke by `mandateId` and track cumulative spend. | [`src/mandates/mandate.ts`](../src/mandates/mandate.ts), [`test/mandate.test.ts`](../test/mandate.test.ts), [`test/mcp.test.ts`](../test/mcp.test.ts) | Replay protection is only as strong as revocation and cumulative-spend state freshness. The signed payload itself is bound to one chain and one owner, but revocation is local state today. |
44
47
  | False or weak delivery evidence | A consumer submits a proof that is validly signed by the zkTLS system but not bound to the intended request or stream context. | The Reclaim adapter re-verifies every proof locally against the exact URL/method/body/match/redaction spec supplied by the caller, and binds the proof context to buyer/stream metadata before returning it. MCP only exposes the public-request subset so secrets do not leak through tool arguments. | [`src/zktls/reclaim.ts`](../src/zktls/reclaim.ts), [`src/x402/facilitator.ts`](../src/x402/facilitator.ts), [`src/mcp/server.ts`](../src/mcp/server.ts), [`test/reclaim.test.ts`](../test/reclaim.test.ts), [`test/x402.test.ts`](../test/x402.test.ts), [`test/mcp.test.ts`](../test/mcp.test.ts) | L2 is still consumer-supplied evidence. A malicious consumer can withhold proofs, and settlement must still treat L1/oracle evidence as authoritative on failure, ambiguity, and expiry. |
45
48
 
49
+ ## x402 v2 opener threats
50
+
51
+ The opener path moves a buyer's USDC only under that buyer's own EIP-3009
52
+ signature, so no agent-held key spends and no spend mandate applies; the
53
+ threats are about where the signed payment can end up.
54
+
55
+ | Threat | Mitigation | Code / tests |
56
+ | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
57
+ | A stock x402 facilitator (or anyone holding the signed payload) submits `transferWithAuthorization` to the opener directly, stranding the payment | Only `MetrikOpenerExactScheme` settles, via `deployAndSettle`; its stock verifier gets a signer that cannot broadcast. The gateway refuses to start, and fails closed (503) later, unless the facilitator's `/supported` marker names its exact factory/escrow/token/chain. `PAYMENT-SIGNATURE` is never forwarded upstream. No discovery extension is ever declared. | `src/x402-opener/scheme.ts`, `src/gateway/x402-opener-mode.ts`; `test/x402-opener-gateway.test.ts`, `test/x402-opener.fork.int.test.ts` |
58
+ | A forged or tampered `payTo`, terms or listing redirects funds | `payTo` must equal `factory.predict(terms)` read on-chain (never code/codehash); `(operator, serviceRef)` must resolve to exactly one operator-signed listing via discovery signature verification; amount bounded by `rate × maxDuration` | `test/x402-opener-scheme.test.ts`, `test/x402-opener-config.test.ts` |
59
+ | Success reported without a stream | `settle` returns `success: true` only from a mined receipt carrying the escrow `StreamOpened` (buyer = opener) and the opener `StreamOpenedFor` for this payer/nonce/amount | `test/x402-opener-scheme.test.ts` |
60
+ | One payment buys several served responses (a replayed `PAYMENT-SIGNATURE`) | At most one successful settle per authorization (chain + asset + payer + nonce) per facilitator instance; the gateway refuses an in-flight or settled authorization with `409` before the upstream runs. In-memory, per instance: run one instance | `test/x402-opener-scheme.test.ts`, `test/x402-opener-gateway.test.ts`, `test/x402-opener-regressions.fork.int.test.ts` |
61
+ | Concurrent payers fail on the relayer nonce after being served | Relayer submissions are serialized | same |
62
+ | Dust payments drain relayer gas; unauthenticated `/verify` amplifies RPC load | Minimum amount (default 0.01 USDC, advertised; the gateway refuses a lower price), `/settle` rate limit per token, `/verify` requires the token by default | `test/x402-opener-config.test.ts`, fork regressions |
63
+ | A failed upstream still charges the buyer | The gateway settles only when the upstream status is `< 400`, and a failed settle sends no body | `test/x402-opener-gateway.test.ts`, fork E2E |
64
+ | Relayer key or facilitator token leaks | Env only, never logged or echoed in errors; `/settle` requires the bearer token (constant-time compared) | `src/x402-opener/facilitator-server.ts`; `test/x402-opener-config.test.ts` |
65
+
46
66
  ## Fund-Moving Paths
47
67
 
48
68
  These are the autonomous fund-moving paths in the current agent layer: