@absol-labs/agent 0.9.1 → 0.11.0

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 (293) hide show
  1. package/README.md +184 -26
  2. package/dist/capability/invocation-capability.d.ts +18 -6
  3. package/dist/capability/invocation-capability.d.ts.map +1 -1
  4. package/dist/capability/invocation-capability.js.map +1 -1
  5. package/dist/cli/amounts.d.ts +26 -0
  6. package/dist/cli/amounts.d.ts.map +1 -0
  7. package/dist/cli/amounts.js +71 -0
  8. package/dist/cli/amounts.js.map +1 -0
  9. package/dist/cli/args.d.ts +32 -0
  10. package/dist/cli/args.d.ts.map +1 -0
  11. package/dist/cli/args.js +119 -0
  12. package/dist/cli/args.js.map +1 -0
  13. package/dist/cli/attesters.d.ts +51 -0
  14. package/dist/cli/attesters.d.ts.map +1 -0
  15. package/dist/cli/attesters.js +58 -0
  16. package/dist/cli/attesters.js.map +1 -0
  17. package/dist/cli/bin.d.ts +3 -0
  18. package/dist/cli/bin.d.ts.map +1 -0
  19. package/dist/cli/bin.js +5 -0
  20. package/dist/cli/bin.js.map +1 -0
  21. package/dist/cli/commands/claim.d.ts +13 -0
  22. package/dist/cli/commands/claim.d.ts.map +1 -0
  23. package/dist/cli/commands/claim.js +102 -0
  24. package/dist/cli/commands/claim.js.map +1 -0
  25. package/dist/cli/commands/close.d.ts +3 -0
  26. package/dist/cli/commands/close.d.ts.map +1 -0
  27. package/dist/cli/commands/close.js +55 -0
  28. package/dist/cli/commands/close.js.map +1 -0
  29. package/dist/cli/commands/discover.d.ts +3 -0
  30. package/dist/cli/commands/discover.d.ts.map +1 -0
  31. package/dist/cli/commands/discover.js +183 -0
  32. package/dist/cli/commands/discover.js.map +1 -0
  33. package/dist/cli/commands/invoke.d.ts +3 -0
  34. package/dist/cli/commands/invoke.d.ts.map +1 -0
  35. package/dist/cli/commands/invoke.js +142 -0
  36. package/dist/cli/commands/invoke.js.map +1 -0
  37. package/dist/cli/commands/mcp.d.ts +3 -0
  38. package/dist/cli/commands/mcp.d.ts.map +1 -0
  39. package/dist/cli/commands/mcp.js +113 -0
  40. package/dist/cli/commands/mcp.js.map +1 -0
  41. package/dist/cli/commands/open.d.ts +3 -0
  42. package/dist/cli/commands/open.d.ts.map +1 -0
  43. package/dist/cli/commands/open.js +331 -0
  44. package/dist/cli/commands/open.js.map +1 -0
  45. package/dist/cli/commands/reclaim-unverified.d.ts +3 -0
  46. package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -0
  47. package/dist/cli/commands/reclaim-unverified.js +172 -0
  48. package/dist/cli/commands/reclaim-unverified.js.map +1 -0
  49. package/dist/cli/commands/reclaim.d.ts +33 -0
  50. package/dist/cli/commands/reclaim.d.ts.map +1 -0
  51. package/dist/cli/commands/reclaim.js +308 -0
  52. package/dist/cli/commands/reclaim.js.map +1 -0
  53. package/dist/cli/commands/status.d.ts +3 -0
  54. package/dist/cli/commands/status.d.ts.map +1 -0
  55. package/dist/cli/commands/status.js +43 -0
  56. package/dist/cli/commands/status.js.map +1 -0
  57. package/dist/cli/commands/streams.d.ts +3 -0
  58. package/dist/cli/commands/streams.d.ts.map +1 -0
  59. package/dist/cli/commands/streams.js +257 -0
  60. package/dist/cli/commands/streams.js.map +1 -0
  61. package/dist/cli/commands/watch.d.ts +3 -0
  62. package/dist/cli/commands/watch.d.ts.map +1 -0
  63. package/dist/cli/commands/watch.js +55 -0
  64. package/dist/cli/commands/watch.js.map +1 -0
  65. package/dist/cli/config.d.ts +61 -0
  66. package/dist/cli/config.d.ts.map +1 -0
  67. package/dist/cli/config.js +140 -0
  68. package/dist/cli/config.js.map +1 -0
  69. package/dist/cli/context.d.ts +29 -0
  70. package/dist/cli/context.d.ts.map +1 -0
  71. package/dist/cli/context.js +2 -0
  72. package/dist/cli/context.js.map +1 -0
  73. package/dist/cli/deps.d.ts +191 -0
  74. package/dist/cli/deps.d.ts.map +1 -0
  75. package/dist/cli/deps.js +277 -0
  76. package/dist/cli/deps.js.map +1 -0
  77. package/dist/cli/descriptor.d.ts +61 -0
  78. package/dist/cli/descriptor.d.ts.map +1 -0
  79. package/dist/cli/descriptor.js +78 -0
  80. package/dist/cli/descriptor.js.map +1 -0
  81. package/dist/cli/errors.d.ts +48 -0
  82. package/dist/cli/errors.d.ts.map +1 -0
  83. package/dist/cli/errors.js +129 -0
  84. package/dist/cli/errors.js.map +1 -0
  85. package/dist/cli/escape-hatch.d.ts +50 -0
  86. package/dist/cli/escape-hatch.d.ts.map +1 -0
  87. package/dist/cli/escape-hatch.js +45 -0
  88. package/dist/cli/escape-hatch.js.map +1 -0
  89. package/dist/cli/index.d.ts +8 -0
  90. package/dist/cli/index.d.ts.map +1 -0
  91. package/dist/cli/index.js +7 -0
  92. package/dist/cli/index.js.map +1 -0
  93. package/dist/cli/listings.d.ts +15 -0
  94. package/dist/cli/listings.d.ts.map +1 -0
  95. package/dist/cli/listings.js +56 -0
  96. package/dist/cli/listings.js.map +1 -0
  97. package/dist/cli/mandate.d.ts +66 -0
  98. package/dist/cli/mandate.d.ts.map +1 -0
  99. package/dist/cli/mandate.js +61 -0
  100. package/dist/cli/mandate.js.map +1 -0
  101. package/dist/cli/output.d.ts +33 -0
  102. package/dist/cli/output.d.ts.map +1 -0
  103. package/dist/cli/output.js +34 -0
  104. package/dist/cli/output.js.map +1 -0
  105. package/dist/cli/preflight.d.ts +38 -0
  106. package/dist/cli/preflight.d.ts.map +1 -0
  107. package/dist/cli/preflight.js +72 -0
  108. package/dist/cli/preflight.js.map +1 -0
  109. package/dist/cli/privy.d.ts +83 -0
  110. package/dist/cli/privy.d.ts.map +1 -0
  111. package/dist/cli/privy.js +230 -0
  112. package/dist/cli/privy.js.map +1 -0
  113. package/dist/cli/progress.d.ts +36 -0
  114. package/dist/cli/progress.d.ts.map +1 -0
  115. package/dist/cli/progress.js +40 -0
  116. package/dist/cli/progress.js.map +1 -0
  117. package/dist/cli/run.d.ts +25 -0
  118. package/dist/cli/run.d.ts.map +1 -0
  119. package/dist/cli/run.js +173 -0
  120. package/dist/cli/run.js.map +1 -0
  121. package/dist/cli/scan-range.d.ts +55 -0
  122. package/dist/cli/scan-range.d.ts.map +1 -0
  123. package/dist/cli/scan-range.js +57 -0
  124. package/dist/cli/scan-range.js.map +1 -0
  125. package/dist/cli/spend-policy.d.ts +60 -0
  126. package/dist/cli/spend-policy.d.ts.map +1 -0
  127. package/dist/cli/spend-policy.js +65 -0
  128. package/dist/cli/spend-policy.js.map +1 -0
  129. package/dist/cli/status.d.ts +90 -0
  130. package/dist/cli/status.d.ts.map +1 -0
  131. package/dist/cli/status.js +252 -0
  132. package/dist/cli/status.js.map +1 -0
  133. package/dist/cli/timeout.d.ts +30 -0
  134. package/dist/cli/timeout.d.ts.map +1 -0
  135. package/dist/cli/timeout.js +55 -0
  136. package/dist/cli/timeout.js.map +1 -0
  137. package/dist/cli/version.d.ts +12 -0
  138. package/dist/cli/version.d.ts.map +1 -0
  139. package/dist/cli/version.js +22 -0
  140. package/dist/cli/version.js.map +1 -0
  141. package/dist/cli/wallet.d.ts +164 -0
  142. package/dist/cli/wallet.d.ts.map +1 -0
  143. package/dist/cli/wallet.js +234 -0
  144. package/dist/cli/wallet.js.map +1 -0
  145. package/dist/frameworks/crewai.d.ts +10 -0
  146. package/dist/frameworks/crewai.d.ts.map +1 -1
  147. package/dist/frameworks/crewai.js +12 -2
  148. package/dist/frameworks/crewai.js.map +1 -1
  149. package/dist/gateway/caller-auth-gateway.d.ts +38 -3
  150. package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
  151. package/dist/gateway/caller-auth-gateway.js +25 -1
  152. package/dist/gateway/caller-auth-gateway.js.map +1 -1
  153. package/dist/gateway/http-server.d.ts +53 -0
  154. package/dist/gateway/http-server.d.ts.map +1 -1
  155. package/dist/gateway/http-server.js +189 -4
  156. package/dist/gateway/http-server.js.map +1 -1
  157. package/dist/gateway/server-entry.d.ts +1 -0
  158. package/dist/gateway/server-entry.js +1 -0
  159. package/dist/gateway/server-entry.js.map +1 -1
  160. package/dist/gateway/x402-mode.d.ts +123 -0
  161. package/dist/gateway/x402-mode.d.ts.map +1 -0
  162. package/dist/gateway/x402-mode.js +212 -0
  163. package/dist/gateway/x402-mode.js.map +1 -0
  164. package/dist/index.d.ts +6 -4
  165. package/dist/index.d.ts.map +1 -1
  166. package/dist/index.js +8 -3
  167. package/dist/index.js.map +1 -1
  168. package/dist/mandates/env.d.ts +3 -2
  169. package/dist/mandates/env.d.ts.map +1 -1
  170. package/dist/mandates/env.js +3 -2
  171. package/dist/mandates/env.js.map +1 -1
  172. package/dist/mcp/http-server.d.ts +1 -0
  173. package/dist/mcp/http-server.js +1 -0
  174. package/dist/mcp/http-server.js.map +1 -1
  175. package/dist/mcp/server.d.ts +49 -0
  176. package/dist/mcp/server.d.ts.map +1 -1
  177. package/dist/mcp/server.js +533 -25
  178. package/dist/mcp/server.js.map +1 -1
  179. package/dist/mcp/service-invoker.d.ts +97 -0
  180. package/dist/mcp/service-invoker.d.ts.map +1 -0
  181. package/dist/mcp/service-invoker.js +167 -0
  182. package/dist/mcp/service-invoker.js.map +1 -0
  183. package/dist/mcp/stdio.d.ts +1 -0
  184. package/dist/mcp/stdio.js +1 -0
  185. package/dist/mcp/stdio.js.map +1 -1
  186. package/dist/sdk/client.d.ts +25 -1
  187. package/dist/sdk/client.d.ts.map +1 -1
  188. package/dist/sdk/client.js +20 -1
  189. package/dist/sdk/client.js.map +1 -1
  190. package/dist/sdk/invoke.d.ts +19 -4
  191. package/dist/sdk/invoke.d.ts.map +1 -1
  192. package/dist/sdk/invoke.js +69 -5
  193. package/dist/sdk/invoke.js.map +1 -1
  194. package/dist/x402/delivery-oracle.d.ts +108 -0
  195. package/dist/x402/delivery-oracle.d.ts.map +1 -0
  196. package/dist/x402/delivery-oracle.js +174 -0
  197. package/dist/x402/delivery-oracle.js.map +1 -0
  198. package/dist/x402/facilitator-server-entry.d.ts +3 -0
  199. package/dist/x402/facilitator-server-entry.d.ts.map +1 -0
  200. package/dist/x402/facilitator-server-entry.js +27 -0
  201. package/dist/x402/facilitator-server-entry.js.map +1 -0
  202. package/dist/x402/facilitator-server.d.ts +73 -0
  203. package/dist/x402/facilitator-server.d.ts.map +1 -0
  204. package/dist/x402/facilitator-server.js +305 -0
  205. package/dist/x402/facilitator-server.js.map +1 -0
  206. package/dist/x402/facilitator.d.ts +69 -17
  207. package/dist/x402/facilitator.d.ts.map +1 -1
  208. package/dist/x402/facilitator.js +76 -33
  209. package/dist/x402/facilitator.js.map +1 -1
  210. package/dist/x402/payment-requirements.d.ts +106 -0
  211. package/dist/x402/payment-requirements.d.ts.map +1 -0
  212. package/dist/x402/payment-requirements.js +138 -0
  213. package/dist/x402/payment-requirements.js.map +1 -0
  214. package/dist/x402/relayer.d.ts +41 -0
  215. package/dist/x402/relayer.d.ts.map +1 -0
  216. package/dist/x402/relayer.js +64 -0
  217. package/dist/x402/relayer.js.map +1 -0
  218. package/dist/x402/resource-index.d.ts +75 -0
  219. package/dist/x402/resource-index.d.ts.map +1 -0
  220. package/dist/x402/resource-index.js +126 -0
  221. package/dist/x402/resource-index.js.map +1 -0
  222. package/dist/x402/verified-delivery-facilitator.d.ts +285 -0
  223. package/dist/x402/verified-delivery-facilitator.d.ts.map +1 -0
  224. package/dist/x402/verified-delivery-facilitator.js +423 -0
  225. package/dist/x402/verified-delivery-facilitator.js.map +1 -0
  226. package/dist/zktls/t2-delivery-proof.d.ts +6 -6
  227. package/docs/agent-layer.md +14 -9
  228. package/docs/cli.md +705 -0
  229. package/docs/crewai.md +6 -2
  230. package/docs/eliza.md +6 -1
  231. package/docs/mcp-hosted.md +7 -5
  232. package/docs/mcp-reference.md +175 -0
  233. package/docs/quickstart.md +36 -9
  234. package/docs/threat-model.md +4 -3
  235. package/docs/x402.md +648 -0
  236. package/package.json +33 -10
  237. package/src/capability/invocation-capability.ts +17 -1
  238. package/src/cli/amounts.ts +102 -0
  239. package/src/cli/args.ts +150 -0
  240. package/src/cli/attesters.ts +86 -0
  241. package/src/cli/bin.ts +5 -0
  242. package/src/cli/commands/claim.ts +126 -0
  243. package/src/cli/commands/close.ts +69 -0
  244. package/src/cli/commands/discover.ts +228 -0
  245. package/src/cli/commands/invoke.ts +178 -0
  246. package/src/cli/commands/mcp.ts +135 -0
  247. package/src/cli/commands/open.ts +397 -0
  248. package/src/cli/commands/reclaim-unverified.ts +218 -0
  249. package/src/cli/commands/reclaim.ts +377 -0
  250. package/src/cli/commands/status.ts +56 -0
  251. package/src/cli/commands/streams.ts +344 -0
  252. package/src/cli/commands/watch.ts +70 -0
  253. package/src/cli/config.ts +208 -0
  254. package/src/cli/context.ts +30 -0
  255. package/src/cli/deps.ts +528 -0
  256. package/src/cli/descriptor.ts +133 -0
  257. package/src/cli/errors.ts +162 -0
  258. package/src/cli/escape-hatch.ts +92 -0
  259. package/src/cli/index.ts +27 -0
  260. package/src/cli/listings.ts +77 -0
  261. package/src/cli/mandate.ts +126 -0
  262. package/src/cli/output.ts +63 -0
  263. package/src/cli/preflight.ts +88 -0
  264. package/src/cli/privy.ts +396 -0
  265. package/src/cli/progress.ts +75 -0
  266. package/src/cli/run.ts +209 -0
  267. package/src/cli/scan-range.ts +76 -0
  268. package/src/cli/spend-policy.ts +139 -0
  269. package/src/cli/status.ts +381 -0
  270. package/src/cli/timeout.ts +64 -0
  271. package/src/cli/version.ts +21 -0
  272. package/src/cli/wallet.ts +372 -0
  273. package/src/frameworks/crewai.ts +13 -2
  274. package/src/gateway/caller-auth-gateway.ts +77 -6
  275. package/src/gateway/http-server.ts +280 -6
  276. package/src/gateway/server-entry.ts +1 -0
  277. package/src/gateway/x402-mode.ts +350 -0
  278. package/src/index.ts +40 -5
  279. package/src/mandates/env.ts +3 -2
  280. package/src/mcp/http-server.ts +1 -0
  281. package/src/mcp/server.ts +706 -30
  282. package/src/mcp/service-invoker.ts +285 -0
  283. package/src/mcp/stdio.ts +1 -0
  284. package/src/sdk/client.ts +33 -1
  285. package/src/sdk/invoke.ts +90 -12
  286. package/src/x402/delivery-oracle.ts +285 -0
  287. package/src/x402/facilitator-server-entry.ts +35 -0
  288. package/src/x402/facilitator-server.ts +445 -0
  289. package/src/x402/facilitator.ts +125 -46
  290. package/src/x402/payment-requirements.ts +231 -0
  291. package/src/x402/relayer.ts +122 -0
  292. package/src/x402/resource-index.ts +228 -0
  293. package/src/x402/verified-delivery-facilitator.ts +744 -0
@@ -0,0 +1,308 @@
1
+ import { SettlementWindowOpenError } from "@absol-labs/sdk";
2
+ import { booleanFlag, integerFlag } from "../args.js";
3
+ import { formatUsdc } from "../amounts.js";
4
+ import { assertTestnetRpc, parseBytes32 } from "../config.js";
5
+ import { CliError, describeError, usageError } from "../errors.js";
6
+ import { describeEscapeHatch, escapeHatchWindow, readChainNow, } from "../escape-hatch.js";
7
+ import { signRecoveryMandate } from "../mandate.js";
8
+ import { assertCanPayGas } from "../preflight.js";
9
+ import { writeClientOptions } from "../deps.js";
10
+ import { isPolicyEnforced, resolveCliWallet } from "../wallet.js";
11
+ /**
12
+ * How long to keep retrying a reclaim that is blocked only by an open
13
+ * settlement window. Defaults chosen to comfortably cover the oracle's ~15-22s
14
+ * checkpoint cadence: 12 x 10s = two minutes.
15
+ */
16
+ const DEFAULT_ATTEMPTS = 12;
17
+ const DEFAULT_INTERVAL_SECONDS = 10;
18
+ export const reclaimCommand = {
19
+ name: "reclaim",
20
+ summary: "Take back the budget that was never earned",
21
+ usage: "metrik reclaim <streamId> [--close-first] [--attempts 12] [--interval 10]",
22
+ flags: ["close-first", "attempts", "interval"],
23
+ signs: true,
24
+ async run(context) {
25
+ const { args, config, env, out, deps } = context;
26
+ const [streamIdArg] = args.positionals;
27
+ if (streamIdArg === undefined) {
28
+ throw usageError("metrik reclaim requires a <streamId>");
29
+ }
30
+ const streamId = parseBytes32(streamIdArg, "<streamId>");
31
+ const closeFirst = booleanFlag(args, "close-first");
32
+ const attempts = integerFlag(args, "attempts") ?? DEFAULT_ATTEMPTS;
33
+ const interval = integerFlag(args, "interval") ?? DEFAULT_INTERVAL_SECONDS;
34
+ if (attempts < 1)
35
+ throw usageError("--attempts must be at least 1");
36
+ if (interval < 1)
37
+ throw usageError("--interval must be at least 1 second");
38
+ const wallet = await resolveCliWallet(env, {
39
+ note: out.note,
40
+ rpcUrl: config.rpcUrl,
41
+ });
42
+ const { account } = wallet;
43
+ const read = deps.createReadClient(config);
44
+ await assertTestnetRpc(config, () => read.getChainId());
45
+ await assertCanPayGas(read, account.address);
46
+ const write = deps.createWriteClient(config, account, writeClientOptions(wallet));
47
+ // A policy-enforced wallet (Privy) signs no mandate of its own: these are
48
+ // buyer-recovery actions, gated by the escrow on the buyer address and by
49
+ // the broker on `stream.buyer == wallet`. See `../spend-policy.ts`.
50
+ const recoveryMandate = async () => isPolicyEnforced(wallet)
51
+ ? null
52
+ : await signRecoveryMandate(account, config.chainId);
53
+ // `--close-first` is TWO transactions, and the second one only simulates
54
+ // successfully against post-close state. Sequencing them inside a single SDK
55
+ // call sent the reclaim simulation at a node that had not yet applied the
56
+ // close, and the command died with `StreamNotReclaimable()` having already
57
+ // spent the buyer's close — leaving the stream closed-but-unreclaimed, and
58
+ // the obvious retry dead-ending on `StreamClosedAlready()`. So the CLI owns
59
+ // the ordering: skip the close when the stream is already closed, and wait
60
+ // for the close to be VISIBLE to the node we are about to simulate against.
61
+ let closeResult = null;
62
+ let alreadyClosed = false;
63
+ if (closeFirst) {
64
+ const before = await read.getStreamV2(streamId).catch(() => null);
65
+ if (before !== null && before.status === "closed") {
66
+ alreadyClosed = true;
67
+ out.note("stream is already closed — skipping the close and going straight to reclaim.");
68
+ }
69
+ else {
70
+ closeResult = await closeBeforeReclaim(write, {
71
+ streamId,
72
+ signedMandate: await recoveryMandate(),
73
+ spentSoFarUsdc: 0n,
74
+ nowSeconds: deps.nowSeconds(),
75
+ });
76
+ if (closeResult === null) {
77
+ alreadyClosed = true;
78
+ out.note("the stream was already closed by the time the close was sent — going straight to reclaim.");
79
+ }
80
+ else {
81
+ out.note(`closed in ${closeResult.txHash} — waiting for it to land …`);
82
+ await awaitClosed(read, streamId, deps.sleep, closeResult.txHash);
83
+ }
84
+ }
85
+ }
86
+ const result = await reclaimWithBoundedRetry(write, async () => ({
87
+ streamId,
88
+ signedMandate: await recoveryMandate(),
89
+ spentSoFarUsdc: 0n,
90
+ nowSeconds: deps.nowSeconds(),
91
+ }), {
92
+ attempts,
93
+ intervalMs: interval * 1_000,
94
+ sleep: deps.sleep,
95
+ onRetry: (attempt, reason) => out.note(`reclaim blocked: no checkpoint covers the stream's stop time yet (attempt ${attempt}/${attempts}) — ${reason}`),
96
+ // "Retry later" is the wrong advice when the oracle is gone: every
97
+ // attempt this command makes needs a proof FROM that oracle. So the
98
+ // give-up message names the path that needs no proof, and says whether
99
+ // it is open yet.
100
+ exhaustedHint: async () => await describeLastResort(read, streamId, deps.nowSeconds()),
101
+ });
102
+ // Read the settled figure back only once the node we are reading from has
103
+ // actually applied the reclaim. `sepolia.base.org` is load-balanced, so an
104
+ // immediate read can land on a lagged node and report `settledCumulative: 0`
105
+ // for a stream that just settled a non-zero amount — printing "settled to
106
+ // operator 0 USDC" when it was not zero. Poll for `reclaimed` (which the
107
+ // transaction we just landed sets) so the number beside it is from the same
108
+ // post-reclaim state. Bounded, and falls back to the last value read.
109
+ const stream = await readSettledStream(read, streamId, deps.sleep);
110
+ out.result({
111
+ streamId,
112
+ txHash: result.reclaimResult.txHash,
113
+ closeTxHash: closeResult?.txHash ?? null,
114
+ // `--close-first` on a stream that was already closed is a no-op, not a
115
+ // failure. Machine consumers need to tell that apart from "no close was
116
+ // asked for".
117
+ closeSkippedAlreadyClosed: closeFirst ? alreadyClosed : null,
118
+ settledCumulative: stream?.settledCumulative ?? null,
119
+ });
120
+ out.print("Reclaimed.");
121
+ out.table([
122
+ ["streamId", streamId],
123
+ ["reclaim tx", result.reclaimResult.txHash],
124
+ ...(closeResult === null
125
+ ? alreadyClosed
126
+ ? [["close", "skipped — already closed"]]
127
+ : []
128
+ : [["close tx", closeResult.txHash]]),
129
+ ...(stream === null
130
+ ? []
131
+ : [
132
+ ["settled to operator", formatUsdc(stream.settledCumulative)],
133
+ ]),
134
+ ]);
135
+ },
136
+ };
137
+ /**
138
+ * Send the close that `--close-first` asks for, tolerating the one race that
139
+ * matters: a stale read said "active" but the stream was in fact already closed.
140
+ * That is the state the caller wanted, so it is reported as `null` (no close
141
+ * sent) rather than as a failure — re-running the command must never dead-end on
142
+ * `StreamClosedAlready()` while the buyer's money is still reclaimable.
143
+ *
144
+ * Every OTHER close failure is rethrown untouched.
145
+ */
146
+ export async function closeBeforeReclaim(write, input) {
147
+ try {
148
+ return await write.closeStream(input);
149
+ }
150
+ catch (error) {
151
+ if (isAlreadyClosed(error))
152
+ return null;
153
+ throw error;
154
+ }
155
+ }
156
+ /** True when `error`, or anything in its cause chain, is StreamClosedAlready. */
157
+ function isAlreadyClosed(error) {
158
+ for (let current = error, depth = 0; depth < 8; depth++) {
159
+ if (!(current instanceof Error))
160
+ return false;
161
+ if (current.message.includes("StreamClosedAlready"))
162
+ return true;
163
+ if (current.cause === undefined)
164
+ return false;
165
+ current = current.cause;
166
+ }
167
+ return false;
168
+ }
169
+ /**
170
+ * Block until the node we read from reports the stream as closed.
171
+ *
172
+ * The close transaction's own receipt is awaited inside the SDK, but
173
+ * `sepolia.base.org` is load-balanced: the receipt can come back from one node
174
+ * while the very next `simulateContract` lands on another that has not imported
175
+ * the block yet. That node simulates `reclaim` against PRE-close state and
176
+ * reverts `StreamNotReclaimable()`. Confirming the close through the same read
177
+ * path the reclaim will use is what closes that window.
178
+ */
179
+ const CLOSE_VISIBLE_ATTEMPTS = 20;
180
+ const CLOSE_VISIBLE_INTERVAL_MS = 500;
181
+ export async function awaitClosed(read, streamId, sleep, closeTxHash) {
182
+ for (let attempt = 1; attempt <= CLOSE_VISIBLE_ATTEMPTS; attempt += 1) {
183
+ const stream = await read.getStreamV2(streamId).catch(() => null);
184
+ if (stream !== null && stream.status === "closed")
185
+ return;
186
+ if (attempt < CLOSE_VISIBLE_ATTEMPTS)
187
+ await sleep(CLOSE_VISIBLE_INTERVAL_MS);
188
+ }
189
+ throw new CliError(`the close landed in ${closeTxHash} but the RPC endpoint still reports the stream as active`, {
190
+ exitCode: 4,
191
+ hint: `the close is not lost and neither is the money: re-run \`metrik reclaim ${streamId} --close-first\` once the endpoint has caught up — it will skip the close and reclaim.`,
192
+ });
193
+ }
194
+ /**
195
+ * `reclaimWithProof` reverts with `SettlementWindowOpen` until the oracle has
196
+ * published a checkpoint whose `checkedAt` is at or after the stream's stop
197
+ * time. That is not an error condition — it is the normal ordering of a
198
+ * checkpoint rail, and it happens on the FIRST attempt essentially every time.
199
+ * A CLI that surfaced the raw revert would teach every user that reclaim is
200
+ * broken, so the wait is built in and narrated. Any other failure is rethrown
201
+ * immediately: only an open settlement window is retried.
202
+ */
203
+ export async function reclaimWithBoundedRetry(write, buildInput, options) {
204
+ let lastError;
205
+ for (let attempt = 1; attempt <= options.attempts; attempt++) {
206
+ try {
207
+ return await write.reclaimStream(await buildInput());
208
+ }
209
+ catch (error) {
210
+ if (!isSettlementWindowOpen(error))
211
+ throw explainReclaimFailure(error);
212
+ lastError = error;
213
+ if (attempt === options.attempts)
214
+ break;
215
+ options.onRetry(attempt, describeError(error));
216
+ await options.sleep(options.intervalMs);
217
+ }
218
+ }
219
+ throw new CliError(`reclaim is still blocked after ${options.attempts} attempt${options.attempts === 1 ? "" : "s"}: the oracle has not published a checkpoint covering this stream's stop time`, {
220
+ exitCode: 4,
221
+ hint: (await options.exhaustedHint?.().catch(() => null)) ??
222
+ DEFAULT_EXHAUSTED_HINT,
223
+ cause: lastError,
224
+ });
225
+ }
226
+ const DEFAULT_EXHAUSTED_HINT = "the funds are not lost — retry later, or raise --attempts / --interval. If the oracle stays down, `metrik reclaim-unverified` takes them back with no proof once the escape hatch opens.";
227
+ /**
228
+ * What to do when the proof-based path has run out of attempts.
229
+ *
230
+ * Every reclaim this command can perform goes through `reclaimWithProof`, which
231
+ * needs a Merkle proof fetched from the oracle — so a buyer whose oracle is gone
232
+ * can retry this command forever and never get their money. The escrow's
233
+ * `reclaimUnverified` is the path that does not need the oracle, and the only
234
+ * question that matters is whether its window has opened yet. That is fully
235
+ * computable: `expiresAt + escapeGraceSeconds` against the chain's clock.
236
+ */
237
+ async function describeLastResort(read, streamId, fallbackNowSeconds) {
238
+ const [stream, grace] = await Promise.all([
239
+ read.getStreamV2(streamId),
240
+ read.escapeGraceSeconds(),
241
+ ]);
242
+ const now = await readChainNow(read, fallbackNowSeconds);
243
+ const window = escapeHatchWindow(stream, grace, now.seconds);
244
+ const escapeCommand = `metrik reclaim-unverified ${streamId} --forfeit-unclaimed`;
245
+ return window.ready
246
+ ? `the funds are not lost. The no-proof escape hatch is OPEN (${describeEscapeHatch(window)}): \`${escapeCommand}\` takes them back without the oracle — at the cost of forfeiting whatever the operator earned but has not yet claimed. Retrying this command only helps if the oracle comes back.`
247
+ : `the funds are not lost, but retrying only helps if the oracle comes back — this path needs a proof from it. The no-proof escape hatch ${describeEscapeHatch(window)}; from then on \`${escapeCommand}\` takes them back without the oracle, forfeiting whatever the operator earned but has not yet claimed.`;
248
+ }
249
+ /**
250
+ * `StreamNotReclaimable()` means exactly one thing: the stream is neither closed
251
+ * nor past its expiry. Say that, and say which command fixes it, instead of
252
+ * handing the reader a selector.
253
+ */
254
+ function explainReclaimFailure(error) {
255
+ if (!(error instanceof Error))
256
+ return error;
257
+ for (let current = error, depth = 0; depth < 8; depth++) {
258
+ if (!(current instanceof Error))
259
+ break;
260
+ if (current.message.includes("StreamNotReclaimable")) {
261
+ return new CliError("the stream is still running, so there is nothing to reclaim yet", {
262
+ exitCode: 4,
263
+ hint: "stop the clock first — `metrik close <streamId>`, or re-run this command with --close-first — or wait for the stream to expire.",
264
+ cause: error,
265
+ });
266
+ }
267
+ if (current.cause === undefined)
268
+ break;
269
+ current = current.cause;
270
+ }
271
+ return error;
272
+ }
273
+ /** True when `error`, or anything in its cause chain, is SettlementWindowOpen. */
274
+ function isSettlementWindowOpen(error) {
275
+ for (let current = error, depth = 0; depth < 8; depth++) {
276
+ if (current instanceof SettlementWindowOpenError)
277
+ return true;
278
+ if (!(current instanceof Error) || current.cause === undefined)
279
+ return false;
280
+ current = current.cause;
281
+ }
282
+ return false;
283
+ }
284
+ /**
285
+ * Bounded poll for the post-reclaim stream state. Returns as soon as the node
286
+ * reports `reclaimed`, or the last successful read once attempts are exhausted
287
+ * (never throws — the reclaim already succeeded and a display read must not
288
+ * turn that into a command failure).
289
+ */
290
+ const SETTLED_READ_ATTEMPTS = 5;
291
+ // Deliberately sub-second and distinct from the retry interval (whole seconds,
292
+ // `--interval`), so the two waits are tellable apart in tests and in logs.
293
+ const SETTLED_READ_INTERVAL_MS = 500;
294
+ async function readSettledStream(read, streamId, sleep) {
295
+ let last = null;
296
+ for (let attempt = 1; attempt <= SETTLED_READ_ATTEMPTS; attempt += 1) {
297
+ const stream = await read.getStreamV2(streamId).catch(() => null);
298
+ if (stream !== null) {
299
+ last = stream;
300
+ if (stream.reclaimed)
301
+ return stream;
302
+ }
303
+ if (attempt < SETTLED_READ_ATTEMPTS)
304
+ await sleep(SETTLED_READ_INTERVAL_MS);
305
+ }
306
+ return last;
307
+ }
308
+ //# sourceMappingURL=reclaim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reclaim.js","sourceRoot":"","sources":["../../../src/cli/commands/reclaim.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CommandSpec } from "../context.js";
2
+ export declare const statusCommand: CommandSpec;
3
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/status.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjE,eAAO,MAAM,aAAa,EAAE,WA6C3B,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { attesterDisclosure } from "../attesters.js";
2
+ import { assertTestnetRpc, parseBytes32 } from "../config.js";
3
+ import { usageError } from "../errors.js";
4
+ import { readStreamReport, streamReportJson, streamReportRows, } from "../status.js";
5
+ export const statusCommand = {
6
+ name: "status",
7
+ summary: "Show a stream's on-chain state and its oracle-verified accrual",
8
+ usage: "metrik status <streamId>",
9
+ flags: [],
10
+ signs: false,
11
+ async run(context) {
12
+ const { args, config, out, deps } = context;
13
+ const [streamIdArg] = args.positionals;
14
+ if (streamIdArg === undefined) {
15
+ throw usageError("metrik status requires a <streamId>");
16
+ }
17
+ const streamId = parseBytes32(streamIdArg, "<streamId>");
18
+ const read = deps.createReadClient(config);
19
+ // Reads get the same chain allowlist as the signing paths. A read pointed at
20
+ // the wrong chain does not lose money, but the V1 and V2 stream tuples
21
+ // diverge from index 5 onward, so a wrong-chain read does not revert — it
22
+ // silently decodes something else into fields with the right names.
23
+ await assertTestnetRpc(config, () => read.getChainId());
24
+ const report = await readStreamReport(read, streamId, deps.nowSeconds());
25
+ out.result(streamReportJson(report));
26
+ // Who attests, before the numbers. Printed on stderr so it survives `--json`
27
+ // and cannot be lost by a consumer that only parses stdout.
28
+ const disclosure = attesterDisclosure(report.attesters, report.stream.operator);
29
+ if (disclosure !== null)
30
+ out.note(disclosure);
31
+ out.print(`Stream ${streamId}`);
32
+ out.table(streamReportRows(report));
33
+ if (report.settlement.pending) {
34
+ out.print("");
35
+ out.print("This stream has stopped but its final checkpoint has not landed, so there is no settled reclaimable figure yet — reclaim reverts until the oracle publishes a checkpoint reaching the stop time. `metrik reclaim <streamId>` waits for exactly that.");
36
+ }
37
+ if (report.proof === null) {
38
+ out.print("");
39
+ out.print("No checkpoint covers this stream yet. Accrual is published by the oracle roughly every 15-22s; funds are earned only for intervals that verified.");
40
+ }
41
+ },
42
+ };
43
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/cli/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAGtB,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,gEAAgE;IACzE,KAAK,EAAE,0BAA0B;IACjC,KAAK,EAAE,EAAE;IACT,KAAK,EAAE,KAAK;IACZ,KAAK,CAAC,GAAG,CAAC,OAAuB;QAC/B,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;QAC5C,MAAM,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC;QACvC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,UAAU,CAAC,qCAAqC,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QAEzD,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC3C,6EAA6E;QAC7E,uEAAuE;QACvE,0EAA0E;QAC1E,oEAAoE;QACpE,MAAM,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAEzE,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;QACrC,6EAA6E;QAC7E,4DAA4D;QAC5D,MAAM,UAAU,GAAG,kBAAkB,CACnC,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,MAAM,CAAC,QAAQ,CACvB,CAAC;QACF,IAAI,UAAU,KAAK,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9C,GAAG,CAAC,KAAK,CAAC,UAAU,QAAQ,EAAE,CAAC,CAAC;QAChC,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YAC9B,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACd,GAAG,CAAC,KAAK,CACP,sPAAsP,CACvP,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YAC1B,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACd,GAAG,CAAC,KAAK,CACP,mJAAmJ,CACpJ,CAAC;QACJ,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CommandSpec } from "../context.js";
2
+ export declare const streamsCommand: CommandSpec;
3
+ //# sourceMappingURL=streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/streams.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,eAAe,CAAC;AA8BjE,eAAO,MAAM,cAAc,EAAE,WA6K5B,CAAC"}
@@ -0,0 +1,257 @@
1
+ import { BASE_SEPOLIA_ESCROW_V2 } from "@absol-labs/sdk";
2
+ import { getAddress, isAddress } from "viem";
3
+ import { integerFlag, stringFlag } from "../args.js";
4
+ import { formatUsdc } from "../amounts.js";
5
+ import { BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK, assertTestnetRpc, } from "../config.js";
6
+ import { CliError, configError, usageError } from "../errors.js";
7
+ import { escrowScanRange } from "../scan-range.js";
8
+ import { optionalCliWallet, PRIVATE_KEY_ENV } from "../wallet.js";
9
+ /** Indexed-party filters, and the `--role` values that select them. */
10
+ const ROLES = ["buyer", "operator"];
11
+ /**
12
+ * How many `eth_getLogs` calls are in flight at once. The full history of the
13
+ * live escrow is ~106 windows x 2 role filters; firing those unbounded is how a
14
+ * public RPC starts answering 429, and a rate-limited window is a window this
15
+ * command has to report as unscanned.
16
+ */
17
+ const SCAN_CONCURRENCY = 6;
18
+ /**
19
+ * Attempts per window before it is reported as unscanned. A public RPC answers
20
+ * a burst of `eth_getLogs` with an occasional 429 — measured against
21
+ * `sepolia.base.org`, 2 of 212 windows on a full-history scan — and a transient
22
+ * rate-limit is NOT a window that cannot be scanned. Declaring it one would
23
+ * make the "this scan is incomplete" warning routine, and a warning that fires
24
+ * on every healthy run is a warning nobody reads on the run that matters.
25
+ */
26
+ const WINDOW_ATTEMPTS = 3;
27
+ const RETRY_DELAY_MS = 400;
28
+ export const streamsCommand = {
29
+ name: "streams",
30
+ summary: "List the streams a wallet has opened, or been hired through",
31
+ usage: "metrik streams [--address 0x…] [--role buyer|operator|any] [--limit N] [--from-block N]",
32
+ flags: ["address", "role", "limit", "from-block"],
33
+ signs: false,
34
+ async run(context) {
35
+ const { args, config, env, out, deps } = context;
36
+ const roleFlag = stringFlag(args, "role") ?? "any";
37
+ if (roleFlag !== "any" && !ROLES.includes(roleFlag)) {
38
+ throw usageError(`--role must be buyer, operator or any (got "${roleFlag}")`);
39
+ }
40
+ const roles = roleFlag === "any" ? ROLES : [roleFlag];
41
+ const limit = integerFlag(args, "limit");
42
+ if (limit !== undefined && limit < 1) {
43
+ throw usageError("--limit must be at least 1");
44
+ }
45
+ const address = await resolveScanAddress(context);
46
+ const read = deps.createReadClient(config);
47
+ // The same allowlist the signing paths get. This command spends nothing,
48
+ // but it reads a V2 stream tuple, and the V1 and V2 tuples diverge from
49
+ // index 5 onward — a wrong-chain read does not revert, it decodes something
50
+ // else into fields with the right names.
51
+ await assertTestnetRpc(config, () => read.getChainId());
52
+ const anchor = resolveAnchorBlock(args, config.escrow);
53
+ const head = await read.blockNumber();
54
+ const range = escrowScanRange(head, anchor.block);
55
+ // Retention canary, before the scan is believed. A node that has pruned its
56
+ // log index answers a range it no longer retains with a SUCCESSFUL EMPTY
57
+ // result (measured on Base Sepolia: one public endpoint returned 0 logs for
58
+ // a window where two others returned 582). This CLI talks to ONE endpoint,
59
+ // so it cannot recover those logs from a sibling the way a multi-provider
60
+ // client can — but it can refuse to call the silence "no streams".
61
+ const retention = anchor.canary === null
62
+ ? null
63
+ : await read
64
+ .escrowLogCountAt(anchor.canary)
65
+ .then((count) => count > 0)
66
+ .catch(() => false);
67
+ const scan = await scanOpenedStreams(read, address, roles, range.windows, deps.sleep);
68
+ const reasons = [];
69
+ if (range.degraded) {
70
+ reasons.push(`the window budget could not reach block ${anchor.block} (scan starts at ${range.fromBlock})`);
71
+ }
72
+ if (scan.failedWindows > 0) {
73
+ reasons.push(`${scan.failedWindows} of ${range.windows.length * roles.length} log queries failed after ${WINDOW_ATTEMPTS} attempts each`);
74
+ }
75
+ if (retention === false) {
76
+ reasons.push(`${config.rpcUrl} does not serve logs for block ${anchor.canary}, so it has pruned the escrow's history and its empty windows cannot be trusted`);
77
+ }
78
+ const degraded = reasons.length > 0;
79
+ // Newest first: the stream someone is looking for is almost always the one
80
+ // they just opened.
81
+ const opened = [...scan.opened].sort((a, b) => a.blockNumber === b.blockNumber
82
+ ? 0
83
+ : a.blockNumber > b.blockNumber
84
+ ? -1
85
+ : 1);
86
+ const shown = limit === undefined ? opened : opened.slice(0, limit);
87
+ // One `getStream` per row. A list that cannot distinguish an active stream
88
+ // from a closed one is not actionable, and status is the one field the
89
+ // event cannot carry. The oracle's checkpoint proof is deliberately NOT
90
+ // fetched here — that is a per-stream HTTP call, and `metrik status` is the
91
+ // command that makes it.
92
+ const rows = await Promise.all(shown.map(async (log) => {
93
+ const stream = await read.getStreamV2(log.streamId).catch(() => null);
94
+ return {
95
+ streamId: log.streamId,
96
+ role: describeRole(log, address),
97
+ buyer: log.buyer,
98
+ operator: log.operator,
99
+ serviceRef: log.serviceRef,
100
+ // V2 MUTATES `Stream.deposit` on reclaim, so the event is the only
101
+ // surviving record of what was originally escrowed.
102
+ originalDepositUsdc: log.deposit,
103
+ ratePerSecondUsdc: log.ratePerSecond,
104
+ expiresAt: log.expiresAt,
105
+ openedAtBlock: log.blockNumber,
106
+ // A read failure is reported as unread, never as a dropped row: a
107
+ // stream that vanishes from this list is the exact failure the whole
108
+ // command is built to avoid.
109
+ status: stream?.status ?? "unread",
110
+ reclaimed: stream?.reclaimed ?? null,
111
+ };
112
+ }));
113
+ out.result({
114
+ address,
115
+ role: roleFlag,
116
+ escrow: config.escrow,
117
+ fromBlock: range.fromBlock,
118
+ toBlock: range.toBlock,
119
+ windows: range.windows.length,
120
+ logRetentionProven: retention,
121
+ degraded,
122
+ degradedReasons: reasons,
123
+ found: opened.length,
124
+ count: rows.length,
125
+ streams: rows,
126
+ });
127
+ if (degraded) {
128
+ // stderr in both modes: a partial scan must reach someone piping stdout
129
+ // into jq just as loudly as someone reading the table.
130
+ out.note(`warning: this scan is INCOMPLETE — ${reasons.join("; ")}. Streams may exist that are not listed below.`);
131
+ }
132
+ if (rows.length === 0) {
133
+ out.print(degraded
134
+ ? `No streams found for ${address} in the part of the escrow's history that could be scanned. This is NOT a statement that none exist — see the warning above.`
135
+ : `No streams for ${address} across the escrow's full history (blocks ${range.fromBlock}-${range.toBlock}).`);
136
+ return;
137
+ }
138
+ out.print(`${rows.length} stream${rows.length === 1 ? "" : "s"} for ${address}${opened.length > rows.length ? ` (of ${opened.length}, limited)` : ""}`);
139
+ for (const row of rows) {
140
+ out.print("");
141
+ out.print(`${row.streamId} [${row.status}]`);
142
+ out.table([
143
+ ["role", row.role],
144
+ ["counterparty", row.role === "buyer" ? row.operator : row.buyer],
145
+ ["serviceRef", row.serviceRef],
146
+ ["escrowed", formatUsdc(row.originalDepositUsdc)],
147
+ ["rate", `${formatUsdc(row.ratePerSecondUsdc)} / second`],
148
+ ["opened at block", row.openedAtBlock.toString()],
149
+ [
150
+ "reclaimed",
151
+ row.reclaimed === null ? "unread" : String(row.reclaimed),
152
+ ],
153
+ ]);
154
+ }
155
+ out.print("");
156
+ out.print("Earned-so-far is published by the oracle, not held on the stream: metrik status <streamId>");
157
+ },
158
+ };
159
+ /** Whose streams to list: an explicit `--address`, else the configured wallet. */
160
+ async function resolveScanAddress(context) {
161
+ const { args, env, out } = context;
162
+ const explicit = stringFlag(args, "address");
163
+ if (explicit !== undefined) {
164
+ if (!isAddress(explicit)) {
165
+ throw usageError(`--address must be an EVM address (got "${explicit}")`);
166
+ }
167
+ return getAddress(explicit);
168
+ }
169
+ // This command broadcasts nothing and signs nothing, so it must not demand a
170
+ // wallet: `--address` is enough to read anyone's streams. A configured wallet
171
+ // is simply the default subject.
172
+ const wallet = await optionalCliWallet(env, { note: out.note });
173
+ if (wallet === null) {
174
+ throw configError("metrik streams needs a wallet to know whose streams to list", `pass --address 0x… to list any address's streams without one, or set ${PRIVATE_KEY_ENV} (or the CDP or Privy variables).`);
175
+ }
176
+ return wallet.account.address;
177
+ }
178
+ /**
179
+ * Where the log scan starts, and whether a retention canary is possible there.
180
+ *
181
+ * The canary block must be one that is KNOWN to carry escrow logs, otherwise a
182
+ * zero from it proves nothing and every scan would report itself degraded
183
+ * forever. That is only true of the deploy block of the escrow we shipped the
184
+ * constant for — so an overridden `--escrow` or `--from-block` scans without a
185
+ * canary rather than with a meaningless one.
186
+ */
187
+ function resolveAnchorBlock(args, escrow) {
188
+ const override = integerFlag(args, "from-block");
189
+ const isKnownEscrow = escrow.toLowerCase() === BASE_SEPOLIA_ESCROW_V2.toLowerCase();
190
+ if (override !== undefined) {
191
+ return { block: BigInt(override), canary: null };
192
+ }
193
+ if (!isKnownEscrow) {
194
+ throw configError(`no scan anchor is known for escrow ${escrow}`, "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.");
195
+ }
196
+ return {
197
+ block: BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK,
198
+ canary: BASE_SEPOLIA_ESCROW_V2_DEPLOY_BLOCK,
199
+ };
200
+ }
201
+ /**
202
+ * Page every window for every selected role filter, bounded concurrency.
203
+ *
204
+ * A window that ERRORS is counted, not thrown: a partial list with an honest
205
+ * warning is more useful than no list at all, and the count is what makes the
206
+ * warning specific.
207
+ */
208
+ async function scanOpenedStreams(read, address, roles, windows, sleep) {
209
+ // A wallet that is BOTH buyer and operator on one stream matches two filters.
210
+ const byStreamId = new Map();
211
+ let failedWindows = 0;
212
+ const readWindow = async (query) => {
213
+ for (let attempt = 1; attempt <= WINDOW_ATTEMPTS; attempt++) {
214
+ try {
215
+ return await read.streamOpenedLogs(query);
216
+ }
217
+ catch {
218
+ if (attempt < WINDOW_ATTEMPTS)
219
+ await sleep(RETRY_DELAY_MS * attempt);
220
+ }
221
+ }
222
+ return null;
223
+ };
224
+ const queries = windows.flatMap(([fromBlock, toBlock]) => roles.map((role) => ({ role, address, fromBlock, toBlock })));
225
+ for (let index = 0; index < queries.length; index += SCAN_CONCURRENCY) {
226
+ const batch = queries.slice(index, index + SCAN_CONCURRENCY);
227
+ const settled = await Promise.all(batch.map(readWindow));
228
+ for (const logs of settled) {
229
+ if (logs === null) {
230
+ failedWindows++;
231
+ continue;
232
+ }
233
+ for (const log of logs) {
234
+ if (!byStreamId.has(log.streamId))
235
+ byStreamId.set(log.streamId, log);
236
+ }
237
+ }
238
+ }
239
+ return { opened: [...byStreamId.values()], failedWindows };
240
+ }
241
+ function describeRole(log, address) {
242
+ const wanted = address.toLowerCase();
243
+ const isBuyer = log.buyer.toLowerCase() === wanted;
244
+ const isOperator = log.operator.toLowerCase() === wanted;
245
+ if (isBuyer && isOperator)
246
+ return "buyer+operator";
247
+ if (isBuyer)
248
+ return "buyer";
249
+ if (isOperator)
250
+ return "operator";
251
+ // Only reachable if the node ignored the indexed topic filter it was given.
252
+ throw new CliError(`the RPC returned stream ${log.streamId}, which does not involve ${address}`, {
253
+ exitCode: 4,
254
+ hint: "the escrow log filter was not applied by the endpoint; the result cannot be trusted.",
255
+ });
256
+ }
257
+ //# sourceMappingURL=streams.js.map