@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/status.ts CHANGED
@@ -75,6 +75,155 @@ export function settlementCoverage(
75
75
  };
76
76
  }
77
77
 
78
+ /**
79
+ * How much of the operator's verified, unclaimed accrual the buyer's no-proof
80
+ * escape hatch (`reclaimUnverified`) could take. All amounts are GROSS
81
+ * cumulative USDC base units (before the protocol fee), the unit the escrow
82
+ * settles in; the operator receives a claimed amount net of the fee.
83
+ *
84
+ * The rule depends on the escrow's generation:
85
+ *
86
+ * - **V2**: the hatch settles the operator at `claimedCumulative`, so every
87
+ * verified-but-unclaimed unit is exposed.
88
+ * - **V3**: the hatch settles the operator at
89
+ * `max(claimedCumulative, provenCumulative)` — the on-chain proven high-water
90
+ * the oracle's `recordProven` keeper (or any claim / reclaim with a proof)
91
+ * writes, and which the operator can redeem with `claimProven`. Only verified
92
+ * value NOBODY has proven on-chain is exposed.
93
+ */
94
+ export interface OperatorHatchExposure {
95
+ /** Verified cumulative from the oracle proof, capped at the deposit (gross). */
96
+ readonly verifiedCumulative: bigint;
97
+ /** What the hatch would still settle to the operator (gross). */
98
+ readonly protectedCumulative: bigint;
99
+ /** `max(0, verifiedCumulative − protectedCumulative)` (gross). */
100
+ readonly atRiskCumulative: bigint;
101
+ /**
102
+ * V3 only: whether `provenCumulative` was read. `false` means it was assumed
103
+ * to be zero (worst case for the operator), never assumed to be protective.
104
+ * Always `true` on V2, which has no such view.
105
+ */
106
+ readonly provenCumulativeKnown: boolean;
107
+ }
108
+
109
+ /**
110
+ * The exposure for a stream that has NOT been reclaimed and has a verified
111
+ * proof. Pure, so the per-generation rule is testable without an RPC. Returns
112
+ * `null` when there is nothing to reason about (no proof, or already reclaimed:
113
+ * a reclaimed stream has no hatch left to open).
114
+ */
115
+ export function operatorHatchExposure(
116
+ stream: Pick<StreamV2, "deposit" | "claimedCumulative" | "reclaimed">,
117
+ verifiedCumulative: bigint | null,
118
+ generation: string,
119
+ provenCumulative: bigint | null,
120
+ ): OperatorHatchExposure | null {
121
+ if (stream.reclaimed || verifiedCumulative === null) return null;
122
+ const verified =
123
+ verifiedCumulative > stream.deposit ? stream.deposit : verifiedCumulative;
124
+ let protectedCumulative = stream.claimedCumulative;
125
+ let provenCumulativeKnown = true;
126
+ if (generation === "v3") {
127
+ if (provenCumulative === null) {
128
+ provenCumulativeKnown = false;
129
+ } else if (provenCumulative > protectedCumulative) {
130
+ protectedCumulative = provenCumulative;
131
+ }
132
+ }
133
+ const atRiskCumulative =
134
+ verified > protectedCumulative ? verified - protectedCumulative : 0n;
135
+ return {
136
+ verifiedCumulative: verified,
137
+ protectedCumulative,
138
+ atRiskCumulative,
139
+ provenCumulativeKnown,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * A stream whose buyer side is over, read from chain state. All amounts gross.
145
+ *
146
+ * Two ways to get here:
147
+ *
148
+ * - `reclaimed`: the buyer reclaimed. Read from the stream tuple, which reclaim
149
+ * WRITES, unlike during the stream's active life. The buyer's refund is
150
+ * deliberately absent: reclaim overwrites `deposit` with the operator's
151
+ * entitlement, so `deposit − settled` is NOT the refund, and the true figure
152
+ * lives only in the reclaim's `BuyerReclaimed` event, which this screen does
153
+ * not scan for (the reclaim block is unknown, so the lookup is not bounded).
154
+ * - `deposit-exhausted`: the stream has stopped (closed, or past expiry) and the
155
+ * operator's floor already covers the whole deposit, so there is nothing to
156
+ * reclaim and the buyer never will. Both buyer exits settle the operator at no
157
+ * less than `max(claimedCumulative, provenCumulative)` (V2:
158
+ * `claimedCumulative`) and revert `NothingToReclaim` when `deposit − floor` is
159
+ * zero, so `reclaimed` stays `false` forever on such a stream.
160
+ */
161
+ export interface FinalSettlement {
162
+ readonly finishedBy: "reclaimed" | "deposit-exhausted";
163
+ /**
164
+ * The operator's entitlement: `settledCumulative` (fixed at reclaim), or the
165
+ * whole `deposit` when it was exhausted.
166
+ */
167
+ readonly settledToOperator: bigint;
168
+ /** `claimedCumulative`: what the operator has redeemed so far. */
169
+ readonly operatorClaimed: bigint;
170
+ /** `settledToOperator − operatorClaimed`, still escrowed for the operator. */
171
+ readonly operatorUnclaimed: bigint;
172
+ }
173
+
174
+ /**
175
+ * Whether the buyer side of a stream is finished, and what the operator holds.
176
+ * Pure, so the rule is testable without an RPC.
177
+ *
178
+ * `stopped` is the escrow's own reclaim gate (closed, or `now >= expiresAt`).
179
+ * `provenCumulative` counts only on V3, and only when it was READ: an
180
+ * unreadable high-water is treated as zero, so it can never make a stream look
181
+ * finished that is not.
182
+ */
183
+ export function finalSettlement(
184
+ stream: Pick<
185
+ StreamV2,
186
+ "reclaimed" | "settledCumulative" | "claimedCumulative" | "deposit"
187
+ >,
188
+ exhaustion?: {
189
+ readonly stopped: boolean;
190
+ readonly generation: string;
191
+ readonly provenCumulative: bigint | null;
192
+ },
193
+ ): FinalSettlement | null {
194
+ if (stream.reclaimed) {
195
+ return settled("reclaimed", stream.settledCumulative, stream);
196
+ }
197
+ if (exhaustion === undefined || !exhaustion.stopped) return null;
198
+ if (stream.deposit <= 0n) return null;
199
+ let operatorFloor = stream.claimedCumulative;
200
+ if (
201
+ exhaustion.generation === "v3" &&
202
+ exhaustion.provenCumulative !== null &&
203
+ exhaustion.provenCumulative > operatorFloor
204
+ ) {
205
+ operatorFloor = exhaustion.provenCumulative;
206
+ }
207
+ if (operatorFloor < stream.deposit) return null;
208
+ return settled("deposit-exhausted", stream.deposit, stream);
209
+ }
210
+
211
+ function settled(
212
+ finishedBy: FinalSettlement["finishedBy"],
213
+ settledToOperator: bigint,
214
+ stream: Pick<StreamV2, "claimedCumulative">,
215
+ ): FinalSettlement {
216
+ return {
217
+ finishedBy,
218
+ settledToOperator,
219
+ operatorClaimed: stream.claimedCumulative,
220
+ operatorUnclaimed:
221
+ settledToOperator > stream.claimedCumulative
222
+ ? settledToOperator - stream.claimedCumulative
223
+ : 0n,
224
+ };
225
+ }
226
+
78
227
  /**
79
228
  * A stream as the CLI reports it.
80
229
  *
@@ -92,6 +241,12 @@ export interface StreamReport {
92
241
  /** Why there is no proof yet, when applicable. */
93
242
  readonly proofUnavailable: string | null;
94
243
  readonly claimable: bigint | null;
244
+ /**
245
+ * What {@link claimable} was computed against: the oracle's current proof,
246
+ * or — V3, when the latest checkpoint no longer contains the stream — the
247
+ * on-chain proven high-water, which `claimProven` redeems without a proof.
248
+ */
249
+ readonly claimableSource: "checkpoint-proof" | "proven-on-chain" | null;
95
250
  readonly reclaimable: bigint | null;
96
251
  readonly protocolFeeBps: number | null;
97
252
  /**
@@ -120,12 +275,30 @@ export interface StreamReport {
120
275
  * "no overlap".
121
276
  */
122
277
  readonly attesters: AttesterSet;
278
+ /** The escrow generation this report was read against (`v2` / `v3`). */
279
+ readonly escrowGeneration: string;
280
+ /**
281
+ * V3 only: the on-chain proven high-water (`provenCumulativeOf`). `null` on
282
+ * V2, or when the read failed — see {@link provenUnavailable}.
283
+ */
284
+ readonly provenCumulative: bigint | null;
285
+ /** Why a V3 proven high-water could not be read, when applicable. */
286
+ readonly provenUnavailable: string | null;
287
+ /** See {@link operatorHatchExposure}. `null` when not applicable. */
288
+ readonly operatorExposure: OperatorHatchExposure | null;
289
+ /**
290
+ * See {@link finalSettlement}. Non-null exactly when the buyer side is over:
291
+ * reclaimed, or stopped with the whole deposit already the operator's.
292
+ */
293
+ readonly finalSettlement: FinalSettlement | null;
123
294
  }
124
295
 
125
296
  export async function readStreamReport(
126
297
  read: CliReadClient,
127
298
  streamId: `0x${string}`,
128
299
  nowSeconds: number,
300
+ /** The escrow's generation (decides the escape-hatch rule). Default `v2`. */
301
+ escrowGeneration = "v2",
129
302
  ): Promise<StreamReport> {
130
303
  const stream = await read.getStreamV2(streamId);
131
304
 
@@ -142,13 +315,38 @@ export async function readStreamReport(
142
315
  proofUnavailable = describeError(error);
143
316
  }
144
317
 
318
+ let provenCumulative: bigint | null = null;
319
+ let provenUnavailable: string | null = null;
320
+ if (escrowGeneration === "v3") {
321
+ if (read.provenCumulativeOf === undefined) {
322
+ provenUnavailable = "this read client cannot read provenCumulativeOf";
323
+ } else {
324
+ try {
325
+ provenCumulative = await read.provenCumulativeOf(streamId);
326
+ } catch (error) {
327
+ provenUnavailable = describeError(error);
328
+ }
329
+ }
330
+ }
331
+
145
332
  let claimable: bigint | null = null;
333
+ let claimableSource: StreamReport["claimableSource"] = null;
146
334
  let reclaimable: bigint | null = null;
147
335
  if (proof !== null) {
148
336
  [claimable, reclaimable] = await Promise.all([
149
337
  read.claimableForV2(streamId, proof.cumulativeAmount),
150
338
  read.reclaimableForV2(streamId, proof.cumulativeAmount),
151
339
  ]);
340
+ claimableSource = "checkpoint-proof";
341
+ } else if (
342
+ provenCumulative !== null &&
343
+ provenCumulative > stream.claimedCumulative
344
+ ) {
345
+ // The latest checkpoint no longer lists the stream, but a value proven
346
+ // against a signed root is recorded on-chain. `claimableFor` at that value
347
+ // is exactly what `claimProven` pays (same `_payableCumulative` and fee).
348
+ claimable = await read.claimableForV2(streamId, provenCumulative);
349
+ claimableSource = "proven-on-chain";
152
350
  }
153
351
 
154
352
  let protocolFeeBps: number | null = null;
@@ -164,6 +362,7 @@ export async function readStreamReport(
164
362
  stream,
165
363
  await read.escapeGraceSeconds(),
166
364
  now.seconds,
365
+ escrowGeneration,
167
366
  );
168
367
  } catch {
169
368
  escapeHatch = null;
@@ -176,11 +375,19 @@ export async function readStreamReport(
176
375
  latestCheckpointCheckedAt = null;
177
376
  }
178
377
 
179
- const settlement = settlementCoverage(
378
+ const coverage = settlementCoverage(
180
379
  stream,
181
380
  latestCheckpointCheckedAt,
182
381
  now.seconds,
183
382
  );
383
+ const final = finalSettlement(stream, {
384
+ stopped: coverage.stopAt !== null,
385
+ generation: escrowGeneration,
386
+ provenCumulative,
387
+ });
388
+ // A finished stream is settled: there is no checkpoint left to wait for.
389
+ const settlement: SettlementCoverage =
390
+ final !== null ? { ...coverage, pending: false } : coverage;
184
391
  const attesters = await readAttesterSet(read);
185
392
 
186
393
  return {
@@ -189,6 +396,7 @@ export async function readStreamReport(
189
396
  proof,
190
397
  proofUnavailable,
191
398
  claimable,
399
+ claimableSource,
192
400
  reclaimable,
193
401
  protocolFeeBps,
194
402
  escapeHatch,
@@ -197,6 +405,20 @@ export async function readStreamReport(
197
405
  clockSource: now.source,
198
406
  settlement,
199
407
  attesters,
408
+ escrowGeneration,
409
+ provenCumulative,
410
+ provenUnavailable,
411
+ // A finished stream has no hatch left that could take anything.
412
+ operatorExposure:
413
+ final !== null
414
+ ? null
415
+ : operatorHatchExposure(
416
+ stream,
417
+ proof === null ? null : proof.cumulativeAmount,
418
+ escrowGeneration,
419
+ provenCumulative,
420
+ ),
421
+ finalSettlement: final,
200
422
  };
201
423
  }
202
424
 
@@ -208,6 +430,11 @@ export function streamReportJson(report: StreamReport): unknown {
208
430
  status: stream.status,
209
431
  reclaimed: stream.reclaimed,
210
432
  buyer: stream.buyer,
433
+ // An x402 stream's `buyer` is the X402StreamOpener contract, not the
434
+ // person who paid; `payer` is who any refund goes to.
435
+ opener: stream.opener ?? null,
436
+ payer: stream.payer ?? stream.buyer,
437
+ boughtVia: (stream.opener ?? null) === null ? "wallet" : "x402-opener",
211
438
  operator: stream.operator,
212
439
  serviceRef: stream.serviceRef,
213
440
  deposit: stream.deposit,
@@ -234,8 +461,21 @@ export function streamReportJson(report: StreamReport): unknown {
234
461
  // settled-looking number derived from a checkpoint that predates the
235
462
  // stream's stop time. The superseded figure is still available, explicitly
236
463
  // labelled, under `settlement.provisionalReclaimable`.
237
- claimable: report.claimable,
238
- reclaimable: report.settlement.pending ? null : report.reclaimable,
464
+ // A finished stream with nothing left for the operator has nothing
465
+ // claimable, even when no proof was fetched.
466
+ claimable:
467
+ report.claimable ??
468
+ (report.finalSettlement?.operatorUnclaimed === 0n ? 0n : null),
469
+ // `checkpoint-proof`, `proven-on-chain` (V3: what `claimProven` pays), or
470
+ // `null` when `claimable` is.
471
+ claimableSource: report.claimableSource,
472
+ // Nothing is reclaimable from a stream whose buyer side is finished.
473
+ reclaimable:
474
+ report.finalSettlement !== null
475
+ ? 0n
476
+ : report.settlement.pending
477
+ ? null
478
+ : report.reclaimable,
239
479
  settlement: {
240
480
  pending: report.settlement.pending,
241
481
  stopAt: report.settlement.stopAt,
@@ -263,6 +503,39 @@ export function streamReportJson(report: StreamReport): unknown {
263
503
  ready: report.escapeHatch.ready,
264
504
  secondsUntilReady: report.escapeHatch.secondsRemaining,
265
505
  },
506
+ escrowGeneration: report.escrowGeneration,
507
+ // V3 only; `null` on V2 or when unreadable (`provenCumulativeUnavailable`).
508
+ provenCumulative: report.provenCumulative,
509
+ provenCumulativeUnavailable: report.provenUnavailable,
510
+ // True once nothing more is owed to, or can be reclaimed by, the buyer —
511
+ // reclaimed, or stopped with the whole deposit already the operator's —
512
+ // and the escape hatch no longer applies.
513
+ buyerSideFinished: report.finalSettlement !== null,
514
+ // Non-null exactly when `buyerSideFinished`. Gross base units. The buyer's
515
+ // refund is not included: see `FinalSettlement`.
516
+ finalSettlement:
517
+ report.finalSettlement === null
518
+ ? null
519
+ : {
520
+ finishedBy: report.finalSettlement.finishedBy,
521
+ settledToOperator: report.finalSettlement.settledToOperator,
522
+ operatorClaimed: report.finalSettlement.operatorClaimed,
523
+ operatorUnclaimed: report.finalSettlement.operatorUnclaimed,
524
+ },
525
+ // What the buyer's no-proof escape hatch could take from the operator.
526
+ // `null` once reclaimed or before any proof. Gross base units.
527
+ operatorHatchExposure:
528
+ report.operatorExposure === null
529
+ ? null
530
+ : {
531
+ units: "gross",
532
+ verifiedCumulative: report.operatorExposure.verifiedCumulative,
533
+ protectedCumulative: report.operatorExposure.protectedCumulative,
534
+ atRiskCumulative: report.operatorExposure.atRiskCumulative,
535
+ protectedOnChain: report.operatorExposure.atRiskCumulative === 0n,
536
+ provenCumulativeKnown:
537
+ report.operatorExposure.provenCumulativeKnown,
538
+ },
266
539
  };
267
540
  }
268
541
 
@@ -271,14 +544,24 @@ export function streamReportRows(
271
544
  ): (readonly [string, string])[] {
272
545
  const { stream, proof } = report;
273
546
  const rows: (readonly [string, string])[] = [
274
- [
275
- "status",
276
- stream.reclaimed ? `${stream.status} (reclaimed)` : stream.status,
277
- ],
278
- ["buyer", stream.buyer],
547
+ ["status", describeStatus(report)],
548
+ ...(stream.opener === null || stream.opener === undefined
549
+ ? ([["buyer", stream.buyer]] as const)
550
+ : ([
551
+ ["buyer", `${stream.buyer} (x402 opener contract)`],
552
+ ["payer", `${stream.payer} (refunds go here)`],
553
+ ] as const)),
279
554
  ["operator", stream.operator],
280
555
  ["serviceRef", stream.serviceRef],
281
- ["deposit", formatUsdc(stream.deposit)],
556
+ // Reclaim OVERWRITES `deposit` with the operator's entitlement, so after a
557
+ // reclaim the field is what is still escrowed for the operator — not what
558
+ // the buyer put in. Printing it bare would read as "the buyer deposited 0".
559
+ [
560
+ "deposit",
561
+ stream.reclaimed
562
+ ? `${formatUsdc(stream.deposit)} still escrowed for the operator (reclaim replaces the original deposit with the operator's entitlement)`
563
+ : formatUsdc(stream.deposit),
564
+ ],
282
565
  ["rate", `${formatUsdc(stream.ratePerSecond)} / second`],
283
566
  ["opened", formatTimestamp(stream.openedAt)],
284
567
  ["expires", describeDeadline(stream.expiresAt, report.nowSeconds)],
@@ -286,12 +569,12 @@ export function streamReportRows(
286
569
  if (stream.closedAt > 0) {
287
570
  rows.push(["closed", formatTimestamp(stream.closedAt)]);
288
571
  }
289
- rows.push([
290
- "verified accrual",
291
- proof === null
292
- ? `not checkpointed yet (${report.proofUnavailable ?? "no proof"})`
293
- : `${formatUsdc(proof.cumulativeAmount)} @ checkpoint ${proof.checkpointId} (${formatTimestamp(Number(proof.checkedAt))})`,
294
- ]);
572
+ if (report.finalSettlement !== null) {
573
+ rows.push(...finalSettlementRows(report, report.finalSettlement));
574
+ pushTrailingRows(rows, report);
575
+ return rows;
576
+ }
577
+ rows.push(["verified accrual", describeVerifiedAccrual(report)]);
295
578
  // `claim` accepts ANY retained root, so the operator's balance is obtainable
296
579
  // now — it is merely not FINAL until the covering checkpoint lands, and it
297
580
  // can only grow. `reclaim` requires the LATEST root to reach the stop time,
@@ -301,9 +584,11 @@ export function streamReportRows(
301
584
  "operator claimable",
302
585
  report.claimable === null
303
586
  ? "-"
304
- : report.settlement.pending
305
- ? `${formatUsdc(report.claimable)} so far — not final until the checkpoint covering the stop time lands`
306
- : formatUsdc(report.claimable),
587
+ : report.claimableSource === "proven-on-chain"
588
+ ? `${formatUsdc(report.claimable)} (net of the protocol fee) — from the on-chain proven high-water; \`metrik claim\` redeems it with claimProven, no fresh proof needed`
589
+ : report.settlement.pending
590
+ ? `${formatUsdc(report.claimable)} so far (net of the protocol fee) — not final until the checkpoint covering the stop time lands`
591
+ : `${formatUsdc(report.claimable)} (net of the protocol fee)`,
307
592
  ]);
308
593
  rows.push([
309
594
  "buyer reclaimable",
@@ -313,48 +598,173 @@ export function streamReportRows(
313
598
  ? "-"
314
599
  : formatUsdc(report.reclaimable),
315
600
  ]);
601
+ if (report.escrowGeneration === "v3") {
602
+ rows.push([
603
+ "proven on-chain",
604
+ report.provenCumulative === null
605
+ ? `unknown (${report.provenUnavailable ?? "not read"})`
606
+ : `${formatUsdc(report.provenCumulative)} gross (the escrow's proven high-water; the escape hatch cannot take it)`,
607
+ ]);
608
+ }
316
609
  rows.push(["fees paid", formatUsdc(stream.feesPaid)]);
317
- rows.push([
318
- "oracle checkpoint",
319
- report.latestCheckpointCheckedAt === null
320
- ? "-"
321
- : report.latestCheckpointCheckedAt <= 0
322
- ? "none posted yet"
323
- : `latest covers up to ${formatTimestamp(report.latestCheckpointCheckedAt)}`,
324
- ]);
325
- // The no-proof path. Printed for every stream, not only blocked ones: a buyer
326
- // should learn that this exists before they need it.
610
+ rows.push(oracleCheckpointRow(report));
611
+ // The no-proof path. Printed for every live stream, not only blocked ones: a
612
+ // buyer should learn that this exists before they need it.
327
613
  rows.push([
328
614
  "escape hatch",
329
615
  report.escapeHatch === null
330
616
  ? "-"
331
- : `${describeEscapeHatch(report.escapeHatch)} — no-proof reclaim, forfeits the operator's unclaimed earnings`,
617
+ : `${describeEscapeHatch(report.escapeHatch)} — ${
618
+ report.escrowGeneration === "v3"
619
+ ? "no-proof reclaim, refunds only value not proven on-chain; the operator keeps max(claimed, proven high-water)"
620
+ : "no-proof reclaim, forfeits the operator's unclaimed earnings"
621
+ }`,
332
622
  ]);
333
- // The same row, read from the seller's side. `reclaimUnverified` pays the
334
- // buyer the WHOLE remaining deposit with no proof, so an operator who has
335
- // earned but not claimed loses it the moment that window opens. The window is
336
- // `expiresAt + escapeGraceSeconds` and the grace is immutable on the live
337
- // escrow, so the only thing an operator can do about it is claim in time —
338
- // which they cannot do if nothing tells them the deadline exists.
339
- if (
340
- report.escapeHatch !== null &&
341
- report.claimable !== null &&
342
- report.claimable > 0n
343
- ) {
344
- rows.push([
623
+ const operatorRow = operatorExposureRow(report);
624
+ if (operatorRow !== null) rows.push(operatorRow);
625
+ pushTrailingRows(rows, report);
626
+ return rows;
627
+ }
628
+
629
+ /**
630
+ * The same escape-hatch row, read from the seller's side.
631
+ *
632
+ * V2: `reclaimUnverified` settles the operator at `claimedCumulative`, so an
633
+ * operator who has earned but not claimed loses all of it the moment the
634
+ * window opens. The window is fixed and the grace immutable, so the only thing
635
+ * an operator can do about it is claim in time — which they cannot do if
636
+ * nothing tells them the deadline exists.
637
+ *
638
+ * V3: the hatch settles the operator at `max(claimed, provenCumulative)`, so
639
+ * only verified value nobody proved on-chain is exposed. Warning about the
640
+ * WHOLE unclaimed balance there would be false — and would push an operator to
641
+ * spend gas racing a deadline that cannot hurt them.
642
+ */
643
+ function operatorExposureRow(
644
+ report: StreamReport,
645
+ ): readonly [string, string] | null {
646
+ const hatch = report.escapeHatch;
647
+ if (hatch === null || report.claimable === null || report.claimable <= 0n) {
648
+ return null;
649
+ }
650
+ if (report.escrowGeneration !== "v3") {
651
+ return [
345
652
  "operator deadline",
346
- report.escapeHatch.ready
653
+ hatch.ready
347
654
  ? `${formatUsdc(report.claimable)} is unclaimed and the buyer's no-proof reclaim is ALREADY open — it would take this amount from the operator`
348
- : `${formatUsdc(report.claimable)} is unclaimed; \`metrik claim\` it before ${formatTimestamp(report.escapeHatch.readyAt)}, after which a buyer's no-proof reclaim can take it`,
655
+ : `${formatUsdc(report.claimable)} is unclaimed; \`metrik claim\` it before ${formatTimestamp(hatch.readyAt)}, after which a buyer's no-proof reclaim can take it`,
656
+ ];
657
+ }
658
+ const exposure = report.operatorExposure;
659
+ if (exposure === null) return null;
660
+ const units = `(gross: before the protocol fee; \`metrik claim\` pays the operator net of it — ${formatUsdc(report.claimable)} net unclaimed)`;
661
+ if (exposure.atRiskCumulative === 0n) {
662
+ return [
663
+ "operator earnings",
664
+ `${formatUsdc(exposure.verifiedCumulative)} gross verified, all protected on-chain by the proven high-water — a buyer's no-proof reclaim cannot take it, so it can be claimed at any time with \`metrik claim\` ${units}`,
665
+ ];
666
+ }
667
+ const protectedPart = exposure.provenCumulativeKnown
668
+ ? `${formatUsdc(exposure.protectedCumulative)} gross is protected on-chain (claimed or proven high-water)`
669
+ : "the on-chain proven high-water could not be read, so this assumes none of the unclaimed balance is protected";
670
+ return [
671
+ "operator deadline",
672
+ hatch.ready
673
+ ? `${formatUsdc(exposure.atRiskCumulative)} gross of verified accrual is not proven on-chain and the buyer's no-proof reclaim is ALREADY open — it would refund that part to the buyer. ${protectedPart}. ${units}`
674
+ : `${formatUsdc(exposure.atRiskCumulative)} gross of verified accrual is not proven on-chain; \`metrik claim\` it before ${formatTimestamp(hatch.readyAt)}, after which a buyer's no-proof reclaim refunds that part to the buyer. ${protectedPart}. ${units}`,
675
+ ];
676
+ }
677
+
678
+ /**
679
+ * A reclaimed stream, from the state reclaim WROTE. The buyer side is over:
680
+ * no checkpoint to wait for, nothing reclaimable, and no escape hatch left to
681
+ * open (`reclaimUnverified` reverts `AlreadyReclaimed`). So none of those are
682
+ * printed as live items — only what the operator is still owed.
683
+ */
684
+ function finalSettlementRows(
685
+ report: StreamReport,
686
+ settlement: FinalSettlement,
687
+ ): (readonly [string, string])[] {
688
+ const refundTo = report.stream.payer ?? report.stream.buyer;
689
+ const exhausted = settlement.finishedBy === "deposit-exhausted";
690
+ const rows: (readonly [string, string])[] = [
691
+ [
692
+ "buyer side",
693
+ exhausted
694
+ ? `finished — the operator's claimed${report.escrowGeneration === "v3" ? " or proven" : ""} amount already covers the whole deposit, so there is nothing to reclaim (the escrow would revert NothingToReclaim) and the buyer never reclaimed. Nothing more is owed to, or reclaimable by, the buyer.`
695
+ : `finished — the buyer has reclaimed, and the refund was paid to ${refundTo}. Nothing more is owed to, or reclaimable by, the buyer.`,
696
+ ],
697
+ [
698
+ "settled to operator",
699
+ `${formatUsdc(settlement.settledToOperator)} gross (${exhausted ? "the whole deposit" : "fixed at reclaim"})`,
700
+ ],
701
+ ["operator claimed", `${formatUsdc(settlement.operatorClaimed)} gross`],
702
+ [
703
+ "operator unclaimed",
704
+ settlement.operatorUnclaimed === 0n
705
+ ? "none — fully settled"
706
+ : `${formatUsdc(settlement.operatorUnclaimed)} gross, reserved for the operator — the buyer side is finished, so nothing can take it; \`metrik claim\` it at any time (paid net of the protocol fee)`,
707
+ ],
708
+ ];
709
+ if (exhausted && report.provenCumulative !== null) {
710
+ rows.push([
711
+ "proven on-chain",
712
+ `${formatUsdc(report.provenCumulative)} gross (the escrow's proven high-water)`,
349
713
  ]);
350
714
  }
715
+ rows.push(["fees paid", formatUsdc(report.stream.feesPaid)]);
716
+ return rows;
717
+ }
718
+
719
+ function describeStatus(report: StreamReport): string {
720
+ const { stream } = report;
721
+ const final = report.finalSettlement;
722
+ if (final === null) return stream.status;
723
+ if (final.finishedBy === "reclaimed") return `${stream.status} (reclaimed)`;
724
+ const stop = stream.closedAt > 0 ? "closed" : "expired";
725
+ return final.operatorUnclaimed === 0n
726
+ ? `${stream.status} (${stop}, fully settled)`
727
+ : `${stream.status} (${stop}, buyer side finished)`;
728
+ }
729
+
730
+ /**
731
+ * The verified-accrual row. With no current proof on V3, a non-zero proven
732
+ * high-water IS verified accrual — proven against a signed checkpoint root and
733
+ * recorded on-chain — so it is stated rather than "not checkpointed yet", which
734
+ * reads as "nothing verified".
735
+ */
736
+ function describeVerifiedAccrual(report: StreamReport): string {
737
+ const { proof } = report;
738
+ if (proof !== null) {
739
+ return `${formatUsdc(proof.cumulativeAmount)} gross @ checkpoint ${proof.checkpointId} (${formatTimestamp(Number(proof.checkedAt))})`;
740
+ }
741
+ if (report.provenCumulative !== null && report.provenCumulative > 0n) {
742
+ return `${formatUsdc(report.provenCumulative)} gross proven on-chain (provenCumulativeOf) — the latest checkpoint does not contain this stream (${report.proofUnavailable ?? "no proof"}), so this is read from the escrow`;
743
+ }
744
+ return `not checkpointed yet (${report.proofUnavailable ?? "no proof"})`;
745
+ }
746
+
747
+ function oracleCheckpointRow(report: StreamReport): readonly [string, string] {
748
+ return [
749
+ "oracle checkpoint",
750
+ report.latestCheckpointCheckedAt === null
751
+ ? "-"
752
+ : report.latestCheckpointCheckedAt <= 0
753
+ ? "none posted yet"
754
+ : `latest covers up to ${formatTimestamp(report.latestCheckpointCheckedAt)}`,
755
+ ];
756
+ }
757
+
758
+ function pushTrailingRows(
759
+ rows: (readonly [string, string])[],
760
+ report: StreamReport,
761
+ ): void {
351
762
  if (report.protocolFeeBps !== null) {
352
763
  rows.push([
353
764
  "protocol fee",
354
765
  `${report.protocolFeeBps} bps (read from the escrow)`,
355
766
  ]);
356
767
  }
357
- return rows;
358
768
  }
359
769
 
360
770
  /**