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
@@ -0,0 +1,283 @@
1
+ import { getAddress, recoverTypedDataAddress } from 'viem'
2
+
3
+ import type * as Credential from '../../Credential.js'
4
+ import { VerificationFailedError } from '../../Errors.js'
5
+ import * as Method from '../../Method.js'
6
+ import * as Receipt from '../../Receipt.js'
7
+ import * as ServerTransport from '../../server/Transport.js'
8
+ import * as X402 from '../../x402/server/EvmCharge.js'
9
+ import * as Assets from '../Assets.js'
10
+ import * as Methods from '../Methods.js'
11
+ import * as Types from '../Types.js'
12
+
13
+ /**
14
+ * Creates an EVM charge server method.
15
+ *
16
+ * Speaks the native Payment-auth `evm/charge` wire format.
17
+ */
18
+ export function charge(
19
+ parameters: charge.NativeConfig,
20
+ ): Method.Server<typeof Methods.charge, charge.Defaults>
21
+ export function charge(parameters: charge.NativeConfig): Method.AnyServer {
22
+ const config = resolveConfig(parameters)
23
+ const paths = createPaths(config)
24
+ const transport = httpTransport(paths)
25
+
26
+ return Method.toServer<typeof Methods.charge, charge.Defaults, typeof transport>(Methods.charge, {
27
+ defaults: {
28
+ chainId: config.chainId,
29
+ currency: config.currency,
30
+ credentialTypes: ['authorization'],
31
+ decimals: config.decimals,
32
+ recipient: config.recipient,
33
+ },
34
+ transport,
35
+ async verify({ credential }) {
36
+ const payload = credential.payload as Types.AuthorizationPayload
37
+ const request = credential.challenge.request as Types.ChargeRequest
38
+ const chainId = request.methodDetails.chainId
39
+ const isX402Credential = X402.isCredential(credential)
40
+
41
+ if (!request.methodDetails.credentialTypes?.includes('authorization')) {
42
+ throw new VerificationFailedError({
43
+ reason: 'EVM authorization credentials are not supported for this challenge',
44
+ })
45
+ }
46
+
47
+ if (request.methodDetails.splits?.length) {
48
+ throw new VerificationFailedError({
49
+ reason: 'EVM authorization credentials do not support splits',
50
+ })
51
+ }
52
+
53
+ assertAddressEqual(payload.to, request.recipient, 'EVM authorization recipient mismatch')
54
+ if (payload.value !== request.amount)
55
+ throw new VerificationFailedError({ reason: 'EVM authorization amount mismatch' })
56
+ if (!isX402Credential && payload.nonce !== Types.challengeHash(credential.challenge))
57
+ throw new VerificationFailedError({ reason: 'EVM authorization challenge hash mismatch' })
58
+ const now = BigInt(Math.floor(Date.now() / 1000))
59
+ if (BigInt(payload.validAfter) > now)
60
+ throw new VerificationFailedError({ reason: 'EVM authorization is not valid yet' })
61
+ if (BigInt(payload.validBefore) <= now)
62
+ throw new VerificationFailedError({ reason: 'EVM authorization has expired' })
63
+
64
+ const signer = await recoverTypedDataAddress({
65
+ domain: Types.authorizationDomain({
66
+ authorization: config.authorization,
67
+ chainId,
68
+ currency: request.currency as `0x${string}`,
69
+ }),
70
+ message: {
71
+ from: getAddress(payload.from),
72
+ nonce: payload.nonce as `0x${string}`,
73
+ to: getAddress(payload.to),
74
+ validAfter: BigInt(payload.validAfter),
75
+ validBefore: BigInt(payload.validBefore),
76
+ value: BigInt(payload.value),
77
+ },
78
+ primaryType: 'TransferWithAuthorization',
79
+ signature: payload.signature as `0x${string}`,
80
+ types: Types.authorizationTypes,
81
+ })
82
+ assertAddressEqual(signer, payload.from, 'EVM authorization signature mismatch')
83
+
84
+ const source = Types.toSource({ address: getAddress(payload.from), chainId })
85
+ if (credential.source && credential.source !== source) {
86
+ throw new VerificationFailedError({ reason: 'EVM authorization source mismatch' })
87
+ }
88
+
89
+ const settled = await config.settle({
90
+ credential,
91
+ payload,
92
+ request,
93
+ source,
94
+ })
95
+
96
+ return Receipt.from({
97
+ method: Types.paymentMethod,
98
+ reference: settled.reference,
99
+ status: 'success',
100
+ timestamp: settled.timestamp ?? new Date().toISOString(),
101
+ })
102
+ },
103
+ })
104
+ }
105
+
106
+ export declare namespace charge {
107
+ type Parameters = NativeConfig
108
+ type Native = Method.Server<typeof Methods.charge, Defaults>
109
+
110
+ type NativeConfig = BaseConfig & CurrencyConfig & RecipientConfig
111
+
112
+ type BaseConfig = {
113
+ /** EIP-3009 token domain metadata. Required for custom currency addresses; inferred for known assets. */
114
+ authorization?: Types.AuthorizationConfig | undefined
115
+ /** EVM chain ID. Required for custom currency addresses; inferred for known assets. */
116
+ chainId?: number | undefined
117
+ /** Token decimal places. Required for custom currency addresses; inferred for known assets. */
118
+ decimals?: number | undefined
119
+ /** Custom settlement override. If omitted, `x402.facilitator` is used. */
120
+ settle?: SettleAuthorization | undefined
121
+ /** x402 compatibility options. */
122
+ x402?: X402.Options | undefined
123
+ }
124
+
125
+ type CurrencyConfig = {
126
+ /** Token contract address or known EVM asset metadata. */
127
+ currency: `0x${string}` | Assets.KnownAsset
128
+ }
129
+
130
+ type RecipientConfig = {
131
+ /** Recipient wallet address. */
132
+ recipient: `0x${string}`
133
+ }
134
+
135
+ type RouteOptions = {
136
+ /** Required display-unit token amount. */
137
+ amount: string
138
+ /** Optional human-readable payment description. */
139
+ description?: string | undefined
140
+ /** Optional external correlation ID. */
141
+ externalId?: string | undefined
142
+ }
143
+
144
+ type SettleAuthorization = (parameters: {
145
+ credential: Credential.Credential<Types.AuthorizationPayload>
146
+ payload: Types.AuthorizationPayload
147
+ request: Types.ChargeRequest
148
+ source: ReturnType<typeof Types.toSource>
149
+ }) => Promise<{
150
+ reference: string
151
+ timestamp?: string | undefined
152
+ }>
153
+
154
+ type Defaults = {
155
+ chainId: number
156
+ currency: `0x${string}`
157
+ credentialTypes: ['authorization']
158
+ decimals: number
159
+ recipient: `0x${string}`
160
+ }
161
+ }
162
+
163
+ type ResolvedConfig = {
164
+ authorization: Types.AuthorizationConfig
165
+ chainId: number
166
+ currency: `0x${string}`
167
+ decimals: number
168
+ recipient: `0x${string}`
169
+ settle: charge.SettleAuthorization
170
+ x402: X402.ResolvedOptions
171
+ }
172
+
173
+ type HttpPath = {
174
+ bindCredential: NonNullable<ServerTransport.Http['bindCredential']>
175
+ captureRequest?: ServerTransport.Http['captureRequest'] | undefined
176
+ getCredential: ServerTransport.Http['getCredential']
177
+ respondChallenge: (
178
+ options: Parameters<ServerTransport.Http['respondChallenge']>[0],
179
+ response?: Response | undefined,
180
+ ) => Response | Promise<Response>
181
+ respondReceipt: (
182
+ options: Parameters<ServerTransport.Http['respondReceipt']>[0],
183
+ response: Response,
184
+ ) => Response
185
+ }
186
+
187
+ type HttpPaths = {
188
+ mpp: HttpPath
189
+ x402: HttpPath
190
+ }
191
+
192
+ function resolveConfig(config: charge.NativeConfig): ResolvedConfig {
193
+ const { currency, recipient } = config
194
+ let address: `0x${string}`
195
+ let authorization = config.authorization
196
+ let chainId = config.chainId
197
+ let decimals = config.decimals
198
+
199
+ if (Assets.isAsset(currency)) {
200
+ address = currency.address
201
+ chainId ??= Number(currency.network.slice('eip155:'.length))
202
+ decimals ??= currency.decimals
203
+ if (currency.transfer.type === Types.eip3009) {
204
+ authorization ??= {
205
+ name: currency.transfer.name,
206
+ version: currency.transfer.version,
207
+ }
208
+ }
209
+ } else {
210
+ address = currency
211
+ }
212
+
213
+ if (!authorization) throw new Error('EVM authorization requires `authorization` metadata.')
214
+ if (chainId === undefined) throw new Error('EVM authorization requires `chainId`.')
215
+ if (decimals === undefined) throw new Error('EVM authorization requires `decimals`.')
216
+
217
+ const x402 = X402.resolveOptions({
218
+ authorization,
219
+ options: config.x402,
220
+ })
221
+ const settle = config.settle ?? (x402?.facilitator ? X402.settleWithFacilitator(x402) : undefined)
222
+ if (!settle) throw new Error('EVM authorization requires `settle` or `x402.facilitator`.')
223
+
224
+ return {
225
+ authorization,
226
+ chainId,
227
+ currency: getAddress(address),
228
+ decimals,
229
+ recipient: getAddress(recipient),
230
+ settle,
231
+ x402,
232
+ }
233
+ }
234
+
235
+ function createPaths(config: ResolvedConfig): HttpPaths {
236
+ return {
237
+ mpp: createMppPath(),
238
+ x402: X402.createPath(config.x402),
239
+ }
240
+ }
241
+
242
+ function createMppPath(): HttpPath {
243
+ const transport = ServerTransport.http()
244
+ return {
245
+ bindCredential: (options) => transport.bindCredential?.(options) ?? options.credential,
246
+ captureRequest: transport.captureRequest,
247
+ getCredential: transport.getCredential,
248
+ respondChallenge: (options) => transport.respondChallenge(options),
249
+ respondReceipt: (options, response) => transport.respondReceipt({ ...options, response }),
250
+ }
251
+ }
252
+
253
+ function httpTransport(paths: HttpPaths): ServerTransport.Http {
254
+ return ServerTransport.from<Request, Response>({
255
+ name: 'evm-http',
256
+
257
+ captureRequest: paths.mpp.captureRequest,
258
+
259
+ getCredential(input) {
260
+ return paths.mpp.getCredential(input) ?? paths.x402.getCredential(input)
261
+ },
262
+
263
+ bindCredential(options) {
264
+ if (X402.isPendingCredential(options.credential)) return paths.x402.bindCredential(options)
265
+ return paths.mpp.bindCredential?.(options) ?? options.credential
266
+ },
267
+
268
+ async respondChallenge(options) {
269
+ const response = await paths.mpp.respondChallenge(options)
270
+ return paths.x402.respondChallenge(options, response)
271
+ },
272
+
273
+ respondReceipt(options) {
274
+ const response = paths.mpp.respondReceipt(options, options.response)
275
+ return paths.x402.respondReceipt(options, response)
276
+ },
277
+ })
278
+ }
279
+
280
+ function assertAddressEqual(actual: string, expected: string, reason: string) {
281
+ if (getAddress(actual) === getAddress(expected)) return
282
+ throw new VerificationFailedError({ reason })
283
+ }
@@ -0,0 +1,22 @@
1
+ import type { NoExtraKeys } from '../../internal/types.js'
2
+ import * as Assets from '../Assets.js'
3
+ import * as Chains from '../Chains.js'
4
+ import { charge as charge_ } from './Charge.js'
5
+
6
+ /** Creates EVM server methods from shared charge parameters. */
7
+ export function evm<const parameters extends evm.Parameters>(
8
+ parameters: NoExtraKeys<parameters, evm.Parameters>,
9
+ ) {
10
+ return [evm.charge(parameters)] as const
11
+ }
12
+
13
+ export namespace evm {
14
+ export type Parameters = charge_.Parameters
15
+
16
+ /** Creates an EVM `charge` server method. */
17
+ export const charge = charge_
18
+ /** Known EVM asset metadata for public config. */
19
+ export const assets = Assets
20
+ /** Common EVM chain IDs for public config. */
21
+ export const chains = Chains
22
+ }
@@ -0,0 +1,5 @@
1
+ export * as assets from '../Assets.js'
2
+ export * as Chains from '../Chains.js'
3
+ export * as chains from '../Chains.js'
4
+ export { charge } from './Charge.js'
5
+ export { evm } from './Methods.js'
package/src/index.ts CHANGED
@@ -3,9 +3,11 @@ export * as Challenge from './Challenge.js'
3
3
  export * as Credential from './Credential.js'
4
4
  export * as Errors from './Errors.js'
5
5
  export * as Expires from './Expires.js'
6
+ export * as evm from './evm/index.js'
6
7
  export * as Mcp from './Mcp.js'
7
8
  export * as Method from './Method.js'
8
9
  export * as PaymentRequest from './PaymentRequest.js'
9
10
  export * as Receipt from './Receipt.js'
10
11
  export * as Store from './Store.js'
12
+ export * as x402 from './x402/index.js'
11
13
  export * as z from './zod.js'
@@ -0,0 +1,36 @@
1
+ import { Base64 } from 'ox'
2
+
3
+ import type * as z from '../zod.js'
4
+
5
+ /**
6
+ * Creates a typed codec for JSON HTTP header values.
7
+ *
8
+ * x402 uses plain base64 JSON header bodies, while the Payment auth scheme uses
9
+ * its own base64url/JCS serializers. Keep this helper internal so transports
10
+ * can opt into the exact wire encoding their protocol expects.
11
+ */
12
+ export function createJson<const schema extends z.ZodMiniType>(schema: schema) {
13
+ type value = z.output<schema>
14
+
15
+ return {
16
+ encode(value: value): string {
17
+ return Base64.fromString(JSON.stringify(schema.parse(value)))
18
+ },
19
+ decode(value: string): value {
20
+ try {
21
+ return schema.parse(JSON.parse(Base64.toString(value))) as value
22
+ } catch {
23
+ throw new InvalidJsonHeaderError()
24
+ }
25
+ },
26
+ }
27
+ }
28
+
29
+ /** Error thrown when a JSON header value is not valid base64-encoded JSON. */
30
+ export class InvalidJsonHeaderError extends Error {
31
+ override readonly name = 'InvalidJsonHeaderError'
32
+
33
+ constructor() {
34
+ super('Invalid base64 JSON header.')
35
+ }
36
+ }
@@ -63,6 +63,31 @@ describe('payment', () => {
63
63
 
64
64
  server.close()
65
65
  })
66
+
67
+ test('copies transport-specific success headers', async () => {
68
+ const intent = () => async () => ({
69
+ status: 200 as const,
70
+ withReceipt: (response?: Response) =>
71
+ new Response(response?.body ?? null, {
72
+ headers: {
73
+ ...(response ? Object.fromEntries(response.headers) : {}),
74
+ 'PAYMENT-RESPONSE': 'x402-response',
75
+ },
76
+ status: response?.status ?? 200,
77
+ }),
78
+ })
79
+
80
+ const app = new Elysia().guard({ beforeHandle: payment(intent as any, {} as any) }, (app) =>
81
+ app.get('/', () => ({ data: 'content' })),
82
+ )
83
+
84
+ const server = await createServer(app)
85
+ const response = await globalThis.fetch(server.url)
86
+ expect(response.status).toBe(200)
87
+ expect(response.headers.get('PAYMENT-RESPONSE')).toBe('x402-response')
88
+
89
+ server.close()
90
+ })
66
91
  })
67
92
 
68
93
  function createChargeHarness(feePayer: boolean) {
@@ -67,8 +67,7 @@ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
67
67
  const managementResponse = getManagementResponse(result)
68
68
  if (managementResponse) return managementResponse
69
69
  const receipt = result.withReceipt(new Response())
70
- const header = receipt.headers.get('Payment-Receipt')
71
- if (header) set.headers['Payment-Receipt'] = header
70
+ for (const [key, value] of receipt.headers) set.headers[key] = value
72
71
  }
73
72
  }
74
73
 
@@ -162,6 +162,34 @@ describe('charge', () => {
162
162
  })
163
163
  })
164
164
 
165
+ describe('payment', () => {
166
+ test('copies transport-specific success headers', async () => {
167
+ const intent = () => async () => ({
168
+ status: 200 as const,
169
+ withReceipt: (response?: Response) =>
170
+ new Response(response?.body ?? null, {
171
+ headers: {
172
+ ...(response ? Object.fromEntries(response.headers) : {}),
173
+ 'PAYMENT-RESPONSE': 'x402-response',
174
+ },
175
+ status: response?.status ?? 200,
176
+ }),
177
+ })
178
+
179
+ const app = express()
180
+ app.get('/', payment(intent as any, {} as any), (_req, res) => {
181
+ res.json({ data: 'content' })
182
+ })
183
+
184
+ const server = await createServer(app)
185
+ const response = await globalThis.fetch(server.url)
186
+ expect(response.status).toBe(200)
187
+ expect(response.headers.get('PAYMENT-RESPONSE')).toBe('x402-response')
188
+
189
+ server.close()
190
+ })
191
+ })
192
+
165
193
  describe('session', () => {
166
194
  let escrowContract: Address
167
195
 
@@ -99,7 +99,7 @@ export function payment<const intent extends Mppx_internal.AnyMethodFn>(
99
99
  const originalJson = res.json.bind(res)
100
100
  res.json = (body: any) => {
101
101
  const wrapped = result.withReceipt(Response.json(body))
102
- res.setHeader('Payment-Receipt', wrapped.headers.get('Payment-Receipt')!)
102
+ for (const [key, value] of wrapped.headers) res.setHeader(key, value)
103
103
  return originalJson(body)
104
104
  }
105
105
 
@@ -1,9 +1,20 @@
1
1
  import { serve } from '@hono/node-server'
2
2
  import { Hono } from 'hono'
3
3
  import { Challenge, Credential, Method, Receipt, z } from 'mppx'
4
- import { Mppx as Mppx_client, session as sessionIntent, tempo as tempo_client } from 'mppx/client'
4
+ import {
5
+ evm as evm_client,
6
+ Mppx as Mppx_client,
7
+ session as sessionIntent,
8
+ tempo as tempo_client,
9
+ } from 'mppx/client'
5
10
  import { Mppx, discovery, payment } from 'mppx/hono'
6
- import { tempo as tempo_server } from 'mppx/server'
11
+ import { evm as evm_server, Mppx as ServerMppx, tempo as tempo_server } from 'mppx/server'
12
+ import {
13
+ paymentRequiredHeader,
14
+ paymentResponseHeader,
15
+ paymentSignatureHeader,
16
+ type PaymentPayload,
17
+ } from 'mppx/x402'
7
18
  import type { Address } from 'viem'
8
19
  import { Addresses } from 'viem/tempo'
9
20
  import { beforeAll, describe, expect, test } from 'vp/test'
@@ -53,6 +64,30 @@ describe('payment', () => {
53
64
 
54
65
  server.close()
55
66
  })
67
+
68
+ test('copies transport-specific success headers', async () => {
69
+ const intent = () => async () => ({
70
+ status: 200 as const,
71
+ withReceipt: (response?: Response) =>
72
+ new Response(response?.body ?? null, {
73
+ headers: {
74
+ ...(response ? Object.fromEntries(response.headers) : {}),
75
+ 'PAYMENT-RESPONSE': 'x402-response',
76
+ },
77
+ status: response?.status ?? 200,
78
+ }),
79
+ })
80
+
81
+ const app = new Hono()
82
+ app.get('/', payment(intent as any, {} as any), (c) => c.json({ data: 'content' }))
83
+
84
+ const server = await createServer(app)
85
+ const response = await globalThis.fetch(server.url)
86
+ expect(response.status).toBe(200)
87
+ expect(response.headers.get('PAYMENT-RESPONSE')).toBe('x402-response')
88
+
89
+ server.close()
90
+ })
56
91
  })
57
92
 
58
93
  const scopeMethod = Method.toServer(
@@ -187,8 +222,109 @@ describe('charge', () => {
187
222
 
188
223
  server.close()
189
224
  })
225
+
226
+ test('serves tempo and x402 from one Hono endpoint', async () => {
227
+ const transaction = `0x${'2'.repeat(64)}` as const
228
+ const payments = ServerMppx.create({
229
+ methods: [
230
+ tempo_server.charge({
231
+ account: accounts[0],
232
+ currency: asset,
233
+ getClient: () => client,
234
+ recipient: accounts[0].address,
235
+ }),
236
+ evm_server.charge({
237
+ currency: evm_server.assets.baseSepolia.USDC,
238
+ recipient: accounts[0].address,
239
+ x402: {
240
+ facilitator: {
241
+ async verify(paymentPayload: PaymentPayload) {
242
+ return {
243
+ isValid: true,
244
+ payer: payerOf(paymentPayload),
245
+ }
246
+ },
247
+ async settle(paymentPayload: PaymentPayload) {
248
+ return {
249
+ network: paymentPayload.accepted.network,
250
+ payer: payerOf(paymentPayload),
251
+ success: true,
252
+ transaction,
253
+ }
254
+ },
255
+ },
256
+ },
257
+ }),
258
+ ],
259
+ secretKey,
260
+ })
261
+
262
+ const route = payments.compose(
263
+ [payments.tempo.charge, { amount: '0', chainId: client.chain!.id }],
264
+ [payments.evm.charge, { amount: '0.01' }],
265
+ )
266
+
267
+ const app = new Hono()
268
+ app.get('/paid', async (c) => {
269
+ const result = await route(c.req.raw)
270
+ if (result.status === 402) return result.challenge
271
+ return result.withReceipt(c.json({ data: 'paid' }))
272
+ })
273
+
274
+ const server = await createServer(app)
275
+ const challenge = await globalThis.fetch(`${server.url}/paid`)
276
+ expect(challenge.status).toBe(402)
277
+ expect(challenge.headers.get('WWW-Authenticate')).toContain('Payment')
278
+ expect(challenge.headers.get(paymentRequiredHeader)).toBeTruthy()
279
+
280
+ const tempoPayment = Mppx_client.create({
281
+ methods: [
282
+ tempo_client.charge({
283
+ account: accounts[0],
284
+ getClient: () => client,
285
+ }),
286
+ ],
287
+ polyfill: false,
288
+ })
289
+ const tempoResponse = await tempoPayment.fetch(`${server.url}/paid`)
290
+ expect(tempoResponse.status).toBe(200)
291
+ expect(await tempoResponse.json()).toEqual({ data: 'paid' })
292
+ expect(tempoResponse.headers.get('Payment-Receipt')).toBeTruthy()
293
+
294
+ const x402Payment = Mppx_client.create({
295
+ methods: [
296
+ evm_client.charge({
297
+ account: accounts[0],
298
+ }),
299
+ ],
300
+ polyfill: false,
301
+ })
302
+ const paymentSignature = await x402Payment.createCredential(pureX402Challenge(challenge))
303
+ const x402Response = await x402Payment.rawFetch(`${server.url}/paid`, {
304
+ headers: { [paymentSignatureHeader]: paymentSignature },
305
+ })
306
+ expect(x402Response.status).toBe(200)
307
+ expect(await x402Response.json()).toEqual({ data: 'paid' })
308
+ expect(x402Response.headers.get(paymentResponseHeader)).toBeTruthy()
309
+
310
+ server.close()
311
+ })
190
312
  })
191
313
 
314
+ function payerOf(paymentPayload: PaymentPayload): string {
315
+ if ('authorization' in paymentPayload.payload) return paymentPayload.payload.authorization.from
316
+ return paymentPayload.payload.permit2Authorization.from
317
+ }
318
+
319
+ function pureX402Challenge(response: Response): Response {
320
+ const paymentRequired = response.headers.get(paymentRequiredHeader)
321
+ if (!paymentRequired) throw new Error('Missing PAYMENT-REQUIRED header.')
322
+ return new Response(null, {
323
+ headers: { [paymentRequiredHeader]: paymentRequired },
324
+ status: 402,
325
+ })
326
+ }
327
+
192
328
  describe('scope binding', () => {
193
329
  const scopeOpts = {
194
330
  amount: '1',
@@ -59,6 +59,28 @@ describe('payment', () => {
59
59
 
60
60
  server.close()
61
61
  })
62
+
63
+ test('copies transport-specific success headers', async () => {
64
+ const intent = () => async () => ({
65
+ status: 200 as const,
66
+ withReceipt: (response?: Response) =>
67
+ new Response(response?.body ?? null, {
68
+ headers: {
69
+ ...(response ? Object.fromEntries(response.headers) : {}),
70
+ 'PAYMENT-RESPONSE': 'x402-response',
71
+ },
72
+ status: response?.status ?? 200,
73
+ }),
74
+ })
75
+ const handler = payment(intent as any, {} as any, () => Response.json({ data: 'content' }))
76
+
77
+ const server = await createServer(handler)
78
+ const response = await globalThis.fetch(server.url)
79
+ expect(response.status).toBe(200)
80
+ expect(response.headers.get('PAYMENT-RESPONSE')).toBe('x402-response')
81
+
82
+ server.close()
83
+ })
62
84
  })
63
85
 
64
86
  function createChargeHarness(feePayer: boolean) {
@@ -13,6 +13,26 @@ describe('scrub', () => {
13
13
  expect(result.get('content-type')).toBe('application/json')
14
14
  })
15
15
 
16
+ test('behavior: strips payment protocol headers', () => {
17
+ const headers = new globalThis.Headers({
18
+ 'Accept-Payment': 'evm/charge',
19
+ 'Content-Type': 'application/json',
20
+ 'PAYMENT-RECEIPT': 'receipt',
21
+ 'PAYMENT-REQUIRED': 'required',
22
+ 'PAYMENT-RESPONSE': 'response',
23
+ 'PAYMENT-SIGNATURE': 'signature',
24
+ 'WWW-Authenticate': 'Payment id="abc"',
25
+ })
26
+ const result = Headers.scrub(headers)
27
+ expect(result.has('accept-payment')).toBe(false)
28
+ expect(result.has('payment-receipt')).toBe(false)
29
+ expect(result.has('payment-required')).toBe(false)
30
+ expect(result.has('payment-response')).toBe(false)
31
+ expect(result.has('payment-signature')).toBe(false)
32
+ expect(result.has('www-authenticate')).toBe(false)
33
+ expect(result.get('content-type')).toBe('application/json')
34
+ })
35
+
16
36
  test('behavior: strips cookie header', () => {
17
37
  const headers = new globalThis.Headers({
18
38
  Cookie: 'session=abc123',