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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/README.md +106 -30
  2. package/dist/capability/invocation-capability.d.ts +20 -8
  3. package/dist/capability/invocation-capability.d.ts.map +1 -1
  4. package/dist/capability/invocation-capability.js.map +1 -1
  5. package/dist/capability/probe-capability.d.ts +225 -0
  6. package/dist/capability/probe-capability.d.ts.map +1 -0
  7. package/dist/capability/probe-capability.js +248 -0
  8. package/dist/capability/probe-capability.js.map +1 -0
  9. package/dist/cli/args.d.ts +11 -0
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/args.js +23 -0
  12. package/dist/cli/args.js.map +1 -1
  13. package/dist/cli/commands/claim.d.ts.map +1 -1
  14. package/dist/cli/commands/claim.js +21 -5
  15. package/dist/cli/commands/claim.js.map +1 -1
  16. package/dist/cli/commands/close.d.ts.map +1 -1
  17. package/dist/cli/commands/close.js +51 -7
  18. package/dist/cli/commands/close.js.map +1 -1
  19. package/dist/cli/commands/invoke.d.ts +2 -0
  20. package/dist/cli/commands/invoke.d.ts.map +1 -1
  21. package/dist/cli/commands/invoke.js +3 -2
  22. package/dist/cli/commands/invoke.js.map +1 -1
  23. package/dist/cli/commands/mcp.d.ts.map +1 -1
  24. package/dist/cli/commands/mcp.js +15 -2
  25. package/dist/cli/commands/mcp.js.map +1 -1
  26. package/dist/cli/commands/open.d.ts.map +1 -1
  27. package/dist/cli/commands/open.js +121 -15
  28. package/dist/cli/commands/open.js.map +1 -1
  29. package/dist/cli/commands/pay.d.ts +19 -0
  30. package/dist/cli/commands/pay.d.ts.map +1 -0
  31. package/dist/cli/commands/pay.js +355 -0
  32. package/dist/cli/commands/pay.js.map +1 -0
  33. package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -1
  34. package/dist/cli/commands/reclaim-unverified.js +15 -6
  35. package/dist/cli/commands/reclaim-unverified.js.map +1 -1
  36. package/dist/cli/commands/reclaim.d.ts +23 -2
  37. package/dist/cli/commands/reclaim.d.ts.map +1 -1
  38. package/dist/cli/commands/reclaim.js +200 -21
  39. package/dist/cli/commands/reclaim.js.map +1 -1
  40. package/dist/cli/commands/refund.d.ts +13 -0
  41. package/dist/cli/commands/refund.d.ts.map +1 -0
  42. package/dist/cli/commands/refund.js +224 -0
  43. package/dist/cli/commands/refund.js.map +1 -0
  44. package/dist/cli/commands/status.d.ts.map +1 -1
  45. package/dist/cli/commands/status.js +10 -1
  46. package/dist/cli/commands/status.js.map +1 -1
  47. package/dist/cli/commands/streams.js +9 -13
  48. package/dist/cli/commands/streams.js.map +1 -1
  49. package/dist/cli/commands/watch.d.ts.map +1 -1
  50. package/dist/cli/commands/watch.js +2 -1
  51. package/dist/cli/commands/watch.js.map +1 -1
  52. package/dist/cli/config.d.ts +44 -26
  53. package/dist/cli/config.d.ts.map +1 -1
  54. package/dist/cli/config.js +88 -38
  55. package/dist/cli/config.js.map +1 -1
  56. package/dist/cli/deps.d.ts +86 -7
  57. package/dist/cli/deps.d.ts.map +1 -1
  58. package/dist/cli/deps.js +102 -16
  59. package/dist/cli/deps.js.map +1 -1
  60. package/dist/cli/errors.d.ts.map +1 -1
  61. package/dist/cli/errors.js +21 -1
  62. package/dist/cli/errors.js.map +1 -1
  63. package/dist/cli/escape-hatch.d.ts +6 -4
  64. package/dist/cli/escape-hatch.d.ts.map +1 -1
  65. package/dist/cli/escape-hatch.js +6 -2
  66. package/dist/cli/escape-hatch.js.map +1 -1
  67. package/dist/cli/listings.d.ts +18 -0
  68. package/dist/cli/listings.d.ts.map +1 -1
  69. package/dist/cli/listings.js +22 -12
  70. package/dist/cli/listings.js.map +1 -1
  71. package/dist/cli/preflight.d.ts +20 -14
  72. package/dist/cli/preflight.d.ts.map +1 -1
  73. package/dist/cli/preflight.js +34 -20
  74. package/dist/cli/preflight.js.map +1 -1
  75. package/dist/cli/privy.d.ts +83 -0
  76. package/dist/cli/privy.d.ts.map +1 -0
  77. package/dist/cli/privy.js +232 -0
  78. package/dist/cli/privy.js.map +1 -0
  79. package/dist/cli/reclaimability.d.ts +82 -0
  80. package/dist/cli/reclaimability.d.ts.map +1 -0
  81. package/dist/cli/reclaimability.js +80 -0
  82. package/dist/cli/reclaimability.js.map +1 -0
  83. package/dist/cli/run.d.ts +2 -1
  84. package/dist/cli/run.d.ts.map +1 -1
  85. package/dist/cli/run.js +40 -11
  86. package/dist/cli/run.js.map +1 -1
  87. package/dist/cli/scan-range.d.ts +2 -2
  88. package/dist/cli/scan-range.js +2 -2
  89. package/dist/cli/spend-policy.d.ts +60 -0
  90. package/dist/cli/spend-policy.d.ts.map +1 -0
  91. package/dist/cli/spend-policy.js +65 -0
  92. package/dist/cli/spend-policy.js.map +1 -0
  93. package/dist/cli/status.d.ts +3 -1
  94. package/dist/cli/status.d.ts.map +1 -1
  95. package/dist/cli/status.js +15 -3
  96. package/dist/cli/status.js.map +1 -1
  97. package/dist/cli/wallet.d.ts +109 -40
  98. package/dist/cli/wallet.d.ts.map +1 -1
  99. package/dist/cli/wallet.js +109 -44
  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 +91 -4
  121. package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
  122. package/dist/gateway/caller-auth-gateway.js +184 -7
  123. package/dist/gateway/caller-auth-gateway.js.map +1 -1
  124. package/dist/gateway/http-server.d.ts +121 -1
  125. package/dist/gateway/http-server.d.ts.map +1 -1
  126. package/dist/gateway/http-server.js +474 -5
  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 +215 -0
  137. package/dist/gateway/x402-opener-mode.d.ts.map +1 -0
  138. package/dist/gateway/x402-opener-mode.js +639 -0
  139. package/dist/gateway/x402-opener-mode.js.map +1 -0
  140. package/dist/index.d.ts +11 -5
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +15 -5
  143. package/dist/index.js.map +1 -1
  144. package/dist/mandates/env.d.ts +3 -2
  145. package/dist/mandates/env.d.ts.map +1 -1
  146. package/dist/mandates/env.js +3 -2
  147. package/dist/mandates/env.js.map +1 -1
  148. package/dist/mcp/server.d.ts +100 -0
  149. package/dist/mcp/server.d.ts.map +1 -1
  150. package/dist/mcp/server.js +719 -39
  151. package/dist/mcp/server.js.map +1 -1
  152. package/dist/mcp/service-invoker.d.ts +97 -0
  153. package/dist/mcp/service-invoker.d.ts.map +1 -0
  154. package/dist/mcp/service-invoker.js +167 -0
  155. package/dist/mcp/service-invoker.js.map +1 -0
  156. package/dist/mcp/x402-tools.d.ts +104 -0
  157. package/dist/mcp/x402-tools.d.ts.map +1 -0
  158. package/dist/mcp/x402-tools.js +434 -0
  159. package/dist/mcp/x402-tools.js.map +1 -0
  160. package/dist/network.d.ts +119 -0
  161. package/dist/network.d.ts.map +1 -0
  162. package/dist/network.js +199 -0
  163. package/dist/network.js.map +1 -0
  164. package/dist/sdk/invoke.d.ts +23 -4
  165. package/dist/sdk/invoke.d.ts.map +1 -1
  166. package/dist/sdk/invoke.js +71 -10
  167. package/dist/sdk/invoke.js.map +1 -1
  168. package/dist/wallet/autonomous-wallet-broker.d.ts +3 -0
  169. package/dist/wallet/autonomous-wallet-broker.d.ts.map +1 -1
  170. package/dist/wallet/autonomous-wallet-broker.js +27 -23
  171. package/dist/wallet/autonomous-wallet-broker.js.map +1 -1
  172. package/dist/wallet/autonomous-wallet-protocol.d.ts +8 -2
  173. package/dist/wallet/autonomous-wallet-protocol.d.ts.map +1 -1
  174. package/dist/wallet/autonomous-wallet-protocol.js +9 -2
  175. package/dist/wallet/autonomous-wallet-protocol.js.map +1 -1
  176. package/dist/wallet/autonomous-wallet.d.ts.map +1 -1
  177. package/dist/wallet/autonomous-wallet.js +3 -2
  178. package/dist/wallet/autonomous-wallet.js.map +1 -1
  179. package/dist/wallet/broker-escrows.d.ts +222 -0
  180. package/dist/wallet/broker-escrows.d.ts.map +1 -0
  181. package/dist/wallet/broker-escrows.js +36 -0
  182. package/dist/wallet/broker-escrows.js.map +1 -0
  183. package/dist/wallet/lifecycle.d.ts +21 -5
  184. package/dist/wallet/lifecycle.d.ts.map +1 -1
  185. package/dist/wallet/lifecycle.js +32 -12
  186. package/dist/wallet/lifecycle.js.map +1 -1
  187. package/dist/wallet/privy-broker-server.d.ts.map +1 -1
  188. package/dist/wallet/privy-broker-server.js +31 -26
  189. package/dist/wallet/privy-broker-server.js.map +1 -1
  190. package/dist/wallet/privy-session-broker.d.ts +14 -5
  191. package/dist/wallet/privy-session-broker.d.ts.map +1 -1
  192. package/dist/wallet/privy-session-broker.js +29 -17
  193. package/dist/wallet/privy-session-broker.js.map +1 -1
  194. package/dist/wallet/privy-session-provider.d.ts +3 -1
  195. package/dist/wallet/privy-session-provider.d.ts.map +1 -1
  196. package/dist/wallet/privy-session-provider.js +5 -3
  197. package/dist/wallet/privy-session-provider.js.map +1 -1
  198. package/dist/wallet/provider.d.ts.map +1 -1
  199. package/dist/wallet/provider.js +15 -8
  200. package/dist/wallet/provider.js.map +1 -1
  201. package/dist/x402/facilitator.d.ts +42 -17
  202. package/dist/x402/facilitator.d.ts.map +1 -1
  203. package/dist/x402/facilitator.js +49 -33
  204. package/dist/x402/facilitator.js.map +1 -1
  205. package/dist/x402-buyer/guarded-fetch.d.ts +51 -0
  206. package/dist/x402-buyer/guarded-fetch.d.ts.map +1 -0
  207. package/dist/x402-buyer/guarded-fetch.js +147 -0
  208. package/dist/x402-buyer/guarded-fetch.js.map +1 -0
  209. package/dist/x402-buyer/index.d.ts +10 -0
  210. package/dist/x402-buyer/index.d.ts.map +1 -0
  211. package/dist/x402-buyer/index.js +10 -0
  212. package/dist/x402-buyer/index.js.map +1 -0
  213. package/dist/x402-buyer/pay.d.ts +135 -0
  214. package/dist/x402-buyer/pay.d.ts.map +1 -0
  215. package/dist/x402-buyer/pay.js +336 -0
  216. package/dist/x402-buyer/pay.js.map +1 -0
  217. package/dist/x402-opener/abi.d.ts +684 -0
  218. package/dist/x402-opener/abi.d.ts.map +1 -0
  219. package/dist/x402-opener/abi.js +880 -0
  220. package/dist/x402-opener/abi.js.map +1 -0
  221. package/dist/x402-opener/facilitator-entry.d.ts +3 -0
  222. package/dist/x402-opener/facilitator-entry.d.ts.map +1 -0
  223. package/dist/x402-opener/facilitator-entry.js +37 -0
  224. package/dist/x402-opener/facilitator-entry.js.map +1 -0
  225. package/dist/x402-opener/facilitator-server.d.ts +180 -0
  226. package/dist/x402-opener/facilitator-server.d.ts.map +1 -0
  227. package/dist/x402-opener/facilitator-server.js +696 -0
  228. package/dist/x402-opener/facilitator-server.js.map +1 -0
  229. package/dist/x402-opener/index.d.ts +14 -0
  230. package/dist/x402-opener/index.d.ts.map +1 -0
  231. package/dist/x402-opener/index.js +14 -0
  232. package/dist/x402-opener/index.js.map +1 -0
  233. package/dist/x402-opener/listing-resolver.d.ts +55 -0
  234. package/dist/x402-opener/listing-resolver.d.ts.map +1 -0
  235. package/dist/x402-opener/listing-resolver.js +125 -0
  236. package/dist/x402-opener/listing-resolver.js.map +1 -0
  237. package/dist/x402-opener/refund-relay.d.ts +240 -0
  238. package/dist/x402-opener/refund-relay.d.ts.map +1 -0
  239. package/dist/x402-opener/refund-relay.js +1240 -0
  240. package/dist/x402-opener/refund-relay.js.map +1 -0
  241. package/dist/x402-opener/relayer-queue.d.ts +14 -0
  242. package/dist/x402-opener/relayer-queue.d.ts.map +1 -0
  243. package/dist/x402-opener/relayer-queue.js +18 -0
  244. package/dist/x402-opener/relayer-queue.js.map +1 -0
  245. package/dist/x402-opener/scheme.d.ts +217 -0
  246. package/dist/x402-opener/scheme.d.ts.map +1 -0
  247. package/dist/x402-opener/scheme.js +749 -0
  248. package/dist/x402-opener/scheme.js.map +1 -0
  249. package/dist/x402-opener/terms.d.ts +105 -0
  250. package/dist/x402-opener/terms.d.ts.map +1 -0
  251. package/dist/x402-opener/terms.js +212 -0
  252. package/dist/x402-opener/terms.js.map +1 -0
  253. package/dist/zktls/reclaim.d.ts +2 -2
  254. package/docs/agent-layer.md +13 -8
  255. package/docs/autonomous-privy-wallet.md +188 -0
  256. package/docs/cli.md +342 -53
  257. package/docs/langchain.md +5 -0
  258. package/docs/mcp-hosted.md +4 -3
  259. package/docs/mcp-reference.md +245 -0
  260. package/docs/probe-capability.md +191 -0
  261. package/docs/quickstart.md +54 -5
  262. package/docs/threat-model.md +24 -3
  263. package/docs/x402.md +681 -0
  264. package/package.json +19 -6
  265. package/src/capability/invocation-capability.ts +17 -1
  266. package/src/capability/probe-capability.ts +355 -0
  267. package/src/cli/args.ts +29 -0
  268. package/src/cli/commands/claim.ts +31 -5
  269. package/src/cli/commands/close.ts +56 -7
  270. package/src/cli/commands/invoke.ts +6 -2
  271. package/src/cli/commands/mcp.ts +18 -2
  272. package/src/cli/commands/open.ts +151 -14
  273. package/src/cli/commands/pay.ts +442 -0
  274. package/src/cli/commands/reclaim-unverified.ts +24 -6
  275. package/src/cli/commands/reclaim.ts +274 -19
  276. package/src/cli/commands/refund.ts +277 -0
  277. package/src/cli/commands/status.ts +17 -1
  278. package/src/cli/commands/streams.ts +11 -15
  279. package/src/cli/commands/watch.ts +7 -1
  280. package/src/cli/config.ts +139 -52
  281. package/src/cli/deps.ts +189 -25
  282. package/src/cli/errors.ts +24 -1
  283. package/src/cli/escape-hatch.ts +19 -5
  284. package/src/cli/listings.ts +43 -10
  285. package/src/cli/preflight.ts +36 -21
  286. package/src/cli/privy.ts +398 -0
  287. package/src/cli/reclaimability.ts +154 -0
  288. package/src/cli/run.ts +54 -11
  289. package/src/cli/scan-range.ts +2 -2
  290. package/src/cli/spend-policy.ts +139 -0
  291. package/src/cli/status.ts +14 -1
  292. package/src/cli/wallet.ts +189 -58
  293. package/src/discovery/rate-band.ts +172 -0
  294. package/src/escrow-generation.ts +135 -0
  295. package/src/frameworks/agentkit.ts +45 -11
  296. package/src/frameworks/eliza.ts +27 -5
  297. package/src/frameworks/langchain.ts +14 -0
  298. package/src/gateway/caller-auth-gateway.ts +336 -13
  299. package/src/gateway/http-server.ts +690 -7
  300. package/src/gateway/probe-auth.ts +191 -0
  301. package/src/gateway/signed-rate-bounds.ts +164 -0
  302. package/src/gateway/x402-opener-mode.ts +974 -0
  303. package/src/index.ts +112 -14
  304. package/src/mandates/env.ts +3 -2
  305. package/src/mcp/server.ts +973 -44
  306. package/src/mcp/service-invoker.ts +285 -0
  307. package/src/mcp/x402-tools.ts +621 -0
  308. package/src/network.ts +302 -0
  309. package/src/sdk/invoke.ts +96 -17
  310. package/src/wallet/autonomous-wallet-broker.ts +46 -20
  311. package/src/wallet/autonomous-wallet-protocol.ts +11 -2
  312. package/src/wallet/autonomous-wallet.ts +4 -2
  313. package/src/wallet/broker-escrows.ts +66 -0
  314. package/src/wallet/lifecycle.ts +53 -18
  315. package/src/wallet/privy-broker-server.ts +35 -25
  316. package/src/wallet/privy-session-broker.ts +58 -25
  317. package/src/wallet/privy-session-provider.ts +7 -3
  318. package/src/wallet/provider.ts +20 -15
  319. package/src/x402/facilitator.ts +85 -46
  320. package/src/x402-buyer/guarded-fetch.ts +213 -0
  321. package/src/x402-buyer/index.ts +9 -0
  322. package/src/x402-buyer/pay.ts +517 -0
  323. package/src/x402-opener/abi.ts +881 -0
  324. package/src/x402-opener/facilitator-entry.ts +45 -0
  325. package/src/x402-opener/facilitator-server.ts +1014 -0
  326. package/src/x402-opener/index.ts +13 -0
  327. package/src/x402-opener/listing-resolver.ts +202 -0
  328. package/src/x402-opener/refund-relay.ts +1743 -0
  329. package/src/x402-opener/relayer-queue.ts +18 -0
  330. package/src/x402-opener/scheme.ts +1133 -0
  331. package/src/x402-opener/terms.ts +300 -0
@@ -1,4 +1,3 @@
1
- import { MetrikClient } from "@absol-labs/sdk";
2
1
  import { isAddress, isHex, type Address, type Hex } from "viem";
3
2
 
4
3
  import {
@@ -10,6 +9,29 @@ import {
10
9
  type InvocationCapabilityDomainInput,
11
10
  type SignedInvocationCapability,
12
11
  } from "../capability/invocation-capability.js";
12
+ import {
13
+ decodeProbeCapabilityHeader,
14
+ hashProbePath,
15
+ InvalidProbeCapabilityHeaderError,
16
+ PROBE_CAPABILITY_HEADER_NAME,
17
+ recoverProbeCapabilitySigner,
18
+ type ProbeCapabilityDomainInput,
19
+ type SignedProbeCapability,
20
+ } from "../capability/probe-capability.js";
21
+ import {
22
+ CachedOracleSignerSet,
23
+ createSdkOracleSignerSetReader,
24
+ FixedWindowProbeRateLimiter,
25
+ type GatewayProbeMode,
26
+ type ProbeAuthorization,
27
+ type ProbeRateLimiter,
28
+ } from "./probe-auth.js";
29
+ import { defaultNetwork, readOnlyMetrikClient } from "../network.js";
30
+ import {
31
+ isVerifiedSignedRateBounds,
32
+ rateOutsideBounds,
33
+ type SignedRateBounds,
34
+ } from "./signed-rate-bounds.js";
13
35
 
14
36
  /**
15
37
  * Seller-side caller-auth gateway (metrik-agent#63, impl of #62 part 1/2).
@@ -69,7 +91,45 @@ export type CallerAuthDenialReason =
69
91
  | "stream-not-active"
70
92
  | "stream-expired"
71
93
  | "stream-not-funded"
72
- | "stream-state-stale";
94
+ | "stream-state-stale"
95
+ /**
96
+ * The stream's on-chain `ratePerSecond` is outside the operator-SIGNED
97
+ * `minRatePerSecond`/`maxRatePerSecond` of this service's descriptor
98
+ * (metrik-contracts#41: the escrow accepts any non-zero rate, so a buyer
99
+ * calling it directly could otherwise open an underpriced stream and be
100
+ * served). Irreversible: the rate is fixed at open.
101
+ */
102
+ | "stream-rate-out-of-bounds"
103
+ /**
104
+ * Signed rate bounds are configured but the stream view carries no
105
+ * `ratePerSecond`, so the band cannot be checked. Fail closed.
106
+ */
107
+ | "stream-rate-unknown"
108
+ /**
109
+ * The request carried an oracle probe capability AND a buyer capability or an
110
+ * `X-PAYMENT` header. Refused rather than guessed: a probe is free and unbilled,
111
+ * a buyer call is neither, and the two must never be conflated.
112
+ */
113
+ | "ambiguous-probe-mode"
114
+ /**
115
+ * A probe capability was presented but this gateway has probe verification
116
+ * turned off. Distinct from the malformed/invalid reasons: it tells the
117
+ * oracle the paid route is UNVERIFIABLE here, not that it failed.
118
+ */
119
+ | "probe-not-enabled"
120
+ | "malformed-probe-capability"
121
+ | "probe-capability-expired"
122
+ | "probe-invalid-signature"
123
+ | "probe-method-mismatch"
124
+ | "probe-path-mismatch"
125
+ | "probe-service-ref-mismatch"
126
+ | "probe-nonce-replayed"
127
+ /** The recovered signer is not in the escrow's `getOracleSigners()` set. */
128
+ | "probe-signer-not-authorized"
129
+ /** The signer set could not be read fresh enough to decide. Fail closed. */
130
+ | "probe-signer-set-unavailable"
131
+ /** The free-probe budget for this window is spent. */
132
+ | "probe-rate-limited";
73
133
 
74
134
  /**
75
135
  * Default freshness bound for on-chain stream state, in seconds. Base blocks
@@ -92,7 +152,9 @@ const DENIAL_PRECEDENCE: readonly CallerAuthDenialReason[] = [
92
152
  "stream-expired",
93
153
  "buyer-mismatch",
94
154
  "service-ref-mismatch",
155
+ "stream-rate-out-of-bounds",
95
156
  "stream-not-funded",
157
+ "stream-rate-unknown",
96
158
  "stream-state-stale",
97
159
  "stream-not-found",
98
160
  ];
@@ -110,15 +172,36 @@ const DENIAL_PRECEDENCE: readonly CallerAuthDenialReason[] = [
110
172
  const TERMINAL_DENIALS: ReadonlySet<CallerAuthDenialReason> = new Set([
111
173
  "stream-not-active",
112
174
  "stream-not-funded",
175
+ // `ratePerSecond` is fixed at open and the signed band is this process's
176
+ // configuration, so an out-of-band stream stays out of band.
177
+ "stream-rate-out-of-bounds",
113
178
  ]);
114
179
 
180
+ /**
181
+ * A probe carrying an `X-PAYMENT` header is refused as `ambiguous-probe-mode`.
182
+ * This gateway implements no per-call payment mode (the x402 v1 integration was
183
+ * removed in metrik-agent#115), but the refusal is kept deliberately: a probe
184
+ * is free, unbilled verification traffic and must never be mixed with anything
185
+ * that looks like a payment, and keeping it leaves the deployed gateway's
186
+ * behaviour byte-for-byte unchanged.
187
+ */
188
+ const PAYMENT_HEADER_REFUSED_ON_PROBES = "x-payment";
189
+
115
190
  export interface CallerAuthDecision {
116
191
  readonly authorized: boolean;
117
192
  readonly reason?: CallerAuthDenialReason;
118
193
  /** Set only when `authorized === false`. */
119
- readonly httpStatus?: 402 | 403;
120
- /** Set only when `authorized === true`. */
194
+ readonly httpStatus?: 400 | 402 | 403 | 429;
195
+ /** Set only when `authorized === true` via the Metrik stream path. */
121
196
  readonly stream?: GatewayStreamView;
197
+ /**
198
+ * Set only when `authorized === true` via the ORACLE PROBE path. Mutually
199
+ * exclusive with {@link stream} by construction: the probe branch returns
200
+ * before a stream is ever produced, so an authorized probe can never draw on
201
+ * a stream's budget. It is
202
+ * verification traffic and MUST NOT be metered or billed.
203
+ */
204
+ readonly probe?: ProbeAuthorization;
122
205
  /** The decoded (but not necessarily valid) capability, when one was present. */
123
206
  readonly capability?: SignedInvocationCapability;
124
207
  }
@@ -131,6 +214,13 @@ export interface GatewayStreamView {
131
214
  readonly claimedCumulative: bigint;
132
215
  readonly expiresAt: number;
133
216
  readonly status: "active" | "closed";
217
+ /**
218
+ * The stream's contracted `ratePerSecond` (USDC base units per verified
219
+ * second), fixed at open. Required when the gateway enforces operator-signed
220
+ * rate bounds; a view without it is refused (`stream-rate-unknown`) there.
221
+ * The default SDK reader always reports it.
222
+ */
223
+ readonly ratePerSecond?: bigint;
134
224
  /**
135
225
  * The chain head this view was observed against. Readers SHOULD report it:
136
226
  * when present, the gateway rejects the view outright if the block is older
@@ -203,11 +293,10 @@ export class UnknownStreamGatewayError extends Error {
203
293
  export function createSdkStreamReader(options: {
204
294
  readonly escrowAddress: Address;
205
295
  readonly rpcUrl: string;
296
+ /** Chain of the escrow; must be a registered network. Default: the registry default. */
297
+ readonly chainId?: number;
206
298
  }): StreamReader {
207
- const metrik = MetrikClient.baseSepolia({
208
- escrow: options.escrowAddress,
209
- rpcUrl: options.rpcUrl,
210
- });
299
+ const metrik = readOnlyMetrikClient(options);
211
300
  return {
212
301
  async getStream(streamId) {
213
302
  try {
@@ -226,6 +315,7 @@ export function createSdkStreamReader(options: {
226
315
  claimedCumulative: stream.claimedCumulative,
227
316
  expiresAt: stream.expiresAt,
228
317
  status: stream.status,
318
+ ratePerSecond: stream.ratePerSecond,
229
319
  observedAt: {
230
320
  blockNumber: block.number,
231
321
  blockTimestamp: Number(block.timestamp),
@@ -284,7 +374,7 @@ export interface CallerAuthGatewayConfig {
284
374
  readonly serviceRef: Hex;
285
375
  /** Additional service refs accepted during signed-descriptor migrations. */
286
376
  readonly serviceRefs?: readonly Hex[];
287
- /** Chain id for EIP-712 domain separation. Default `84532` (Base Sepolia). */
377
+ /** Chain id for EIP-712 domain separation and the default readers. Default: the registry's default network. */
288
378
  readonly chainId?: number;
289
379
  /** Capability header name. Default {@link CAPABILITY_HEADER_NAME}. */
290
380
  readonly headerName?: string;
@@ -316,6 +406,28 @@ export interface CallerAuthGatewayConfig {
316
406
  readonly nonceCache?: NonceReplayCache;
317
407
  /** Injectable clock (unix seconds), for tests. */
318
408
  readonly now?: () => number;
409
+ /**
410
+ * Optional ORACLE PROBE mode. When set, a request carrying a valid
411
+ * oracle-signed `ProbeCapability` is authorized and proxied WITHOUT any
412
+ * stream, payment or metering, so the neutral verifier can exercise this
413
+ * gateway's PAID route even though the listing is gated.
414
+ *
415
+ * OFF by default. A gateway that leaves it unset behaves byte for byte as
416
+ * before and answers a probe with `probe-not-enabled` / `403` — which the
417
+ * oracle reports as UNVERIFIABLE (health-route-only), never as passing.
418
+ */
419
+ readonly probe?: GatewayProbeMode;
420
+ /**
421
+ * Operator-SIGNED rate bounds to enforce on the capability path, one per
422
+ * served serviceRef (metrik-contracts#41). Each entry must come from
423
+ * {@link verifySignedRateBounds} (or `loadSignedRateBoundsFile`) and name one
424
+ * of this gateway's service refs. A stream whose serviceRef has bounds is
425
+ * served only while its on-chain `ratePerSecond` lies inside them.
426
+ *
427
+ * Unset = no rate enforcement here (the previous behaviour); the reference
428
+ * server warns when it starts without them.
429
+ */
430
+ readonly signedRateBounds?: readonly SignedRateBounds[];
319
431
  }
320
432
 
321
433
  export interface CallerAuthRequestInput {
@@ -341,6 +453,11 @@ export class CallerAuthGateway {
341
453
  private readonly domain: InvocationCapabilityDomainInput;
342
454
  private readonly serviceRefs: ReadonlySet<string>;
343
455
  private readonly now: () => number;
456
+ private readonly probeHeaderName: string;
457
+ private readonly probeDomain: ProbeCapabilityDomainInput;
458
+ private readonly probeSigners: CachedOracleSignerSet | undefined;
459
+ private readonly probeRateLimiter: ProbeRateLimiter | undefined;
460
+ private readonly rateBounds: ReadonlyMap<string, SignedRateBounds>;
344
461
 
345
462
  constructor(config: CallerAuthGatewayConfig) {
346
463
  if (!isAddress(config.escrowAddress)) {
@@ -358,7 +475,29 @@ export class CallerAuthGateway {
358
475
  );
359
476
  }
360
477
 
478
+ const chainId = config.chainId ?? defaultNetwork().chain.id;
361
479
  this.serviceRefs = new Set(serviceRefs.map((value) => value.toLowerCase()));
480
+ const rateBounds = new Map<string, SignedRateBounds>();
481
+ for (const bounds of config.signedRateBounds ?? []) {
482
+ if (!isVerifiedSignedRateBounds(bounds)) {
483
+ throw new Error(
484
+ "signedRateBounds entries must come from verifySignedRateBounds() / loadSignedRateBoundsFile(); an unverified object is not operator-signed",
485
+ );
486
+ }
487
+ const key = bounds.serviceRef.toLowerCase();
488
+ if (!this.serviceRefs.has(key)) {
489
+ throw new Error(
490
+ `signed rate bounds for ${bounds.serviceRef} are not a service ref this gateway serves`,
491
+ );
492
+ }
493
+ if (rateBounds.has(key)) {
494
+ throw new Error(
495
+ `more than one set of signed rate bounds for ${bounds.serviceRef}`,
496
+ );
497
+ }
498
+ rateBounds.set(key, bounds);
499
+ }
500
+ this.rateBounds = rateBounds;
362
501
  this.headerName = (
363
502
  config.headerName ?? CAPABILITY_HEADER_NAME
364
503
  ).toLowerCase();
@@ -367,6 +506,7 @@ export class CallerAuthGateway {
367
506
  createSdkStreamReader({
368
507
  escrowAddress: config.escrowAddress,
369
508
  rpcUrl: config.rpcUrl as string,
509
+ chainId,
370
510
  });
371
511
  const corroborating =
372
512
  config.corroboratingStreamReaders ??
@@ -374,6 +514,7 @@ export class CallerAuthGateway {
374
514
  createSdkStreamReader({
375
515
  escrowAddress: config.escrowAddress,
376
516
  rpcUrl,
517
+ chainId,
377
518
  }),
378
519
  );
379
520
  if (corroborating.length > MAX_CORROBORATING_STREAM_READERS) {
@@ -393,23 +534,105 @@ export class CallerAuthGateway {
393
534
  config.revocationSet ?? new InMemoryStreamRevocationSet();
394
535
  this.nonceCache = config.nonceCache ?? new InMemoryNonceReplayCache();
395
536
  this.domain = {
396
- chainId: config.chainId ?? 84532,
537
+ chainId,
397
538
  verifyingContract: config.escrowAddress,
398
539
  };
399
540
  this.now = config.now ?? (() => Math.floor(Date.now() / 1000));
541
+
542
+ // Probe mode shares the chain binding with the invocation domain (same
543
+ // escrow, same chain) but a DIFFERENT EIP-712 domain name, so neither
544
+ // signature is ever valid as the other.
545
+ this.probeDomain = {
546
+ chainId,
547
+ verifyingContract: config.escrowAddress,
548
+ };
549
+ this.probeHeaderName = (
550
+ config.probe?.headerName ?? PROBE_CAPABILITY_HEADER_NAME
551
+ ).toLowerCase();
552
+ if (this.probeHeaderName === this.headerName) {
553
+ throw new Error(
554
+ "the probe capability header must differ from the invocation capability header",
555
+ );
556
+ }
557
+ if (config.probe === undefined) {
558
+ this.probeSigners = undefined;
559
+ this.probeRateLimiter = undefined;
560
+ } else {
561
+ const reader =
562
+ config.probe.signerSetReader ??
563
+ (config.rpcUrl === undefined
564
+ ? undefined
565
+ : createSdkOracleSignerSetReader({
566
+ escrowAddress: config.escrowAddress,
567
+ rpcUrl: config.rpcUrl,
568
+ chainId,
569
+ }));
570
+ if (reader === undefined) {
571
+ throw new Error(
572
+ "probe mode requires rpcUrl unless an explicit signerSetReader is supplied",
573
+ );
574
+ }
575
+ this.probeSigners = new CachedOracleSignerSet(
576
+ reader,
577
+ config.probe.signerCacheSeconds,
578
+ this.now,
579
+ );
580
+ this.probeRateLimiter =
581
+ config.probe.rateLimiter ??
582
+ new FixedWindowProbeRateLimiter(
583
+ config.probe.maxProbesPerMinute,
584
+ this.now,
585
+ );
586
+ }
587
+ }
588
+
589
+ /** Lower-cased header name that carries the buyer's invocation capability. */
590
+ get capabilityHeaderName(): string {
591
+ return this.headerName;
592
+ }
593
+
594
+ /** Lower-cased header name that carries an oracle probe capability. */
595
+ get probeCapabilityHeaderName(): string {
596
+ return this.probeHeaderName;
400
597
  }
401
598
 
402
599
  async authorize(
403
600
  request: CallerAuthRequestInput,
404
601
  ): Promise<CallerAuthDecision> {
405
602
  const headerValue = lookupHeader(request.headers, this.headerName);
406
- if (headerValue === undefined || headerValue.length === 0) {
603
+ const hasCapabilityHeader =
604
+ headerValue !== undefined && headerValue.length > 0;
605
+
606
+ // ── Oracle probe path ────────────────────────────────────────────────
607
+ // Evaluated FIRST and returning unconditionally, so an authorized probe can
608
+ // never fall through into the stream branch. That ordering is the
609
+ // structural guarantee that a probe consumes no budget: no `stream` is ever
610
+ // produced on this path.
611
+ const probeHeaderValue = lookupHeader(
612
+ request.headers,
613
+ this.probeHeaderName,
614
+ );
615
+ if (probeHeaderValue !== undefined && probeHeaderValue.length > 0) {
616
+ const paymentHeader = lookupHeader(
617
+ request.headers,
618
+ PAYMENT_HEADER_REFUSED_ON_PROBES,
619
+ );
620
+ if (
621
+ hasCapabilityHeader ||
622
+ (paymentHeader !== undefined && paymentHeader.trim().length > 0)
623
+ ) {
624
+ return deny("ambiguous-probe-mode", 400);
625
+ }
626
+ return this.authorizeProbe(probeHeaderValue, request);
627
+ }
628
+
629
+ if (!hasCapabilityHeader) {
407
630
  return deny("missing-capability-header", 403);
408
631
  }
409
632
 
410
633
  let capability: SignedInvocationCapability;
411
634
  try {
412
- capability = decodeCapabilityHeader(headerValue);
635
+ capability = decodeCapabilityHeader(headerValue as string);
413
636
  } catch (error) {
414
637
  if (error instanceof InvalidCapabilityHeaderError) {
415
638
  return deny("malformed-capability-header", 403);
@@ -501,6 +724,95 @@ export class CallerAuthGateway {
501
724
  return { authorized: true, stream: freshestView(views), capability };
502
725
  }
503
726
 
727
+ /**
728
+ * Authorize one oracle probe. Cheapest, most local gates first; the on-chain
729
+ * signer-set read is last so an unsigned or replayed flood cannot amplify
730
+ * into RPC load. Every failure mode is distinct and fail-closed.
731
+ */
732
+ private async authorizeProbe(
733
+ headerValue: string,
734
+ request: CallerAuthRequestInput,
735
+ ): Promise<CallerAuthDecision> {
736
+ const signerSet = this.probeSigners;
737
+ const rateLimiter = this.probeRateLimiter;
738
+ if (signerSet === undefined || rateLimiter === undefined) {
739
+ // Probe mode is off. Say so precisely: the oracle distinguishes
740
+ // "unverifiable here" from "the paid route failed".
741
+ return deny("probe-not-enabled", 403);
742
+ }
743
+
744
+ let capability: SignedProbeCapability;
745
+ try {
746
+ capability = decodeProbeCapabilityHeader(headerValue);
747
+ } catch (error) {
748
+ if (error instanceof InvalidProbeCapabilityHeaderError) {
749
+ return deny("malformed-probe-capability", 403);
750
+ }
751
+ throw error;
752
+ }
753
+
754
+ const nowSeconds = this.now();
755
+ if (nowSeconds >= capability.expiry) {
756
+ return deny("probe-capability-expired", 403);
757
+ }
758
+
759
+ let signer: Address;
760
+ try {
761
+ signer = await recoverProbeCapabilitySigner(
762
+ capability,
763
+ this.probeDomain,
764
+ capability.signature as Hex,
765
+ );
766
+ } catch {
767
+ return deny("probe-invalid-signature", 403);
768
+ }
769
+ if (signer.toLowerCase() !== capability.oracle.toLowerCase()) {
770
+ return deny("probe-invalid-signature", 403);
771
+ }
772
+
773
+ if (capability.method.toUpperCase() !== request.method.toUpperCase()) {
774
+ return deny("probe-method-mismatch", 403);
775
+ }
776
+ if (
777
+ hashProbePath(request.path).toLowerCase() !==
778
+ capability.pathHash.toLowerCase()
779
+ ) {
780
+ return deny("probe-path-mismatch", 403);
781
+ }
782
+ if (!this.serviceRefs.has(capability.serviceRef.toLowerCase())) {
783
+ return deny("probe-service-ref-mismatch", 403);
784
+ }
785
+
786
+ // Replay BEFORE the budget check: a captured capability replayed at volume
787
+ // must be rejected as a replay, not silently eat the honest probe budget.
788
+ // The SAME nonce cache as the buyer path, so a nonce can never be spent
789
+ // once as a probe and once as an invocation.
790
+ if (!this.nonceCache.consume(capability.nonce as Hex, capability.expiry)) {
791
+ return deny("probe-nonce-replayed", 403);
792
+ }
793
+
794
+ if (!rateLimiter.tryConsume()) {
795
+ return deny("probe-rate-limited", 429);
796
+ }
797
+
798
+ const signers = await signerSet.resolve();
799
+ if (signers === undefined) {
800
+ return deny("probe-signer-set-unavailable", 403);
801
+ }
802
+ if (!signers.has(signer.toLowerCase())) {
803
+ return deny("probe-signer-not-authorized", 403);
804
+ }
805
+
806
+ return {
807
+ authorized: true,
808
+ probe: {
809
+ oracle: signer,
810
+ serviceRef: capability.serviceRef as Hex,
811
+ expiry: capability.expiry,
812
+ },
813
+ };
814
+ }
815
+
504
816
  /** Returns the denial reason this single view produces, or `undefined` if it authorizes. */
505
817
  private evaluateView(
506
818
  stream: GatewayStreamView,
@@ -524,6 +836,17 @@ export class CallerAuthGateway {
524
836
  ) {
525
837
  return "service-ref-mismatch";
526
838
  }
839
+ // Operator-signed price band (metrik-contracts#41). Checked against the
840
+ // STREAM's serviceRef, which equals the capability's by the check above.
841
+ const bounds = this.rateBounds.get(stream.serviceRef.toLowerCase());
842
+ if (bounds !== undefined) {
843
+ if (stream.ratePerSecond === undefined) {
844
+ return "stream-rate-unknown";
845
+ }
846
+ if (rateOutsideBounds(stream.ratePerSecond, bounds) !== undefined) {
847
+ return "stream-rate-out-of-bounds";
848
+ }
849
+ }
527
850
  if (stream.status !== "active") {
528
851
  return "stream-not-active";
529
852
  }
@@ -569,7 +892,7 @@ function freshestView(views: readonly GatewayStreamView[]): GatewayStreamView {
569
892
 
570
893
  function deny(
571
894
  reason: CallerAuthDenialReason,
572
- httpStatus: 402 | 403,
895
+ httpStatus: 400 | 402 | 403 | 429,
573
896
  capability?: SignedInvocationCapability,
574
897
  ): CallerAuthDecision {
575
898
  return {