mppx 0.6.28 → 0.6.30

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 (272) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/Challenge.d.ts.map +1 -1
  3. package/dist/Challenge.js +16 -10
  4. package/dist/Challenge.js.map +1 -1
  5. package/dist/Method.d.ts +1 -1
  6. package/dist/Method.d.ts.map +1 -1
  7. package/dist/client/Methods.d.ts +1 -0
  8. package/dist/client/Methods.d.ts.map +1 -1
  9. package/dist/client/Methods.js +1 -0
  10. package/dist/client/Methods.js.map +1 -1
  11. package/dist/client/Mppx.d.ts +3 -3
  12. package/dist/client/Mppx.d.ts.map +1 -1
  13. package/dist/client/Mppx.js +1 -0
  14. package/dist/client/Mppx.js.map +1 -1
  15. package/dist/client/Transport.d.ts +10 -3
  16. package/dist/client/Transport.d.ts.map +1 -1
  17. package/dist/client/Transport.js +60 -7
  18. package/dist/client/Transport.js.map +1 -1
  19. package/dist/client/index.d.ts +1 -1
  20. package/dist/client/index.d.ts.map +1 -1
  21. package/dist/client/index.js +1 -1
  22. package/dist/client/index.js.map +1 -1
  23. package/dist/client/internal/Fetch.d.ts +3 -0
  24. package/dist/client/internal/Fetch.d.ts.map +1 -1
  25. package/dist/client/internal/Fetch.js +12 -20
  26. package/dist/client/internal/Fetch.js.map +1 -1
  27. package/dist/evm/Assets.d.ts +2 -0
  28. package/dist/evm/Assets.d.ts.map +1 -0
  29. package/dist/evm/Assets.js +2 -0
  30. package/dist/evm/Assets.js.map +1 -0
  31. package/dist/evm/Chains.d.ts +5 -0
  32. package/dist/evm/Chains.d.ts.map +1 -0
  33. package/dist/evm/Chains.js +5 -0
  34. package/dist/evm/Chains.js.map +1 -0
  35. package/dist/evm/Methods.d.ts +68 -0
  36. package/dist/evm/Methods.d.ts.map +1 -0
  37. package/dist/evm/Methods.js +28 -0
  38. package/dist/evm/Methods.js.map +1 -0
  39. package/dist/evm/Types.d.ts +143 -0
  40. package/dist/evm/Types.d.ts.map +1 -0
  41. package/dist/evm/Types.js +102 -0
  42. package/dist/evm/Types.js.map +1 -0
  43. package/dist/evm/client/Charge.d.ts +102 -0
  44. package/dist/evm/client/Charge.d.ts.map +1 -0
  45. package/dist/evm/client/Charge.js +141 -0
  46. package/dist/evm/client/Charge.js.map +1 -0
  47. package/dist/evm/client/Methods.d.ts +81 -0
  48. package/dist/evm/client/Methods.d.ts.map +1 -0
  49. package/dist/evm/client/Methods.js +16 -0
  50. package/dist/evm/client/Methods.js.map +1 -0
  51. package/dist/evm/client/index.d.ts +6 -0
  52. package/dist/evm/client/index.d.ts.map +1 -0
  53. package/dist/evm/client/index.js +6 -0
  54. package/dist/evm/client/index.js.map +1 -0
  55. package/dist/evm/index.d.ts +10 -0
  56. package/dist/evm/index.d.ts.map +1 -0
  57. package/dist/evm/index.js +9 -0
  58. package/dist/evm/index.js.map +1 -0
  59. package/dist/evm/server/Charge.d.ts +62 -0
  60. package/dist/evm/server/Charge.d.ts.map +1 -0
  61. package/dist/evm/server/Charge.js +172 -0
  62. package/dist/evm/server/Charge.js.map +1 -0
  63. package/dist/evm/server/Methods.d.ts +80 -0
  64. package/dist/evm/server/Methods.d.ts.map +1 -0
  65. package/dist/evm/server/Methods.js +16 -0
  66. package/dist/evm/server/Methods.js.map +1 -0
  67. package/dist/evm/server/index.d.ts +6 -0
  68. package/dist/evm/server/index.d.ts.map +1 -0
  69. package/dist/evm/server/index.js +6 -0
  70. package/dist/evm/server/index.js.map +1 -0
  71. package/dist/index.d.ts +2 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +2 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/internal/HeaderCodec.d.ts +18 -0
  76. package/dist/internal/HeaderCodec.d.ts.map +1 -0
  77. package/dist/internal/HeaderCodec.js +31 -0
  78. package/dist/internal/HeaderCodec.js.map +1 -0
  79. package/dist/middlewares/elysia.d.ts.map +1 -1
  80. package/dist/middlewares/elysia.js +2 -3
  81. package/dist/middlewares/elysia.js.map +1 -1
  82. package/dist/middlewares/express.js +2 -1
  83. package/dist/middlewares/express.js.map +1 -1
  84. package/dist/proxy/internal/Headers.d.ts.map +1 -1
  85. package/dist/proxy/internal/Headers.js +11 -1
  86. package/dist/proxy/internal/Headers.js.map +1 -1
  87. package/dist/proxy/services/openai.d.ts.map +1 -1
  88. package/dist/proxy/services/openai.js +2 -0
  89. package/dist/proxy/services/openai.js.map +1 -1
  90. package/dist/server/Methods.d.ts +1 -0
  91. package/dist/server/Methods.d.ts.map +1 -1
  92. package/dist/server/Methods.js +1 -0
  93. package/dist/server/Methods.js.map +1 -1
  94. package/dist/server/Mppx.d.ts.map +1 -1
  95. package/dist/server/Mppx.js +90 -12
  96. package/dist/server/Mppx.js.map +1 -1
  97. package/dist/server/Transport.d.ts +10 -0
  98. package/dist/server/Transport.d.ts.map +1 -1
  99. package/dist/server/Transport.js +9 -0
  100. package/dist/server/Transport.js.map +1 -1
  101. package/dist/server/index.d.ts +1 -1
  102. package/dist/server/index.d.ts.map +1 -1
  103. package/dist/server/index.js +1 -1
  104. package/dist/server/index.js.map +1 -1
  105. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  106. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  107. package/dist/stripe/server/internal/html.gen.js +1 -1
  108. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  109. package/dist/tempo/client/ChannelOps.d.ts +3 -3
  110. package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
  111. package/dist/tempo/client/ChannelOps.js +13 -6
  112. package/dist/tempo/client/ChannelOps.js.map +1 -1
  113. package/dist/tempo/client/Charge.d.ts.map +1 -1
  114. package/dist/tempo/client/Charge.js +8 -5
  115. package/dist/tempo/client/Charge.js.map +1 -1
  116. package/dist/tempo/client/Methods.d.ts +0 -1
  117. package/dist/tempo/client/Methods.d.ts.map +1 -1
  118. package/dist/tempo/client/Session.d.ts +2 -4
  119. package/dist/tempo/client/Session.d.ts.map +1 -1
  120. package/dist/tempo/client/Session.js +10 -12
  121. package/dist/tempo/client/Session.js.map +1 -1
  122. package/dist/tempo/client/SessionManager.d.ts +3 -3
  123. package/dist/tempo/client/SessionManager.d.ts.map +1 -1
  124. package/dist/tempo/client/SessionManager.js +1 -1
  125. package/dist/tempo/client/SessionManager.js.map +1 -1
  126. package/dist/tempo/internal/account.d.ts +5 -0
  127. package/dist/tempo/internal/account.d.ts.map +1 -1
  128. package/dist/tempo/internal/account.js +8 -0
  129. package/dist/tempo/internal/account.js.map +1 -1
  130. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  131. package/dist/tempo/internal/fee-payer.js +5 -2
  132. package/dist/tempo/internal/fee-payer.js.map +1 -1
  133. package/dist/tempo/server/Charge.d.ts.map +1 -1
  134. package/dist/tempo/server/Charge.js +23 -1
  135. package/dist/tempo/server/Charge.js.map +1 -1
  136. package/dist/tempo/server/Session.d.ts.map +1 -1
  137. package/dist/tempo/server/Session.js +13 -12
  138. package/dist/tempo/server/Session.js.map +1 -1
  139. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  140. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  141. package/dist/tempo/server/internal/html.gen.js +1 -1
  142. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  143. package/dist/tempo/session/Chain.d.ts +2 -0
  144. package/dist/tempo/session/Chain.d.ts.map +1 -1
  145. package/dist/tempo/session/Chain.js +8 -8
  146. package/dist/tempo/session/Chain.js.map +1 -1
  147. package/dist/tempo/session/Voucher.d.ts +4 -3
  148. package/dist/tempo/session/Voucher.d.ts.map +1 -1
  149. package/dist/tempo/session/Voucher.js +71 -44
  150. package/dist/tempo/session/Voucher.js.map +1 -1
  151. package/dist/tempo/session/Ws.d.ts.map +1 -1
  152. package/dist/tempo/session/Ws.js +15 -0
  153. package/dist/tempo/session/Ws.js.map +1 -1
  154. package/dist/tempo/subscription/KeyAuthorization.d.ts +2 -2
  155. package/dist/x402/Assets.d.ts +29 -0
  156. package/dist/x402/Assets.d.ts.map +1 -0
  157. package/dist/x402/Assets.js +46 -0
  158. package/dist/x402/Assets.js.map +1 -0
  159. package/dist/x402/Header.d.ts +14 -0
  160. package/dist/x402/Header.d.ts.map +1 -0
  161. package/dist/x402/Header.js +18 -0
  162. package/dist/x402/Header.js.map +1 -0
  163. package/dist/x402/Types.d.ts +289 -0
  164. package/dist/x402/Types.d.ts.map +1 -0
  165. package/dist/x402/Types.js +139 -0
  166. package/dist/x402/Types.js.map +1 -0
  167. package/dist/x402/client/Exact.d.ts +38 -0
  168. package/dist/x402/client/Exact.d.ts.map +1 -0
  169. package/dist/x402/client/Exact.js +141 -0
  170. package/dist/x402/client/Exact.js.map +1 -0
  171. package/dist/x402/index.d.ts +6 -0
  172. package/dist/x402/index.d.ts.map +1 -0
  173. package/dist/x402/index.js +6 -0
  174. package/dist/x402/index.js.map +1 -0
  175. package/dist/x402/internal/ChallengeBrand.d.ts +5 -0
  176. package/dist/x402/internal/ChallengeBrand.d.ts.map +1 -0
  177. package/dist/x402/internal/ChallengeBrand.js +13 -0
  178. package/dist/x402/internal/ChallengeBrand.js.map +1 -0
  179. package/dist/x402/internal/RouteBinding.d.ts +8 -0
  180. package/dist/x402/internal/RouteBinding.d.ts.map +1 -0
  181. package/dist/x402/internal/RouteBinding.js +12 -0
  182. package/dist/x402/internal/RouteBinding.js.map +1 -0
  183. package/dist/x402/server/EvmCharge.d.ts +50 -0
  184. package/dist/x402/server/EvmCharge.d.ts.map +1 -0
  185. package/dist/x402/server/EvmCharge.js +301 -0
  186. package/dist/x402/server/EvmCharge.js.map +1 -0
  187. package/dist/x402/server/Facilitator.d.ts +12 -0
  188. package/dist/x402/server/Facilitator.d.ts.map +1 -0
  189. package/dist/x402/server/Facilitator.js +42 -0
  190. package/dist/x402/server/Facilitator.js.map +1 -0
  191. package/package.json +41 -21
  192. package/src/Challenge.test.ts +54 -0
  193. package/src/Challenge.ts +17 -10
  194. package/src/Method.ts +1 -1
  195. package/src/client/Methods.ts +1 -0
  196. package/src/client/Mppx.ts +4 -3
  197. package/src/client/Transport.test.ts +165 -30
  198. package/src/client/Transport.ts +76 -8
  199. package/src/client/index.ts +1 -1
  200. package/src/client/internal/Fetch.test.ts +31 -2
  201. package/src/client/internal/Fetch.ts +26 -19
  202. package/src/evm/Assets.ts +1 -0
  203. package/src/evm/Chains.ts +5 -0
  204. package/src/evm/Methods.ts +44 -0
  205. package/src/evm/PublicInterface.test-d.ts +114 -0
  206. package/src/evm/Types.ts +140 -0
  207. package/src/evm/client/Charge.test.ts +99 -0
  208. package/src/evm/client/Charge.ts +198 -0
  209. package/src/evm/client/Methods.ts +19 -0
  210. package/src/evm/client/index.ts +5 -0
  211. package/src/evm/index.ts +14 -0
  212. package/src/evm/server/Charge.test.ts +199 -0
  213. package/src/evm/server/Charge.ts +283 -0
  214. package/src/evm/server/Methods.ts +22 -0
  215. package/src/evm/server/index.ts +5 -0
  216. package/src/index.ts +2 -0
  217. package/src/internal/HeaderCodec.ts +36 -0
  218. package/src/middlewares/elysia.test.ts +25 -0
  219. package/src/middlewares/elysia.ts +1 -2
  220. package/src/middlewares/express.test.ts +28 -0
  221. package/src/middlewares/express.ts +1 -1
  222. package/src/middlewares/hono.test.ts +138 -2
  223. package/src/middlewares/nextjs.test.ts +22 -0
  224. package/src/proxy/internal/Headers.test.ts +20 -0
  225. package/src/proxy/internal/Headers.ts +12 -1
  226. package/src/proxy/services/openai.test.ts +57 -1
  227. package/src/proxy/services/openai.ts +2 -0
  228. package/src/server/Methods.ts +1 -0
  229. package/src/server/Mppx.test.ts +244 -1
  230. package/src/server/Mppx.ts +124 -11
  231. package/src/server/NodeListener.test.ts +28 -1
  232. package/src/server/Transport.test.ts +19 -0
  233. package/src/server/Transport.ts +20 -0
  234. package/src/server/index.ts +1 -1
  235. package/src/stripe/server/internal/html.gen.ts +1 -1
  236. package/src/tempo/AccessKeyAuthorization.test.ts +231 -0
  237. package/src/tempo/client/ChannelOps.test.ts +61 -7
  238. package/src/tempo/client/ChannelOps.ts +18 -7
  239. package/src/tempo/client/Charge.test.ts +126 -0
  240. package/src/tempo/client/Charge.ts +10 -6
  241. package/src/tempo/client/Session.test.ts +130 -1
  242. package/src/tempo/client/Session.ts +12 -19
  243. package/src/tempo/client/SessionManager.test.ts +69 -2
  244. package/src/tempo/client/SessionManager.ts +4 -4
  245. package/src/tempo/internal/account.ts +13 -0
  246. package/src/tempo/internal/fee-payer.test.ts +32 -2
  247. package/src/tempo/internal/fee-payer.ts +6 -2
  248. package/src/tempo/server/Charge.test.ts +69 -0
  249. package/src/tempo/server/Charge.ts +32 -0
  250. package/src/tempo/server/Session.test.ts +30 -0
  251. package/src/tempo/server/Session.ts +15 -16
  252. package/src/tempo/server/internal/html.gen.ts +1 -1
  253. package/src/tempo/session/Chain.test.ts +4 -4
  254. package/src/tempo/session/Chain.ts +10 -6
  255. package/src/tempo/session/Voucher.test.ts +230 -1
  256. package/src/tempo/session/Voucher.ts +96 -48
  257. package/src/tempo/session/Ws.test.ts +71 -0
  258. package/src/tempo/session/Ws.ts +13 -0
  259. package/src/x402/Assets.ts +65 -0
  260. package/src/x402/Exact.e2e.test.ts +448 -0
  261. package/src/x402/Header.test.ts +73 -0
  262. package/src/x402/Header.ts +34 -0
  263. package/src/x402/PublicInterface.test-d.ts +39 -0
  264. package/src/x402/Types.ts +248 -0
  265. package/src/x402/client/Exact.test.ts +180 -0
  266. package/src/x402/client/Exact.ts +198 -0
  267. package/src/x402/index.ts +5 -0
  268. package/src/x402/internal/ChallengeBrand.ts +14 -0
  269. package/src/x402/internal/RouteBinding.ts +18 -0
  270. package/src/x402/server/EvmCharge.ts +394 -0
  271. package/src/x402/server/Facilitator.test.ts +111 -0
  272. package/src/x402/server/Facilitator.ts +56 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mppx",
3
3
  "type": "module",
4
- "version": "0.6.28",
4
+ "version": "0.6.30",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "files": [
@@ -11,93 +11,113 @@
11
11
  ],
12
12
  "exports": {
13
13
  ".": {
14
- "types": "./dist/index.d.ts",
15
14
  "src": "./src/index.ts",
15
+ "types": "./dist/index.d.ts",
16
16
  "default": "./dist/index.js"
17
17
  },
18
18
  "./cli": {
19
- "types": "./dist/cli/config.d.ts",
20
19
  "src": "./src/cli/config.ts",
20
+ "types": "./dist/cli/config.d.ts",
21
21
  "default": "./dist/cli/config.js"
22
22
  },
23
23
  "./cli/plugins": {
24
- "types": "./dist/cli/plugins/index.d.ts",
25
24
  "src": "./src/cli/plugins/index.ts",
25
+ "types": "./dist/cli/plugins/index.d.ts",
26
26
  "default": "./dist/cli/plugins/index.js"
27
27
  },
28
28
  "./client": {
29
- "types": "./dist/client/index.d.ts",
30
29
  "src": "./src/client/index.ts",
30
+ "types": "./dist/client/index.d.ts",
31
31
  "default": "./dist/client/index.js"
32
32
  },
33
33
  "./discovery": {
34
- "types": "./dist/discovery/index.d.ts",
35
34
  "src": "./src/discovery/index.ts",
35
+ "types": "./dist/discovery/index.d.ts",
36
36
  "default": "./dist/discovery/index.js"
37
37
  },
38
+ "./evm": {
39
+ "src": "./src/evm/index.ts",
40
+ "types": "./dist/evm/index.d.ts",
41
+ "default": "./dist/evm/index.js"
42
+ },
43
+ "./evm/client": {
44
+ "src": "./src/evm/client/index.ts",
45
+ "types": "./dist/evm/client/index.d.ts",
46
+ "default": "./dist/evm/client/index.js"
47
+ },
48
+ "./evm/server": {
49
+ "src": "./src/evm/server/index.ts",
50
+ "types": "./dist/evm/server/index.d.ts",
51
+ "default": "./dist/evm/server/index.js"
52
+ },
38
53
  "./mcp-sdk/client": {
39
- "types": "./dist/mcp-sdk/client/index.d.ts",
40
54
  "src": "./src/mcp-sdk/client/index.ts",
55
+ "types": "./dist/mcp-sdk/client/index.d.ts",
41
56
  "default": "./dist/mcp-sdk/client/index.js"
42
57
  },
43
58
  "./mcp-sdk/server": {
44
- "types": "./dist/mcp-sdk/server/index.d.ts",
45
59
  "src": "./src/mcp-sdk/server/index.ts",
60
+ "types": "./dist/mcp-sdk/server/index.d.ts",
46
61
  "default": "./dist/mcp-sdk/server/index.js"
47
62
  },
48
63
  "./proxy": {
49
- "types": "./dist/proxy/index.d.ts",
50
64
  "src": "./src/proxy/index.ts",
65
+ "types": "./dist/proxy/index.d.ts",
51
66
  "default": "./dist/proxy/index.js"
52
67
  },
53
68
  "./server": {
54
- "types": "./dist/server/index.d.ts",
55
69
  "src": "./src/server/index.ts",
70
+ "types": "./dist/server/index.d.ts",
56
71
  "default": "./dist/server/index.js"
57
72
  },
58
73
  "./stripe": {
59
- "types": "./dist/stripe/index.d.ts",
60
74
  "src": "./src/stripe/index.ts",
75
+ "types": "./dist/stripe/index.d.ts",
61
76
  "default": "./dist/stripe/index.js"
62
77
  },
63
78
  "./stripe/client": {
64
- "types": "./dist/stripe/client/index.d.ts",
65
79
  "src": "./src/stripe/client/index.ts",
80
+ "types": "./dist/stripe/client/index.d.ts",
66
81
  "default": "./dist/stripe/client/index.js"
67
82
  },
68
83
  "./stripe/server": {
69
- "types": "./dist/stripe/server/index.d.ts",
70
84
  "src": "./src/stripe/server/index.ts",
85
+ "types": "./dist/stripe/server/index.d.ts",
71
86
  "default": "./dist/stripe/server/index.js"
72
87
  },
88
+ "./x402": {
89
+ "src": "./src/x402/index.ts",
90
+ "types": "./dist/x402/index.d.ts",
91
+ "default": "./dist/x402/index.js"
92
+ },
73
93
  "./tempo": {
74
- "types": "./dist/tempo/index.d.ts",
75
94
  "src": "./src/tempo/index.ts",
95
+ "types": "./dist/tempo/index.d.ts",
76
96
  "default": "./dist/tempo/index.js"
77
97
  },
78
98
  "./html": {
79
- "types": "./dist/Html.d.ts",
80
99
  "src": "./src/Html.ts",
100
+ "types": "./dist/Html.d.ts",
81
101
  "default": "./dist/Html.js"
82
102
  },
83
103
  "./hono": {
84
- "types": "./dist/middlewares/hono.d.ts",
85
104
  "src": "./src/middlewares/hono.ts",
105
+ "types": "./dist/middlewares/hono.d.ts",
86
106
  "default": "./dist/middlewares/hono.js"
87
107
  },
88
108
  "./express": {
89
- "types": "./dist/middlewares/express.d.ts",
90
109
  "src": "./src/middlewares/express.ts",
110
+ "types": "./dist/middlewares/express.d.ts",
91
111
  "default": "./dist/middlewares/express.js"
92
112
  },
93
113
  "./nextjs": {
94
- "types": "./dist/middlewares/nextjs.d.ts",
95
114
  "src": "./src/middlewares/nextjs.ts",
115
+ "types": "./dist/middlewares/nextjs.d.ts",
96
116
  "default": "./dist/middlewares/nextjs.js"
97
117
  },
98
118
  "./elysia": {
99
- "types": "./dist/middlewares/elysia.d.ts",
100
119
  "src": "./src/middlewares/elysia.ts",
120
+ "types": "./dist/middlewares/elysia.d.ts",
101
121
  "default": "./dist/middlewares/elysia.js"
102
122
  }
103
123
  },
@@ -106,7 +126,7 @@
106
126
  "elysia": ">=1",
107
127
  "express": ">=5",
108
128
  "hono": ">=4.12.18",
109
- "viem": ">=2.50.4"
129
+ "viem": ">=2.51.0"
110
130
  },
111
131
  "peerDependenciesMeta": {
112
132
  "@modelcontextprotocol/sdk": {
@@ -124,7 +144,7 @@
124
144
  },
125
145
  "dependencies": {
126
146
  "incur": "^0.4.5",
127
- "ox": "0.14.22",
147
+ "ox": "0.14.24",
128
148
  "zod": "^4.4.3"
129
149
  },
130
150
  "repository": {
@@ -453,6 +453,60 @@ describe('serialize', () => {
453
453
  expect(header).toContain('digest="sha-256=abc"')
454
454
  expect(header).toContain('expires="2025-01-06T12:00:00Z"')
455
455
  })
456
+
457
+ test.each([
458
+ {
459
+ description: 'Plain payment description',
460
+ escaped: 'Plain payment description',
461
+ label: 'plain values',
462
+ },
463
+ {
464
+ description: 'Pay "premium"',
465
+ escaped: 'Pay \\"premium\\"',
466
+ label: 'double quotes',
467
+ },
468
+ {
469
+ description: 'Path C:\\tempo\\api',
470
+ escaped: 'Path C:\\\\tempo\\\\api',
471
+ label: 'backslashes',
472
+ },
473
+ {
474
+ description: 'Pay "premium" path C:\\tempo\\api',
475
+ escaped: 'Pay \\"premium\\" path C:\\\\tempo\\\\api',
476
+ label: 'mixed quotes and backslashes',
477
+ },
478
+ {
479
+ description: 'Ends with slash \\',
480
+ escaped: 'Ends with slash \\\\',
481
+ label: 'trailing backslash',
482
+ },
483
+ ])('behavior: escapes quoted-string values: $label', ({ description, escaped }) => {
484
+ const challenge = Challenge.from({
485
+ id: 'abc123',
486
+ realm: 'api.example.com',
487
+ method: 'tempo',
488
+ intent: 'charge',
489
+ request: { amount: '1000000' },
490
+ description,
491
+ })
492
+
493
+ const header = Challenge.serialize(challenge)
494
+ expect(header).toContain(`description="${escaped}"`)
495
+ expect(Challenge.deserialize(header).description).toBe(description)
496
+ })
497
+
498
+ test('error: rejects CRLF in quoted-string values', () => {
499
+ const challenge = Challenge.from({
500
+ id: 'abc123',
501
+ realm: 'api.example.com',
502
+ method: 'tempo',
503
+ intent: 'charge',
504
+ request: { amount: '1000000' },
505
+ description: 'Line one\r\nLine two',
506
+ })
507
+
508
+ expect(() => Challenge.serialize(challenge)).toThrow('Invalid quoted-string value.')
509
+ })
456
510
  })
457
511
 
458
512
  describe('deserialize', () => {
package/src/Challenge.ts CHANGED
@@ -294,23 +294,30 @@ export declare namespace fromMethod {
294
294
  */
295
295
  export function serialize(challenge: Challenge): string {
296
296
  const parts = [
297
- `id="${challenge.id}"`,
298
- `realm="${challenge.realm}"`,
299
- `method="${challenge.method}"`,
300
- `intent="${challenge.intent}"`,
301
- `request="${PaymentRequest.serialize(challenge.request)}"`,
297
+ authParam('id', challenge.id),
298
+ authParam('realm', challenge.realm),
299
+ authParam('method', challenge.method),
300
+ authParam('intent', challenge.intent),
301
+ authParam('request', PaymentRequest.serialize(challenge.request)),
302
302
  ]
303
303
 
304
- if (challenge.description !== undefined) parts.push(`description="${challenge.description}"`)
305
- if (challenge.digest !== undefined) parts.push(`digest="${challenge.digest}"`)
306
- if (challenge.expires !== undefined) parts.push(`expires="${challenge.expires}"`)
307
- if (challenge.opaque !== undefined) parts.push(`opaque="${challenge.opaque}"`)
304
+ if (challenge.description !== undefined)
305
+ parts.push(authParam('description', challenge.description))
306
+ if (challenge.digest !== undefined) parts.push(authParam('digest', challenge.digest))
307
+ if (challenge.expires !== undefined) parts.push(authParam('expires', challenge.expires))
308
+ if (challenge.opaque !== undefined) parts.push(authParam('opaque', challenge.opaque))
308
309
  else if (challenge.meta !== undefined)
309
- parts.push(`opaque="${PaymentRequest.serialize(challenge.meta)}"`)
310
+ parts.push(authParam('opaque', PaymentRequest.serialize(challenge.meta)))
310
311
 
311
312
  return `Payment ${parts.join(', ')}`
312
313
  }
313
314
 
315
+ /** @internal */
316
+ function authParam(name: string, value: string): string {
317
+ if (/[\r\n]/.test(value)) throw new Error('Invalid quoted-string value.')
318
+ return `${name}="${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
319
+ }
320
+
314
321
  /**
315
322
  * Deserializes a WWW-Authenticate header value to a challenge.
316
323
  *
package/src/Method.ts CHANGED
@@ -334,7 +334,7 @@ export declare namespace toServer {
334
334
  request?: RequestFn<method> | undefined
335
335
  respond?: RespondFn<method> | undefined
336
336
  stableBinding?: StableBindingFn<method> | undefined
337
- transport?: transportOverride | undefined
337
+ transport?: transportOverride | Transport.AnyTransport | undefined
338
338
  verify: VerifyFn<method>
339
339
  }
340
340
  }
@@ -1,3 +1,4 @@
1
+ export { evm } from '../evm/client/index.js'
1
2
  export { stripe } from '../stripe/client/index.js'
2
3
  export { subscription } from '../tempo/client/Subscription.js'
3
4
  export { tempo } from '../tempo/client/index.js'
@@ -7,7 +7,7 @@ import * as Fetch from './internal/Fetch.js'
7
7
  import * as Transport from './Transport.js'
8
8
 
9
9
  export type Methods = readonly (Method.AnyClient | readonly Method.AnyClient[])[]
10
- type EventResponseOf<transport extends Transport.Transport> =
10
+ type EventResponseOf<transport extends Transport.AnyTransport> =
11
11
  | Response
12
12
  | Transport.ResponseOf<transport>
13
13
 
@@ -16,7 +16,7 @@ type EventResponseOf<transport extends Transport.Transport> =
16
16
  */
17
17
  export type Mppx<
18
18
  methods extends Methods = Methods,
19
- transport extends Transport.Transport = Transport.Transport,
19
+ transport extends Transport.AnyTransport = Transport.Transport,
20
20
  > = {
21
21
  /** Payment-aware fetch function that automatically handles 402 responses. */
22
22
  fetch: Fetch.from.Fetch<FlattenMethods<methods>>
@@ -125,6 +125,7 @@ export function create<
125
125
  ...(resolvedOnChallenge && { onChallenge: resolvedOnChallenge }),
126
126
  ...(orderChallenges && { orderChallenges }),
127
127
  methods,
128
+ transport,
128
129
  } satisfies Fetch.from.Config<FlattenMethods<methods>>
129
130
  const fetch = Fetch.from<FlattenMethods<methods>>(config_fetch)
130
131
 
@@ -282,7 +283,7 @@ export function restore(): void {
282
283
  export declare namespace create {
283
284
  type Config<
284
285
  methods extends Methods = Methods,
285
- transport extends Transport.Transport = Transport.Transport,
286
+ transport extends Transport.AnyTransport = Transport.Transport,
286
287
  > = {
287
288
  /** Controls when `Accept-Payment` is injected. */
288
289
  acceptPaymentPolicy?: Fetch.from.Config['acceptPaymentPolicy'] | undefined
@@ -1,6 +1,7 @@
1
1
  import { Challenge, Credential, Mcp } from 'mppx'
2
2
  import { Transport } from 'mppx/client'
3
3
  import { Methods } from 'mppx/tempo'
4
+ import { Header as x402_Header, Types as x402_Types, type PaymentRequired } from 'mppx/x402'
4
5
  import { describe, expect, test } from 'vp/test'
5
6
 
6
7
  const realm = 'api.example.com'
@@ -23,27 +24,34 @@ const credential = Credential.from({
23
24
  payload: { signature: '0xabc123', type: 'transaction' },
24
25
  })
25
26
 
27
+ const x402PaymentRequired = {
28
+ accepts: [
29
+ {
30
+ amount: '10000',
31
+ asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
32
+ maxTimeoutSeconds: 60,
33
+ network: 'eip155:84532',
34
+ payTo: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
35
+ scheme: x402_Types.schemes[0],
36
+ },
37
+ ],
38
+ resource: {
39
+ url: 'https://api.example.com/x402',
40
+ },
41
+ x402Version: 2,
42
+ } satisfies PaymentRequired
43
+
26
44
  describe('http', () => {
27
45
  describe('isPaymentRequired', () => {
28
- test('returns true for 402 response', () => {
29
- const transport = Transport.http()
30
- const response = new Response(null, { status: 402 })
31
-
32
- expect(transport.isPaymentRequired(response)).toBe(true)
33
- })
46
+ test.each([
47
+ { expected: true, status: 402 },
48
+ { expected: false, status: 200 },
49
+ { expected: false, status: 401 },
50
+ ])('returns $expected for $status response', ({ expected, status }) => {
51
+ const response = new Response(null, { status })
34
52
 
35
- test('returns false for 200 response', () => {
36
53
  const transport = Transport.http()
37
- const response = new Response(null, { status: 200 })
38
-
39
- expect(transport.isPaymentRequired(response)).toBe(false)
40
- })
41
-
42
- test('returns false for other error responses', () => {
43
- const transport = Transport.http()
44
- const response = new Response(null, { status: 401 })
45
-
46
- expect(transport.isPaymentRequired(response)).toBe(false)
54
+ expect(transport.isPaymentRequired(response)).toBe(expected)
47
55
  })
48
56
  })
49
57
 
@@ -80,32 +88,159 @@ describe('http', () => {
80
88
  })
81
89
 
82
90
  describe('getChallenges', () => {
83
- test('returns all HTTP challenges', () => {
91
+ test.each([
92
+ {
93
+ expectedIds: [challenge.id, 'alternate'],
94
+ expectedMethods: ['tempo', 'stripe'],
95
+ headers: () => ({
96
+ 'WWW-Authenticate': `${Challenge.serialize(challenge)}, ${Challenge.serialize({
97
+ ...challenge,
98
+ id: 'alternate',
99
+ method: 'stripe' as const,
100
+ })}`,
101
+ }),
102
+ name: 'Payment auth challenges',
103
+ },
104
+ {
105
+ expectedIds: [`${x402_Types.syntheticChallengeIdPrefix}0`],
106
+ expectedMethods: [x402_Types.paymentMethod],
107
+ headers: () => ({
108
+ 'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired(x402PaymentRequired),
109
+ }),
110
+ name: 'x402 challenges',
111
+ },
112
+ {
113
+ expectedIds: [
114
+ `${x402_Types.syntheticChallengeIdPrefix}0`,
115
+ `${x402_Types.syntheticChallengeIdPrefix}1`,
116
+ ],
117
+ expectedMethods: [x402_Types.paymentMethod, x402_Types.paymentMethod],
118
+ headers: () => ({
119
+ 'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired({
120
+ ...x402PaymentRequired,
121
+ accepts: [
122
+ x402PaymentRequired.accepts[0]!,
123
+ {
124
+ ...x402PaymentRequired.accepts[0]!,
125
+ amount: '20000',
126
+ },
127
+ ],
128
+ }),
129
+ }),
130
+ name: 'multiple x402 accepts',
131
+ },
132
+ {
133
+ expectedIds: [challenge.id, `${x402_Types.syntheticChallengeIdPrefix}0`],
134
+ expectedMethods: ['tempo', x402_Types.paymentMethod],
135
+ headers: () => ({
136
+ 'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired(x402PaymentRequired),
137
+ 'WWW-Authenticate': Challenge.serialize(challenge),
138
+ }),
139
+ name: 'Payment auth and x402 challenges when both are present',
140
+ },
141
+ ])('returns $name', ({ expectedIds, expectedMethods, headers }) => {
84
142
  const transport = Transport.http()
85
- const alternate = { ...challenge, id: 'alternate', method: 'stripe' as const }
86
143
  const response = new Response(null, {
87
144
  status: 402,
88
- headers: {
89
- 'WWW-Authenticate': `${Challenge.serialize(challenge)}, ${Challenge.serialize(alternate)}`,
90
- },
145
+ headers: headers(),
91
146
  })
147
+ const challenges = transport.getChallenges?.(response) ?? []
92
148
 
93
- expect(transport.getChallenges?.(response).map((entry) => entry.id)).toEqual([
94
- challenge.id,
95
- 'alternate',
96
- ])
149
+ expect(challenges.map((entry) => entry.id)).toEqual(expectedIds)
150
+ expect(challenges.map((entry) => entry.method)).toEqual(expectedMethods)
97
151
  })
98
152
  })
99
153
 
100
154
  describe('setCredential', () => {
101
- test('default', () => {
155
+ test.each([
156
+ {
157
+ challenge,
158
+ credential: Credential.serialize(credential),
159
+ expectedHeader: 'Authorization',
160
+ expectedValue: Credential.serialize(credential),
161
+ name: 'Payment auth credential for Payment auth challenge',
162
+ },
163
+ {
164
+ challenge: Transport.http().getChallenges!(
165
+ new Response(null, {
166
+ status: 402,
167
+ headers: {
168
+ 'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired(x402PaymentRequired),
169
+ },
170
+ }),
171
+ )[0],
172
+ credential: 'x402-signature',
173
+ expectedHeader: 'PAYMENT-SIGNATURE',
174
+ expectedValue: 'x402-signature',
175
+ name: 'raw x402 credential for x402 challenge',
176
+ },
177
+ {
178
+ challenge,
179
+ credential: 'custom-credential',
180
+ expectedHeader: 'Authorization',
181
+ expectedValue: 'custom-credential',
182
+ name: 'non-Payment credential for non-x402 challenge',
183
+ },
184
+ {
185
+ challenge: undefined,
186
+ credential: 'custom-credential',
187
+ expectedHeader: 'Authorization',
188
+ expectedValue: 'custom-credential',
189
+ name: 'credential without selected challenge',
190
+ },
191
+ ])('writes $name', ({ challenge, credential, expectedHeader, expectedValue }) => {
102
192
  const transport = Transport.http()
103
- const serialized = Credential.serialize(credential)
104
193
 
105
- const result = transport.setCredential({}, serialized)
194
+ const result = transport.setCredential({}, credential, { challenge })
195
+ const headers = result.headers as Headers
196
+
197
+ expect(headers.get(expectedHeader)).toBe(expectedValue)
198
+ })
199
+
200
+ test('does not treat unbranded Payment-auth challenges as x402', () => {
201
+ const transport = Transport.http()
202
+ const untrustedChallenge = Challenge.from({
203
+ id: `${x402_Types.syntheticChallengeIdPrefix}0`,
204
+ intent: x402_Types.exactIntent,
205
+ method: x402_Types.paymentMethod,
206
+ realm: 'api.example.com',
207
+ request: x402PaymentRequired.accepts[0]!,
208
+ })
209
+
210
+ const result = transport.setCredential({}, 'credential', {
211
+ challenge: untrustedChallenge,
212
+ })
213
+ const headers = result.headers as Headers
214
+
215
+ expect(headers.get('Authorization')).toBe('credential')
216
+ expect(headers.get(x402_Types.paymentSignatureHeader)).toBeNull()
217
+ })
218
+
219
+ test('removes stale credential headers before setting the retry credential', () => {
220
+ const transport = Transport.http()
221
+ const x402Challenge = Transport.http().getChallenges!(
222
+ new Response(null, {
223
+ status: 402,
224
+ headers: {
225
+ 'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired(x402PaymentRequired),
226
+ },
227
+ }),
228
+ )[0]
229
+
230
+ const result = transport.setCredential(
231
+ {
232
+ headers: {
233
+ Authorization: 'Payment stale',
234
+ [x402_Types.paymentSignatureHeader]: 'stale-x402',
235
+ },
236
+ },
237
+ 'fresh-x402',
238
+ { challenge: x402Challenge },
239
+ )
106
240
  const headers = result.headers as Headers
107
241
 
108
- expect(headers.get('Authorization')).toBe(serialized)
242
+ expect(headers.get('Authorization')).toBeNull()
243
+ expect(headers.get(x402_Types.paymentSignatureHeader)).toBe('fresh-x402')
109
244
  })
110
245
 
111
246
  test('preserves existing headers', () => {
@@ -1,6 +1,19 @@
1
1
  import * as Challenge from '../Challenge.js'
2
2
  import * as Credential from '../Credential.js'
3
3
  import * as Mcp from '../Mcp.js'
4
+ import * as x402_Header from '../x402/Header.js'
5
+ import * as x402_ChallengeBrand from '../x402/internal/ChallengeBrand.js'
6
+ import * as x402_Types from '../x402/Types.js'
7
+
8
+ const paymentRequiredStatus = 402
9
+ const paymentAuthChallengeHeader = 'WWW-Authenticate'
10
+ const paymentAuthCredentialHeader = 'Authorization'
11
+ const credentialHeaders = [
12
+ paymentAuthCredentialHeader,
13
+ x402_Types.paymentRequiredHeader,
14
+ x402_Types.paymentResponseHeader,
15
+ x402_Types.paymentSignatureHeader,
16
+ ]
4
17
 
5
18
  /**
6
19
  * Client-side transport adapter.
@@ -18,10 +31,21 @@ export type Transport<in out request = unknown, in out response = unknown> = {
18
31
  /** Extracts the challenge from a payment-required response. */
19
32
  getChallenge: (response: response) => Challenge.Challenge
20
33
  /** Attaches a credential to a request. */
21
- setCredential: (request: request, credential: string) => request
34
+ setCredential: (
35
+ request: request,
36
+ credential: string,
37
+ options?: setCredential.Options | undefined,
38
+ ) => request
22
39
  }
23
40
  export type AnyTransport = Transport<any, any>
24
41
 
42
+ export declare namespace setCredential {
43
+ type Options = {
44
+ /** Challenge selected for credential creation. */
45
+ challenge?: Challenge.Challenge | undefined
46
+ }
47
+ }
48
+
25
49
  /** Extracts the response type from a transport. */
26
50
  export type ResponseOf<transport extends Transport> =
27
51
  transport extends Transport<any, infer response> ? response : never
@@ -55,33 +79,77 @@ export function from<request, response>(
55
79
  * HTTP transport for client-side payment handling.
56
80
  *
57
81
  * - Detects payment required via 402 status
58
- * - Extracts challenges from `WWW-Authenticate` header
59
- * - Sends credentials via `Authorization` header
82
+ * - Extracts Payment auth challenges from `WWW-Authenticate`
83
+ * - Falls back to x402 exact challenges from `PAYMENT-REQUIRED`
84
+ * - Sends credentials via `Authorization` or `PAYMENT-SIGNATURE`
60
85
  */
61
86
  export function http() {
62
87
  return from<RequestInit, Response>({
63
88
  name: 'http',
64
89
 
65
90
  isPaymentRequired(response) {
66
- return response.status === 402
91
+ return response.status === paymentRequiredStatus
67
92
  },
68
93
 
69
94
  getChallenges(response) {
70
- return Challenge.fromResponseList(response)
95
+ return paymentRequiredChallenges(response)
71
96
  },
72
97
 
73
98
  getChallenge(response) {
74
- return Challenge.fromResponse(response)
99
+ const challenge = paymentRequiredChallenges(response)[0]
100
+ if (!challenge) throw new Error('No challenge in response.')
101
+ return challenge
75
102
  },
76
103
 
77
- setCredential(request, credential) {
104
+ setCredential(request, credential, options) {
78
105
  const headers = new Headers(request.headers)
79
- headers.set('Authorization', credential)
106
+ for (const header of credentialHeaders) headers.delete(header)
107
+ if (isX402Challenge(options?.challenge)) {
108
+ headers.set(x402_Types.paymentSignatureHeader, credential)
109
+ } else {
110
+ headers.set(paymentAuthCredentialHeader, credential)
111
+ }
80
112
  return { ...request, headers }
81
113
  },
82
114
  })
83
115
  }
84
116
 
117
+ function paymentRequiredChallenges(response: Response): Challenge.Challenge[] {
118
+ return [
119
+ ...(response.headers.has(paymentAuthChallengeHeader)
120
+ ? Challenge.fromResponseList(response)
121
+ : []),
122
+ ...x402Challenges(response),
123
+ ]
124
+ }
125
+
126
+ function x402Challenges(response: Response): Challenge.Challenge[] {
127
+ const header = response.headers.get(x402_Types.paymentRequiredHeader)
128
+ if (!header) return []
129
+ const paymentRequired = x402_Header.decodePaymentRequired(header)
130
+ if (response.url && paymentRequired.resource.url !== response.url)
131
+ throw new Error('x402 payment-required resource does not match response URL.')
132
+ return paymentRequired.accepts.map((accepted, index) =>
133
+ x402_ChallengeBrand.mark(
134
+ Challenge.from({
135
+ id: `${x402_Types.syntheticChallengeIdPrefix}${index}`,
136
+ intent: x402_Types.exactIntent,
137
+ method: x402_Types.paymentMethod,
138
+ realm: new URL(paymentRequired.resource.url).host,
139
+ request: {
140
+ ...accepted,
141
+ ...(paymentRequired.extensions ? { extensions: paymentRequired.extensions } : {}),
142
+ resource: paymentRequired.resource,
143
+ },
144
+ }),
145
+ ),
146
+ )
147
+ }
148
+
149
+ function isX402Challenge(challenge: Challenge.Challenge | undefined): boolean {
150
+ return x402_ChallengeBrand.is(challenge)
151
+ }
152
+
85
153
  /**
86
154
  * MCP transport for client-side payment handling.
87
155
  *
@@ -1,5 +1,5 @@
1
1
  export * as Expires from '../Expires.js'
2
2
  export * as Fetch from './internal/Fetch.js'
3
- export { session, stripe, tempo } from './Methods.js'
3
+ export { evm, session, stripe, tempo } from './Methods.js'
4
4
  export * as Mppx from './Mppx.js'
5
5
  export * as Transport from './Transport.js'