mppx 0.7.0 → 0.8.0

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 (278) hide show
  1. package/CHANGELOG.md +33 -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/client/Mppx.js +2 -2
  13. package/dist/client/Mppx.js.map +1 -1
  14. package/dist/client/Transport.d.ts +11 -16
  15. package/dist/client/Transport.d.ts.map +1 -1
  16. package/dist/client/Transport.js +55 -75
  17. package/dist/client/Transport.js.map +1 -1
  18. package/dist/client/index.d.ts +3 -0
  19. package/dist/client/index.d.ts.map +1 -1
  20. package/dist/client/index.js +1 -0
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/client/internal/Fetch.d.ts.map +1 -1
  23. package/dist/client/internal/Fetch.js +46 -7
  24. package/dist/client/internal/Fetch.js.map +1 -1
  25. package/dist/client/internal/protocols/Mcp.d.ts +7 -0
  26. package/dist/client/internal/protocols/Mcp.d.ts.map +1 -0
  27. package/dist/client/internal/protocols/Mcp.js +159 -0
  28. package/dist/client/internal/protocols/Mcp.js.map +1 -0
  29. package/dist/client/internal/protocols/Mpp.d.ts +4 -0
  30. package/dist/client/internal/protocols/Mpp.d.ts.map +1 -0
  31. package/dist/client/internal/protocols/Mpp.js +18 -0
  32. package/dist/client/internal/protocols/Mpp.js.map +1 -0
  33. package/dist/client/internal/protocols/Protocol.d.ts +10 -0
  34. package/dist/client/internal/protocols/Protocol.d.ts.map +1 -0
  35. package/dist/client/internal/protocols/Protocol.js +2 -0
  36. package/dist/client/internal/protocols/Protocol.js.map +1 -0
  37. package/dist/client/internal/protocols/Shared.d.ts +5 -0
  38. package/dist/client/internal/protocols/Shared.d.ts.map +1 -0
  39. package/dist/client/internal/protocols/Shared.js +20 -0
  40. package/dist/client/internal/protocols/Shared.js.map +1 -0
  41. package/dist/client/internal/protocols/X402.d.ts +8 -0
  42. package/dist/client/internal/protocols/X402.d.ts.map +1 -0
  43. package/dist/client/internal/protocols/X402.js +39 -0
  44. package/dist/client/internal/protocols/X402.js.map +1 -0
  45. package/dist/evm/client/index.d.ts +1 -0
  46. package/dist/evm/client/index.d.ts.map +1 -1
  47. package/dist/evm/client/index.js +1 -0
  48. package/dist/evm/client/index.js.map +1 -1
  49. package/dist/evm/index.d.ts +2 -0
  50. package/dist/evm/index.d.ts.map +1 -1
  51. package/dist/evm/index.js +2 -0
  52. package/dist/evm/index.js.map +1 -1
  53. package/dist/evm/server/index.d.ts +1 -0
  54. package/dist/evm/server/index.d.ts.map +1 -1
  55. package/dist/evm/server/index.js +1 -0
  56. package/dist/evm/server/index.js.map +1 -1
  57. package/dist/mcp/client/McpClient.d.ts +101 -0
  58. package/dist/mcp/client/McpClient.d.ts.map +1 -0
  59. package/dist/mcp/client/McpClient.js +162 -0
  60. package/dist/mcp/client/McpClient.js.map +1 -0
  61. package/dist/mcp/client/index.d.ts.map +1 -0
  62. package/dist/mcp/client/index.js.map +1 -0
  63. package/dist/mcp/server/Transport.d.ts.map +1 -0
  64. package/dist/mcp/server/Transport.js.map +1 -0
  65. package/dist/mcp/server/index.d.ts.map +1 -0
  66. package/dist/mcp/server/index.js.map +1 -0
  67. package/dist/server/Mppx.d.ts +1 -1
  68. package/dist/server/Mppx.d.ts.map +1 -1
  69. package/dist/server/Mppx.js +9 -0
  70. package/dist/server/Mppx.js.map +1 -1
  71. package/dist/server/Transport.d.ts +1 -1
  72. package/dist/server/Transport.d.ts.map +1 -1
  73. package/dist/server/Transport.js +1 -1
  74. package/dist/server/Transport.js.map +1 -1
  75. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  76. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  77. package/dist/stripe/server/internal/html.gen.js +1 -1
  78. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  79. package/dist/tempo/Proof.d.ts +85 -1
  80. package/dist/tempo/Proof.d.ts.map +1 -1
  81. package/dist/tempo/Proof.js +35 -0
  82. package/dist/tempo/Proof.js.map +1 -1
  83. package/dist/tempo/client/Charge.d.ts +13 -1
  84. package/dist/tempo/client/Charge.d.ts.map +1 -1
  85. package/dist/tempo/client/Charge.js +38 -25
  86. package/dist/tempo/client/Charge.js.map +1 -1
  87. package/dist/tempo/client/Methods.d.ts +5 -3
  88. package/dist/tempo/client/Methods.d.ts.map +1 -1
  89. package/dist/tempo/client/Methods.js +4 -2
  90. package/dist/tempo/client/Methods.js.map +1 -1
  91. package/dist/tempo/client/ResolveAccount.d.ts +40 -0
  92. package/dist/tempo/client/ResolveAccount.d.ts.map +1 -0
  93. package/dist/tempo/client/ResolveAccount.js +2 -0
  94. package/dist/tempo/client/ResolveAccount.js.map +1 -0
  95. package/dist/tempo/internal/fee-payer.d.ts +9 -1
  96. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  97. package/dist/tempo/internal/fee-payer.js +35 -6
  98. package/dist/tempo/internal/fee-payer.js.map +1 -1
  99. package/dist/tempo/internal/proof.d.ts +71 -5
  100. package/dist/tempo/internal/proof.d.ts.map +1 -1
  101. package/dist/tempo/internal/proof.js +42 -6
  102. package/dist/tempo/internal/proof.js.map +1 -1
  103. package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -1
  104. package/dist/tempo/legacy/client/SessionManager.js +10 -3
  105. package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
  106. package/dist/tempo/server/Charge.d.ts.map +1 -1
  107. package/dist/tempo/server/Charge.js +42 -18
  108. package/dist/tempo/server/Charge.js.map +1 -1
  109. package/dist/tempo/server/Methods.d.ts +4 -2
  110. package/dist/tempo/server/Methods.d.ts.map +1 -1
  111. package/dist/tempo/server/Methods.js +4 -2
  112. package/dist/tempo/server/Methods.js.map +1 -1
  113. package/dist/tempo/server/Subscription.d.ts +10 -0
  114. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  115. package/dist/tempo/server/Subscription.js +135 -23
  116. package/dist/tempo/server/Subscription.js.map +1 -1
  117. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  118. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  119. package/dist/tempo/server/internal/html.gen.js +1 -1
  120. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  121. package/dist/tempo/session/client/ChannelOps.d.ts +2 -3
  122. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  123. package/dist/tempo/session/client/ChannelOps.js +7 -10
  124. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  125. package/dist/tempo/session/client/ChannelStore.d.ts +51 -0
  126. package/dist/tempo/session/client/ChannelStore.d.ts.map +1 -0
  127. package/dist/tempo/session/client/ChannelStore.js +63 -0
  128. package/dist/tempo/session/client/ChannelStore.js.map +1 -0
  129. package/dist/tempo/session/client/CredentialState.d.ts +7 -24
  130. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  131. package/dist/tempo/session/client/CredentialState.js +51 -49
  132. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  133. package/dist/tempo/session/client/Session.d.ts +8 -2
  134. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  135. package/dist/tempo/session/client/Session.js +22 -8
  136. package/dist/tempo/session/client/Session.js.map +1 -1
  137. package/dist/tempo/session/client/SessionManager.d.ts +4 -40
  138. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  139. package/dist/tempo/session/client/SessionManager.js +124 -174
  140. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  141. package/dist/tempo/session/client/index.d.ts +3 -4
  142. package/dist/tempo/session/client/index.d.ts.map +1 -1
  143. package/dist/tempo/session/client/index.js +1 -0
  144. package/dist/tempo/session/client/index.js.map +1 -1
  145. package/dist/tempo/session/precompile/Voucher.d.ts +3 -3
  146. package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
  147. package/dist/tempo/session/precompile/Voucher.js +24 -25
  148. package/dist/tempo/session/precompile/Voucher.js.map +1 -1
  149. package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
  150. package/dist/tempo/session/server/Settlement.js +4 -2
  151. package/dist/tempo/session/server/Settlement.js.map +1 -1
  152. package/dist/tempo/session/server/Sse.d.ts.map +1 -1
  153. package/dist/tempo/session/server/Sse.js.map +1 -1
  154. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  155. package/dist/tempo/session/server/Ws.js.map +1 -1
  156. package/dist/tempo/subscription/KeyAuthorization.d.ts +712 -1
  157. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
  158. package/dist/tempo/subscription/Store.d.ts +2 -0
  159. package/dist/tempo/subscription/Store.d.ts.map +1 -1
  160. package/dist/tempo/subscription/Store.js +16 -1
  161. package/dist/tempo/subscription/Store.js.map +1 -1
  162. package/dist/x402/index.d.ts +1 -0
  163. package/dist/x402/index.d.ts.map +1 -1
  164. package/dist/x402/index.js +1 -0
  165. package/dist/x402/index.js.map +1 -1
  166. package/package.json +21 -10
  167. package/src/Challenge.test.ts +40 -0
  168. package/src/Challenge.ts +19 -6
  169. package/src/Mcp.ts +4 -0
  170. package/src/PaymentRequest.ts +10 -10
  171. package/src/cli/cli.test.ts +15 -15
  172. package/src/client/Mppx.test-d.ts +21 -1
  173. package/src/client/Mppx.test.ts +1 -1
  174. package/src/client/Mppx.ts +2 -2
  175. package/src/client/Transport.test.ts +225 -178
  176. package/src/client/Transport.ts +77 -83
  177. package/src/client/index.ts +14 -0
  178. package/src/client/internal/Fetch.test.ts +207 -2
  179. package/src/client/internal/Fetch.ts +52 -6
  180. package/src/client/internal/protocols/Mcp.test.ts +220 -0
  181. package/src/client/internal/protocols/Mcp.ts +162 -0
  182. package/src/client/internal/protocols/Mpp.ts +21 -0
  183. package/src/client/internal/protocols/Protocol.ts +10 -0
  184. package/src/client/internal/protocols/Shared.ts +25 -0
  185. package/src/client/internal/protocols/X402.ts +42 -0
  186. package/src/discovery/OpenApi.test.ts +1 -1
  187. package/src/evm/PublicInterface.test-d.ts +1 -1
  188. package/src/evm/client/index.ts +1 -0
  189. package/src/evm/index.ts +2 -0
  190. package/src/evm/server/Charge.test.ts +1 -1
  191. package/src/evm/server/index.ts +1 -0
  192. package/src/{mcp-sdk → mcp}/client/McpClient.integration.test.ts +10 -4
  193. package/src/{mcp-sdk → mcp}/client/McpClient.test-d.ts +45 -18
  194. package/src/{mcp-sdk → mcp}/client/McpClient.test.ts +211 -5
  195. package/src/mcp/client/McpClient.ts +307 -0
  196. package/src/{mcp-sdk → mcp}/client/McpClient.unit.test.ts +9 -5
  197. package/src/middlewares/elysia.test.ts +1 -1
  198. package/src/middlewares/express.test.ts +1 -1
  199. package/src/middlewares/hono.test.ts +1 -1
  200. package/src/middlewares/internal/mppx.test.ts +1 -1
  201. package/src/middlewares/nextjs.test.ts +1 -1
  202. package/src/proxy/Proxy.test.ts +1 -1
  203. package/src/proxy/services/anthropic.test.ts +1 -1
  204. package/src/proxy/services/openai.test.ts +1 -1
  205. package/src/proxy/services/stripe.test.ts +1 -1
  206. package/src/server/Mppx.authorize.test.ts +1 -1
  207. package/src/server/Mppx.test-d.ts +1 -1
  208. package/src/server/Mppx.test.ts +20 -2
  209. package/src/server/Mppx.ts +14 -1
  210. package/src/server/Transport.test.ts +6 -6
  211. package/src/server/Transport.ts +1 -1
  212. package/src/stripe/Charge.integration.test.ts +1 -1
  213. package/src/stripe/client/Charge.test.ts +1 -1
  214. package/src/stripe/server/Charge.test.ts +1 -1
  215. package/src/stripe/server/internal/html/package.json +1 -1
  216. package/src/stripe/server/internal/html.gen.ts +1 -1
  217. package/src/tempo/Proof.conformance.test.ts +146 -0
  218. package/src/tempo/Proof.test-d.ts +15 -0
  219. package/src/tempo/Proof.ts +52 -1
  220. package/src/tempo/Subscription.integration.test.ts +1 -1
  221. package/src/tempo/client/Charge.test.ts +173 -0
  222. package/src/tempo/client/Charge.ts +65 -36
  223. package/src/tempo/client/Methods.ts +4 -2
  224. package/src/tempo/client/ResolveAccount.ts +46 -0
  225. package/src/tempo/internal/fee-payer.test.ts +65 -10
  226. package/src/tempo/internal/fee-payer.ts +42 -6
  227. package/src/tempo/internal/proof.test.ts +12 -4
  228. package/src/tempo/internal/proof.ts +55 -6
  229. package/src/tempo/legacy/client/SessionManager.ts +11 -3
  230. package/src/tempo/legacy/server/Session.test.ts +91 -26
  231. package/src/tempo/server/Charge.test.ts +388 -17
  232. package/src/tempo/server/Charge.ts +46 -24
  233. package/src/tempo/server/Methods.ts +4 -2
  234. package/src/tempo/server/Subscription.test.ts +465 -3
  235. package/src/tempo/server/Subscription.ts +174 -19
  236. package/src/tempo/server/internal/html/package.json +2 -2
  237. package/src/tempo/server/internal/html.gen.ts +1 -1
  238. package/src/tempo/session/client/ChannelOps.ts +5 -19
  239. package/src/tempo/session/client/ChannelStore.ts +111 -0
  240. package/src/tempo/session/client/CredentialState.test.ts +206 -62
  241. package/src/tempo/session/client/CredentialState.ts +58 -73
  242. package/src/tempo/session/client/Session.test.ts +41 -1
  243. package/src/tempo/session/client/Session.ts +36 -10
  244. package/src/tempo/session/client/SessionManager.test.ts +154 -65
  245. package/src/tempo/session/client/SessionManager.ts +141 -235
  246. package/src/tempo/session/client/index.ts +8 -5
  247. package/src/tempo/session/precompile/Voucher.test.ts +45 -7
  248. package/src/tempo/session/precompile/Voucher.ts +27 -25
  249. package/src/tempo/session/server/Session.test.ts +4 -4
  250. package/src/tempo/session/server/Settlement.test.ts +88 -1
  251. package/src/tempo/session/server/Settlement.ts +2 -1
  252. package/src/tempo/session/server/Sse.ts +0 -2
  253. package/src/tempo/session/server/Ws.ts +0 -4
  254. package/src/tempo/subscription/Store.ts +27 -9
  255. package/src/x402/Exact.e2e.test.ts +1 -1
  256. package/src/x402/PublicInterface.test-d.ts +1 -1
  257. package/src/x402/index.ts +1 -0
  258. package/dist/mcp-sdk/client/McpClient.d.ts +0 -85
  259. package/dist/mcp-sdk/client/McpClient.d.ts.map +0 -1
  260. package/dist/mcp-sdk/client/McpClient.js +0 -118
  261. package/dist/mcp-sdk/client/McpClient.js.map +0 -1
  262. package/dist/mcp-sdk/client/index.d.ts.map +0 -1
  263. package/dist/mcp-sdk/client/index.js.map +0 -1
  264. package/dist/mcp-sdk/server/Transport.d.ts.map +0 -1
  265. package/dist/mcp-sdk/server/Transport.js.map +0 -1
  266. package/dist/mcp-sdk/server/index.d.ts.map +0 -1
  267. package/dist/mcp-sdk/server/index.js.map +0 -1
  268. package/src/mcp-sdk/client/McpClient.ts +0 -228
  269. /package/dist/{mcp-sdk → mcp}/client/index.d.ts +0 -0
  270. /package/dist/{mcp-sdk → mcp}/client/index.js +0 -0
  271. /package/dist/{mcp-sdk → mcp}/server/Transport.d.ts +0 -0
  272. /package/dist/{mcp-sdk → mcp}/server/Transport.js +0 -0
  273. /package/dist/{mcp-sdk → mcp}/server/index.d.ts +0 -0
  274. /package/dist/{mcp-sdk → mcp}/server/index.js +0 -0
  275. /package/src/{mcp-sdk → mcp}/client/index.ts +0 -0
  276. /package/src/{mcp-sdk → mcp}/server/Transport.test.ts +0 -0
  277. /package/src/{mcp-sdk → mcp}/server/Transport.ts +0 -0
  278. /package/src/{mcp-sdk → mcp}/server/index.ts +0 -0
@@ -25,6 +25,7 @@ import {
25
25
  resolveEscrow,
26
26
  type ChannelEntry,
27
27
  } from './ChannelOps.js'
28
+ import { channelKey, entryKey, type ChannelSink } from './ChannelStore.js'
28
29
  import { assertWithinMaxDeposit, resolveOpeningDeposit } from './Runtime.js'
29
30
 
30
31
  /** Credential payload variants that carry cumulative voucher authorization. */
@@ -33,39 +34,6 @@ export type CumulativeCredentialPayload = Extract<
33
34
  { cumulativeAmount: string }
34
35
  >
35
36
 
36
- /** In-memory client channel cache used by automatic precompile session credential planning. */
37
- export type ChannelCache = {
38
- /** Maps reusable channel keys to cached channel metadata. */
39
- channels: Map<string, ChannelEntry>
40
- /** Maps channel IDs back to reusable channel keys for manual credential cache updates. */
41
- channelIdToKey: Map<string, string>
42
- /** Emits cache updates to the public `onChannelUpdate` callback. */
43
- notifyUpdate(entry: ChannelEntry): void
44
- }
45
-
46
- /** Creates an empty client channel cache with an optional update callback. */
47
- export function createChannelCache(
48
- onUpdate?: ((entry: ChannelEntry) => void) | undefined,
49
- ): ChannelCache {
50
- return {
51
- channels: new Map(),
52
- channelIdToKey: new Map(),
53
- notifyUpdate: (entry) => onUpdate?.(entry),
54
- } satisfies ChannelCache
55
- }
56
-
57
- /** Returns the cache key for a reusable payer session channel. */
58
- export function channelKey(payee: Address, token: Address, escrow: Address): string {
59
- return `${payee.toLowerCase()}:${token.toLowerCase()}:${escrow.toLowerCase()}`
60
- }
61
-
62
- /** Stores a channel entry and notifies observers. */
63
- export function storeChannelEntry(cache: ChannelCache, key: string, entry: ChannelEntry): void {
64
- cache.channels.set(key, entry)
65
- cache.channelIdToKey.set(entry.channelId, key)
66
- cache.notifyUpdate(entry)
67
- }
68
-
69
37
  /** Returns whether a credential payload carries cumulative voucher authorization. */
70
38
  export function hasCredentialCumulativeAmount(
71
39
  payload: SessionCredentialPayload,
@@ -81,21 +49,32 @@ export function readCredentialCumulativeAmount(
81
49
  return BigInt(payload.cumulativeAmount)
82
50
  }
83
51
 
84
- /** Applies a credential payload's cumulative amount to an existing cached channel. */
85
- export function updateCachedCumulative(
86
- cache: ChannelCache,
87
- channelId: Hex,
52
+ /**
53
+ * Persists a channel entry through the sink and notifies observers. Closed
54
+ * channels are removed from the store but still reported to observers so callers
55
+ * can react to the close.
56
+ */
57
+ async function storeChannelEntry(sink: ChannelSink, entry: ChannelEntry): Promise<void> {
58
+ if (entry.opened) await sink.store.set(entry)
59
+ else await sink.store.delete(entryKey(entry))
60
+ sink.notifyUpdate(entry)
61
+ }
62
+
63
+ /** Applies a credential payload's cumulative amount to the stored channel at `key`. */
64
+ async function applyCumulative(
65
+ sink: ChannelSink,
66
+ key: string,
88
67
  payload: SessionCredentialPayload,
89
- ): void {
90
- const key = cache.channelIdToKey.get(channelId)
68
+ ): Promise<void> {
91
69
  const cumulativeAmount = readCredentialCumulativeAmount(payload)
92
- if (!key || cumulativeAmount === undefined) return
93
- const entry = cache.channels.get(key)
70
+ if (cumulativeAmount === undefined) return
71
+ const entry = await sink.store.get(key)
94
72
  if (!entry) return
73
+ if (entry.channelId.toLowerCase() !== payload.channelId.toLowerCase()) return
95
74
  entry.cumulativeAmount =
96
75
  entry.cumulativeAmount > cumulativeAmount ? entry.cumulativeAmount : cumulativeAmount
97
76
  if (payload.action === 'close') entry.opened = false
98
- cache.notifyUpdate(entry)
77
+ await storeChannelEntry(sink, entry)
99
78
  }
100
79
 
101
80
  const hexSchema = z.custom<Hex>(
@@ -252,7 +231,7 @@ export type ReusableChannelExpectation = {
252
231
  payee: Address
253
232
  /** Payer address controlled by the local account. */
254
233
  payer: Address
255
- /** Voucher signer resolved from local account configuration. */
234
+ /** Voucher authority resolved from the local account. */
256
235
  authorizedSigner: Address
257
236
  /** Token expected by the current challenge. */
258
237
  token: Address
@@ -300,8 +279,8 @@ export type ChallengeContext = {
300
279
  /** Inputs used to choose the next client-side session credential operation. */
301
280
  export type PlanCredentialParameters = {
302
281
  account: ViemAccount
303
- authorizedSigner?: Address | undefined
304
- cache: ChannelCache
282
+ /** Channel previously stored for this challenge scope, fetched by the caller. */
283
+ entry: ChannelEntry | undefined
305
284
  context?: SessionContext | undefined
306
285
  decimals: number
307
286
  maxDeposit?: bigint | undefined
@@ -336,7 +315,6 @@ export type CredentialPlan =
336
315
  | {
337
316
  type: 'open'
338
317
  account: ViemAccount
339
- authorizedSigner?: Address | undefined
340
318
  context?: SessionContext | undefined
341
319
  maxDeposit?: bigint | undefined
342
320
  resolved: ChallengeContext
@@ -345,7 +323,6 @@ export type CredentialPlan =
345
323
  | {
346
324
  type: 'recover'
347
325
  account: ViemAccount
348
- authorizedSigner?: Address | undefined
349
326
  context: DescriptorSessionContext
350
327
  decimals: number
351
328
  maxDeposit?: bigint | undefined
@@ -363,7 +340,6 @@ export type CredentialPlan =
363
340
  | {
364
341
  type: 'manual'
365
342
  account: ViemAccount
366
- authorizedSigner?: Address | undefined
367
343
  context: ManualSessionDescriptorContext
368
344
  decimals: number
369
345
  resolved: ChallengeContext
@@ -436,7 +412,7 @@ export async function resolveChallengeContext(
436
412
  client,
437
413
  escrow,
438
414
  feePayer: methodDetails.feePayer,
439
- key: channelKey(payee, token, escrow),
415
+ key: channelKey({ payee, token, escrow, chainId }),
440
416
  operator: methodDetails.operator,
441
417
  payee,
442
418
  snapshot: methodDetails.sessionSnapshot,
@@ -533,9 +509,21 @@ export function resolveRecoveredCumulative(
533
509
  return settled + requestAmount
534
510
  }
535
511
 
512
+ /** Returns whether `account` can satisfy the descriptor's voucher authority. */
513
+ export function canSignDescriptor(
514
+ account: ViemAccount,
515
+ descriptor: Channel.ChannelDescriptor,
516
+ ): boolean {
517
+ // Only the payer can deposit into and voucher against its own channel.
518
+ if (!isSameAddress(account.address, descriptor.payer)) return false
519
+ const authority = descriptor.authorizedSigner
520
+ if (BigInt(authority) === 0n || isSameAddress(authority, descriptor.payer)) return true
521
+ return isSameAddress(resolveAuthorizedSigner(account), authority)
522
+ }
523
+
536
524
  /** Chooses the next credential plan from local channel cache and optional caller context. */
537
525
  export function planCredential(parameters: PlanCredentialParameters): CredentialPlan {
538
- const { account, authorizedSigner, cache, context, decimals, maxDeposit, resolved } = parameters
526
+ const { account, entry, context, decimals, maxDeposit, resolved } = parameters
539
527
 
540
528
  if (hasSessionAction(context)) {
541
529
  if (!hasManualSessionDescriptor(context))
@@ -543,54 +531,52 @@ export function planCredential(parameters: PlanCredentialParameters): Credential
543
531
  return {
544
532
  type: 'manual',
545
533
  account,
546
- authorizedSigner,
547
534
  context,
548
535
  decimals,
549
536
  resolved,
550
537
  }
551
538
  }
552
539
 
553
- const entry = cache.channels.get(resolved.key)
554
540
  if (!entry && context?.channelId && !context.descriptor)
555
541
  throw new Error('descriptor required to reuse TIP-1034 channel')
556
542
  const recoverContext = resolveRecoverContext({ context, snapshot: resolved.snapshot })
557
- if (!entry && recoverContext) {
543
+ if (!entry && recoverContext && canSignDescriptor(account, recoverContext.descriptor)) {
558
544
  return {
559
545
  type: 'recover',
560
546
  account,
561
- authorizedSigner,
562
547
  context: recoverContext,
563
548
  decimals,
564
549
  maxDeposit,
565
550
  resolved,
566
551
  }
567
552
  }
568
- if (entry?.opened) return { type: 'voucher', account, entry, maxDeposit, resolved }
569
- return { type: 'open', account, authorizedSigner, context, maxDeposit, resolved }
553
+ if (entry?.opened && canSignDescriptor(account, entry.descriptor))
554
+ return { type: 'voucher', account, entry, maxDeposit, resolved }
555
+ return { type: 'open', account, context, maxDeposit, resolved }
570
556
  }
571
557
 
572
558
  /** Executes a credential plan and returns the concrete session credential payload. */
573
559
  export async function executeCredentialPlan(
574
560
  plan: CredentialPlan,
575
- cache: ChannelCache,
561
+ sink: ChannelSink,
576
562
  ): Promise<SessionCredentialPayload> {
577
563
  switch (plan.type) {
578
564
  case 'open':
579
- return open(plan, cache)
565
+ return open(plan, sink)
580
566
  case 'recover':
581
- return recover(plan, cache)
567
+ return recover(plan, sink)
582
568
  case 'voucher':
583
- return voucher(plan, cache)
569
+ return voucher(plan, sink)
584
570
  case 'manual':
585
- return manual(plan, cache)
571
+ return manual(plan, sink)
586
572
  }
587
573
  }
588
574
 
589
575
  async function open(
590
576
  plan: Extract<CredentialPlan, { type: 'open' }>,
591
- cache: ChannelCache,
577
+ sink: ChannelSink,
592
578
  ): Promise<SessionCredentialPayload> {
593
- const { account, authorizedSigner, resolved } = plan
579
+ const { account, resolved } = plan
594
580
  const deposit = resolveOpeningDeposit({
595
581
  contextDepositRaw: plan.context?.depositRaw,
596
582
  maxDeposit: plan.maxDeposit,
@@ -598,7 +584,6 @@ async function open(
598
584
  suggestedDepositRaw: resolved.suggestedDepositRaw,
599
585
  })
600
586
  const payload = await createOpenPayload(resolved.client, account, {
601
- authorizedSigner,
602
587
  chainId: resolved.chainId,
603
588
  deposit,
604
589
  escrow: resolved.escrow,
@@ -608,7 +593,7 @@ async function open(
608
593
  payee: resolved.payee,
609
594
  token: resolved.token,
610
595
  })
611
- storeChannelEntry(cache, resolved.key, {
596
+ await storeChannelEntry(sink, {
612
597
  channelId: payload.channelId,
613
598
  cumulativeAmount: resolved.amount,
614
599
  deposit,
@@ -622,7 +607,7 @@ async function open(
622
607
 
623
608
  async function recover(
624
609
  plan: Extract<CredentialPlan, { type: 'recover' }>,
625
- cache: ChannelCache,
610
+ sink: ChannelSink,
626
611
  ): Promise<SessionCredentialPayload> {
627
612
  const { account, context, decimals, maxDeposit, resolved } = plan
628
613
  const { descriptor } = context
@@ -635,7 +620,7 @@ async function recover(
635
620
  escrow: resolved.escrow,
636
621
  payee: resolved.payee,
637
622
  payer: account.address,
638
- authorizedSigner: resolveAuthorizedSigner(account, plan.authorizedSigner),
623
+ authorizedSigner: resolveAuthorizedSigner(account),
639
624
  token: resolved.token,
640
625
  },
641
626
  })
@@ -657,7 +642,7 @@ async function recover(
657
642
  resolved.chainId,
658
643
  resolved.escrow,
659
644
  )
660
- storeChannelEntry(cache, resolved.key, {
645
+ await storeChannelEntry(sink, {
661
646
  channelId: reusable.channelId,
662
647
  cumulativeAmount,
663
648
  deposit: reusable.state.deposit,
@@ -671,7 +656,7 @@ async function recover(
671
656
 
672
657
  async function voucher(
673
658
  plan: Extract<CredentialPlan, { type: 'voucher' }>,
674
- cache: ChannelCache,
659
+ sink: ChannelSink,
675
660
  ): Promise<SessionCredentialPayload> {
676
661
  const { account, entry, resolved } = plan
677
662
  const cumulativeAmount = entry.cumulativeAmount + resolved.amount
@@ -685,13 +670,13 @@ async function voucher(
685
670
  resolved.escrow,
686
671
  )
687
672
  entry.cumulativeAmount = cumulativeAmount
688
- cache.notifyUpdate(entry)
673
+ await storeChannelEntry(sink, entry)
689
674
  return payload
690
675
  }
691
676
 
692
677
  async function manual(
693
678
  plan: Extract<CredentialPlan, { type: 'manual' }>,
694
- cache: ChannelCache,
679
+ sink: ChannelSink,
695
680
  ): Promise<SessionCredentialPayload> {
696
681
  const { account, context, decimals, resolved } = plan
697
682
  const { descriptor } = context
@@ -706,7 +691,7 @@ async function manual(
706
691
  expectedChannelId: channelId,
707
692
  payee: resolved.payee,
708
693
  payer: account.address,
709
- authorizedSigner: resolveAuthorizedSigner(account, plan.authorizedSigner),
694
+ authorizedSigner: resolveAuthorizedSigner(account),
710
695
  token: resolved.token,
711
696
  })
712
697
 
@@ -718,7 +703,7 @@ async function manual(
718
703
  descriptor,
719
704
  resolved,
720
705
  })
721
- updateCachedCumulative(cache, channelId, payload)
706
+ await applyCumulative(sink, resolved.key, payload)
722
707
  return payload
723
708
  }
724
709
 
@@ -1,6 +1,6 @@
1
1
  import { type Address, createClient, custom, decodeFunctionData, encodeFunctionResult } from 'viem'
2
2
  import { privateKeyToAccount } from 'viem/accounts'
3
- import { Transaction } from 'viem/tempo'
3
+ import { Account as TempoAccount, Secp256k1, Transaction } from 'viem/tempo'
4
4
  import { describe, expect, test } from 'vp/test'
5
5
 
6
6
  import type { Challenge } from '../../../Challenge.js'
@@ -286,6 +286,46 @@ describe('precompile client session', () => {
286
286
  expect(updates).toEqual([100n, 200n])
287
287
  })
288
288
 
289
+ test('passes existing channel authority to resolveAccount before reusing session channel', async () => {
290
+ const accessKey = TempoAccount.fromSecp256k1(Secp256k1.randomPrivateKey(), {
291
+ access: account,
292
+ })
293
+ const calls: session.ResolveAccountInfo[] = []
294
+ const method = session({
295
+ account,
296
+ decimals: 0,
297
+ maxDeposit: '1000',
298
+ getClient: () => client,
299
+ resolveAccount(info) {
300
+ calls.push(info)
301
+ return accessKey
302
+ },
303
+ })
304
+ const first = deserializeCredential(
305
+ await method.createCredential({ challenge: makeChallenge(), context: {} }),
306
+ )
307
+ const second = deserializeCredential(
308
+ await method.createCredential({ challenge: makeChallenge(), context: {} }),
309
+ )
310
+
311
+ expect(calls).toHaveLength(2)
312
+ expect(calls[0]!.account.address).toBe(account.address)
313
+ expect(calls[0]!.operation).toEqual({ kind: 'authorizePaymentChannel' })
314
+ expect(calls[1]!.operation).toEqual({
315
+ kind: 'authorizePaymentChannel',
316
+ authority: accessKey.accessKeyAddress,
317
+ })
318
+ expect(first.source).toBe(`did:pkh:eip155:${chainId}:${account.address}`)
319
+ expect(second.source).toBe(`did:pkh:eip155:${chainId}:${account.address}`)
320
+ expect(first.payload.action).toBe('open')
321
+ if (first.payload.action !== 'open' || second.payload.action !== 'voucher')
322
+ throw new Error('expected open then voucher payloads')
323
+ expect(first.payload.descriptor.payer).toBe(account.address)
324
+ expect(first.payload.descriptor.authorizedSigner).toBe(accessKey.accessKeyAddress)
325
+ expect(second.payload.channelId).toBe(first.payload.channelId)
326
+ expect(second.payload.cumulativeAmount).toBe('200')
327
+ })
328
+
289
329
  test('enforces maxDeposit when reusing a cached auto-mode channel', async () => {
290
330
  const updates: bigint[] = []
291
331
  const method = session({
@@ -5,15 +5,20 @@ import * as Constants from '../../../Constants.js'
5
5
  import * as Method from '../../../Method.js'
6
6
  import * as Account from '../../../viem/Account.js'
7
7
  import * as Client from '../../../viem/Client.js'
8
+ import type {
9
+ ResolveAccount as ResolveAccount_,
10
+ ResolveAccountInfo as ResolveAccountInfo_,
11
+ } from '../../client/ResolveAccount.js'
8
12
  import * as defaults from '../../internal/defaults.js'
9
13
  import * as Methods from '../../Methods.js'
10
14
  import { serializeCredential, type ChannelEntry } from './ChannelOps.js'
11
- import { sessionContextSchema } from './CredentialState.js'
15
+ import { createChannelStore, type ChannelStore } from './ChannelStore.js'
12
16
  import {
13
- createChannelCache,
14
17
  executeCredentialPlan,
15
18
  planCredential,
16
19
  resolveChallengeContext,
20
+ resolveRecoverContext,
21
+ sessionContextSchema,
17
22
  } from './CredentialState.js'
18
23
 
19
24
  export { sessionContextSchema, type SessionContext } from './CredentialState.js'
@@ -28,12 +33,13 @@ export { sessionContextSchema, type SessionContext } from './CredentialState.js'
28
33
  export function session(parameters: session.Parameters = {}) {
29
34
  const {
30
35
  account,
31
- authorizedSigner,
36
+ channelStore,
32
37
  decimals = defaults.decimals,
33
38
  escrow: escrowOverride,
34
39
  getClient: getClientParameter,
35
40
  maxDeposit: maxDepositParameter,
36
41
  onChannelUpdate,
42
+ resolveAccount,
37
43
  } = parameters
38
44
  const getClient = Client.getResolver({
39
45
  chain: tempo_chain,
@@ -43,7 +49,8 @@ export function session(parameters: session.Parameters = {}) {
43
49
  const getAccount = Account.getResolver({ account })
44
50
  const maxDeposit =
45
51
  maxDepositParameter !== undefined ? parseUnits(maxDepositParameter, decimals) : undefined
46
- const cache = createChannelCache(onChannelUpdate)
52
+ const store = channelStore ?? createChannelStore()
53
+ const sink = { store, notifyUpdate: (entry: ChannelEntry) => onChannelUpdate?.(entry) }
47
54
 
48
55
  return Method.toClient(Methods.session, {
49
56
  canHandleChallenge({ challenge }) {
@@ -61,18 +68,32 @@ export function session(parameters: session.Parameters = {}) {
61
68
  escrowOverride,
62
69
  getClient,
63
70
  })
64
- const account = getAccount(resolved.client, context)
71
+ const defaultAccount = getAccount(resolved.client, context)
72
+ const entry = await store.get(resolved.key)
73
+ // Resolve recovery hints early so account selection can satisfy an existing channel authority.
74
+ const recoverContext = resolveRecoverContext({ context, snapshot: resolved.snapshot })
75
+ const descriptor = context?.action
76
+ ? context.descriptor
77
+ : (entry?.descriptor ?? recoverContext?.descriptor)
78
+ const account =
79
+ (await resolveAccount?.({
80
+ account: defaultAccount,
81
+ chainId: resolved.chainId,
82
+ operation: {
83
+ kind: 'authorizePaymentChannel',
84
+ ...(descriptor ? { authority: descriptor.authorizedSigner } : {}),
85
+ },
86
+ })) ?? defaultAccount
65
87
  const payload = await executeCredentialPlan(
66
88
  planCredential({
67
89
  account,
68
- authorizedSigner,
69
- cache,
90
+ entry,
70
91
  context,
71
92
  decimals,
72
93
  maxDeposit,
73
94
  resolved,
74
95
  }),
75
- cache,
96
+ sink,
76
97
  )
77
98
  return serializeCredential(challenge, payload, resolved.chainId, account)
78
99
  },
@@ -81,10 +102,13 @@ export function session(parameters: session.Parameters = {}) {
81
102
 
82
103
  /** Type helpers for the low-level TIP-1034 session client method. */
83
104
  export declare namespace session {
105
+ type ResolveAccount = ResolveAccount_
106
+ type ResolveAccountInfo = ResolveAccountInfo_
107
+
84
108
  type Parameters = Account.getResolver.Parameters &
85
109
  Client.getResolver.Parameters & {
86
- /** Address authorized to sign vouchers on behalf of the payer. Defaults to the account access key address when available, otherwise the account address. */
87
- authorizedSigner?: Address | undefined
110
+ /** Pluggable persistence for reusable channels. Defaults to an in-memory store. */
111
+ channelStore?: ChannelStore | undefined
88
112
  /** Token decimals for parsing human-readable amounts (default: 6). */
89
113
  decimals?: number | undefined
90
114
  /** TIP20EscrowChannel address override. */
@@ -93,5 +117,7 @@ export declare namespace session {
93
117
  maxDeposit?: string | undefined
94
118
  /** Called whenever channel state changes. */
95
119
  onChannelUpdate?: ((entry: ChannelEntry) => void) | undefined
120
+ /** Selects the account that signs this session credential after the challenge is known. */
121
+ resolveAccount?: ResolveAccount | undefined
96
122
  }
97
123
  }