@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,6 +1,6 @@
1
1
  import { booleanFlag, stringFlag } from "../args.js";
2
2
  import { packageVersion } from "../version.js";
3
- import { CDP_ENV_VARS, PRIVATE_KEY_ENV } from "../wallet.js";
3
+ import { CDP_ENV_VARS, PRIVATE_KEY_ENV, PRIVY_ENV_VARS } from "../wallet.js";
4
4
  import type { CommandContext, CommandSpec } from "../context.js";
5
5
 
6
6
  /** The published executable an MCP host starts. Declared in `package.json#bin`. */
@@ -82,6 +82,17 @@ export const mcpCommand: CommandSpec = {
82
82
  },
83
83
  };
84
84
 
85
+ // Honesty about the one wallet source this block cannot carry. The CLI
86
+ // resolves a Privy wallet (step 3), but the MCP server's own resolver,
87
+ // `parseAgentWalletEnv()`, reads a raw key or CDP and nothing else — there
88
+ // is no Privy path from the environment there. Printing Privy variables
89
+ // into a config the server would ignore is the failure this says out loud.
90
+ if (PRIVY_ENV_VARS.some((variable) => (env[variable] ?? "").length > 0)) {
91
+ out.note(
92
+ `note: this environment configures a Privy wallet, which the CLI can use but \`${MCP_BIN}\` cannot — its wallet resolver reads ${PRIVATE_KEY_ENV} or the CDP variables only. The block below therefore carries a key placeholder; give the MCP server a raw key or a CDP account.`,
93
+ );
94
+ }
95
+
85
96
  out.result(document);
86
97
  out.print(
87
98
  `Paste this into your MCP client's config (Claude Desktop: claude_desktop_config.json), then replace every <…> placeholder:`,
@@ -102,6 +113,11 @@ export const mcpCommand: CommandSpec = {
102
113
  * Wallet variables, shaped to match how the environment running this command is
103
114
  * already configured — CDP if that is what is set, a raw key otherwise.
104
115
  *
116
+ * Privy is deliberately absent: `parseAgentWalletEnv()`, which is what the MCP
117
+ * server resolves its wallet with, has no Privy branch. A Privy block here
118
+ * would print a config that cannot start the server. The command says so on
119
+ * stderr instead.
120
+ *
105
121
  * Values are ALWAYS placeholders. `metrik` prints derived addresses and never
106
122
  * echoes a credential, and a config block is the single most likely thing to be
107
123
  * pasted into a chat window or an issue.
@@ -116,7 +132,7 @@ function walletEnv(env: NodeJS.ProcessEnv): Record<string, string> {
116
132
  );
117
133
  }
118
134
  return {
119
- [PRIVATE_KEY_ENV]: "<0x… a funded Base Sepolia test key>",
135
+ [PRIVATE_KEY_ENV]: "<0x… a funded testnet key>",
120
136
  };
121
137
  }
122
138
 
@@ -8,15 +8,27 @@ import {
8
8
  parseUsdcAmount,
9
9
  } from "../amounts.js";
10
10
  import { assertTestnetRpc, parseBytes32 } from "../config.js";
11
+ import { writeClientOptions } from "../deps.js";
11
12
  import { CliError, configError } from "../errors.js";
13
+ import type { PrivySpendPolicy } from "../privy.js";
14
+ import {
15
+ assertWithinOperatorCaps,
16
+ assertWithinSpendPolicy,
17
+ } from "../spend-policy.js";
12
18
  import { cliMandateCaps, signCliMandate } from "../mandate.js";
13
19
  import { assertCanFundStream, assertCanPayGas } from "../preflight.js";
14
20
  import {
21
+ isPolicyEnforced,
15
22
  optionalCliWallet,
16
23
  resolveCliWallet,
17
24
  PRIVATE_KEY_ENV,
18
25
  } from "../wallet.js";
19
- import { resolveListing } from "../listings.js";
26
+ import { resolveListingRecord } from "../listings.js";
27
+ import {
28
+ listingRateBand,
29
+ rateBandRefusal,
30
+ SignedRateBoundsError,
31
+ } from "../../discovery/rate-band.js";
20
32
  import type { CommandContext, CommandSpec } from "../context.js";
21
33
 
22
34
  export const openCommand: CommandSpec = {
@@ -74,22 +86,29 @@ export const openCommand: CommandSpec = {
74
86
  // the intended call and says which parts (allowance, simulation, mandate
75
87
  // signature) could not be produced, rather than inventing them.
76
88
  const wallet = dryRun
77
- ? await optionalCliWallet(env, { note: out.note })
78
- : await resolveCliWallet(env, { note: out.note });
89
+ ? await optionalCliWallet(env, { note: out.note, rpcUrl: config.rpcUrl })
90
+ : await resolveCliWallet(env, { note: out.note, rpcUrl: config.rpcUrl });
79
91
  const account = wallet?.account ?? null;
80
92
  const read = deps.createReadClient(config);
81
93
  await assertTestnetRpc(config, () => read.getChainId());
82
94
  if (account !== null && !dryRun) {
83
95
  // Both balances, before anything is signed. Gas first: a wallet with no
84
96
  // ETH cannot send the approval that the USDC check exists to protect.
85
- await assertCanPayGas(read, account.address);
86
- await assertCanFundStream(read, account.address, budgetUsdc);
97
+ await assertCanPayGas(read, account.address, config.network);
98
+ await assertCanFundStream(
99
+ read,
100
+ account.address,
101
+ budgetUsdc,
102
+ config.network,
103
+ );
87
104
  }
88
105
 
89
106
  const operatorOverride = stringFlag(args, "operator");
90
- const listing = await resolveListing(context, serviceRef, {
107
+ const resolution = await resolveListingRecord(context, serviceRef, {
91
108
  required: operatorOverride === undefined,
92
109
  });
110
+ const listing = resolution.listing;
111
+ await assertRateInSignedBand(context, resolution, ratePerSecondUsdc);
93
112
  const operator = (operatorOverride ?? listing?.operator) as
94
113
  | `0x${string}`
95
114
  | undefined;
@@ -101,16 +120,41 @@ export const openCommand: CommandSpec = {
101
120
  }
102
121
 
103
122
  const spend = { operator, budgetUsdc, ratePerSecondUsdc, durationSeconds };
123
+ const mandateCaps = cliMandateCaps(spend, env);
124
+
125
+ // A policy-enforced wallet (Privy) is capped by its broker, not by a
126
+ // mandate this process signs for itself. Read the REAL ceiling and hold it
127
+ // to that instead — see `../spend-policy.ts` for why that substitution is
128
+ // the stronger of the two, and why re-adding the self-signed mandate here
129
+ // would both weaken it and break the wallet outright.
130
+ const policy: PrivySpendPolicy | null =
131
+ wallet !== null && isPolicyEnforced(wallet)
132
+ ? await wallet.readSpendPolicy()
133
+ : null;
134
+ if (policy !== null && !dryRun) {
135
+ assertWithinSpendPolicy(policy, spend);
136
+ // The operator's own standing ceiling still applies, and may be TIGHTER
137
+ // than the broker's. Enforced here because no mandate is signed to carry
138
+ // it — "policy-enforced" retires the self-signed mandate, not the caps
139
+ // the operator deliberately configured.
140
+ if (mandateCaps.source === "env") {
141
+ assertWithinOperatorCaps(mandateCaps.caps, spend);
142
+ }
143
+ }
144
+
104
145
  const mandate =
105
- account === null
146
+ account === null || policy !== null
106
147
  ? null
107
148
  : await signCliMandate(account, spend, {
108
149
  chainId: config.chainId,
109
150
  env,
110
151
  });
111
- const mandateCaps = cliMandateCaps(spend, env);
112
152
 
113
- const write = deps.createWriteClient(config, account);
153
+ const write = deps.createWriteClient(
154
+ config,
155
+ account,
156
+ writeClientOptions(wallet),
157
+ );
114
158
  const hire = {
115
159
  operator,
116
160
  serviceRef,
@@ -145,6 +189,19 @@ export const openCommand: CommandSpec = {
145
189
  durationSeconds,
146
190
  mandateSource: mandateCaps.source,
147
191
  mandateSigned: mandate !== null,
192
+ spendPolicy:
193
+ policy === null
194
+ ? null
195
+ : {
196
+ brokerUrl: policy.brokerUrl,
197
+ maxTotalUsdc: policy.maxTotalUsdc,
198
+ maxRatePerSecondUsdc: policy.maxRatePerSecondUsdc,
199
+ maxDurationSeconds: policy.maxDurationSeconds,
200
+ fits:
201
+ budgetUsdc <= policy.maxTotalUsdc &&
202
+ ratePerSecondUsdc <= policy.maxRatePerSecondUsdc &&
203
+ durationSeconds <= policy.maxDurationSeconds,
204
+ },
148
205
  to: preview.to,
149
206
  data: preview.data,
150
207
  chainId: preview.chainId,
@@ -180,10 +237,28 @@ export const openCommand: CommandSpec = {
180
237
  ["duration", formatDuration(durationSeconds)],
181
238
  [
182
239
  "mandate",
183
- mandate === null
184
- ? `not signed (no wallet); caps would come from ${mandateCaps.source}`
185
- : `self-signed, caps from ${mandateCaps.source}`,
240
+ mandate !== null
241
+ ? `self-signed, caps from ${mandateCaps.source}`
242
+ : policy !== null
243
+ ? "not signed — this wallet's ceiling is enforced by its broker, which is stronger"
244
+ : `not signed (no wallet); caps would come from ${mandateCaps.source}`,
186
245
  ],
246
+ ...(policy === null
247
+ ? []
248
+ : ([
249
+ [
250
+ "spend policy",
251
+ `${formatUsdc(policy.maxTotalUsdc)} total, ${policy.maxRatePerSecondUsdc} atomic/second, ${formatDuration(policy.maxDurationSeconds)} max — enforced by ${policy.brokerUrl}`,
252
+ ],
253
+ [
254
+ "fits policy",
255
+ budgetUsdc <= policy.maxTotalUsdc &&
256
+ ratePerSecondUsdc <= policy.maxRatePerSecondUsdc &&
257
+ durationSeconds <= policy.maxDurationSeconds
258
+ ? "yes"
259
+ : "NO — a real open would be refused before broadcasting",
260
+ ],
261
+ ] as [string, string][])),
187
262
  ["escrow", preview.to],
188
263
  [
189
264
  "usdc balance",
@@ -219,7 +294,7 @@ export const openCommand: CommandSpec = {
219
294
  );
220
295
  // Unreachable without a wallet: `resolveCliWallet` above throws when the
221
296
  // command is not a dry run. The narrowing keeps that a type-level fact.
222
- if (account === null || mandate === null) {
297
+ if (account === null) {
223
298
  throw configError(
224
299
  "this command signs a transaction and needs a wallet, and none was resolved",
225
300
  );
@@ -233,7 +308,10 @@ export const openCommand: CommandSpec = {
233
308
  budgetUsdc,
234
309
  ratePerSecondUsdc,
235
310
  maxDurationSeconds: durationSeconds,
236
- signedMandate: mandate.signed,
311
+ // `null` for a policy-enforced wallet: the ceiling was checked against
312
+ // the broker's own policy above, and the broker checks it again on the
313
+ // transaction it signs.
314
+ signedMandate: mandate === null ? null : mandate.signed,
237
315
  spentSoFarUsdc: 0n,
238
316
  nowSeconds: deps.nowSeconds(),
239
317
  });
@@ -282,6 +360,65 @@ export const openCommand: CommandSpec = {
282
360
  },
283
361
  };
284
362
 
363
+ /**
364
+ * Refuse a `--rate` outside the band the operator SIGNED, before anything is
365
+ * signed or broadcast — dry run included, because the rate is an invalid input
366
+ * for this service exactly like `--rate 0` is.
367
+ *
368
+ * The escrow accepts any non-zero rate; the seller's capability gateway does
369
+ * not, and answers every request on an out-of-band stream with
370
+ * `403 stream-rate-out-of-bounds`. Without this check the CLI locked a buyer's
371
+ * deposit in a stream that could never be served, and said nothing.
372
+ *
373
+ * - Listing found: its signed descriptor is re-verified and the band enforced.
374
+ * A descriptor that does not verify is a refusal (exit 4), never "no band".
375
+ * A legacy binding signs no commercial terms, so there is nothing to enforce.
376
+ * - No listing: only reachable with `--operator`, the CLI's existing,
377
+ * explicit escape hatch for a service the registry cannot vouch for. The
378
+ * check cannot run, so it is skipped LOUDLY rather than silently. Without
379
+ * `--operator` an unreachable registry or a missing listing has already
380
+ * failed the command before this point (`resolveListingRecord`).
381
+ */
382
+ async function assertRateInSignedBand(
383
+ context: CommandContext,
384
+ resolution: Awaited<ReturnType<typeof resolveListingRecord>>,
385
+ ratePerSecondUsdc: bigint,
386
+ ): Promise<void> {
387
+ const { out } = context;
388
+ if (resolution.record === null) {
389
+ out.note(
390
+ resolution.registryUnreachable
391
+ ? "warning: the operator-signed rate band could NOT be checked because the registry is unreachable. If --rate is outside the band the operator signed, the service's gateway will refuse every request while the deposit stays escrowed. Re-run when the registry answers to have it checked."
392
+ : "note: this serviceRef has no verified listing, so there is no operator-signed rate band to check --rate against.",
393
+ );
394
+ return;
395
+ }
396
+ let bounds;
397
+ try {
398
+ bounds = await listingRateBand(resolution.record);
399
+ } catch (error) {
400
+ if (error instanceof SignedRateBoundsError) {
401
+ throw new CliError(
402
+ `the listing's operator-signed rate band could not be verified: ${error.message}`,
403
+ {
404
+ exitCode: 4,
405
+ hint: "nothing was broadcast. The CLI will not open a stream against a rate band it cannot verify.",
406
+ cause: error,
407
+ },
408
+ );
409
+ }
410
+ throw error;
411
+ }
412
+ if (bounds === null) return;
413
+ const refusal = rateBandRefusal(ratePerSecondUsdc, bounds, "--rate");
414
+ if (refusal !== undefined) {
415
+ throw new CliError(refusal, {
416
+ exitCode: 2,
417
+ hint: "pick a --rate inside the signed band (`--units atomic` takes base units; see `metrik discover` for each listing's band).",
418
+ });
419
+ }
420
+ }
421
+
285
422
  /**
286
423
  * What the buyer is actually paying for.
287
424
  *