mppx 0.8.12 → 0.8.13

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 (193) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +16 -1
  3. package/dist/cli/account.d.ts +88 -0
  4. package/dist/cli/account.d.ts.map +1 -1
  5. package/dist/cli/account.js +39 -9
  6. package/dist/cli/account.js.map +1 -1
  7. package/dist/cli/cli.d.ts +2 -1
  8. package/dist/cli/cli.d.ts.map +1 -1
  9. package/dist/cli/cli.js +59 -2
  10. package/dist/cli/cli.js.map +1 -1
  11. package/dist/cli/sessions/Manager.d.ts +31 -0
  12. package/dist/cli/sessions/Manager.d.ts.map +1 -0
  13. package/dist/cli/sessions/Manager.js +56 -0
  14. package/dist/cli/sessions/Manager.js.map +1 -0
  15. package/dist/cli/sessions/commands.d.ts +36 -0
  16. package/dist/cli/sessions/commands.d.ts.map +1 -0
  17. package/dist/cli/sessions/commands.js +407 -0
  18. package/dist/cli/sessions/commands.js.map +1 -0
  19. package/dist/cli/sessions/request.d.ts +35 -0
  20. package/dist/cli/sessions/request.d.ts.map +1 -0
  21. package/dist/cli/sessions/request.js +271 -0
  22. package/dist/cli/sessions/request.js.map +1 -0
  23. package/dist/cli/sessions/store.d.ts +134 -0
  24. package/dist/cli/sessions/store.d.ts.map +1 -0
  25. package/dist/cli/sessions/store.js +723 -0
  26. package/dist/cli/sessions/store.js.map +1 -0
  27. package/dist/cli/utils.d.ts +2 -0
  28. package/dist/cli/utils.d.ts.map +1 -1
  29. package/dist/cli/utils.js +12 -0
  30. package/dist/cli/utils.js.map +1 -1
  31. package/dist/cli/validate/index.d.ts.map +1 -1
  32. package/dist/cli/validate/index.js +12 -6
  33. package/dist/cli/validate/index.js.map +1 -1
  34. package/dist/cli/validate/messages.d.ts +2 -0
  35. package/dist/cli/validate/messages.d.ts.map +1 -0
  36. package/dist/cli/validate/messages.js +8 -0
  37. package/dist/cli/validate/messages.js.map +1 -0
  38. package/dist/client/node.d.ts +29 -0
  39. package/dist/client/node.d.ts.map +1 -0
  40. package/dist/client/node.js +177 -0
  41. package/dist/client/node.js.map +1 -0
  42. package/dist/internal/types.d.ts +24 -0
  43. package/dist/internal/types.d.ts.map +1 -1
  44. package/dist/stripe/server/Charge.d.ts +8 -4
  45. package/dist/stripe/server/Charge.d.ts.map +1 -1
  46. package/dist/stripe/server/Charge.js.map +1 -1
  47. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  48. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  49. package/dist/stripe/server/internal/html.gen.js +1 -1
  50. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  51. package/dist/tempo/client/Methods.d.ts +1 -1
  52. package/dist/tempo/internal/types.d.ts +10 -1
  53. package/dist/tempo/internal/types.d.ts.map +1 -1
  54. package/dist/tempo/legacy/client/Session.d.ts +2 -2
  55. package/dist/tempo/legacy/server/Session.d.ts +3 -3
  56. package/dist/tempo/legacy/server/Session.d.ts.map +1 -1
  57. package/dist/tempo/legacy/server/Session.js.map +1 -1
  58. package/dist/tempo/server/Charge.d.ts +3 -2
  59. package/dist/tempo/server/Charge.d.ts.map +1 -1
  60. package/dist/tempo/server/Charge.js.map +1 -1
  61. package/dist/tempo/server/Methods.d.ts +2 -23
  62. package/dist/tempo/server/Methods.d.ts.map +1 -1
  63. package/dist/tempo/server/Methods.js +2 -2
  64. package/dist/tempo/server/Methods.js.map +1 -1
  65. package/dist/tempo/server/Subscription.d.ts +3 -2
  66. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  67. package/dist/tempo/server/Subscription.js.map +1 -1
  68. package/dist/tempo/server/index.d.ts +1 -1
  69. package/dist/tempo/server/index.d.ts.map +1 -1
  70. package/dist/tempo/server/index.js.map +1 -1
  71. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  72. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  73. package/dist/tempo/server/internal/html.gen.js +1 -1
  74. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  75. package/dist/tempo/session/Snapshot.d.ts +9 -0
  76. package/dist/tempo/session/Snapshot.d.ts.map +1 -1
  77. package/dist/tempo/session/Snapshot.js +6 -0
  78. package/dist/tempo/session/Snapshot.js.map +1 -1
  79. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  80. package/dist/tempo/session/client/ChannelOps.js +11 -2
  81. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  82. package/dist/tempo/session/client/CredentialState.d.ts +21 -1
  83. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  84. package/dist/tempo/session/client/CredentialState.js +66 -1
  85. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  86. package/dist/tempo/session/client/Session.d.ts +1 -1
  87. package/dist/tempo/session/client/SessionManager.d.ts +2 -2
  88. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  89. package/dist/tempo/session/client/SessionManager.js +128 -30
  90. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  91. package/dist/tempo/session/client/Transports.d.ts +11 -2
  92. package/dist/tempo/session/client/Transports.d.ts.map +1 -1
  93. package/dist/tempo/session/client/Transports.js +33 -9
  94. package/dist/tempo/session/client/Transports.js.map +1 -1
  95. package/dist/tempo/session/client/internal/SessionManager.d.ts +19 -0
  96. package/dist/tempo/session/client/internal/SessionManager.d.ts.map +1 -0
  97. package/dist/tempo/session/client/internal/SessionManager.js +13 -0
  98. package/dist/tempo/session/client/internal/SessionManager.js.map +1 -0
  99. package/dist/tempo/session/precompile/Chain.js +13 -4
  100. package/dist/tempo/session/precompile/Chain.js.map +1 -1
  101. package/dist/tempo/session/server/CredentialVerification.d.ts +3 -0
  102. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  103. package/dist/tempo/session/server/CredentialVerification.js +14 -0
  104. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  105. package/dist/tempo/session/server/MeteredStream.d.ts +7 -1
  106. package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
  107. package/dist/tempo/session/server/MeteredStream.js +5 -4
  108. package/dist/tempo/session/server/MeteredStream.js.map +1 -1
  109. package/dist/tempo/session/server/RequestState.d.ts +5 -1
  110. package/dist/tempo/session/server/RequestState.d.ts.map +1 -1
  111. package/dist/tempo/session/server/RequestState.js +10 -4
  112. package/dist/tempo/session/server/RequestState.js.map +1 -1
  113. package/dist/tempo/session/server/Session.d.ts +21 -6
  114. package/dist/tempo/session/server/Session.d.ts.map +1 -1
  115. package/dist/tempo/session/server/Session.js +4 -1
  116. package/dist/tempo/session/server/Session.js.map +1 -1
  117. package/dist/tempo/session/server/Settlement.d.ts +20 -0
  118. package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
  119. package/dist/tempo/session/server/Settlement.js +20 -0
  120. package/dist/tempo/session/server/Settlement.js.map +1 -1
  121. package/dist/tempo/session/server/Ws.d.ts +2 -0
  122. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  123. package/dist/tempo/session/server/Ws.js.map +1 -1
  124. package/dist/tempo/session/server/index.d.ts +1 -1
  125. package/dist/tempo/session/server/index.d.ts.map +1 -1
  126. package/dist/tempo/subscription/KeyAuthorization.d.ts +21 -21
  127. package/dist/validation/core.d.ts.map +1 -1
  128. package/dist/validation/core.js +6 -2
  129. package/dist/validation/core.js.map +1 -1
  130. package/dist/viem/Client.d.ts.map +1 -1
  131. package/dist/viem/Client.js +24 -17
  132. package/dist/viem/Client.js.map +1 -1
  133. package/package.json +6 -1
  134. package/src/cli/account.ts +45 -10
  135. package/src/cli/cli.test.ts +165 -70
  136. package/src/cli/cli.ts +67 -2
  137. package/src/cli/mcp.test.ts +11 -0
  138. package/src/cli/sessions/Manager.test.ts +249 -0
  139. package/src/cli/sessions/Manager.ts +93 -0
  140. package/src/cli/sessions/commands.ts +444 -0
  141. package/src/cli/sessions/request.test.ts +51 -0
  142. package/src/cli/sessions/request.ts +353 -0
  143. package/src/cli/sessions/store.test.ts +581 -0
  144. package/src/cli/sessions/store.ts +940 -0
  145. package/src/cli/utils.test.ts +23 -0
  146. package/src/cli/utils.ts +10 -0
  147. package/src/cli/validate/index.ts +12 -14
  148. package/src/cli/validate/messages.ts +7 -0
  149. package/src/cli/validate.test.ts +38 -5
  150. package/src/client/node.test.ts +115 -0
  151. package/src/client/node.ts +247 -0
  152. package/src/internal/types.test-d.ts +21 -0
  153. package/src/internal/types.ts +33 -0
  154. package/src/server/Mppx.test-d.ts +42 -0
  155. package/src/stripe/Methods.test.ts +10 -0
  156. package/src/stripe/server/Charge.test-d.ts +66 -0
  157. package/src/stripe/server/Charge.ts +8 -5
  158. package/src/stripe/server/internal/html.gen.ts +1 -1
  159. package/src/tempo/internal/types.ts +13 -2
  160. package/src/tempo/legacy/server/Defaults.test-d.ts +9 -0
  161. package/src/tempo/legacy/server/Session.ts +6 -5
  162. package/src/tempo/server/Charge.ts +6 -4
  163. package/src/tempo/server/Methods.ts +5 -4
  164. package/src/tempo/server/Subscription.ts +6 -4
  165. package/src/tempo/server/index.ts +5 -1
  166. package/src/tempo/server/internal/html.gen.ts +1 -1
  167. package/src/tempo/session/README.md +201 -0
  168. package/src/tempo/session/Snapshot.ts +21 -0
  169. package/src/tempo/session/client/ChannelOps.test.ts +40 -0
  170. package/src/tempo/session/client/ChannelOps.ts +12 -2
  171. package/src/tempo/session/client/CredentialState.test.ts +2 -2
  172. package/src/tempo/session/client/CredentialState.ts +103 -1
  173. package/src/tempo/session/client/Session.test.ts +3 -3
  174. package/src/tempo/session/client/SessionManager.test.ts +137 -4
  175. package/src/tempo/session/client/SessionManager.ts +139 -33
  176. package/src/tempo/session/client/Transports.test.ts +150 -8
  177. package/src/tempo/session/client/Transports.ts +48 -10
  178. package/src/tempo/session/client/internal/SessionManager.ts +36 -0
  179. package/src/tempo/session/precompile/Chain.test.ts +26 -0
  180. package/src/tempo/session/precompile/Chain.ts +17 -4
  181. package/src/tempo/session/server/CredentialVerification.ts +18 -1
  182. package/src/tempo/session/server/MeteredStream.ts +12 -5
  183. package/src/tempo/session/server/RequestState.test.ts +39 -3
  184. package/src/tempo/session/server/RequestState.ts +13 -6
  185. package/src/tempo/session/server/Session.test.ts +461 -0
  186. package/src/tempo/session/server/Session.ts +31 -8
  187. package/src/tempo/session/server/Settlement.ts +45 -0
  188. package/src/tempo/session/server/Ws.test.ts +113 -0
  189. package/src/tempo/session/server/Ws.ts +2 -0
  190. package/src/tempo/session/server/index.ts +2 -0
  191. package/src/validation/core.ts +7 -1
  192. package/src/viem/Client.test.ts +44 -1
  193. package/src/viem/Client.ts +31 -17
@@ -14,6 +14,7 @@ import { createSessionReceipt, serializeSessionReceipt } from '../precompile/Pro
14
14
  import type { NeedVoucherEvent, SessionReceipt } from '../precompile/Protocol.js'
15
15
  import { formatNeedVoucherEvent, parseEvent } from '../precompile/Protocol.js'
16
16
  import type { SessionCredentialPayload } from '../precompile/Protocol.js'
17
+ import * as Voucher from '../precompile/Voucher.js'
17
18
  import { computeFallbackCloseAmount, sessionManager } from './SessionManager.js'
18
19
 
19
20
  const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex
@@ -44,6 +45,24 @@ const client = createClient({
44
45
  }),
45
46
  })
46
47
 
48
+ function channelStateClient(state: { closeRequestedAt: number; deposit: bigint; settled: bigint }) {
49
+ return createClient({
50
+ account,
51
+ chain: { id: 4217 } as never,
52
+ transport: custom({
53
+ async request(args) {
54
+ if (args.method === 'eth_call')
55
+ return encodeFunctionResult({
56
+ abi: escrowAbi,
57
+ functionName: 'getChannelState',
58
+ result: state,
59
+ })
60
+ throw new Error(`unexpected rpc request: ${args.method}`)
61
+ },
62
+ }),
63
+ })
64
+ }
65
+
47
66
  const storedDescriptor = {
48
67
  authorizedSigner: account.address,
49
68
  expiringNonceHash: `0x${'11'.repeat(32)}` as Hex,
@@ -332,6 +351,17 @@ describe('Session', () => {
332
351
  test('seeds a same-route HEAD snapshot and resolves the account when resuming it', async () => {
333
352
  const { store, set } = makeChannelStore()
334
353
  const posted: SessionCredentialPayload[] = []
354
+ const highestVoucher = {
355
+ channelId: storedChannelId,
356
+ cumulativeAmount: '1000000',
357
+ signature: await Voucher.signVoucher(
358
+ client,
359
+ account,
360
+ { channelId: storedChannelId, cumulativeAmount: 1_000_000n },
361
+ tip20ChannelEscrow,
362
+ 4217,
363
+ ),
364
+ }
335
365
  const resolveAccount = vi.fn(
336
366
  (info: Parameters<NonNullable<sessionManager.Parameters['resolveAccount']>>[0]) =>
337
367
  info.account,
@@ -356,6 +386,7 @@ describe('Session', () => {
356
386
  deposit: '10000000',
357
387
  descriptor: storedDescriptor,
358
388
  escrow: tip20ChannelEscrow,
389
+ highestVoucher,
359
390
  requiredCumulative: '1000000',
360
391
  settled: '0',
361
392
  spent: '0',
@@ -372,7 +403,11 @@ describe('Session', () => {
372
403
  ? Credential.deserialize<SessionCredentialPayload>(authorization).payload
373
404
  : undefined
374
405
  if (payload) posted.push(payload)
375
- if (!payload) return Promise.resolve(make402Response())
406
+ if (!payload) {
407
+ expect(s.channelId).toBe(storedChannelId)
408
+ expect(s.cumulative).toBe(1_000_000n)
409
+ return Promise.resolve(make402Response())
410
+ }
376
411
  return Promise.resolve(makeOkResponse())
377
412
  })
378
413
  const s = sessionManager({
@@ -389,8 +424,8 @@ describe('Session', () => {
389
424
  expect(response.status).toBe(200)
390
425
  expect(set).toHaveBeenCalledWith(expect.objectContaining({ channelId: storedChannelId }))
391
426
  expect(posted[0]).toMatchObject({ action: 'voucher', channelId: storedChannelId })
392
- expect(resolveAccount).toHaveBeenCalledOnce()
393
- expect(resolveAccount).toHaveBeenCalledWith({
427
+ expect(resolveAccount).toHaveBeenCalledTimes(2)
428
+ expect(resolveAccount).toHaveBeenNthCalledWith(1, {
394
429
  account,
395
430
  chainId: 4217,
396
431
  operation: { authority: account.address, kind: 'authorizePaymentChannel' },
@@ -399,6 +434,103 @@ describe('Session', () => {
399
434
  expect(new Headers(contentCall?.[1]?.headers).get('Payment-Session')).toBeNull()
400
435
  })
401
436
 
437
+ test('does not cache a server snapshot for a channel that is closed on-chain', async () => {
438
+ const { store, set } = makeChannelStore()
439
+ const highestVoucher = {
440
+ channelId: storedChannelId,
441
+ cumulativeAmount: '1000000',
442
+ signature: await Voucher.signVoucher(
443
+ client,
444
+ account,
445
+ { channelId: storedChannelId, cumulativeAmount: 1_000_000n },
446
+ tip20ChannelEscrow,
447
+ 4217,
448
+ ),
449
+ }
450
+ const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
451
+ const headers = new Headers(init?.headers)
452
+ if (init?.method === 'HEAD' && !headers.get(Constants.Headers.authorization))
453
+ return Promise.resolve(make402Response(makeChargeChallenge()))
454
+ if (init?.method === 'HEAD')
455
+ return Promise.resolve(
456
+ new Response(null, {
457
+ status: 204,
458
+ headers: {
459
+ [Constants.Headers.paymentSessionSnapshot]: sessionManager.serializeSnapshot({
460
+ acceptedCumulative: '1000000',
461
+ chainId: 4217,
462
+ channelId: storedChannelId,
463
+ deposit: '10000000',
464
+ descriptor: storedDescriptor,
465
+ escrow: tip20ChannelEscrow,
466
+ highestVoucher,
467
+ requiredCumulative: '1000000',
468
+ settled: '0',
469
+ spent: '0',
470
+ units: 0,
471
+ }),
472
+ },
473
+ }),
474
+ )
475
+ return Promise.resolve(makeOkResponse())
476
+ })
477
+ const s = sessionManager({
478
+ account,
479
+ bootstrap: true,
480
+ client: channelStateClient({ closeRequestedAt: 0, deposit: 0n, settled: 0n }),
481
+ fetch: mockFetch as typeof globalThis.fetch,
482
+ channelStore: store,
483
+ })
484
+
485
+ expect((await s.fetch('https://api.example.com/data')).status).toBe(200)
486
+ expect(set).not.toHaveBeenCalled()
487
+ })
488
+
489
+ test('does not cache a server snapshot with an invalid highest voucher signature', async () => {
490
+ const { store, set } = makeChannelStore()
491
+ const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
492
+ const headers = new Headers(init?.headers)
493
+ if (init?.method === 'HEAD' && !headers.get(Constants.Headers.authorization))
494
+ return Promise.resolve(make402Response(makeChargeChallenge()))
495
+ if (init?.method === 'HEAD')
496
+ return Promise.resolve(
497
+ new Response(null, {
498
+ status: 204,
499
+ headers: {
500
+ [Constants.Headers.paymentSessionSnapshot]: sessionManager.serializeSnapshot({
501
+ acceptedCumulative: '1000000',
502
+ chainId: 4217,
503
+ channelId: storedChannelId,
504
+ deposit: '10000000',
505
+ descriptor: storedDescriptor,
506
+ escrow: tip20ChannelEscrow,
507
+ highestVoucher: {
508
+ channelId: storedChannelId,
509
+ cumulativeAmount: '1000000',
510
+ signature: `0x${'00'.repeat(64)}`,
511
+ },
512
+ requiredCumulative: '1000000',
513
+ settled: '0',
514
+ spent: '0',
515
+ units: 0,
516
+ }),
517
+ },
518
+ }),
519
+ )
520
+ return Promise.resolve(makeOkResponse())
521
+ })
522
+ const s = sessionManager({
523
+ account,
524
+ bootstrap: true,
525
+ client,
526
+ fetch: mockFetch as typeof globalThis.fetch,
527
+ channelStore: store,
528
+ })
529
+
530
+ expect((await s.fetch('https://api.example.com/data')).status).toBe(200)
531
+ expect(set).not.toHaveBeenCalled()
532
+ })
533
+
402
534
  test('does not answer non-zero bootstrap charge challenges', async () => {
403
535
  const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
404
536
  const headers = new Headers(init?.headers)
@@ -1082,7 +1214,7 @@ describe('Session', () => {
1082
1214
  expect(voucherPayload.channelId).toBe(openPayload.channelId)
1083
1215
  expect(voucherPayload.descriptor).toEqual(openPayload.descriptor)
1084
1216
  expect(voucherPayload.cumulativeAmount).toBe('2000000')
1085
- expect(requestedUrls[2]).toBe('https://api.example.com/stream')
1217
+ expect(requestedUrls[2]).toBe('https://api.example.com/stream?prompt=paid')
1086
1218
  })
1087
1219
 
1088
1220
  test('ignores precompile SSE voucher requests for a different channel', async () => {
@@ -1321,6 +1453,7 @@ describe('Session', () => {
1321
1453
  challengeId,
1322
1454
  channelId: payload.channelId,
1323
1455
  spent: BigInt(payload.cumulativeAmount),
1456
+ txHash: `0x${'aa'.repeat(32)}` as Hex,
1324
1457
  units: 1,
1325
1458
  }),
1326
1459
  ),
@@ -1,23 +1,27 @@
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'
6
7
  import * as MethodResponse from '../../../client/internal/MethodResponse.js'
7
8
  import * as Constants from '../../../Constants.js'
8
- import type * as Account from '../../../viem/Account.js'
9
- import type * as Client from '../../../viem/Client.js'
9
+ import * as Account from '../../../viem/Account.js'
10
+ import * as Client from '../../../viem/Client.js'
10
11
  import { charge as chargePlugin } from '../../client/Charge.js'
12
+ import * as defaults from '../../internal/defaults.js'
11
13
  import type { ChannelEntry } from '../client/ChannelOps.js'
12
14
  import { createChannelStore, entryKey, type ChannelStore } from '../client/ChannelStore.js'
13
- import type { SessionContext } from '../client/CredentialState.js'
15
+ import { hydrateSessionSnapshot, type SessionContext } from '../client/CredentialState.js'
14
16
  import { session as sessionPlugin } from '../client/Session.js'
17
+ import * as Channel from '../precompile/Channel.js'
15
18
  import { deserializeSessionReceipt } from '../precompile/Protocol.js'
16
19
  import { readSessionChallengeAmount, type SessionReceipt } from '../precompile/Protocol.js'
17
20
  import {
18
21
  deserializeSnapshot as deserializeSessionSnapshot,
19
22
  serializeSnapshot as serializeSessionSnapshot,
20
23
  } from '../Snapshot.js'
24
+ import { registerSessionManagerInternals } from './internal/SessionManager.js'
21
25
  import { createSessionReceiptCoordinator } from './ReceiptCoordinator.js'
22
26
  import { resolveCloseTarget, type CloseTarget } from './Runtime.js'
23
27
  import { assertVoucherWithinLocalLimit as assertVoucherWithinLocalAuthorization } from './Runtime.js'
@@ -35,6 +39,7 @@ import {
35
39
  import { closeSocketSession } from './Runtime.js'
36
40
  import {
37
41
  closeHttpSession,
42
+ getSessionSnapshot,
38
43
  isTempoSessionChallenge,
39
44
  managementInput,
40
45
  postTopUp,
@@ -47,7 +52,7 @@ import {
47
52
  type WebSocketConstructor,
48
53
  WebSocketReadyState,
49
54
  } from './Transports.js'
50
- import { openSseSession, type SseDriverOptions } from './Transports.js'
55
+ import { consumeSseSessionResponse, openSseSession, type SseDriverOptions } from './Transports.js'
51
56
  import { applyTopUpResult, resolveManualTopUp, type TopUpRequirement } from './Transports.js'
52
57
  import {
53
58
  openWebSocketSession,
@@ -126,19 +131,6 @@ function isZeroAmountChargeChallenge(challenge: Challenge.Challenge) {
126
131
  }
127
132
  }
128
133
 
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
134
  function requestInitWithSessionHint(
143
135
  input: RequestInfo | URL,
144
136
  init: RequestInit | undefined,
@@ -192,6 +184,12 @@ function resolveSessionManagerConfig(parameters: sessionManager.Parameters): Ses
192
184
  */
193
185
  export function sessionManager(parameters: sessionManager.Parameters): SessionManager {
194
186
  const config = resolveSessionManagerConfig(parameters)
187
+ const getClient = Client.getResolver({
188
+ chain: tempo_chain,
189
+ getClient: parameters.client ? () => parameters.client! : parameters.getClient,
190
+ rpcUrl: defaults.rpcUrl,
191
+ })
192
+ const getAccount = Account.getResolver({ account: parameters.account })
195
193
  const runtime = createSessionManagerRuntime()
196
194
  const receipts = createSessionReceiptCoordinator({
197
195
  getSocketSession: () => runtime.socketSession,
@@ -365,8 +363,27 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
365
363
  async function storeSnapshotHeader(response: Response): Promise<ChannelEntry | undefined> {
366
364
  const header = response.headers.get(Constants.Headers.paymentSessionSnapshot)
367
365
  if (!header) return undefined
368
- const entry = entryFromSnapshot(deserializeSessionSnapshot(header))
366
+ const snapshot = deserializeSessionSnapshot(header)
367
+ const client = await getClient({ chainId: snapshot.chainId })
368
+ const defaultAccount = getAccount(client)
369
+ const authority =
370
+ BigInt(snapshot.descriptor.authorizedSigner) === 0n
371
+ ? snapshot.descriptor.payer
372
+ : snapshot.descriptor.authorizedSigner
373
+ const account =
374
+ (await parameters.resolveAccount?.({
375
+ account: defaultAccount,
376
+ chainId: snapshot.chainId,
377
+ operation: {
378
+ authority,
379
+ kind: 'authorizePaymentChannel',
380
+ },
381
+ })) ?? defaultAccount
382
+ const { entry, spent } = await hydrateSessionSnapshot({ account, client, snapshot })
383
+ assertVoucherWithinLocalLimit(entry.cumulativeAmount)
369
384
  await Promise.resolve(store.set(entry)).catch(() => undefined)
385
+ runtime.channel = entry
386
+ runtime.spent = spent
370
387
  return entry
371
388
  }
372
389
 
@@ -430,8 +447,60 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
430
447
  })
431
448
  }
432
449
 
433
- function getValidatedFallbackCloseAmount(target: CloseTarget) {
434
- const closeAmount = getFallbackCloseAmount(target.challenge, target.channelId)
450
+ function validateCloseSnapshot(channel: ChannelEntry, challenge: TempoSessionChallenge) {
451
+ const snapshot = getSessionSnapshot(challenge)
452
+ if (!snapshot) return undefined
453
+
454
+ const computedSnapshotId = Channel.computeId({
455
+ ...snapshot.descriptor,
456
+ chainId: snapshot.chainId,
457
+ escrow: snapshot.escrow,
458
+ })
459
+ if (computedSnapshotId.toLowerCase() !== snapshot.channelId.toLowerCase()) {
460
+ throw new Error('close snapshot descriptor does not match its channel ID')
461
+ }
462
+ if (snapshot.channelId.toLowerCase() !== channel.channelId.toLowerCase()) {
463
+ throw new Error('close snapshot channel ID does not match local session')
464
+ }
465
+ if (
466
+ snapshot.chainId !== channel.chainId ||
467
+ snapshot.escrow.toLowerCase() !== channel.escrow.toLowerCase()
468
+ ) {
469
+ throw new Error('close snapshot payment scope does not match local session')
470
+ }
471
+
472
+ const acceptedCumulative = BigInt(snapshot.acceptedCumulative)
473
+ const snapshotSpent = BigInt(snapshot.spent)
474
+ if (acceptedCumulative < 0n || snapshotSpent < 0n) {
475
+ throw new Error('close snapshot amounts must not be negative')
476
+ }
477
+ if (snapshotSpent > acceptedCumulative) {
478
+ throw new Error('close snapshot spent exceeds accepted cumulative amount')
479
+ }
480
+ if (acceptedCumulative > channel.cumulativeAmount) {
481
+ throw new Error('close snapshot accepted cumulative exceeds local voucher state')
482
+ }
483
+ return { acceptedCumulative, spent: snapshotSpent }
484
+ }
485
+
486
+ function applyCloseSnapshot(target: CloseTarget, challenge: TempoSessionChallenge) {
487
+ const snapshot = validateCloseSnapshot(target.channel, challenge)
488
+ if (!snapshot) return
489
+
490
+ const { acceptedCumulative, spent } = snapshot
491
+ if (runtime.spent > acceptedCumulative) {
492
+ throw new Error('close snapshot accepted cumulative is below locally confirmed spend')
493
+ }
494
+ if (spent > runtime.spent) runtime.spent = spent
495
+ }
496
+
497
+ function getValidatedFallbackCloseAmount(
498
+ target: CloseTarget,
499
+ challenge: TempoSessionChallenge = target.challenge,
500
+ applySnapshot = false,
501
+ ) {
502
+ if (applySnapshot) applyCloseSnapshot(target, challenge)
503
+ const closeAmount = getFallbackCloseAmount(challenge, target.channelId)
435
504
  if (closeAmount > target.channel.cumulativeAmount) {
436
505
  throw new Error('fallback close amount exceeds local voucher state')
437
506
  }
@@ -469,6 +538,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
469
538
  spent: runtime.spent,
470
539
  })
471
540
  if (applied?.channel && runtime.lastChallenge) {
541
+ await store.set(applied.channel)
472
542
  dispatch({
473
543
  type: 'activated',
474
544
  challengeId: runtime.lastChallenge.id,
@@ -625,6 +695,8 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
625
695
  createSessionCredential,
626
696
  fetch: config.fetch,
627
697
  lastUrl: runtime.lastUrl,
698
+ resolveSignedCloseAmount: (challenge) =>
699
+ getValidatedFallbackCloseAmount(target, challenge, true),
628
700
  signedCloseAmount: getValidatedFallbackCloseAmount(target),
629
701
  setChallenge(challenge) {
630
702
  runtime.lastChallenge = challenge
@@ -640,6 +712,20 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
640
712
  return receipt
641
713
  }
642
714
 
715
+ const sseDriver = {
716
+ createSessionCredential,
717
+ doFetch,
718
+ fetch: config.fetch,
719
+ getChannel: () => runtime.channel,
720
+ getChallenge: () => runtime.lastChallenge,
721
+ assertVoucherWithinLocalLimit,
722
+ managementInput,
723
+ acceptReceipt(receipt: SessionReceipt) {
724
+ updateSpentFromReceipt(receipt)
725
+ },
726
+ topUpIfNeeded,
727
+ }
728
+
643
729
  const self: SessionManager = {
644
730
  get channelId() {
645
731
  return runtime.channel?.channelId
@@ -675,19 +761,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
675
761
  },
676
762
 
677
763
  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
- })
764
+ return openSseSession(input, init, sseDriver)
691
765
  },
692
766
 
693
767
  async ws(input, init) {
@@ -768,6 +842,38 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
768
842
  },
769
843
  }
770
844
 
845
+ registerSessionManagerInternals(self, {
846
+ consumeSseResponse(input, response, options) {
847
+ return consumeSseSessionResponse(input, response, options, sseDriver)
848
+ },
849
+ rehydrate({ channel, challenge, input, spent }) {
850
+ if (!channel.opened) throw new Error('Cannot restore a closed session channel.')
851
+ if (!isTempoSessionChallenge(challenge)) {
852
+ throw new Error('Cannot restore session: challenge is not tempo/session.')
853
+ }
854
+ if (spent < 0n) throw new Error('Cannot restore session: spent must not be negative.')
855
+ if (spent > channel.cumulativeAmount) {
856
+ throw new Error('Cannot restore session: spent exceeds local voucher state.')
857
+ }
858
+ assertVoucherWithinLocalLimit(channel.cumulativeAmount)
859
+ const snapshot = validateCloseSnapshot(channel, challenge)
860
+ const restoredSpent = snapshot && snapshot.spent > spent ? snapshot.spent : spent
861
+
862
+ runtime.lastUrl = input
863
+ runtime.lastChallenge = challenge
864
+ runtime.channel = channel
865
+ runtime.spent = restoredSpent
866
+ dispatch({ type: 'challengeReceived', challengeId: challenge.id })
867
+ dispatch({
868
+ type: 'activated',
869
+ challengeId: challenge.id,
870
+ entry: channel,
871
+ spent: restoredSpent.toString(),
872
+ units: 0,
873
+ })
874
+ },
875
+ })
876
+
771
877
  return self
772
878
  }
773
879