@absol-labs/agent 0.11.0 → 0.12.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/README.md +43 -51
  2. package/dist/capability/invocation-capability.d.ts +2 -2
  3. package/dist/capability/probe-capability.d.ts +225 -0
  4. package/dist/capability/probe-capability.d.ts.map +1 -0
  5. package/dist/capability/probe-capability.js +248 -0
  6. package/dist/capability/probe-capability.js.map +1 -0
  7. package/dist/cli/args.d.ts +11 -0
  8. package/dist/cli/args.d.ts.map +1 -1
  9. package/dist/cli/args.js +23 -0
  10. package/dist/cli/args.js.map +1 -1
  11. package/dist/cli/commands/claim.d.ts +32 -0
  12. package/dist/cli/commands/claim.d.ts.map +1 -1
  13. package/dist/cli/commands/claim.js +116 -28
  14. package/dist/cli/commands/claim.js.map +1 -1
  15. package/dist/cli/commands/close.d.ts.map +1 -1
  16. package/dist/cli/commands/close.js +35 -3
  17. package/dist/cli/commands/close.js.map +1 -1
  18. package/dist/cli/commands/invoke.d.ts +2 -0
  19. package/dist/cli/commands/invoke.d.ts.map +1 -1
  20. package/dist/cli/commands/invoke.js +2 -2
  21. package/dist/cli/commands/invoke.js.map +1 -1
  22. package/dist/cli/commands/mcp.d.ts.map +1 -1
  23. package/dist/cli/commands/mcp.js +15 -2
  24. package/dist/cli/commands/mcp.js.map +1 -1
  25. package/dist/cli/commands/open.d.ts.map +1 -1
  26. package/dist/cli/commands/open.js +58 -4
  27. package/dist/cli/commands/open.js.map +1 -1
  28. package/dist/cli/commands/pay.d.ts +34 -0
  29. package/dist/cli/commands/pay.d.ts.map +1 -0
  30. package/dist/cli/commands/pay.js +479 -0
  31. package/dist/cli/commands/pay.js.map +1 -0
  32. package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -1
  33. package/dist/cli/commands/reclaim-unverified.js +3 -2
  34. package/dist/cli/commands/reclaim-unverified.js.map +1 -1
  35. package/dist/cli/commands/reclaim.d.ts +23 -2
  36. package/dist/cli/commands/reclaim.d.ts.map +1 -1
  37. package/dist/cli/commands/reclaim.js +204 -20
  38. package/dist/cli/commands/reclaim.js.map +1 -1
  39. package/dist/cli/commands/refund.d.ts +31 -0
  40. package/dist/cli/commands/refund.d.ts.map +1 -0
  41. package/dist/cli/commands/refund.js +397 -0
  42. package/dist/cli/commands/refund.js.map +1 -0
  43. package/dist/cli/commands/status.d.ts.map +1 -1
  44. package/dist/cli/commands/status.js +21 -2
  45. package/dist/cli/commands/status.js.map +1 -1
  46. package/dist/cli/commands/streams.js +8 -12
  47. package/dist/cli/commands/streams.js.map +1 -1
  48. package/dist/cli/commands/watch.d.ts.map +1 -1
  49. package/dist/cli/commands/watch.js +4 -1
  50. package/dist/cli/commands/watch.js.map +1 -1
  51. package/dist/cli/config.d.ts +44 -26
  52. package/dist/cli/config.d.ts.map +1 -1
  53. package/dist/cli/config.js +88 -38
  54. package/dist/cli/config.js.map +1 -1
  55. package/dist/cli/context.d.ts +19 -0
  56. package/dist/cli/context.d.ts.map +1 -1
  57. package/dist/cli/deps.d.ts +53 -0
  58. package/dist/cli/deps.d.ts.map +1 -1
  59. package/dist/cli/deps.js +51 -1
  60. package/dist/cli/deps.js.map +1 -1
  61. package/dist/cli/errors.d.ts.map +1 -1
  62. package/dist/cli/errors.js +21 -1
  63. package/dist/cli/errors.js.map +1 -1
  64. package/dist/cli/escape-hatch.d.ts +6 -4
  65. package/dist/cli/escape-hatch.d.ts.map +1 -1
  66. package/dist/cli/escape-hatch.js +6 -2
  67. package/dist/cli/escape-hatch.js.map +1 -1
  68. package/dist/cli/listings.d.ts +18 -0
  69. package/dist/cli/listings.d.ts.map +1 -1
  70. package/dist/cli/listings.js +22 -12
  71. package/dist/cli/listings.js.map +1 -1
  72. package/dist/cli/preflight.d.ts +20 -14
  73. package/dist/cli/preflight.d.ts.map +1 -1
  74. package/dist/cli/preflight.js +34 -20
  75. package/dist/cli/preflight.js.map +1 -1
  76. package/dist/cli/privy.d.ts.map +1 -1
  77. package/dist/cli/privy.js +4 -2
  78. package/dist/cli/privy.js.map +1 -1
  79. package/dist/cli/progress.d.ts +13 -1
  80. package/dist/cli/progress.d.ts.map +1 -1
  81. package/dist/cli/progress.js +7 -1
  82. package/dist/cli/progress.js.map +1 -1
  83. package/dist/cli/reclaimability.d.ts +82 -0
  84. package/dist/cli/reclaimability.d.ts.map +1 -0
  85. package/dist/cli/reclaimability.js +80 -0
  86. package/dist/cli/reclaimability.js.map +1 -0
  87. package/dist/cli/run.d.ts +2 -1
  88. package/dist/cli/run.d.ts.map +1 -1
  89. package/dist/cli/run.js +62 -21
  90. package/dist/cli/run.js.map +1 -1
  91. package/dist/cli/scan-range.d.ts +2 -2
  92. package/dist/cli/scan-range.js +2 -2
  93. package/dist/cli/status.d.ts +106 -1
  94. package/dist/cli/status.d.ts.map +1 -1
  95. package/dist/cli/status.js +325 -44
  96. package/dist/cli/status.js.map +1 -1
  97. package/dist/cli/wallet.d.ts +2 -2
  98. package/dist/cli/wallet.d.ts.map +1 -1
  99. package/dist/cli/wallet.js +29 -5
  100. package/dist/cli/wallet.js.map +1 -1
  101. package/dist/discovery/rate-band.d.ts +74 -0
  102. package/dist/discovery/rate-band.d.ts.map +1 -0
  103. package/dist/discovery/rate-band.js +114 -0
  104. package/dist/discovery/rate-band.js.map +1 -0
  105. package/dist/escrow-generation.d.ts +81 -0
  106. package/dist/escrow-generation.d.ts.map +1 -0
  107. package/dist/escrow-generation.js +76 -0
  108. package/dist/escrow-generation.js.map +1 -0
  109. package/dist/frameworks/agentkit.d.ts +8 -4
  110. package/dist/frameworks/agentkit.d.ts.map +1 -1
  111. package/dist/frameworks/agentkit.js +27 -10
  112. package/dist/frameworks/agentkit.js.map +1 -1
  113. package/dist/frameworks/eliza.d.ts +3 -3
  114. package/dist/frameworks/eliza.d.ts.map +1 -1
  115. package/dist/frameworks/eliza.js +15 -6
  116. package/dist/frameworks/eliza.js.map +1 -1
  117. package/dist/frameworks/langchain.d.ts.map +1 -1
  118. package/dist/frameworks/langchain.js +8 -0
  119. package/dist/frameworks/langchain.js.map +1 -1
  120. package/dist/gateway/caller-auth-gateway.d.ts +83 -31
  121. package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
  122. package/dist/gateway/caller-auth-gateway.js +180 -27
  123. package/dist/gateway/caller-auth-gateway.js.map +1 -1
  124. package/dist/gateway/http-server.d.ts +128 -44
  125. package/dist/gateway/http-server.d.ts.map +1 -1
  126. package/dist/gateway/http-server.js +506 -155
  127. package/dist/gateway/http-server.js.map +1 -1
  128. package/dist/gateway/probe-auth.d.ts +112 -0
  129. package/dist/gateway/probe-auth.d.ts.map +1 -0
  130. package/dist/gateway/probe-auth.js +127 -0
  131. package/dist/gateway/probe-auth.js.map +1 -0
  132. package/dist/gateway/signed-rate-bounds.d.ts +51 -0
  133. package/dist/gateway/signed-rate-bounds.d.ts.map +1 -0
  134. package/dist/gateway/signed-rate-bounds.js +94 -0
  135. package/dist/gateway/signed-rate-bounds.js.map +1 -0
  136. package/dist/gateway/x402-opener-mode.d.ts +252 -0
  137. package/dist/gateway/x402-opener-mode.d.ts.map +1 -0
  138. package/dist/gateway/x402-opener-mode.js +772 -0
  139. package/dist/gateway/x402-opener-mode.js.map +1 -0
  140. package/dist/index.d.ts +6 -2
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +10 -5
  143. package/dist/index.js.map +1 -1
  144. package/dist/mcp/server.d.ts +64 -1
  145. package/dist/mcp/server.d.ts.map +1 -1
  146. package/dist/mcp/server.js +247 -37
  147. package/dist/mcp/server.js.map +1 -1
  148. package/dist/mcp/x402-tools.d.ts +109 -0
  149. package/dist/mcp/x402-tools.d.ts.map +1 -0
  150. package/dist/mcp/x402-tools.js +554 -0
  151. package/dist/mcp/x402-tools.js.map +1 -0
  152. package/dist/network.d.ts +119 -0
  153. package/dist/network.d.ts.map +1 -0
  154. package/dist/network.js +199 -0
  155. package/dist/network.js.map +1 -0
  156. package/dist/sdk/client.d.ts +13 -0
  157. package/dist/sdk/client.d.ts.map +1 -1
  158. package/dist/sdk/client.js +15 -0
  159. package/dist/sdk/client.js.map +1 -1
  160. package/dist/sdk/invoke.d.ts +4 -0
  161. package/dist/sdk/invoke.d.ts.map +1 -1
  162. package/dist/sdk/invoke.js +2 -5
  163. package/dist/sdk/invoke.js.map +1 -1
  164. package/dist/wallet/autonomous-wallet-broker.d.ts +3 -0
  165. package/dist/wallet/autonomous-wallet-broker.d.ts.map +1 -1
  166. package/dist/wallet/autonomous-wallet-broker.js +27 -23
  167. package/dist/wallet/autonomous-wallet-broker.js.map +1 -1
  168. package/dist/wallet/autonomous-wallet-protocol.d.ts +8 -2
  169. package/dist/wallet/autonomous-wallet-protocol.d.ts.map +1 -1
  170. package/dist/wallet/autonomous-wallet-protocol.js +9 -2
  171. package/dist/wallet/autonomous-wallet-protocol.js.map +1 -1
  172. package/dist/wallet/autonomous-wallet.d.ts.map +1 -1
  173. package/dist/wallet/autonomous-wallet.js +3 -2
  174. package/dist/wallet/autonomous-wallet.js.map +1 -1
  175. package/dist/wallet/broker-escrows.d.ts +222 -0
  176. package/dist/wallet/broker-escrows.d.ts.map +1 -0
  177. package/dist/wallet/broker-escrows.js +36 -0
  178. package/dist/wallet/broker-escrows.js.map +1 -0
  179. package/dist/wallet/lifecycle.d.ts +21 -5
  180. package/dist/wallet/lifecycle.d.ts.map +1 -1
  181. package/dist/wallet/lifecycle.js +32 -12
  182. package/dist/wallet/lifecycle.js.map +1 -1
  183. package/dist/wallet/privy-broker-server.d.ts.map +1 -1
  184. package/dist/wallet/privy-broker-server.js +31 -26
  185. package/dist/wallet/privy-broker-server.js.map +1 -1
  186. package/dist/wallet/privy-session-broker.d.ts +14 -5
  187. package/dist/wallet/privy-session-broker.d.ts.map +1 -1
  188. package/dist/wallet/privy-session-broker.js +29 -17
  189. package/dist/wallet/privy-session-broker.js.map +1 -1
  190. package/dist/wallet/privy-session-provider.d.ts +3 -1
  191. package/dist/wallet/privy-session-provider.d.ts.map +1 -1
  192. package/dist/wallet/privy-session-provider.js +5 -3
  193. package/dist/wallet/privy-session-provider.js.map +1 -1
  194. package/dist/wallet/provider.d.ts.map +1 -1
  195. package/dist/wallet/provider.js +15 -8
  196. package/dist/wallet/provider.js.map +1 -1
  197. package/dist/x402/facilitator.d.ts +3 -30
  198. package/dist/x402/facilitator.d.ts.map +1 -1
  199. package/dist/x402/facilitator.js +0 -27
  200. package/dist/x402/facilitator.js.map +1 -1
  201. package/dist/x402-buyer/guarded-fetch.d.ts +51 -0
  202. package/dist/x402-buyer/guarded-fetch.d.ts.map +1 -0
  203. package/dist/x402-buyer/guarded-fetch.js +147 -0
  204. package/dist/x402-buyer/guarded-fetch.js.map +1 -0
  205. package/dist/x402-buyer/index.d.ts +10 -0
  206. package/dist/x402-buyer/index.d.ts.map +1 -0
  207. package/dist/x402-buyer/index.js +10 -0
  208. package/dist/x402-buyer/index.js.map +1 -0
  209. package/dist/x402-buyer/pay.d.ts +157 -0
  210. package/dist/x402-buyer/pay.d.ts.map +1 -0
  211. package/dist/x402-buyer/pay.js +366 -0
  212. package/dist/x402-buyer/pay.js.map +1 -0
  213. package/dist/x402-opener/abi.d.ts +684 -0
  214. package/dist/x402-opener/abi.d.ts.map +1 -0
  215. package/dist/x402-opener/abi.js +880 -0
  216. package/dist/x402-opener/abi.js.map +1 -0
  217. package/dist/x402-opener/facilitator-entry.d.ts +3 -0
  218. package/dist/x402-opener/facilitator-entry.d.ts.map +1 -0
  219. package/dist/x402-opener/facilitator-entry.js +40 -0
  220. package/dist/x402-opener/facilitator-entry.js.map +1 -0
  221. package/dist/x402-opener/facilitator-server.d.ts +209 -0
  222. package/dist/x402-opener/facilitator-server.d.ts.map +1 -0
  223. package/dist/x402-opener/facilitator-server.js +762 -0
  224. package/dist/x402-opener/facilitator-server.js.map +1 -0
  225. package/dist/x402-opener/index.d.ts +14 -0
  226. package/dist/x402-opener/index.d.ts.map +1 -0
  227. package/dist/x402-opener/index.js +14 -0
  228. package/dist/x402-opener/index.js.map +1 -0
  229. package/dist/x402-opener/listing-resolver.d.ts +55 -0
  230. package/dist/x402-opener/listing-resolver.d.ts.map +1 -0
  231. package/dist/x402-opener/listing-resolver.js +125 -0
  232. package/dist/x402-opener/listing-resolver.js.map +1 -0
  233. package/dist/x402-opener/refund-relay.d.ts +266 -0
  234. package/dist/x402-opener/refund-relay.d.ts.map +1 -0
  235. package/dist/x402-opener/refund-relay.js +1341 -0
  236. package/dist/x402-opener/refund-relay.js.map +1 -0
  237. package/dist/x402-opener/relayer-queue.d.ts +14 -0
  238. package/dist/x402-opener/relayer-queue.d.ts.map +1 -0
  239. package/dist/x402-opener/relayer-queue.js +18 -0
  240. package/dist/x402-opener/relayer-queue.js.map +1 -0
  241. package/dist/x402-opener/scheme.d.ts +296 -0
  242. package/dist/x402-opener/scheme.d.ts.map +1 -0
  243. package/dist/x402-opener/scheme.js +1035 -0
  244. package/dist/x402-opener/scheme.js.map +1 -0
  245. package/dist/x402-opener/terms.d.ts +165 -0
  246. package/dist/x402-opener/terms.d.ts.map +1 -0
  247. package/dist/x402-opener/terms.js +304 -0
  248. package/dist/x402-opener/terms.js.map +1 -0
  249. package/dist/zktls/reclaim.d.ts +2 -2
  250. package/dist/zktls/t2-delivery-proof.d.ts +6 -6
  251. package/docs/agent-layer.md +3 -2
  252. package/docs/autonomous-privy-wallet.md +188 -0
  253. package/docs/cli.md +265 -34
  254. package/docs/langchain.md +5 -0
  255. package/docs/mcp-reference.md +91 -5
  256. package/docs/probe-capability.md +191 -0
  257. package/docs/quickstart.md +39 -4
  258. package/docs/threat-model.md +20 -0
  259. package/docs/x402.md +766 -593
  260. package/package.json +19 -17
  261. package/src/capability/probe-capability.ts +355 -0
  262. package/src/cli/args.ts +29 -0
  263. package/src/cli/commands/claim.ts +168 -33
  264. package/src/cli/commands/close.ts +36 -3
  265. package/src/cli/commands/invoke.ts +5 -2
  266. package/src/cli/commands/mcp.ts +18 -2
  267. package/src/cli/commands/open.ts +75 -4
  268. package/src/cli/commands/pay.ts +584 -0
  269. package/src/cli/commands/reclaim-unverified.ts +8 -2
  270. package/src/cli/commands/reclaim.ts +277 -19
  271. package/src/cli/commands/refund.ts +475 -0
  272. package/src/cli/commands/status.ts +28 -2
  273. package/src/cli/commands/streams.ts +10 -14
  274. package/src/cli/commands/watch.ts +11 -1
  275. package/src/cli/config.ts +139 -52
  276. package/src/cli/context.ts +16 -0
  277. package/src/cli/deps.ts +111 -1
  278. package/src/cli/errors.ts +24 -1
  279. package/src/cli/escape-hatch.ts +19 -5
  280. package/src/cli/listings.ts +43 -10
  281. package/src/cli/preflight.ts +36 -21
  282. package/src/cli/privy.ts +4 -2
  283. package/src/cli/progress.ts +21 -2
  284. package/src/cli/reclaimability.ts +154 -0
  285. package/src/cli/run.ts +77 -21
  286. package/src/cli/scan-range.ts +2 -2
  287. package/src/cli/status.ts +454 -44
  288. package/src/cli/wallet.ts +45 -8
  289. package/src/discovery/rate-band.ts +172 -0
  290. package/src/escrow-generation.ts +135 -0
  291. package/src/frameworks/agentkit.ts +45 -11
  292. package/src/frameworks/eliza.ts +27 -5
  293. package/src/frameworks/langchain.ts +14 -0
  294. package/src/gateway/caller-auth-gateway.ts +320 -68
  295. package/src/gateway/http-server.ts +717 -220
  296. package/src/gateway/probe-auth.ts +191 -0
  297. package/src/gateway/signed-rate-bounds.ts +164 -0
  298. package/src/gateway/x402-opener-mode.ts +1171 -0
  299. package/src/index.ts +82 -19
  300. package/src/mcp/server.ts +351 -42
  301. package/src/mcp/x402-tools.ts +785 -0
  302. package/src/network.ts +302 -0
  303. package/src/sdk/client.ts +25 -0
  304. package/src/sdk/invoke.ts +6 -5
  305. package/src/wallet/autonomous-wallet-broker.ts +46 -20
  306. package/src/wallet/autonomous-wallet-protocol.ts +11 -2
  307. package/src/wallet/autonomous-wallet.ts +4 -2
  308. package/src/wallet/broker-escrows.ts +66 -0
  309. package/src/wallet/lifecycle.ts +53 -18
  310. package/src/wallet/privy-broker-server.ts +35 -25
  311. package/src/wallet/privy-session-broker.ts +58 -25
  312. package/src/wallet/privy-session-provider.ts +7 -3
  313. package/src/wallet/provider.ts +20 -15
  314. package/src/x402/facilitator.ts +3 -43
  315. package/src/x402-buyer/guarded-fetch.ts +213 -0
  316. package/src/x402-buyer/index.ts +9 -0
  317. package/src/x402-buyer/pay.ts +555 -0
  318. package/src/x402-opener/abi.ts +881 -0
  319. package/src/x402-opener/facilitator-entry.ts +48 -0
  320. package/src/x402-opener/facilitator-server.ts +1114 -0
  321. package/src/x402-opener/index.ts +13 -0
  322. package/src/x402-opener/listing-resolver.ts +202 -0
  323. package/src/x402-opener/refund-relay.ts +1873 -0
  324. package/src/x402-opener/relayer-queue.ts +18 -0
  325. package/src/x402-opener/scheme.ts +1551 -0
  326. package/src/x402-opener/terms.ts +453 -0
  327. package/dist/gateway/x402-mode.d.ts +0 -123
  328. package/dist/gateway/x402-mode.d.ts.map +0 -1
  329. package/dist/gateway/x402-mode.js +0 -212
  330. package/dist/gateway/x402-mode.js.map +0 -1
  331. package/dist/x402/delivery-oracle.d.ts +0 -108
  332. package/dist/x402/delivery-oracle.d.ts.map +0 -1
  333. package/dist/x402/delivery-oracle.js +0 -174
  334. package/dist/x402/delivery-oracle.js.map +0 -1
  335. package/dist/x402/facilitator-server-entry.d.ts +0 -3
  336. package/dist/x402/facilitator-server-entry.d.ts.map +0 -1
  337. package/dist/x402/facilitator-server-entry.js +0 -27
  338. package/dist/x402/facilitator-server-entry.js.map +0 -1
  339. package/dist/x402/facilitator-server.d.ts +0 -73
  340. package/dist/x402/facilitator-server.d.ts.map +0 -1
  341. package/dist/x402/facilitator-server.js +0 -305
  342. package/dist/x402/facilitator-server.js.map +0 -1
  343. package/dist/x402/payment-requirements.d.ts +0 -106
  344. package/dist/x402/payment-requirements.d.ts.map +0 -1
  345. package/dist/x402/payment-requirements.js +0 -138
  346. package/dist/x402/payment-requirements.js.map +0 -1
  347. package/dist/x402/relayer.d.ts +0 -41
  348. package/dist/x402/relayer.d.ts.map +0 -1
  349. package/dist/x402/relayer.js +0 -64
  350. package/dist/x402/relayer.js.map +0 -1
  351. package/dist/x402/resource-index.d.ts +0 -75
  352. package/dist/x402/resource-index.d.ts.map +0 -1
  353. package/dist/x402/resource-index.js +0 -126
  354. package/dist/x402/resource-index.js.map +0 -1
  355. package/dist/x402/verified-delivery-facilitator.d.ts +0 -285
  356. package/dist/x402/verified-delivery-facilitator.d.ts.map +0 -1
  357. package/dist/x402/verified-delivery-facilitator.js +0 -423
  358. package/dist/x402/verified-delivery-facilitator.js.map +0 -1
  359. package/src/gateway/x402-mode.ts +0 -350
  360. package/src/x402/delivery-oracle.ts +0 -285
  361. package/src/x402/facilitator-server-entry.ts +0 -35
  362. package/src/x402/facilitator-server.ts +0 -445
  363. package/src/x402/payment-requirements.ts +0 -231
  364. package/src/x402/relayer.ts +0 -122
  365. package/src/x402/resource-index.ts +0 -228
  366. package/src/x402/verified-delivery-facilitator.ts +0 -744
@@ -0,0 +1,584 @@
1
+ import type { X402PayerSigner, X402QuoteSummary } from "@absol-labs/sdk/x402";
2
+
3
+ import {
4
+ DEFAULT_SERVER_MANDATE_TTL_SECONDS,
5
+ parseServerMandateCapsEnv,
6
+ signServerMandate,
7
+ type ServerMandateCaps,
8
+ } from "../../mandates/env.js";
9
+ import {
10
+ booleanFlag,
11
+ integerFlag,
12
+ jsonBodyFlag,
13
+ stringFlag,
14
+ stringFlags,
15
+ } from "../args.js";
16
+ import {
17
+ formatDuration,
18
+ formatUsdc,
19
+ parseAmountUnits,
20
+ parseDurationSeconds,
21
+ parseUsdcAmount,
22
+ } from "../amounts.js";
23
+ import { assertTestnetRpc } from "../config.js";
24
+ import { rpcTransport, type CliX402 } from "../deps.js";
25
+ import { CliError, configError, usageError } from "../errors.js";
26
+ import { usdcFundingHint } from "../../network.js";
27
+ import {
28
+ CDP_ENV_VARS,
29
+ PRIVATE_KEY_ENV,
30
+ isPrivyWalletSource,
31
+ resolveCliWallet,
32
+ } from "../wallet.js";
33
+ import { hasHeader, parseHeaders } from "./invoke.js";
34
+ import type { X402PostSignatureError } from "../../x402-buyer/pay.js";
35
+ import type { CommandContext, CommandSpec } from "../context.js";
36
+
37
+ /** Default stream window a request-sized mandate allows (one day). */
38
+ const DEFAULT_MAX_DURATION_SECONDS = 86_400;
39
+ /** Longest response body printed in human mode before it is truncated. */
40
+ const MAX_PRINTED_BODY_CHARS = 64 * 1024;
41
+
42
+ export const payCommand: CommandSpec = {
43
+ name: "pay",
44
+ summary:
45
+ "Pay an x402 endpoint into a recoverable escrow stream, under a spend mandate",
46
+ usage:
47
+ 'metrik pay <url> [--json \'{"text":"hi"}\' | --data <body>] [--method POST] [--header "K: V"] --max-amount <usdc> [--max-rate <usdc/s>] [--max-upfront <usdc>] [--max-duration <seconds>] [--min-duration <seconds>] [--operator <address>] [--units atomic]',
48
+ flags: [
49
+ "data",
50
+ "json-body",
51
+ "method",
52
+ "header",
53
+ "max-amount",
54
+ "max-rate",
55
+ "max-upfront",
56
+ "max-duration",
57
+ "min-duration",
58
+ "operator",
59
+ "units",
60
+ "allow-legacy-factory",
61
+ ],
62
+ // It moves the payer's USDC (an EIP-3009 authorization the facilitator
63
+ // settles into the escrow). The PAYER broadcasts nothing and needs no gas,
64
+ // but it is a fund-moving command and is gated like one.
65
+ signs: true,
66
+ // ...so the generic "Broadcasts a transaction" help would be false, and so
67
+ // would listing Privy: the command refuses a Privy wallet (its broker does
68
+ // not sign EIP-3009). Say what actually happens.
69
+ walletHelp: {
70
+ tag: "[signs a USDC payment]",
71
+ text:
72
+ "Broadcasts nothing and needs no gas: the payer signs an EIP-3009 USDC authorization (TransferWithAuthorization) " +
73
+ "that the seller's facilitator settles into an escrow stream. Requires a wallet holding USDC in the environment: " +
74
+ `${PRIVATE_KEY_ENV} or ${CDP_ENV_VARS.join(" + ")}. A Privy wallet cannot pay x402: its broker does not sign EIP-3009.`,
75
+ },
76
+ flagHelp: {
77
+ "max-upfront":
78
+ "--max-upfront <usdc> (cap on the NON-REFUNDABLE per-call part of an upfront-opener quote; default 0.10 USDC, 0 refuses any)",
79
+ data: "--data <body>",
80
+ "json-body": "--json <body> (alias: --json-body <body>)",
81
+ },
82
+ async run(context: CommandContext): Promise<void> {
83
+ const { args, config, env, out, deps } = context;
84
+ const x402 = requireX402(deps.x402);
85
+ const [urlArg] = args.positionals;
86
+ if (urlArg === undefined) {
87
+ throw usageError("metrik pay requires the <url> of an x402 endpoint");
88
+ }
89
+ const url = parseResourceUrl(urlArg);
90
+ const units = parseAmountUnits(stringFlag(args, "units"));
91
+ const maxAmountRaw = stringFlag(args, "max-amount");
92
+ const maxAmount =
93
+ maxAmountRaw === undefined
94
+ ? undefined
95
+ : parseUsdcAmount(maxAmountRaw, units, "max-amount");
96
+ const maxRateRaw = stringFlag(args, "max-rate");
97
+ const maxRate =
98
+ maxRateRaw === undefined
99
+ ? undefined
100
+ : parseUsdcAmount(maxRateRaw, units, "max-rate");
101
+ const maxUpfrontRaw = stringFlag(args, "max-upfront");
102
+ const maxUpfront =
103
+ maxUpfrontRaw === undefined
104
+ ? undefined
105
+ : parseUsdcAmount(maxUpfrontRaw, units, "max-upfront");
106
+ const maxDurationRaw = stringFlag(args, "max-duration");
107
+ const maxDuration =
108
+ maxDurationRaw === undefined
109
+ ? undefined
110
+ : parseDurationSeconds(maxDurationRaw);
111
+ const minDuration = integerFlag(args, "min-duration");
112
+ const operator = stringFlag(args, "operator");
113
+ if (operator !== undefined && !/^0x[0-9a-fA-F]{40}$/.test(operator)) {
114
+ throw usageError(`--operator must be an EVM address (got "${operator}")`);
115
+ }
116
+ if (maxAmount !== undefined && maxAmount <= 0n) {
117
+ throw usageError("--max-amount must be greater than zero");
118
+ }
119
+ if (maxRate !== undefined && maxRate <= 0n) {
120
+ throw usageError("--max-rate must be greater than zero");
121
+ }
122
+
123
+ const init = buildRequest(context);
124
+
125
+ // The mandate: an operator-set standing ceiling (METRIK_AGENT_MANDATE_*),
126
+ // or one sized to exactly this payment. Either way it is SIGNED and
127
+ // `checkMandate()` gates the quote before anything is signed for the
128
+ // seller. There is no unbounded path: without standing caps, --max-amount
129
+ // is required.
130
+ const envCaps = parseServerMandateCapsEnv(env);
131
+ if (envCaps === null && maxAmount === undefined) {
132
+ throw usageError(
133
+ "metrik pay needs a spend ceiling: pass --max-amount <usdc> (or configure a standing mandate with METRIK_AGENT_MANDATE_*)",
134
+ "no spend without a mandate: the seller picks the price, so the buyer must cap it. Nothing was sent.",
135
+ );
136
+ }
137
+ const caps = intersectCaps(envCaps, {
138
+ maxAmount,
139
+ maxRate,
140
+ maxDuration,
141
+ operator: operator as `0x${string}` | undefined,
142
+ });
143
+
144
+ const wallet = await resolveCliWallet(env, {
145
+ purpose: "x402-payment",
146
+ note: out.note,
147
+ rpcUrl: config.rpcUrl,
148
+ });
149
+ if (isPrivyWalletSource(wallet.source)) {
150
+ throw configError(
151
+ "metrik pay cannot sign with a Privy wallet",
152
+ "an x402 payment is an EIP-3009 TransferWithAuthorization signature, and the Privy broker's typed-data policy allows only an InvocationCapability. Pay with METRIK_AGENT_PRIVATE_KEY or a CDP wallet.",
153
+ );
154
+ }
155
+ const { account } = wallet;
156
+ const read = deps.createReadClient(config);
157
+ await assertTestnetRpc(config, () => read.getChainId());
158
+ // The payer needs USDC only — no gas: the facilitator's relayer submits
159
+ // the authorization. A zero balance is a knowable fact before signing.
160
+ const balance = await read.usdcBalance(account.address).catch(() => null);
161
+ if (balance === 0n) {
162
+ throw new CliError(
163
+ `wallet ${account.address} holds 0 USDC on ${config.network.name}, so it cannot pay`,
164
+ {
165
+ exitCode: 3,
166
+ hint: `${usdcFundingHint(config.network)}, then re-run. (An x402 payer needs USDC only; no gas.)`,
167
+ },
168
+ );
169
+ }
170
+
171
+ const signedMandate = await signServerMandate(account, caps, {
172
+ chainId: config.chainId,
173
+ });
174
+
175
+ out.note(
176
+ `paying ${url} (ceiling ${formatUsdc(maxAmount === undefined ? caps.maxPerStreamUsdc : maxAmount < caps.maxPerStreamUsdc ? maxAmount : caps.maxPerStreamUsdc)}, mandate from ${envCaps === null ? "this command" : "METRIK_AGENT_MANDATE_*"}) …`,
177
+ );
178
+ const result = await payOrExplain(x402, {
179
+ url,
180
+ init,
181
+ signer: account as unknown as X402PayerSigner,
182
+ network: config.network,
183
+ signedMandate,
184
+ spentSoFarUsdc: 0n,
185
+ nowSeconds: deps.nowSeconds(),
186
+ ...(maxAmount === undefined ? {} : { maxAmount }),
187
+ ...(maxRate === undefined ? {} : { maxRatePerSecond: maxRate }),
188
+ ...(minDuration === undefined ? {} : { minDurationSeconds: minDuration }),
189
+ ...(maxUpfront === undefined ? {} : { maxUpfrontAmount: maxUpfront }),
190
+ // The split, stated BEFORE anything is signed (the mandate approved the
191
+ // quote; the SDK signs right after this).
192
+ onApproved: ({ quote }) => {
193
+ out.note(describeQuoteSplit(quote));
194
+ },
195
+ sdkOptions: {
196
+ transport: rpcTransport(config),
197
+ // A LEGACY deployment's factory is refused by the SDK unless the
198
+ // buyer opts in: streams there can still be closed and reclaimed, but
199
+ // nothing new should open on a generation being retired by default.
200
+ ...(booleanFlag(args, "allow-legacy-factory")
201
+ ? { allowLegacyFactory: true }
202
+ : {}),
203
+ },
204
+ });
205
+
206
+ const bodyText = await result.response.text();
207
+ const opener = result.metrikStreamOpener;
208
+ const quote = result.quote;
209
+ const settlementTx = settlementTransaction(result.paymentResponse);
210
+ out.result({
211
+ paid: result.paid,
212
+ // The facilitator's settlement transaction, from the PAYMENT-RESPONSE
213
+ // header the seller relayed: the facilitator's relayer broadcast it, not
214
+ // the payer. Reported as-is; null when the header carries none.
215
+ settlementTransaction: settlementTx,
216
+ settlementTransactionSource:
217
+ settlementTx === null ? null : "PAYMENT-RESPONSE",
218
+ httpStatus: result.response.status,
219
+ contentType: result.response.headers.get("content-type"),
220
+ body: bodyText,
221
+ streamId: opener?.streamId ?? null,
222
+ opener: opener?.opener ?? null,
223
+ refundUrl: opener?.refundUrl ?? null,
224
+ escrow: opener?.escrow ?? null,
225
+ deposit: opener?.deposit ?? null,
226
+ openerKind: quote?.openerKind ?? null,
227
+ price: quote?.amount ?? null,
228
+ // The NON-REFUNDABLE part (paid to the seller at settlement) and the
229
+ // metered, refundable part (the most any refund can return).
230
+ upfrontAmount: quote?.upfrontAmount ?? null,
231
+ refundableAmount: quote?.refundableAmount ?? null,
232
+ upfrontFee: opener?.upfrontFee ?? null,
233
+ upfrontOnly: opener?.upfrontOnly ?? quote?.upfrontOnly ?? null,
234
+ // True for an upfront-only payment: nothing was escrowed, nothing to refund.
235
+ final: opener?.upfrontOnly === true,
236
+ ratePerSecond: opener?.ratePerSecond ?? null,
237
+ expiresAt: opener?.expiresAt ?? null,
238
+ operator: quote?.operator ?? null,
239
+ serviceRef: quote?.serviceRef ?? null,
240
+ minEarnSeconds: quote?.minEarnSeconds ?? null,
241
+ mandateSource: envCaps === null ? "request" : "env",
242
+ refundIsAutomatic: false,
243
+ });
244
+
245
+ if (!result.paid) {
246
+ out.print(
247
+ `The endpoint answered ${result.response.status} without asking for payment; nothing was signed or paid.`,
248
+ );
249
+ out.print(truncate(bodyText));
250
+ return;
251
+ }
252
+ out.print(`Paid. HTTP ${result.response.status}`);
253
+ out.print(truncate(bodyText));
254
+ out.print("");
255
+ if (opener !== null && opener.streamId === null) {
256
+ // Upfront-only: no stream exists, so there is no refund or reclaim to offer.
257
+ out.table([
258
+ ["opener", opener.opener],
259
+ ["payer", account.address],
260
+ ...splitRows(quote, opener.upfrontFee),
261
+ ...(settlementTx === null
262
+ ? []
263
+ : ([
264
+ [
265
+ "settlement tx",
266
+ `${settlementTx} (reported by the seller's facilitator in PAYMENT-RESPONSE; its relayer broadcast it)`,
267
+ ],
268
+ ] as const)),
269
+ ]);
270
+ out.print("");
271
+ out.print(
272
+ "This payment is FINAL: it was an upfront-only (minimum-charge) payment, the whole price was paid to the seller at settlement as a non-refundable per-call charge, and no escrow stream was opened. There is nothing to refund or reclaim.",
273
+ );
274
+ return;
275
+ }
276
+ if (opener !== null && opener.streamId !== null) {
277
+ out.table([
278
+ ["streamId", opener.streamId],
279
+ ["opener", opener.opener],
280
+ ["payer", account.address],
281
+ ...splitRows(quote, opener.upfrontFee),
282
+ ...(opener.deposit === null
283
+ ? []
284
+ : ([["deposit", formatUsdc(opener.deposit)]] as const)),
285
+ ...(opener.ratePerSecond === null
286
+ ? []
287
+ : ([
288
+ ["rate", `${formatUsdc(opener.ratePerSecond)} / verified second`],
289
+ ] as const)),
290
+ ...(quote?.minEarnSeconds === null ||
291
+ quote?.minEarnSeconds === undefined
292
+ ? []
293
+ : ([
294
+ [
295
+ "fully earned after",
296
+ `${formatDuration(Number(quote.minEarnSeconds))} of verified delivery`,
297
+ ],
298
+ ] as const)),
299
+ ...(opener.expiresAt === null
300
+ ? []
301
+ : ([
302
+ ["expires", new Date(opener.expiresAt * 1000).toISOString()],
303
+ ] as const)),
304
+ ["refund url", opener.refundUrl ?? "(the seller advertised no relay)"],
305
+ ...(settlementTx === null
306
+ ? []
307
+ : ([
308
+ [
309
+ "settlement tx",
310
+ `${settlementTx} (reported by the seller's facilitator in PAYMENT-RESPONSE; its relayer broadcast it)`,
311
+ ],
312
+ ] as const)),
313
+ ]);
314
+ out.print("");
315
+ // A stream on another (e.g. legacy) registered escrow is reached by
316
+ // naming that escrow; the CLI defaults to the network's current one.
317
+ const escrowFlag =
318
+ opener.escrow !== null &&
319
+ opener.escrow.toLowerCase() !== config.escrow.toLowerCase()
320
+ ? ` --escrow ${opener.escrow}`
321
+ : "";
322
+ out.print(
323
+ quote?.openerKind === "upfront" &&
324
+ quote.upfrontAmount !== null &&
325
+ quote.upfrontAmount > 0n
326
+ ? `Refunds are NOT automatic. The ${formatUsdc(quote.upfrontAmount)} upfront charge is final; of the ${formatUsdc(quote.refundableAmount ?? 0n)} escrowed, the seller earns only verified seconds, and whatever is unearned stays in the escrow until someone reclaims it for you:`
327
+ : "Refunds are NOT automatic. The seller earns only for verified seconds; whatever is unearned stays in the escrow until someone reclaims it for you:",
328
+ );
329
+ out.print(
330
+ opener.refundUrl === null
331
+ ? ` metrik reclaim ${opener.streamId}${escrowFlag} (anyone may send it; the refund always goes to the payer)`
332
+ : ` metrik refund ${opener.streamId} --refund-url ${opener.refundUrl}${escrowFlag} --close --reclaim`,
333
+ );
334
+ }
335
+ },
336
+ };
337
+
338
+ /**
339
+ * One line stating what a quote commits the payer to, printed before signing:
340
+ * for an upfront-opener quote, the NON-REFUNDABLE per-call part and the
341
+ * metered, refundable part; for a stream quote, that all of it is metered.
342
+ */
343
+ export function describeQuoteSplit(quote: X402QuoteSummary): string {
344
+ if (quote.openerKind === "upfront" && quote.upfrontAmount !== null) {
345
+ if (quote.upfrontOnly === true) {
346
+ return `quote: ${formatUsdc(quote.amount)}, ALL of it a NON-REFUNDABLE per-call charge (upfront-only: no stream is opened, nothing is metered, nothing can be refunded). Signing it.`;
347
+ }
348
+ return `quote: ${formatUsdc(quote.amount)} = ${formatUsdc(quote.upfrontAmount)} NON-REFUNDABLE per-call charge (paid to the seller at settlement) + ${formatUsdc(quote.refundableAmount ?? 0n)} metered and refundable (escrowed; the seller earns it only for verified seconds). Signing it.`;
349
+ }
350
+ return `quote: ${formatUsdc(quote.amount)}, all of it escrowed, metered and refundable (the seller earns it only for verified seconds). Signing it.`;
351
+ }
352
+
353
+ /** Table rows stating a paid quote's split (upfront quotes only). */
354
+ function splitRows(
355
+ quote: X402QuoteSummary | null,
356
+ upfrontFee: bigint | null,
357
+ ): (readonly [string, string])[] {
358
+ if (quote === null || quote.openerKind !== "upfront") return [];
359
+ return [
360
+ ["price", formatUsdc(quote.amount)],
361
+ [
362
+ "non-refundable",
363
+ `${formatUsdc(quote.upfrontAmount ?? 0n)} (per-call charge, paid to the seller at settlement${upfrontFee === null ? "" : `; protocol fee ${formatUsdc(upfrontFee)} as the seller reports it`})`,
364
+ ],
365
+ [
366
+ "refundable",
367
+ `${formatUsdc(quote.refundableAmount ?? 0n)} (metered; the most any refund can return)`,
368
+ ],
369
+ ];
370
+ }
371
+
372
+ /**
373
+ * The settlement transaction hash from the facilitator's `PAYMENT-RESPONSE`,
374
+ * when it carries a well-formed one. Anything else (absent, empty, not a
375
+ * 32-byte hash) is `null`: a malformed value is never printed as a hash.
376
+ */
377
+ export function settlementTransaction(
378
+ paymentResponse: { readonly transaction?: unknown } | null | undefined,
379
+ ): `0x${string}` | null {
380
+ const value = paymentResponse?.transaction;
381
+ return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value)
382
+ ? (value as `0x${string}`)
383
+ : null;
384
+ }
385
+
386
+ const minBig = (a: bigint, b: bigint | undefined): bigint =>
387
+ b !== undefined && b < a ? b : a;
388
+
389
+ /**
390
+ * The mandate this command signs: a standing `METRIK_AGENT_MANDATE_*` ceiling
391
+ * INTERSECTED with every flag (the most restrictive value wins), or, with no
392
+ * standing ceiling, one sized to the flags. A flag can only tighten a
393
+ * standing ceiling, never loosen or bypass it; an `--operator` outside a
394
+ * standing allowlist is refused outright.
395
+ */
396
+ export function intersectCaps(
397
+ envCaps: ServerMandateCaps | null,
398
+ flags: {
399
+ readonly maxAmount: bigint | undefined;
400
+ readonly maxRate: bigint | undefined;
401
+ readonly maxDuration: number | undefined;
402
+ readonly operator: `0x${string}` | undefined;
403
+ },
404
+ ): ServerMandateCaps {
405
+ if (envCaps === null) {
406
+ const amount = flags.maxAmount as bigint;
407
+ return {
408
+ maxPerStreamUsdc: amount,
409
+ maxTotalUsdc: amount,
410
+ // No --max-rate: the price is the only cap on how fast the seller can
411
+ // earn it. Reported as minEarnSeconds so the buyer can see it.
412
+ maxRatePerSecondUsdc: flags.maxRate ?? amount,
413
+ maxDurationSeconds: flags.maxDuration ?? DEFAULT_MAX_DURATION_SECONDS,
414
+ allowedOperators: flags.operator === undefined ? [] : [flags.operator],
415
+ ttlSeconds: DEFAULT_SERVER_MANDATE_TTL_SECONDS,
416
+ };
417
+ }
418
+ let allowedOperators = envCaps.allowedOperators;
419
+ if (flags.operator !== undefined) {
420
+ if (
421
+ allowedOperators.length > 0 &&
422
+ !allowedOperators.some(
423
+ (entry) => entry.toLowerCase() === flags.operator?.toLowerCase(),
424
+ )
425
+ ) {
426
+ throw new CliError(
427
+ `--operator ${flags.operator} is not in METRIK_AGENT_MANDATE_ALLOWED_OPERATORS`,
428
+ {
429
+ exitCode: 3,
430
+ hint: "a flag can only narrow the standing mandate, never widen it. Nothing was sent.",
431
+ },
432
+ );
433
+ }
434
+ allowedOperators = [flags.operator];
435
+ }
436
+ return {
437
+ maxPerStreamUsdc: minBig(envCaps.maxPerStreamUsdc, flags.maxAmount),
438
+ maxTotalUsdc: minBig(envCaps.maxTotalUsdc, flags.maxAmount),
439
+ maxRatePerSecondUsdc: minBig(envCaps.maxRatePerSecondUsdc, flags.maxRate),
440
+ maxDurationSeconds:
441
+ flags.maxDuration !== undefined &&
442
+ flags.maxDuration < envCaps.maxDurationSeconds
443
+ ? flags.maxDuration
444
+ : envCaps.maxDurationSeconds,
445
+ allowedOperators,
446
+ ttlSeconds: envCaps.ttlSeconds,
447
+ };
448
+ }
449
+
450
+ function requireX402(x402: CliX402 | undefined): CliX402 {
451
+ if (x402 === undefined) {
452
+ throw new CliError(
453
+ "this CLI build has no x402 dependency wired (CliDependencies.x402)",
454
+ { exitCode: 4 },
455
+ );
456
+ }
457
+ return x402;
458
+ }
459
+
460
+ function parseResourceUrl(raw: string): string {
461
+ let url: URL;
462
+ try {
463
+ url = new URL(raw);
464
+ } catch {
465
+ throw usageError(`<url> must be an absolute http(s) URL (got "${raw}")`);
466
+ }
467
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
468
+ throw usageError(`<url> must be http(s) (got "${url.protocol}")`);
469
+ }
470
+ return url.toString();
471
+ }
472
+
473
+ function buildRequest(context: CommandContext): RequestInit {
474
+ const { args } = context;
475
+ const data = stringFlag(args, "data");
476
+ const jsonValue = jsonBodyFlag(args);
477
+ const jsonAlias = stringFlag(args, "json-body");
478
+ if (jsonValue !== undefined && jsonAlias !== undefined) {
479
+ throw usageError(
480
+ "give the JSON body once: --json <body> or --json-body <body>",
481
+ );
482
+ }
483
+ const jsonBody = jsonValue ?? jsonAlias;
484
+ if (data !== undefined && jsonBody !== undefined) {
485
+ throw usageError("give either --data or --json <body>, not both");
486
+ }
487
+ if (jsonBody !== undefined) {
488
+ try {
489
+ JSON.parse(jsonBody);
490
+ } catch {
491
+ throw usageError("--json <body> must be valid JSON");
492
+ }
493
+ }
494
+ const body = jsonBody ?? data;
495
+ const method = (
496
+ stringFlag(args, "method") ?? (body === undefined ? "GET" : "POST")
497
+ ).toUpperCase();
498
+ const headers = parseHeaders(stringFlags(args, "header"));
499
+ if (jsonBody !== undefined && !hasHeader(headers, "content-type")) {
500
+ headers["content-type"] = "application/json";
501
+ }
502
+ for (const name of Object.keys(headers)) {
503
+ if (name === "payment-signature" || name === "x-payment") {
504
+ throw usageError(
505
+ `do not set the ${name} header: the payment is signed by this command, after the quote passes the mandate`,
506
+ );
507
+ }
508
+ }
509
+ return {
510
+ method,
511
+ headers,
512
+ ...(body === undefined ? {} : { body }),
513
+ };
514
+ }
515
+
516
+ function truncate(text: string): string {
517
+ return text.length <= MAX_PRINTED_BODY_CHARS
518
+ ? text
519
+ : `${text.slice(0, MAX_PRINTED_BODY_CHARS)}\n… (${text.length - MAX_PRINTED_BODY_CHARS} more characters; use --json for the whole body)`;
520
+ }
521
+
522
+ async function payOrExplain(
523
+ x402: CliX402,
524
+ input: Parameters<CliX402["pay"]>[0],
525
+ ): ReturnType<CliX402["pay"]> {
526
+ try {
527
+ return await x402.pay(input);
528
+ } catch (error) {
529
+ throw explainPayFailure(error);
530
+ }
531
+ }
532
+
533
+ /** Map the SDK's and the mandate's typed failures onto CLI exit codes. */
534
+ export function explainPayFailure(error: unknown): unknown {
535
+ if (!(error instanceof Error)) return error;
536
+ switch (error.name) {
537
+ case "X402MandateDeniedError":
538
+ return new CliError(error.message, {
539
+ exitCode: 3,
540
+ hint: "raise --max-amount / --max-rate / --max-duration (or METRIK_AGENT_MANDATE_*) only if you mean to pay that much, that fast, for that long.",
541
+ cause: error,
542
+ });
543
+ case "X402PaymentRefusedError":
544
+ return new CliError(error.message, {
545
+ exitCode: 3,
546
+ hint: /maxUpfrontAmount/.test(error.message)
547
+ ? "the quote's NON-REFUNDABLE upfront (per-call) charge is above your cap (--max-upfront, default 0.10 USDC). Raise --max-upfront only if you accept losing that much per call whatever is delivered. Nothing was signed."
548
+ : /exceeds maxAmount|exceeds maxRatePerSecond/.test(error.message)
549
+ ? "that ceiling is your spend mandate's (--max-amount / --max-rate or METRIK_AGENT_MANDATE_*), handed to the SDK as a second gate. Nothing was signed."
550
+ : "the quote failed one of the SDK's protections (wrong network or token, unknown factory, payTo != factory.predict(terms), invalid upfront terms, not an opener payment, ...). Nothing was signed.",
551
+ cause: error,
552
+ });
553
+ case "X402PostSignatureError": {
554
+ // The signature was handed out: never say "nothing moved" unless the
555
+ // chain proves the authorization can never be used.
556
+ const post = error as X402PostSignatureError;
557
+ if (post.upfrontOnly && post.state === "used") {
558
+ return new CliError(error.message, {
559
+ exitCode: 4,
560
+ hint: "the money LEFT the payer as an upfront-only (minimum-charge) payment: the whole price was a non-refundable per-call charge and no stream was opened, so there is nothing to refund or reclaim. Keep this output as evidence if the response was not delivered (see docs/x402.md).",
561
+ cause: error,
562
+ });
563
+ }
564
+ const recover =
565
+ post.streamId === null
566
+ ? ""
567
+ : ` Recover the unearned part: metrik refund ${post.streamId} --refund-url <the seller's relay> --close --reclaim (or metrik reclaim ${post.streamId}).`;
568
+ return new CliError(error.message, {
569
+ exitCode: 4,
570
+ hint:
571
+ post.state === "used"
572
+ ? `the money LEFT the payer.${post.streamId === null ? " Its stream could not be identified on-chain; keep this output as evidence (see docs/x402.md)." : ` It is in stream ${post.streamId} (opener ${post.opener}).`}${recover}`
573
+ : post.state === "possibly-used"
574
+ ? "whether the authorization was used could not be established: treat the payment as SPENT and check USDC.authorizationState before paying again (see docs/x402.md)."
575
+ : post.state === "unused-but-valid"
576
+ ? "it has not moved yet, but the seller holds a signature valid until its validBefore: treat it as spent, or cancel it with USDC.cancelAuthorization (needs gas). See docs/x402.md."
577
+ : "the authorization was never used and has expired: nothing moved.",
578
+ cause: error,
579
+ });
580
+ }
581
+ default:
582
+ return error;
583
+ }
584
+ }
@@ -14,6 +14,7 @@ import { writeClientOptions } from "../deps.js";
14
14
  import { isPolicyEnforced, resolveCliWallet } from "../wallet.js";
15
15
  import type { CommandContext, CommandSpec } from "../context.js";
16
16
  import type { CliReadClient } from "../deps.js";
17
+ import { escrowGenerationFor } from "../../escrow-generation.js";
17
18
 
18
19
  /**
19
20
  * The acknowledgement this command will not run without.
@@ -88,7 +89,7 @@ export const reclaimUnverifiedCommand: CommandSpec = {
88
89
  const { account } = wallet;
89
90
  const read = deps.createReadClient(config);
90
91
  await assertTestnetRpc(config, () => read.getChainId());
91
- await assertCanPayGas(read, account.address);
92
+ await assertCanPayGas(read, account.address, config.network);
92
93
 
93
94
  const stream = await read.getStreamV2(streamId);
94
95
  if (stream.reclaimed) {
@@ -107,7 +108,12 @@ export const reclaimUnverifiedCommand: CommandSpec = {
107
108
  // prevent.
108
109
  const grace = await readEscapeGrace(read);
109
110
  const now = await readChainNow(read, deps.nowSeconds());
110
- const window = escapeHatchWindow(stream, grace, now.seconds);
111
+ const window = escapeHatchWindow(
112
+ stream,
113
+ grace,
114
+ now.seconds,
115
+ escrowGenerationFor(config.chainId, config.escrow),
116
+ );
111
117
  if (!window.ready) {
112
118
  throw notReady(streamId, window, now.source);
113
119
  }