mppx 0.6.27 → 0.6.29

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 (295) hide show
  1. package/CHANGELOG.md +25 -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/Store.d.ts +32 -9
  8. package/dist/Store.d.ts.map +1 -1
  9. package/dist/Store.js +42 -10
  10. package/dist/Store.js.map +1 -1
  11. package/dist/client/Methods.d.ts +1 -0
  12. package/dist/client/Methods.d.ts.map +1 -1
  13. package/dist/client/Methods.js +1 -0
  14. package/dist/client/Methods.js.map +1 -1
  15. package/dist/client/Mppx.d.ts +3 -3
  16. package/dist/client/Mppx.d.ts.map +1 -1
  17. package/dist/client/Mppx.js +1 -0
  18. package/dist/client/Mppx.js.map +1 -1
  19. package/dist/client/Transport.d.ts +10 -3
  20. package/dist/client/Transport.d.ts.map +1 -1
  21. package/dist/client/Transport.js +60 -7
  22. package/dist/client/Transport.js.map +1 -1
  23. package/dist/client/index.d.ts +1 -1
  24. package/dist/client/index.d.ts.map +1 -1
  25. package/dist/client/index.js +1 -1
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/client/internal/Fetch.d.ts +3 -0
  28. package/dist/client/internal/Fetch.d.ts.map +1 -1
  29. package/dist/client/internal/Fetch.js +12 -20
  30. package/dist/client/internal/Fetch.js.map +1 -1
  31. package/dist/evm/Assets.d.ts +2 -0
  32. package/dist/evm/Assets.d.ts.map +1 -0
  33. package/dist/evm/Assets.js +2 -0
  34. package/dist/evm/Assets.js.map +1 -0
  35. package/dist/evm/Chains.d.ts +5 -0
  36. package/dist/evm/Chains.d.ts.map +1 -0
  37. package/dist/evm/Chains.js +5 -0
  38. package/dist/evm/Chains.js.map +1 -0
  39. package/dist/evm/Methods.d.ts +68 -0
  40. package/dist/evm/Methods.d.ts.map +1 -0
  41. package/dist/evm/Methods.js +28 -0
  42. package/dist/evm/Methods.js.map +1 -0
  43. package/dist/evm/Types.d.ts +143 -0
  44. package/dist/evm/Types.d.ts.map +1 -0
  45. package/dist/evm/Types.js +102 -0
  46. package/dist/evm/Types.js.map +1 -0
  47. package/dist/evm/client/Charge.d.ts +102 -0
  48. package/dist/evm/client/Charge.d.ts.map +1 -0
  49. package/dist/evm/client/Charge.js +141 -0
  50. package/dist/evm/client/Charge.js.map +1 -0
  51. package/dist/evm/client/Methods.d.ts +81 -0
  52. package/dist/evm/client/Methods.d.ts.map +1 -0
  53. package/dist/evm/client/Methods.js +16 -0
  54. package/dist/evm/client/Methods.js.map +1 -0
  55. package/dist/evm/client/index.d.ts +6 -0
  56. package/dist/evm/client/index.d.ts.map +1 -0
  57. package/dist/evm/client/index.js +6 -0
  58. package/dist/evm/client/index.js.map +1 -0
  59. package/dist/evm/index.d.ts +9 -0
  60. package/dist/evm/index.d.ts.map +1 -0
  61. package/dist/evm/index.js +8 -0
  62. package/dist/evm/index.js.map +1 -0
  63. package/dist/evm/server/Charge.d.ts +62 -0
  64. package/dist/evm/server/Charge.d.ts.map +1 -0
  65. package/dist/evm/server/Charge.js +172 -0
  66. package/dist/evm/server/Charge.js.map +1 -0
  67. package/dist/evm/server/Methods.d.ts +80 -0
  68. package/dist/evm/server/Methods.d.ts.map +1 -0
  69. package/dist/evm/server/Methods.js +16 -0
  70. package/dist/evm/server/Methods.js.map +1 -0
  71. package/dist/evm/server/index.d.ts +6 -0
  72. package/dist/evm/server/index.d.ts.map +1 -0
  73. package/dist/evm/server/index.js +6 -0
  74. package/dist/evm/server/index.js.map +1 -0
  75. package/dist/index.d.ts +2 -0
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +2 -0
  78. package/dist/index.js.map +1 -1
  79. package/dist/internal/HeaderCodec.d.ts +18 -0
  80. package/dist/internal/HeaderCodec.d.ts.map +1 -0
  81. package/dist/internal/HeaderCodec.js +31 -0
  82. package/dist/internal/HeaderCodec.js.map +1 -0
  83. package/dist/middlewares/elysia.d.ts.map +1 -1
  84. package/dist/middlewares/elysia.js +2 -3
  85. package/dist/middlewares/elysia.js.map +1 -1
  86. package/dist/middlewares/express.js +2 -1
  87. package/dist/middlewares/express.js.map +1 -1
  88. package/dist/proxy/internal/Headers.d.ts +13 -1
  89. package/dist/proxy/internal/Headers.d.ts.map +1 -1
  90. package/dist/proxy/internal/Headers.js +25 -2
  91. package/dist/proxy/internal/Headers.js.map +1 -1
  92. package/dist/proxy/services/openai.d.ts.map +1 -1
  93. package/dist/proxy/services/openai.js +2 -0
  94. package/dist/proxy/services/openai.js.map +1 -1
  95. package/dist/server/Methods.d.ts +1 -0
  96. package/dist/server/Methods.d.ts.map +1 -1
  97. package/dist/server/Methods.js +1 -0
  98. package/dist/server/Methods.js.map +1 -1
  99. package/dist/server/Mppx.d.ts.map +1 -1
  100. package/dist/server/Mppx.js +90 -12
  101. package/dist/server/Mppx.js.map +1 -1
  102. package/dist/server/Transport.d.ts +10 -0
  103. package/dist/server/Transport.d.ts.map +1 -1
  104. package/dist/server/Transport.js +9 -0
  105. package/dist/server/Transport.js.map +1 -1
  106. package/dist/server/index.d.ts +1 -1
  107. package/dist/server/index.d.ts.map +1 -1
  108. package/dist/server/index.js +1 -1
  109. package/dist/server/index.js.map +1 -1
  110. package/dist/stripe/server/Charge.d.ts +31 -1
  111. package/dist/stripe/server/Charge.d.ts.map +1 -1
  112. package/dist/stripe/server/Charge.js +88 -11
  113. package/dist/stripe/server/Charge.js.map +1 -1
  114. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  115. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  116. package/dist/stripe/server/internal/html.gen.js +1 -1
  117. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  118. package/dist/tempo/client/ChannelOps.d.ts +3 -3
  119. package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
  120. package/dist/tempo/client/ChannelOps.js +13 -6
  121. package/dist/tempo/client/ChannelOps.js.map +1 -1
  122. package/dist/tempo/client/Charge.d.ts.map +1 -1
  123. package/dist/tempo/client/Charge.js +8 -5
  124. package/dist/tempo/client/Charge.js.map +1 -1
  125. package/dist/tempo/client/Methods.d.ts +0 -1
  126. package/dist/tempo/client/Methods.d.ts.map +1 -1
  127. package/dist/tempo/client/Session.d.ts +2 -4
  128. package/dist/tempo/client/Session.d.ts.map +1 -1
  129. package/dist/tempo/client/Session.js +10 -12
  130. package/dist/tempo/client/Session.js.map +1 -1
  131. package/dist/tempo/client/SessionManager.d.ts +3 -3
  132. package/dist/tempo/client/SessionManager.d.ts.map +1 -1
  133. package/dist/tempo/client/SessionManager.js +1 -1
  134. package/dist/tempo/client/SessionManager.js.map +1 -1
  135. package/dist/tempo/internal/account.d.ts +5 -0
  136. package/dist/tempo/internal/account.d.ts.map +1 -1
  137. package/dist/tempo/internal/account.js +8 -0
  138. package/dist/tempo/internal/account.js.map +1 -1
  139. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  140. package/dist/tempo/internal/fee-payer.js +5 -2
  141. package/dist/tempo/internal/fee-payer.js.map +1 -1
  142. package/dist/tempo/server/Charge.d.ts +6 -0
  143. package/dist/tempo/server/Charge.d.ts.map +1 -1
  144. package/dist/tempo/server/Charge.js +30 -3
  145. package/dist/tempo/server/Charge.js.map +1 -1
  146. package/dist/tempo/server/Session.d.ts +6 -0
  147. package/dist/tempo/server/Session.d.ts.map +1 -1
  148. package/dist/tempo/server/Session.js +14 -13
  149. package/dist/tempo/server/Session.js.map +1 -1
  150. package/dist/tempo/server/Subscription.d.ts +6 -0
  151. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  152. package/dist/tempo/server/Subscription.js +1 -1
  153. package/dist/tempo/server/Subscription.js.map +1 -1
  154. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  155. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  156. package/dist/tempo/server/internal/html.gen.js +1 -1
  157. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  158. package/dist/tempo/session/Chain.d.ts +2 -0
  159. package/dist/tempo/session/Chain.d.ts.map +1 -1
  160. package/dist/tempo/session/Chain.js +8 -8
  161. package/dist/tempo/session/Chain.js.map +1 -1
  162. package/dist/tempo/session/Voucher.d.ts +4 -3
  163. package/dist/tempo/session/Voucher.d.ts.map +1 -1
  164. package/dist/tempo/session/Voucher.js +71 -44
  165. package/dist/tempo/session/Voucher.js.map +1 -1
  166. package/dist/tempo/session/Ws.d.ts.map +1 -1
  167. package/dist/tempo/session/Ws.js +15 -0
  168. package/dist/tempo/session/Ws.js.map +1 -1
  169. package/dist/tempo/subscription/KeyAuthorization.d.ts +2 -2
  170. package/dist/x402/Assets.d.ts +29 -0
  171. package/dist/x402/Assets.d.ts.map +1 -0
  172. package/dist/x402/Assets.js +46 -0
  173. package/dist/x402/Assets.js.map +1 -0
  174. package/dist/x402/Header.d.ts +14 -0
  175. package/dist/x402/Header.d.ts.map +1 -0
  176. package/dist/x402/Header.js +18 -0
  177. package/dist/x402/Header.js.map +1 -0
  178. package/dist/x402/Types.d.ts +289 -0
  179. package/dist/x402/Types.d.ts.map +1 -0
  180. package/dist/x402/Types.js +139 -0
  181. package/dist/x402/Types.js.map +1 -0
  182. package/dist/x402/client/Exact.d.ts +38 -0
  183. package/dist/x402/client/Exact.d.ts.map +1 -0
  184. package/dist/x402/client/Exact.js +141 -0
  185. package/dist/x402/client/Exact.js.map +1 -0
  186. package/dist/x402/index.d.ts +6 -0
  187. package/dist/x402/index.d.ts.map +1 -0
  188. package/dist/x402/index.js +6 -0
  189. package/dist/x402/index.js.map +1 -0
  190. package/dist/x402/internal/ChallengeBrand.d.ts +5 -0
  191. package/dist/x402/internal/ChallengeBrand.d.ts.map +1 -0
  192. package/dist/x402/internal/ChallengeBrand.js +13 -0
  193. package/dist/x402/internal/ChallengeBrand.js.map +1 -0
  194. package/dist/x402/internal/RouteBinding.d.ts +8 -0
  195. package/dist/x402/internal/RouteBinding.d.ts.map +1 -0
  196. package/dist/x402/internal/RouteBinding.js +12 -0
  197. package/dist/x402/internal/RouteBinding.js.map +1 -0
  198. package/dist/x402/server/EvmCharge.d.ts +50 -0
  199. package/dist/x402/server/EvmCharge.d.ts.map +1 -0
  200. package/dist/x402/server/EvmCharge.js +301 -0
  201. package/dist/x402/server/EvmCharge.js.map +1 -0
  202. package/dist/x402/server/Facilitator.d.ts +12 -0
  203. package/dist/x402/server/Facilitator.d.ts.map +1 -0
  204. package/dist/x402/server/Facilitator.js +42 -0
  205. package/dist/x402/server/Facilitator.js.map +1 -0
  206. package/package.json +41 -21
  207. package/src/Challenge.test.ts +28 -0
  208. package/src/Challenge.ts +17 -10
  209. package/src/Method.ts +1 -1
  210. package/src/Store.test-d.ts +58 -0
  211. package/src/Store.test.ts +77 -0
  212. package/src/Store.ts +155 -74
  213. package/src/client/Methods.ts +1 -0
  214. package/src/client/Mppx.ts +4 -3
  215. package/src/client/Transport.test.ts +165 -30
  216. package/src/client/Transport.ts +76 -8
  217. package/src/client/index.ts +1 -1
  218. package/src/client/internal/Fetch.test.ts +31 -2
  219. package/src/client/internal/Fetch.ts +26 -19
  220. package/src/evm/Assets.ts +1 -0
  221. package/src/evm/Chains.ts +5 -0
  222. package/src/evm/Methods.ts +44 -0
  223. package/src/evm/PublicInterface.test-d.ts +109 -0
  224. package/src/evm/Types.ts +140 -0
  225. package/src/evm/client/Charge.test.ts +99 -0
  226. package/src/evm/client/Charge.ts +198 -0
  227. package/src/evm/client/Methods.ts +19 -0
  228. package/src/evm/client/index.ts +5 -0
  229. package/src/evm/index.ts +13 -0
  230. package/src/evm/server/Charge.test.ts +199 -0
  231. package/src/evm/server/Charge.ts +283 -0
  232. package/src/evm/server/Methods.ts +22 -0
  233. package/src/evm/server/index.ts +5 -0
  234. package/src/index.ts +2 -0
  235. package/src/internal/HeaderCodec.ts +36 -0
  236. package/src/middlewares/elysia.test.ts +25 -0
  237. package/src/middlewares/elysia.ts +1 -2
  238. package/src/middlewares/express.test.ts +28 -0
  239. package/src/middlewares/express.ts +1 -1
  240. package/src/middlewares/hono.test.ts +138 -2
  241. package/src/middlewares/nextjs.test.ts +22 -0
  242. package/src/proxy/internal/Headers.test.ts +38 -0
  243. package/src/proxy/internal/Headers.ts +26 -2
  244. package/src/proxy/services/openai.test.ts +57 -1
  245. package/src/proxy/services/openai.ts +2 -0
  246. package/src/server/Methods.ts +1 -0
  247. package/src/server/Mppx.test.ts +244 -1
  248. package/src/server/Mppx.ts +124 -11
  249. package/src/server/NodeListener.test.ts +28 -1
  250. package/src/server/Transport.test.ts +19 -0
  251. package/src/server/Transport.ts +20 -0
  252. package/src/server/index.ts +1 -1
  253. package/src/stripe/server/Charge.test.ts +215 -1
  254. package/src/stripe/server/Charge.ts +150 -20
  255. package/src/stripe/server/internal/html.gen.ts +1 -1
  256. package/src/tempo/AccessKeyAuthorization.test.ts +231 -0
  257. package/src/tempo/client/ChannelOps.test.ts +61 -7
  258. package/src/tempo/client/ChannelOps.ts +18 -7
  259. package/src/tempo/client/Charge.test.ts +126 -0
  260. package/src/tempo/client/Charge.ts +10 -6
  261. package/src/tempo/client/Session.test.ts +130 -1
  262. package/src/tempo/client/Session.ts +12 -19
  263. package/src/tempo/client/SessionManager.test.ts +69 -2
  264. package/src/tempo/client/SessionManager.ts +4 -4
  265. package/src/tempo/internal/account.ts +13 -0
  266. package/src/tempo/internal/fee-payer.test.ts +32 -2
  267. package/src/tempo/internal/fee-payer.ts +6 -2
  268. package/src/tempo/server/Charge.test.ts +91 -1
  269. package/src/tempo/server/Charge.ts +48 -2
  270. package/src/tempo/server/Session.test.ts +30 -0
  271. package/src/tempo/server/Session.ts +24 -17
  272. package/src/tempo/server/Subscription.ts +7 -1
  273. package/src/tempo/server/internal/html.gen.ts +1 -1
  274. package/src/tempo/session/Chain.test.ts +4 -4
  275. package/src/tempo/session/Chain.ts +10 -6
  276. package/src/tempo/session/ChannelStore.test.ts +21 -0
  277. package/src/tempo/session/Voucher.test.ts +230 -1
  278. package/src/tempo/session/Voucher.ts +96 -48
  279. package/src/tempo/session/Ws.test.ts +71 -0
  280. package/src/tempo/session/Ws.ts +13 -0
  281. package/src/tempo/subscription/Store.test.ts +55 -0
  282. package/src/x402/Assets.ts +65 -0
  283. package/src/x402/Exact.e2e.test.ts +448 -0
  284. package/src/x402/Header.test.ts +73 -0
  285. package/src/x402/Header.ts +34 -0
  286. package/src/x402/PublicInterface.test-d.ts +39 -0
  287. package/src/x402/Types.ts +248 -0
  288. package/src/x402/client/Exact.test.ts +180 -0
  289. package/src/x402/client/Exact.ts +198 -0
  290. package/src/x402/index.ts +5 -0
  291. package/src/x402/internal/ChallengeBrand.ts +14 -0
  292. package/src/x402/internal/RouteBinding.ts +18 -0
  293. package/src/x402/server/EvmCharge.ts +394 -0
  294. package/src/x402/server/Facilitator.test.ts +111 -0
  295. package/src/x402/server/Facilitator.ts +56 -0
@@ -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'
@@ -665,8 +665,37 @@ describe('Fetch.from: 402 retry path', () => {
665
665
  await fetch('https://example.com/api')
666
666
 
667
667
  const retryInit = calls[1]!.init as Record<string, unknown>
668
- const headers = retryInit.headers as Record<string, string>
669
- expect(headers.Authorization).toBe('credential')
668
+ const headers = new Headers(retryInit.headers as HeadersInit)
669
+ expect(headers.get('Authorization')).toBe('credential')
670
+ })
671
+
672
+ test('sends credential retry to the final 402 response URL', async () => {
673
+ let callCount = 0
674
+ const calls: { input: RequestInfo | URL; init: RequestInit | undefined }[] = []
675
+ const mockFetch: typeof globalThis.fetch = async (input, init) => {
676
+ calls.push({ input, init })
677
+ callCount++
678
+ if (callCount === 1) {
679
+ const response = make402()
680
+ Object.defineProperty(response, 'url', {
681
+ value: 'https://payments.example.com/protected',
682
+ })
683
+ return response
684
+ }
685
+ return new Response('OK', { status: 200 })
686
+ }
687
+
688
+ const fetch = Fetch.from({
689
+ fetch: mockFetch,
690
+ methods: [noopMethod],
691
+ })
692
+
693
+ const response = await fetch('https://api.example.com/protected')
694
+
695
+ expect(response.status).toBe(200)
696
+ expect(calls[0]!.input).toBe('https://api.example.com/protected')
697
+ expect(calls[1]!.input).toBe('https://payments.example.com/protected')
698
+ expect(new Headers(calls[1]!.init?.headers).get('Authorization')).toBe('credential')
670
699
  })
671
700
 
672
701
  test('emits client events and allows challenge handler to provide credential', async () => {
@@ -4,6 +4,7 @@ import * as AcceptPayment from '../../internal/AcceptPayment.js'
4
4
  import type { MaybePromise } from '../../internal/types.js'
5
5
  import type * as Method from '../../Method.js'
6
6
  import type * as z from '../../zod.js'
7
+ import * as Transport from '../Transport.js'
7
8
 
8
9
  // We tag wrappers with a global symbol so we can recognize wrappers created by mppx,
9
10
  // even across multiple module instances/bundles. This lets restore() avoid clobbering
@@ -162,6 +163,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
162
163
  methods,
163
164
  onChallenge,
164
165
  orderChallenges,
166
+ transport = Transport.http(),
165
167
  } = config
166
168
  const events = config.eventDispatcher ?? createEventDispatcher()
167
169
  const resolvedAcceptPayment = acceptPayment ?? AcceptPayment.resolve(methods)
@@ -183,7 +185,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
183
185
  )
184
186
  const response = await baseFetch(initialRequest.input, initialRequest.init)
185
187
 
186
- if (response.status !== 402) return response
188
+ if (!transport.isPaymentRequired(response)) return response
187
189
 
188
190
  // Only extract context for payment handling after confirming 402.
189
191
  const context = (init as Record<string, unknown> | undefined)?.context
@@ -198,8 +200,9 @@ export function from<const methods extends readonly Method.AnyClient[]>(
198
200
  let mi: methods[number] | undefined
199
201
 
200
202
  try {
201
- // Parse all challenges from the response (supports merged WWW-Authenticate headers).
202
- challenges = Challenge.fromResponseList(response)
203
+ challenges = transport.getChallenges
204
+ ? transport.getChallenges(response)
205
+ : [transport.getChallenge(response)]
203
206
 
204
207
  const candidates = AcceptPayment.selectChallengeCandidates(
205
208
  challenges,
@@ -258,10 +261,17 @@ export function from<const methods extends readonly Method.AnyClient[]>(
258
261
  }),
259
262
  )
260
263
 
261
- const paymentResponse = await baseFetch(initialRequest.input, {
262
- ...fetchInit,
263
- headers: withAuthorizationHeader(initialRequest.headers, credential),
264
- })
264
+ const paymentResponse = await baseFetch(
265
+ resolvePaymentRetryInput(response, initialRequest.input),
266
+ transport.setCredential(
267
+ {
268
+ ...fetchInit,
269
+ headers: initialRequest.headers,
270
+ },
271
+ credential,
272
+ { challenge: selectedChallenge },
273
+ ),
274
+ )
265
275
  if (paymentResponse.ok)
266
276
  await events.emit(
267
277
  'payment.response',
@@ -335,6 +345,8 @@ export declare namespace from {
335
345
  | undefined
336
346
  /** Filters and sorts supported challenges before credential creation. */
337
347
  orderChallenges?: AcceptPayment.OrderChallenges<methods> | undefined
348
+ /** Transport to use for challenge extraction and credential attachment. */
349
+ transport?: Transport.AnyTransport | undefined
338
350
  }
339
351
 
340
352
  type Fetch<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> = (
@@ -688,18 +700,6 @@ function freezeSnapshot<value>(value: value): value {
688
700
  return value
689
701
  }
690
702
 
691
- /** @internal */
692
- function withAuthorizationHeader(headers: unknown, credential: string): Record<string, string> {
693
- const normalized = normalizeHeaders(headers)
694
- // Remove any existing Authorization header regardless of casing to avoid
695
- // duplicate/conflicting credentials on retry.
696
- for (const key of Object.keys(normalized)) {
697
- if (key.toLowerCase() === 'authorization') delete normalized[key]
698
- }
699
- normalized.Authorization = credential
700
- return normalized
701
- }
702
-
703
703
  /** @internal */
704
704
  function prepareInitialRequest<methods extends readonly Method.AnyClient[]>(
705
705
  input: RequestInfo | URL,
@@ -843,3 +843,10 @@ function resolveRequestUrl(input: RequestInfo | URL): URL {
843
843
  if (input instanceof Request) return new URL(input.url)
844
844
  return new URL(input, isBrowser() ? globalThis.location.href : undefined)
845
845
  }
846
+
847
+ function resolvePaymentRetryInput(
848
+ response: Response,
849
+ fallback: RequestInfo | URL,
850
+ ): RequestInfo | URL {
851
+ return response.url ? response.url : fallback
852
+ }
@@ -0,0 +1 @@
1
+ export * from '../x402/Assets.js'
@@ -0,0 +1,5 @@
1
+ /** Base mainnet EVM chain ID. */
2
+ export const base = 8453
3
+
4
+ /** Base Sepolia testnet EVM chain ID. */
5
+ export const baseSepolia = 84532
@@ -0,0 +1,44 @@
1
+ import { getAddress, parseUnits } from 'viem'
2
+
3
+ import * as Method from '../Method.js'
4
+ import * as z from '../zod.js'
5
+ import * as Types from './Types.js'
6
+
7
+ /** Native Payment-auth EVM charge method. */
8
+ export const charge = Method.from({
9
+ name: Types.paymentMethod,
10
+ intent: Types.chargeIntent,
11
+ schema: {
12
+ credential: {
13
+ payload: Types.ChargePayloadSchema,
14
+ },
15
+ request: z.pipe(
16
+ Types.ChargeRequestInputSchema,
17
+ z.transform(
18
+ ({
19
+ amount,
20
+ chainId,
21
+ credentialTypes = ['authorization'],
22
+ currency,
23
+ decimals,
24
+ permit2Address,
25
+ recipient,
26
+ splits,
27
+ ...request
28
+ }) => ({
29
+ ...request,
30
+ amount: parseUnits(amount, decimals).toString(),
31
+ currency: getAddress(currency),
32
+ methodDetails: {
33
+ chainId,
34
+ credentialTypes,
35
+ decimals,
36
+ ...(permit2Address ? { permit2Address: getAddress(permit2Address) } : {}),
37
+ ...(splits ? { splits } : {}),
38
+ },
39
+ recipient: getAddress(recipient),
40
+ }),
41
+ ),
42
+ ),
43
+ },
44
+ })