@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
@@ -1,445 +0,0 @@
1
- import {
2
- createServer,
3
- type IncomingMessage,
4
- type Server,
5
- type ServerResponse,
6
- } from "node:http";
7
- import type { AddressInfo } from "node:net";
8
-
9
- import { timingSafeEqual } from "node:crypto";
10
-
11
- import { isAddress, isHex, type Hex } from "viem";
12
-
13
- import {
14
- createOracleDeliveryOracle,
15
- DEFAULT_DELIVERY_FRESHNESS_SECONDS,
16
- DEFAULT_METRIK_ORACLE_URL,
17
- } from "./delivery-oracle.js";
18
- import { createRegistryResourceIndex } from "./resource-index.js";
19
- import {
20
- createX402PaymentSettler,
21
- createX402PaymentVerifier,
22
- relayerAddress,
23
- } from "./relayer.js";
24
- import {
25
- DEFAULT_SETTLEMENT_SCOPE,
26
- MetrikVerifiedDeliveryFacilitator,
27
- RelayerNotConfiguredError,
28
- type IndeterminatePolicy,
29
- type SettlementScope,
30
- } from "./verified-delivery-facilitator.js";
31
-
32
- /**
33
- * Reference HTTP server for Metrik's x402 facilitator: `POST /verify`,
34
- * `POST /settle`, `GET /supported` — the three endpoints the x402 facilitator
35
- * interface defines, with Metrik's verified-delivery gate wired into `/verify`.
36
- *
37
- * Point any x402 resource server at this with
38
- * `facilitator: { url: "https://<host>" }` and it becomes a resource server
39
- * whose payments stop when the seller stops delivering.
40
- */
41
-
42
- const MAX_BODY_BYTES = 256 * 1024;
43
-
44
- export interface MetrikFacilitatorServerConfig {
45
- readonly facilitator: MetrikVerifiedDeliveryFacilitator;
46
- /** Present only to report settlement availability on `/supported`. */
47
- readonly settlementEnabled: boolean;
48
- /**
49
- * Shared secret required on `POST /settle`.
50
- *
51
- * `/settle` spends the operator's gas: it makes the relayer sign and
52
- * broadcast. It is not a public read, and leaving it open on `0.0.0.0` makes
53
- * it a permissionless "make the relayer broadcast for me" endpoint. `/verify`
54
- * and `/supported` stay public — they cost nothing and are genuinely useful
55
- * to anyone.
56
- *
57
- * Omit ONLY for a verify-only server. `startMetrikFacilitatorServerFromEnv`
58
- * refuses to start a settling facilitator without one.
59
- */
60
- readonly settlementToken?: string;
61
- }
62
-
63
- export interface MetrikFacilitatorServer {
64
- readonly httpServer: Server;
65
- listen(port: number, host?: string): Promise<{ port: number; host: string }>;
66
- close(): Promise<void>;
67
- }
68
-
69
- export function createMetrikFacilitatorServer(
70
- config: MetrikFacilitatorServerConfig,
71
- ): MetrikFacilitatorServer {
72
- const httpServer = createServer((req, res) => {
73
- handle(req, res, config).catch((error) => {
74
- if (error instanceof RelayerNotConfiguredError) {
75
- writeJson(res, 503, {
76
- success: false,
77
- errorReason: "unexpected_settle_error",
78
- transaction: "",
79
- error: error.message,
80
- });
81
- return;
82
- }
83
- logError("unhandled request error", error);
84
- writeJson(res, 500, { error: "internal-error" });
85
- });
86
- });
87
-
88
- return {
89
- httpServer,
90
- listen: (port, host = "0.0.0.0") =>
91
- new Promise((resolve, reject) => {
92
- const onError = (error: Error) => reject(error);
93
- httpServer.once("error", onError);
94
- httpServer.listen(port, host, () => {
95
- httpServer.removeListener("error", onError);
96
- const address = httpServer.address() as AddressInfo;
97
- resolve({ port: address.port, host });
98
- });
99
- }),
100
- close: () =>
101
- new Promise<void>((resolve, reject) => {
102
- httpServer.close((error) => (error ? reject(error) : resolve()));
103
- }),
104
- };
105
- }
106
-
107
- async function handle(
108
- req: IncomingMessage,
109
- res: ServerResponse,
110
- config: MetrikFacilitatorServerConfig,
111
- ): Promise<void> {
112
- const url = new URL(req.url ?? "/", "http://localhost");
113
- const path = url.pathname.replace(/\/+$/, "") || "/";
114
-
115
- if (req.method === "GET" && (path === "/supported" || path === "/")) {
116
- writeJson(res, 200, {
117
- ...config.facilitator.supported(),
118
- settlementEnabled: config.settlementEnabled,
119
- });
120
- return;
121
- }
122
- if (req.method !== "POST") {
123
- writeJson(res, 405, { error: "method-not-allowed" });
124
- return;
125
- }
126
- if (path !== "/verify" && path !== "/settle") {
127
- writeJson(res, 404, { error: "not-found" });
128
- return;
129
- }
130
-
131
- // Authenticate BEFORE reading the body: an unauthenticated caller must not be
132
- // able to make this process do work, parse input, or reach the chain.
133
- if (path === "/settle" && !isAuthorizedSettle(req, config)) {
134
- writeJson(res, 401, {
135
- success: false,
136
- errorReason: "invalid_payment",
137
- transaction: "",
138
- error:
139
- "POST /settle requires the facilitator's bearer token. It spends the operator's gas.",
140
- });
141
- return;
142
- }
143
-
144
- let body: unknown;
145
- try {
146
- body = JSON.parse(await readBody(req));
147
- } catch {
148
- writeJson(res, 400, { error: "invalid-json" });
149
- return;
150
- }
151
-
152
- if (path === "/verify") {
153
- writeJson(res, 200, await config.facilitator.verify(body));
154
- return;
155
- }
156
- writeJson(res, 200, await config.facilitator.settle(body));
157
- }
158
-
159
- /**
160
- * Constant-time bearer check. A missing configured token denies, so a
161
- * misconfiguration can never silently open the endpoint.
162
- */
163
- function isAuthorizedSettle(
164
- req: IncomingMessage,
165
- config: MetrikFacilitatorServerConfig,
166
- ): boolean {
167
- const expected = config.settlementToken;
168
- if (expected === undefined || expected.length === 0) {
169
- return false;
170
- }
171
- const header = req.headers.authorization;
172
- if (typeof header !== "string") {
173
- return false;
174
- }
175
- const match = /^Bearer\s+(.+)$/i.exec(header.trim());
176
- if (match === null) {
177
- return false;
178
- }
179
- const presented = Buffer.from(match[1] as string, "utf8");
180
- const target = Buffer.from(expected, "utf8");
181
- // `timingSafeEqual` throws on a length mismatch, which would itself leak the
182
- // length, so compare a fixed-width digest-shaped pair instead: pad both to the
183
- // longer length and AND in the length equality.
184
- const width = Math.max(presented.length, target.length);
185
- const a = Buffer.alloc(width);
186
- const b = Buffer.alloc(width);
187
- presented.copy(a);
188
- target.copy(b);
189
- return timingSafeEqual(a, b) && presented.length === target.length;
190
- }
191
-
192
- async function readBody(req: IncomingMessage): Promise<string> {
193
- const chunks: Buffer[] = [];
194
- let size = 0;
195
- for await (const chunk of req) {
196
- const buffer = chunk as Buffer;
197
- size += buffer.length;
198
- if (size > MAX_BODY_BYTES) {
199
- throw new Error("request body too large");
200
- }
201
- chunks.push(buffer);
202
- }
203
- return Buffer.concat(chunks).toString("utf8");
204
- }
205
-
206
- function writeJson(res: ServerResponse, status: number, body: unknown): void {
207
- if (res.headersSent) {
208
- res.end();
209
- return;
210
- }
211
- res
212
- .writeHead(status, { "Content-Type": "application/json" })
213
- .end(JSON.stringify(body));
214
- }
215
-
216
- function logError(context: string, error: unknown): void {
217
- const message = error instanceof Error ? error.message : String(error);
218
- console.error(`[metrik-x402-facilitator] ${context}: ${message}`);
219
- }
220
-
221
- // ── Environment configuration ────────────────────────────────────────────────
222
-
223
- export interface MetrikFacilitatorEnvConfig {
224
- readonly host: string;
225
- readonly port: number;
226
- readonly chainId: number;
227
- readonly rpcUrl: string;
228
- readonly oracleUrl: string;
229
- readonly registryUrl?: string;
230
- readonly freshnessWindowSeconds: number;
231
- readonly indeterminatePolicy: IndeterminatePolicy;
232
- /** `undefined` means settlement is DISABLED — `/settle` will refuse loudly. */
233
- readonly relayerPrivateKey?: Hex;
234
- /** Required whenever a relayer is configured. Guards `POST /settle`. */
235
- readonly settlementToken?: string;
236
- /** Token contracts the relayer may be asked to call. Defaults to chain USDC. */
237
- readonly settlementAssetAllowlist?: readonly string[];
238
- readonly settlementScope: SettlementScope;
239
- }
240
-
241
- /**
242
- * Env contract:
243
- * - `METRIK_X402_RPC_URL` (required) — RPC for balance checks and, when a relayer
244
- * is configured, for broadcasting.
245
- * - `METRIK_X402_RELAYER_PRIVATE_KEY` (required unless
246
- * `METRIK_X402_ALLOW_VERIFY_ONLY=true`) — the relayer that broadcasts EIP-3009
247
- * transfers and pays gas.
248
- * - `METRIK_X402_ALLOW_VERIFY_ONLY` (optional) — explicit opt-in to booting a
249
- * verify-only facilitator. Without it, a missing relayer key is a STARTUP
250
- * FAILURE rather than a server that silently never settles.
251
- * - `METRIK_X402_CHAIN_ID` (optional) — default `84532`.
252
- * - `METRIK_X402_ORACLE_URL` (optional) — default the public Metrik oracle.
253
- * - `METRIK_X402_REGISTRY_URL` (optional) — listing source; default the oracle's
254
- * public credential-free registry.
255
- * - `METRIK_X402_FRESHNESS_SECONDS` (optional) — default
256
- * {@link DEFAULT_DELIVERY_FRESHNESS_SECONDS}.
257
- * - `METRIK_X402_INDETERMINATE_POLICY` (optional) — `allow` (default) or `deny`.
258
- * - `PORT` / `METRIK_X402_PORT` (optional) — default `8788`.
259
- * - `METRIK_X402_HOST` (optional) — default `0.0.0.0`.
260
- */
261
- export function parseMetrikFacilitatorEnvConfig(
262
- env: NodeJS.ProcessEnv = process.env,
263
- ): MetrikFacilitatorEnvConfig {
264
- const rpcUrl = env.METRIK_X402_RPC_URL;
265
- if (rpcUrl === undefined || rpcUrl.trim().length === 0) {
266
- throw new Error("METRIK_X402_RPC_URL is required (fail-closed)");
267
- }
268
-
269
- const rawKey = env.METRIK_X402_RELAYER_PRIVATE_KEY?.trim();
270
- const allowVerifyOnly = env.METRIK_X402_ALLOW_VERIFY_ONLY === "true";
271
- let relayerPrivateKey: Hex | undefined;
272
- if (rawKey !== undefined && rawKey.length > 0) {
273
- if (!isHex(rawKey, { strict: true }) || rawKey.length !== 66) {
274
- throw new Error(
275
- "METRIK_X402_RELAYER_PRIVATE_KEY must be a 32-byte 0x-prefixed hex private key",
276
- );
277
- }
278
- relayerPrivateKey = rawKey;
279
- } else if (!allowVerifyOnly) {
280
- throw new Error(
281
- "METRIK_X402_RELAYER_PRIVATE_KEY is required to settle payments. " +
282
- "This facilitator will NOT start without one, because a facilitator that " +
283
- "accepts /settle and never broadcasts is worse than one that is absent. " +
284
- "Set METRIK_X402_ALLOW_VERIFY_ONLY=true to deliberately run verify-only.",
285
- );
286
- }
287
-
288
- // `/settle` makes the relayer sign and pay gas. An open one is a
289
- // permissionless "broadcast for me" endpoint, so a settling facilitator
290
- // cannot boot without a token. Fail closed at startup, not at request time.
291
- const settlementToken = env.METRIK_X402_FACILITATOR_TOKEN?.trim();
292
- if (relayerPrivateKey !== undefined) {
293
- if (settlementToken === undefined || settlementToken.length < 32) {
294
- throw new Error(
295
- "METRIK_X402_FACILITATOR_TOKEN is required (at least 32 characters) whenever a " +
296
- "settlement relayer is configured. POST /settle spends the operator's gas and " +
297
- "binds 0.0.0.0; without a token anyone who can reach it can make the relayer " +
298
- "broadcast and pay for it.",
299
- );
300
- }
301
- }
302
-
303
- const rawAllowlist = (env.METRIK_X402_SETTLEMENT_ASSETS ?? "")
304
- .split(",")
305
- .map((value) => value.trim())
306
- .filter((value) => value.length > 0);
307
- for (const candidate of rawAllowlist) {
308
- if (!isAddress(candidate)) {
309
- throw new Error(
310
- `METRIK_X402_SETTLEMENT_ASSETS must contain only EVM addresses (got ${candidate})`,
311
- );
312
- }
313
- }
314
-
315
- const rawScope = env.METRIK_X402_SETTLEMENT_SCOPE ?? DEFAULT_SETTLEMENT_SCOPE;
316
- if (rawScope !== "tracked-resources" && rawScope !== "any-resource") {
317
- throw new Error(
318
- `invalid METRIK_X402_SETTLEMENT_SCOPE: ${rawScope} (tracked-resources|any-resource)`,
319
- );
320
- }
321
-
322
- const chainId = Number(env.METRIK_X402_CHAIN_ID ?? "84532");
323
- if (!Number.isInteger(chainId) || chainId <= 0) {
324
- throw new Error(
325
- `invalid METRIK_X402_CHAIN_ID: ${env.METRIK_X402_CHAIN_ID}`,
326
- );
327
- }
328
-
329
- const freshnessWindowSeconds = Number(
330
- env.METRIK_X402_FRESHNESS_SECONDS ??
331
- String(DEFAULT_DELIVERY_FRESHNESS_SECONDS),
332
- );
333
- if (!Number.isFinite(freshnessWindowSeconds) || freshnessWindowSeconds <= 0) {
334
- throw new Error(
335
- `invalid METRIK_X402_FRESHNESS_SECONDS: ${env.METRIK_X402_FRESHNESS_SECONDS}`,
336
- );
337
- }
338
-
339
- const rawPolicy = env.METRIK_X402_INDETERMINATE_POLICY ?? "allow";
340
- if (rawPolicy !== "allow" && rawPolicy !== "deny") {
341
- throw new Error(
342
- `invalid METRIK_X402_INDETERMINATE_POLICY: ${rawPolicy} (allow|deny)`,
343
- );
344
- }
345
-
346
- const port = Number(env.METRIK_X402_PORT ?? env.PORT ?? "8788");
347
- if (!Number.isInteger(port) || port < 0 || port > 65535) {
348
- throw new Error(`invalid port: ${env.METRIK_X402_PORT ?? env.PORT}`);
349
- }
350
-
351
- const registryUrl = env.METRIK_X402_REGISTRY_URL;
352
-
353
- return {
354
- host: env.METRIK_X402_HOST ?? "0.0.0.0",
355
- port,
356
- chainId,
357
- rpcUrl,
358
- oracleUrl: env.METRIK_X402_ORACLE_URL ?? DEFAULT_METRIK_ORACLE_URL,
359
- freshnessWindowSeconds,
360
- indeterminatePolicy: rawPolicy,
361
- settlementScope: rawScope,
362
- ...(rawAllowlist.length === 0
363
- ? {}
364
- : { settlementAssetAllowlist: rawAllowlist }),
365
- ...(registryUrl === undefined ? {} : { registryUrl }),
366
- ...(relayerPrivateKey === undefined ? {} : { relayerPrivateKey }),
367
- ...(settlementToken === undefined || settlementToken.length === 0
368
- ? {}
369
- : { settlementToken }),
370
- };
371
- }
372
-
373
- export function buildMetrikFacilitatorFromEnvConfig(
374
- config: MetrikFacilitatorEnvConfig,
375
- ): MetrikVerifiedDeliveryFacilitator {
376
- return new MetrikVerifiedDeliveryFacilitator({
377
- paymentVerifier: createX402PaymentVerifier({
378
- chainId: config.chainId,
379
- rpcUrl: config.rpcUrl,
380
- }),
381
- ...(config.relayerPrivateKey === undefined
382
- ? {}
383
- : {
384
- paymentSettler: createX402PaymentSettler({
385
- chainId: config.chainId,
386
- rpcUrl: config.rpcUrl,
387
- privateKey: config.relayerPrivateKey,
388
- }),
389
- }),
390
- resourceIndex: createRegistryResourceIndex(
391
- config.registryUrl === undefined
392
- ? {}
393
- : { discoverOptions: { registryUrl: config.registryUrl } },
394
- ),
395
- deliveryOracle: createOracleDeliveryOracle({
396
- oracleUrl: config.oracleUrl,
397
- freshnessWindowSeconds: config.freshnessWindowSeconds,
398
- }),
399
- indeterminatePolicy: config.indeterminatePolicy,
400
- chainId: config.chainId,
401
- settlementScope: config.settlementScope,
402
- ...(config.settlementAssetAllowlist === undefined
403
- ? {}
404
- : { settlementAssetAllowlist: config.settlementAssetAllowlist }),
405
- });
406
- }
407
-
408
- export async function startMetrikFacilitatorServerFromEnv(
409
- env: NodeJS.ProcessEnv = process.env,
410
- ): Promise<MetrikFacilitatorServer & { readonly boundPort: number }> {
411
- const config = parseMetrikFacilitatorEnvConfig(env);
412
- const facilitator = buildMetrikFacilitatorFromEnvConfig(config);
413
-
414
- if (config.relayerPrivateKey === undefined) {
415
- console.error(
416
- "[metrik-x402-facilitator] SETTLEMENT DISABLED — no METRIK_X402_RELAYER_PRIVATE_KEY. " +
417
- "/verify works; /settle will return 503 and broadcast nothing.",
418
- );
419
- } else {
420
- console.error(
421
- `[metrik-x402-facilitator] settlement relayer ${relayerAddress(
422
- config.relayerPrivateKey,
423
- )} — this address must hold native gas on chain ${config.chainId}.`,
424
- );
425
- }
426
- console.error(
427
- `[metrik-x402-facilitator] delivery gate: oracle=${config.oracleUrl} ` +
428
- `freshness=${config.freshnessWindowSeconds}s policy-on-no-evidence=${config.indeterminatePolicy}`,
429
- );
430
- console.error(
431
- `[metrik-x402-facilitator] settlement policy: scope=${config.settlementScope} ` +
432
- `assets=${facilitator.allowedSettlementAssets.join(",")} — the relayer will not be ` +
433
- "asked to call any other contract.",
434
- );
435
-
436
- const server = createMetrikFacilitatorServer({
437
- facilitator,
438
- settlementEnabled: config.relayerPrivateKey !== undefined,
439
- ...(config.settlementToken === undefined
440
- ? {}
441
- : { settlementToken: config.settlementToken }),
442
- });
443
- const { port } = await server.listen(config.port, config.host);
444
- return Object.assign(server, { boundPort: port });
445
- }
@@ -1,231 +0,0 @@
1
- import type { Address, Hex } from "viem";
2
- import { getAddress, isAddress, isHex } from "viem";
3
- // TYPE-ONLY import: erased at compile time, so the core package never gains a
4
- // runtime dependency on `x402`. The shape is still pinned to the real library —
5
- // `test/x402-facilitator-conformance.test.ts` parses what this module builds
6
- // with the REAL `PaymentRequirementsSchema`, so a drift in either direction
7
- // fails CI rather than shipping.
8
- import type { PaymentRequirements } from "x402/types";
9
-
10
- /**
11
- * Standard x402 `exact` payment requirements, built from an OPERATOR-SIGNED
12
- * Metrik listing.
13
- *
14
- * This is the "additional way in" described in `docs/x402.md`: the Metrik
15
- * verified-stream flow (`./facilitator.ts`) is unchanged and is still the only
16
- * path with recoverable escrow. What this module adds is the ability for a
17
- * seller's gateway to ALSO quote an ordinary, fully conformant x402 `exact`
18
- * price for a single call, so an agent holding nothing but `x402-fetch` can pay
19
- * without knowing Metrik exists.
20
- *
21
- * ## What is signed and what is not
22
- *
23
- * `payTo` is derived from the operator's EIP-712-signed service descriptor and
24
- * from nothing else. It is never read from configuration and never read from a
25
- * caller-supplied field, because `payTo` is the one field whose corruption
26
- * silently redirects money.
27
- *
28
- * The PRICE is not signed. The descriptor schema (`@absol-labs/shared`) carries
29
- * `minRatePerSecond` / `maxRatePerSecond` for metered streams and has no
30
- * per-call price field, so a per-call x402 amount can only come from the seller
31
- * running the gateway. That is legitimate — it is the seller's own price on the
32
- * seller's own gateway — but a buyer CANNOT cross-check it against the registry
33
- * the way they can cross-check `payTo`. Said plainly here so nobody assumes a
34
- * guarantee that does not exist.
35
- */
36
-
37
- /** The only x402 protocol version implemented by the published `x402` package. */
38
- export const X402_VERSION = 1 as const;
39
-
40
- /** The only x402 payment scheme implemented by the published `x402` package. */
41
- export const X402_EXACT_SCHEME = "exact" as const;
42
-
43
- /** x402 v1 uses bare network names, not CAIP-2. Metrik settles on Base only. */
44
- export const X402_NETWORK_BY_CHAIN_ID: Readonly<
45
- Record<number, "base" | "base-sepolia">
46
- > = Object.freeze({
47
- 8453: "base",
48
- 84532: "base-sepolia",
49
- });
50
-
51
- export interface X402AssetConfig {
52
- readonly address: Address;
53
- /** EIP-712 domain `name` of the asset, needed to sign EIP-3009 offline. */
54
- readonly eip712Name: string;
55
- /** EIP-712 domain `version` of the asset. */
56
- readonly eip712Version: string;
57
- readonly decimals: number;
58
- }
59
-
60
- /**
61
- * USDC per Base chain. The EIP-712 domain values are what a client needs to
62
- * sign `TransferWithAuthorization` WITHOUT an extra RPC round trip: the x402
63
- * client falls back to reading `version()` on chain only when
64
- * `PaymentRequirements.extra` omits it, so quoting them here removes a network
65
- * dependency from the payer's hot path.
66
- *
67
- * Base Sepolia values read from the live contract on 2026-09-18
68
- * (`name() == "USDC"`, `version() == "2"`).
69
- */
70
- export const X402_USDC_BY_CHAIN_ID: Readonly<Record<number, X402AssetConfig>> =
71
- Object.freeze({
72
- 8453: Object.freeze({
73
- address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as Address,
74
- eip712Name: "USD Coin",
75
- eip712Version: "2",
76
- decimals: 6,
77
- }),
78
- 84532: Object.freeze({
79
- address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" as Address,
80
- eip712Name: "USDC",
81
- eip712Version: "2",
82
- decimals: 6,
83
- }),
84
- });
85
-
86
- export class X402RequirementsError extends Error {
87
- constructor(message: string) {
88
- super(message);
89
- this.name = "X402RequirementsError";
90
- }
91
- }
92
-
93
- /**
94
- * The minimum a payee must prove. Both fields MUST be projected from the
95
- * operator-signed descriptor (see `../discovery/registry.ts`, which recovers the
96
- * EIP-712 signer and re-derives `serviceRef` from the signed content), never
97
- * from an unsigned registry column and never from a request.
98
- */
99
- export interface SignedListingPayee {
100
- readonly serviceRef: Hex;
101
- readonly operator: Address;
102
- }
103
-
104
- export interface BuildMetrikPaymentRequirementsInput {
105
- readonly listing: SignedListingPayee;
106
- /** Absolute URL of the paid resource, as the buyer will request it. */
107
- readonly resource: string;
108
- /** Price for ONE call, in the asset's base units (micro-USDC at 6 decimals). */
109
- readonly priceBaseUnits: bigint;
110
- readonly chainId: number;
111
- readonly description?: string;
112
- readonly mimeType?: string;
113
- /** How long the signed authorization stays valid. Default 60s. */
114
- readonly maxTimeoutSeconds?: number;
115
- readonly outputSchema?: Record<string, unknown>;
116
- /** Override the settlement asset (defaults to USDC for `chainId`). */
117
- readonly asset?: X402AssetConfig;
118
- }
119
-
120
- /** Default authorization validity window quoted to payers, in seconds. */
121
- export const DEFAULT_X402_MAX_TIMEOUT_SECONDS = 60;
122
-
123
- /**
124
- * Builds standard-`exact` `PaymentRequirements` for one call of a Metrik-listed
125
- * resource. Throws rather than emitting anything a conformant client would
126
- * reject or a payer could be harmed by.
127
- */
128
- export function buildMetrikPaymentRequirements(
129
- input: BuildMetrikPaymentRequirementsInput,
130
- ): PaymentRequirements {
131
- const network = X402_NETWORK_BY_CHAIN_ID[input.chainId];
132
- if (network === undefined) {
133
- throw new X402RequirementsError(
134
- `x402 v1 has no network name for chain ${input.chainId} (Metrik settles on Base: 8453, 84532)`,
135
- );
136
- }
137
- const asset = input.asset ?? X402_USDC_BY_CHAIN_ID[input.chainId];
138
- if (asset === undefined) {
139
- throw new X402RequirementsError(
140
- `no default settlement asset known for chain ${input.chainId}`,
141
- );
142
- }
143
- if (!isAddress(input.listing.operator)) {
144
- throw new X402RequirementsError(
145
- "listing.operator must be an EVM address (it becomes payTo)",
146
- );
147
- }
148
- if (
149
- !isHex(input.listing.serviceRef, { strict: true }) ||
150
- input.listing.serviceRef.length !== 66
151
- ) {
152
- throw new X402RequirementsError(
153
- "listing.serviceRef must be a 32-byte hex value",
154
- );
155
- }
156
- if (input.priceBaseUnits <= 0n) {
157
- throw new X402RequirementsError(
158
- "priceBaseUnits must be positive — a zero-priced x402 quote is not a payment",
159
- );
160
- }
161
- let resource: URL;
162
- try {
163
- resource = new URL(input.resource);
164
- } catch {
165
- throw new X402RequirementsError(`resource must be an absolute URL`);
166
- }
167
- const maxTimeoutSeconds =
168
- input.maxTimeoutSeconds ?? DEFAULT_X402_MAX_TIMEOUT_SECONDS;
169
- if (!Number.isInteger(maxTimeoutSeconds) || maxTimeoutSeconds <= 0) {
170
- throw new X402RequirementsError(
171
- "maxTimeoutSeconds must be a positive integer",
172
- );
173
- }
174
-
175
- return {
176
- scheme: X402_EXACT_SCHEME,
177
- network,
178
- maxAmountRequired: input.priceBaseUnits.toString(),
179
- resource: resource.toString(),
180
- description:
181
- input.description ??
182
- "One call to a Metrik-listed service. Metrik's facilitator refuses to settle this payment while its oracle reports the service as failing.",
183
- mimeType: input.mimeType ?? "application/json",
184
- payTo: getAddress(input.listing.operator),
185
- maxTimeoutSeconds,
186
- asset: getAddress(asset.address),
187
- extra: {
188
- // Required by the x402 `exact` EVM binding so the payer can sign the
189
- // EIP-3009 authorization offline.
190
- name: asset.eip712Name,
191
- version: asset.eip712Version,
192
- // ADVISORY ONLY. The facilitator NEVER reads this to decide anything: it
193
- // arrives inside a caller-supplied request body, so trusting it would let
194
- // a caller pick which service's delivery record gets checked. The
195
- // facilitator re-derives the service from `resource` against its own
196
- // registry view. Published purely so a client can look the listing up.
197
- metrikServiceRef: input.listing.serviceRef,
198
- },
199
- ...(input.outputSchema === undefined
200
- ? {}
201
- : { outputSchema: input.outputSchema }),
202
- };
203
- }
204
-
205
- /**
206
- * The HTTP `402` response body a resource server returns.
207
- *
208
- * NOTE on `error`: the shipped `x402ResponseSchema` types `error` as the closed
209
- * `ErrorReasons` enum, which no real x402 server obeys — `x402-express` and
210
- * friends put human-readable text there, and `x402-fetch` never reads the field
211
- * at all. Metrik follows the real-world behaviour (free text) rather than the
212
- * schema; `test/x402-facilitator-conformance.test.ts` pins that divergence so it
213
- * is a recorded decision and not an accident.
214
- */
215
- export interface X402PaymentRequiredBody {
216
- readonly x402Version: typeof X402_VERSION;
217
- readonly error: string;
218
- readonly accepts: readonly PaymentRequirements[];
219
- }
220
-
221
- export function buildX402PaymentRequiredBody(
222
- accepts: readonly PaymentRequirements[],
223
- error = "X-PAYMENT header is required",
224
- ): X402PaymentRequiredBody {
225
- if (accepts.length === 0) {
226
- throw new X402RequirementsError(
227
- "a 402 must advertise at least one PaymentRequirements",
228
- );
229
- }
230
- return { x402Version: X402_VERSION, error, accepts };
231
- }