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,126 @@
1
+ import { Challenge, Credential } from 'mppx'
2
+ import { createClient, http } from 'viem'
3
+ import { privateKeyToAccount } from 'viem/accounts'
4
+ import { tempoLocalnet } from 'viem/chains'
5
+ import { describe, expect, test, vi } from 'vp/test'
6
+
7
+ import * as Methods from '../Methods.js'
8
+ import { charge } from './Charge.js'
9
+
10
+ const account = privateKeyToAccount(
11
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
12
+ )
13
+ const currency = '0x3333333333333333333333333333333333333333'
14
+ const recipient = '0x2222222222222222222222222222222222222222'
15
+
16
+ type ChargeRequest = ReturnType<typeof Methods.charge.schema.request.parse>
17
+
18
+ function createChallenge(
19
+ overrides: Partial<Parameters<typeof Methods.charge.schema.request.parse>[0]> = {},
20
+ ): Challenge.Challenge<ChargeRequest, 'charge', 'tempo'> {
21
+ const request = Methods.charge.schema.request.parse({
22
+ amount: '0',
23
+ currency,
24
+ decimals: 6,
25
+ recipient,
26
+ ...overrides,
27
+ })
28
+ return Challenge.from({
29
+ id: 'test-challenge-id',
30
+ intent: 'charge',
31
+ method: 'tempo',
32
+ realm: 'api.example.com',
33
+ request,
34
+ }) as Challenge.Challenge<ChargeRequest, 'charge', 'tempo'>
35
+ }
36
+
37
+ describe('tempo.charge client', () => {
38
+ test('uses client chain ID when the challenge omits chainId', async () => {
39
+ const client = createClient({
40
+ account,
41
+ chain: tempoLocalnet,
42
+ transport: http('http://127.0.0.1'),
43
+ })
44
+ const method = charge({
45
+ account,
46
+ getClient: () => client,
47
+ })
48
+
49
+ const credential = Credential.deserialize(
50
+ await method.createCredential({
51
+ challenge: createChallenge(),
52
+ context: {},
53
+ }),
54
+ )
55
+
56
+ expect(credential.source).toBe(`did:pkh:eip155:${tempoLocalnet.id}:${account.address}`)
57
+ })
58
+
59
+ test('uses challenge chainId for client resolution and proof source', async () => {
60
+ let requestedChainId: number | undefined
61
+ const chainId = 42431
62
+ const client = createClient({
63
+ account,
64
+ chain: tempoLocalnet,
65
+ transport: http('http://127.0.0.1'),
66
+ })
67
+ const method = charge({
68
+ account,
69
+ getClient: (parameters) => {
70
+ requestedChainId = parameters.chainId
71
+ return client
72
+ },
73
+ })
74
+
75
+ const credential = Credential.deserialize(
76
+ await method.createCredential({
77
+ challenge: createChallenge({ chainId }),
78
+ context: {},
79
+ }),
80
+ )
81
+
82
+ expect(requestedChainId).toBe(chainId)
83
+ expect(credential.source).toBe(`did:pkh:eip155:${chainId}:${account.address}`)
84
+ })
85
+
86
+ test('uses challenge chainId for non-zero transaction source', async () => {
87
+ vi.resetModules()
88
+ const prepareTransactionRequest = vi.fn(async () => ({}))
89
+ const signTransaction = vi.fn(async () => '0xdeadbeef')
90
+ vi.doMock('viem/actions', () => ({
91
+ prepareTransactionRequest,
92
+ sendCallsSync: vi.fn(),
93
+ signTransaction,
94
+ signTypedData: vi.fn(),
95
+ }))
96
+
97
+ try {
98
+ const { charge: chargeWithMockedActions } = await import('./Charge.js')
99
+ const chainId = 42431
100
+ const client = createClient({
101
+ account,
102
+ chain: tempoLocalnet,
103
+ transport: http('http://127.0.0.1'),
104
+ })
105
+ const method = chargeWithMockedActions({
106
+ account,
107
+ getClient: () => client,
108
+ })
109
+
110
+ const credential = Credential.deserialize(
111
+ await method.createCredential({
112
+ challenge: createChallenge({ amount: '1', chainId, supportedModes: ['pull'] }),
113
+ context: {},
114
+ }),
115
+ )
116
+
117
+ expect(prepareTransactionRequest).toHaveBeenCalledOnce()
118
+ expect(signTransaction).toHaveBeenCalledOnce()
119
+ expect(credential.payload).toEqual({ signature: '0xdeadbeef', type: 'transaction' })
120
+ expect(credential.source).toBe(`did:pkh:eip155:${chainId}:${account.address}`)
121
+ } finally {
122
+ vi.doUnmock('viem/actions')
123
+ vi.resetModules()
124
+ }
125
+ })
126
+ })
@@ -51,8 +51,12 @@ export function charge(parameters: charge.Parameters = {}) {
51
51
  }),
52
52
 
53
53
  async createCredential({ challenge, context }) {
54
- const chainId = challenge.request.methodDetails?.chainId
55
- const client = await getClient({ chainId })
54
+ const challengeChainId = challenge.request.methodDetails?.chainId
55
+ const client = await getClient({ chainId: challengeChainId })
56
+ const chainId = challengeChainId ?? client.chain?.id
57
+ if (chainId === undefined)
58
+ throw new Error('No `chainId` provided. Pass a chain ID in the challenge or client.')
59
+
56
60
  const account = getAccount(client, context)
57
61
 
58
62
  const { request } = challenge
@@ -62,7 +66,7 @@ export function charge(parameters: charge.Parameters = {}) {
62
66
  if (BigInt(amount) === 0n) {
63
67
  const signature = await signTypedData(client, {
64
68
  account,
65
- domain: Proof.domain(chainId!),
69
+ domain: Proof.domain(chainId),
66
70
  types: Proof.types,
67
71
  primaryType: 'Proof',
68
72
  message: Proof.message(challenge.id, challenge.realm),
@@ -70,7 +74,7 @@ export function charge(parameters: charge.Parameters = {}) {
70
74
  return Credential.serialize({
71
75
  challenge,
72
76
  payload: { signature, type: 'proof' },
73
- source: Proof.proofSource({ address: account.address, chainId: chainId! }),
77
+ source: Proof.proofSource({ address: account.address, chainId }),
74
78
  })
75
79
  }
76
80
 
@@ -156,7 +160,7 @@ export function charge(parameters: charge.Parameters = {}) {
156
160
  return Credential.serialize({
157
161
  challenge,
158
162
  payload: { hash, type: 'hash' },
159
- source: `did:pkh:eip155:${chainId}:${account.address}`,
163
+ source: Proof.proofSource({ address: account.address, chainId }),
160
164
  })
161
165
  }
162
166
 
@@ -175,7 +179,7 @@ export function charge(parameters: charge.Parameters = {}) {
175
179
  return Credential.serialize({
176
180
  challenge,
177
181
  payload: { signature, type: 'transaction' },
178
- source: `did:pkh:eip155:${chainId}:${account.address}`,
182
+ source: Proof.proofSource({ address: account.address, chainId }),
179
183
  })
180
184
  },
181
185
  })
@@ -1,6 +1,7 @@
1
+ import { SignatureEnvelope } from 'ox/tempo'
1
2
  import { type Address, createClient, decodeFunctionData, erc20Abi, type Hex, http } from 'viem'
2
3
  import { privateKeyToAccount } from 'viem/accounts'
3
- import { Addresses, Transaction } from 'viem/tempo'
4
+ import { Account as TempoAccount, Addresses, Transaction, WebCryptoP256 } from 'viem/tempo'
4
5
  import { beforeAll, describe, expect, test } from 'vp/test'
5
6
  import { nodeEnv } from '~test/config.js'
6
7
  import { deployEscrow, openChannel } from '~test/tempo/session.js'
@@ -13,6 +14,7 @@ import * as Credential from '../../Credential.js'
13
14
  import { chainId, escrowContract as escrowContractDefaults } from '../internal/defaults.js'
14
15
  import { escrowAbi } from '../session/Chain.js'
15
16
  import type { SessionCredentialPayload } from '../session/Types.js'
17
+ import { verifyVoucher } from '../session/Voucher.js'
16
18
  import { session } from './Session.js'
17
19
 
18
20
  function deserializePayload(result: string) {
@@ -184,6 +186,133 @@ describe('session (pure)', () => {
184
186
  expect(cred.source).toBe(`did:pkh:eip155:42431:${pureAccount.address}`)
185
187
  })
186
188
 
189
+ test('manual open rejects P256 voucher signer while TIP-1020 verification is disabled', async () => {
190
+ const keyPair = await WebCryptoP256.createKeyPair()
191
+ const voucherSigner = TempoAccount.fromWebCryptoP256(keyPair)
192
+ const method = session({
193
+ getClient: () => pureClient,
194
+ account: pureAccount,
195
+ voucherSigner,
196
+ })
197
+
198
+ await expect(
199
+ method.createCredential({
200
+ challenge: makeChallenge(),
201
+ context: {
202
+ action: 'open',
203
+ channelId,
204
+ cumulativeAmount: '5',
205
+ transaction: '0xdeadbeef',
206
+ },
207
+ }),
208
+ ).rejects.toThrow('Session vouchers only support secp256k1 signatures')
209
+ })
210
+
211
+ test('manual open signs access-key vouchers with raw signatures', async () => {
212
+ const accessKey = TempoAccount.fromSecp256k1(
213
+ '0x59c6995e998f97a5a0044966f09453863d462d2b3f1446a99f0a3d7b5d0f5a0d',
214
+ { access: pureAccount },
215
+ )
216
+ const accessKeyClient = createClient({
217
+ account: accessKey,
218
+ transport: http('http://127.0.0.1'),
219
+ })
220
+ const method = session({
221
+ getClient: () => accessKeyClient,
222
+ account: accessKey,
223
+ })
224
+
225
+ const result = await method.createCredential({
226
+ challenge: makeChallenge(),
227
+ context: {
228
+ action: 'open',
229
+ channelId,
230
+ cumulativeAmount: '5',
231
+ transaction: '0xdeadbeef',
232
+ },
233
+ })
234
+
235
+ const cred = deserializePayload(result)
236
+ expect(cred.payload.action).toBe('open')
237
+ if (cred.payload.action !== 'open') throw new Error('unexpected action')
238
+ expect(cred.payload.authorizedSigner).toBeDefined()
239
+ if (!cred.payload.authorizedSigner) throw new Error('missing authorizedSigner')
240
+ expect(cred.payload.authorizedSigner.toLowerCase()).toBe(
241
+ accessKey.accessKeyAddress.toLowerCase(),
242
+ )
243
+
244
+ const envelope = SignatureEnvelope.from(
245
+ cred.payload.signature as SignatureEnvelope.Serialized,
246
+ )
247
+ expect(envelope.type).toBe('secp256k1')
248
+
249
+ const isValid = await verifyVoucher(
250
+ escrowAddress,
251
+ 42431,
252
+ {
253
+ channelId,
254
+ cumulativeAmount: 5_000_000n,
255
+ signature: cred.payload.signature,
256
+ },
257
+ accessKey.accessKeyAddress,
258
+ )
259
+ expect(isValid).toBe(true)
260
+ expect(cred.source).toBe(`did:pkh:eip155:42431:${pureAccount.address}`)
261
+ })
262
+
263
+ test('manual open signs access-key vouchers with direct voucher signer', async () => {
264
+ const privateKey = '0x59c6995e998f97a5a0044966f09453863d462d2b3f1446a99f0a3d7b5d0f5a0d'
265
+ const accessKey = TempoAccount.fromSecp256k1(privateKey, { access: pureAccount })
266
+ const voucherSigner = TempoAccount.fromSecp256k1(privateKey)
267
+ const accessKeyClient = createClient({
268
+ account: accessKey,
269
+ transport: http('http://127.0.0.1'),
270
+ })
271
+ const method = session({
272
+ getClient: () => accessKeyClient,
273
+ account: accessKey,
274
+ voucherSigner,
275
+ })
276
+
277
+ const result = await method.createCredential({
278
+ challenge: makeChallenge(),
279
+ context: {
280
+ action: 'open',
281
+ channelId,
282
+ cumulativeAmount: '5',
283
+ transaction: '0xdeadbeef',
284
+ },
285
+ })
286
+
287
+ const cred = deserializePayload(result)
288
+ expect(cred.payload.action).toBe('open')
289
+ if (cred.payload.action !== 'open') throw new Error('unexpected action')
290
+ expect(cred.payload.authorizedSigner).toBeDefined()
291
+ if (!cred.payload.authorizedSigner) throw new Error('missing authorizedSigner')
292
+ expect(cred.payload.authorizedSigner.toLowerCase()).toBe(
293
+ accessKey.accessKeyAddress.toLowerCase(),
294
+ )
295
+
296
+ const envelope = SignatureEnvelope.from(
297
+ cred.payload.signature as SignatureEnvelope.Serialized,
298
+ )
299
+ expect(envelope.type).toBe('secp256k1')
300
+ expect(cred.payload.signature.length).toBe(132)
301
+
302
+ const isValid = await verifyVoucher(
303
+ escrowAddress,
304
+ 42431,
305
+ {
306
+ channelId,
307
+ cumulativeAmount: 5_000_000n,
308
+ signature: cred.payload.signature,
309
+ },
310
+ accessKey.accessKeyAddress,
311
+ )
312
+ expect(isValid).toBe(true)
313
+ expect(cred.source).toBe(`did:pkh:eip155:42431:${pureAccount.address}`)
314
+ })
315
+
187
316
  test('manual close produces valid credential', async () => {
188
317
  const method = session({ getClient: () => pureClient, account: pureAccount })
189
318
 
@@ -7,6 +7,7 @@ import * as Method from '../../Method.js'
7
7
  import * as Account from '../../viem/Account.js'
8
8
  import * as Client from '../../viem/Client.js'
9
9
  import * as z from '../../zod.js'
10
+ import { getAccountSignerAddress } from '../internal/account.js'
10
11
  import * as defaults from '../internal/defaults.js'
11
12
  import * as Methods from '../Methods.js'
12
13
  import type { SessionCredentialPayload } from '../session/Types.js'
@@ -27,7 +28,6 @@ export const sessionContextSchema = z.object({
27
28
  cumulativeAmount: z.optional(z.amount()),
28
29
  cumulativeAmountRaw: z.optional(z.string()),
29
30
  transaction: z.optional(z.string()),
30
- authorizedSigner: z.optional(z.string()),
31
31
  additionalDeposit: z.optional(z.amount()),
32
32
  additionalDepositRaw: z.optional(z.string()),
33
33
  depositRaw: z.optional(z.string()),
@@ -79,9 +79,6 @@ export function session(parameters: session.Parameters = {}) {
79
79
  rpcUrl: defaults.rpcUrl,
80
80
  })
81
81
  const getAccount = Account.getResolver({ account: parameters.account })
82
- const getAuthorizedSigner = (account: viem_Account) =>
83
- parameters.authorizedSigner ??
84
- (account as unknown as { accessKeyAddress?: Address }).accessKeyAddress
85
82
 
86
83
  const maxDeposit =
87
84
  parameters.maxDeposit !== undefined ? parseUnits(parameters.maxDeposit, decimals) : undefined
@@ -141,7 +138,7 @@ export function session(parameters: session.Parameters = {}) {
141
138
  )
142
139
  })()
143
140
 
144
- const authorizedSigner = getAuthorizedSigner(account)
141
+ const voucherSigner = parameters.voucherSigner ?? account
145
142
 
146
143
  const key = channelKey(payee, currency, escrowContract)
147
144
  let entry = channels.get(key)
@@ -186,12 +183,12 @@ export function session(parameters: session.Parameters = {}) {
186
183
  entry.cumulativeAmount,
187
184
  escrowContract,
188
185
  chainId,
189
- authorizedSigner,
186
+ voucherSigner,
190
187
  )
191
188
  notifyUpdate(entry)
192
189
  } else {
193
190
  const result = await createOpenPayload(client, account, {
194
- authorizedSigner,
191
+ voucherSigner,
195
192
  escrowContract,
196
193
  payee,
197
194
  currency,
@@ -222,12 +219,8 @@ export function session(parameters: session.Parameters = {}) {
222
219
  const client = await getClient({ chainId })
223
220
 
224
221
  const action = context.action!
225
- const {
226
- channelId: channelIdRaw,
227
- transaction,
228
- authorizedSigner: contextAuthorizedSigner,
229
- } = context
230
- const authorizedSigner = (contextAuthorizedSigner as Address) ?? getAuthorizedSigner(account)
222
+ const { channelId: channelIdRaw, transaction } = context
223
+ const voucherSigner = parameters.voucherSigner ?? account
231
224
  const channelId = channelIdRaw as Hex.Hex
232
225
  const cumulativeAmount = context.cumulativeAmountRaw
233
226
  ? BigInt(context.cumulativeAmountRaw)
@@ -256,14 +249,14 @@ export function session(parameters: session.Parameters = {}) {
256
249
  { channelId, cumulativeAmount },
257
250
  escrowContract,
258
251
  chainId,
259
- authorizedSigner,
252
+ voucherSigner,
260
253
  )
261
254
  payload = {
262
255
  action: 'open',
263
256
  type: 'transaction',
264
257
  channelId,
265
258
  transaction: transaction as Hex.Hex,
266
- authorizedSigner: authorizedSigner ?? account.address,
259
+ authorizedSigner: getAccountSignerAddress(voucherSigner),
267
260
  cumulativeAmount: cumulativeAmount.toString(),
268
261
  signature,
269
262
  }
@@ -293,7 +286,7 @@ export function session(parameters: session.Parameters = {}) {
293
286
  cumulativeAmount,
294
287
  escrowContract,
295
288
  chainId,
296
- authorizedSigner,
289
+ voucherSigner,
297
290
  )
298
291
  const key = channelIdToKey.get(channelId)
299
292
  if (key) {
@@ -316,7 +309,7 @@ export function session(parameters: session.Parameters = {}) {
316
309
  { channelId, cumulativeAmount },
317
310
  escrowContract,
318
311
  chainId,
319
- authorizedSigner,
312
+ voucherSigner,
320
313
  )
321
314
  payload = {
322
315
  action: 'close',
@@ -364,8 +357,8 @@ export function session(parameters: session.Parameters = {}) {
364
357
  export declare namespace session {
365
358
  type Parameters = Account.getResolver.Parameters &
366
359
  Client.getResolver.Parameters & {
367
- /** Address authorized to sign vouchers. Defaults to the account address. Use when a separate access key (e.g. secp256k1) signs vouchers while the root account funds the channel. */
368
- authorizedSigner?: Address | undefined
360
+ /** Account that signs voucher digests. Defaults to `account`; access-key accounts sign raw vouchers as their access-key address. */
361
+ voucherSigner?: viem_Account | undefined
369
362
  /** Token decimals for parsing human-readable amounts (default: 6). */
370
363
  decimals?: number | undefined
371
364
  /** Initial deposit amount in human-readable units (e.g. "10" for 10 tokens). When set, the method handles the full channel lifecycle (open, voucher, cumulative tracking) automatically. */
@@ -1,9 +1,16 @@
1
- import type { Hex } from 'viem'
1
+ import { createClient, type Hex, http } from 'viem'
2
+ import { privateKeyToAccount } from 'viem/accounts'
3
+ import { Account as TempoAccount } from 'viem/tempo'
2
4
  import { describe, expect, test, vi } from 'vp/test'
3
5
 
4
6
  import * as Challenge from '../../Challenge.js'
7
+ import * as PaymentCredential from '../../Credential.js'
5
8
  import { formatNeedVoucherEvent, parseEvent } from '../session/Sse.js'
6
- import type { NeedVoucherEvent, SessionReceipt } from '../session/Types.js'
9
+ import type {
10
+ NeedVoucherEvent,
11
+ SessionCredentialPayload,
12
+ SessionReceipt,
13
+ } from '../session/Types.js'
7
14
  import { sessionManager } from './SessionManager.js'
8
15
 
9
16
  const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex
@@ -82,6 +89,66 @@ describe('Session', () => {
82
89
  expect(s.cumulative).toBe(0n)
83
90
  expect(s.opened).toBe(false)
84
91
  })
92
+
93
+ test('uses voucherSigner for managed open credentials', async () => {
94
+ vi.resetModules()
95
+ vi.doMock('viem/actions', () => ({
96
+ prepareTransactionRequest: vi.fn(async () => ({})),
97
+ sendCallsSync: vi.fn(),
98
+ signTransaction: vi.fn(async () => '0xdeadbeef'),
99
+ signTypedData: vi.fn(),
100
+ }))
101
+
102
+ try {
103
+ const { sessionManager: sessionManagerWithMocks } = await import('./SessionManager.js')
104
+ const account = privateKeyToAccount(
105
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
106
+ )
107
+ const voucherSigner = TempoAccount.fromSecp256k1(
108
+ '0x0000000000000000000000000000000000000000000000000000000000000002',
109
+ { access: account },
110
+ )
111
+ const client = createClient({
112
+ account,
113
+ transport: http('http://127.0.0.1'),
114
+ })
115
+ const challenge = makeChallenge({
116
+ recipient: '0x742d35cc6634c0532925a3b844bc9e7595f8fe00',
117
+ methodDetails: {
118
+ escrowContract: '0x9d136eea063ede5418a6bc7beaff009bbb6cfa70',
119
+ chainId: 4217,
120
+ },
121
+ })
122
+ const mockFetch = vi
123
+ .fn()
124
+ .mockResolvedValueOnce(make402Response(challenge))
125
+ .mockResolvedValueOnce(makeOkResponse())
126
+
127
+ const manager = sessionManagerWithMocks({
128
+ account,
129
+ client,
130
+ fetch: mockFetch as typeof globalThis.fetch,
131
+ maxDeposit: '10',
132
+ voucherSigner,
133
+ })
134
+
135
+ const response = await manager.fetch('https://api.example.com/data')
136
+ const authorization = new Headers((mockFetch.mock.calls[1]![1] as RequestInit).headers).get(
137
+ 'Authorization',
138
+ )
139
+
140
+ expect(response.status).toBe(200)
141
+ expect(authorization).toBeDefined()
142
+ if (!authorization) throw new Error('missing authorization header')
143
+ const credential = PaymentCredential.deserialize<SessionCredentialPayload>(authorization)
144
+ expect(credential.payload.action).toBe('open')
145
+ if (credential.payload.action !== 'open') throw new Error('unexpected action')
146
+ expect(credential.payload.authorizedSigner).toBe(voucherSigner.accessKeyAddress)
147
+ } finally {
148
+ vi.doUnmock('viem/actions')
149
+ vi.resetModules()
150
+ }
151
+ })
85
152
  })
86
153
 
87
154
  describe('.fetch()', () => {
@@ -1,5 +1,5 @@
1
1
  import type { Hex } from 'ox'
2
- import { parseUnits, type Address } from 'viem'
2
+ import { parseUnits, type Account as viem_Account, type Address } from 'viem'
3
3
 
4
4
  import * as Challenge from '../../Challenge.js'
5
5
  import * as Fetch from '../../client/internal/Fetch.js'
@@ -124,7 +124,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
124
124
 
125
125
  const method = sessionPlugin({
126
126
  account: parameters.account,
127
- authorizedSigner: parameters.authorizedSigner,
127
+ voucherSigner: parameters.voucherSigner,
128
128
  getClient: parameters.client ? () => parameters.client! : parameters.getClient,
129
129
  escrowContract: parameters.escrowContract,
130
130
  decimals: parameters.decimals,
@@ -853,8 +853,8 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
853
853
  export declare namespace sessionManager {
854
854
  type Parameters = Account.getResolver.Parameters &
855
855
  Client.getResolver.Parameters & {
856
- /** Address authorized to sign vouchers. Defaults to the account address. */
857
- authorizedSigner?: Address | undefined
856
+ /** Account that signs voucher digests. Defaults to `account`; access-key accounts sign raw vouchers as their access-key address. */
857
+ voucherSigner?: viem_Account | undefined
858
858
  /** Viem client instance. Shorthand for `getClient: () => client`. */
859
859
  client?: import('viem').Client | undefined
860
860
  /** Token decimals used to convert `maxDeposit` to raw units. Defaults to `6`. */
@@ -1,4 +1,17 @@
1
1
  import type { Account, Address } from 'viem'
2
+ import type { Account as TempoAccount } from 'viem/tempo'
3
+
4
+ /** Returns whether an account is a Tempo access-key account. */
5
+ export function isAccessKeyAccount(
6
+ account: Account,
7
+ ): account is Account & TempoAccount.AccessKeyAccount {
8
+ return 'accessKeyAddress' in account
9
+ }
10
+
11
+ /** Returns the address that should authorize direct account signatures. */
12
+ export function getAccountSignerAddress(account: Account): Address {
13
+ return isAccessKeyAccount(account) ? account.accessKeyAddress : account.address
14
+ }
2
15
 
3
16
  /**
4
17
  * Resolves a recipient address and optional fee payer from flexible input parameters.
@@ -1,4 +1,4 @@
1
- import { encodeFunctionData } from 'viem'
1
+ import { encodeFunctionData, maxUint256 } from 'viem'
2
2
  import { Abis, Addresses } from 'viem/tempo'
3
3
  import { describe, expect, test } from 'vp/test'
4
4
 
@@ -443,7 +443,7 @@ describe('prepareSponsoredTransaction', () => {
443
443
  maxFeePerGas: 1_000_000_000n,
444
444
  maxPriorityFeePerGas: 1_000_000_000n,
445
445
  nonce: 1n,
446
- nonceKey: 1n,
446
+ nonceKey: 'expiring',
447
447
  signature: { r: 1n, s: 1n, yParity: 0 } as any,
448
448
  validBefore: Math.floor(Date.now() / 1_000) + 300,
449
449
  } as const
@@ -460,6 +460,36 @@ describe('prepareSponsoredTransaction', () => {
460
460
  ).not.toThrow()
461
461
  })
462
462
 
463
+ test('accepts serialized expiring nonce key', () => {
464
+ expect(() =>
465
+ prepareSponsoredTransaction({
466
+ account: sponsor,
467
+ chainId: 42431,
468
+ details,
469
+ expectedFeeToken: bogus,
470
+ transaction: {
471
+ ...baseTransaction,
472
+ nonceKey: maxUint256,
473
+ } as any,
474
+ }),
475
+ ).not.toThrow()
476
+ })
477
+
478
+ test('error: rejects non-expiring nonce keys', () => {
479
+ expect(() =>
480
+ prepareSponsoredTransaction({
481
+ account: sponsor,
482
+ chainId: 42431,
483
+ details,
484
+ expectedFeeToken: bogus,
485
+ transaction: {
486
+ ...baseTransaction,
487
+ nonceKey: 1n,
488
+ } as any,
489
+ }),
490
+ ).toThrow('must use an expiring nonce')
491
+ })
492
+
463
493
  test('accepts higher Moderato priority fees by default', () => {
464
494
  expect(() =>
465
495
  prepareSponsoredTransaction({
@@ -2,7 +2,7 @@ import type { TempoAddress } from 'ox/tempo'
2
2
  import { TxEnvelopeTempo } from 'ox/tempo'
3
3
  import type { Hex } from 'viem'
4
4
  import type { Account } from 'viem'
5
- import { decodeFunctionData } from 'viem'
5
+ import { decodeFunctionData, maxUint256 } from 'viem'
6
6
  import { Abis, Addresses, Transaction } from 'viem/tempo'
7
7
 
8
8
  import * as TempoAddress_internal from './address.js'
@@ -126,6 +126,10 @@ function getPolicy(chainId: number, overrides: Partial<Policy> | undefined): Pol
126
126
  }
127
127
  }
128
128
 
129
+ function isExpiringNonceKey(nonceKey: SponsoredTransaction['nonceKey']): boolean {
130
+ return nonceKey === 'expiring' || nonceKey === maxUint256
131
+ }
132
+
129
133
  /** Validates that a set of transaction calls matches an allowed fee-payer pattern. */
130
134
  export function validateCalls(
131
135
  calls: readonly { data?: `0x${string}` | undefined; to?: TempoAddress.Address | undefined }[],
@@ -358,7 +362,7 @@ export function prepareSponsoredTransaction(parameters: {
358
362
  maxPriorityFeePerGas: maxPriorityFeePerGas.toString(),
359
363
  })
360
364
 
361
- if (nonceKey === undefined) fail('fee-sponsored transaction must use an expiring nonce')
365
+ if (!isExpiringNonceKey(nonceKey)) fail('fee-sponsored transaction must use an expiring nonce')
362
366
  if (validBefore === undefined)
363
367
  fail('fee-sponsored transaction must declare validBefore for the expiring nonce')
364
368
  const validBeforeValue = validBefore