mppx 0.7.0 → 0.8.1

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 (290) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +20 -11
  3. package/dist/Challenge.d.ts.map +1 -1
  4. package/dist/Challenge.js +18 -6
  5. package/dist/Challenge.js.map +1 -1
  6. package/dist/Mcp.d.ts +3 -0
  7. package/dist/Mcp.d.ts.map +1 -1
  8. package/dist/Mcp.js +2 -0
  9. package/dist/Mcp.js.map +1 -1
  10. package/dist/PaymentRequest.d.ts +10 -10
  11. package/dist/PaymentRequest.js +8 -8
  12. package/dist/cli/internal.d.ts +1 -0
  13. package/dist/cli/internal.d.ts.map +1 -1
  14. package/dist/cli/internal.js +1 -15
  15. package/dist/cli/internal.js.map +1 -1
  16. package/dist/client/Mppx.js +2 -2
  17. package/dist/client/Mppx.js.map +1 -1
  18. package/dist/client/Transport.d.ts +11 -16
  19. package/dist/client/Transport.d.ts.map +1 -1
  20. package/dist/client/Transport.js +55 -75
  21. package/dist/client/Transport.js.map +1 -1
  22. package/dist/client/index.d.ts +3 -0
  23. package/dist/client/index.d.ts.map +1 -1
  24. package/dist/client/index.js +1 -0
  25. package/dist/client/index.js.map +1 -1
  26. package/dist/client/internal/Fetch.d.ts.map +1 -1
  27. package/dist/client/internal/Fetch.js +46 -7
  28. package/dist/client/internal/Fetch.js.map +1 -1
  29. package/dist/client/internal/protocols/Mcp.d.ts +7 -0
  30. package/dist/client/internal/protocols/Mcp.d.ts.map +1 -0
  31. package/dist/client/internal/protocols/Mcp.js +159 -0
  32. package/dist/client/internal/protocols/Mcp.js.map +1 -0
  33. package/dist/client/internal/protocols/Mpp.d.ts +4 -0
  34. package/dist/client/internal/protocols/Mpp.d.ts.map +1 -0
  35. package/dist/client/internal/protocols/Mpp.js +18 -0
  36. package/dist/client/internal/protocols/Mpp.js.map +1 -0
  37. package/dist/client/internal/protocols/Protocol.d.ts +10 -0
  38. package/dist/client/internal/protocols/Protocol.d.ts.map +1 -0
  39. package/dist/client/internal/protocols/Protocol.js +2 -0
  40. package/dist/client/internal/protocols/Protocol.js.map +1 -0
  41. package/dist/client/internal/protocols/Shared.d.ts +5 -0
  42. package/dist/client/internal/protocols/Shared.d.ts.map +1 -0
  43. package/dist/client/internal/protocols/Shared.js +20 -0
  44. package/dist/client/internal/protocols/Shared.js.map +1 -0
  45. package/dist/client/internal/protocols/X402.d.ts +8 -0
  46. package/dist/client/internal/protocols/X402.d.ts.map +1 -0
  47. package/dist/client/internal/protocols/X402.js +39 -0
  48. package/dist/client/internal/protocols/X402.js.map +1 -0
  49. package/dist/evm/client/index.d.ts +1 -0
  50. package/dist/evm/client/index.d.ts.map +1 -1
  51. package/dist/evm/client/index.js +1 -0
  52. package/dist/evm/client/index.js.map +1 -1
  53. package/dist/evm/index.d.ts +2 -0
  54. package/dist/evm/index.d.ts.map +1 -1
  55. package/dist/evm/index.js +2 -0
  56. package/dist/evm/index.js.map +1 -1
  57. package/dist/evm/server/index.d.ts +1 -0
  58. package/dist/evm/server/index.d.ts.map +1 -1
  59. package/dist/evm/server/index.js +1 -0
  60. package/dist/evm/server/index.js.map +1 -1
  61. package/dist/mcp/client/McpClient.d.ts +101 -0
  62. package/dist/mcp/client/McpClient.d.ts.map +1 -0
  63. package/dist/mcp/client/McpClient.js +162 -0
  64. package/dist/mcp/client/McpClient.js.map +1 -0
  65. package/dist/mcp/client/index.d.ts.map +1 -0
  66. package/dist/mcp/client/index.js.map +1 -0
  67. package/dist/mcp/server/Transport.d.ts.map +1 -0
  68. package/dist/mcp/server/Transport.js.map +1 -0
  69. package/dist/mcp/server/index.d.ts.map +1 -0
  70. package/dist/mcp/server/index.js.map +1 -0
  71. package/dist/server/Mppx.d.ts +1 -1
  72. package/dist/server/Mppx.d.ts.map +1 -1
  73. package/dist/server/Mppx.js +9 -0
  74. package/dist/server/Mppx.js.map +1 -1
  75. package/dist/server/Transport.d.ts +1 -1
  76. package/dist/server/Transport.d.ts.map +1 -1
  77. package/dist/server/Transport.js +1 -1
  78. package/dist/server/Transport.js.map +1 -1
  79. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  80. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  81. package/dist/stripe/server/internal/html.gen.js +1 -1
  82. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  83. package/dist/tempo/Proof.d.ts +85 -1
  84. package/dist/tempo/Proof.d.ts.map +1 -1
  85. package/dist/tempo/Proof.js +35 -0
  86. package/dist/tempo/Proof.js.map +1 -1
  87. package/dist/tempo/client/Charge.d.ts +13 -1
  88. package/dist/tempo/client/Charge.d.ts.map +1 -1
  89. package/dist/tempo/client/Charge.js +38 -25
  90. package/dist/tempo/client/Charge.js.map +1 -1
  91. package/dist/tempo/client/Methods.d.ts +5 -3
  92. package/dist/tempo/client/Methods.d.ts.map +1 -1
  93. package/dist/tempo/client/Methods.js +4 -2
  94. package/dist/tempo/client/Methods.js.map +1 -1
  95. package/dist/tempo/client/ResolveAccount.d.ts +40 -0
  96. package/dist/tempo/client/ResolveAccount.d.ts.map +1 -0
  97. package/dist/tempo/client/ResolveAccount.js +2 -0
  98. package/dist/tempo/client/ResolveAccount.js.map +1 -0
  99. package/dist/tempo/internal/fee-payer.d.ts +26 -1
  100. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  101. package/dist/tempo/internal/fee-payer.js +83 -30
  102. package/dist/tempo/internal/fee-payer.js.map +1 -1
  103. package/dist/tempo/internal/proof.d.ts +71 -5
  104. package/dist/tempo/internal/proof.d.ts.map +1 -1
  105. package/dist/tempo/internal/proof.js +42 -6
  106. package/dist/tempo/internal/proof.js.map +1 -1
  107. package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -1
  108. package/dist/tempo/legacy/client/SessionManager.js +10 -3
  109. package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
  110. package/dist/tempo/server/Charge.d.ts.map +1 -1
  111. package/dist/tempo/server/Charge.js +46 -18
  112. package/dist/tempo/server/Charge.js.map +1 -1
  113. package/dist/tempo/server/Methods.d.ts +4 -2
  114. package/dist/tempo/server/Methods.d.ts.map +1 -1
  115. package/dist/tempo/server/Methods.js +4 -2
  116. package/dist/tempo/server/Methods.js.map +1 -1
  117. package/dist/tempo/server/Subscription.d.ts +10 -0
  118. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  119. package/dist/tempo/server/Subscription.js +135 -23
  120. package/dist/tempo/server/Subscription.js.map +1 -1
  121. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  122. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  123. package/dist/tempo/server/internal/html.gen.js +1 -1
  124. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  125. package/dist/tempo/session/client/ChannelOps.d.ts +2 -3
  126. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  127. package/dist/tempo/session/client/ChannelOps.js +7 -10
  128. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  129. package/dist/tempo/session/client/ChannelStore.d.ts +51 -0
  130. package/dist/tempo/session/client/ChannelStore.d.ts.map +1 -0
  131. package/dist/tempo/session/client/ChannelStore.js +63 -0
  132. package/dist/tempo/session/client/ChannelStore.js.map +1 -0
  133. package/dist/tempo/session/client/CredentialState.d.ts +7 -24
  134. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  135. package/dist/tempo/session/client/CredentialState.js +51 -49
  136. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  137. package/dist/tempo/session/client/Session.d.ts +8 -2
  138. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  139. package/dist/tempo/session/client/Session.js +22 -8
  140. package/dist/tempo/session/client/Session.js.map +1 -1
  141. package/dist/tempo/session/client/SessionManager.d.ts +4 -40
  142. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  143. package/dist/tempo/session/client/SessionManager.js +124 -174
  144. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  145. package/dist/tempo/session/client/index.d.ts +3 -4
  146. package/dist/tempo/session/client/index.d.ts.map +1 -1
  147. package/dist/tempo/session/client/index.js +1 -0
  148. package/dist/tempo/session/client/index.js.map +1 -1
  149. package/dist/tempo/session/precompile/Voucher.d.ts +3 -3
  150. package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
  151. package/dist/tempo/session/precompile/Voucher.js +24 -25
  152. package/dist/tempo/session/precompile/Voucher.js.map +1 -1
  153. package/dist/tempo/session/server/CredentialVerification.d.ts +6 -0
  154. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  155. package/dist/tempo/session/server/CredentialVerification.js +13 -0
  156. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  157. package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
  158. package/dist/tempo/session/server/Settlement.js +4 -2
  159. package/dist/tempo/session/server/Settlement.js.map +1 -1
  160. package/dist/tempo/session/server/Sse.d.ts.map +1 -1
  161. package/dist/tempo/session/server/Sse.js.map +1 -1
  162. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  163. package/dist/tempo/session/server/Ws.js.map +1 -1
  164. package/dist/tempo/subscription/KeyAuthorization.d.ts +712 -1
  165. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
  166. package/dist/tempo/subscription/Store.d.ts +2 -0
  167. package/dist/tempo/subscription/Store.d.ts.map +1 -1
  168. package/dist/tempo/subscription/Store.js +16 -1
  169. package/dist/tempo/subscription/Store.js.map +1 -1
  170. package/dist/x402/index.d.ts +1 -0
  171. package/dist/x402/index.d.ts.map +1 -1
  172. package/dist/x402/index.js +1 -0
  173. package/dist/x402/index.js.map +1 -1
  174. package/package.json +21 -10
  175. package/src/Challenge.test.ts +40 -0
  176. package/src/Challenge.ts +19 -6
  177. package/src/Mcp.ts +4 -0
  178. package/src/PaymentRequest.ts +10 -10
  179. package/src/cli/cli.test.ts +15 -15
  180. package/src/cli/config.test.ts +13 -7
  181. package/src/cli/internal.ts +1 -16
  182. package/src/client/Mppx.test-d.ts +21 -1
  183. package/src/client/Mppx.test.ts +1 -1
  184. package/src/client/Mppx.ts +2 -2
  185. package/src/client/Transport.test.ts +225 -178
  186. package/src/client/Transport.ts +77 -83
  187. package/src/client/index.ts +14 -0
  188. package/src/client/internal/Fetch.test.ts +207 -2
  189. package/src/client/internal/Fetch.ts +52 -6
  190. package/src/client/internal/protocols/Mcp.test.ts +220 -0
  191. package/src/client/internal/protocols/Mcp.ts +162 -0
  192. package/src/client/internal/protocols/Mpp.ts +21 -0
  193. package/src/client/internal/protocols/Protocol.ts +10 -0
  194. package/src/client/internal/protocols/Shared.ts +25 -0
  195. package/src/client/internal/protocols/X402.ts +42 -0
  196. package/src/discovery/OpenApi.test.ts +1 -1
  197. package/src/evm/PublicInterface.test-d.ts +1 -1
  198. package/src/evm/client/index.ts +1 -0
  199. package/src/evm/index.ts +2 -0
  200. package/src/evm/server/Charge.test.ts +1 -1
  201. package/src/evm/server/index.ts +1 -0
  202. package/src/{mcp-sdk → mcp}/client/McpClient.integration.test.ts +10 -4
  203. package/src/{mcp-sdk → mcp}/client/McpClient.test-d.ts +45 -18
  204. package/src/{mcp-sdk → mcp}/client/McpClient.test.ts +211 -5
  205. package/src/mcp/client/McpClient.ts +307 -0
  206. package/src/{mcp-sdk → mcp}/client/McpClient.unit.test.ts +9 -5
  207. package/src/middlewares/elysia.test.ts +1 -1
  208. package/src/middlewares/express.test.ts +1 -1
  209. package/src/middlewares/hono.test.ts +1 -1
  210. package/src/middlewares/internal/mppx.test.ts +1 -1
  211. package/src/middlewares/nextjs.test.ts +1 -1
  212. package/src/proxy/Proxy.test.ts +1 -1
  213. package/src/proxy/services/anthropic.test.ts +1 -1
  214. package/src/proxy/services/openai.test.ts +1 -1
  215. package/src/proxy/services/stripe.test.ts +1 -1
  216. package/src/server/Mppx.authorize.test.ts +1 -1
  217. package/src/server/Mppx.test-d.ts +1 -1
  218. package/src/server/Mppx.test.ts +20 -2
  219. package/src/server/Mppx.ts +14 -1
  220. package/src/server/Transport.test.ts +6 -6
  221. package/src/server/Transport.ts +1 -1
  222. package/src/stripe/Charge.integration.test.ts +1 -1
  223. package/src/stripe/client/Charge.test.ts +1 -1
  224. package/src/stripe/server/Charge.test.ts +1 -1
  225. package/src/stripe/server/internal/html/package.json +1 -1
  226. package/src/stripe/server/internal/html.gen.ts +1 -1
  227. package/src/tempo/Proof.conformance.test.ts +146 -0
  228. package/src/tempo/Proof.test-d.ts +15 -0
  229. package/src/tempo/Proof.ts +52 -1
  230. package/src/tempo/Subscription.integration.test.ts +1 -1
  231. package/src/tempo/client/Charge.test.ts +173 -0
  232. package/src/tempo/client/Charge.ts +65 -36
  233. package/src/tempo/client/Methods.ts +4 -2
  234. package/src/tempo/client/ResolveAccount.ts +46 -0
  235. package/src/tempo/internal/fee-payer.test.ts +89 -10
  236. package/src/tempo/internal/fee-payer.ts +128 -41
  237. package/src/tempo/internal/proof.test.ts +12 -4
  238. package/src/tempo/internal/proof.ts +55 -6
  239. package/src/tempo/legacy/client/SessionManager.ts +11 -3
  240. package/src/tempo/legacy/server/Session.test.ts +91 -26
  241. package/src/tempo/server/Charge.test.ts +388 -17
  242. package/src/tempo/server/Charge.ts +50 -24
  243. package/src/tempo/server/Methods.ts +4 -2
  244. package/src/tempo/server/Subscription.test.ts +465 -3
  245. package/src/tempo/server/Subscription.ts +174 -19
  246. package/src/tempo/server/internal/html/package.json +2 -2
  247. package/src/tempo/server/internal/html.gen.ts +1 -1
  248. package/src/tempo/session/client/ChannelOps.ts +5 -19
  249. package/src/tempo/session/client/ChannelStore.ts +111 -0
  250. package/src/tempo/session/client/CredentialState.test.ts +206 -62
  251. package/src/tempo/session/client/CredentialState.ts +58 -73
  252. package/src/tempo/session/client/Session.test.ts +41 -1
  253. package/src/tempo/session/client/Session.ts +36 -10
  254. package/src/tempo/session/client/SessionManager.test.ts +154 -65
  255. package/src/tempo/session/client/SessionManager.ts +141 -235
  256. package/src/tempo/session/client/index.ts +8 -5
  257. package/src/tempo/session/precompile/Voucher.test.ts +45 -7
  258. package/src/tempo/session/precompile/Voucher.ts +27 -25
  259. package/src/tempo/session/server/CredentialVerification.test.ts +36 -0
  260. package/src/tempo/session/server/CredentialVerification.ts +18 -0
  261. package/src/tempo/session/server/Session.test.ts +4 -4
  262. package/src/tempo/session/server/Settlement.test.ts +88 -1
  263. package/src/tempo/session/server/Settlement.ts +2 -1
  264. package/src/tempo/session/server/Sse.ts +0 -2
  265. package/src/tempo/session/server/Ws.ts +0 -4
  266. package/src/tempo/subscription/Store.ts +27 -9
  267. package/src/x402/Exact.e2e.test.ts +1 -1
  268. package/src/x402/PublicInterface.test-d.ts +1 -1
  269. package/src/x402/index.ts +1 -0
  270. package/dist/mcp-sdk/client/McpClient.d.ts +0 -85
  271. package/dist/mcp-sdk/client/McpClient.d.ts.map +0 -1
  272. package/dist/mcp-sdk/client/McpClient.js +0 -118
  273. package/dist/mcp-sdk/client/McpClient.js.map +0 -1
  274. package/dist/mcp-sdk/client/index.d.ts.map +0 -1
  275. package/dist/mcp-sdk/client/index.js.map +0 -1
  276. package/dist/mcp-sdk/server/Transport.d.ts.map +0 -1
  277. package/dist/mcp-sdk/server/Transport.js.map +0 -1
  278. package/dist/mcp-sdk/server/index.d.ts.map +0 -1
  279. package/dist/mcp-sdk/server/index.js.map +0 -1
  280. package/src/mcp-sdk/client/McpClient.ts +0 -228
  281. /package/dist/{mcp-sdk → mcp}/client/index.d.ts +0 -0
  282. /package/dist/{mcp-sdk → mcp}/client/index.js +0 -0
  283. /package/dist/{mcp-sdk → mcp}/server/Transport.d.ts +0 -0
  284. /package/dist/{mcp-sdk → mcp}/server/Transport.js +0 -0
  285. /package/dist/{mcp-sdk → mcp}/server/index.d.ts +0 -0
  286. /package/dist/{mcp-sdk → mcp}/server/index.js +0 -0
  287. /package/src/{mcp-sdk → mcp}/client/index.ts +0 -0
  288. /package/src/{mcp-sdk → mcp}/server/Transport.test.ts +0 -0
  289. /package/src/{mcp-sdk → mcp}/server/Transport.ts +0 -0
  290. /package/src/{mcp-sdk → mcp}/server/index.ts +0 -0
@@ -29,7 +29,7 @@ import * as defaults from '../internal/defaults.js'
29
29
  import * as Proof from '../internal/proof.js'
30
30
 
31
31
  const realm = 'api.example.com'
32
- const secretKey = 'test-secret-key'
32
+ const secretKey = 'test-secret-key-test-secret-key-32'
33
33
 
34
34
  function isPairAlreadyExistsError(error: unknown) {
35
35
  if (typeof error !== 'object' || error === null) return false
@@ -1604,6 +1604,228 @@ describe('tempo', () => {
1604
1604
  httpServer.close()
1605
1605
  })
1606
1606
 
1607
+ test('behavior: fee payer pre-broadcast simulation targets the co-signed transaction', async () => {
1608
+ // The pre-broadcast simulation must reflect the FINAL co-signed envelope
1609
+ // (concrete sponsor fee payer), not the pre-cosign 0x78 (`feePayer: true`).
1610
+ const callRequests: any[] = []
1611
+ const interceptingClient = createClient({
1612
+ account: accounts[0],
1613
+ chain: client.chain,
1614
+ transport: custom({
1615
+ async request(args: any) {
1616
+ if (args.method === 'eth_call') callRequests.push(args.params?.[0])
1617
+ return client.transport.request(args)
1618
+ },
1619
+ }),
1620
+ })
1621
+
1622
+ const serverWithTrace = Mppx_server.create({
1623
+ methods: [
1624
+ tempo_server.charge({
1625
+ getClient() {
1626
+ return interceptingClient
1627
+ },
1628
+ currency: asset,
1629
+ account: accounts[0],
1630
+ }),
1631
+ ],
1632
+ realm,
1633
+ secretKey,
1634
+ })
1635
+
1636
+ const mppx = Mppx_client.create({
1637
+ polyfill: false,
1638
+ methods: [
1639
+ tempo_client({
1640
+ account: accounts[1],
1641
+ getClient() {
1642
+ return client
1643
+ },
1644
+ }),
1645
+ ],
1646
+ })
1647
+
1648
+ const httpServer = await Http.createServer(async (req, res) => {
1649
+ const result = await Mppx_server.toNodeListener(
1650
+ serverWithTrace.charge({
1651
+ feePayer: accounts[0],
1652
+ amount: '1',
1653
+ currency: asset,
1654
+ recipient: accounts[0].address,
1655
+ }),
1656
+ )(req, res)
1657
+ if (result.status === 402) return
1658
+ res.end('OK')
1659
+ })
1660
+
1661
+ const challengeResponse = await fetch(httpServer.url)
1662
+ const credential = await mppx.createCredential(challengeResponse)
1663
+ callRequests.length = 0
1664
+
1665
+ const authResponse = await fetch(httpServer.url, {
1666
+ headers: { Authorization: credential },
1667
+ })
1668
+ expect(authResponse.status).toBe(200)
1669
+
1670
+ expect(callRequests.length).toBeGreaterThan(0)
1671
+ const simRequest = callRequests[0]
1672
+ // The co-signed envelope names a concrete sponsor as fee payer. The
1673
+ // pre-cosign 0x78 instead carries `feePayer: true`; asserting the address
1674
+ // proves we simulate the transaction the sponsor actually broadcasts.
1675
+ expect(simRequest.feePayer).not.toBe(true)
1676
+ expect(typeof simRequest.feePayer).toBe('string')
1677
+ expect((simRequest.feePayer as string).toLowerCase()).toBe(accounts[0].address.toLowerCase())
1678
+ // Execution runs as the sender, not the sponsor.
1679
+ expect((simRequest.from as string).toLowerCase()).toBe(accounts[1].address.toLowerCase())
1680
+ expect(simRequest.calls?.length).toBeGreaterThan(0)
1681
+
1682
+ httpServer.close()
1683
+ })
1684
+
1685
+ test('behavior: fee payer fails closed when pre-broadcast simulation reverts', async () => {
1686
+ // A reverting pre-broadcast simulation must abort before broadcast so the
1687
+ // sponsor never pays gas for a transaction that would revert.
1688
+ const rpcMethods: string[] = []
1689
+ const interceptingClient = createClient({
1690
+ account: accounts[0],
1691
+ chain: client.chain,
1692
+ transport: custom({
1693
+ async request(args: any) {
1694
+ rpcMethods.push(args.method)
1695
+ if (args.method === 'eth_call')
1696
+ throw new Error('execution reverted: simulation fixture')
1697
+ return client.transport.request(args)
1698
+ },
1699
+ }),
1700
+ })
1701
+
1702
+ const serverWithRevert = Mppx_server.create({
1703
+ methods: [
1704
+ tempo_server.charge({
1705
+ getClient() {
1706
+ return interceptingClient
1707
+ },
1708
+ currency: asset,
1709
+ account: accounts[0],
1710
+ }),
1711
+ ],
1712
+ realm,
1713
+ secretKey,
1714
+ })
1715
+
1716
+ const mppx = Mppx_client.create({
1717
+ polyfill: false,
1718
+ methods: [
1719
+ tempo_client({
1720
+ account: accounts[1],
1721
+ getClient() {
1722
+ return client
1723
+ },
1724
+ }),
1725
+ ],
1726
+ })
1727
+
1728
+ const httpServer = await Http.createServer(async (req, res) => {
1729
+ const result = await Mppx_server.toNodeListener(
1730
+ serverWithRevert.charge({
1731
+ feePayer: accounts[0],
1732
+ amount: '1',
1733
+ currency: asset,
1734
+ recipient: accounts[0].address,
1735
+ }),
1736
+ )(req, res)
1737
+ if (result.status === 402) return
1738
+ res.end('OK')
1739
+ })
1740
+
1741
+ const challengeResponse = await fetch(httpServer.url)
1742
+ const credential = await mppx.createCredential(challengeResponse)
1743
+ rpcMethods.length = 0
1744
+
1745
+ const authResponse = await fetch(httpServer.url, {
1746
+ headers: { Authorization: credential },
1747
+ })
1748
+
1749
+ // Fails closed: not successful, and the transaction is never broadcast.
1750
+ expect(authResponse.status).not.toBe(200)
1751
+ expect(rpcMethods).toContain('eth_call')
1752
+ expect(rpcMethods).not.toContain('eth_sendRawTransactionSync')
1753
+ expect(rpcMethods).not.toContain('eth_sendRawTransaction')
1754
+
1755
+ httpServer.close()
1756
+ })
1757
+
1758
+ test('behavior: fee payer fails closed when simulation reverts (optimistic mode)', async () => {
1759
+ const rpcMethods: string[] = []
1760
+ const interceptingClient = createClient({
1761
+ account: accounts[0],
1762
+ chain: client.chain,
1763
+ transport: custom({
1764
+ async request(args: any) {
1765
+ rpcMethods.push(args.method)
1766
+ if (args.method === 'eth_call')
1767
+ throw new Error('execution reverted: simulation fixture')
1768
+ return client.transport.request(args)
1769
+ },
1770
+ }),
1771
+ })
1772
+
1773
+ const serverNoWait = Mppx_server.create({
1774
+ methods: [
1775
+ tempo_server.charge({
1776
+ getClient() {
1777
+ return interceptingClient
1778
+ },
1779
+ currency: asset,
1780
+ account: accounts[0],
1781
+ waitForConfirmation: false,
1782
+ }),
1783
+ ],
1784
+ realm,
1785
+ secretKey,
1786
+ })
1787
+
1788
+ const mppx = Mppx_client.create({
1789
+ polyfill: false,
1790
+ methods: [
1791
+ tempo_client({
1792
+ account: accounts[1],
1793
+ getClient() {
1794
+ return client
1795
+ },
1796
+ }),
1797
+ ],
1798
+ })
1799
+
1800
+ const httpServer = await Http.createServer(async (req, res) => {
1801
+ const result = await Mppx_server.toNodeListener(
1802
+ serverNoWait.charge({
1803
+ feePayer: accounts[0],
1804
+ amount: '1',
1805
+ currency: asset,
1806
+ recipient: accounts[0].address,
1807
+ }),
1808
+ )(req, res)
1809
+ if (result.status === 402) return
1810
+ res.end('OK')
1811
+ })
1812
+
1813
+ const challengeResponse = await fetch(httpServer.url)
1814
+ const credential = await mppx.createCredential(challengeResponse)
1815
+ rpcMethods.length = 0
1816
+
1817
+ const authResponse = await fetch(httpServer.url, {
1818
+ headers: { Authorization: credential },
1819
+ })
1820
+
1821
+ expect(authResponse.status).not.toBe(200)
1822
+ expect(rpcMethods).toContain('eth_call')
1823
+ expect(rpcMethods).not.toContain('eth_sendRawTransaction')
1824
+ expect(rpcMethods).not.toContain('eth_sendRawTransactionSync')
1825
+
1826
+ httpServer.close()
1827
+ })
1828
+
1607
1829
  test('behavior: fee payer rejects concurrent in-flight transactions from one sender', async () => {
1608
1830
  let releaseSimulation!: () => void
1609
1831
  let resolveSimulationStarted!: () => void
@@ -2484,7 +2706,11 @@ describe('tempo', () => {
2484
2706
  domain: Proof.domain(chain.id),
2485
2707
  types: Proof.types,
2486
2708
  primaryType: 'Proof',
2487
- message: Proof.message(challenge.id, challenge.realm),
2709
+ message: Proof.message({
2710
+ account: accounts[1].address,
2711
+ challengeId: challenge.id,
2712
+ realm: challenge.realm,
2713
+ }),
2488
2714
  })
2489
2715
 
2490
2716
  const credential = Credential.from({
@@ -2522,7 +2748,11 @@ describe('tempo', () => {
2522
2748
  domain: Proof.domain(chain.id),
2523
2749
  types: Proof.types,
2524
2750
  primaryType: 'Proof',
2525
- message: Proof.message(challenge.id, challenge.realm),
2751
+ message: Proof.message({
2752
+ account: accounts[1].address,
2753
+ challengeId: challenge.id,
2754
+ realm: challenge.realm,
2755
+ }),
2526
2756
  })
2527
2757
 
2528
2758
  const credential = Credential.from({
@@ -2637,7 +2867,11 @@ describe('tempo', () => {
2637
2867
  domain: Proof.domain(chain.id),
2638
2868
  types: Proof.types,
2639
2869
  primaryType: 'Proof',
2640
- message: Proof.message(challenge.id, challenge.realm),
2870
+ message: Proof.message({
2871
+ account: accounts[1].address,
2872
+ challengeId: challenge.id,
2873
+ realm: challenge.realm,
2874
+ }),
2641
2875
  })
2642
2876
 
2643
2877
  const credential = Credential.from({
@@ -2761,6 +2995,91 @@ describe('tempo', () => {
2761
2995
  },
2762
2996
  )
2763
2997
 
2998
+ test('behavior: rejects access-key proof replayed across a co-authorized account', async () => {
2999
+ // The same access key is authorized by two roots (`payer` and `other`),
3000
+ // so a proof bound to `payer` must not be claimable as `other` even
3001
+ // though `other` independently authorized the key.
3002
+ const payer = accounts[1]
3003
+ const other = accounts[2]
3004
+ const accessKey = Account.fromSecp256k1(Secp256k1.randomPrivateKey(), {
3005
+ access: payer,
3006
+ })
3007
+
3008
+ await fundAccount({ address: other.address, token: asset })
3009
+ await Actions.accessKey.authorizeSync(client, {
3010
+ account: payer,
3011
+ accessKey,
3012
+ feeToken: asset,
3013
+ })
3014
+ await Actions.accessKey.authorizeSync(client, {
3015
+ account: other,
3016
+ accessKey,
3017
+ feeToken: asset,
3018
+ })
3019
+
3020
+ const httpServer = await Http.createServer(async (req, res) => {
3021
+ const result = await Mppx_server.toNodeListener(
3022
+ server.charge({ amount: '0', decimals: 6 }),
3023
+ )(req, res)
3024
+ if (result.status === 402) return
3025
+ res.end('OK')
3026
+ })
3027
+
3028
+ try {
3029
+ const response1 = await fetch(httpServer.url)
3030
+ expect(response1.status).toBe(402)
3031
+
3032
+ const challenge = Challenge.fromResponse(response1, {
3033
+ methods: [tempo_client.charge()],
3034
+ })
3035
+
3036
+ // Access key signs a proof bound to `payer` (the `account` field).
3037
+ const signature = await signTypedData(client, {
3038
+ account: accessKey,
3039
+ domain: Proof.domain(chain.id),
3040
+ types: Proof.types,
3041
+ primaryType: 'Proof',
3042
+ message: Proof.message({
3043
+ account: payer.address,
3044
+ challengeId: challenge.id,
3045
+ realm: challenge.realm,
3046
+ }),
3047
+ })
3048
+
3049
+ // The bound payer accepts the proof.
3050
+ const accepted = await fetch(httpServer.url, {
3051
+ headers: {
3052
+ Authorization: Credential.serialize(
3053
+ Credential.from({
3054
+ challenge,
3055
+ payload: { signature, type: 'proof' as const },
3056
+ source: `did:pkh:eip155:${chain.id}:${payer.address}`,
3057
+ }),
3058
+ ),
3059
+ },
3060
+ })
3061
+ expect(accepted.status).toBe(200)
3062
+
3063
+ // Replaying the same proof as `other` is rejected by the wallet binding.
3064
+ const replay = await fetch(httpServer.url, {
3065
+ headers: {
3066
+ Authorization: Credential.serialize(
3067
+ Credential.from({
3068
+ challenge,
3069
+ payload: { signature, type: 'proof' as const },
3070
+ source: `did:pkh:eip155:${chain.id}:${other.address}`,
3071
+ }),
3072
+ ),
3073
+ },
3074
+ })
3075
+ expect(replay.status).toBe(402)
3076
+ const body = (await replay.json()) as { detail: string }
3077
+ expect(body.detail).toContain('Proof signature does not match source.')
3078
+ } finally {
3079
+ httpServer.close()
3080
+ }
3081
+ })
3082
+
2764
3083
  test('behavior: rejects replayed proof credential when store is configured', async () => {
2765
3084
  const replayStore = Store.memory()
2766
3085
  const server_ = Mppx_server.create({
@@ -2798,7 +3117,11 @@ describe('tempo', () => {
2798
3117
  domain: Proof.domain(chain.id),
2799
3118
  types: Proof.types,
2800
3119
  primaryType: 'Proof',
2801
- message: Proof.message(challenge.id, challenge.realm),
3120
+ message: Proof.message({
3121
+ account: accounts[1].address,
3122
+ challengeId: challenge.id,
3123
+ realm: challenge.realm,
3124
+ }),
2802
3125
  })
2803
3126
 
2804
3127
  const credential = Credential.from({
@@ -2859,7 +3182,11 @@ describe('tempo', () => {
2859
3182
  domain: Proof.domain(chain.id),
2860
3183
  types: Proof.types,
2861
3184
  primaryType: 'Proof',
2862
- message: Proof.message(challenge.id, challenge.realm),
3185
+ message: Proof.message({
3186
+ account: accounts[1].address,
3187
+ challengeId: challenge.id,
3188
+ realm: challenge.realm,
3189
+ }),
2863
3190
  })
2864
3191
 
2865
3192
  const credential = Credential.serialize(
@@ -2933,7 +3260,11 @@ describe('tempo', () => {
2933
3260
  domain: Proof.domain(chain.id),
2934
3261
  types: Proof.types,
2935
3262
  primaryType: 'Proof',
2936
- message: Proof.message(challenge.id, challenge.realm),
3263
+ message: Proof.message({
3264
+ account: accounts[1].address,
3265
+ challengeId: challenge.id,
3266
+ realm: challenge.realm,
3267
+ }),
2937
3268
  })
2938
3269
 
2939
3270
  const credential = Credential.from({
@@ -2996,7 +3327,11 @@ describe('tempo', () => {
2996
3327
  domain: Proof.domain(chain.id),
2997
3328
  types: Proof.types,
2998
3329
  primaryType: 'Proof',
2999
- message: Proof.message(challenge1.id, challenge1.realm),
3330
+ message: Proof.message({
3331
+ account: accounts[1].address,
3332
+ challengeId: challenge1.id,
3333
+ realm: challenge1.realm,
3334
+ }),
3000
3335
  })
3001
3336
 
3002
3337
  const credential1 = Credential.from({
@@ -3027,7 +3362,11 @@ describe('tempo', () => {
3027
3362
  domain: Proof.domain(chain.id),
3028
3363
  types: Proof.types,
3029
3364
  primaryType: 'Proof',
3030
- message: Proof.message(challenge2.id, challenge2.realm),
3365
+ message: Proof.message({
3366
+ account: accounts[1].address,
3367
+ challengeId: challenge2.id,
3368
+ realm: challenge2.realm,
3369
+ }),
3031
3370
  })
3032
3371
 
3033
3372
  const credential2 = Credential.from({
@@ -3064,7 +3403,11 @@ describe('tempo', () => {
3064
3403
  domain: Proof.domain(chain.id),
3065
3404
  types: Proof.types,
3066
3405
  primaryType: 'Proof',
3067
- message: Proof.message(challenge.id, challenge.realm),
3406
+ message: Proof.message({
3407
+ account: accounts[1].address,
3408
+ challengeId: challenge.id,
3409
+ realm: challenge.realm,
3410
+ }),
3068
3411
  })
3069
3412
 
3070
3413
  const credential = Credential.from({
@@ -3100,7 +3443,11 @@ describe('tempo', () => {
3100
3443
  domain: Proof.domain(chain.id),
3101
3444
  types: Proof.types,
3102
3445
  primaryType: 'Proof',
3103
- message: Proof.message(challenge.id, challenge.realm),
3446
+ message: Proof.message({
3447
+ account: accounts[1].address,
3448
+ challengeId: challenge.id,
3449
+ realm: challenge.realm,
3450
+ }),
3104
3451
  })
3105
3452
 
3106
3453
  const credential = Credential.from({
@@ -3199,7 +3546,11 @@ describe('tempo', () => {
3199
3546
  domain: Proof.domain(chain.id),
3200
3547
  types: Proof.types,
3201
3548
  primaryType: 'Proof',
3202
- message: Proof.message(challenge.id, challenge.realm),
3549
+ message: Proof.message({
3550
+ account: accounts[1].address,
3551
+ challengeId: challenge.id,
3552
+ realm: challenge.realm,
3553
+ }),
3203
3554
  })
3204
3555
 
3205
3556
  const credential = Credential.from({
@@ -3237,7 +3588,11 @@ describe('tempo', () => {
3237
3588
  domain: Proof.domain(chain.id),
3238
3589
  types: Proof.types,
3239
3590
  primaryType: 'Proof',
3240
- message: Proof.message(challenge.id, challenge.realm),
3591
+ message: Proof.message({
3592
+ account: accounts[1].address,
3593
+ challengeId: challenge.id,
3594
+ realm: challenge.realm,
3595
+ }),
3241
3596
  })
3242
3597
 
3243
3598
  const credential = Credential.from({
@@ -3276,7 +3631,11 @@ describe('tempo', () => {
3276
3631
  domain: Proof.domain(99999),
3277
3632
  types: Proof.types,
3278
3633
  primaryType: 'Proof',
3279
- message: Proof.message(challenge.id, challenge.realm),
3634
+ message: Proof.message({
3635
+ account: accounts[1].address,
3636
+ challengeId: challenge.id,
3637
+ realm: challenge.realm,
3638
+ }),
3280
3639
  })
3281
3640
 
3282
3641
  const credential = Credential.from({
@@ -3312,7 +3671,11 @@ describe('tempo', () => {
3312
3671
  domain: Proof.domain(chain.id),
3313
3672
  types: Proof.types,
3314
3673
  primaryType: 'Proof',
3315
- message: Proof.message(challenge.id, 'evil.example.com'),
3674
+ message: Proof.message({
3675
+ account: accounts[1].address,
3676
+ challengeId: challenge.id,
3677
+ realm: 'evil.example.com',
3678
+ }),
3316
3679
  })
3317
3680
 
3318
3681
  const credential = Credential.from({
@@ -3348,7 +3711,11 @@ describe('tempo', () => {
3348
3711
  domain: Proof.domain(chain.id),
3349
3712
  types: Proof.types,
3350
3713
  primaryType: 'Proof',
3351
- message: Proof.message(challenge.id, challenge.realm),
3714
+ message: Proof.message({
3715
+ account: accounts[1].address,
3716
+ challengeId: challenge.id,
3717
+ realm: challenge.realm,
3718
+ }),
3352
3719
  })
3353
3720
 
3354
3721
  const credential = Credential.from({
@@ -3384,7 +3751,11 @@ describe('tempo', () => {
3384
3751
  domain: Proof.domain(chain.id),
3385
3752
  types: Proof.types,
3386
3753
  primaryType: 'Proof',
3387
- message: Proof.message(challenge.id, challenge.realm),
3754
+ message: Proof.message({
3755
+ account: accounts[1].address,
3756
+ challengeId: challenge.id,
3757
+ realm: challenge.realm,
3758
+ }),
3388
3759
  })
3389
3760
 
3390
3761
  const credential = Credential.from({
@@ -2,7 +2,6 @@ import * as SignatureEnvelope from 'ox/tempo/SignatureEnvelope'
2
2
  import {
3
3
  decodeFunctionData,
4
4
  formatUnits,
5
- hashTypedData,
6
5
  keccak256,
7
6
  parseEventLogs,
8
7
  type TransactionReceipt,
@@ -292,11 +291,15 @@ export function charge<const parameters extends charge.Parameters>(
292
291
  }
293
292
 
294
293
  const valid = await verifyTypedData(client, {
294
+ // Bind verification to the claimed payer (`source.address`): the
295
+ // signer may be the payer itself or an access key authorized for it.
295
296
  address: source.address,
296
- domain: Proof.domain(resolvedChainId),
297
- types: Proof.types,
298
- primaryType: 'Proof',
299
- message: Proof.message(challenge.id, challenge.realm),
297
+ ...Proof.typedData({
298
+ account: source.address,
299
+ chainId: resolvedChainId,
300
+ challengeId: challenge.id,
301
+ realm: challenge.realm,
302
+ }),
300
303
  signature: payload.signature as `0x${string}`,
301
304
  })
302
305
  if (!valid) {
@@ -403,6 +406,13 @@ export function charge<const parameters extends charge.Parameters>(
403
406
  const allowedFeeTokens = FeePayer.defaultAllowedFeeTokens(chainId)
404
407
  if (isFeePayerTx) FeePayer.assertAllowedFeeToken(transaction, allowedFeeTokens)
405
408
 
409
+ // Request for the pre-broadcast simulation; for sponsored payments
410
+ // this is overwritten below with the co-signed shape.
411
+ let simulationRequest: Record<string, unknown> = FeePayer.simulationTransaction(
412
+ transaction,
413
+ { feePayer: isFeePayerTx },
414
+ )
415
+
406
416
  const serializedTransaction_final = await (async () => {
407
417
  if (feePayerAccount && methodDetails?.feePayer !== false) {
408
418
  const sponsored = FeePayer.prepareSponsoredTransaction({
@@ -417,22 +427,47 @@ export function charge<const parameters extends charge.Parameters>(
417
427
  feeToken: transaction.feeToken ?? defaults.tokens.pathUsd,
418
428
  },
419
429
  })
430
+ // `account` is the sender (eth_call `from`); `feePayer` is the
431
+ // sponsor that pays gas.
432
+ simulationRequest = {
433
+ ...sponsored,
434
+ account: transaction.from,
435
+ feePayer: feePayerAccount.address,
436
+ feePayerSignature: undefined,
437
+ signature: undefined,
438
+ }
420
439
  return signTransaction(client, sponsored as never)
421
440
  }
422
- if (feePayerUrl && isFeePayerTx)
423
- return FeePayer.fillHostedFeePayerTransaction({
441
+ if (feePayerUrl && isFeePayerTx) {
442
+ const hosted = await FeePayer.fillHostedFeePayerTransaction({
424
443
  allowedFeeTokens,
444
+ challengeExpires: expires,
445
+ chainId: chainId ?? client.chain!.id,
446
+ details: { amount, currency, recipient },
447
+ policy: feePayerPolicy,
425
448
  transaction,
426
449
  url: feePayerUrl,
427
450
  })
451
+ // Simulate the co-signed envelope (concrete fee payer + chosen
452
+ // fee token), mirroring the local-sponsor path.
453
+ simulationRequest = {
454
+ ...transaction,
455
+ account: transaction.from,
456
+ feePayer: hosted.feePayer,
457
+ feePayerSignature: undefined,
458
+ feeToken: hosted.feeToken,
459
+ signature: undefined,
460
+ }
461
+ return hosted.serializedTransaction
462
+ }
428
463
  return serializedTransaction
429
464
  })()
430
465
 
466
+ // Pre-broadcast simulation: fail closed before broadcast so the
467
+ // sponsor never pays gas for a transaction that would revert.
468
+ await viem_call(client, simulationRequest as never)
469
+
431
470
  if (waitForConfirmation) {
432
- await viem_call(
433
- client,
434
- FeePayer.simulationTransaction(transaction, { feePayer: isFeePayerTx }),
435
- )
436
471
  const receipt = await sendRawTransactionSync(client, {
437
472
  serializedTransaction: serializedTransaction_final,
438
473
  })
@@ -462,13 +497,9 @@ export function charge<const parameters extends charge.Parameters>(
462
497
  return toReceipt(receipt)
463
498
  }
464
499
 
465
- // Optimistic path: simulate to catch obvious reverts, then broadcast
466
- // without waiting for on-chain confirmation. The returned receipt
467
- // assumes success — callers opt into this risk via waitForConfirmation: false.
468
- await viem_call(
469
- client,
470
- FeePayer.simulationTransaction(transaction, { feePayer: isFeePayerTx }),
471
- )
500
+ // Optimistic path: broadcast without waiting for confirmation
501
+ // (simulation above already ran). The returned receipt assumes
502
+ // success — callers opt in via waitForConfirmation: false.
472
503
  const reference = await sendRawTransaction(client, {
473
504
  serializedTransaction: serializedTransaction_final,
474
505
  })
@@ -1007,12 +1038,7 @@ function recoverAuthorizedProofSigner(parameters: {
1007
1038
 
1008
1039
  try {
1009
1040
  const envelope = SignatureEnvelope.from(signature)
1010
- const proofHash = hashTypedData({
1011
- domain: Proof.domain(chainId),
1012
- types: Proof.types,
1013
- primaryType: 'Proof',
1014
- message: Proof.message(challengeId, realm),
1015
- })
1041
+ const proofHash = Proof.hash({ account: sourceAddress, chainId, challengeId, realm })
1016
1042
 
1017
1043
  if (envelope.type === 'keychain') {
1018
1044
  if (!TempoAddress.isEqual(envelope.userAddress, sourceAddress)) return null
@@ -49,14 +49,14 @@ function createSessionMethod<const parameters extends tempo.Parameters>(
49
49
  }
50
50
 
51
51
  /**
52
- * Creates both Tempo `charge` and `session` methods from shared parameters.
52
+ * Creates the common Tempo `charge` and `session` methods from shared parameters.
53
53
  *
54
54
  * @example
55
55
  * ```ts
56
56
  * import { Mppx, tempo } from 'mppx/server'
57
57
  *
58
58
  * const mppx = Mppx.create({
59
- * methods: [tempo({ currency: '0x...', recipient: '0x...' })],
59
+ * methods: [tempo.common({ currency: '0x...', recipient: '0x...' })],
60
60
  * })
61
61
  * ```
62
62
  */
@@ -69,6 +69,8 @@ export namespace tempo {
69
69
 
70
70
  /** Creates a Tempo `charge` method for one-time TIP-20 token transfers. */
71
71
  export const charge = charge_
72
+ /** Creates the common Tempo `charge` and `session` methods from shared parameters. */
73
+ export const common = tempo
72
74
  /** Creates a TIP-1034 Tempo `session` method for session-based TIP-20 token payments. */
73
75
  export const session = sessionServer
74
76
  /** @deprecated Use `tempo.session()` for the TIP-1034 session server method. */