mppx 0.8.12 → 0.8.14

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 (266) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +16 -1
  3. package/dist/Errors.d.ts +10 -0
  4. package/dist/Errors.d.ts.map +1 -1
  5. package/dist/Errors.js +11 -2
  6. package/dist/Errors.js.map +1 -1
  7. package/dist/Method.d.ts +105 -3
  8. package/dist/Method.d.ts.map +1 -1
  9. package/dist/Method.js +102 -2
  10. package/dist/Method.js.map +1 -1
  11. package/dist/cli/account.d.ts +88 -0
  12. package/dist/cli/account.d.ts.map +1 -1
  13. package/dist/cli/account.js +39 -9
  14. package/dist/cli/account.js.map +1 -1
  15. package/dist/cli/cli.d.ts +2 -1
  16. package/dist/cli/cli.d.ts.map +1 -1
  17. package/dist/cli/cli.js +59 -2
  18. package/dist/cli/cli.js.map +1 -1
  19. package/dist/cli/sessions/Manager.d.ts +31 -0
  20. package/dist/cli/sessions/Manager.d.ts.map +1 -0
  21. package/dist/cli/sessions/Manager.js +56 -0
  22. package/dist/cli/sessions/Manager.js.map +1 -0
  23. package/dist/cli/sessions/commands.d.ts +36 -0
  24. package/dist/cli/sessions/commands.d.ts.map +1 -0
  25. package/dist/cli/sessions/commands.js +407 -0
  26. package/dist/cli/sessions/commands.js.map +1 -0
  27. package/dist/cli/sessions/request.d.ts +35 -0
  28. package/dist/cli/sessions/request.d.ts.map +1 -0
  29. package/dist/cli/sessions/request.js +271 -0
  30. package/dist/cli/sessions/request.js.map +1 -0
  31. package/dist/cli/sessions/store.d.ts +134 -0
  32. package/dist/cli/sessions/store.d.ts.map +1 -0
  33. package/dist/cli/sessions/store.js +723 -0
  34. package/dist/cli/sessions/store.js.map +1 -0
  35. package/dist/cli/utils.d.ts +2 -0
  36. package/dist/cli/utils.d.ts.map +1 -1
  37. package/dist/cli/utils.js +12 -0
  38. package/dist/cli/utils.js.map +1 -1
  39. package/dist/cli/validate/index.d.ts.map +1 -1
  40. package/dist/cli/validate/index.js +12 -6
  41. package/dist/cli/validate/index.js.map +1 -1
  42. package/dist/cli/validate/messages.d.ts +2 -0
  43. package/dist/cli/validate/messages.d.ts.map +1 -0
  44. package/dist/cli/validate/messages.js +8 -0
  45. package/dist/cli/validate/messages.js.map +1 -0
  46. package/dist/client/internal/Fetch.d.ts.map +1 -1
  47. package/dist/client/internal/Fetch.js +12 -2
  48. package/dist/client/internal/Fetch.js.map +1 -1
  49. package/dist/client/internal/MethodChallenge.d.ts +17 -0
  50. package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
  51. package/dist/client/internal/MethodChallenge.js +11 -0
  52. package/dist/client/internal/MethodChallenge.js.map +1 -0
  53. package/dist/client/node.d.ts +29 -0
  54. package/dist/client/node.d.ts.map +1 -0
  55. package/dist/client/node.js +177 -0
  56. package/dist/client/node.js.map +1 -0
  57. package/dist/internal/types.d.ts +24 -0
  58. package/dist/internal/types.d.ts.map +1 -1
  59. package/dist/server/Mppx.d.ts +23 -4
  60. package/dist/server/Mppx.d.ts.map +1 -1
  61. package/dist/server/Mppx.js +91 -82
  62. package/dist/server/Mppx.js.map +1 -1
  63. package/dist/stripe/server/Charge.d.ts +8 -4
  64. package/dist/stripe/server/Charge.d.ts.map +1 -1
  65. package/dist/stripe/server/Charge.js.map +1 -1
  66. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  67. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  68. package/dist/stripe/server/internal/html.gen.js +1 -1
  69. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  70. package/dist/tempo/client/Methods.d.ts +2 -1
  71. package/dist/tempo/client/Methods.d.ts.map +1 -1
  72. package/dist/tempo/internal/fee-payer.d.ts +1 -0
  73. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  74. package/dist/tempo/internal/fee-payer.js +7 -2
  75. package/dist/tempo/internal/fee-payer.js.map +1 -1
  76. package/dist/tempo/internal/types.d.ts +10 -1
  77. package/dist/tempo/internal/types.d.ts.map +1 -1
  78. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
  79. package/dist/tempo/legacy/client/ChannelOps.js +2 -1
  80. package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
  81. package/dist/tempo/legacy/client/Session.d.ts +2 -2
  82. package/dist/tempo/legacy/server/Session.d.ts +3 -3
  83. package/dist/tempo/legacy/server/Session.d.ts.map +1 -1
  84. package/dist/tempo/legacy/server/Session.js.map +1 -1
  85. package/dist/tempo/server/Charge.d.ts +40 -78
  86. package/dist/tempo/server/Charge.d.ts.map +1 -1
  87. package/dist/tempo/server/Charge.js +289 -209
  88. package/dist/tempo/server/Charge.js.map +1 -1
  89. package/dist/tempo/server/Methods.d.ts +13 -23
  90. package/dist/tempo/server/Methods.d.ts.map +1 -1
  91. package/dist/tempo/server/Methods.js +6 -2
  92. package/dist/tempo/server/Methods.js.map +1 -1
  93. package/dist/tempo/server/Relay.d.ts +48 -0
  94. package/dist/tempo/server/Relay.d.ts.map +1 -0
  95. package/dist/tempo/server/Relay.js +177 -0
  96. package/dist/tempo/server/Relay.js.map +1 -0
  97. package/dist/tempo/server/SponsorBudget.d.ts +59 -0
  98. package/dist/tempo/server/SponsorBudget.d.ts.map +1 -0
  99. package/dist/tempo/server/SponsorBudget.js +144 -0
  100. package/dist/tempo/server/SponsorBudget.js.map +1 -0
  101. package/dist/tempo/server/Subscription.d.ts +3 -2
  102. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  103. package/dist/tempo/server/Subscription.js.map +1 -1
  104. package/dist/tempo/server/index.d.ts +1 -1
  105. package/dist/tempo/server/index.d.ts.map +1 -1
  106. package/dist/tempo/server/index.js.map +1 -1
  107. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  108. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  109. package/dist/tempo/server/internal/html.gen.js +1 -1
  110. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  111. package/dist/tempo/session/Snapshot.d.ts +9 -0
  112. package/dist/tempo/session/Snapshot.d.ts.map +1 -1
  113. package/dist/tempo/session/Snapshot.js +6 -0
  114. package/dist/tempo/session/Snapshot.js.map +1 -1
  115. package/dist/tempo/session/client/ChannelOps.d.ts +7 -1
  116. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  117. package/dist/tempo/session/client/ChannelOps.js +17 -3
  118. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  119. package/dist/tempo/session/client/CredentialState.d.ts +26 -2
  120. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  121. package/dist/tempo/session/client/CredentialState.js +99 -16
  122. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  123. package/dist/tempo/session/client/Runtime.d.ts +8 -0
  124. package/dist/tempo/session/client/Runtime.d.ts.map +1 -1
  125. package/dist/tempo/session/client/Runtime.js +17 -0
  126. package/dist/tempo/session/client/Runtime.js.map +1 -1
  127. package/dist/tempo/session/client/Session.d.ts +16 -6
  128. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  129. package/dist/tempo/session/client/Session.js +93 -36
  130. package/dist/tempo/session/client/Session.js.map +1 -1
  131. package/dist/tempo/session/client/SessionManager.d.ts +10 -3
  132. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  133. package/dist/tempo/session/client/SessionManager.js +176 -50
  134. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  135. package/dist/tempo/session/client/Transports.d.ts +18 -7
  136. package/dist/tempo/session/client/Transports.d.ts.map +1 -1
  137. package/dist/tempo/session/client/Transports.js +81 -33
  138. package/dist/tempo/session/client/Transports.js.map +1 -1
  139. package/dist/tempo/session/client/internal/SessionManager.d.ts +19 -0
  140. package/dist/tempo/session/client/internal/SessionManager.d.ts.map +1 -0
  141. package/dist/tempo/session/client/internal/SessionManager.js +13 -0
  142. package/dist/tempo/session/client/internal/SessionManager.js.map +1 -0
  143. package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
  144. package/dist/tempo/session/precompile/Chain.js +109 -12
  145. package/dist/tempo/session/precompile/Chain.js.map +1 -1
  146. package/dist/tempo/session/precompile/Channel.d.ts +2 -0
  147. package/dist/tempo/session/precompile/Channel.d.ts.map +1 -1
  148. package/dist/tempo/session/precompile/Channel.js +4 -0
  149. package/dist/tempo/session/precompile/Channel.js.map +1 -1
  150. package/dist/tempo/session/server/CredentialVerification.d.ts +3 -0
  151. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  152. package/dist/tempo/session/server/CredentialVerification.js +14 -0
  153. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  154. package/dist/tempo/session/server/MeteredStream.d.ts +7 -1
  155. package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
  156. package/dist/tempo/session/server/MeteredStream.js +5 -4
  157. package/dist/tempo/session/server/MeteredStream.js.map +1 -1
  158. package/dist/tempo/session/server/RequestState.d.ts +5 -1
  159. package/dist/tempo/session/server/RequestState.d.ts.map +1 -1
  160. package/dist/tempo/session/server/RequestState.js +13 -5
  161. package/dist/tempo/session/server/RequestState.js.map +1 -1
  162. package/dist/tempo/session/server/Session.d.ts +21 -6
  163. package/dist/tempo/session/server/Session.d.ts.map +1 -1
  164. package/dist/tempo/session/server/Session.js +4 -1
  165. package/dist/tempo/session/server/Session.js.map +1 -1
  166. package/dist/tempo/session/server/Settlement.d.ts +20 -0
  167. package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
  168. package/dist/tempo/session/server/Settlement.js +20 -0
  169. package/dist/tempo/session/server/Settlement.js.map +1 -1
  170. package/dist/tempo/session/server/Ws.d.ts +2 -0
  171. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  172. package/dist/tempo/session/server/Ws.js.map +1 -1
  173. package/dist/tempo/session/server/index.d.ts +1 -1
  174. package/dist/tempo/session/server/index.d.ts.map +1 -1
  175. package/dist/tempo/subscription/KeyAuthorization.d.ts +21 -21
  176. package/dist/validation/core.d.ts.map +1 -1
  177. package/dist/validation/core.js +6 -2
  178. package/dist/validation/core.js.map +1 -1
  179. package/dist/viem/Client.d.ts.map +1 -1
  180. package/dist/viem/Client.js +24 -17
  181. package/dist/viem/Client.js.map +1 -1
  182. package/package.json +6 -1
  183. package/src/Errors.test.ts +23 -0
  184. package/src/Errors.ts +21 -2
  185. package/src/Method.test.ts +102 -1
  186. package/src/Method.ts +241 -5
  187. package/src/cli/account.ts +45 -10
  188. package/src/cli/cli.test.ts +165 -70
  189. package/src/cli/cli.ts +67 -2
  190. package/src/cli/mcp.test.ts +11 -0
  191. package/src/cli/sessions/Manager.test.ts +249 -0
  192. package/src/cli/sessions/Manager.ts +93 -0
  193. package/src/cli/sessions/commands.ts +444 -0
  194. package/src/cli/sessions/request.test.ts +51 -0
  195. package/src/cli/sessions/request.ts +353 -0
  196. package/src/cli/sessions/store.test.ts +581 -0
  197. package/src/cli/sessions/store.ts +940 -0
  198. package/src/cli/utils.test.ts +23 -0
  199. package/src/cli/utils.ts +10 -0
  200. package/src/cli/validate/index.ts +12 -14
  201. package/src/cli/validate/messages.ts +7 -0
  202. package/src/cli/validate.test.ts +38 -5
  203. package/src/client/Mppx.test-d.ts +3 -3
  204. package/src/client/internal/Fetch.ts +16 -7
  205. package/src/client/internal/MethodChallenge.ts +30 -0
  206. package/src/client/node.test.ts +115 -0
  207. package/src/client/node.ts +247 -0
  208. package/src/internal/types.test-d.ts +21 -0
  209. package/src/internal/types.ts +33 -0
  210. package/src/server/Mppx.test-d.ts +44 -0
  211. package/src/server/Mppx.test.ts +205 -0
  212. package/src/server/Mppx.ts +167 -103
  213. package/src/stripe/Methods.test.ts +10 -0
  214. package/src/stripe/server/Charge.test-d.ts +66 -0
  215. package/src/stripe/server/Charge.ts +8 -5
  216. package/src/stripe/server/internal/html.gen.ts +1 -1
  217. package/src/tempo/PublicExports.test-d.ts +20 -0
  218. package/src/tempo/internal/fee-payer.ts +7 -3
  219. package/src/tempo/internal/types.ts +13 -2
  220. package/src/tempo/legacy/client/ChannelOps.test.ts +7 -0
  221. package/src/tempo/legacy/client/ChannelOps.ts +2 -1
  222. package/src/tempo/legacy/server/Defaults.test-d.ts +9 -0
  223. package/src/tempo/legacy/server/Session.ts +6 -5
  224. package/src/tempo/server/Charge.test.ts +337 -63
  225. package/src/tempo/server/Charge.ts +440 -270
  226. package/src/tempo/server/Methods.ts +16 -4
  227. package/src/tempo/server/Relay.test.ts +523 -0
  228. package/src/tempo/server/Relay.ts +288 -0
  229. package/src/tempo/server/SponsorBudget.test.ts +125 -0
  230. package/src/tempo/server/SponsorBudget.ts +213 -0
  231. package/src/tempo/server/Subscription.ts +6 -4
  232. package/src/tempo/server/index.ts +5 -1
  233. package/src/tempo/server/internal/html.gen.ts +1 -1
  234. package/src/tempo/session/README.md +201 -0
  235. package/src/tempo/session/Snapshot.ts +21 -0
  236. package/src/tempo/session/client/ChannelOps.test.ts +55 -0
  237. package/src/tempo/session/client/ChannelOps.ts +21 -3
  238. package/src/tempo/session/client/CredentialState.test.ts +2 -2
  239. package/src/tempo/session/client/CredentialState.ts +159 -16
  240. package/src/tempo/session/client/Runtime.test.ts +12 -0
  241. package/src/tempo/session/client/Runtime.ts +22 -0
  242. package/src/tempo/session/client/Session.test.ts +238 -5
  243. package/src/tempo/session/client/Session.ts +130 -41
  244. package/src/tempo/session/client/SessionManager.test.ts +239 -8
  245. package/src/tempo/session/client/SessionManager.ts +204 -54
  246. package/src/tempo/session/client/Transports.test.ts +181 -8
  247. package/src/tempo/session/client/Transports.ts +127 -45
  248. package/src/tempo/session/client/internal/SessionManager.ts +36 -0
  249. package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
  250. package/src/tempo/session/precompile/Chain.test.ts +223 -10
  251. package/src/tempo/session/precompile/Chain.ts +119 -12
  252. package/src/tempo/session/precompile/Channel.test.ts +9 -0
  253. package/src/tempo/session/precompile/Channel.ts +5 -0
  254. package/src/tempo/session/server/CredentialVerification.ts +18 -1
  255. package/src/tempo/session/server/MeteredStream.ts +12 -5
  256. package/src/tempo/session/server/RequestState.test.ts +47 -4
  257. package/src/tempo/session/server/RequestState.ts +15 -8
  258. package/src/tempo/session/server/Session.test.ts +461 -0
  259. package/src/tempo/session/server/Session.ts +31 -8
  260. package/src/tempo/session/server/Settlement.ts +45 -0
  261. package/src/tempo/session/server/Ws.test.ts +113 -0
  262. package/src/tempo/session/server/Ws.ts +2 -0
  263. package/src/tempo/session/server/index.ts +2 -0
  264. package/src/validation/core.ts +7 -1
  265. package/src/viem/Client.test.ts +44 -1
  266. package/src/viem/Client.ts +31 -17
@@ -1,23 +1,28 @@
1
1
  import type { Hex } from 'ox'
2
2
  import { parseUnits, type Address } from 'viem'
3
+ import { tempo as tempo_chain } from 'viem/chains'
3
4
 
4
5
  import * as Challenge from '../../../Challenge.js'
5
6
  import * as Fetch from '../../../client/internal/Fetch.js'
7
+ import * as MethodChallenge from '../../../client/internal/MethodChallenge.js'
6
8
  import * as MethodResponse from '../../../client/internal/MethodResponse.js'
7
9
  import * as Constants from '../../../Constants.js'
8
- import type * as Account from '../../../viem/Account.js'
9
- import type * as Client from '../../../viem/Client.js'
10
+ import * as Account from '../../../viem/Account.js'
11
+ import * as Client from '../../../viem/Client.js'
10
12
  import { charge as chargePlugin } from '../../client/Charge.js'
13
+ import * as defaults from '../../internal/defaults.js'
11
14
  import type { ChannelEntry } from '../client/ChannelOps.js'
12
15
  import { createChannelStore, entryKey, type ChannelStore } from '../client/ChannelStore.js'
13
- import type { SessionContext } from '../client/CredentialState.js'
16
+ import { hydrateSessionSnapshot, type SessionContext } from '../client/CredentialState.js'
14
17
  import { session as sessionPlugin } from '../client/Session.js'
18
+ import * as Channel from '../precompile/Channel.js'
15
19
  import { deserializeSessionReceipt } from '../precompile/Protocol.js'
16
- import { readSessionChallengeAmount, type SessionReceipt } from '../precompile/Protocol.js'
20
+ import type { SessionReceipt } from '../precompile/Protocol.js'
17
21
  import {
18
22
  deserializeSnapshot as deserializeSessionSnapshot,
19
23
  serializeSnapshot as serializeSessionSnapshot,
20
24
  } from '../Snapshot.js'
25
+ import { registerSessionManagerInternals } from './internal/SessionManager.js'
21
26
  import { createSessionReceiptCoordinator } from './ReceiptCoordinator.js'
22
27
  import { resolveCloseTarget, type CloseTarget } from './Runtime.js'
23
28
  import { assertVoucherWithinLocalLimit as assertVoucherWithinLocalAuthorization } from './Runtime.js'
@@ -30,13 +35,14 @@ import {
30
35
  dispatchSessionEvent,
31
36
  restoreCumulativeAuthorization,
32
37
  restoreRuntimeSnapshot as restoreRuntimeStateSnapshot,
38
+ resolveAutomaticTopUp,
33
39
  type RuntimeSnapshot,
34
40
  } from './Runtime.js'
35
41
  import { closeSocketSession } from './Runtime.js'
36
42
  import {
37
43
  closeHttpSession,
44
+ getSessionSnapshot,
38
45
  isTempoSessionChallenge,
39
- managementInput,
40
46
  postTopUp,
41
47
  retryHttpPaymentRequired,
42
48
  type TempoSessionChallenge,
@@ -47,7 +53,7 @@ import {
47
53
  type WebSocketConstructor,
48
54
  WebSocketReadyState,
49
55
  } from './Transports.js'
50
- import { openSseSession, type SseDriverOptions } from './Transports.js'
56
+ import { consumeSseSessionResponse, openSseSession, type SseDriverOptions } from './Transports.js'
51
57
  import { applyTopUpResult, resolveManualTopUp, type TopUpRequirement } from './Transports.js'
52
58
  import {
53
59
  openWebSocketSession,
@@ -106,6 +112,8 @@ type SessionManagerConfig = {
106
112
  fetch: typeof globalThis.fetch
107
113
  /** Local maximum cumulative voucher authorization, or null when uncapped. */
108
114
  maxVoucherCumulative: bigint | null
115
+ /** Preferred top-up, or null to use a bounded server suggestion and then the exact shortfall. */
116
+ topUpAmount: bigint | null
109
117
  /** WebSocket constructor available in the current runtime, when configured. */
110
118
  WebSocket: WebSocketConstructor | undefined
111
119
  }
@@ -126,19 +134,6 @@ function isZeroAmountChargeChallenge(challenge: Challenge.Challenge) {
126
134
  }
127
135
  }
128
136
 
129
- /** Builds a reusable channel entry from a server session snapshot header. */
130
- function entryFromSnapshot(snapshot: ReturnType<typeof deserializeSessionSnapshot>): ChannelEntry {
131
- return {
132
- channelId: snapshot.channelId,
133
- cumulativeAmount: BigInt(snapshot.acceptedCumulative),
134
- deposit: BigInt(snapshot.deposit),
135
- descriptor: snapshot.descriptor,
136
- escrow: snapshot.escrow,
137
- chainId: snapshot.chainId,
138
- opened: true,
139
- }
140
- }
141
-
142
137
  function requestInitWithSessionHint(
143
138
  input: RequestInfo | URL,
144
139
  init: RequestInit | undefined,
@@ -176,6 +171,8 @@ function resolveSessionManagerConfig(parameters: sessionManager.Parameters): Ses
176
171
  fetch: parameters.fetch ?? globalThis.fetch.bind(globalThis),
177
172
  maxVoucherCumulative:
178
173
  parameters.maxDeposit !== undefined ? parseUnits(parameters.maxDeposit, decimals) : null,
174
+ topUpAmount:
175
+ parameters.topUpAmount !== undefined ? parseUnits(parameters.topUpAmount, decimals) : null,
179
176
  WebSocket,
180
177
  }
181
178
  }
@@ -192,6 +189,12 @@ function resolveSessionManagerConfig(parameters: sessionManager.Parameters): Ses
192
189
  */
193
190
  export function sessionManager(parameters: sessionManager.Parameters): SessionManager {
194
191
  const config = resolveSessionManagerConfig(parameters)
192
+ const getClient = Client.getResolver({
193
+ chain: tempo_chain,
194
+ getClient: parameters.client ? () => parameters.client! : parameters.getClient,
195
+ rpcUrl: defaults.rpcUrl,
196
+ })
197
+ const getAccount = Account.getResolver({ account: parameters.account })
195
198
  const runtime = createSessionManagerRuntime()
196
199
  const receipts = createSessionReceiptCoordinator({
197
200
  getSocketSession: () => runtime.socketSession,
@@ -203,6 +206,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
203
206
  // Tracks one fetch's channel reuse so stale stored entries can be evicted once.
204
207
  type ChannelUse = {
205
208
  challengesReceived: number
209
+ committed: RuntimeSnapshot | undefined
206
210
  created: Map<string, ChannelEntry>
207
211
  seenExisting: Set<string>
208
212
  previous: RuntimeSnapshot
@@ -223,7 +227,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
223
227
  const entry = await getReusable(key)
224
228
  if (entry && channelUse) {
225
229
  channelUse.seenExisting.add(key)
226
- if (!channelUse.created.has(key)) channelUse.resumed ??= entry
230
+ if (!channelUse.committed && !channelUse.created.has(key)) channelUse.resumed ??= entry
227
231
  }
228
232
  return entry
229
233
  },
@@ -272,13 +276,32 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
272
276
  return dispatchSessionEvent(runtime, event)
273
277
  }
274
278
 
279
+ function commitDurableTopUp(entry: ChannelEntry) {
280
+ const use = channelUse
281
+ const baseline = use?.committed?.channel?.entry ?? use?.resumed
282
+ if (
283
+ !use ||
284
+ baseline?.channelId.toLowerCase() !== entry.channelId.toLowerCase() ||
285
+ entry.deposit <= baseline.deposit
286
+ )
287
+ return
288
+ use.resumed = undefined
289
+ use.committed = captureRuntimeStateSnapshot({
290
+ channel: runtime.channel,
291
+ spent: runtime.spent,
292
+ state: runtime.state,
293
+ })
294
+ }
295
+
275
296
  const method = sessionPlugin({
276
297
  account: parameters.account,
298
+ autoSwap: parameters.autoSwap,
277
299
  getClient: parameters.client ? () => parameters.client! : parameters.getClient,
278
300
  resolveAccount: parameters.resolveAccount,
279
301
  escrow: parameters.escrow,
280
302
  decimals: config.decimals,
281
303
  maxDeposit: parameters.maxDeposit,
304
+ topUpAmount: parameters.topUpAmount,
282
305
  channelStore: store,
283
306
  onChannelUpdate(entry) {
284
307
  if (entry.channelId !== runtime.channel?.channelId) runtime.spent = 0n
@@ -292,6 +315,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
292
315
  units: 0,
293
316
  })
294
317
  }
318
+ commitDurableTopUp(entry)
295
319
  },
296
320
  })
297
321
  MethodResponse.unregister(method)
@@ -315,17 +339,6 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
315
339
  if (use) use.challengesReceived++
316
340
  runtime.lastChallenge = challenge
317
341
  dispatch({ type: 'challengeReceived', challengeId: challenge.id })
318
- if (runtime.channel?.opened && runtime.lastUrl) {
319
- const requiredCumulative =
320
- runtime.channel.cumulativeAmount + readSessionChallengeAmount(challenge)
321
- await topUpIfNeeded({
322
- challenge,
323
- input: runtime.lastUrl,
324
- channelId: runtime.channel.channelId,
325
- deposit: runtime.channel.deposit,
326
- requiredCumulative,
327
- })
328
- }
329
342
  return undefined
330
343
  },
331
344
  })
@@ -365,8 +378,23 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
365
378
  async function storeSnapshotHeader(response: Response): Promise<ChannelEntry | undefined> {
366
379
  const header = response.headers.get(Constants.Headers.paymentSessionSnapshot)
367
380
  if (!header) return undefined
368
- const entry = entryFromSnapshot(deserializeSessionSnapshot(header))
381
+ const snapshot = deserializeSessionSnapshot(header)
382
+ const client = await getClient({ chainId: snapshot.chainId })
383
+ const defaultAccount = getAccount(client)
384
+ const account =
385
+ (await parameters.resolveAccount?.({
386
+ account: defaultAccount,
387
+ chainId: snapshot.chainId,
388
+ operation: {
389
+ authority: Channel.resolveAuthorizedSigner(snapshot.descriptor),
390
+ kind: 'authorizePaymentChannel',
391
+ },
392
+ })) ?? defaultAccount
393
+ const { entry, spent } = await hydrateSessionSnapshot({ account, client, snapshot })
394
+ assertVoucherWithinLocalLimit(entry.cumulativeAmount)
369
395
  await Promise.resolve(store.set(entry)).catch(() => undefined)
396
+ runtime.channel = entry
397
+ runtime.spent = spent
370
398
  return entry
371
399
  }
372
400
 
@@ -430,8 +458,60 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
430
458
  })
431
459
  }
432
460
 
433
- function getValidatedFallbackCloseAmount(target: CloseTarget) {
434
- const closeAmount = getFallbackCloseAmount(target.challenge, target.channelId)
461
+ function validateCloseSnapshot(channel: ChannelEntry, challenge: TempoSessionChallenge) {
462
+ const snapshot = getSessionSnapshot(challenge)
463
+ if (!snapshot) return undefined
464
+
465
+ const computedSnapshotId = Channel.computeId({
466
+ ...snapshot.descriptor,
467
+ chainId: snapshot.chainId,
468
+ escrow: snapshot.escrow,
469
+ })
470
+ if (computedSnapshotId.toLowerCase() !== snapshot.channelId.toLowerCase()) {
471
+ throw new Error('close snapshot descriptor does not match its channel ID')
472
+ }
473
+ if (snapshot.channelId.toLowerCase() !== channel.channelId.toLowerCase()) {
474
+ throw new Error('close snapshot channel ID does not match local session')
475
+ }
476
+ if (
477
+ snapshot.chainId !== channel.chainId ||
478
+ snapshot.escrow.toLowerCase() !== channel.escrow.toLowerCase()
479
+ ) {
480
+ throw new Error('close snapshot payment scope does not match local session')
481
+ }
482
+
483
+ const acceptedCumulative = BigInt(snapshot.acceptedCumulative)
484
+ const snapshotSpent = BigInt(snapshot.spent)
485
+ if (acceptedCumulative < 0n || snapshotSpent < 0n) {
486
+ throw new Error('close snapshot amounts must not be negative')
487
+ }
488
+ if (snapshotSpent > acceptedCumulative) {
489
+ throw new Error('close snapshot spent exceeds accepted cumulative amount')
490
+ }
491
+ if (acceptedCumulative > channel.cumulativeAmount) {
492
+ throw new Error('close snapshot accepted cumulative exceeds local voucher state')
493
+ }
494
+ return { acceptedCumulative, spent: snapshotSpent }
495
+ }
496
+
497
+ function applyCloseSnapshot(target: CloseTarget, challenge: TempoSessionChallenge) {
498
+ const snapshot = validateCloseSnapshot(target.channel, challenge)
499
+ if (!snapshot) return
500
+
501
+ const { acceptedCumulative, spent } = snapshot
502
+ if (runtime.spent > acceptedCumulative) {
503
+ throw new Error('close snapshot accepted cumulative is below locally confirmed spend')
504
+ }
505
+ if (spent > runtime.spent) runtime.spent = spent
506
+ }
507
+
508
+ function getValidatedFallbackCloseAmount(
509
+ target: CloseTarget,
510
+ challenge: TempoSessionChallenge = target.challenge,
511
+ applySnapshot = false,
512
+ ) {
513
+ if (applySnapshot) applyCloseSnapshot(target, challenge)
514
+ const closeAmount = getFallbackCloseAmount(challenge, target.channelId)
435
515
  if (closeAmount > target.channel.cumulativeAmount) {
436
516
  throw new Error('fallback close amount exceeds local voucher state')
437
517
  }
@@ -451,9 +531,11 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
451
531
  challenge: TempoSessionChallenge
452
532
  channelId: Hex.Hex
453
533
  input: RequestInfo | URL
534
+ knownDeposit?: bigint | undefined
454
535
  }) {
536
+ const { knownDeposit, ...topUp } = parameters
455
537
  const receipt = await postTopUp({
456
- ...parameters,
538
+ ...topUp,
457
539
  channel: runtime.channel,
458
540
  createSessionCredential,
459
541
  fetch: config.fetch,
@@ -465,10 +547,12 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
465
547
  channelId: parameters.channelId,
466
548
  challengeId: runtime.lastChallenge?.id,
467
549
  currentState: runtime.state,
550
+ knownDeposit,
468
551
  receipt,
469
552
  spent: runtime.spent,
470
553
  })
471
554
  if (applied?.channel && runtime.lastChallenge) {
555
+ await store.set(applied.channel)
472
556
  dispatch({
473
557
  type: 'activated',
474
558
  challengeId: runtime.lastChallenge.id,
@@ -476,18 +560,32 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
476
560
  spent: runtime.spent.toString(),
477
561
  units: runtime.state.status === 'active' ? runtime.state.units : 0,
478
562
  })
563
+ commitDurableTopUp(applied.channel)
479
564
  }
480
565
  return receipt
481
566
  }
482
567
 
483
568
  async function topUpIfNeeded(parameters: TopUpRequirement) {
484
- if (parameters.requiredCumulative <= parameters.deposit) return
485
- assertVoucherWithinLocalLimit(parameters.requiredCumulative)
569
+ const channelDeposit =
570
+ runtime.channel?.channelId === parameters.channelId ? runtime.channel.deposit : 0n
571
+ const deposit = channelDeposit > parameters.deposit ? channelDeposit : parameters.deposit
572
+ const additionalDeposit = resolveAutomaticTopUp({
573
+ deposit,
574
+ maxDeposit: config.maxVoucherCumulative,
575
+ requiredCumulative: parameters.requiredCumulative,
576
+ suggestedDeposit:
577
+ parameters.challenge.request.suggestedDeposit === undefined
578
+ ? undefined
579
+ : BigInt(parameters.challenge.request.suggestedDeposit),
580
+ topUpAmount: config.topUpAmount,
581
+ })
582
+ if (additionalDeposit === 0n) return
486
583
  await postTopUpAndApply({
487
584
  challenge: parameters.challenge,
488
585
  input: parameters.input,
489
586
  channelId: parameters.channelId,
490
- additionalDeposit: parameters.requiredCumulative - parameters.deposit,
587
+ additionalDeposit,
588
+ knownDeposit: deposit,
491
589
  })
492
590
  }
493
591
 
@@ -547,6 +645,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
547
645
  })
548
646
  const use: ChannelUse = {
549
647
  challengesReceived: 0,
648
+ committed: undefined,
550
649
  created: new Map(),
551
650
  previous,
552
651
  seenExisting: new Set(),
@@ -580,7 +679,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
580
679
  try {
581
680
  response = await wrappedFetch(input, effectiveInit)
582
681
  } catch (error) {
583
- restoreRuntime(previous)
682
+ restoreRuntime(use.committed ?? previous)
584
683
  if (await retryWithoutResumed()) continue
585
684
  throw error
586
685
  }
@@ -607,7 +706,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
607
706
  }
608
707
  }
609
708
  if (!attemptedHttpManagement && !paymentResponse.ok && !paymentResponse.receipt) {
610
- restoreRuntime(previous)
709
+ restoreRuntime(use.committed ?? previous)
611
710
  if (await retryWithoutResumed()) continue
612
711
  return paymentResponse
613
712
  }
@@ -625,6 +724,8 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
625
724
  createSessionCredential,
626
725
  fetch: config.fetch,
627
726
  lastUrl: runtime.lastUrl,
727
+ resolveSignedCloseAmount: (challenge) =>
728
+ getValidatedFallbackCloseAmount(target, challenge, true),
628
729
  signedCloseAmount: getValidatedFallbackCloseAmount(target),
629
730
  setChallenge(challenge) {
630
731
  runtime.lastChallenge = challenge
@@ -640,6 +741,19 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
640
741
  return receipt
641
742
  }
642
743
 
744
+ const sseDriver = {
745
+ createSessionCredential,
746
+ doFetch,
747
+ fetch: config.fetch,
748
+ getChannel: () => runtime.channel,
749
+ getChallenge: () => runtime.lastChallenge,
750
+ assertVoucherWithinLocalLimit,
751
+ acceptReceipt(receipt: SessionReceipt) {
752
+ updateSpentFromReceipt(receipt)
753
+ },
754
+ topUpIfNeeded,
755
+ }
756
+
643
757
  const self: SessionManager = {
644
758
  get channelId() {
645
759
  return runtime.channel?.channelId
@@ -675,19 +789,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
675
789
  },
676
790
 
677
791
  async sse(input, init) {
678
- return openSseSession(input, init, {
679
- createSessionCredential,
680
- doFetch,
681
- fetch: config.fetch,
682
- getChannel: () => runtime.channel,
683
- getChallenge: () => runtime.lastChallenge,
684
- assertVoucherWithinLocalLimit,
685
- managementInput,
686
- acceptReceipt(receipt) {
687
- updateSpentFromReceipt(receipt)
688
- },
689
- topUpIfNeeded,
690
- })
792
+ return openSseSession(input, init, sseDriver)
691
793
  },
692
794
 
693
795
  async ws(input, init) {
@@ -703,7 +805,16 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
703
805
  const liveHint = runtime.channel?.opened ? runtime.channel.channelId : undefined
704
806
 
705
807
  const prepared = await prepareWebSocketSession({
706
- createSessionCredential,
808
+ async createSessionCredential(challenge, context) {
809
+ runtime.lastChallenge = challenge
810
+ await MethodChallenge.handle(method, {
811
+ challenge,
812
+ context,
813
+ fetch: config.fetch,
814
+ input: probeUrl,
815
+ })
816
+ return createSessionCredential(challenge, context)
817
+ },
707
818
  fetch: config.fetch,
708
819
  input,
709
820
  onProbeUrl(httpUrl) {
@@ -768,6 +879,38 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
768
879
  },
769
880
  }
770
881
 
882
+ registerSessionManagerInternals(self, {
883
+ consumeSseResponse(input, response, options) {
884
+ return consumeSseSessionResponse(input, response, options, sseDriver)
885
+ },
886
+ rehydrate({ channel, challenge, input, spent }) {
887
+ if (!channel.opened) throw new Error('Cannot restore a closed session channel.')
888
+ if (!isTempoSessionChallenge(challenge)) {
889
+ throw new Error('Cannot restore session: challenge is not tempo/session.')
890
+ }
891
+ if (spent < 0n) throw new Error('Cannot restore session: spent must not be negative.')
892
+ if (spent > channel.cumulativeAmount) {
893
+ throw new Error('Cannot restore session: spent exceeds local voucher state.')
894
+ }
895
+ assertVoucherWithinLocalLimit(channel.cumulativeAmount)
896
+ const snapshot = validateCloseSnapshot(channel, challenge)
897
+ const restoredSpent = snapshot && snapshot.spent > spent ? snapshot.spent : spent
898
+
899
+ runtime.lastUrl = input
900
+ runtime.lastChallenge = challenge
901
+ runtime.channel = channel
902
+ runtime.spent = restoredSpent
903
+ dispatch({ type: 'challengeReceived', challengeId: challenge.id })
904
+ dispatch({
905
+ type: 'activated',
906
+ challengeId: challenge.id,
907
+ entry: channel,
908
+ spent: restoredSpent.toString(),
909
+ units: 0,
910
+ })
911
+ },
912
+ })
913
+
771
914
  return self
772
915
  }
773
916
 
@@ -778,6 +921,8 @@ export namespace sessionManager {
778
921
 
779
922
  export type Parameters = Account.getResolver.Parameters &
780
923
  Client.getResolver.Parameters & {
924
+ /** Automatically acquire the session currency from fallback stablecoins before open/top-up. */
925
+ autoSwap?: sessionPlugin.Parameters['autoSwap']
781
926
  /** Enables same-route HEAD bootstrap from a server session snapshot before opening a new channel. */
782
927
  bootstrap?: boolean | undefined
783
928
  /** Viem client instance. Shorthand for `getClient: () => client`. */
@@ -790,6 +935,11 @@ export namespace sessionManager {
790
935
  fetch?: typeof globalThis.fetch | undefined
791
936
  /** Maximum deposit in human-readable units (e.g. `'10'` for 10 tokens). Converted to raw units via `decimals`. */
792
937
  maxDeposit?: string | undefined
938
+ /**
939
+ * Preferred automatic top-up size in human-readable units. When omitted,
940
+ * a bounded server `suggestedDeposit` is preferred, then the exact shortfall.
941
+ */
942
+ topUpAmount?: string | undefined
793
943
  /** Selects the account that signs session credentials. */
794
944
  resolveAccount?: sessionPlugin.ResolveAccount | undefined
795
945
  /** Store for reusable session channels. Defaults to in-memory. */