@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
@@ -7,6 +7,7 @@ import {
7
7
  streamReportRows,
8
8
  } from "../status.js";
9
9
  import type { CommandContext, CommandSpec } from "../context.js";
10
+ import { escrowGenerationFor } from "../../escrow-generation.js";
10
11
 
11
12
  export const statusCommand: CommandSpec = {
12
13
  name: "status",
@@ -28,7 +29,12 @@ export const statusCommand: CommandSpec = {
28
29
  // diverge from index 5 onward, so a wrong-chain read does not revert — it
29
30
  // silently decodes something else into fields with the right names.
30
31
  await assertTestnetRpc(config, () => read.getChainId());
31
- const report = await readStreamReport(read, streamId, deps.nowSeconds());
32
+ const report = await readStreamReport(
33
+ read,
34
+ streamId,
35
+ deps.nowSeconds(),
36
+ escrowGenerationFor(config.chainId, config.escrow),
37
+ );
32
38
 
33
39
  out.result(streamReportJson(report));
34
40
  // Who attests, before the numbers. Printed on stderr so it survives `--json`
@@ -40,6 +46,16 @@ export const statusCommand: CommandSpec = {
40
46
  if (disclosure !== null) out.note(disclosure);
41
47
  out.print(`Stream ${streamId}`);
42
48
  out.table(streamReportRows(report));
49
+ const opener = report.stream.opener ?? null;
50
+ if (opener !== null) {
51
+ out.print("");
52
+ out.print(
53
+ `Bought over x402: the escrow accepts close/reclaim only from the opener ${opener}. ` +
54
+ `\`metrik close ${streamId}\` (signed by the payer ${report.stream.payer}) routes through it; ` +
55
+ `\`metrik reclaim ${streamId}\` sends the permissionless opener.reclaim, and the refund always goes to the payer. ` +
56
+ "A payer with no gas can use the seller's relay instead: `metrik refund <streamId> --refund-url <url> --close --reclaim`. Nothing refunds automatically.",
57
+ );
58
+ }
43
59
  if (report.settlement.pending) {
44
60
  out.print("");
45
61
  out.print(
@@ -1,11 +1,11 @@
1
- import { BASE_SEPOLIA_ESCROW_V2 } from "@absol-labs/sdk";
2
1
  import { getAddress, isAddress } from "viem";
3
2
 
4
3
  import { integerFlag, stringFlag } from "../args.js";
5
4
  import { formatUsdc } from "../amounts.js";
6
5
  import {
7
- BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK,
8
6
  assertTestnetRpc,
7
+ escrowDeployBlock,
8
+ type CliConfig,
9
9
  } from "../config.js";
10
10
  import { CliError, configError, usageError } from "../errors.js";
11
11
  import { escrowScanRange } from "../scan-range.js";
@@ -32,7 +32,7 @@ const SCAN_CONCURRENCY = 6;
32
32
  /**
33
33
  * Attempts per window before it is reported as unscanned. A public RPC answers
34
34
  * a burst of `eth_getLogs` with an occasional 429 — measured against
35
- * `sepolia.base.org`, 2 of 212 windows on a full-history scan — and a transient
35
+ * the network's public RPC, 2 of 212 windows on a full-history scan — and a transient
36
36
  * rate-limit is NOT a window that cannot be scanned. Declaring it one would
37
37
  * make the "this scan is incomplete" warning routine, and a warning that fires
38
38
  * on every healthy run is a warning nobody reads on the run that matters.
@@ -71,7 +71,7 @@ export const streamsCommand: CommandSpec = {
71
71
  // else into fields with the right names.
72
72
  await assertTestnetRpc(config, () => read.getChainId());
73
73
 
74
- const anchor = resolveAnchorBlock(args, config.escrow);
74
+ const anchor = resolveAnchorBlock(args, config);
75
75
  const head = await read.blockNumber();
76
76
  const range = escrowScanRange(head, anchor.block);
77
77
 
@@ -251,25 +251,21 @@ async function resolveScanAddress(
251
251
  */
252
252
  function resolveAnchorBlock(
253
253
  args: CommandContext["args"],
254
- escrow: `0x${string}`,
254
+ config: CliConfig,
255
255
  ): { readonly block: bigint; readonly canary: bigint | null } {
256
256
  const override = integerFlag(args, "from-block");
257
- const isKnownEscrow =
258
- escrow.toLowerCase() === BASE_SEPOLIA_ESCROW_V2.toLowerCase();
257
+ const deployBlock = escrowDeployBlock(config);
259
258
 
260
259
  if (override !== undefined) {
261
260
  return { block: BigInt(override), canary: null };
262
261
  }
263
- if (!isKnownEscrow) {
262
+ if (deployBlock === undefined) {
264
263
  throw configError(
265
- `no scan anchor is known for escrow ${escrow}`,
266
- "the scan is anchored at the escrow's deploy block, and this CLI only ships that block for its default Base Sepolia escrow. Pass --from-block <deploy block> for a different escrow.",
264
+ `no scan anchor is known for escrow ${config.escrow}`,
265
+ `the scan is anchored at the escrow's deploy block, and the network registry only records it for ${config.network.name}'s registered escrow (${config.network.escrow.address}). Pass --from-block <deploy block> for a different escrow.`,
267
266
  );
268
267
  }
269
- return {
270
- block: BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK,
271
- canary: BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK,
272
- };
268
+ return { block: deployBlock, canary: deployBlock };
273
269
  }
274
270
 
275
271
  /**
@@ -7,6 +7,7 @@ import {
7
7
  streamReportRows,
8
8
  } from "../status.js";
9
9
  import type { CommandContext, CommandSpec } from "../context.js";
10
+ import { escrowGenerationFor } from "../../escrow-generation.js";
10
11
 
11
12
  const DEFAULT_INTERVAL_SECONDS = 15;
12
13
 
@@ -43,7 +44,12 @@ export const watchCommand: CommandSpec = {
43
44
 
44
45
  let report;
45
46
  try {
46
- report = await readStreamReport(read, streamId, deps.nowSeconds());
47
+ report = await readStreamReport(
48
+ read,
49
+ streamId,
50
+ deps.nowSeconds(),
51
+ escrowGenerationFor(config.chainId, config.escrow),
52
+ );
47
53
  } catch (error) {
48
54
  // A transient RPC blip must not end a long watch.
49
55
  out.note(`read failed, retrying: ${describeError(error)}`);
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
 
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";
@@ -212,6 +217,32 @@ export interface CreateWriteClientOptions {
212
217
  readonly transport?: Transport;
213
218
  }
214
219
 
220
+ /**
221
+ * The buyer side of x402 (`metrik pay` / `metrik refund`). Behind the injectable
222
+ * surface like every other side effect, and loaded lazily by default so a CLI
223
+ * run that never pays over x402 never imports `@x402/*`.
224
+ */
225
+ export interface CliX402 {
226
+ /** Mandate-gated `payWithX402` (see `../x402-buyer/pay.ts`). */
227
+ readonly pay: (
228
+ input: PayX402WithMandateInput,
229
+ ) => Promise<PayX402WithMandateResult>;
230
+ /**
231
+ * The SDK's `refundClient`, bound to ONE stream and verifying every relay
232
+ * "success" against the chain this CLI is configured for.
233
+ */
234
+ readonly refundClient: (
235
+ config: CliConfig,
236
+ target: {
237
+ readonly refundUrl: string;
238
+ readonly streamId: `0x${string}`;
239
+ readonly opener?: `0x${string}`;
240
+ readonly maxAttempts?: number;
241
+ readonly maxDelayMs?: number;
242
+ },
243
+ ) => Promise<RefundClient>;
244
+ }
245
+
215
246
  export interface CliDependencies {
216
247
  readonly createReadClient: (config: CliConfig) => CliReadClient;
217
248
  /**
@@ -236,6 +267,8 @@ export interface CliDependencies {
236
267
  readonly createInvokeStreamReader: (config: CliConfig) => InvokeStreamReader;
237
268
  readonly nowSeconds: () => number;
238
269
  readonly sleep: (ms: number) => Promise<void>;
270
+ /** Optional so an embedder's existing dependency object keeps compiling. */
271
+ readonly x402?: CliX402;
239
272
  }
240
273
 
241
274
  /**
@@ -256,13 +289,29 @@ const STREAM_OPENED_EVENT = getAbiItem({
256
289
  * with nothing on screen. Bounding it here bounds every read and every
257
290
  * simulation the SDK performs, in one place.
258
291
  */
259
- function rpcTransport(config: CliConfig) {
292
+ export function rpcTransport(config: CliConfig) {
260
293
  return http(config.rpcUrl, {
261
294
  timeout: RPC_TIMEOUT_MS,
262
295
  retryCount: RPC_RETRY_COUNT,
263
296
  });
264
297
  }
265
298
 
299
+ /**
300
+ * An `--escrow` the registry does not record was declared with
301
+ * `--escrow-generation`: the only case the SDK may skip its code-hash pin.
302
+ */
303
+ function unregisteredEscrowOptions(config: CliConfig): {
304
+ readonly allowUnregisteredEscrow?: true;
305
+ readonly escrowGeneration?: "v2" | "v3";
306
+ } {
307
+ return config.unregisteredEscrowGeneration === undefined
308
+ ? {}
309
+ : {
310
+ allowUnregisteredEscrow: true,
311
+ escrowGeneration: config.unregisteredEscrowGeneration,
312
+ };
313
+ }
314
+
266
315
  /** Shared `MetrikClient` construction: one escrow, declared as V2 on both keys. */
267
316
  function createMetrikClient(
268
317
  config: CliConfig,
@@ -281,6 +330,7 @@ function createMetrikClient(
281
330
  usdc: config.usdc,
282
331
  oracleBaseUrl: config.oracleBaseUrl,
283
332
  settlementTarget: { chainId: config.chainId, escrow: config.escrow },
333
+ ...unregisteredEscrowOptions(config),
284
334
  ...(account === undefined ? {} : { account }),
285
335
  });
286
336
  }
@@ -416,6 +466,7 @@ export const defaultCliDependencies: CliDependencies = {
416
466
  usdc: config.usdc,
417
467
  oracleBaseUrl: config.oracleBaseUrl,
418
468
  settlementTarget: { chainId: config.chainId, escrow: config.escrow },
469
+ ...unregisteredEscrowOptions(config),
419
470
  ...(account === null ? {} : { account }),
420
471
  };
421
472
  // The mandate-gated agent client owns every fund-moving call; the raw SDK
@@ -506,6 +557,28 @@ export const defaultCliDependencies: CliDependencies = {
506
557
  nowSeconds() {
507
558
  return Math.floor(Date.now() / 1000);
508
559
  },
560
+ x402: {
561
+ async pay(input) {
562
+ const { payX402WithMandate } = await import("../x402-buyer/pay.js");
563
+ return await payX402WithMandate(input);
564
+ },
565
+ async refundClient(config, target) {
566
+ const { refundClient } = await import("@absol-labs/sdk/x402");
567
+ return refundClient(target.refundUrl, {
568
+ streamId: target.streamId,
569
+ ...(target.opener === undefined ? {} : { opener: target.opener }),
570
+ // Every relay "success" is checked against the receipt on THIS chain.
571
+ verifyWith: createMetrikClient(config),
572
+ expectedChainId: config.chainId,
573
+ ...(target.maxAttempts === undefined
574
+ ? {}
575
+ : { maxAttempts: target.maxAttempts }),
576
+ ...(target.maxDelayMs === undefined
577
+ ? {}
578
+ : { maxDelayMs: target.maxDelayMs }),
579
+ });
580
+ },
581
+ },
509
582
  async sleep(ms) {
510
583
  await new Promise((resolve) => setTimeout(resolve, ms));
511
584
  },
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,