mppx 0.8.11 → 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 (218) hide show
  1. package/CHANGELOG.md +25 -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/internal/Fetch.d.ts.map +1 -1
  39. package/dist/client/internal/Fetch.js +35 -6
  40. package/dist/client/internal/Fetch.js.map +1 -1
  41. package/dist/client/internal/MethodResponse.d.ts +23 -0
  42. package/dist/client/internal/MethodResponse.d.ts.map +1 -0
  43. package/dist/client/internal/MethodResponse.js +15 -0
  44. package/dist/client/internal/MethodResponse.js.map +1 -0
  45. package/dist/client/node.d.ts +29 -0
  46. package/dist/client/node.d.ts.map +1 -0
  47. package/dist/client/node.js +177 -0
  48. package/dist/client/node.js.map +1 -0
  49. package/dist/internal/types.d.ts +24 -0
  50. package/dist/internal/types.d.ts.map +1 -1
  51. package/dist/stripe/server/Charge.d.ts +8 -4
  52. package/dist/stripe/server/Charge.d.ts.map +1 -1
  53. package/dist/stripe/server/Charge.js.map +1 -1
  54. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  55. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  56. package/dist/stripe/server/internal/html.gen.js +1 -1
  57. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  58. package/dist/tempo/client/Methods.d.ts +1 -1
  59. package/dist/tempo/internal/types.d.ts +10 -1
  60. package/dist/tempo/internal/types.d.ts.map +1 -1
  61. package/dist/tempo/legacy/client/ChannelOps.d.ts +2 -0
  62. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
  63. package/dist/tempo/legacy/client/ChannelOps.js +4 -0
  64. package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
  65. package/dist/tempo/legacy/client/Session.d.ts +2 -2
  66. package/dist/tempo/legacy/client/Session.d.ts.map +1 -1
  67. package/dist/tempo/legacy/client/Session.js +7 -0
  68. package/dist/tempo/legacy/client/Session.js.map +1 -1
  69. package/dist/tempo/legacy/server/Session.d.ts +3 -3
  70. package/dist/tempo/legacy/server/Session.d.ts.map +1 -1
  71. package/dist/tempo/legacy/server/Session.js.map +1 -1
  72. package/dist/tempo/server/Charge.d.ts +3 -2
  73. package/dist/tempo/server/Charge.d.ts.map +1 -1
  74. package/dist/tempo/server/Charge.js.map +1 -1
  75. package/dist/tempo/server/Methods.d.ts +2 -23
  76. package/dist/tempo/server/Methods.d.ts.map +1 -1
  77. package/dist/tempo/server/Methods.js +2 -2
  78. package/dist/tempo/server/Methods.js.map +1 -1
  79. package/dist/tempo/server/Subscription.d.ts +3 -2
  80. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  81. package/dist/tempo/server/Subscription.js.map +1 -1
  82. package/dist/tempo/server/index.d.ts +1 -1
  83. package/dist/tempo/server/index.d.ts.map +1 -1
  84. package/dist/tempo/server/index.js.map +1 -1
  85. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  86. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  87. package/dist/tempo/server/internal/html.gen.js +1 -1
  88. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  89. package/dist/tempo/session/Snapshot.d.ts +9 -0
  90. package/dist/tempo/session/Snapshot.d.ts.map +1 -1
  91. package/dist/tempo/session/Snapshot.js +6 -0
  92. package/dist/tempo/session/Snapshot.js.map +1 -1
  93. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  94. package/dist/tempo/session/client/ChannelOps.js +11 -2
  95. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  96. package/dist/tempo/session/client/CredentialState.d.ts +21 -1
  97. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  98. package/dist/tempo/session/client/CredentialState.js +66 -1
  99. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  100. package/dist/tempo/session/client/Session.d.ts +1 -1
  101. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  102. package/dist/tempo/session/client/Session.js +53 -1
  103. package/dist/tempo/session/client/Session.js.map +1 -1
  104. package/dist/tempo/session/client/SessionManager.d.ts +2 -2
  105. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  106. package/dist/tempo/session/client/SessionManager.js +130 -30
  107. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  108. package/dist/tempo/session/client/Transports.d.ts +21 -2
  109. package/dist/tempo/session/client/Transports.d.ts.map +1 -1
  110. package/dist/tempo/session/client/Transports.js +129 -18
  111. package/dist/tempo/session/client/Transports.js.map +1 -1
  112. package/dist/tempo/session/client/internal/SessionManager.d.ts +19 -0
  113. package/dist/tempo/session/client/internal/SessionManager.d.ts.map +1 -0
  114. package/dist/tempo/session/client/internal/SessionManager.js +13 -0
  115. package/dist/tempo/session/client/internal/SessionManager.js.map +1 -0
  116. package/dist/tempo/session/precompile/Chain.js +13 -4
  117. package/dist/tempo/session/precompile/Chain.js.map +1 -1
  118. package/dist/tempo/session/server/CredentialVerification.d.ts +3 -0
  119. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  120. package/dist/tempo/session/server/CredentialVerification.js +14 -0
  121. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  122. package/dist/tempo/session/server/MeteredStream.d.ts +7 -1
  123. package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
  124. package/dist/tempo/session/server/MeteredStream.js +5 -4
  125. package/dist/tempo/session/server/MeteredStream.js.map +1 -1
  126. package/dist/tempo/session/server/RequestState.d.ts +5 -1
  127. package/dist/tempo/session/server/RequestState.d.ts.map +1 -1
  128. package/dist/tempo/session/server/RequestState.js +10 -4
  129. package/dist/tempo/session/server/RequestState.js.map +1 -1
  130. package/dist/tempo/session/server/Session.d.ts +21 -6
  131. package/dist/tempo/session/server/Session.d.ts.map +1 -1
  132. package/dist/tempo/session/server/Session.js +4 -1
  133. package/dist/tempo/session/server/Session.js.map +1 -1
  134. package/dist/tempo/session/server/Settlement.d.ts +20 -0
  135. package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
  136. package/dist/tempo/session/server/Settlement.js +20 -0
  137. package/dist/tempo/session/server/Settlement.js.map +1 -1
  138. package/dist/tempo/session/server/Ws.d.ts +2 -0
  139. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  140. package/dist/tempo/session/server/Ws.js.map +1 -1
  141. package/dist/tempo/session/server/index.d.ts +1 -1
  142. package/dist/tempo/session/server/index.d.ts.map +1 -1
  143. package/dist/tempo/subscription/KeyAuthorization.d.ts +21 -21
  144. package/dist/validation/core.d.ts.map +1 -1
  145. package/dist/validation/core.js +6 -2
  146. package/dist/validation/core.js.map +1 -1
  147. package/dist/viem/Client.d.ts.map +1 -1
  148. package/dist/viem/Client.js +24 -17
  149. package/dist/viem/Client.js.map +1 -1
  150. package/package.json +6 -1
  151. package/src/cli/account.ts +45 -10
  152. package/src/cli/cli.test.ts +165 -70
  153. package/src/cli/cli.ts +67 -2
  154. package/src/cli/mcp.test.ts +11 -0
  155. package/src/cli/sessions/Manager.test.ts +249 -0
  156. package/src/cli/sessions/Manager.ts +93 -0
  157. package/src/cli/sessions/commands.ts +444 -0
  158. package/src/cli/sessions/request.test.ts +51 -0
  159. package/src/cli/sessions/request.ts +353 -0
  160. package/src/cli/sessions/store.test.ts +581 -0
  161. package/src/cli/sessions/store.ts +940 -0
  162. package/src/cli/utils.test.ts +23 -0
  163. package/src/cli/utils.ts +10 -0
  164. package/src/cli/validate/index.ts +12 -14
  165. package/src/cli/validate/messages.ts +7 -0
  166. package/src/cli/validate.test.ts +38 -5
  167. package/src/client/internal/Fetch.test.ts +29 -0
  168. package/src/client/internal/Fetch.ts +51 -6
  169. package/src/client/internal/MethodResponse.ts +39 -0
  170. package/src/client/node.test.ts +115 -0
  171. package/src/client/node.ts +247 -0
  172. package/src/internal/types.test-d.ts +21 -0
  173. package/src/internal/types.ts +33 -0
  174. package/src/server/Mppx.test-d.ts +42 -0
  175. package/src/stripe/Methods.test.ts +10 -0
  176. package/src/stripe/server/Charge.test-d.ts +66 -0
  177. package/src/stripe/server/Charge.ts +8 -5
  178. package/src/stripe/server/internal/html.gen.ts +1 -1
  179. package/src/tempo/internal/types.ts +13 -2
  180. package/src/tempo/legacy/client/ChannelOps.test.ts +21 -0
  181. package/src/tempo/legacy/client/ChannelOps.ts +7 -0
  182. package/src/tempo/legacy/client/Session.test.ts +102 -1
  183. package/src/tempo/legacy/client/Session.ts +10 -0
  184. package/src/tempo/legacy/server/Defaults.test-d.ts +9 -0
  185. package/src/tempo/legacy/server/Session.ts +6 -5
  186. package/src/tempo/server/Charge.ts +6 -4
  187. package/src/tempo/server/Methods.ts +5 -4
  188. package/src/tempo/server/Subscription.ts +6 -4
  189. package/src/tempo/server/index.ts +5 -1
  190. package/src/tempo/server/internal/html.gen.ts +1 -1
  191. package/src/tempo/session/README.md +201 -0
  192. package/src/tempo/session/Snapshot.ts +21 -0
  193. package/src/tempo/session/client/ChannelOps.test.ts +40 -0
  194. package/src/tempo/session/client/ChannelOps.ts +12 -2
  195. package/src/tempo/session/client/CredentialState.test.ts +2 -2
  196. package/src/tempo/session/client/CredentialState.ts +103 -1
  197. package/src/tempo/session/client/Session.test.ts +86 -4
  198. package/src/tempo/session/client/Session.ts +73 -1
  199. package/src/tempo/session/client/SessionManager.test.ts +137 -4
  200. package/src/tempo/session/client/SessionManager.ts +141 -33
  201. package/src/tempo/session/client/Transports.test.ts +225 -8
  202. package/src/tempo/session/client/Transports.ts +156 -21
  203. package/src/tempo/session/client/internal/SessionManager.ts +36 -0
  204. package/src/tempo/session/precompile/Chain.test.ts +26 -0
  205. package/src/tempo/session/precompile/Chain.ts +17 -4
  206. package/src/tempo/session/server/CredentialVerification.ts +18 -1
  207. package/src/tempo/session/server/MeteredStream.ts +12 -5
  208. package/src/tempo/session/server/RequestState.test.ts +39 -3
  209. package/src/tempo/session/server/RequestState.ts +13 -6
  210. package/src/tempo/session/server/Session.test.ts +461 -0
  211. package/src/tempo/session/server/Session.ts +31 -8
  212. package/src/tempo/session/server/Settlement.ts +45 -0
  213. package/src/tempo/session/server/Ws.test.ts +113 -0
  214. package/src/tempo/session/server/Ws.ts +2 -0
  215. package/src/tempo/session/server/index.ts +2 -0
  216. package/src/validation/core.ts +7 -1
  217. package/src/viem/Client.test.ts +44 -1
  218. package/src/viem/Client.ts +31 -17
@@ -0,0 +1,353 @@
1
+ import { Errors } from 'incur'
2
+ import type { Hex } from 'ox'
3
+ import { createClient, formatUnits, http } from 'viem'
4
+
5
+ import type * as Challenge from '../../Challenge.js'
6
+ import {
7
+ canSignDescriptor,
8
+ resolveChallengeContext,
9
+ } from '../../tempo/session/client/CredentialState.js'
10
+ import { getSessionManagerInternals } from '../../tempo/session/client/internal/SessionManager.js'
11
+ import { sessionManager } from '../../tempo/session/client/SessionManager.js'
12
+ import type { TempoSessionChallenge } from '../../tempo/session/client/Transports.js'
13
+ import { isEventStream, type SessionReceipt } from '../../tempo/session/precompile/Protocol.js'
14
+ import { resolvePersistentAccount } from '../account.js'
15
+ import {
16
+ isTestnet,
17
+ printResponseHeaders,
18
+ resolveChain,
19
+ resolveRpcUrl,
20
+ type Network,
21
+ } from '../utils.js'
22
+ import {
23
+ createSessionRegistry,
24
+ type ManagedSession,
25
+ type SessionPersistenceContext,
26
+ type SessionRegistry,
27
+ type SessionSelection,
28
+ type SessionStatus,
29
+ SessionBusyError,
30
+ sessionResourceUrl,
31
+ sessionScope,
32
+ sessionScopeKey,
33
+ toChannelStore,
34
+ } from './store.js'
35
+
36
+ /** CLI options needed to run a persistent Tempo session request. */
37
+ export type PersistentSessionRequestOptions = {
38
+ account?: string | undefined
39
+ fail?: boolean | undefined
40
+ include?: boolean | undefined
41
+ network?: Network | undefined
42
+ rpcUrl?: string | undefined
43
+ session: string
44
+ silent: boolean
45
+ verbose: number
46
+ }
47
+
48
+ /** Inputs for a persistent request after challenge selection and confirmation. */
49
+ export type PersistentSessionRequestParameters = {
50
+ challenge: TempoSessionChallenge
51
+ challengeResponse: Response
52
+ endpoint: string
53
+ fetch: typeof globalThis.fetch
54
+ fetchInput: RequestInfo | URL
55
+ init: RequestInit
56
+ info(message: string): void
57
+ methodOptions: Record<string, string>
58
+ options: PersistentSessionRequestOptions
59
+ registry?: SessionRegistry | undefined
60
+ }
61
+
62
+ /** Resolves `--session` and the `-M channel=` compatibility alias. */
63
+ export function resolveSessionSelection(
64
+ session: string,
65
+ channelAlias: string | undefined,
66
+ ): SessionSelection {
67
+ if (channelAlias && session !== 'auto' && channelAlias.toLowerCase() !== session.toLowerCase())
68
+ throw new Errors.IncurError({
69
+ code: 'SESSION_SELECTION_CONFLICT',
70
+ message: '--session and -M channel= select different sessions.',
71
+ exitCode: 2,
72
+ })
73
+ const value = channelAlias ?? session
74
+ if (value === 'auto' || value === 'new') return value
75
+ if (/^0x[0-9a-fA-F]{64}$/.test(value)) return value.toLowerCase() as Hex.Hex
76
+ throw new Errors.IncurError({
77
+ code: 'INVALID_SESSION',
78
+ message: 'Session must be auto, new, or a 32-byte channel ID.',
79
+ exitCode: 2,
80
+ })
81
+ }
82
+
83
+ function sessionDecimals(challenge: Challenge.Challenge): number {
84
+ return typeof challenge.request.decimals === 'number' ? challenge.request.decimals : 6
85
+ }
86
+
87
+ /** @internal Resolves the manager deposit cap in human-readable token units. */
88
+ export function resolveSessionMaxDeposit(
89
+ challenge: Challenge.Challenge,
90
+ methodOptions: Record<string, string>,
91
+ testnet: boolean,
92
+ ): string | undefined {
93
+ if (methodOptions.deposit !== undefined) return methodOptions.deposit
94
+ const suggested = challenge.request.suggestedDeposit
95
+ if (typeof suggested === 'string')
96
+ return formatUnits(BigInt(suggested), sessionDecimals(challenge))
97
+ return testnet ? '10' : undefined
98
+ }
99
+
100
+ function validateReceipt(
101
+ receipt: SessionReceipt | null | undefined,
102
+ parameters: {
103
+ challenge: Challenge.Challenge
104
+ channelId: string
105
+ cumulative: bigint
106
+ },
107
+ ): SessionReceipt | undefined {
108
+ if (!receipt) return undefined
109
+ if (receipt.challengeId !== parameters.challenge.id)
110
+ throw new Error('Session receipt challenge does not match the paid request.')
111
+ if (receipt.channelId.toLowerCase() !== parameters.channelId.toLowerCase())
112
+ throw new Error('Session receipt channel does not match the selected channel.')
113
+ const accepted = BigInt(receipt.acceptedCumulative)
114
+ const spent = BigInt(receipt.spent)
115
+ if (accepted > parameters.cumulative)
116
+ throw new Error('Session receipt exceeds the local cumulative authorization.')
117
+ if (spent > accepted) throw new Error('Session receipt spend exceeds its accepted authorization.')
118
+ return receipt
119
+ }
120
+
121
+ function sameChannel(record: ManagedSession, channelId: string): boolean {
122
+ return record.channel.channelId.toLowerCase() === channelId.toLowerCase()
123
+ }
124
+
125
+ function writeSseChunk(chunk: string): void {
126
+ if (chunk.trim() === '[DONE]') return
127
+ if (chunk.length === 0) {
128
+ process.stdout.write('\n')
129
+ return
130
+ }
131
+ try {
132
+ const parsed = JSON.parse(chunk) as {
133
+ token?: string
134
+ choices?: { delta?: { content?: string } }[]
135
+ }
136
+ process.stdout.write(parsed.token ?? parsed.choices?.[0]?.delta?.content ?? chunk)
137
+ } catch {
138
+ process.stdout.write(chunk)
139
+ }
140
+ }
141
+
142
+ /** Runs one manager-backed request while holding the payer and payment-scope lock. */
143
+ export async function runPersistentSessionRequest(
144
+ parameters: PersistentSessionRequestParameters,
145
+ ): Promise<void> {
146
+ const { options } = parameters
147
+ const resolvedAccount = await resolvePersistentAccount(options.account)
148
+ const rpcUrl = resolveRpcUrl(options.rpcUrl, { network: options.network })
149
+ const chain = await resolveChain({ network: options.network, rpcUrl })
150
+ const client = createClient({ chain, transport: http(rpcUrl) })
151
+ const challengeContext = await resolveChallengeContext({
152
+ challenge: parameters.challenge,
153
+ getClient: async () => client,
154
+ })
155
+ if (challengeContext.chainId !== chain.id)
156
+ throw new Errors.IncurError({
157
+ code: 'CHAIN_MISMATCH',
158
+ message: `Challenge requires chainId ${challengeContext.chainId}, but RPC is chainId ${chain.id}.`,
159
+ exitCode: 2,
160
+ })
161
+
162
+ const scope = {
163
+ payer: resolvedAccount.account.address,
164
+ payee: challengeContext.payee,
165
+ token: challengeContext.token,
166
+ escrow: challengeContext.escrow,
167
+ chainId: challengeContext.chainId,
168
+ }
169
+ const selection = resolveSessionSelection(options.session, parameters.methodOptions.channel)
170
+ const registry = parameters.registry ?? createSessionRegistry()
171
+ const lock = await registry.acquire(scope).catch((cause: unknown) => {
172
+ if (cause instanceof SessionBusyError)
173
+ throw new Errors.IncurError({
174
+ code: cause.code,
175
+ message: cause.message,
176
+ exitCode: cause.exitCode,
177
+ cause,
178
+ })
179
+ throw cause
180
+ })
181
+
182
+ try {
183
+ const selectedId =
184
+ selection === 'auto'
185
+ ? await registry.getPreferred(scope)
186
+ : selection === 'new'
187
+ ? undefined
188
+ : selection
189
+ const selected = selectedId ? await registry.get(selectedId) : undefined
190
+ if (selectedId && !selected)
191
+ throw new Errors.IncurError({
192
+ code: 'SESSION_NOT_FOUND',
193
+ message: `Session ${selectedId} was not found.`,
194
+ exitCode: 2,
195
+ })
196
+ if (selected && selection !== 'auto') {
197
+ if (
198
+ selected.status !== 'open' ||
199
+ !selected.channel.opened ||
200
+ sessionScopeKey(sessionScope(selected.channel)) !== sessionScopeKey(scope) ||
201
+ !canSignDescriptor(resolvedAccount.account, selected.channel.descriptor)
202
+ )
203
+ throw new Errors.IncurError({
204
+ code: 'SESSION_MISMATCH',
205
+ message: `Session ${selected.channel.channelId} cannot be used for this account and challenge.`,
206
+ exitCode: 2,
207
+ })
208
+ }
209
+
210
+ const reusable = selected?.status === 'open' && selected.channel.opened ? selected : undefined
211
+ let status: SessionStatus = reusable ? 'open' : 'opening'
212
+ let latestChallenge = parameters.challenge
213
+ let latestReceipt = reusable?.receipt
214
+ let spent = reusable?.spent ?? 0n
215
+ let units = reusable?.units ?? 0
216
+ const endpoint = sessionResourceUrl(parameters.endpoint)
217
+ const account = {
218
+ ...(resolvedAccount.source === 'keychain' && { name: resolvedAccount.accountName }),
219
+ address: resolvedAccount.account.address,
220
+ }
221
+ const persistenceContext = (): SessionPersistenceContext => ({
222
+ status,
223
+ account,
224
+ endpoint,
225
+ challenge: latestChallenge,
226
+ ...(latestReceipt && { receipt: latestReceipt }),
227
+ spent,
228
+ units,
229
+ })
230
+ const channelStore = toChannelStore(registry, {
231
+ scope,
232
+ selection,
233
+ context: persistenceContext,
234
+ onNewChannel() {
235
+ status = 'opening'
236
+ latestReceipt = undefined
237
+ spent = 0n
238
+ units = 0
239
+ },
240
+ })
241
+ const { signal, ...baseInit } = parameters.init
242
+ const requestInit = {
243
+ ...baseInit,
244
+ ...(signal && { signal }),
245
+ onReceipt(receipt: SessionReceipt) {
246
+ latestReceipt = receipt
247
+ spent = spent > BigInt(receipt.spent) ? spent : BigInt(receipt.spent)
248
+ units = Math.max(units, receipt.units ?? 0)
249
+ },
250
+ }
251
+ if (parameters.challengeResponse.status !== 402 || parameters.challengeResponse.bodyUsed)
252
+ throw new Error('Session manager requires an unconsumed 402 challenge response.')
253
+ let replayPending = true
254
+ const manager = sessionManager({
255
+ account: resolvedAccount.account,
256
+ bootstrap: false,
257
+ client,
258
+ channelStore,
259
+ decimals: sessionDecimals(parameters.challenge),
260
+ maxDeposit: resolveSessionMaxDeposit(
261
+ parameters.challenge,
262
+ parameters.methodOptions,
263
+ isTestnet(chain),
264
+ ),
265
+ fetch: async (input, init) => {
266
+ if (!replayPending) return parameters.fetch(input, init)
267
+ replayPending = false
268
+ return parameters.challengeResponse
269
+ },
270
+ })
271
+ if (reusable)
272
+ getSessionManagerInternals(manager).rehydrate({
273
+ channel: reusable.channel,
274
+ challenge: parameters.challenge,
275
+ input: parameters.fetchInput,
276
+ spent: reusable.spent,
277
+ })
278
+ const { onReceipt, ...managerInit } = requestInit
279
+ const response = await manager.fetch(parameters.fetchInput, managerInit)
280
+
281
+ if (options.fail && response.status >= 400)
282
+ throw new Errors.IncurError({
283
+ code: 'HTTP_ERROR',
284
+ message: `HTTP error ${response.status}`,
285
+ exitCode: 22,
286
+ })
287
+ if (response.status === 402)
288
+ throw new Errors.IncurError({
289
+ code: 'PAYMENT_REJECTED',
290
+ message: 'Payment rejected.',
291
+ exitCode: 75,
292
+ })
293
+
294
+ printResponseHeaders(response, {
295
+ include: false,
296
+ verbose: options.include ? Math.max(options.verbose, 2) : options.verbose,
297
+ silent: options.silent,
298
+ })
299
+ latestChallenge = response.challenge ?? parameters.challenge
300
+ const channelId = manager.channelId
301
+ if (!channelId) throw new Error('Session manager did not select a channel.')
302
+ latestReceipt =
303
+ validateReceipt(response.receipt, {
304
+ challenge: latestChallenge,
305
+ channelId,
306
+ cumulative: manager.cumulative,
307
+ }) ?? latestReceipt
308
+
309
+ if (isEventStream(response)) {
310
+ const stream = getSessionManagerInternals(manager).consumeSseResponse(
311
+ parameters.fetchInput,
312
+ response,
313
+ { onReceipt, signal: parameters.init.signal ?? undefined },
314
+ )
315
+ for await (const chunk of stream) writeSseChunk(chunk)
316
+ } else {
317
+ process.stdout.write(Buffer.from(await response.arrayBuffer()))
318
+ }
319
+
320
+ const record = await registry.get(channelId)
321
+ if (!record || !sameChannel(record, channelId))
322
+ throw new Error(`Session ${channelId} was not persisted.`)
323
+ if (latestReceipt) {
324
+ const validatedReceipt = validateReceipt(latestReceipt, {
325
+ challenge: latestChallenge,
326
+ channelId,
327
+ cumulative: manager.cumulative,
328
+ })
329
+ if (!validatedReceipt) throw new Error('Session receipt validation failed.')
330
+ latestReceipt = validatedReceipt
331
+ spent = spent > BigInt(validatedReceipt.spent) ? spent : BigInt(validatedReceipt.spent)
332
+ units = Math.max(units, validatedReceipt.units ?? 0)
333
+ status = 'open'
334
+ }
335
+ await registry.upsert({
336
+ status,
337
+ channel: record.channel,
338
+ account,
339
+ endpoint,
340
+ challenge: latestChallenge,
341
+ ...(latestReceipt && { receipt: latestReceipt }),
342
+ spent,
343
+ units,
344
+ })
345
+
346
+ if (!options.silent) {
347
+ parameters.info(`Session retained ${channelId}\n`)
348
+ parameters.info(`Close with: mppx sessions close ${channelId}\n`)
349
+ }
350
+ } finally {
351
+ await lock.release()
352
+ }
353
+ }