@absol-labs/agent 0.11.0 → 0.12.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/README.md +42 -50
  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.js +1 -1
  12. package/dist/cli/commands/claim.js.map +1 -1
  13. package/dist/cli/commands/close.d.ts.map +1 -1
  14. package/dist/cli/commands/close.js +35 -3
  15. package/dist/cli/commands/close.js.map +1 -1
  16. package/dist/cli/commands/invoke.d.ts +2 -0
  17. package/dist/cli/commands/invoke.d.ts.map +1 -1
  18. package/dist/cli/commands/invoke.js +2 -2
  19. package/dist/cli/commands/invoke.js.map +1 -1
  20. package/dist/cli/commands/mcp.d.ts.map +1 -1
  21. package/dist/cli/commands/mcp.js +15 -2
  22. package/dist/cli/commands/mcp.js.map +1 -1
  23. package/dist/cli/commands/open.d.ts.map +1 -1
  24. package/dist/cli/commands/open.js +58 -4
  25. package/dist/cli/commands/open.js.map +1 -1
  26. package/dist/cli/commands/pay.d.ts +19 -0
  27. package/dist/cli/commands/pay.d.ts.map +1 -0
  28. package/dist/cli/commands/pay.js +355 -0
  29. package/dist/cli/commands/pay.js.map +1 -0
  30. package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -1
  31. package/dist/cli/commands/reclaim-unverified.js +3 -2
  32. package/dist/cli/commands/reclaim-unverified.js.map +1 -1
  33. package/dist/cli/commands/reclaim.d.ts +23 -2
  34. package/dist/cli/commands/reclaim.d.ts.map +1 -1
  35. package/dist/cli/commands/reclaim.js +184 -16
  36. package/dist/cli/commands/reclaim.js.map +1 -1
  37. package/dist/cli/commands/refund.d.ts +13 -0
  38. package/dist/cli/commands/refund.d.ts.map +1 -0
  39. package/dist/cli/commands/refund.js +224 -0
  40. package/dist/cli/commands/refund.js.map +1 -0
  41. package/dist/cli/commands/status.d.ts.map +1 -1
  42. package/dist/cli/commands/status.js +10 -1
  43. package/dist/cli/commands/status.js.map +1 -1
  44. package/dist/cli/commands/streams.js +8 -12
  45. package/dist/cli/commands/streams.js.map +1 -1
  46. package/dist/cli/commands/watch.d.ts.map +1 -1
  47. package/dist/cli/commands/watch.js +2 -1
  48. package/dist/cli/commands/watch.js.map +1 -1
  49. package/dist/cli/config.d.ts +44 -26
  50. package/dist/cli/config.d.ts.map +1 -1
  51. package/dist/cli/config.js +88 -38
  52. package/dist/cli/config.js.map +1 -1
  53. package/dist/cli/deps.d.ts +33 -0
  54. package/dist/cli/deps.d.ts.map +1 -1
  55. package/dist/cli/deps.js +37 -1
  56. package/dist/cli/deps.js.map +1 -1
  57. package/dist/cli/errors.d.ts.map +1 -1
  58. package/dist/cli/errors.js +21 -1
  59. package/dist/cli/errors.js.map +1 -1
  60. package/dist/cli/escape-hatch.d.ts +6 -4
  61. package/dist/cli/escape-hatch.d.ts.map +1 -1
  62. package/dist/cli/escape-hatch.js +6 -2
  63. package/dist/cli/escape-hatch.js.map +1 -1
  64. package/dist/cli/listings.d.ts +18 -0
  65. package/dist/cli/listings.d.ts.map +1 -1
  66. package/dist/cli/listings.js +22 -12
  67. package/dist/cli/listings.js.map +1 -1
  68. package/dist/cli/preflight.d.ts +20 -14
  69. package/dist/cli/preflight.d.ts.map +1 -1
  70. package/dist/cli/preflight.js +34 -20
  71. package/dist/cli/preflight.js.map +1 -1
  72. package/dist/cli/privy.d.ts.map +1 -1
  73. package/dist/cli/privy.js +4 -2
  74. package/dist/cli/privy.js.map +1 -1
  75. package/dist/cli/reclaimability.d.ts +82 -0
  76. package/dist/cli/reclaimability.d.ts.map +1 -0
  77. package/dist/cli/reclaimability.js +80 -0
  78. package/dist/cli/reclaimability.js.map +1 -0
  79. package/dist/cli/run.d.ts +2 -1
  80. package/dist/cli/run.d.ts.map +1 -1
  81. package/dist/cli/run.js +40 -11
  82. package/dist/cli/run.js.map +1 -1
  83. package/dist/cli/scan-range.d.ts +2 -2
  84. package/dist/cli/scan-range.js +2 -2
  85. package/dist/cli/status.d.ts +3 -1
  86. package/dist/cli/status.d.ts.map +1 -1
  87. package/dist/cli/status.js +15 -3
  88. package/dist/cli/status.js.map +1 -1
  89. package/dist/cli/wallet.d.ts +1 -1
  90. package/dist/cli/wallet.js +2 -2
  91. package/dist/cli/wallet.js.map +1 -1
  92. package/dist/discovery/rate-band.d.ts +74 -0
  93. package/dist/discovery/rate-band.d.ts.map +1 -0
  94. package/dist/discovery/rate-band.js +114 -0
  95. package/dist/discovery/rate-band.js.map +1 -0
  96. package/dist/escrow-generation.d.ts +81 -0
  97. package/dist/escrow-generation.d.ts.map +1 -0
  98. package/dist/escrow-generation.js +76 -0
  99. package/dist/escrow-generation.js.map +1 -0
  100. package/dist/frameworks/agentkit.d.ts +8 -4
  101. package/dist/frameworks/agentkit.d.ts.map +1 -1
  102. package/dist/frameworks/agentkit.js +27 -10
  103. package/dist/frameworks/agentkit.js.map +1 -1
  104. package/dist/frameworks/eliza.d.ts +3 -3
  105. package/dist/frameworks/eliza.d.ts.map +1 -1
  106. package/dist/frameworks/eliza.js +15 -6
  107. package/dist/frameworks/eliza.js.map +1 -1
  108. package/dist/frameworks/langchain.d.ts.map +1 -1
  109. package/dist/frameworks/langchain.js +8 -0
  110. package/dist/frameworks/langchain.js.map +1 -1
  111. package/dist/gateway/caller-auth-gateway.d.ts +83 -31
  112. package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
  113. package/dist/gateway/caller-auth-gateway.js +180 -27
  114. package/dist/gateway/caller-auth-gateway.js.map +1 -1
  115. package/dist/gateway/http-server.d.ts +111 -44
  116. package/dist/gateway/http-server.d.ts.map +1 -1
  117. package/dist/gateway/http-server.js +439 -155
  118. package/dist/gateway/http-server.js.map +1 -1
  119. package/dist/gateway/probe-auth.d.ts +112 -0
  120. package/dist/gateway/probe-auth.d.ts.map +1 -0
  121. package/dist/gateway/probe-auth.js +127 -0
  122. package/dist/gateway/probe-auth.js.map +1 -0
  123. package/dist/gateway/signed-rate-bounds.d.ts +51 -0
  124. package/dist/gateway/signed-rate-bounds.d.ts.map +1 -0
  125. package/dist/gateway/signed-rate-bounds.js +94 -0
  126. package/dist/gateway/signed-rate-bounds.js.map +1 -0
  127. package/dist/gateway/x402-opener-mode.d.ts +215 -0
  128. package/dist/gateway/x402-opener-mode.d.ts.map +1 -0
  129. package/dist/gateway/x402-opener-mode.js +639 -0
  130. package/dist/gateway/x402-opener-mode.js.map +1 -0
  131. package/dist/index.d.ts +6 -2
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +10 -5
  134. package/dist/index.js.map +1 -1
  135. package/dist/mcp/server.d.ts +52 -1
  136. package/dist/mcp/server.d.ts.map +1 -1
  137. package/dist/mcp/server.js +228 -37
  138. package/dist/mcp/server.js.map +1 -1
  139. package/dist/mcp/x402-tools.d.ts +104 -0
  140. package/dist/mcp/x402-tools.d.ts.map +1 -0
  141. package/dist/mcp/x402-tools.js +434 -0
  142. package/dist/mcp/x402-tools.js.map +1 -0
  143. package/dist/network.d.ts +119 -0
  144. package/dist/network.d.ts.map +1 -0
  145. package/dist/network.js +199 -0
  146. package/dist/network.js.map +1 -0
  147. package/dist/sdk/invoke.d.ts +4 -0
  148. package/dist/sdk/invoke.d.ts.map +1 -1
  149. package/dist/sdk/invoke.js +2 -5
  150. package/dist/sdk/invoke.js.map +1 -1
  151. package/dist/wallet/autonomous-wallet-broker.d.ts +3 -0
  152. package/dist/wallet/autonomous-wallet-broker.d.ts.map +1 -1
  153. package/dist/wallet/autonomous-wallet-broker.js +27 -23
  154. package/dist/wallet/autonomous-wallet-broker.js.map +1 -1
  155. package/dist/wallet/autonomous-wallet-protocol.d.ts +8 -2
  156. package/dist/wallet/autonomous-wallet-protocol.d.ts.map +1 -1
  157. package/dist/wallet/autonomous-wallet-protocol.js +9 -2
  158. package/dist/wallet/autonomous-wallet-protocol.js.map +1 -1
  159. package/dist/wallet/autonomous-wallet.d.ts.map +1 -1
  160. package/dist/wallet/autonomous-wallet.js +3 -2
  161. package/dist/wallet/autonomous-wallet.js.map +1 -1
  162. package/dist/wallet/broker-escrows.d.ts +222 -0
  163. package/dist/wallet/broker-escrows.d.ts.map +1 -0
  164. package/dist/wallet/broker-escrows.js +36 -0
  165. package/dist/wallet/broker-escrows.js.map +1 -0
  166. package/dist/wallet/lifecycle.d.ts +21 -5
  167. package/dist/wallet/lifecycle.d.ts.map +1 -1
  168. package/dist/wallet/lifecycle.js +32 -12
  169. package/dist/wallet/lifecycle.js.map +1 -1
  170. package/dist/wallet/privy-broker-server.d.ts.map +1 -1
  171. package/dist/wallet/privy-broker-server.js +31 -26
  172. package/dist/wallet/privy-broker-server.js.map +1 -1
  173. package/dist/wallet/privy-session-broker.d.ts +14 -5
  174. package/dist/wallet/privy-session-broker.d.ts.map +1 -1
  175. package/dist/wallet/privy-session-broker.js +29 -17
  176. package/dist/wallet/privy-session-broker.js.map +1 -1
  177. package/dist/wallet/privy-session-provider.d.ts +3 -1
  178. package/dist/wallet/privy-session-provider.d.ts.map +1 -1
  179. package/dist/wallet/privy-session-provider.js +5 -3
  180. package/dist/wallet/privy-session-provider.js.map +1 -1
  181. package/dist/wallet/provider.d.ts.map +1 -1
  182. package/dist/wallet/provider.js +15 -8
  183. package/dist/wallet/provider.js.map +1 -1
  184. package/dist/x402/facilitator.d.ts +3 -30
  185. package/dist/x402/facilitator.d.ts.map +1 -1
  186. package/dist/x402/facilitator.js +0 -27
  187. package/dist/x402/facilitator.js.map +1 -1
  188. package/dist/x402-buyer/guarded-fetch.d.ts +51 -0
  189. package/dist/x402-buyer/guarded-fetch.d.ts.map +1 -0
  190. package/dist/x402-buyer/guarded-fetch.js +147 -0
  191. package/dist/x402-buyer/guarded-fetch.js.map +1 -0
  192. package/dist/x402-buyer/index.d.ts +10 -0
  193. package/dist/x402-buyer/index.d.ts.map +1 -0
  194. package/dist/x402-buyer/index.js +10 -0
  195. package/dist/x402-buyer/index.js.map +1 -0
  196. package/dist/x402-buyer/pay.d.ts +135 -0
  197. package/dist/x402-buyer/pay.d.ts.map +1 -0
  198. package/dist/x402-buyer/pay.js +336 -0
  199. package/dist/x402-buyer/pay.js.map +1 -0
  200. package/dist/x402-opener/abi.d.ts +684 -0
  201. package/dist/x402-opener/abi.d.ts.map +1 -0
  202. package/dist/x402-opener/abi.js +880 -0
  203. package/dist/x402-opener/abi.js.map +1 -0
  204. package/dist/x402-opener/facilitator-entry.d.ts +3 -0
  205. package/dist/x402-opener/facilitator-entry.d.ts.map +1 -0
  206. package/dist/x402-opener/facilitator-entry.js +37 -0
  207. package/dist/x402-opener/facilitator-entry.js.map +1 -0
  208. package/dist/x402-opener/facilitator-server.d.ts +180 -0
  209. package/dist/x402-opener/facilitator-server.d.ts.map +1 -0
  210. package/dist/x402-opener/facilitator-server.js +696 -0
  211. package/dist/x402-opener/facilitator-server.js.map +1 -0
  212. package/dist/x402-opener/index.d.ts +14 -0
  213. package/dist/x402-opener/index.d.ts.map +1 -0
  214. package/dist/x402-opener/index.js +14 -0
  215. package/dist/x402-opener/index.js.map +1 -0
  216. package/dist/x402-opener/listing-resolver.d.ts +55 -0
  217. package/dist/x402-opener/listing-resolver.d.ts.map +1 -0
  218. package/dist/x402-opener/listing-resolver.js +125 -0
  219. package/dist/x402-opener/listing-resolver.js.map +1 -0
  220. package/dist/x402-opener/refund-relay.d.ts +240 -0
  221. package/dist/x402-opener/refund-relay.d.ts.map +1 -0
  222. package/dist/x402-opener/refund-relay.js +1240 -0
  223. package/dist/x402-opener/refund-relay.js.map +1 -0
  224. package/dist/x402-opener/relayer-queue.d.ts +14 -0
  225. package/dist/x402-opener/relayer-queue.d.ts.map +1 -0
  226. package/dist/x402-opener/relayer-queue.js +18 -0
  227. package/dist/x402-opener/relayer-queue.js.map +1 -0
  228. package/dist/x402-opener/scheme.d.ts +217 -0
  229. package/dist/x402-opener/scheme.d.ts.map +1 -0
  230. package/dist/x402-opener/scheme.js +749 -0
  231. package/dist/x402-opener/scheme.js.map +1 -0
  232. package/dist/x402-opener/terms.d.ts +105 -0
  233. package/dist/x402-opener/terms.d.ts.map +1 -0
  234. package/dist/x402-opener/terms.js +212 -0
  235. package/dist/x402-opener/terms.js.map +1 -0
  236. package/dist/zktls/reclaim.d.ts +2 -2
  237. package/dist/zktls/t2-delivery-proof.d.ts +6 -6
  238. package/docs/agent-layer.md +3 -2
  239. package/docs/autonomous-privy-wallet.md +188 -0
  240. package/docs/cli.md +184 -31
  241. package/docs/langchain.md +5 -0
  242. package/docs/mcp-reference.md +75 -5
  243. package/docs/probe-capability.md +191 -0
  244. package/docs/quickstart.md +36 -1
  245. package/docs/threat-model.md +20 -0
  246. package/docs/x402.md +628 -595
  247. package/package.json +19 -17
  248. package/src/capability/probe-capability.ts +355 -0
  249. package/src/cli/args.ts +29 -0
  250. package/src/cli/commands/claim.ts +1 -1
  251. package/src/cli/commands/close.ts +36 -3
  252. package/src/cli/commands/invoke.ts +5 -2
  253. package/src/cli/commands/mcp.ts +18 -2
  254. package/src/cli/commands/open.ts +75 -4
  255. package/src/cli/commands/pay.ts +442 -0
  256. package/src/cli/commands/reclaim-unverified.ts +8 -2
  257. package/src/cli/commands/reclaim.ts +252 -14
  258. package/src/cli/commands/refund.ts +277 -0
  259. package/src/cli/commands/status.ts +17 -1
  260. package/src/cli/commands/streams.ts +10 -14
  261. package/src/cli/commands/watch.ts +7 -1
  262. package/src/cli/config.ts +139 -52
  263. package/src/cli/deps.ts +74 -1
  264. package/src/cli/errors.ts +24 -1
  265. package/src/cli/escape-hatch.ts +19 -5
  266. package/src/cli/listings.ts +43 -10
  267. package/src/cli/preflight.ts +36 -21
  268. package/src/cli/privy.ts +4 -2
  269. package/src/cli/reclaimability.ts +154 -0
  270. package/src/cli/run.ts +54 -11
  271. package/src/cli/scan-range.ts +2 -2
  272. package/src/cli/status.ts +14 -1
  273. package/src/cli/wallet.ts +2 -2
  274. package/src/discovery/rate-band.ts +172 -0
  275. package/src/escrow-generation.ts +135 -0
  276. package/src/frameworks/agentkit.ts +45 -11
  277. package/src/frameworks/eliza.ts +27 -5
  278. package/src/frameworks/langchain.ts +14 -0
  279. package/src/gateway/caller-auth-gateway.ts +320 -68
  280. package/src/gateway/http-server.ts +629 -220
  281. package/src/gateway/probe-auth.ts +191 -0
  282. package/src/gateway/signed-rate-bounds.ts +164 -0
  283. package/src/gateway/x402-opener-mode.ts +974 -0
  284. package/src/index.ts +82 -19
  285. package/src/mcp/server.ts +319 -42
  286. package/src/mcp/x402-tools.ts +621 -0
  287. package/src/network.ts +302 -0
  288. package/src/sdk/invoke.ts +6 -5
  289. package/src/wallet/autonomous-wallet-broker.ts +46 -20
  290. package/src/wallet/autonomous-wallet-protocol.ts +11 -2
  291. package/src/wallet/autonomous-wallet.ts +4 -2
  292. package/src/wallet/broker-escrows.ts +66 -0
  293. package/src/wallet/lifecycle.ts +53 -18
  294. package/src/wallet/privy-broker-server.ts +35 -25
  295. package/src/wallet/privy-session-broker.ts +58 -25
  296. package/src/wallet/privy-session-provider.ts +7 -3
  297. package/src/wallet/provider.ts +20 -15
  298. package/src/x402/facilitator.ts +3 -43
  299. package/src/x402-buyer/guarded-fetch.ts +213 -0
  300. package/src/x402-buyer/index.ts +9 -0
  301. package/src/x402-buyer/pay.ts +517 -0
  302. package/src/x402-opener/abi.ts +881 -0
  303. package/src/x402-opener/facilitator-entry.ts +45 -0
  304. package/src/x402-opener/facilitator-server.ts +1014 -0
  305. package/src/x402-opener/index.ts +13 -0
  306. package/src/x402-opener/listing-resolver.ts +202 -0
  307. package/src/x402-opener/refund-relay.ts +1743 -0
  308. package/src/x402-opener/relayer-queue.ts +18 -0
  309. package/src/x402-opener/scheme.ts +1133 -0
  310. package/src/x402-opener/terms.ts +300 -0
  311. package/dist/gateway/x402-mode.d.ts +0 -123
  312. package/dist/gateway/x402-mode.d.ts.map +0 -1
  313. package/dist/gateway/x402-mode.js +0 -212
  314. package/dist/gateway/x402-mode.js.map +0 -1
  315. package/dist/x402/delivery-oracle.d.ts +0 -108
  316. package/dist/x402/delivery-oracle.d.ts.map +0 -1
  317. package/dist/x402/delivery-oracle.js +0 -174
  318. package/dist/x402/delivery-oracle.js.map +0 -1
  319. package/dist/x402/facilitator-server-entry.d.ts +0 -3
  320. package/dist/x402/facilitator-server-entry.d.ts.map +0 -1
  321. package/dist/x402/facilitator-server-entry.js +0 -27
  322. package/dist/x402/facilitator-server-entry.js.map +0 -1
  323. package/dist/x402/facilitator-server.d.ts +0 -73
  324. package/dist/x402/facilitator-server.d.ts.map +0 -1
  325. package/dist/x402/facilitator-server.js +0 -305
  326. package/dist/x402/facilitator-server.js.map +0 -1
  327. package/dist/x402/payment-requirements.d.ts +0 -106
  328. package/dist/x402/payment-requirements.d.ts.map +0 -1
  329. package/dist/x402/payment-requirements.js +0 -138
  330. package/dist/x402/payment-requirements.js.map +0 -1
  331. package/dist/x402/relayer.d.ts +0 -41
  332. package/dist/x402/relayer.d.ts.map +0 -1
  333. package/dist/x402/relayer.js +0 -64
  334. package/dist/x402/relayer.js.map +0 -1
  335. package/dist/x402/resource-index.d.ts +0 -75
  336. package/dist/x402/resource-index.d.ts.map +0 -1
  337. package/dist/x402/resource-index.js +0 -126
  338. package/dist/x402/resource-index.js.map +0 -1
  339. package/dist/x402/verified-delivery-facilitator.d.ts +0 -285
  340. package/dist/x402/verified-delivery-facilitator.d.ts.map +0 -1
  341. package/dist/x402/verified-delivery-facilitator.js +0 -423
  342. package/dist/x402/verified-delivery-facilitator.js.map +0 -1
  343. package/src/gateway/x402-mode.ts +0 -350
  344. package/src/x402/delivery-oracle.ts +0 -285
  345. package/src/x402/facilitator-server-entry.ts +0 -35
  346. package/src/x402/facilitator-server.ts +0 -445
  347. package/src/x402/payment-requirements.ts +0 -231
  348. package/src/x402/relayer.ts +0 -122
  349. package/src/x402/resource-index.ts +0 -228
  350. 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,28 @@ 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;
335
380
  }
336
381
 
337
382
  export interface StreamRegistry {
338
383
  put(record: StoredStreamRecord): Promise<void>;
339
384
  get(streamId: `0x${string}`): Promise<StoredStreamRecord | null>;
340
385
  list(): Promise<readonly StoredStreamRecord[]>;
386
+ /**
387
+ * Remove a record. Used ONLY to release an x402 spend reservation that
388
+ * provably never moved money, or one superseded by its real stream. A
389
+ * registry without it keeps reservations (they over-count: buyer-favouring).
390
+ */
391
+ delete?(streamId: `0x${string}`): Promise<void>;
341
392
  }
342
393
 
343
394
  export class InMemoryStreamRegistry implements StreamRegistry {
@@ -354,6 +405,10 @@ export class InMemoryStreamRegistry implements StreamRegistry {
354
405
  async list(): Promise<readonly StoredStreamRecord[]> {
355
406
  return Array.from(this.records.values());
356
407
  }
408
+
409
+ async delete(streamId: `0x${string}`): Promise<void> {
410
+ this.records.delete(streamId);
411
+ }
357
412
  }
358
413
 
359
414
  export class FileStreamRegistry implements StreamRegistry {
@@ -374,6 +429,11 @@ export class FileStreamRegistry implements StreamRegistry {
374
429
  return Array.from((await this.readAll()).values());
375
430
  }
376
431
 
432
+ async delete(streamId: `0x${string}`): Promise<void> {
433
+ const records = await this.readAll();
434
+ if (records.delete(streamId)) await this.writeAll(records);
435
+ }
436
+
377
437
  private async readAll(): Promise<Map<`0x${string}`, StoredStreamRecord>> {
378
438
  const contents = await fs.readFile(this.filePath, "utf8").catch((error) => {
379
439
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -393,6 +453,9 @@ export class FileStreamRegistry implements StreamRegistry {
393
453
  maxDurationSeconds: z.number().int().positive(),
394
454
  openedAtSeconds: z.number().int().nonnegative(),
395
455
  mandateId: bytes32Schema,
456
+ refundUrl: z.string().url().optional(),
457
+ opener: addressSchema.optional(),
458
+ x402Reservation: z.boolean().optional(),
396
459
  }),
397
460
  )
398
461
  .parse(JSON.parse(contents));
@@ -410,6 +473,13 @@ export class FileStreamRegistry implements StreamRegistry {
410
473
  maxDurationSeconds: record.maxDurationSeconds,
411
474
  openedAtSeconds: record.openedAtSeconds,
412
475
  mandateId: record.mandateId as `0x${string}`,
476
+ ...(record.refundUrl === undefined
477
+ ? {}
478
+ : { refundUrl: record.refundUrl }),
479
+ ...(record.opener === undefined
480
+ ? {}
481
+ : { opener: record.opener as `0x${string}` }),
482
+ ...(record.x402Reservation === true ? { x402Reservation: true } : {}),
413
483
  },
414
484
  ]),
415
485
  );
@@ -494,6 +564,12 @@ export interface VerifiedStreamMcpServerOptions {
494
564
  * the registry URL/apikey come from env inside `discoverServices`.
495
565
  */
496
566
  readonly discoverServices?: DiscoverServicesFn;
567
+ /**
568
+ * The buyer side of x402. When set, `x402_pay`, `refund_status` and `refund`
569
+ * are registered (mandate-gated like `hire_verified_service`); unset, the
570
+ * tool surface is exactly as before.
571
+ */
572
+ readonly x402?: McpX402Options;
497
573
  readonly serverInfo?: {
498
574
  readonly name: string;
499
575
  readonly version: string;
@@ -512,6 +588,12 @@ export interface ParsedMetrikAgentEnv {
512
588
  readonly escrow: `0x${string}`;
513
589
  /** Which escrow generation {@link escrow} is, as CONFIGURED (never probed). */
514
590
  readonly escrowGeneration: EscrowGeneration;
591
+ /**
592
+ * True when `escrow` is NOT a registered deployment and the operator opted
593
+ * in (`METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW=true` + an explicit
594
+ * `METRIK_AGENT_ESCROW_GENERATION`); the SDK then skips its code-hash pin.
595
+ */
596
+ readonly unregisteredEscrow: boolean;
515
597
  /** Registry the invocation origin is verified against. Undefined = the public default. */
516
598
  readonly registryUrl?: string;
517
599
  readonly registryApiKey?: string;
@@ -519,6 +601,12 @@ export interface ParsedMetrikAgentEnv {
519
601
  readonly allowUnverifiedOperatorPairing: boolean;
520
602
  readonly usdc: `0x${string}`;
521
603
  readonly chain: Chain;
604
+ /** The registered network this server settles on (SDK network registry). */
605
+ readonly network: MetrikNetwork;
606
+ /** `METRIK_AGENT_X402`: register the x402 tools. OPT-IN: only `1`/`true`/`yes`/`on`. */
607
+ readonly x402Enabled: boolean;
608
+ /** `METRIK_AGENT_X402_ALLOWED_HOSTS`, comma-separated; empty = any public host. */
609
+ readonly x402AllowedHosts: readonly string[];
522
610
  readonly stateFile?: string;
523
611
  readonly wallet: AgentWalletInput;
524
612
  }
@@ -755,6 +843,30 @@ export function createVerifiedStreamMcpServer(
755
843
  }
756
844
  }
757
845
 
846
+ // Is the rate inside the band the operator SIGNED? The escrow accepts
847
+ // any non-zero rate, but the seller's capability gateway answers every
848
+ // request on an out-of-band stream with 403
849
+ // `stream-rate-out-of-bounds`, so such a hire escrows the buyer's USDC
850
+ // into a stream that can never be served. Checked against the verified
851
+ // listing the pairing check just resolved — no second registry read,
852
+ // and no tool argument can widen the band. A descriptor that does not
853
+ // verify is a refusal, never "no band". Only an unverified pairing
854
+ // (which already needed the operator-set override above) has no listing
855
+ // to check, and its success result says so.
856
+ if (pairing.verdict === "verified") {
857
+ const band = await checkListedRateBand(
858
+ async () => [pairing.listing],
859
+ args.serviceRef,
860
+ request.ratePerSecondUsdc,
861
+ "ratePerSecondUsdc",
862
+ );
863
+ if (!band.ok) {
864
+ return errorResult(
865
+ `${band.message} Pick a ratePerSecondUsdc inside the band (discover_services reports it as ratePerSecondBand).`,
866
+ );
867
+ }
868
+ }
869
+
758
870
  // Serialize per owner: the spent-so-far read, cap check, open, and
759
871
  // registry write must be atomic, or concurrent hires could both pass
760
872
  // the cumulative-cap check on stale spend and overshoot maxTotalUsdc.
@@ -811,7 +923,7 @@ export function createVerifiedStreamMcpServer(
811
923
  : request.ratePerSecondUsdc * BigInt(request.maxDurationSeconds);
812
924
 
813
925
  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.`,
926
+ `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
927
  {
816
928
  streamId: result.streamId,
817
929
  txHash: result.txHash,
@@ -1014,13 +1126,24 @@ export function createVerifiedStreamMcpServer(
1014
1126
  const records = await registry.list();
1015
1127
  const entries = await Promise.all(
1016
1128
  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
- }));
1129
+ // A reservation key is not an escrow stream: report it as what it
1130
+ // is instead of reading a stream that does not exist.
1131
+ const status =
1132
+ record.x402Reservation === true
1133
+ ? {
1134
+ streamId: record.streamId,
1135
+ x402Reservation:
1136
+ "an x402 payment was signed but its stream could not be confirmed; its amount stays counted against the spend mandate",
1137
+ }
1138
+ : await options.agentClient
1139
+ .getStreamStatus(record.streamId)
1140
+ .then((value) =>
1141
+ serializeStreamStatus(value, record.streamId),
1142
+ )
1143
+ .catch((error) => ({
1144
+ streamId: record.streamId,
1145
+ statusError: asErrorMessage(error),
1146
+ }));
1024
1147
  return {
1025
1148
  streamId: record.streamId,
1026
1149
  buyer: record.buyer,
@@ -1046,6 +1169,34 @@ export function createVerifiedStreamMcpServer(
1046
1169
  },
1047
1170
  );
1048
1171
 
1172
+ if (options.x402 !== undefined) {
1173
+ registerX402Tools(mcpServer, {
1174
+ x402: options.x402,
1175
+ resolveMandate,
1176
+ getSpentSoFarUsdc: (owner) => resolver.getSpentSoFarUsdc(owner),
1177
+ getRevokedMandateIds: (owner) => resolver.getRevokedMandateIds(owner),
1178
+ // The SAME per-owner chain as hire_verified_service: a hire and an x402
1179
+ // payment from one owner can never both pass the cumulative cap on
1180
+ // stale spend.
1181
+ serialize: serializeHire,
1182
+ recordStream: async (record) => {
1183
+ await registry.put(record);
1184
+ },
1185
+ ...(registry.delete === undefined
1186
+ ? {}
1187
+ : {
1188
+ removeStream: async (streamId: `0x${string}`) => {
1189
+ await registry.delete?.(streamId);
1190
+ },
1191
+ }),
1192
+ findStream: async (streamId) => await registry.get(streamId),
1193
+ now,
1194
+ ok: successResult,
1195
+ fail: errorResult,
1196
+ unexpected: safeToolError,
1197
+ });
1198
+ }
1199
+
1049
1200
  if (options.deliveryProofService !== undefined) {
1050
1201
  const deliveryProofService = options.deliveryProofService;
1051
1202
  mcpServer.registerTool(
@@ -1187,41 +1338,76 @@ export function parseMetrikAgentEnv(
1187
1338
  // so that array was the ONLY thing an operator ever saw — a correct failure
1188
1339
  // rendered as noise. Name the variables instead.
1189
1340
  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}).`,
1341
+ `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
1342
  );
1192
1343
  }
1193
1344
  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");
1345
+ let chainId: number | undefined;
1346
+ if (parsed.METRIK_AGENT_CHAIN_ID !== undefined) {
1347
+ chainId = Number(parsed.METRIK_AGENT_CHAIN_ID);
1348
+ if (!Number.isInteger(chainId) || chainId <= 0) {
1349
+ throw new Error("METRIK_AGENT_CHAIN_ID must be a positive integer");
1350
+ }
1197
1351
  }
1198
- if (chainId !== baseSepolia.id) {
1352
+ // "Is this a network the registry knows and the build allows": a registered
1353
+ // TESTNET. This server signs with a key from the environment and moves funds.
1354
+ let network: MetrikNetwork;
1355
+ try {
1356
+ network = resolveAgentNetwork(
1357
+ parsed.METRIK_NETWORK !== undefined &&
1358
+ parsed.METRIK_NETWORK.trim().length > 0
1359
+ ? parsed.METRIK_NETWORK
1360
+ : chainId,
1361
+ { testnetOnly: true },
1362
+ );
1363
+ } catch (error) {
1199
1364
  throw new Error(
1200
- `unsupported METRIK_AGENT_CHAIN_ID ${chainId}; phase 2 is Base Sepolia only`,
1365
+ `unsupported METRIK_NETWORK / METRIK_AGENT_CHAIN_ID: ${error instanceof Error ? error.message : String(error)}`,
1201
1366
  );
1202
1367
  }
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") {
1368
+ if (chainId !== undefined && chainId !== network.chain.id) {
1211
1369
  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.`,
1370
+ `METRIK_AGENT_CHAIN_ID ${chainId} does not match METRIK_NETWORK ${network.id} (chain ${network.chain.id})`,
1213
1371
  );
1214
1372
  }
1215
- if (parsed.METRIK_AGENT_ESCROW.toLowerCase() === RETIRED_V1_ESCROW) {
1373
+
1374
+ // Generation: a registered deployment's comes from the registry (a
1375
+ // contradicting env value is refused); an unregistered escrow needs it
1376
+ // DECLARED. Only the checkpoint-settled generations (v2, v3) are served.
1377
+ const declared = parsed.METRIK_AGENT_ESCROW_GENERATION?.trim().toLowerCase();
1378
+ if (declared !== undefined && declared !== "v2" && declared !== "v3") {
1216
1379
  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}).`,
1380
+ `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
1381
  );
1219
1382
  }
1383
+ const deployment = registeredDeployment(network, parsed.METRIK_AGENT_ESCROW);
1384
+ let generation: EscrowGeneration;
1385
+ if (deployment !== undefined) {
1386
+ generation = deployment.escrow.generation;
1387
+ if (declared !== undefined && declared !== generation) {
1388
+ throw new Error(
1389
+ `METRIK_AGENT_ESCROW_GENERATION "${declared}" contradicts the registry: ${parsed.METRIK_AGENT_ESCROW} is ${network.name}'s ${generation} escrow`,
1390
+ );
1391
+ }
1392
+ } else {
1393
+ if (parsed.METRIK_AGENT_ALLOW_UNREGISTERED_ESCROW !== "true") {
1394
+ throw new Error(
1395
+ `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.`,
1396
+ );
1397
+ }
1398
+ if (declared === undefined) {
1399
+ throw new Error(
1400
+ `${ALLOW_UNREGISTERED_ESCROW_ENV}=true also needs METRIK_AGENT_ESCROW_GENERATION ("v2" | "v3"): an unregistered escrow's generation cannot be read from the registry`,
1401
+ );
1402
+ }
1403
+ generation = declared;
1404
+ }
1220
1405
 
1221
1406
  return {
1222
1407
  rpcUrl: parsed.METRIK_AGENT_RPC_URL,
1223
1408
  escrow: parsed.METRIK_AGENT_ESCROW as `0x${string}`,
1224
- escrowGeneration: "v2",
1409
+ escrowGeneration: generation,
1410
+ unregisteredEscrow: deployment === undefined,
1225
1411
  // Opt-IN only: any value but the exact string "true" leaves the check on.
1226
1412
  allowUnverifiedOperatorPairing:
1227
1413
  parsed.METRIK_AGENT_ALLOW_UNVERIFIED_OPERATOR === "true",
@@ -1233,9 +1419,19 @@ export function parseMetrikAgentEnv(
1233
1419
  : { registryApiKey: parsed.METRIK_AGENT_REGISTRY_APIKEY }),
1234
1420
  usdc: parsed.METRIK_AGENT_USDC as `0x${string}`,
1235
1421
  chain: {
1236
- ...baseSepolia,
1237
- name: parsed.METRIK_AGENT_CHAIN_NAME ?? baseSepolia.name,
1422
+ ...network.chain,
1423
+ name: parsed.METRIK_AGENT_CHAIN_NAME ?? network.chain.name,
1238
1424
  },
1425
+ network,
1426
+ // Opt-IN: a fund-moving tool family whose URL the MODEL picks is never
1427
+ // switched on by default or by an unrecognised value.
1428
+ x402Enabled: ["1", "true", "yes", "on"].includes(
1429
+ (parsed.METRIK_AGENT_X402 ?? "").trim().toLowerCase(),
1430
+ ),
1431
+ x402AllowedHosts: (parsed.METRIK_AGENT_X402_ALLOWED_HOSTS ?? "")
1432
+ .split(",")
1433
+ .map((entry) => entry.trim())
1434
+ .filter((entry) => entry.length > 0),
1239
1435
  ...(parsed.METRIK_AGENT_MCP_STATE_FILE === undefined
1240
1436
  ? {}
1241
1437
  : {
@@ -1281,7 +1477,15 @@ export async function createVerifiedStreamMcpServerOptionsFromEnv(
1281
1477
  // SDK treats the client as V1 and routes claimable()/reclaimable() to the
1282
1478
  // legacy ABI, which reverts against StreamEscrowV2: the checkpoint escrow
1283
1479
  // replaced those one-argument views with proof-carrying ones.
1284
- ...(parsed.escrowGeneration === "v2" ? { escrowV2: parsed.escrow } : {}),
1480
+ escrowV2: parsed.escrow,
1481
+ // An unregistered escrow was opted into by the operator: the SDK skips
1482
+ // its code-hash pin only with both flags (see parseMetrikAgentEnv).
1483
+ ...(parsed.unregisteredEscrow
1484
+ ? {
1485
+ allowUnregisteredEscrow: true,
1486
+ escrowGeneration: parsed.escrowGeneration,
1487
+ }
1488
+ : {}),
1285
1489
  usdc: parsed.usdc,
1286
1490
  settlementTarget: {
1287
1491
  chainId: parsed.chain.id,
@@ -1319,6 +1523,10 @@ export async function createVerifiedStreamMcpServerOptionsFromEnv(
1319
1523
  streamReader: createSdkInvokeStreamReader({
1320
1524
  escrowAddress: parsed.escrow,
1321
1525
  rpcUrl: parsed.rpcUrl,
1526
+ chainId: parsed.chain.id,
1527
+ ...(parsed.unregisteredEscrow
1528
+ ? { unregisteredEscrowGeneration: parsed.escrowGeneration }
1529
+ : {}),
1322
1530
  }),
1323
1531
  buyer: requireCapabilitySigner(wallet.account),
1324
1532
  domain: { chainId, verifyingContract: parsed.escrow },
@@ -1346,6 +1554,39 @@ export async function createVerifiedStreamMcpServerOptionsFromEnv(
1346
1554
  : new FileStreamRegistry(parsed.stateFile),
1347
1555
  allowUnverifiedOperatorPairing: parsed.allowUnverifiedOperatorPairing,
1348
1556
  serviceInvoker,
1557
+ ...(parsed.x402Enabled
1558
+ ? {
1559
+ x402: {
1560
+ signer: requireCapabilitySigner(
1561
+ wallet.account,
1562
+ ) as unknown as McpX402Options["signer"],
1563
+ network: parsed.network,
1564
+ // The SDK's on-chain quote checks (factory.predict) read through
1565
+ // this server's own RPC.
1566
+ sdkOptions: { transport: http(parsed.rpcUrl) },
1567
+ ...(parsed.x402AllowedHosts.length === 0
1568
+ ? {}
1569
+ : { allowedHosts: parsed.x402AllowedHosts }),
1570
+ refundClient: async (target) => {
1571
+ const { refundClient } = await import("@absol-labs/sdk/x402");
1572
+ return refundClient(target.refundUrl, {
1573
+ streamId: target.streamId,
1574
+ ...(target.opener === undefined
1575
+ ? {}
1576
+ : { opener: target.opener }),
1577
+ ...(target.fetch === undefined ? {} : { fetch: target.fetch }),
1578
+ // Every relay "success" is checked against the receipt.
1579
+ verifyWith: readOnlyMetrikClient({
1580
+ escrowAddress: parsed.escrow,
1581
+ rpcUrl: parsed.rpcUrl,
1582
+ chainId: parsed.chain.id,
1583
+ }),
1584
+ expectedChainId: parsed.chain.id,
1585
+ });
1586
+ },
1587
+ } satisfies McpX402Options,
1588
+ }
1589
+ : {}),
1349
1590
  ...(deliveryProofService === null ? {} : { deliveryProofService }),
1350
1591
  };
1351
1592
  }
@@ -1395,6 +1636,42 @@ function serializeServiceListing(
1395
1636
  ...(listing.targetMetadata === undefined
1396
1637
  ? {}
1397
1638
  : { targetMetadata: listing.targetMetadata }),
1639
+ ...signedRateBandField(listing),
1640
+ };
1641
+ }
1642
+
1643
+ /**
1644
+ * The operator-signed rate band, in USDC atomic units per second, so an agent
1645
+ * can pick a `ratePerSecondUsdc` that hire_verified_service will accept. Read
1646
+ * from the record discovery already verified; omitted when none was signed.
1647
+ * hire_verified_service re-verifies it before enforcing it.
1648
+ */
1649
+ function signedRateBandField(listing: ServiceListing): {
1650
+ readonly ratePerSecondBand?: Record<string, string | null>;
1651
+ } {
1652
+ const commercial = (
1653
+ listing.signed as
1654
+ | {
1655
+ readonly descriptor?: {
1656
+ readonly commercial?: {
1657
+ readonly minRatePerSecond?: string;
1658
+ readonly maxRatePerSecond?: string;
1659
+ };
1660
+ };
1661
+ }
1662
+ | undefined
1663
+ )?.descriptor?.commercial;
1664
+ if (
1665
+ commercial?.minRatePerSecond === undefined &&
1666
+ commercial?.maxRatePerSecond === undefined
1667
+ ) {
1668
+ return {};
1669
+ }
1670
+ return {
1671
+ ratePerSecondBand: {
1672
+ minRatePerSecondUsdc: commercial.minRatePerSecond ?? null,
1673
+ maxRatePerSecondUsdc: commercial.maxRatePerSecond ?? null,
1674
+ },
1398
1675
  };
1399
1676
  }
1400
1677
 
@@ -1481,7 +1758,7 @@ function hasForbiddenPathCharacter(value: string): boolean {
1481
1758
 
1482
1759
  /** Lower-cases header names so duplicate/odd-cased keys collapse to one. */
1483
1760
  type OperatorPairing =
1484
- | { readonly verdict: "verified" }
1761
+ | { readonly verdict: "verified"; readonly listing: ServiceListing }
1485
1762
  | { readonly verdict: "mismatch"; readonly listedOperator: `0x${string}` }
1486
1763
  | { readonly verdict: "unlisted" }
1487
1764
  | {
@@ -1531,7 +1808,7 @@ async function checkOperatorPairing(
1531
1808
  if (listing.operator.toLowerCase() !== operator.toLowerCase()) {
1532
1809
  return { verdict: "mismatch", listedOperator: listing.operator };
1533
1810
  }
1534
- return { verdict: "verified" };
1811
+ return { verdict: "verified", listing };
1535
1812
  }
1536
1813
 
1537
1814
  /**