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
@@ -0,0 +1,109 @@
1
+ import * as evmRoot from 'mppx/evm'
2
+ import {
3
+ assets as clientAssets,
4
+ chains as clientChains,
5
+ charge as clientCharge,
6
+ evm as clientEvm,
7
+ } from 'mppx/evm/client'
8
+ import { assets as serverAssets, charge as serverCharge, evm as serverEvm } from 'mppx/evm/server'
9
+ import type { Account } from 'viem'
10
+ import { describe, expectTypeOf, test } from 'vp/test'
11
+
12
+ import { Mppx as ClientMppx } from '../client/index.js'
13
+ import { Mppx as ServerMppx } from '../server/index.js'
14
+
15
+ const account = {} as Account
16
+ const recipient = '0x209693Bc6afc0C5328bA36FaF03C514EF312287C'
17
+ const secretKey = 'test-secret'
18
+ const settle = async () => ({
19
+ reference: `0x${'1'.repeat(64)}` as `0x${string}`,
20
+ })
21
+ const facilitator = {
22
+ settle: async () => ({
23
+ network: 'eip155:8453',
24
+ success: true,
25
+ transaction: `0x${'1'.repeat(64)}` as `0x${string}`,
26
+ }),
27
+ verify: async () => ({ isValid: true }),
28
+ }
29
+
30
+ describe('evm public interface', () => {
31
+ test('exports EVM asset metadata from root and subpaths', () => {
32
+ expectTypeOf(evmRoot.assets.base.USDC).toMatchTypeOf<typeof serverAssets.base.USDC>()
33
+ expectTypeOf(clientAssets.baseSepolia.USDC).toMatchTypeOf<
34
+ typeof serverAssets.baseSepolia.USDC
35
+ >()
36
+ expectTypeOf(evmRoot.chains.base).toMatchTypeOf<number>()
37
+ expectTypeOf(clientChains.baseSepolia).toMatchTypeOf<number>()
38
+ })
39
+
40
+ test('server charge works through subpath exports and tuple helper', () => {
41
+ const direct = serverCharge({
42
+ currency: serverAssets.base.USDC,
43
+ recipient,
44
+ x402: { facilitator },
45
+ })
46
+ expectTypeOf(direct.name).toEqualTypeOf<'evm'>()
47
+ expectTypeOf(direct.intent).toEqualTypeOf<'charge'>()
48
+
49
+ const mppx = ServerMppx.create({
50
+ methods: [
51
+ serverEvm({
52
+ currency: serverAssets.base.USDC,
53
+ recipient,
54
+ x402: { facilitator },
55
+ }),
56
+ ],
57
+ secretKey,
58
+ })
59
+
60
+ expectTypeOf(mppx.evm.charge).toBeFunction()
61
+ expectTypeOf(mppx.evm.charge({ amount: '0.01' })).toBeFunction()
62
+ })
63
+
64
+ test('server charge accepts a custom settlement override', () => {
65
+ const direct = serverCharge({
66
+ currency: serverAssets.base.USDC,
67
+ recipient,
68
+ settle,
69
+ })
70
+ expectTypeOf(direct.name).toEqualTypeOf<'evm'>()
71
+ expectTypeOf(direct.intent).toEqualTypeOf<'charge'>()
72
+ })
73
+
74
+ test('client charge works through subpath exports and tuple helper', () => {
75
+ const direct = clientCharge({
76
+ account,
77
+ currencies: [clientAssets.baseSepolia.USDC],
78
+ maxAmount: '0.01',
79
+ networks: [clientChains.baseSepolia],
80
+ })
81
+ expectTypeOf(direct.name).toEqualTypeOf<'evm'>()
82
+ expectTypeOf(direct.intent).toEqualTypeOf<'charge'>()
83
+
84
+ const mppx = ClientMppx.create({
85
+ methods: [
86
+ clientEvm({
87
+ account,
88
+ currencies: [clientAssets.baseSepolia.USDC],
89
+ maxAmount: '0.01',
90
+ networks: [clientEvm.chains.baseSepolia],
91
+ }),
92
+ ],
93
+ polyfill: false,
94
+ })
95
+
96
+ expectTypeOf(mppx.createCredential).toBeFunction()
97
+ })
98
+
99
+ test('server charge rejects x402 exact config shape', () => {
100
+ serverCharge({
101
+ // @ts-expect-error evm.charge takes shared charge config, not x402 exact config.
102
+ config: {
103
+ currency: serverAssets.base.USDC,
104
+ recipient,
105
+ x402: { facilitator },
106
+ },
107
+ })
108
+ })
109
+ })
@@ -0,0 +1,140 @@
1
+ import { Bytes, Hash } from 'ox'
2
+
3
+ import * as z from '../zod.js'
4
+
5
+ /** Payment method name for Payment-auth EVM challenges. */
6
+ export const paymentMethod = 'evm' as const
7
+
8
+ /** Payment intent name for one-time EVM charges. */
9
+ export const chargeIntent = 'charge' as const
10
+
11
+ /** CAIP-2 namespace prefix for EVM networks. */
12
+ export const evmNetworkPrefix = 'eip155:' as const
13
+
14
+ /** EIP-3009 transfer authorization method identifier. */
15
+ export const eip3009 = 'eip3009' as const
16
+
17
+ /** EVM charge credential types supported by this package. */
18
+ export const credentialTypes = ['authorization'] as const
19
+
20
+ const atomicAmount = z.string().check(z.regex(/^\d+$/, 'Invalid atomic amount'))
21
+
22
+ /** EIP-3009 domain metadata for `transferWithAuthorization` signatures. */
23
+ export const AuthorizationConfigSchema = z.object({
24
+ name: z.string().check(z.minLength(1)),
25
+ version: z.string().check(z.minLength(1)),
26
+ })
27
+ export type AuthorizationConfig = z.infer<typeof AuthorizationConfigSchema>
28
+
29
+ /** CAIP-2 EVM network identifier. */
30
+ export type EvmNetwork = `${typeof evmNetworkPrefix}${number}`
31
+
32
+ /** EVM-specific charge method details. */
33
+ export const MethodDetailsSchema = z.object({
34
+ chainId: z.number(),
35
+ credentialTypes: z.optional(z.array(z.enum(credentialTypes)).check(z.minLength(1))),
36
+ decimals: z.optional(z.number()),
37
+ permit2Address: z.optional(z.address()),
38
+ splits: z.optional(
39
+ z.array(
40
+ z.object({
41
+ amount: atomicAmount,
42
+ recipient: z.address(),
43
+ }),
44
+ ),
45
+ ),
46
+ })
47
+ export type MethodDetails = z.infer<typeof MethodDetailsSchema>
48
+
49
+ /** Canonical Payment-auth EVM charge request. */
50
+ export const ChargeRequestSchema = z.object({
51
+ amount: atomicAmount,
52
+ currency: z.address(),
53
+ description: z.optional(z.string()),
54
+ externalId: z.optional(z.string()),
55
+ methodDetails: MethodDetailsSchema,
56
+ recipient: z.address(),
57
+ })
58
+ export type ChargeRequest = z.infer<typeof ChargeRequestSchema>
59
+
60
+ /** Public route input before display-unit amounts are converted to atomic units. */
61
+ export const ChargeRequestInputSchema = z.object({
62
+ amount: z.amount(),
63
+ chainId: z.number(),
64
+ currency: z.address(),
65
+ credentialTypes: z.optional(z.array(z.enum(credentialTypes)).check(z.minLength(1))),
66
+ decimals: z.number(),
67
+ description: z.optional(z.string()),
68
+ externalId: z.optional(z.string()),
69
+ permit2Address: z.optional(z.address()),
70
+ recipient: z.address(),
71
+ splits: z.optional(
72
+ z.array(
73
+ z.object({
74
+ amount: atomicAmount,
75
+ recipient: z.address(),
76
+ }),
77
+ ),
78
+ ),
79
+ })
80
+ export type ChargeRequestInput = z.infer<typeof ChargeRequestInputSchema>
81
+
82
+ /** Payment-auth EVM authorization credential payload. */
83
+ export const AuthorizationPayloadSchema = z.object({
84
+ from: z.address(),
85
+ nonce: z.hash(),
86
+ signature: z.signature(),
87
+ to: z.address(),
88
+ type: z.literal('authorization'),
89
+ validAfter: atomicAmount,
90
+ validBefore: atomicAmount,
91
+ value: atomicAmount,
92
+ })
93
+ export type AuthorizationPayload = z.infer<typeof AuthorizationPayloadSchema>
94
+
95
+ /** Payment-auth EVM charge credential payload. */
96
+ export const ChargePayloadSchema = AuthorizationPayloadSchema
97
+ export type ChargePayload = z.infer<typeof ChargePayloadSchema>
98
+
99
+ /** EIP-712 type definition for EIP-3009 `transferWithAuthorization`. */
100
+ export const authorizationTypes = {
101
+ TransferWithAuthorization: [
102
+ { name: 'from', type: 'address' },
103
+ { name: 'to', type: 'address' },
104
+ { name: 'value', type: 'uint256' },
105
+ { name: 'validAfter', type: 'uint256' },
106
+ { name: 'validBefore', type: 'uint256' },
107
+ { name: 'nonce', type: 'bytes32' },
108
+ ],
109
+ } as const
110
+
111
+ /** Returns the EIP-712 domain for an EIP-3009 authorization signature. */
112
+ export function authorizationDomain(parameters: {
113
+ authorization: AuthorizationConfig
114
+ chainId: number
115
+ currency: `0x${string}`
116
+ }) {
117
+ return {
118
+ chainId: parameters.chainId,
119
+ name: parameters.authorization.name,
120
+ verifyingContract: parameters.currency,
121
+ version: parameters.authorization.version,
122
+ } as const
123
+ }
124
+
125
+ /** Computes the Payment-auth EVM challenge hash used as the EIP-3009 nonce. */
126
+ export function challengeHash(challenge: { id: string; realm: string }): `0x${string}` {
127
+ return Hash.keccak256(Bytes.fromString(`${challenge.id}${challenge.realm}`), {
128
+ as: 'Hex',
129
+ }) as `0x${string}`
130
+ }
131
+
132
+ /** Converts a chain ID to the CAIP-2 EVM network identifier. */
133
+ export function networkOf(chainId: number): EvmNetwork {
134
+ return `${evmNetworkPrefix}${chainId}`
135
+ }
136
+
137
+ /** Formats an EVM address as a did:pkh source identifier. */
138
+ export function toSource(parameters: { address: `0x${string}`; chainId: number }) {
139
+ return `did:pkh:eip155:${parameters.chainId}:${parameters.address}` as const
140
+ }
@@ -0,0 +1,99 @@
1
+ import { Challenge } from 'mppx'
2
+ import type { Account } from 'viem'
3
+ import { describe, expect, test, vi } from 'vp/test'
4
+
5
+ import * as Assets from '../Assets.js'
6
+ import * as Chains from '../Chains.js'
7
+ import { charge } from './Charge.js'
8
+
9
+ const account = {
10
+ address: '0x1111111111111111111111111111111111111111',
11
+ signTypedData: vi.fn(async () => '0x1234'),
12
+ } as unknown as Account
13
+
14
+ describe('evm charge client', () => {
15
+ test('does not sign x402 payloads from unbranded Payment-auth challenges', async () => {
16
+ const signTypedData = vi.fn(async () => '0x1234')
17
+ const client = charge({
18
+ account: {
19
+ ...account,
20
+ signTypedData,
21
+ } as unknown as Account,
22
+ currencies: [Assets.baseSepolia.USDC],
23
+ maxAmount: '0.01',
24
+ networks: [Chains.baseSepolia],
25
+ })
26
+ const challenge = Challenge.from({
27
+ id: 'attacker-controlled',
28
+ intent: 'charge',
29
+ method: 'evm',
30
+ realm: 'api.example.com',
31
+ request: {
32
+ amount: '10000',
33
+ asset: Assets.baseSepolia.USDC.address,
34
+ maxTimeoutSeconds: 60,
35
+ network: 'eip155:84532',
36
+ payTo: '0x2222222222222222222222222222222222222222',
37
+ scheme: 'exact',
38
+ },
39
+ })
40
+
41
+ await expect(client.createCredential({ challenge } as never)).rejects.toThrow()
42
+ expect(signTypedData).not.toHaveBeenCalled()
43
+ })
44
+
45
+ test('does not use server-supplied decimals for maxAmount policy', async () => {
46
+ const client = charge({
47
+ account,
48
+ maxAmount: '1',
49
+ })
50
+ const challenge = Challenge.from({
51
+ id: 'native',
52
+ intent: 'charge',
53
+ method: 'evm',
54
+ realm: 'api.example.com',
55
+ request: {
56
+ amount: '1000000000000000000',
57
+ currency: Assets.baseSepolia.USDC.address,
58
+ methodDetails: {
59
+ chainId: 84532,
60
+ credentialTypes: ['authorization'],
61
+ decimals: 18,
62
+ },
63
+ recipient: '0x2222222222222222222222222222222222222222',
64
+ },
65
+ })
66
+
67
+ await expect(client.createCredential({ challenge } as never)).rejects.toThrow(
68
+ 'EVM charge maxAmount requires currency decimals.',
69
+ )
70
+ })
71
+
72
+ test('requires network policy for raw hex currencies', async () => {
73
+ const client = charge({
74
+ account,
75
+ currencies: [Assets.baseSepolia.USDC.address],
76
+ maxAmount: '1',
77
+ })
78
+ const challenge = Challenge.from({
79
+ id: 'native',
80
+ intent: 'charge',
81
+ method: 'evm',
82
+ realm: 'api.example.com',
83
+ request: {
84
+ amount: '1000000',
85
+ currency: Assets.baseSepolia.USDC.address,
86
+ methodDetails: {
87
+ chainId: 84532,
88
+ credentialTypes: ['authorization'],
89
+ decimals: 6,
90
+ },
91
+ recipient: '0x2222222222222222222222222222222222222222',
92
+ },
93
+ })
94
+
95
+ await expect(client.createCredential({ challenge } as never)).rejects.toThrow(
96
+ 'EVM raw currency allowlists require networks.',
97
+ )
98
+ })
99
+ })
@@ -0,0 +1,198 @@
1
+ import type { Account } from 'viem'
2
+ import { getAddress, parseUnits } from 'viem'
3
+
4
+ import * as Credential from '../../Credential.js'
5
+ import * as Method from '../../Method.js'
6
+ import * as x402_Exact from '../../x402/client/Exact.js'
7
+ import * as x402_ChallengeBrand from '../../x402/internal/ChallengeBrand.js'
8
+ import * as z from '../../zod.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 client method.
15
+ *
16
+ * Signs native Payment-auth `authorization` credentials for EVM charges. It
17
+ * also keeps x402 exact signing for x402 compatibility challenges.
18
+ */
19
+ export function charge(parameters: charge.Parameters) {
20
+ return Method.toClient(Methods.charge, {
21
+ context: z.object({
22
+ account: z.optional(z.custom<Account>()),
23
+ }),
24
+ async createCredential({ challenge, context }) {
25
+ if (isX402Challenge(challenge))
26
+ return x402_Exact.createCredential({
27
+ challenge: challenge as never,
28
+ config: parameters as x402_Exact.Config,
29
+ context,
30
+ })
31
+
32
+ const account = (context?.account ?? parameters.account) as charge.Signer
33
+ if (!account.signTypedData) throw new Error('EVM authorization requires a typed-data signer.')
34
+
35
+ const request = challenge.request as Types.ChargeRequest
36
+ assertPolicy(parameters, request)
37
+ if (!request.methodDetails.credentialTypes?.includes('authorization')) {
38
+ throw new Error('EVM authorization is not accepted by this challenge.')
39
+ }
40
+ if (request.methodDetails.splits?.length) {
41
+ throw new Error('EVM authorization does not support payment splits.')
42
+ }
43
+
44
+ const authorization = resolveAuthorization(parameters, request)
45
+ const validBefore = challenge.expires
46
+ ? Math.floor(new Date(challenge.expires).getTime() / 1000).toString()
47
+ : (Math.floor(Date.now() / 1000) + 300).toString()
48
+
49
+ const payload: Types.AuthorizationPayload = {
50
+ from: getAddress(account.address),
51
+ nonce: Types.challengeHash(challenge),
52
+ signature: await account.signTypedData({
53
+ domain: Types.authorizationDomain({
54
+ authorization,
55
+ chainId: request.methodDetails.chainId,
56
+ currency: request.currency as `0x${string}`,
57
+ }),
58
+ message: {
59
+ from: getAddress(account.address),
60
+ nonce: Types.challengeHash(challenge),
61
+ to: getAddress(request.recipient),
62
+ validAfter: 0n,
63
+ validBefore: BigInt(validBefore),
64
+ value: BigInt(request.amount),
65
+ },
66
+ primaryType: 'TransferWithAuthorization',
67
+ types: Types.authorizationTypes,
68
+ }),
69
+ to: getAddress(request.recipient),
70
+ type: 'authorization',
71
+ validAfter: '0',
72
+ validBefore,
73
+ value: request.amount,
74
+ }
75
+
76
+ return Credential.serialize(
77
+ Credential.from({
78
+ challenge,
79
+ payload,
80
+ source: Types.toSource({
81
+ address: getAddress(account.address),
82
+ chainId: request.methodDetails.chainId,
83
+ }),
84
+ }),
85
+ )
86
+ },
87
+ })
88
+ }
89
+
90
+ export declare namespace charge {
91
+ type Signer = Account & {
92
+ signTypedData?: (parameters: any) => Promise<`0x${string}`>
93
+ }
94
+
95
+ type Parameters = {
96
+ /** Account used to sign EVM charge credentials. */
97
+ account: Account
98
+ /** EIP-3009 token domain metadata for custom currencies. */
99
+ authorization?: Types.AuthorizationConfig | undefined
100
+ /** Optional token decimals used to parse `maxAmount` when currency metadata is not provided. */
101
+ decimals?: number | undefined
102
+ /** Optional maximum display-unit amount the client is willing to pay. */
103
+ maxAmount?: string | undefined
104
+ /** Optional maximum atomic amount the client is willing to pay. */
105
+ maxAtomicAmount?: string | undefined
106
+ /** Optional allowlist of supported EVM chain IDs. */
107
+ networks?: readonly number[] | undefined
108
+ /** Optional allowlist of supported currencies. */
109
+ currencies?: readonly (`0x${string}` | Assets.KnownAsset)[] | undefined
110
+ /** Legacy alias for `currencies`. */
111
+ assets?: readonly (`0x${string}` | Assets.KnownAsset)[] | undefined
112
+ }
113
+ }
114
+
115
+ function isX402Challenge(challenge: { request: Record<string, unknown> }) {
116
+ return (
117
+ x402_ChallengeBrand.is(challenge) &&
118
+ challenge.request.scheme === 'exact' &&
119
+ typeof challenge.request.network === 'string'
120
+ )
121
+ }
122
+
123
+ function assertPolicy(parameters: charge.Parameters, request: Types.ChargeRequest) {
124
+ const chainId = request.methodDetails.chainId
125
+ const network = Types.networkOf(chainId)
126
+ if (parameters.networks && !parameters.networks.includes(chainId))
127
+ throw new Error(`EVM chain ID is not allowed: ${chainId}.`)
128
+
129
+ const currencies = parameters.currencies ?? parameters.assets
130
+ if (currencies?.some((currency) => !Assets.isAsset(currency)) && !parameters.networks?.length)
131
+ throw new Error('EVM raw currency allowlists require networks.')
132
+ if (currencies) {
133
+ const acceptedCurrency = getAddress(request.currency as `0x${string}`)
134
+ const allowed = currencies.some((currency) =>
135
+ currencyMatches(currency, acceptedCurrency, network),
136
+ )
137
+ if (!allowed) throw new Error(`EVM currency is not allowed: ${acceptedCurrency}.`)
138
+ }
139
+
140
+ if (
141
+ parameters.maxAtomicAmount !== undefined &&
142
+ BigInt(request.amount) > BigInt(parameters.maxAtomicAmount)
143
+ )
144
+ throw new Error('EVM charge amount exceeds maxAtomicAmount.')
145
+
146
+ if (parameters.maxAmount !== undefined) {
147
+ const decimals = decimalsOfAcceptedCurrency(parameters, request)
148
+ if (decimals === undefined) throw new Error('EVM charge maxAmount requires currency decimals.')
149
+ if (BigInt(request.amount) > parseUnits(parameters.maxAmount, decimals))
150
+ throw new Error('EVM charge amount exceeds maxAmount.')
151
+ }
152
+ }
153
+
154
+ function resolveAuthorization(
155
+ parameters: charge.Parameters,
156
+ request: Types.ChargeRequest,
157
+ ): Types.AuthorizationConfig {
158
+ const currencies = parameters.currencies ?? parameters.assets
159
+ const acceptedCurrency = getAddress(request.currency as `0x${string}`)
160
+ const network = Types.networkOf(request.methodDetails.chainId)
161
+ const currency = currencies?.find((currency) =>
162
+ currencyMatches(currency, acceptedCurrency, network),
163
+ )
164
+ if (currency && Assets.isAsset(currency) && currency.transfer.type === Types.eip3009)
165
+ return {
166
+ name: currency.transfer.name,
167
+ version: currency.transfer.version,
168
+ }
169
+ if (parameters.authorization) return parameters.authorization
170
+ throw new Error('EVM authorization requires token name and version.')
171
+ }
172
+
173
+ function addressOf(currency: `0x${string}` | Assets.KnownAsset): `0x${string}` {
174
+ return Assets.isAsset(currency) ? currency.address : currency
175
+ }
176
+
177
+ function currencyMatches(
178
+ currency: `0x${string}` | Assets.KnownAsset,
179
+ acceptedCurrency: `0x${string}`,
180
+ network: Types.EvmNetwork,
181
+ ): boolean {
182
+ if (getAddress(addressOf(currency)) !== acceptedCurrency) return false
183
+ return !Assets.isAsset(currency) || currency.network === network
184
+ }
185
+
186
+ function decimalsOfAcceptedCurrency(
187
+ parameters: charge.Parameters,
188
+ request: Types.ChargeRequest,
189
+ ): number | undefined {
190
+ const currencies = parameters.currencies ?? parameters.assets
191
+ const acceptedCurrency = getAddress(request.currency as `0x${string}`)
192
+ const network = Types.networkOf(request.methodDetails.chainId)
193
+ const currency = currencies?.find((currency) =>
194
+ currencyMatches(currency, acceptedCurrency, network),
195
+ )
196
+ if (currency && Assets.isAsset(currency)) return currency.decimals
197
+ return parameters.decimals
198
+ }
@@ -0,0 +1,19 @@
1
+ import * as Assets from '../Assets.js'
2
+ import * as Chains from '../Chains.js'
3
+ import { charge as charge_ } from './Charge.js'
4
+
5
+ /** Creates EVM client methods from shared charge parameters. */
6
+ export function evm(parameters: evm.Parameters) {
7
+ return [evm.charge(parameters)] as const
8
+ }
9
+
10
+ export namespace evm {
11
+ export type Parameters = charge_.Parameters
12
+
13
+ /** Creates an EVM `charge` client method. */
14
+ export const charge = charge_
15
+ /** Known EVM asset metadata for public config. */
16
+ export const assets = Assets
17
+ /** Common EVM chain IDs for public config. */
18
+ export const chains = Chains
19
+ }
@@ -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'
@@ -0,0 +1,13 @@
1
+ export * as Assets from './Assets.js'
2
+ export * as Chains from './Chains.js'
3
+ export * as Methods from './Methods.js'
4
+ export * as Types from './Types.js'
5
+ export * as assets from './Assets.js'
6
+ export * as chains from './Chains.js'
7
+ export * from './Types.js'
8
+ export type {
9
+ ExactEip3009Transfer,
10
+ ExactPermit2Transfer,
11
+ ExactTransfer,
12
+ ResourceInfo,
13
+ } from '../x402/Types.js'