@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/cli/config.ts CHANGED
@@ -1,76 +1,97 @@
1
- import {
2
- BASE_SEPOLIA_ESCROW_V2,
3
- BASE_SEPOLIA_USDC,
4
- DEFAULT_ORACLE_BASE_URL,
5
- DEFAULT_PUBLIC_REGISTRY_URL,
6
- } from "@absol-labs/sdk";
7
- import { isAddress, getAddress } from "viem";
8
- import { baseSepolia } from "viem/chains";
1
+ import type { MetrikNetwork } from "@absol-labs/sdk";
2
+ import { isAddress, getAddress, type Chain } from "viem";
9
3
 
4
+ import {
5
+ allowedNetworks,
6
+ describeAllowedNetworks,
7
+ registeredDeployment,
8
+ METRIK_NETWORK_ENV,
9
+ resolveAgentNetwork,
10
+ UnsupportedNetworkError,
11
+ } from "../network.js";
10
12
  import { CliError, configError, usageError } from "./errors.js";
11
13
  import { stringFlag, integerFlag, type ParsedArgs } from "./args.js";
12
14
 
13
15
  /**
14
- * TESTNET ONLY — the single allowlist this CLI will settle against.
16
+ * TESTNET ONLY — the CLI settles only on registered networks the SDK registry
17
+ * marks `testnet: true`.
15
18
  *
16
19
  * Metrik's escrow moves real USDC the moment it is pointed at a mainnet chain,
17
20
  * and this CLI signs with a raw private key read from the environment. So the
18
- * chain is an allowlist, not a default: any other chain id is refused outright,
19
- * with no override flag. There is deliberately no `--allow-mainnet` — an escape
20
- * hatch that re-enables real funds would defeat the guarantee, and a mainnet
21
- * deployment is not something a flag can conjure (there is none).
21
+ * chain is an allowlist, not a default: a chain the registry does not know, or
22
+ * one it knows as a mainnet, is refused outright, with no override flag. There
23
+ * is deliberately no `--allow-mainnet` — an escape hatch that re-enables real
24
+ * funds would defeat the guarantee.
22
25
  *
23
- * The allowlist is enforced TWICE: once against the configured/declared chain
24
- * id, and again against the chain id the RPC endpoint actually reports before
25
- * any fund-moving command signs anything (see `assertTestnetRpc`). The second
26
- * check is what stops `--chain-id 84532 --rpc-url <a mainnet RPC>`.
26
+ * The allowlist is enforced TWICE: once against the configured/declared network,
27
+ * and again against the chain id the RPC endpoint actually reports before any
28
+ * fund-moving command signs anything (see `assertTestnetRpc`). The second check
29
+ * is what stops a testnet `--chain-id` paired with a mainnet `--rpc-url`.
27
30
  */
28
- export const TESTNET_CHAIN_IDS: readonly number[] = [baseSepolia.id];
31
+ export const CLI_NETWORK_POLICY = { testnetOnly: true } as const;
32
+
33
+ /** Chain ids of every registered testnet this CLI will settle against. */
34
+ export function testnetChainIds(): number[] {
35
+ return allowedNetworks(CLI_NETWORK_POLICY).map((network) => network.chain.id);
36
+ }
29
37
 
30
38
  /**
31
- * The block `BASE_SEPOLIA_ESCROW_V2` was deployed at — the anchor of every
32
- * escrow log scan, because the contract has no history before it.
33
- *
34
- * Verified against Base Sepolia on 2026-09-17: `eth_getLogs` for this single
35
- * block returns 6 escrow logs (the deployment's own events). History is
36
- * immutable, so that is a stable known answer forever, which is what lets the
37
- * same block serve as the log-retention canary in `metrik streams`.
38
- *
39
- * It is tied to that ONE address. A run pointed at a different `--escrow` has
40
- * no anchor and must be given `--from-block`, rather than silently scanning
41
- * from a block that means nothing for it.
39
+ * @deprecated Snapshot of {@link testnetChainIds} at import time. A network
40
+ * registered later is not in it; call `testnetChainIds()` instead.
42
41
  */
43
- export const BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK = 45_892_018n;
42
+ export const TESTNET_CHAIN_IDS: readonly number[] = testnetChainIds();
44
43
 
45
44
  export interface CliConfig {
46
- readonly chain: typeof baseSepolia;
45
+ /** The registered network this run settles on (from the SDK registry). */
46
+ readonly network: MetrikNetwork;
47
+ readonly chain: Chain;
47
48
  readonly chainId: number;
48
49
  readonly rpcUrl: string;
49
50
  readonly escrow: `0x${string}`;
50
51
  readonly usdc: `0x${string}`;
51
52
  readonly oracleBaseUrl: string;
52
53
  readonly registryUrl: string;
54
+ /**
55
+ * Set ONLY for an `--escrow` the registry does not record, from the explicit
56
+ * `--escrow-generation`: the SDK then opts out of its code-hash pin
57
+ * (`allowUnregisteredEscrow`). A registered escrow's generation comes from
58
+ * the registry.
59
+ */
60
+ readonly unregisteredEscrowGeneration?: "v2" | "v3";
61
+ }
62
+
63
+ /**
64
+ * The block the configured escrow was deployed at — the anchor of every escrow
65
+ * log scan — or `undefined` when the escrow is not the network's registered
66
+ * escrow (a run pointed at a different `--escrow` has no anchor and must be
67
+ * given `--from-block`, rather than silently scanning from a block that means
68
+ * nothing for it).
69
+ */
70
+ export function escrowDeployBlock(config: CliConfig): bigint | undefined {
71
+ return registeredDeployment(config.network, config.escrow)?.escrow
72
+ .deployBlock;
53
73
  }
54
74
 
55
75
  /**
56
- * Resolves settlement configuration from flags, then env, then the baked-in
57
- * Base Sepolia defaults — so `metrik discover` works with nothing configured at
76
+ * Resolves settlement configuration from flags, then env, then the network's
77
+ * registry defaults — so `metrik discover` works with nothing configured at
58
78
  * all, while every value stays overridable.
79
+ *
80
+ * The network is selected by `--network` / `METRIK_NETWORK` (registry id,
81
+ * chain id or CAIP-2 id) or by `--chain-id` / `METRIK_AGENT_CHAIN_ID`; giving
82
+ * both is allowed only when they agree.
59
83
  */
60
84
  export function resolveConfig(
61
85
  args: ParsedArgs,
62
86
  env: NodeJS.ProcessEnv,
63
87
  ): CliConfig {
64
- const chainId =
65
- integerFlag(args, "chain-id") ??
66
- parseEnvChainId(env.METRIK_AGENT_CHAIN_ID) ??
67
- baseSepolia.id;
68
- assertTestnetChainId(chainId, "the configured chain id");
88
+ const network = resolveCliNetwork(args, env);
89
+ const chainId = network.chain.id;
69
90
 
70
91
  const rpcUrl =
71
92
  stringFlag(args, "rpc-url") ??
72
93
  env.METRIK_AGENT_RPC_URL ??
73
- baseSepolia.rpcUrls.default.http[0];
94
+ network.rpcUrls[0];
74
95
  if (rpcUrl === undefined || !isHttpUrl(rpcUrl)) {
75
96
  throw configError(
76
97
  `RPC URL must be an http(s) URL (got "${rpcUrl ?? "undefined"}")`,
@@ -78,35 +99,101 @@ export function resolveConfig(
78
99
  );
79
100
  }
80
101
 
102
+ const escrow = resolveAddress(
103
+ stringFlag(args, "escrow") ?? env.METRIK_AGENT_ESCROW,
104
+ network.escrow.address,
105
+ "escrow",
106
+ );
107
+ const declared = (
108
+ stringFlag(args, "escrow-generation") ?? env.METRIK_AGENT_ESCROW_GENERATION
109
+ )
110
+ ?.trim()
111
+ .toLowerCase();
112
+ if (declared !== undefined && declared !== "v2" && declared !== "v3") {
113
+ throw configError(
114
+ `--escrow-generation must be "v2" or "v3" (got "${declared}")`,
115
+ );
116
+ }
117
+ const deployment = registeredDeployment(network, escrow);
118
+ if (deployment === undefined && declared === undefined) {
119
+ throw configError(
120
+ `escrow ${escrow} is not a registered deployment of ${network.name}`,
121
+ `the SDK pins registered escrows by code hash; for your own deployment pass --escrow-generation v2|v3 (it then only checks the interface, which any contract can fake). Registered: ${[network.escrow, ...network.legacy.map((d) => d.escrow)].map((e) => `${e.address} (${e.generation})`).join(", ")}.`,
122
+ );
123
+ }
124
+ if (
125
+ deployment !== undefined &&
126
+ declared !== undefined &&
127
+ declared !== deployment.escrow.generation
128
+ ) {
129
+ throw configError(
130
+ `--escrow-generation ${declared} contradicts the registry: ${escrow} is the ${deployment.escrow.generation} escrow`,
131
+ );
132
+ }
133
+
81
134
  return {
82
- chain: baseSepolia,
135
+ network,
136
+ chain: network.chain,
83
137
  chainId,
84
138
  rpcUrl,
85
- escrow: resolveAddress(
86
- stringFlag(args, "escrow") ?? env.METRIK_AGENT_ESCROW,
87
- BASE_SEPOLIA_ESCROW_V2,
88
- "escrow",
89
- ),
139
+ escrow,
140
+ ...(deployment === undefined
141
+ ? { unregisteredEscrowGeneration: declared as "v2" | "v3" }
142
+ : {}),
90
143
  usdc: resolveAddress(
91
144
  stringFlag(args, "usdc") ?? env.METRIK_AGENT_USDC,
92
- BASE_SEPOLIA_USDC,
145
+ network.usdc.address,
93
146
  "usdc",
94
147
  ),
95
148
  oracleBaseUrl: resolveHttpUrl(
96
149
  stringFlag(args, "oracle-url") ?? env.METRIK_AGENT_ORACLE_URL,
97
- DEFAULT_ORACLE_BASE_URL,
150
+ network.oracleBaseUrl,
98
151
  "oracle-url",
99
152
  ),
100
153
  registryUrl: resolveHttpUrl(
101
154
  stringFlag(args, "registry-url") ?? env.METRIK_AGENT_REGISTRY_URL,
102
- DEFAULT_PUBLIC_REGISTRY_URL,
155
+ network.publicRegistryUrl ?? `${network.oracleBaseUrl}/listings`,
103
156
  "registry-url",
104
157
  ),
105
158
  };
106
159
  }
107
160
 
161
+ function resolveCliNetwork(
162
+ args: ParsedArgs,
163
+ env: NodeJS.ProcessEnv,
164
+ ): MetrikNetwork {
165
+ const chainId =
166
+ integerFlag(args, "chain-id") ?? parseEnvChainId(env.METRIK_AGENT_CHAIN_ID);
167
+ const selector = stringFlag(args, "network") ?? env[METRIK_NETWORK_ENV];
168
+ if (chainId !== undefined)
169
+ assertTestnetChainId(chainId, "the configured chain id");
170
+ let network: MetrikNetwork;
171
+ try {
172
+ network = resolveAgentNetwork(
173
+ selector !== undefined && selector.trim().length > 0 ? selector : chainId,
174
+ CLI_NETWORK_POLICY,
175
+ );
176
+ } catch (error) {
177
+ if (error instanceof UnsupportedNetworkError) {
178
+ throw configError(
179
+ `refusing to run against network "${error.selector}": it is not a supported testnet`,
180
+ `${error.message}. There is no override flag.`,
181
+ );
182
+ }
183
+ throw error;
184
+ }
185
+ if (chainId !== undefined && chainId !== network.chain.id) {
186
+ throw configError(
187
+ `--chain-id ${chainId} does not match --network ${network.id} (chain ${network.chain.id})`,
188
+ "give one of them, or make them agree.",
189
+ );
190
+ }
191
+ return network;
192
+ }
108
193
  /** Flags every command accepts for settlement configuration. */
109
194
  export const CONFIG_FLAGS: readonly string[] = [
195
+ "network",
196
+ "escrow-generation",
110
197
  "chain-id",
111
198
  "rpc-url",
112
199
  "escrow",
@@ -119,12 +206,12 @@ export const CONFIG_FLAGS: readonly string[] = [
119
206
  export const GLOBAL_FLAGS: readonly string[] = ["json", "help"];
120
207
 
121
208
  export function assertTestnetChainId(chainId: number, source: string): void {
122
- if (TESTNET_CHAIN_IDS.includes(chainId)) return;
209
+ if (testnetChainIds().includes(chainId)) return;
123
210
  throw configError(
124
211
  `refusing to run against chain ${chainId}: ${source} is not a supported testnet`,
125
- `this CLI signs with a raw private key and settles real value, so it is testnet-only. Supported: ${TESTNET_CHAIN_IDS.join(
126
- ", ",
127
- )} (Base Sepolia). There is no override flag.`,
212
+ `this CLI signs with a raw private key and settles real value, so it is testnet-only. Supported (registered testnets): ${describeAllowedNetworks(
213
+ CLI_NETWORK_POLICY,
214
+ )}. There is no override flag.`,
128
215
  );
129
216
  }
130
217
 
@@ -2,6 +2,7 @@ import type { ParsedArgs } from "./args.js";
2
2
  import type { CliConfig } from "./config.js";
3
3
  import type { CliDependencies } from "./deps.js";
4
4
  import type { Printer } from "./output.js";
5
+ import type { ProgressActivity } from "./progress.js";
5
6
 
6
7
  /** Everything a command needs, assembled once by the runner. */
7
8
  export interface CommandContext {
@@ -10,6 +11,12 @@ export interface CommandContext {
10
11
  readonly env: NodeJS.ProcessEnv;
11
12
  readonly out: Printer;
12
13
  readonly deps: CliDependencies;
14
+ /**
15
+ * The runner's heartbeat. A command in a deliberate, announced retry or poll
16
+ * names it here so the heartbeat says what it is waiting for. Optional so a
17
+ * command can be driven without the runner (tests); absent means no-op.
18
+ */
19
+ readonly progress?: ProgressActivity;
13
20
  }
14
21
 
15
22
  export interface CommandSpec {
@@ -26,5 +33,14 @@ export interface CommandSpec {
26
33
  * `signs`, since everything that broadcasts obviously needs a key.
27
34
  */
28
35
  readonly requiresKey?: boolean;
36
+ /**
37
+ * Replaces the generic wallet paragraph of `--help` (and the tag in the
38
+ * command list) when "Broadcasts a transaction" / "needs a key" would be
39
+ * false for this command — e.g. `pay`, whose payer signs an EIP-3009
40
+ * authorization and broadcasts nothing, and cannot use a Privy wallet.
41
+ */
42
+ readonly walletHelp?: { readonly tag: string; readonly text: string };
43
+ /** How a flag is shown in `--help`, when `--<name>` alone would mislead. */
44
+ readonly flagHelp?: Readonly<Record<string, string>>;
29
45
  readonly run: (context: CommandContext) => Promise<void>;
30
46
  }
package/src/cli/deps.ts CHANGED
@@ -31,6 +31,11 @@ import {
31
31
  type ReclaimVerifiedStreamResult,
32
32
  } from "../sdk/client.js";
33
33
  import type { SignedSpendMandate } from "../mandates/mandate.js";
34
+ import type {
35
+ PayX402WithMandateInput,
36
+ PayX402WithMandateResult,
37
+ } from "../x402-buyer/pay.js";
38
+ import type { RefundClient } from "@absol-labs/sdk/x402";
34
39
 
35
40
  import type { CliConfig } from "./config.js";
36
41
  import type { CliSignerAccount } from "./wallet.js";
@@ -65,6 +70,19 @@ export interface CliReadClient {
65
70
  ): Promise<bigint>;
66
71
  /** Live protocol fee, read from the escrow rather than hardcoded. */
67
72
  protocolFeeBps(): Promise<number>;
73
+ /**
74
+ * StreamEscrowV3 only: the stream's on-chain proven high-water
75
+ * (`provenCumulativeOf`). Both V3 buyer exits settle the operator at no less
76
+ * than this, and the operator can redeem it with `claimProven` — so it is the
77
+ * part of the operator's verified earnings the no-proof escape hatch can NOT
78
+ * take. Never called against a V2 escrow, which has no such view.
79
+ *
80
+ * Optional so an embedder's existing read client keeps compiling; a client
81
+ * without it is treated as "proven high-water unknown", which the CLI reports
82
+ * as worst case (nothing protected), never as protected.
83
+ */
84
+ provenCumulativeOf?(streamId: `0x${string}`): Promise<bigint>;
85
+
68
86
  /**
69
87
  * `StreamEscrowV2.escapeGraceSeconds` (public immutable). The buyer's no-proof
70
88
  * escape hatch unlocks at `expiresAt + escapeGraceSeconds`, so this is half of
@@ -197,6 +215,16 @@ export interface CliWriteClient {
197
215
  claimStream(
198
216
  input: CliStreamActionInput,
199
217
  ): Promise<StreamProofTransactionResult>;
218
+ /**
219
+ * Operator-only, StreamEscrowV3 only: redeem the stream's on-chain proven
220
+ * high-water (`claimProven`) WITHOUT a fresh Merkle proof — the only way to
221
+ * collect proven value whose checkpoint root has rotated out of the retained
222
+ * set. Optional so an embedder's existing write client keeps compiling; the
223
+ * CLI refuses, rather than falls back to a proof claim, when it is absent.
224
+ */
225
+ claimProvenStream?(
226
+ input: CliStreamActionInput,
227
+ ): Promise<StreamProofTransactionResult>;
200
228
  }
201
229
 
202
230
  /**
@@ -212,6 +240,32 @@ export interface CreateWriteClientOptions {
212
240
  readonly transport?: Transport;
213
241
  }
214
242
 
243
+ /**
244
+ * The buyer side of x402 (`metrik pay` / `metrik refund`). Behind the injectable
245
+ * surface like every other side effect, and loaded lazily by default so a CLI
246
+ * run that never pays over x402 never imports `@x402/*`.
247
+ */
248
+ export interface CliX402 {
249
+ /** Mandate-gated `payWithX402` (see `../x402-buyer/pay.ts`). */
250
+ readonly pay: (
251
+ input: PayX402WithMandateInput,
252
+ ) => Promise<PayX402WithMandateResult>;
253
+ /**
254
+ * The SDK's `refundClient`, bound to ONE stream and verifying every relay
255
+ * "success" against the chain this CLI is configured for.
256
+ */
257
+ readonly refundClient: (
258
+ config: CliConfig,
259
+ target: {
260
+ readonly refundUrl: string;
261
+ readonly streamId: `0x${string}`;
262
+ readonly opener?: `0x${string}`;
263
+ readonly maxAttempts?: number;
264
+ readonly maxDelayMs?: number;
265
+ },
266
+ ) => Promise<RefundClient>;
267
+ }
268
+
215
269
  export interface CliDependencies {
216
270
  readonly createReadClient: (config: CliConfig) => CliReadClient;
217
271
  /**
@@ -236,6 +290,8 @@ export interface CliDependencies {
236
290
  readonly createInvokeStreamReader: (config: CliConfig) => InvokeStreamReader;
237
291
  readonly nowSeconds: () => number;
238
292
  readonly sleep: (ms: number) => Promise<void>;
293
+ /** Optional so an embedder's existing dependency object keeps compiling. */
294
+ readonly x402?: CliX402;
239
295
  }
240
296
 
241
297
  /**
@@ -256,13 +312,29 @@ const STREAM_OPENED_EVENT = getAbiItem({
256
312
  * with nothing on screen. Bounding it here bounds every read and every
257
313
  * simulation the SDK performs, in one place.
258
314
  */
259
- function rpcTransport(config: CliConfig) {
315
+ export function rpcTransport(config: CliConfig) {
260
316
  return http(config.rpcUrl, {
261
317
  timeout: RPC_TIMEOUT_MS,
262
318
  retryCount: RPC_RETRY_COUNT,
263
319
  });
264
320
  }
265
321
 
322
+ /**
323
+ * An `--escrow` the registry does not record was declared with
324
+ * `--escrow-generation`: the only case the SDK may skip its code-hash pin.
325
+ */
326
+ function unregisteredEscrowOptions(config: CliConfig): {
327
+ readonly allowUnregisteredEscrow?: true;
328
+ readonly escrowGeneration?: "v2" | "v3";
329
+ } {
330
+ return config.unregisteredEscrowGeneration === undefined
331
+ ? {}
332
+ : {
333
+ allowUnregisteredEscrow: true,
334
+ escrowGeneration: config.unregisteredEscrowGeneration,
335
+ };
336
+ }
337
+
266
338
  /** Shared `MetrikClient` construction: one escrow, declared as V2 on both keys. */
267
339
  function createMetrikClient(
268
340
  config: CliConfig,
@@ -281,6 +353,7 @@ function createMetrikClient(
281
353
  usdc: config.usdc,
282
354
  oracleBaseUrl: config.oracleBaseUrl,
283
355
  settlementTarget: { chainId: config.chainId, escrow: config.escrow },
356
+ ...unregisteredEscrowOptions(config),
284
357
  ...(account === undefined ? {} : { account }),
285
358
  });
286
359
  }
@@ -309,6 +382,11 @@ export const defaultCliDependencies: CliDependencies = {
309
382
  async reclaimableForV2(streamId, cumulativeAmount) {
310
383
  return await metrik.reclaimableForV2(streamId, cumulativeAmount);
311
384
  },
385
+ async provenCumulativeOf(streamId) {
386
+ // The SDK refuses this (before any RPC) on a client whose escrow is not
387
+ // V3, so a V2 escrow can never be read through the V3 ABI here.
388
+ return await metrik.provenCumulativeOf(streamId);
389
+ },
312
390
  async nativeBalance(address) {
313
391
  return await metrik.publicClient.getBalance({ address });
314
392
  },
@@ -416,6 +494,7 @@ export const defaultCliDependencies: CliDependencies = {
416
494
  usdc: config.usdc,
417
495
  oracleBaseUrl: config.oracleBaseUrl,
418
496
  settlementTarget: { chainId: config.chainId, escrow: config.escrow },
497
+ ...unregisteredEscrowOptions(config),
419
498
  ...(account === null ? {} : { account }),
420
499
  };
421
500
  // The mandate-gated agent client owns every fund-moving call; the raw SDK
@@ -480,6 +559,15 @@ export const defaultCliDependencies: CliDependencies = {
480
559
  if (signedMandate === null) return await metrik.claim(input.streamId);
481
560
  return await agentClient.claimStream({ ...input, signedMandate });
482
561
  },
562
+ async claimProvenStream({ signedMandate, ...input }) {
563
+ if (signedMandate === null) {
564
+ return await metrik.claimProven(input.streamId);
565
+ }
566
+ return await agentClient.claimProvenStream({
567
+ ...input,
568
+ signedMandate,
569
+ });
570
+ },
483
571
  };
484
572
  },
485
573
  async discover(options) {
@@ -506,6 +594,28 @@ export const defaultCliDependencies: CliDependencies = {
506
594
  nowSeconds() {
507
595
  return Math.floor(Date.now() / 1000);
508
596
  },
597
+ x402: {
598
+ async pay(input) {
599
+ const { payX402WithMandate } = await import("../x402-buyer/pay.js");
600
+ return await payX402WithMandate(input);
601
+ },
602
+ async refundClient(config, target) {
603
+ const { refundClient } = await import("@absol-labs/sdk/x402");
604
+ return refundClient(target.refundUrl, {
605
+ streamId: target.streamId,
606
+ ...(target.opener === undefined ? {} : { opener: target.opener }),
607
+ // Every relay "success" is checked against the receipt on THIS chain.
608
+ verifyWith: createMetrikClient(config),
609
+ expectedChainId: config.chainId,
610
+ ...(target.maxAttempts === undefined
611
+ ? {}
612
+ : { maxAttempts: target.maxAttempts }),
613
+ ...(target.maxDelayMs === undefined
614
+ ? {}
615
+ : { maxDelayMs: target.maxDelayMs }),
616
+ });
617
+ },
618
+ },
509
619
  async sleep(ms) {
510
620
  await new Promise((resolve) => setTimeout(resolve, ms));
511
621
  },
package/src/cli/errors.ts CHANGED
@@ -76,6 +76,28 @@ function namesTheRevert(line: string): boolean {
76
76
  );
77
77
  }
78
78
 
79
+ /**
80
+ * Links that state the OUTCOME rather than open a sentence the next link
81
+ * completes — so the trailing "…and it reverted" carries nothing.
82
+ *
83
+ * `metrik status <a stream id that does not exist>` used to print
84
+ *
85
+ * stream 0x…: does not exist on the escrow: The contract function
86
+ * "getStream" reverted.
87
+ *
88
+ * The first link is the whole answer; the tail is viem restating that a revert
89
+ * is how the escrow says so. Contrast "reclaim simulation failed", which is a
90
+ * PREFIX — dropping the boilerplate after it would leave a failure with no
91
+ * stated reason at all, which is why the one-boilerplate rule exists.
92
+ */
93
+ const STATES_THE_OUTCOME: readonly RegExp[] = [
94
+ /\bdoes not exist on the escrow\b/,
95
+ ];
96
+
97
+ function statesTheOutcome(line: string): boolean {
98
+ return STATES_THE_OUTCOME.some((pattern) => pattern.test(line));
99
+ }
100
+
79
101
  function isBoilerplate(line: string): boolean {
80
102
  return VIEM_BOILERPLATE.some((pattern) => pattern.test(line));
81
103
  }
@@ -108,7 +130,8 @@ export function stripRedundantBoilerplate(
108
130
  chain: readonly string[],
109
131
  ): readonly string[] {
110
132
  const decoded = chain.some(
111
- (line) => namesTheRevert(line) && !isBoilerplate(line),
133
+ (line) =>
134
+ (namesTheRevert(line) || statesTheOutcome(line)) && !isBoilerplate(line),
112
135
  );
113
136
  let keptBoilerplate = false;
114
137
  const kept = chain.filter((line, index) => {
@@ -1,5 +1,7 @@
1
1
  import type { StreamV2 } from "@absol-labs/sdk";
2
2
 
3
+ import { escapeHatchReadyAtFormula } from "../escrow-generation.js";
4
+
3
5
  import { formatDuration } from "./amounts.js";
4
6
  import type { CliReadClient } from "./deps.js";
5
7
 
@@ -17,9 +19,11 @@ import type { CliReadClient } from "./deps.js";
17
19
  * — and `escapeGraceSeconds` is a public immutable on the escrow, so readiness
18
20
  * is fully computable before a single wei of gas is spent.
19
21
  *
20
- * Note which timestamp the escrow uses: `expiresAt`, NEVER `closedAt`. Closing a
21
- * stream early does not bring the escape hatch forward, and the CLI must not
22
- * imply that it does.
22
+ * Note which timestamp the escrow uses, which depends on its GENERATION (the
23
+ * SDK registry's `escrow.generation`; see `../escrow-generation.ts`): V2 uses
24
+ * `expiresAt`, NEVER `closedAt` — closing early does not bring the hatch
25
+ * forward, and the CLI must not imply that it does. V3 opens it at
26
+ * `min(expiresAt, closedAt) + escapeGraceSeconds`.
23
27
  */
24
28
  export interface EscapeHatchWindow {
25
29
  /** `escapeGraceSeconds` as read from the escrow. */
@@ -32,11 +36,21 @@ export interface EscapeHatchWindow {
32
36
  }
33
37
 
34
38
  export function escapeHatchWindow(
35
- stream: Pick<StreamV2, "expiresAt">,
39
+ stream: Pick<StreamV2, "expiresAt"> & Partial<Pick<StreamV2, "closedAt">>,
36
40
  graceSeconds: number,
37
41
  nowSeconds: number,
42
+ generation = "v2",
38
43
  ): EscapeHatchWindow {
39
- const readyAt = stream.expiresAt + graceSeconds;
44
+ const readyAt = Number(
45
+ escapeHatchReadyAtFormula(
46
+ generation,
47
+ {
48
+ expiresAt: BigInt(stream.expiresAt),
49
+ closedAt: BigInt(stream.closedAt ?? 0),
50
+ },
51
+ BigInt(graceSeconds),
52
+ ),
53
+ );
40
54
  const secondsRemaining = readyAt - nowSeconds;
41
55
  return {
42
56
  graceSeconds,
@@ -18,6 +18,30 @@ export async function resolveListing(
18
18
  serviceRef: `0x${string}`,
19
19
  options: { readonly required: boolean },
20
20
  ): Promise<ServiceListing | null> {
21
+ return (await resolveListingRecord(context, serviceRef, options)).listing;
22
+ }
23
+
24
+ /**
25
+ * What {@link resolveListing} found, including the verified signed record of a
26
+ * matching row even when that row is not usable as a listing (no signed
27
+ * invocation URL) — `open` still has to read the operator-signed rate band off
28
+ * it — and whether the registry was unreachable (the only case in which a
29
+ * signed band may exist and be unknown).
30
+ */
31
+ export interface ListingRecordResolution {
32
+ readonly listing: ServiceListing | null;
33
+ readonly record: {
34
+ readonly serviceRef: string;
35
+ readonly signed: ServiceListing["signed"];
36
+ } | null;
37
+ readonly registryUnreachable: boolean;
38
+ }
39
+
40
+ export async function resolveListingRecord(
41
+ context: CommandContext,
42
+ serviceRef: `0x${string}`,
43
+ options: { readonly required: boolean },
44
+ ): Promise<ListingRecordResolution> {
21
45
  const { config, out, deps } = context;
22
46
  let services;
23
47
  try {
@@ -32,7 +56,7 @@ export async function resolveListing(
32
56
  out.note(
33
57
  `warning: the registry at ${config.registryUrl} is unreachable; continuing without a verified listing.`,
34
58
  );
35
- return null;
59
+ return { listing: null, record: null, registryUnreachable: true };
36
60
  }
37
61
  throw new CliError(
38
62
  `the service registry at ${config.registryUrl} could not be read`,
@@ -45,7 +69,9 @@ export async function resolveListing(
45
69
  (service) => service.serviceRef.toLowerCase() === wanted,
46
70
  );
47
71
  if (match === undefined) {
48
- if (!options.required) return null;
72
+ if (!options.required) {
73
+ return { listing: null, record: null, registryUnreachable: false };
74
+ }
49
75
  throw new CliError(
50
76
  `no verified listing in the registry matches serviceRef ${serviceRef}`,
51
77
  {
@@ -54,8 +80,11 @@ export async function resolveListing(
54
80
  },
55
81
  );
56
82
  }
83
+ const record = { serviceRef: match.serviceRef, signed: match.signed };
57
84
  if (match.accessUrl === null) {
58
- if (!options.required) return null;
85
+ if (!options.required) {
86
+ return { listing: null, record, registryUnreachable: false };
87
+ }
59
88
  throw new CliError(
60
89
  `listing ${serviceRef} carries no signed invocation URL`,
61
90
  {
@@ -66,12 +95,16 @@ export async function resolveListing(
66
95
  }
67
96
 
68
97
  return {
69
- serviceRef: match.serviceRef,
70
- operator: match.operator,
71
- publicUrl: match.publicUrl,
72
- access: match.access,
73
- accessUrl: match.accessUrl,
74
- signed: match.signed,
75
- category: match.category,
98
+ listing: {
99
+ serviceRef: match.serviceRef,
100
+ operator: match.operator,
101
+ publicUrl: match.publicUrl,
102
+ access: match.access,
103
+ accessUrl: match.accessUrl,
104
+ signed: match.signed,
105
+ category: match.category,
106
+ },
107
+ record,
108
+ registryUnreachable: false,
76
109
  };
77
110
  }