mppx 0.8.13 → 0.8.15

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 (156) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/Errors.d.ts +10 -0
  3. package/dist/Errors.d.ts.map +1 -1
  4. package/dist/Errors.js +11 -2
  5. package/dist/Errors.js.map +1 -1
  6. package/dist/Method.d.ts +105 -3
  7. package/dist/Method.d.ts.map +1 -1
  8. package/dist/Method.js +102 -2
  9. package/dist/Method.js.map +1 -1
  10. package/dist/cli/validate/payment.d.ts.map +1 -1
  11. package/dist/cli/validate/payment.js +18 -4
  12. package/dist/cli/validate/payment.js.map +1 -1
  13. package/dist/client/internal/Fetch.d.ts.map +1 -1
  14. package/dist/client/internal/Fetch.js +98 -5
  15. package/dist/client/internal/Fetch.js.map +1 -1
  16. package/dist/client/internal/MethodChallenge.d.ts +19 -0
  17. package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
  18. package/dist/client/internal/MethodChallenge.js +15 -0
  19. package/dist/client/internal/MethodChallenge.js.map +1 -0
  20. package/dist/client/internal/MethodResponse.d.ts +15 -0
  21. package/dist/client/internal/MethodResponse.d.ts.map +1 -1
  22. package/dist/client/internal/MethodResponse.js +10 -0
  23. package/dist/client/internal/MethodResponse.js.map +1 -1
  24. package/dist/server/Mppx.d.ts +23 -4
  25. package/dist/server/Mppx.d.ts.map +1 -1
  26. package/dist/server/Mppx.js +91 -82
  27. package/dist/server/Mppx.js.map +1 -1
  28. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  29. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  30. package/dist/stripe/server/internal/html.gen.js +1 -1
  31. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  32. package/dist/tempo/client/Methods.d.ts +1 -0
  33. package/dist/tempo/client/Methods.d.ts.map +1 -1
  34. package/dist/tempo/internal/fee-payer.d.ts +1 -0
  35. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  36. package/dist/tempo/internal/fee-payer.js +7 -2
  37. package/dist/tempo/internal/fee-payer.js.map +1 -1
  38. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
  39. package/dist/tempo/legacy/client/ChannelOps.js +2 -1
  40. package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
  41. package/dist/tempo/server/Charge.d.ts +46 -77
  42. package/dist/tempo/server/Charge.d.ts.map +1 -1
  43. package/dist/tempo/server/Charge.js +289 -209
  44. package/dist/tempo/server/Charge.js.map +1 -1
  45. package/dist/tempo/server/Methods.d.ts +12 -1
  46. package/dist/tempo/server/Methods.d.ts.map +1 -1
  47. package/dist/tempo/server/Methods.js +4 -0
  48. package/dist/tempo/server/Methods.js.map +1 -1
  49. package/dist/tempo/server/Relay.d.ts +57 -0
  50. package/dist/tempo/server/Relay.d.ts.map +1 -0
  51. package/dist/tempo/server/Relay.js +179 -0
  52. package/dist/tempo/server/Relay.js.map +1 -0
  53. package/dist/tempo/server/SponsorBudget.d.ts +59 -0
  54. package/dist/tempo/server/SponsorBudget.d.ts.map +1 -0
  55. package/dist/tempo/server/SponsorBudget.js +150 -0
  56. package/dist/tempo/server/SponsorBudget.js.map +1 -0
  57. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  58. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  59. package/dist/tempo/server/internal/html.gen.js +1 -1
  60. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  61. package/dist/tempo/session/client/ChannelOps.d.ts +7 -1
  62. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  63. package/dist/tempo/session/client/ChannelOps.js +9 -4
  64. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  65. package/dist/tempo/session/client/CredentialState.d.ts +5 -1
  66. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  67. package/dist/tempo/session/client/CredentialState.js +37 -19
  68. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  69. package/dist/tempo/session/client/Runtime.d.ts +8 -0
  70. package/dist/tempo/session/client/Runtime.d.ts.map +1 -1
  71. package/dist/tempo/session/client/Runtime.js +17 -0
  72. package/dist/tempo/session/client/Runtime.js.map +1 -1
  73. package/dist/tempo/session/client/Session.d.ts +15 -5
  74. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  75. package/dist/tempo/session/client/Session.js +216 -45
  76. package/dist/tempo/session/client/Session.js.map +1 -1
  77. package/dist/tempo/session/client/SessionManager.d.ts +8 -1
  78. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  79. package/dist/tempo/session/client/SessionManager.js +114 -44
  80. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  81. package/dist/tempo/session/client/Transports.d.ts +16 -6
  82. package/dist/tempo/session/client/Transports.d.ts.map +1 -1
  83. package/dist/tempo/session/client/Transports.js +84 -37
  84. package/dist/tempo/session/client/Transports.js.map +1 -1
  85. package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
  86. package/dist/tempo/session/precompile/Chain.js +96 -8
  87. package/dist/tempo/session/precompile/Chain.js.map +1 -1
  88. package/dist/tempo/session/precompile/Channel.d.ts +2 -0
  89. package/dist/tempo/session/precompile/Channel.d.ts.map +1 -1
  90. package/dist/tempo/session/precompile/Channel.js +4 -0
  91. package/dist/tempo/session/precompile/Channel.js.map +1 -1
  92. package/dist/tempo/session/server/RequestState.js +3 -1
  93. package/dist/tempo/session/server/RequestState.js.map +1 -1
  94. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  95. package/dist/tempo/session/server/Ws.js +3 -0
  96. package/dist/tempo/session/server/Ws.js.map +1 -1
  97. package/dist/tempo/subscription/KeyAuthorization.d.ts +1 -829
  98. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
  99. package/dist/tempo/subscription/KeyAuthorization.js +14 -3
  100. package/dist/tempo/subscription/KeyAuthorization.js.map +1 -1
  101. package/dist/viem/Client.d.ts.map +1 -1
  102. package/dist/viem/Client.js +6 -1
  103. package/dist/viem/Client.js.map +1 -1
  104. package/package.json +2 -2
  105. package/src/Errors.test.ts +23 -0
  106. package/src/Errors.ts +21 -2
  107. package/src/Method.test.ts +102 -1
  108. package/src/Method.ts +241 -5
  109. package/src/cli/validate/payment.ts +18 -4
  110. package/src/cli/validate.test.ts +16 -0
  111. package/src/client/Mppx.test-d.ts +3 -3
  112. package/src/client/internal/Fetch.test.ts +77 -0
  113. package/src/client/internal/Fetch.ts +122 -11
  114. package/src/client/internal/MethodChallenge.ts +35 -0
  115. package/src/client/internal/MethodResponse.ts +28 -0
  116. package/src/server/Mppx.test-d.ts +2 -0
  117. package/src/server/Mppx.test.ts +205 -0
  118. package/src/server/Mppx.ts +167 -103
  119. package/src/stripe/server/internal/html.gen.ts +1 -1
  120. package/src/tempo/PublicExports.test-d.ts +20 -0
  121. package/src/tempo/internal/fee-payer.ts +7 -3
  122. package/src/tempo/legacy/client/ChannelOps.test.ts +7 -0
  123. package/src/tempo/legacy/client/ChannelOps.ts +2 -1
  124. package/src/tempo/server/Charge.test.ts +337 -63
  125. package/src/tempo/server/Charge.ts +443 -267
  126. package/src/tempo/server/Methods.ts +11 -0
  127. package/src/tempo/server/Relay.test.ts +677 -0
  128. package/src/tempo/server/Relay.ts +297 -0
  129. package/src/tempo/server/SponsorBudget.test.ts +154 -0
  130. package/src/tempo/server/SponsorBudget.ts +219 -0
  131. package/src/tempo/server/internal/html.gen.ts +1 -1
  132. package/src/tempo/session/README.md +7 -0
  133. package/src/tempo/session/client/ChannelOps.test.ts +17 -2
  134. package/src/tempo/session/client/ChannelOps.ts +12 -4
  135. package/src/tempo/session/client/CredentialState.ts +61 -20
  136. package/src/tempo/session/client/Runtime.test.ts +12 -0
  137. package/src/tempo/session/client/Runtime.ts +22 -0
  138. package/src/tempo/session/client/Session.test.ts +506 -7
  139. package/src/tempo/session/client/Session.ts +279 -54
  140. package/src/tempo/session/client/SessionManager.test.ts +318 -13
  141. package/src/tempo/session/client/SessionManager.ts +132 -42
  142. package/src/tempo/session/client/Transports.test.ts +54 -1
  143. package/src/tempo/session/client/Transports.ts +137 -57
  144. package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
  145. package/src/tempo/session/precompile/Chain.test.ts +197 -10
  146. package/src/tempo/session/precompile/Chain.ts +102 -8
  147. package/src/tempo/session/precompile/Channel.test.ts +9 -0
  148. package/src/tempo/session/precompile/Channel.ts +5 -0
  149. package/src/tempo/session/server/RequestState.test.ts +8 -1
  150. package/src/tempo/session/server/RequestState.ts +2 -2
  151. package/src/tempo/session/server/Session.test.ts +66 -4
  152. package/src/tempo/session/server/Ws.ts +3 -0
  153. package/src/tempo/subscription/KeyAuthorization.test.ts +70 -1
  154. package/src/tempo/subscription/KeyAuthorization.ts +14 -3
  155. package/src/viem/Client.test.ts +18 -1
  156. package/src/viem/Client.ts +7 -1
@@ -1,6 +1,7 @@
1
1
  import { type Address, parseUnits } from 'viem'
2
2
  import { tempo as tempo_chain } from 'viem/chains'
3
3
 
4
+ import * as MethodChallenge from '../../../client/internal/MethodChallenge.js'
4
5
  import * as MethodResponse from '../../../client/internal/MethodResponse.js'
5
6
  import * as Constants from '../../../Constants.js'
6
7
  import * as Credential from '../../../Credential.js'
@@ -11,30 +12,103 @@ import type {
11
12
  ResolveAccount as ResolveAccount_,
12
13
  ResolveAccountInfo as ResolveAccountInfo_,
13
14
  } from '../../client/ResolveAccount.js'
15
+ import * as AutoSwap from '../../internal/auto-swap.js'
14
16
  import * as defaults from '../../internal/defaults.js'
15
17
  import * as Methods from '../../Methods.js'
16
- import { isEventStream, requireSessionCredentialContext } from '../precompile/Protocol.js'
18
+ import * as Channel from '../precompile/Channel.js'
19
+ import {
20
+ deserializeSessionReceipt,
21
+ isEventStream,
22
+ readSessionChallengeAmount,
23
+ requireSessionCredentialContext,
24
+ } from '../precompile/Protocol.js'
17
25
  import { serializeCredential, type ChannelEntry } from './ChannelOps.js'
18
26
  import { createChannelStore, type ChannelStore } from './ChannelStore.js'
19
27
  import {
28
+ canSignDescriptor,
20
29
  executeCredentialPlan,
30
+ hasSessionAction,
21
31
  planCredential,
22
32
  resolveChallengeContext,
23
33
  resolveRecoverContext,
24
34
  sessionContextSchema,
35
+ type ChallengeContext,
36
+ type SessionContext as CredentialContext,
25
37
  } from './CredentialState.js'
26
- import { assertWithinMaxDeposit } from './Runtime.js'
38
+ import { assertWithinMaxDeposit, resolveAutomaticTopUp } from './Runtime.js'
27
39
  import {
40
+ getSessionSnapshot,
28
41
  handleSseNeedVoucher,
29
- isTempoSessionChallenge,
30
- managementInput,
42
+ isTip1034SessionChallenge,
31
43
  postTopUp,
32
44
  wrapSseResponse,
33
45
  type SsePaymentDriver,
46
+ type TempoSessionChallenge,
34
47
  } from './Transports.js'
35
48
 
36
49
  export { sessionContextSchema, type SessionContext } from './CredentialState.js'
37
50
 
51
+ /** Tail promise for each payment scope, used to serialize automatic opens per store. */
52
+ const channelTails = new WeakMap<ChannelStore, Map<string, Promise<void>>>()
53
+
54
+ /** Serializes automatic opens for one payment scope across methods sharing a store. */
55
+ async function lockChannel(store: ChannelStore, key: string) {
56
+ const tails = channelTails.get(store) ?? new Map<string, Promise<void>>()
57
+ channelTails.set(store, tails)
58
+ const previous = tails.get(key)
59
+ let unlock!: () => void
60
+ const current = new Promise<void>((resolve) => {
61
+ unlock = resolve
62
+ })
63
+ tails.set(key, current)
64
+ await previous
65
+ return () => {
66
+ unlock()
67
+ if (tails.get(key) === current) tails.delete(key)
68
+ }
69
+ }
70
+
71
+ function matchesOpen(
72
+ opened: ChannelEntry,
73
+ acknowledgement: { acceptedCumulative: string; channelId: string } | null | undefined,
74
+ ): boolean {
75
+ try {
76
+ return (
77
+ acknowledgement?.channelId.toLowerCase() === opened.channelId.toLowerCase() &&
78
+ BigInt(acknowledgement.acceptedCumulative) === opened.cumulativeAmount
79
+ )
80
+ } catch {
81
+ return false
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Returns whether the server acknowledged an open.
87
+ *
88
+ * Receipts must match the originating challenge, channel, and cumulative amount.
89
+ * Replacement challenges may acknowledge the same channel and amount through a
90
+ * session snapshot. Malformed acknowledgements are ignored.
91
+ */
92
+ function acknowledgesOpen(
93
+ outcome: MethodResponse.AttemptOutcome,
94
+ challenge: TempoSessionChallenge,
95
+ opened: ChannelEntry,
96
+ ): boolean {
97
+ try {
98
+ const receiptHeader = outcome.response?.headers.get(Constants.Headers.paymentReceipt)
99
+ if (receiptHeader) {
100
+ const receipt = deserializeSessionReceipt(receiptHeader)
101
+ if (receipt.challengeId === challenge.id && matchesOpen(opened, receipt)) return true
102
+ }
103
+ } catch {}
104
+ return (
105
+ outcome.challenges?.some((candidate) => {
106
+ if (!isTip1034SessionChallenge(candidate)) return false
107
+ return matchesOpen(opened, getSessionSnapshot(candidate))
108
+ }) ?? false
109
+ )
110
+ }
111
+
38
112
  /**
39
113
  * Creates the low-level TIP-1034 session payment method for use with `Mppx.create()`.
40
114
  *
@@ -45,11 +119,13 @@ export { sessionContextSchema, type SessionContext } from './CredentialState.js'
45
119
  export function session(parameters: session.Parameters = {}) {
46
120
  const {
47
121
  account,
122
+ autoSwap: autoSwapParameter,
48
123
  channelStore,
49
124
  decimals = defaults.decimals,
50
125
  escrow: escrowOverride,
51
126
  getClient: getClientParameter,
52
127
  maxDeposit: maxDepositParameter,
128
+ topUpAmount: topUpAmountParameter,
53
129
  onChannelUpdate,
54
130
  resolveAccount,
55
131
  } = parameters
@@ -61,60 +137,209 @@ export function session(parameters: session.Parameters = {}) {
61
137
  const getAccount = Account.getResolver({ account })
62
138
  const maxDeposit =
63
139
  maxDepositParameter !== undefined ? parseUnits(maxDepositParameter, decimals) : undefined
140
+ const topUpAmount =
141
+ topUpAmountParameter !== undefined ? parseUnits(topUpAmountParameter, decimals) : undefined
64
142
  const store = channelStore ?? createChannelStore()
65
143
  const sink = { store, notifyUpdate: (entry: ChannelEntry) => onChannelUpdate?.(entry) }
66
144
 
145
+ const resolveCredentialAccount = async (
146
+ resolved: ChallengeContext,
147
+ context: CredentialContext | undefined,
148
+ entry: ChannelEntry | undefined,
149
+ ) => {
150
+ const defaultAccount = getAccount(resolved.client, context)
151
+ const descriptor = context?.action
152
+ ? context.descriptor
153
+ : (entry?.descriptor ??
154
+ resolveRecoverContext({ context, snapshot: resolved.snapshot })?.descriptor)
155
+ return (
156
+ (await resolveAccount?.({
157
+ account: defaultAccount,
158
+ chainId: resolved.chainId,
159
+ operation: {
160
+ kind: 'authorizePaymentChannel',
161
+ ...(descriptor ? { authority: Channel.resolveAuthorizedSigner(descriptor) } : {}),
162
+ },
163
+ })) ?? defaultAccount
164
+ )
165
+ }
166
+
167
+ const resolveCredentialPlan = async (
168
+ resolved: ChallengeContext,
169
+ context: CredentialContext | undefined,
170
+ entry: ChannelEntry | undefined,
171
+ ) =>
172
+ planCredential({
173
+ account: await resolveCredentialAccount(resolved, context, entry),
174
+ entry,
175
+ context,
176
+ decimals,
177
+ maxDeposit,
178
+ resolved,
179
+ })
180
+
67
181
  const method = Method.toClient(Methods.session, {
68
- canHandleChallenge({ challenge }) {
69
- return (
70
- Constants.getMethodDetail(
71
- challenge.request.methodDetails,
72
- Constants.MethodDetailKeys.sessionProtocol,
73
- ) === Constants.SessionProtocols.v2
74
- )
75
- },
182
+ canHandleChallenge: ({ challenge }) => isTip1034SessionChallenge(challenge),
76
183
  context: sessionContextSchema,
77
- async createCredential({ challenge, context }) {
184
+ async createCredential(parameters) {
185
+ const { challenge, context } = parameters
78
186
  const resolved = await resolveChallengeContext({
79
187
  challenge,
80
188
  escrowOverride,
81
189
  getClient,
82
190
  })
83
- const defaultAccount = getAccount(resolved.client, context)
84
- const entry = await store.get(resolved.key)
85
- // Resolve recovery hints early so account selection can satisfy an existing channel authority.
86
- const recoverContext = resolveRecoverContext({ context, snapshot: resolved.snapshot })
87
- const descriptor = context?.action
88
- ? context.descriptor
89
- : (entry?.descriptor ?? recoverContext?.descriptor)
90
- const account =
91
- (await resolveAccount?.({
92
- account: defaultAccount,
93
- chainId: resolved.chainId,
94
- operation: {
95
- kind: 'authorizePaymentChannel',
96
- ...(descriptor ? { authority: descriptor.authorizedSigner } : {}),
97
- },
98
- })) ?? defaultAccount
99
- const payload = await executeCredentialPlan(
100
- planCredential({
101
- account,
102
- entry,
103
- context,
104
- decimals,
105
- maxDeposit,
106
- resolved,
107
- }),
108
- sink,
109
- )
110
- return serializeCredential(challenge, payload, resolved.chainId, account)
191
+ const attempt = MethodResponse.getAttempt(parameters)
192
+ let release = () => {}
193
+ try {
194
+ let entry = await store.get(resolved.key)
195
+ let plan = await resolveCredentialPlan(resolved, context, entry)
196
+ if (attempt && plan.type === 'open') {
197
+ release = await lockChannel(store, resolved.key)
198
+ const nextEntry = await store.get(resolved.key)
199
+ if (nextEntry?.channelId !== entry?.channelId) {
200
+ entry = nextEntry
201
+ if (entry?.opened) {
202
+ await attempt.prepare()
203
+ entry = await store.get(resolved.key)
204
+ }
205
+ plan = await resolveCredentialPlan(resolved, context, entry)
206
+ }
207
+ }
208
+ let pendingOpen: ChannelEntry | undefined
209
+ // Defer opens only when low-level Fetch owns the response lifecycle.
210
+ // SessionManager unregisters this hook and keeps its existing transaction boundary.
211
+ const credentialSink =
212
+ attempt && plan.type === 'open'
213
+ ? {
214
+ store: {
215
+ get: (key: string) => store.get(key),
216
+ async set(next: ChannelEntry) {
217
+ pendingOpen = next
218
+ },
219
+ delete: (key: string) => store.delete(key),
220
+ },
221
+ notifyUpdate() {},
222
+ }
223
+ : sink
224
+ const payload = await executeCredentialPlan(
225
+ plan,
226
+ credentialSink,
227
+ AutoSwap.resolve(context?.autoSwap ?? autoSwapParameter, AutoSwap.defaultCurrencies),
228
+ )
229
+ const credential = await serializeCredential(
230
+ challenge,
231
+ payload,
232
+ resolved.chainId,
233
+ plan.account,
234
+ )
235
+ if (attempt && pendingOpen) {
236
+ const opened = pendingOpen
237
+ attempt.settle = async (outcome) => {
238
+ const accepted =
239
+ outcome.status === 'accepted' || acknowledgesOpen(outcome, challenge, opened)
240
+ if (!accepted && outcome.status === 'pending') return false
241
+ try {
242
+ if (accepted) {
243
+ const current = await store.get(resolved.key)
244
+ if (
245
+ !current?.opened ||
246
+ current.channelId.toLowerCase() !== opened.channelId.toLowerCase()
247
+ ) {
248
+ await store.set(opened)
249
+ sink.notifyUpdate(opened)
250
+ }
251
+ }
252
+ return true
253
+ } finally {
254
+ release()
255
+ }
256
+ }
257
+ } else release()
258
+ return credential
259
+ } catch (error) {
260
+ release()
261
+ throw error
262
+ }
111
263
  },
112
264
  })
113
265
 
266
+ const topUpChannelIfNeeded = async ({
267
+ challenge,
268
+ channel,
269
+ deposit,
270
+ fetch,
271
+ input,
272
+ requiredCumulative,
273
+ }: {
274
+ challenge: TempoSessionChallenge
275
+ channel: ChannelEntry
276
+ deposit: bigint
277
+ fetch: typeof globalThis.fetch
278
+ input: RequestInfo | URL
279
+ requiredCumulative: bigint
280
+ }) => {
281
+ const knownDeposit = channel.deposit > deposit ? channel.deposit : deposit
282
+ const additionalDeposit = resolveAutomaticTopUp({
283
+ deposit: knownDeposit,
284
+ maxDeposit,
285
+ requiredCumulative,
286
+ suggestedDeposit:
287
+ challenge.request.suggestedDeposit === undefined
288
+ ? undefined
289
+ : BigInt(challenge.request.suggestedDeposit),
290
+ topUpAmount,
291
+ })
292
+ if (additionalDeposit > 0n)
293
+ await postTopUp({
294
+ additionalDeposit,
295
+ challenge,
296
+ channel,
297
+ channelId: channel.channelId,
298
+ createSessionCredential: (challenge, context) =>
299
+ method.createCredential({ challenge, context }),
300
+ fetch,
301
+ input,
302
+ })
303
+ const nextDeposit = knownDeposit + additionalDeposit
304
+ if (nextDeposit === channel.deposit) return
305
+ const updated = { ...channel, deposit: nextDeposit }
306
+ await store.set(updated)
307
+ sink.notifyUpdate(updated)
308
+ }
309
+
310
+ MethodChallenge.register(method, async ({ challenge, context, fetch, input }) => {
311
+ if (!isTip1034SessionChallenge(challenge)) return
312
+ const sessionContext = context === undefined ? undefined : sessionContextSchema.parse(context)
313
+ if (hasSessionAction(sessionContext)) return
314
+ const resolved = await resolveChallengeContext({ challenge, escrowOverride, getClient })
315
+ const channel = await store.get(resolved.key)
316
+ if (!channel?.opened) return
317
+ const snapshot =
318
+ resolved.snapshot?.channelId.toLowerCase() === channel.channelId.toLowerCase()
319
+ ? resolved.snapshot
320
+ : undefined
321
+ const nextCumulative = channel.cumulativeAmount + readSessionChallengeAmount(challenge)
322
+ const snapshotRequired = snapshot ? BigInt(snapshot.requiredCumulative) : nextCumulative
323
+ const requiredCumulative = snapshotRequired > nextCumulative ? snapshotRequired : nextCumulative
324
+ const snapshotDeposit = snapshot ? BigInt(snapshot.deposit) : channel.deposit
325
+ const deposit = snapshotDeposit > channel.deposit ? snapshotDeposit : channel.deposit
326
+ if (requiredCumulative <= deposit && deposit === channel.deposit) return
327
+ const account = await resolveCredentialAccount(resolved, sessionContext, channel)
328
+ if (!canSignDescriptor(account, channel.descriptor)) return
329
+ await topUpChannelIfNeeded({
330
+ challenge,
331
+ channel,
332
+ deposit,
333
+ fetch,
334
+ input,
335
+ requiredCumulative,
336
+ })
337
+ })
338
+
114
339
  return MethodResponse.register(
115
340
  method,
116
341
  async ({ challenge, credential, fetch, headers, input, refetch, response, signal }) => {
117
- if (!isTempoSessionChallenge(challenge)) return response
342
+ if (!isTip1034SessionChallenge(challenge)) return response
118
343
  if (!isEventStream(response)) {
119
344
  const credentialContext = requireSessionCredentialContext(
120
345
  Credential.deserialize(credential).payload,
@@ -142,23 +367,16 @@ export function session(parameters: session.Parameters = {}) {
142
367
  method.createCredential({ challenge, context }),
143
368
  fetch,
144
369
  getChannel: () => channel ?? null,
145
- managementInput,
146
- async topUpIfNeeded({ channelId, deposit, requiredCumulative }) {
147
- if (requiredCumulative <= deposit || !channel) return
148
- const additionalDeposit = requiredCumulative - deposit
149
- await postTopUp({
150
- additionalDeposit,
370
+ async topUpIfNeeded({ deposit, requiredCumulative }) {
371
+ if (!channel) return
372
+ await topUpChannelIfNeeded({
151
373
  challenge,
152
374
  channel,
153
- channelId,
154
- createSessionCredential: (challenge, context) =>
155
- method.createCredential({ challenge, context }),
375
+ deposit: channel.deposit > deposit ? channel.deposit : deposit,
156
376
  fetch,
157
377
  input,
378
+ requiredCumulative,
158
379
  })
159
- channel.deposit += additionalDeposit
160
- await store.set(channel)
161
- sink.notifyUpdate(channel)
162
380
  },
163
381
  } satisfies SsePaymentDriver
164
382
 
@@ -179,6 +397,8 @@ export declare namespace session {
179
397
 
180
398
  type Parameters = Account.getResolver.Parameters &
181
399
  Client.getResolver.Parameters & {
400
+ /** Automatically acquire the session currency from fallback stablecoins before open/top-up. */
401
+ autoSwap?: AutoSwap.resolve.Value | undefined
182
402
  /** Pluggable persistence for reusable channels. Defaults to an in-memory store. */
183
403
  channelStore?: ChannelStore | undefined
184
404
  /** Token decimals for parsing human-readable amounts (default: 6). */
@@ -187,6 +407,11 @@ export declare namespace session {
187
407
  escrow?: Address | undefined
188
408
  /** Maximum channel deposit in human-readable units. Caps server-suggested opens and automatic top-ups. */
189
409
  maxDeposit?: string | undefined
410
+ /**
411
+ * Preferred automatic top-up size in human-readable units. When omitted,
412
+ * a bounded server `suggestedDeposit` is preferred, then the exact shortfall.
413
+ */
414
+ topUpAmount?: string | undefined
190
415
  /** Called whenever channel state changes. */
191
416
  onChannelUpdate?: ((entry: ChannelEntry) => void) | undefined
192
417
  /** Selects the account that signs this session credential after the challenge is known. */