@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
package/src/mcp/server.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- import { BASE_SEPOLIA_ESCROW_V2 } from "@absol-labs/sdk";
4
+ import type { MetrikNetwork } from "@absol-labs/sdk";
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
7
  import { privateKeyToAccount } from "viem/accounts";
8
8
  import { http, isAddress, isHex, type Chain } from "viem";
9
- import { baseSepolia } from "viem/chains";
10
9
  import { z } from "zod";
11
10
 
12
11
  import {
@@ -14,6 +13,7 @@ import {
14
13
  type DiscoverServicesOptions,
15
14
  type ServiceListing,
16
15
  } from "../discovery/registry.js";
16
+ import { checkListedRateBand } from "../discovery/rate-band.js";
17
17
  import { checkMandate, type SignedSpendMandate } from "../mandates/mandate.js";
18
18
  import {
19
19
  parseServerMandateCapsEnv,
@@ -60,6 +60,19 @@ import {
60
60
  type ServiceInvocationResult,
61
61
  type ServiceInvoker,
62
62
  } from "./service-invoker.js";
63
+ import {
64
+ defaultNetwork,
65
+ readOnlyMetrikClient,
66
+ registeredDeployment,
67
+ resolveAgentNetwork,
68
+ } from "../network.js";
69
+ import {
70
+ REFUND_STATUS_TOOL_DESCRIPTION,
71
+ REFUND_TOOL_DESCRIPTION,
72
+ X402_PAY_TOOL_DESCRIPTION,
73
+ registerX402Tools,
74
+ type McpX402Options,
75
+ } from "./x402-tools.js";
63
76
 
64
77
  /**
65
78
  * MCP server for the agent lane.
@@ -223,19 +236,26 @@ const metrikAgentEnvSchema = z.object({
223
236
  METRIK_AGENT_REGISTRY_URL: z.string().url().optional(),
224
237
  METRIK_AGENT_REGISTRY_APIKEY: z.string().optional(),
225
238
  METRIK_AGENT_ALLOW_UNVERIFIED_OPERATOR: z.string().optional(),
239
+ METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW: z.string().optional(),
240
+ METRIK_NETWORK: z.string().optional(),
241
+ METRIK_AGENT_X402: z.string().optional(),
242
+ METRIK_AGENT_X402_ALLOWED_HOSTS: z.string().optional(),
226
243
  });
227
244
 
228
245
  /**
229
- * The retired V1 metered escrow.
246
+ * Opt in to an escrow that is NOT the network's registered `StreamEscrowV2`.
230
247
  *
231
- * Refused by NAME rather than left to fail at read time, because it does not
232
- * fail at read time: V1 and V2 `getStream` return different tuples that diverge
233
- * from index 5 onward, so a V2 decoder pointed at V1 does not revert — it
234
- * decodes other fields into correctly-named ones and reports a confident wrong
235
- * answer about someone's money. This repository's own shipped docs pointed
236
- * `METRIK_AGENT_ESCROW` here as recently as 0.10.0.
248
+ * Off by default, because a wrong escrow does not fail at read time: the
249
+ * retired V1 escrow's `getStream` returns a tuple that diverges from V2's from
250
+ * index 5 onward, so a V2 decoder pointed at it does not revert — it decodes
251
+ * other fields into correctly-named ones and reports a confident wrong answer
252
+ * about someone's money. This repository's own shipped docs pointed
253
+ * `METRIK_AGENT_ESCROW` at V1 as recently as 0.10.0. Requiring the escrow to be
254
+ * the one the SDK network registry records for the chain refuses V1 (and any
255
+ * other stray address) without naming it in code. Operator-set input only.
237
256
  */
238
- const RETIRED_V1_ESCROW = "0x21948a5e6ae8d9a3d1050791ab6138657fb54286";
257
+ export const ALLOW_UNREGISTERED_ESCROW_ENV =
258
+ "METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW";
239
259
 
240
260
  /**
241
261
  * Opt out of the operator/serviceRef cross-check on hire. Operator-set input
@@ -255,7 +275,7 @@ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" as const;
255
275
  * generation has to be a stated fact that can be wrong out loud, rather than an
256
276
  * assumption buried in one constructor argument.
257
277
  */
258
- export type EscrowGeneration = "v2";
278
+ export type EscrowGeneration = "v2" | "v3";
259
279
 
260
280
  export interface McpToolSpec {
261
281
  name: string;
@@ -317,6 +337,21 @@ export const METRIK_MCP_TOOLS: readonly McpToolSpec[] = [
317
337
  "Generate a buyer-side zkTLS proof of an HTTPS response from a stream's verified service origin. PUBLIC listings only: the proof is fetched by a third-party Reclaim attestor, which cannot present this stream's caller-auth capability, so a GATED listing answers the attestor with a 403 and the tool fails closed rather than proving a rejection. This is an L2 delivery signal (consumer zkTLS) and must be cross-checked with oracle/L1 evidence; it is not a substitute for it.",
318
338
  movesFunds: false,
319
339
  },
340
+ {
341
+ name: "x402_pay",
342
+ description: X402_PAY_TOOL_DESCRIPTION,
343
+ movesFunds: true,
344
+ },
345
+ {
346
+ name: "refund_status",
347
+ description: REFUND_STATUS_TOOL_DESCRIPTION,
348
+ movesFunds: false,
349
+ },
350
+ {
351
+ name: "refund",
352
+ description: REFUND_TOOL_DESCRIPTION,
353
+ movesFunds: true,
354
+ },
320
355
  ] as const;
321
356
 
322
357
  /** @deprecated Renamed to METRIK_MCP_TOOLS; retained for back-compat. */
@@ -332,12 +367,40 @@ export interface StoredStreamRecord {
332
367
  readonly maxDurationSeconds: number;
333
368
  readonly openedAtSeconds: number;
334
369
  readonly mandateId: `0x${string}`;
370
+ /** x402 streams only: the seller's refund relay URL for this stream. */
371
+ readonly refundUrl?: string;
372
+ /** x402 streams only: the X402StreamOpener that bought the stream. */
373
+ readonly opener?: `0x${string}`;
374
+ /**
375
+ * An x402 payment approved and signed whose stream is not (yet) known. It
376
+ * counts against the cumulative cap exactly like a stream; `streamId` is a
377
+ * reservation key, not an escrow stream id.
378
+ */
379
+ readonly x402Reservation?: boolean;
380
+ /**
381
+ * NOT a stream: a settled x402 upfront-only charge (an upfront opener with
382
+ * `upfrontAmount == price` opens no stream). It counts against the
383
+ * cumulative cap like any spend and has nothing to refund; `streamId` is a
384
+ * charge key, not an escrow stream id.
385
+ */
386
+ readonly x402Charge?: "upfront-only";
387
+ /**
388
+ * x402 upfront openers: the NON-REFUNDABLE part of `budgetUsdc` (paid to the
389
+ * seller at settlement). Informational: `budgetUsdc` always counts it.
390
+ */
391
+ readonly nonRefundableUsdc?: bigint;
335
392
  }
336
393
 
337
394
  export interface StreamRegistry {
338
395
  put(record: StoredStreamRecord): Promise<void>;
339
396
  get(streamId: `0x${string}`): Promise<StoredStreamRecord | null>;
340
397
  list(): Promise<readonly StoredStreamRecord[]>;
398
+ /**
399
+ * Remove a record. Used ONLY to release an x402 spend reservation that
400
+ * provably never moved money, or one superseded by its real stream. A
401
+ * registry without it keeps reservations (they over-count: buyer-favouring).
402
+ */
403
+ delete?(streamId: `0x${string}`): Promise<void>;
341
404
  }
342
405
 
343
406
  export class InMemoryStreamRegistry implements StreamRegistry {
@@ -354,6 +417,10 @@ export class InMemoryStreamRegistry implements StreamRegistry {
354
417
  async list(): Promise<readonly StoredStreamRecord[]> {
355
418
  return Array.from(this.records.values());
356
419
  }
420
+
421
+ async delete(streamId: `0x${string}`): Promise<void> {
422
+ this.records.delete(streamId);
423
+ }
357
424
  }
358
425
 
359
426
  export class FileStreamRegistry implements StreamRegistry {
@@ -374,6 +441,11 @@ export class FileStreamRegistry implements StreamRegistry {
374
441
  return Array.from((await this.readAll()).values());
375
442
  }
376
443
 
444
+ async delete(streamId: `0x${string}`): Promise<void> {
445
+ const records = await this.readAll();
446
+ if (records.delete(streamId)) await this.writeAll(records);
447
+ }
448
+
377
449
  private async readAll(): Promise<Map<`0x${string}`, StoredStreamRecord>> {
378
450
  const contents = await fs.readFile(this.filePath, "utf8").catch((error) => {
379
451
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -393,6 +465,11 @@ export class FileStreamRegistry implements StreamRegistry {
393
465
  maxDurationSeconds: z.number().int().positive(),
394
466
  openedAtSeconds: z.number().int().nonnegative(),
395
467
  mandateId: bytes32Schema,
468
+ refundUrl: z.string().url().optional(),
469
+ opener: addressSchema.optional(),
470
+ x402Reservation: z.boolean().optional(),
471
+ x402Charge: z.literal("upfront-only").optional(),
472
+ nonRefundableUsdc: decimalStringSchema.optional(),
396
473
  }),
397
474
  )
398
475
  .parse(JSON.parse(contents));
@@ -410,6 +487,19 @@ export class FileStreamRegistry implements StreamRegistry {
410
487
  maxDurationSeconds: record.maxDurationSeconds,
411
488
  openedAtSeconds: record.openedAtSeconds,
412
489
  mandateId: record.mandateId as `0x${string}`,
490
+ ...(record.refundUrl === undefined
491
+ ? {}
492
+ : { refundUrl: record.refundUrl }),
493
+ ...(record.opener === undefined
494
+ ? {}
495
+ : { opener: record.opener as `0x${string}` }),
496
+ ...(record.x402Reservation === true ? { x402Reservation: true } : {}),
497
+ ...(record.x402Charge === undefined
498
+ ? {}
499
+ : { x402Charge: record.x402Charge }),
500
+ ...(record.nonRefundableUsdc === undefined
501
+ ? {}
502
+ : { nonRefundableUsdc: BigInt(record.nonRefundableUsdc) }),
413
503
  },
414
504
  ]),
415
505
  );
@@ -426,6 +516,9 @@ export class FileStreamRegistry implements StreamRegistry {
426
516
  ...record,
427
517
  budgetUsdc: record.budgetUsdc.toString(),
428
518
  ratePerSecondUsdc: record.ratePerSecondUsdc.toString(),
519
+ ...(record.nonRefundableUsdc === undefined
520
+ ? {}
521
+ : { nonRefundableUsdc: record.nonRefundableUsdc.toString() }),
429
522
  })),
430
523
  null,
431
524
  2,
@@ -494,6 +587,12 @@ export interface VerifiedStreamMcpServerOptions {
494
587
  * the registry URL/apikey come from env inside `discoverServices`.
495
588
  */
496
589
  readonly discoverServices?: DiscoverServicesFn;
590
+ /**
591
+ * The buyer side of x402. When set, `x402_pay`, `refund_status` and `refund`
592
+ * are registered (mandate-gated like `hire_verified_service`); unset, the
593
+ * tool surface is exactly as before.
594
+ */
595
+ readonly x402?: McpX402Options;
497
596
  readonly serverInfo?: {
498
597
  readonly name: string;
499
598
  readonly version: string;
@@ -512,6 +611,12 @@ export interface ParsedMetrikAgentEnv {
512
611
  readonly escrow: `0x${string}`;
513
612
  /** Which escrow generation {@link escrow} is, as CONFIGURED (never probed). */
514
613
  readonly escrowGeneration: EscrowGeneration;
614
+ /**
615
+ * True when `escrow` is NOT a registered deployment and the operator opted
616
+ * in (`METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW=true` + an explicit
617
+ * `METRIK_AGENT_ESCROW_GENERATION`); the SDK then skips its code-hash pin.
618
+ */
619
+ readonly unregisteredEscrow: boolean;
515
620
  /** Registry the invocation origin is verified against. Undefined = the public default. */
516
621
  readonly registryUrl?: string;
517
622
  readonly registryApiKey?: string;
@@ -519,6 +624,12 @@ export interface ParsedMetrikAgentEnv {
519
624
  readonly allowUnverifiedOperatorPairing: boolean;
520
625
  readonly usdc: `0x${string}`;
521
626
  readonly chain: Chain;
627
+ /** The registered network this server settles on (SDK network registry). */
628
+ readonly network: MetrikNetwork;
629
+ /** `METRIK_AGENT_X402`: register the x402 tools. OPT-IN: only `1`/`true`/`yes`/`on`. */
630
+ readonly x402Enabled: boolean;
631
+ /** `METRIK_AGENT_X402_ALLOWED_HOSTS`, comma-separated; empty = any public host. */
632
+ readonly x402AllowedHosts: readonly string[];
522
633
  readonly stateFile?: string;
523
634
  readonly wallet: AgentWalletInput;
524
635
  }
@@ -755,6 +866,30 @@ export function createVerifiedStreamMcpServer(
755
866
  }
756
867
  }
757
868
 
869
+ // Is the rate inside the band the operator SIGNED? The escrow accepts
870
+ // any non-zero rate, but the seller's capability gateway answers every
871
+ // request on an out-of-band stream with 403
872
+ // `stream-rate-out-of-bounds`, so such a hire escrows the buyer's USDC
873
+ // into a stream that can never be served. Checked against the verified
874
+ // listing the pairing check just resolved — no second registry read,
875
+ // and no tool argument can widen the band. A descriptor that does not
876
+ // verify is a refusal, never "no band". Only an unverified pairing
877
+ // (which already needed the operator-set override above) has no listing
878
+ // to check, and its success result says so.
879
+ if (pairing.verdict === "verified") {
880
+ const band = await checkListedRateBand(
881
+ async () => [pairing.listing],
882
+ args.serviceRef,
883
+ request.ratePerSecondUsdc,
884
+ "ratePerSecondUsdc",
885
+ );
886
+ if (!band.ok) {
887
+ return errorResult(
888
+ `${band.message} Pick a ratePerSecondUsdc inside the band (discover_services reports it as ratePerSecondBand).`,
889
+ );
890
+ }
891
+ }
892
+
758
893
  // Serialize per owner: the spent-so-far read, cap check, open, and
759
894
  // registry write must be atomic, or concurrent hires could both pass
760
895
  // the cumulative-cap check on stale spend and overshoot maxTotalUsdc.
@@ -811,7 +946,7 @@ export function createVerifiedStreamMcpServer(
811
946
  : request.ratePerSecondUsdc * BigInt(request.maxDurationSeconds);
812
947
 
813
948
  return successResult(
814
- `opened verified stream ${result.streamId} on chain ${result.handle.chainId}.${pairing.verdict === "verified" ? "" : ` WARNING: the operator/serviceRef pairing was NOT verified (${pairing.verdict}) and this server is configured to escrow anyway via ${ALLOW_UNVERIFIED_OPERATOR_ENV}. The operator is the wallet that gets paid and nothing proved it owns this service.`} The meter is running from now at ${request.ratePerSecondUsdc} USDC base units per VERIFIED second, whether or not you call the service. Call it with invoke_service; stop the meter with reclaim_unspent.`,
949
+ `opened verified stream ${result.streamId} on chain ${result.handle.chainId}.${pairing.verdict === "verified" ? "" : ` WARNING: the operator/serviceRef pairing was NOT verified (${pairing.verdict}) and this server is configured to escrow anyway via ${ALLOW_UNVERIFIED_OPERATOR_ENV}. The operator is the wallet that gets paid and nothing proved it owns this service, and the operator-signed rate band could not be checked either: if this rate is outside it, the service's gateway refuses every request while the deposit stays escrowed.`} The meter is running from now at ${request.ratePerSecondUsdc} USDC base units per VERIFIED second, whether or not you call the service. Call it with invoke_service; stop the meter with reclaim_unspent.`,
815
950
  {
816
951
  streamId: result.streamId,
817
952
  txHash: result.txHash,
@@ -1014,13 +1149,30 @@ export function createVerifiedStreamMcpServer(
1014
1149
  const records = await registry.list();
1015
1150
  const entries = await Promise.all(
1016
1151
  records.map(async (record) => {
1017
- const status = await options.agentClient
1018
- .getStreamStatus(record.streamId)
1019
- .then((value) => serializeStreamStatus(value, record.streamId))
1020
- .catch((error) => ({
1021
- streamId: record.streamId,
1022
- statusError: asErrorMessage(error),
1023
- }));
1152
+ // A reservation key is not an escrow stream: report it as what it
1153
+ // is instead of reading a stream that does not exist.
1154
+ const status =
1155
+ record.x402Charge === "upfront-only"
1156
+ ? {
1157
+ streamId: record.streamId,
1158
+ x402Charge:
1159
+ "a settled x402 upfront-only charge: the whole price was a non-refundable per-call charge, no stream exists and there is nothing to refund; its amount counts against the spend mandate",
1160
+ }
1161
+ : record.x402Reservation === true
1162
+ ? {
1163
+ streamId: record.streamId,
1164
+ x402Reservation:
1165
+ "an x402 payment was signed but its stream could not be confirmed; its amount stays counted against the spend mandate",
1166
+ }
1167
+ : await options.agentClient
1168
+ .getStreamStatus(record.streamId)
1169
+ .then((value) =>
1170
+ serializeStreamStatus(value, record.streamId),
1171
+ )
1172
+ .catch((error) => ({
1173
+ streamId: record.streamId,
1174
+ statusError: asErrorMessage(error),
1175
+ }));
1024
1176
  return {
1025
1177
  streamId: record.streamId,
1026
1178
  buyer: record.buyer,
@@ -1031,6 +1183,9 @@ export function createVerifiedStreamMcpServer(
1031
1183
  maxDurationSeconds: record.maxDurationSeconds,
1032
1184
  openedAtSeconds: record.openedAtSeconds,
1033
1185
  mandateId: record.mandateId,
1186
+ ...(record.nonRefundableUsdc === undefined
1187
+ ? {}
1188
+ : { nonRefundableUsdc: record.nonRefundableUsdc.toString() }),
1034
1189
  status,
1035
1190
  };
1036
1191
  }),
@@ -1046,6 +1201,34 @@ export function createVerifiedStreamMcpServer(
1046
1201
  },
1047
1202
  );
1048
1203
 
1204
+ if (options.x402 !== undefined) {
1205
+ registerX402Tools(mcpServer, {
1206
+ x402: options.x402,
1207
+ resolveMandate,
1208
+ getSpentSoFarUsdc: (owner) => resolver.getSpentSoFarUsdc(owner),
1209
+ getRevokedMandateIds: (owner) => resolver.getRevokedMandateIds(owner),
1210
+ // The SAME per-owner chain as hire_verified_service: a hire and an x402
1211
+ // payment from one owner can never both pass the cumulative cap on
1212
+ // stale spend.
1213
+ serialize: serializeHire,
1214
+ recordStream: async (record) => {
1215
+ await registry.put(record);
1216
+ },
1217
+ ...(registry.delete === undefined
1218
+ ? {}
1219
+ : {
1220
+ removeStream: async (streamId: `0x${string}`) => {
1221
+ await registry.delete?.(streamId);
1222
+ },
1223
+ }),
1224
+ findStream: async (streamId) => await registry.get(streamId),
1225
+ now,
1226
+ ok: successResult,
1227
+ fail: errorResult,
1228
+ unexpected: safeToolError,
1229
+ });
1230
+ }
1231
+
1049
1232
  if (options.deliveryProofService !== undefined) {
1050
1233
  const deliveryProofService = options.deliveryProofService;
1051
1234
  mcpServer.registerTool(
@@ -1187,41 +1370,76 @@ export function parseMetrikAgentEnv(
1187
1370
  // so that array was the ONLY thing an operator ever saw — a correct failure
1188
1371
  // rendered as noise. Name the variables instead.
1189
1372
  throw new Error(
1190
- `metrik-mcp is not configured: ${describeEnvIssues(result.error)}. Set METRIK_AGENT_RPC_URL (an http(s) URL), METRIK_AGENT_ESCROW and METRIK_AGENT_USDC (EVM addresses), plus a wallet (METRIK_AGENT_PRIVATE_KEY, or the CDP/Privy variables). METRIK_AGENT_CHAIN_ID defaults to Base Sepolia (${baseSepolia.id}).`,
1373
+ `metrik-mcp is not configured: ${describeEnvIssues(result.error)}. Set METRIK_AGENT_RPC_URL (an http(s) URL), METRIK_AGENT_ESCROW and METRIK_AGENT_USDC (EVM addresses), plus a wallet (METRIK_AGENT_PRIVATE_KEY, or the CDP variables — a Privy wallet is a CLI-only source and is not resolved here). METRIK_NETWORK (or METRIK_AGENT_CHAIN_ID) defaults to the registry's default network (${defaultNetwork().name}).`,
1191
1374
  );
1192
1375
  }
1193
1376
  const parsed = result.data;
1194
- const chainId = Number(parsed.METRIK_AGENT_CHAIN_ID ?? baseSepolia.id);
1195
- if (!Number.isInteger(chainId) || chainId <= 0) {
1196
- throw new Error("METRIK_AGENT_CHAIN_ID must be a positive integer");
1377
+ let chainId: number | undefined;
1378
+ if (parsed.METRIK_AGENT_CHAIN_ID !== undefined) {
1379
+ chainId = Number(parsed.METRIK_AGENT_CHAIN_ID);
1380
+ if (!Number.isInteger(chainId) || chainId <= 0) {
1381
+ throw new Error("METRIK_AGENT_CHAIN_ID must be a positive integer");
1382
+ }
1197
1383
  }
1198
- if (chainId !== baseSepolia.id) {
1384
+ // "Is this a network the registry knows and the build allows": a registered
1385
+ // TESTNET. This server signs with a key from the environment and moves funds.
1386
+ let network: MetrikNetwork;
1387
+ try {
1388
+ network = resolveAgentNetwork(
1389
+ parsed.METRIK_NETWORK !== undefined &&
1390
+ parsed.METRIK_NETWORK.trim().length > 0
1391
+ ? parsed.METRIK_NETWORK
1392
+ : chainId,
1393
+ { testnetOnly: true },
1394
+ );
1395
+ } catch (error) {
1199
1396
  throw new Error(
1200
- `unsupported METRIK_AGENT_CHAIN_ID ${chainId}; phase 2 is Base Sepolia only`,
1397
+ `unsupported METRIK_NETWORK / METRIK_AGENT_CHAIN_ID: ${error instanceof Error ? error.message : String(error)}`,
1201
1398
  );
1202
1399
  }
1203
-
1204
- // Generation is CONFIGURED. `v2` is the only generation the checkpoint escrow
1205
- // semantics this server implements apply to, so anything else is refused up
1206
- // front instead of silently mis-decoded (see RETIRED_V1_ESCROW).
1207
- const generation = (
1208
- parsed.METRIK_AGENT_ESCROW_GENERATION ?? "v2"
1209
- ).toLowerCase();
1210
- if (generation !== "v2") {
1400
+ if (chainId !== undefined && chainId !== network.chain.id) {
1211
1401
  throw new Error(
1212
- `unsupported METRIK_AGENT_ESCROW_GENERATION "${parsed.METRIK_AGENT_ESCROW_GENERATION}": this server reads checkpoint-settled StreamEscrowV2 only. Leave it unset (or set it to "v2") and point METRIK_AGENT_ESCROW at the V2 escrow.`,
1402
+ `METRIK_AGENT_CHAIN_ID ${chainId} does not match METRIK_NETWORK ${network.id} (chain ${network.chain.id})`,
1213
1403
  );
1214
1404
  }
1215
- if (parsed.METRIK_AGENT_ESCROW.toLowerCase() === RETIRED_V1_ESCROW) {
1405
+
1406
+ // Generation: a registered deployment's comes from the registry (a
1407
+ // contradicting env value is refused); an unregistered escrow needs it
1408
+ // DECLARED. Only the checkpoint-settled generations (v2, v3) are served.
1409
+ const declared = parsed.METRIK_AGENT_ESCROW_GENERATION?.trim().toLowerCase();
1410
+ if (declared !== undefined && declared !== "v2" && declared !== "v3") {
1216
1411
  throw new Error(
1217
- `METRIK_AGENT_ESCROW is the RETIRED V1 metered escrow (${parsed.METRIK_AGENT_ESCROW}). It is no longer settled by the oracle, and its stream tuple is incompatible with the checkpoint decoder this server uses — reads would not revert, they would return confident nonsense. Point it at the live StreamEscrowV2 (${BASE_SEPOLIA_ESCROW_V2}).`,
1412
+ `unsupported METRIK_AGENT_ESCROW_GENERATION "${parsed.METRIK_AGENT_ESCROW_GENERATION}": this server reads the checkpoint-settled StreamEscrowV2/V3 only. Leave it unset for a registered escrow.`,
1218
1413
  );
1219
1414
  }
1415
+ const deployment = registeredDeployment(network, parsed.METRIK_AGENT_ESCROW);
1416
+ let generation: EscrowGeneration;
1417
+ if (deployment !== undefined) {
1418
+ generation = deployment.escrow.generation;
1419
+ if (declared !== undefined && declared !== generation) {
1420
+ throw new Error(
1421
+ `METRIK_AGENT_ESCROW_GENERATION "${declared}" contradicts the registry: ${parsed.METRIK_AGENT_ESCROW} is ${network.name}'s ${generation} escrow`,
1422
+ );
1423
+ }
1424
+ } else {
1425
+ if (parsed.METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW !== "true") {
1426
+ throw new Error(
1427
+ `METRIK_AGENT_ESCROW (${parsed.METRIK_AGENT_ESCROW}) is not a registered escrow of ${network.name} (current ${network.escrow.address}${network.legacy.length === 0 ? "" : `; legacy ${network.legacy.map((d) => d.escrow.address).join(", ")}`}). A wrong escrow does not fail at read time — the retired V1 escrow's stream tuple decodes into confident nonsense with the checkpoint decoder this server uses. Point it at a registered escrow, or set ${ALLOW_UNREGISTERED_ESCROW_ENV}=true and METRIK_AGENT_ESCROW_GENERATION for a deliberate private deployment.`,
1428
+ );
1429
+ }
1430
+ if (declared === undefined) {
1431
+ throw new Error(
1432
+ `${ALLOW_UNREGISTERED_ESCROW_ENV}=true also needs METRIK_AGENT_ESCROW_GENERATION ("v2" | "v3"): an unregistered escrow's generation cannot be read from the registry`,
1433
+ );
1434
+ }
1435
+ generation = declared;
1436
+ }
1220
1437
 
1221
1438
  return {
1222
1439
  rpcUrl: parsed.METRIK_AGENT_RPC_URL,
1223
1440
  escrow: parsed.METRIK_AGENT_ESCROW as `0x${string}`,
1224
- escrowGeneration: "v2",
1441
+ escrowGeneration: generation,
1442
+ unregisteredEscrow: deployment === undefined,
1225
1443
  // Opt-IN only: any value but the exact string "true" leaves the check on.
1226
1444
  allowUnverifiedOperatorPairing:
1227
1445
  parsed.METRIK_AGENT_ALLOW_UNVERIFIED_OPERATOR === "true",
@@ -1233,9 +1451,19 @@ export function parseMetrikAgentEnv(
1233
1451
  : { registryApiKey: parsed.METRIK_AGENT_REGISTRY_APIKEY }),
1234
1452
  usdc: parsed.METRIK_AGENT_USDC as `0x${string}`,
1235
1453
  chain: {
1236
- ...baseSepolia,
1237
- name: parsed.METRIK_AGENT_CHAIN_NAME ?? baseSepolia.name,
1454
+ ...network.chain,
1455
+ name: parsed.METRIK_AGENT_CHAIN_NAME ?? network.chain.name,
1238
1456
  },
1457
+ network,
1458
+ // Opt-IN: a fund-moving tool family whose URL the MODEL picks is never
1459
+ // switched on by default or by an unrecognised value.
1460
+ x402Enabled: ["1", "true", "yes", "on"].includes(
1461
+ (parsed.METRIK_AGENT_X402 ?? "").trim().toLowerCase(),
1462
+ ),
1463
+ x402AllowedHosts: (parsed.METRIK_AGENT_X402_ALLOWED_HOSTS ?? "")
1464
+ .split(",")
1465
+ .map((entry) => entry.trim())
1466
+ .filter((entry) => entry.length > 0),
1239
1467
  ...(parsed.METRIK_AGENT_MCP_STATE_FILE === undefined
1240
1468
  ? {}
1241
1469
  : {
@@ -1281,7 +1509,15 @@ export async function createVerifiedStreamMcpServerOptionsFromEnv(
1281
1509
  // SDK treats the client as V1 and routes claimable()/reclaimable() to the
1282
1510
  // legacy ABI, which reverts against StreamEscrowV2: the checkpoint escrow
1283
1511
  // replaced those one-argument views with proof-carrying ones.
1284
- ...(parsed.escrowGeneration === "v2" ? { escrowV2: parsed.escrow } : {}),
1512
+ escrowV2: parsed.escrow,
1513
+ // An unregistered escrow was opted into by the operator: the SDK skips
1514
+ // its code-hash pin only with both flags (see parseMetrikAgentEnv).
1515
+ ...(parsed.unregisteredEscrow
1516
+ ? {
1517
+ allowUnregisteredEscrow: true,
1518
+ escrowGeneration: parsed.escrowGeneration,
1519
+ }
1520
+ : {}),
1285
1521
  usdc: parsed.usdc,
1286
1522
  settlementTarget: {
1287
1523
  chainId: parsed.chain.id,
@@ -1319,6 +1555,10 @@ export async function createVerifiedStreamMcpServerOptionsFromEnv(
1319
1555
  streamReader: createSdkInvokeStreamReader({
1320
1556
  escrowAddress: parsed.escrow,
1321
1557
  rpcUrl: parsed.rpcUrl,
1558
+ chainId: parsed.chain.id,
1559
+ ...(parsed.unregisteredEscrow
1560
+ ? { unregisteredEscrowGeneration: parsed.escrowGeneration }
1561
+ : {}),
1322
1562
  }),
1323
1563
  buyer: requireCapabilitySigner(wallet.account),
1324
1564
  domain: { chainId, verifyingContract: parsed.escrow },
@@ -1346,6 +1586,39 @@ export async function createVerifiedStreamMcpServerOptionsFromEnv(
1346
1586
  : new FileStreamRegistry(parsed.stateFile),
1347
1587
  allowUnverifiedOperatorPairing: parsed.allowUnverifiedOperatorPairing,
1348
1588
  serviceInvoker,
1589
+ ...(parsed.x402Enabled
1590
+ ? {
1591
+ x402: {
1592
+ signer: requireCapabilitySigner(
1593
+ wallet.account,
1594
+ ) as unknown as McpX402Options["signer"],
1595
+ network: parsed.network,
1596
+ // The SDK's on-chain quote checks (factory.predict) read through
1597
+ // this server's own RPC.
1598
+ sdkOptions: { transport: http(parsed.rpcUrl) },
1599
+ ...(parsed.x402AllowedHosts.length === 0
1600
+ ? {}
1601
+ : { allowedHosts: parsed.x402AllowedHosts }),
1602
+ refundClient: async (target) => {
1603
+ const { refundClient } = await import("@absol-labs/sdk/x402");
1604
+ return refundClient(target.refundUrl, {
1605
+ streamId: target.streamId,
1606
+ ...(target.opener === undefined
1607
+ ? {}
1608
+ : { opener: target.opener }),
1609
+ ...(target.fetch === undefined ? {} : { fetch: target.fetch }),
1610
+ // Every relay "success" is checked against the receipt.
1611
+ verifyWith: readOnlyMetrikClient({
1612
+ escrowAddress: parsed.escrow,
1613
+ rpcUrl: parsed.rpcUrl,
1614
+ chainId: parsed.chain.id,
1615
+ }),
1616
+ expectedChainId: parsed.chain.id,
1617
+ });
1618
+ },
1619
+ } satisfies McpX402Options,
1620
+ }
1621
+ : {}),
1349
1622
  ...(deliveryProofService === null ? {} : { deliveryProofService }),
1350
1623
  };
1351
1624
  }
@@ -1395,6 +1668,42 @@ function serializeServiceListing(
1395
1668
  ...(listing.targetMetadata === undefined
1396
1669
  ? {}
1397
1670
  : { targetMetadata: listing.targetMetadata }),
1671
+ ...signedRateBandField(listing),
1672
+ };
1673
+ }
1674
+
1675
+ /**
1676
+ * The operator-signed rate band, in USDC atomic units per second, so an agent
1677
+ * can pick a `ratePerSecondUsdc` that hire_verified_service will accept. Read
1678
+ * from the record discovery already verified; omitted when none was signed.
1679
+ * hire_verified_service re-verifies it before enforcing it.
1680
+ */
1681
+ function signedRateBandField(listing: ServiceListing): {
1682
+ readonly ratePerSecondBand?: Record<string, string | null>;
1683
+ } {
1684
+ const commercial = (
1685
+ listing.signed as
1686
+ | {
1687
+ readonly descriptor?: {
1688
+ readonly commercial?: {
1689
+ readonly minRatePerSecond?: string;
1690
+ readonly maxRatePerSecond?: string;
1691
+ };
1692
+ };
1693
+ }
1694
+ | undefined
1695
+ )?.descriptor?.commercial;
1696
+ if (
1697
+ commercial?.minRatePerSecond === undefined &&
1698
+ commercial?.maxRatePerSecond === undefined
1699
+ ) {
1700
+ return {};
1701
+ }
1702
+ return {
1703
+ ratePerSecondBand: {
1704
+ minRatePerSecondUsdc: commercial.minRatePerSecond ?? null,
1705
+ maxRatePerSecondUsdc: commercial.maxRatePerSecond ?? null,
1706
+ },
1398
1707
  };
1399
1708
  }
1400
1709
 
@@ -1481,7 +1790,7 @@ function hasForbiddenPathCharacter(value: string): boolean {
1481
1790
 
1482
1791
  /** Lower-cases header names so duplicate/odd-cased keys collapse to one. */
1483
1792
  type OperatorPairing =
1484
- | { readonly verdict: "verified" }
1793
+ | { readonly verdict: "verified"; readonly listing: ServiceListing }
1485
1794
  | { readonly verdict: "mismatch"; readonly listedOperator: `0x${string}` }
1486
1795
  | { readonly verdict: "unlisted" }
1487
1796
  | {
@@ -1531,7 +1840,7 @@ async function checkOperatorPairing(
1531
1840
  if (listing.operator.toLowerCase() !== operator.toLowerCase()) {
1532
1841
  return { verdict: "mismatch", listedOperator: listing.operator };
1533
1842
  }
1534
- return { verdict: "verified" };
1843
+ return { verdict: "verified", listing };
1535
1844
  }
1536
1845
 
1537
1846
  /**