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.
- package/CHANGELOG.md +34 -0
- package/README.md +16 -1
- package/dist/Errors.d.ts +10 -0
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +11 -2
- package/dist/Errors.js.map +1 -1
- package/dist/Method.d.ts +105 -3
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +102 -2
- package/dist/Method.js.map +1 -1
- package/dist/cli/account.d.ts +88 -0
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/account.js +39 -9
- package/dist/cli/account.js.map +1 -1
- package/dist/cli/cli.d.ts +2 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +59 -2
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/sessions/Manager.d.ts +31 -0
- package/dist/cli/sessions/Manager.d.ts.map +1 -0
- package/dist/cli/sessions/Manager.js +56 -0
- package/dist/cli/sessions/Manager.js.map +1 -0
- package/dist/cli/sessions/commands.d.ts +36 -0
- package/dist/cli/sessions/commands.d.ts.map +1 -0
- package/dist/cli/sessions/commands.js +407 -0
- package/dist/cli/sessions/commands.js.map +1 -0
- package/dist/cli/sessions/request.d.ts +35 -0
- package/dist/cli/sessions/request.d.ts.map +1 -0
- package/dist/cli/sessions/request.js +271 -0
- package/dist/cli/sessions/request.js.map +1 -0
- package/dist/cli/sessions/store.d.ts +134 -0
- package/dist/cli/sessions/store.d.ts.map +1 -0
- package/dist/cli/sessions/store.js +723 -0
- package/dist/cli/sessions/store.js.map +1 -0
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +12 -0
- package/dist/cli/utils.js.map +1 -1
- package/dist/cli/validate/index.d.ts.map +1 -1
- package/dist/cli/validate/index.js +12 -6
- package/dist/cli/validate/index.js.map +1 -1
- package/dist/cli/validate/messages.d.ts +2 -0
- package/dist/cli/validate/messages.d.ts.map +1 -0
- package/dist/cli/validate/messages.js +8 -0
- package/dist/cli/validate/messages.js.map +1 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +12 -2
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/MethodChallenge.d.ts +17 -0
- package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
- package/dist/client/internal/MethodChallenge.js +11 -0
- package/dist/client/internal/MethodChallenge.js.map +1 -0
- package/dist/client/node.d.ts +29 -0
- package/dist/client/node.d.ts.map +1 -0
- package/dist/client/node.js +177 -0
- package/dist/client/node.js.map +1 -0
- package/dist/internal/types.d.ts +24 -0
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/server/Mppx.d.ts +23 -4
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +91 -82
- package/dist/server/Mppx.js.map +1 -1
- package/dist/stripe/server/Charge.d.ts +8 -4
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +2 -1
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts +1 -0
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +7 -2
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/internal/types.d.ts +10 -1
- package/dist/tempo/internal/types.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.js +2 -1
- package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
- package/dist/tempo/legacy/client/Session.d.ts +2 -2
- package/dist/tempo/legacy/server/Session.d.ts +3 -3
- package/dist/tempo/legacy/server/Session.d.ts.map +1 -1
- package/dist/tempo/legacy/server/Session.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts +40 -78
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +289 -209
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +13 -23
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +6 -2
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Relay.d.ts +48 -0
- package/dist/tempo/server/Relay.d.ts.map +1 -0
- package/dist/tempo/server/Relay.js +177 -0
- package/dist/tempo/server/Relay.js.map +1 -0
- package/dist/tempo/server/SponsorBudget.d.ts +59 -0
- package/dist/tempo/server/SponsorBudget.d.ts.map +1 -0
- package/dist/tempo/server/SponsorBudget.js +144 -0
- package/dist/tempo/server/SponsorBudget.js.map +1 -0
- package/dist/tempo/server/Subscription.d.ts +3 -2
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js.map +1 -1
- package/dist/tempo/server/index.d.ts +1 -1
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/session/Snapshot.d.ts +9 -0
- package/dist/tempo/session/Snapshot.d.ts.map +1 -1
- package/dist/tempo/session/Snapshot.js +6 -0
- package/dist/tempo/session/Snapshot.js.map +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +7 -1
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +17 -3
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +26 -2
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +99 -16
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Runtime.d.ts +8 -0
- package/dist/tempo/session/client/Runtime.d.ts.map +1 -1
- package/dist/tempo/session/client/Runtime.js +17 -0
- package/dist/tempo/session/client/Runtime.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +16 -6
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +93 -36
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +10 -3
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +176 -50
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +18 -7
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +81 -33
- package/dist/tempo/session/client/Transports.js.map +1 -1
- package/dist/tempo/session/client/internal/SessionManager.d.ts +19 -0
- package/dist/tempo/session/client/internal/SessionManager.d.ts.map +1 -0
- package/dist/tempo/session/client/internal/SessionManager.js +13 -0
- package/dist/tempo/session/client/internal/SessionManager.js.map +1 -0
- package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Chain.js +109 -12
- package/dist/tempo/session/precompile/Chain.js.map +1 -1
- package/dist/tempo/session/precompile/Channel.d.ts +2 -0
- package/dist/tempo/session/precompile/Channel.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Channel.js +4 -0
- package/dist/tempo/session/precompile/Channel.js.map +1 -1
- package/dist/tempo/session/server/CredentialVerification.d.ts +3 -0
- package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
- package/dist/tempo/session/server/CredentialVerification.js +14 -0
- package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
- package/dist/tempo/session/server/MeteredStream.d.ts +7 -1
- package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
- package/dist/tempo/session/server/MeteredStream.js +5 -4
- package/dist/tempo/session/server/MeteredStream.js.map +1 -1
- package/dist/tempo/session/server/RequestState.d.ts +5 -1
- package/dist/tempo/session/server/RequestState.d.ts.map +1 -1
- package/dist/tempo/session/server/RequestState.js +13 -5
- package/dist/tempo/session/server/RequestState.js.map +1 -1
- package/dist/tempo/session/server/Session.d.ts +21 -6
- package/dist/tempo/session/server/Session.d.ts.map +1 -1
- package/dist/tempo/session/server/Session.js +4 -1
- package/dist/tempo/session/server/Session.js.map +1 -1
- package/dist/tempo/session/server/Settlement.d.ts +20 -0
- package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
- package/dist/tempo/session/server/Settlement.js +20 -0
- package/dist/tempo/session/server/Settlement.js.map +1 -1
- package/dist/tempo/session/server/Ws.d.ts +2 -0
- package/dist/tempo/session/server/Ws.d.ts.map +1 -1
- package/dist/tempo/session/server/Ws.js.map +1 -1
- package/dist/tempo/session/server/index.d.ts +1 -1
- package/dist/tempo/session/server/index.d.ts.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +21 -21
- package/dist/validation/core.d.ts.map +1 -1
- package/dist/validation/core.js +6 -2
- package/dist/validation/core.js.map +1 -1
- package/dist/viem/Client.d.ts.map +1 -1
- package/dist/viem/Client.js +24 -17
- package/dist/viem/Client.js.map +1 -1
- package/package.json +6 -1
- package/src/Errors.test.ts +23 -0
- package/src/Errors.ts +21 -2
- package/src/Method.test.ts +102 -1
- package/src/Method.ts +241 -5
- package/src/cli/account.ts +45 -10
- package/src/cli/cli.test.ts +165 -70
- package/src/cli/cli.ts +67 -2
- package/src/cli/mcp.test.ts +11 -0
- package/src/cli/sessions/Manager.test.ts +249 -0
- package/src/cli/sessions/Manager.ts +93 -0
- package/src/cli/sessions/commands.ts +444 -0
- package/src/cli/sessions/request.test.ts +51 -0
- package/src/cli/sessions/request.ts +353 -0
- package/src/cli/sessions/store.test.ts +581 -0
- package/src/cli/sessions/store.ts +940 -0
- package/src/cli/utils.test.ts +23 -0
- package/src/cli/utils.ts +10 -0
- package/src/cli/validate/index.ts +12 -14
- package/src/cli/validate/messages.ts +7 -0
- package/src/cli/validate.test.ts +38 -5
- package/src/client/Mppx.test-d.ts +3 -3
- package/src/client/internal/Fetch.ts +16 -7
- package/src/client/internal/MethodChallenge.ts +30 -0
- package/src/client/node.test.ts +115 -0
- package/src/client/node.ts +247 -0
- package/src/internal/types.test-d.ts +21 -0
- package/src/internal/types.ts +33 -0
- package/src/server/Mppx.test-d.ts +44 -0
- package/src/server/Mppx.test.ts +205 -0
- package/src/server/Mppx.ts +167 -103
- package/src/stripe/Methods.test.ts +10 -0
- package/src/stripe/server/Charge.test-d.ts +66 -0
- package/src/stripe/server/Charge.ts +8 -5
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/PublicExports.test-d.ts +20 -0
- package/src/tempo/internal/fee-payer.ts +7 -3
- package/src/tempo/internal/types.ts +13 -2
- package/src/tempo/legacy/client/ChannelOps.test.ts +7 -0
- package/src/tempo/legacy/client/ChannelOps.ts +2 -1
- package/src/tempo/legacy/server/Defaults.test-d.ts +9 -0
- package/src/tempo/legacy/server/Session.ts +6 -5
- package/src/tempo/server/Charge.test.ts +337 -63
- package/src/tempo/server/Charge.ts +440 -270
- package/src/tempo/server/Methods.ts +16 -4
- package/src/tempo/server/Relay.test.ts +523 -0
- package/src/tempo/server/Relay.ts +288 -0
- package/src/tempo/server/SponsorBudget.test.ts +125 -0
- package/src/tempo/server/SponsorBudget.ts +213 -0
- package/src/tempo/server/Subscription.ts +6 -4
- package/src/tempo/server/index.ts +5 -1
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/README.md +201 -0
- package/src/tempo/session/Snapshot.ts +21 -0
- package/src/tempo/session/client/ChannelOps.test.ts +55 -0
- package/src/tempo/session/client/ChannelOps.ts +21 -3
- package/src/tempo/session/client/CredentialState.test.ts +2 -2
- package/src/tempo/session/client/CredentialState.ts +159 -16
- package/src/tempo/session/client/Runtime.test.ts +12 -0
- package/src/tempo/session/client/Runtime.ts +22 -0
- package/src/tempo/session/client/Session.test.ts +238 -5
- package/src/tempo/session/client/Session.ts +130 -41
- package/src/tempo/session/client/SessionManager.test.ts +239 -8
- package/src/tempo/session/client/SessionManager.ts +204 -54
- package/src/tempo/session/client/Transports.test.ts +181 -8
- package/src/tempo/session/client/Transports.ts +127 -45
- package/src/tempo/session/client/internal/SessionManager.ts +36 -0
- package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
- package/src/tempo/session/precompile/Chain.test.ts +223 -10
- package/src/tempo/session/precompile/Chain.ts +119 -12
- package/src/tempo/session/precompile/Channel.test.ts +9 -0
- package/src/tempo/session/precompile/Channel.ts +5 -0
- package/src/tempo/session/server/CredentialVerification.ts +18 -1
- package/src/tempo/session/server/MeteredStream.ts +12 -5
- package/src/tempo/session/server/RequestState.test.ts +47 -4
- package/src/tempo/session/server/RequestState.ts +15 -8
- package/src/tempo/session/server/Session.test.ts +461 -0
- package/src/tempo/session/server/Session.ts +31 -8
- package/src/tempo/session/server/Settlement.ts +45 -0
- package/src/tempo/session/server/Ws.test.ts +113 -0
- package/src/tempo/session/server/Ws.ts +2 -0
- package/src/tempo/session/server/index.ts +2 -0
- package/src/validation/core.ts +7 -1
- package/src/viem/Client.test.ts +44 -1
- package/src/viem/Client.ts +31 -17
|
@@ -9,6 +9,7 @@ import type { ChannelEntry } from '../client/ChannelOps.js'
|
|
|
9
9
|
import type { SessionContext } from '../client/CredentialState.js'
|
|
10
10
|
import {
|
|
11
11
|
createSessionReceipt,
|
|
12
|
+
formatNeedVoucherEvent,
|
|
12
13
|
serializeSessionReceipt,
|
|
13
14
|
tip20ChannelEscrow,
|
|
14
15
|
type ChannelDescriptor,
|
|
@@ -18,6 +19,7 @@ import { initialState, type SessionState } from './Runtime.js'
|
|
|
18
19
|
import {
|
|
19
20
|
applyTopUpResult,
|
|
20
21
|
closeHttpSession,
|
|
22
|
+
consumeSseSessionResponse,
|
|
21
23
|
createActiveSocketSession,
|
|
22
24
|
driveSseResponse,
|
|
23
25
|
isExpectedSocketReceipt,
|
|
@@ -83,6 +85,7 @@ describe('HttpManagement', () => {
|
|
|
83
85
|
methodDetails: {
|
|
84
86
|
chainId: 4217,
|
|
85
87
|
escrowContract: undefined,
|
|
88
|
+
sessionProtocol: Constants.SessionProtocols.v2,
|
|
86
89
|
...(snapshot && { [Constants.MethodDetailKeys.sessionSnapshot]: snapshot }),
|
|
87
90
|
},
|
|
88
91
|
},
|
|
@@ -112,9 +115,15 @@ describe('HttpManagement', () => {
|
|
|
112
115
|
})
|
|
113
116
|
}
|
|
114
117
|
|
|
115
|
-
function receiptHeader(acceptedCumulative: bigint, spent: bigint) {
|
|
118
|
+
function receiptHeader(acceptedCumulative: bigint, spent: bigint, challengeId = 'challenge-1') {
|
|
116
119
|
return serializeSessionReceipt(
|
|
117
|
-
createSessionReceipt({
|
|
120
|
+
createSessionReceipt({
|
|
121
|
+
acceptedCumulative,
|
|
122
|
+
challengeId,
|
|
123
|
+
channelId,
|
|
124
|
+
spent,
|
|
125
|
+
txHash: `0x${'aa'.repeat(32)}`,
|
|
126
|
+
}),
|
|
118
127
|
)
|
|
119
128
|
}
|
|
120
129
|
|
|
@@ -139,9 +148,12 @@ describe('HttpManagement', () => {
|
|
|
139
148
|
)
|
|
140
149
|
})
|
|
141
150
|
|
|
142
|
-
test('
|
|
151
|
+
test('preserves query state and strips fragments from management URLs', () => {
|
|
143
152
|
expect(managementInput('https://example.test/resource?cursor=abc').toString()).toBe(
|
|
144
|
-
'https://example.test/resource',
|
|
153
|
+
'https://example.test/resource?cursor=abc',
|
|
154
|
+
)
|
|
155
|
+
expect(managementInput('https://example.test/resource?cursor=abc#result').toString()).toBe(
|
|
156
|
+
'https://example.test/resource?cursor=abc',
|
|
145
157
|
)
|
|
146
158
|
})
|
|
147
159
|
|
|
@@ -185,7 +197,7 @@ describe('HttpManagement', () => {
|
|
|
185
197
|
return 'top-up-credential'
|
|
186
198
|
})
|
|
187
199
|
const fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
188
|
-
expect(input.toString()).toBe('https://example.test/resource')
|
|
200
|
+
expect(input.toString()).toBe('https://example.test/resource?cursor=abc')
|
|
189
201
|
expect(init?.method).toBe('POST')
|
|
190
202
|
expect(authorizationHeader(init)).toBe('top-up-credential')
|
|
191
203
|
return new Response(null, {
|
|
@@ -209,6 +221,36 @@ describe('HttpManagement', () => {
|
|
|
209
221
|
expect(fetch).toHaveBeenCalledOnce()
|
|
210
222
|
})
|
|
211
223
|
|
|
224
|
+
test('postTopUp retries once with the management route challenge', async () => {
|
|
225
|
+
const routeChallenge = { ...challenge(), id: 'management-challenge' } as TempoSessionChallenge
|
|
226
|
+
const createSessionCredential = vi.fn(async (challenge_) => `top-up-${challenge_.id}`)
|
|
227
|
+
const fetch = vi
|
|
228
|
+
.fn()
|
|
229
|
+
.mockResolvedValueOnce(response402(routeChallenge))
|
|
230
|
+
.mockResolvedValueOnce(
|
|
231
|
+
new Response(null, {
|
|
232
|
+
status: 204,
|
|
233
|
+
headers: {
|
|
234
|
+
[Constants.Headers.paymentReceipt]: receiptHeader(8n, 5n, routeChallenge.id),
|
|
235
|
+
},
|
|
236
|
+
}),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
const receipt = await postTopUp({
|
|
240
|
+
additionalDeposit: 3n,
|
|
241
|
+
challenge: challenge(),
|
|
242
|
+
channel: channel(),
|
|
243
|
+
channelId,
|
|
244
|
+
createSessionCredential,
|
|
245
|
+
fetch,
|
|
246
|
+
input: 'https://example.test/resource',
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
expect(receipt?.challengeId).toBe(routeChallenge.id)
|
|
250
|
+
expect(createSessionCredential).toHaveBeenCalledTimes(2)
|
|
251
|
+
expect(authorizationHeader(fetch.mock.calls[1]?.[1])).toBe(`top-up-${routeChallenge.id}`)
|
|
252
|
+
})
|
|
253
|
+
|
|
212
254
|
test('retryHttpPaymentRequired signs the server-required cumulative voucher', async () => {
|
|
213
255
|
let entry = channel({ cumulativeAmount: 5n, deposit: 6n })
|
|
214
256
|
const topUpIfNeeded = vi.fn(async () => {
|
|
@@ -301,16 +343,20 @@ describe('HttpManagement', () => {
|
|
|
301
343
|
const entry = channel()
|
|
302
344
|
const retryChallenge = { ...challenge(), id: 'challenge-2' } as TempoSessionChallenge
|
|
303
345
|
const setChallenge = vi.fn()
|
|
304
|
-
const
|
|
346
|
+
const closeAmounts: string[] = []
|
|
347
|
+
const createSessionCredential = vi.fn(async (challenge_, context: SessionContext) => {
|
|
348
|
+
if (context.action !== 'close') throw new Error('expected close context')
|
|
349
|
+
closeAmounts.push(context.cumulativeAmountRaw!)
|
|
305
350
|
return `close-${challenge_.id}`
|
|
306
351
|
})
|
|
352
|
+
const resolveSignedCloseAmount = vi.fn(() => '6')
|
|
307
353
|
const fetch = vi
|
|
308
354
|
.fn()
|
|
309
355
|
.mockResolvedValueOnce(response402(retryChallenge))
|
|
310
356
|
.mockResolvedValueOnce(
|
|
311
357
|
new Response(null, {
|
|
312
358
|
status: 200,
|
|
313
|
-
headers: { [Constants.Headers.paymentReceipt]: receiptHeader(
|
|
359
|
+
headers: { [Constants.Headers.paymentReceipt]: receiptHeader(6n, 6n, 'challenge-2') },
|
|
314
360
|
}),
|
|
315
361
|
)
|
|
316
362
|
|
|
@@ -318,18 +364,71 @@ describe('HttpManagement', () => {
|
|
|
318
364
|
createSessionCredential,
|
|
319
365
|
fetch,
|
|
320
366
|
lastUrl: 'https://example.test/resource',
|
|
367
|
+
resolveSignedCloseAmount,
|
|
321
368
|
signedCloseAmount: '5',
|
|
322
369
|
setChallenge,
|
|
323
370
|
target: { challenge: challenge(), channel: entry, channelId },
|
|
324
371
|
})
|
|
325
372
|
|
|
326
|
-
expect(receipt?.spent).toBe('
|
|
373
|
+
expect(receipt?.spent).toBe('6')
|
|
327
374
|
expect(setChallenge).toHaveBeenCalledWith(retryChallenge)
|
|
375
|
+
expect(resolveSignedCloseAmount).toHaveBeenCalledOnce()
|
|
376
|
+
expect(resolveSignedCloseAmount).toHaveBeenCalledWith(retryChallenge)
|
|
377
|
+
expect(closeAmounts).toEqual(['5', '6'])
|
|
328
378
|
expect(createSessionCredential).toHaveBeenCalledTimes(2)
|
|
329
379
|
expect(createSessionCredential.mock.calls[1]?.[0]).toMatchObject({ id: retryChallenge.id })
|
|
330
380
|
expect(authorizationHeader(fetch.mock.calls[1]?.[1])).toBe('close-challenge-2')
|
|
331
381
|
})
|
|
332
382
|
|
|
383
|
+
test('closeHttpSession rejects a receipt without settlement proof', async () => {
|
|
384
|
+
const receipt = serializeSessionReceipt(
|
|
385
|
+
createSessionReceipt({
|
|
386
|
+
acceptedCumulative: 5n,
|
|
387
|
+
challengeId: 'challenge-1',
|
|
388
|
+
channelId,
|
|
389
|
+
spent: 5n,
|
|
390
|
+
}),
|
|
391
|
+
)
|
|
392
|
+
await expect(
|
|
393
|
+
closeHttpSession({
|
|
394
|
+
createSessionCredential: async () => 'close-credential',
|
|
395
|
+
fetch: async () =>
|
|
396
|
+
new Response(null, {
|
|
397
|
+
headers: { [Constants.Headers.paymentReceipt]: receipt },
|
|
398
|
+
}),
|
|
399
|
+
lastUrl: 'https://example.test/resource',
|
|
400
|
+
signedCloseAmount: '5',
|
|
401
|
+
target: { challenge: challenge(), channel: channel(), channelId },
|
|
402
|
+
}),
|
|
403
|
+
).rejects.toThrow('Session close response included a mismatched payment receipt.')
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
test('closeHttpSession rejects an invalid settlement transaction hash', async () => {
|
|
407
|
+
for (const txHash of ['0x1234', `0x${'gg'.repeat(32)}`]) {
|
|
408
|
+
const receipt = serializeSessionReceipt(
|
|
409
|
+
createSessionReceipt({
|
|
410
|
+
acceptedCumulative: 5n,
|
|
411
|
+
challengeId: 'challenge-1',
|
|
412
|
+
channelId,
|
|
413
|
+
spent: 5n,
|
|
414
|
+
txHash: txHash as Hex.Hex,
|
|
415
|
+
}),
|
|
416
|
+
)
|
|
417
|
+
await expect(
|
|
418
|
+
closeHttpSession({
|
|
419
|
+
createSessionCredential: async () => 'close-credential',
|
|
420
|
+
fetch: async () =>
|
|
421
|
+
new Response(null, {
|
|
422
|
+
headers: { [Constants.Headers.paymentReceipt]: receipt },
|
|
423
|
+
}),
|
|
424
|
+
lastUrl: 'https://example.test/resource',
|
|
425
|
+
signedCloseAmount: '5',
|
|
426
|
+
target: { challenge: challenge(), channel: channel(), channelId },
|
|
427
|
+
}),
|
|
428
|
+
).rejects.toThrow('Session close response included a mismatched payment receipt.')
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
|
|
333
432
|
test('closeHttpSession includes problem detail and challenge header on failure', async () => {
|
|
334
433
|
await expect(
|
|
335
434
|
closeHttpSession({
|
|
@@ -747,6 +846,79 @@ describe('SseDriver', () => {
|
|
|
747
846
|
await vi.waitFor(() => expect(cancel).toHaveBeenCalledOnce())
|
|
748
847
|
},
|
|
749
848
|
)
|
|
849
|
+
|
|
850
|
+
test('keeps voucher events bound to the challenge that opened the stream', async () => {
|
|
851
|
+
const channelId = `0x${'01'.repeat(32)}` as Hex.Hex
|
|
852
|
+
const token = '0x20c0000000000000000000000000000000000001' as Address
|
|
853
|
+
const payee = '0x0000000000000000000000000000000000000002' as Address
|
|
854
|
+
const descriptor: ChannelDescriptor = {
|
|
855
|
+
authorizedSigner: '0x0000000000000000000000000000000000000001',
|
|
856
|
+
expiringNonceHash: `0x${'03'.repeat(32)}` as Hex.Hex,
|
|
857
|
+
operator: '0x0000000000000000000000000000000000000000',
|
|
858
|
+
payee,
|
|
859
|
+
payer: '0x0000000000000000000000000000000000000001',
|
|
860
|
+
salt: `0x${'02'.repeat(32)}` as Hex.Hex,
|
|
861
|
+
token,
|
|
862
|
+
}
|
|
863
|
+
const makeChallenge = (id: string) =>
|
|
864
|
+
Challenge.from({
|
|
865
|
+
id,
|
|
866
|
+
intent: Constants.Intents.session,
|
|
867
|
+
method: Constants.Methods.tempo,
|
|
868
|
+
realm: 'example.test',
|
|
869
|
+
request: {
|
|
870
|
+
amount: '1',
|
|
871
|
+
currency: token,
|
|
872
|
+
methodDetails: { chainId: 4217, escrowContract: tip20ChannelEscrow },
|
|
873
|
+
recipient: payee,
|
|
874
|
+
unitType: 'request',
|
|
875
|
+
},
|
|
876
|
+
}) as TempoSessionChallenge
|
|
877
|
+
const streamChallenge = makeChallenge('challenge-1')
|
|
878
|
+
let currentChallenge = streamChallenge
|
|
879
|
+
const createSessionCredential = vi.fn(async () => 'Payment credential')
|
|
880
|
+
const channel: ChannelEntry = {
|
|
881
|
+
chainId: 4217,
|
|
882
|
+
channelId,
|
|
883
|
+
cumulativeAmount: 1n,
|
|
884
|
+
deposit: 10n,
|
|
885
|
+
descriptor,
|
|
886
|
+
escrow: tip20ChannelEscrow,
|
|
887
|
+
opened: true,
|
|
888
|
+
}
|
|
889
|
+
const response = new Response(
|
|
890
|
+
formatNeedVoucherEvent({
|
|
891
|
+
acceptedCumulative: '1',
|
|
892
|
+
channelId,
|
|
893
|
+
deposit: '10',
|
|
894
|
+
requiredCumulative: '2',
|
|
895
|
+
}),
|
|
896
|
+
{ headers: { 'Content-Type': 'text/event-stream' } },
|
|
897
|
+
)
|
|
898
|
+
const stream = consumeSseSessionResponse('https://example.test/stream', response, undefined, {
|
|
899
|
+
acceptReceipt() {},
|
|
900
|
+
assertVoucherWithinLocalLimit() {},
|
|
901
|
+
createSessionCredential,
|
|
902
|
+
async doFetch() {
|
|
903
|
+
throw new Error('unexpected resource fetch')
|
|
904
|
+
},
|
|
905
|
+
fetch: vi.fn(async () => new Response(null, { status: 204 })),
|
|
906
|
+
getChallenge: () => currentChallenge,
|
|
907
|
+
getChannel: () => channel,
|
|
908
|
+
async topUpIfNeeded() {},
|
|
909
|
+
})
|
|
910
|
+
|
|
911
|
+
currentChallenge = makeChallenge('challenge-2')
|
|
912
|
+
const frames: string[] = []
|
|
913
|
+
for await (const frame of stream) frames.push(frame)
|
|
914
|
+
|
|
915
|
+
expect(frames).toEqual([])
|
|
916
|
+
expect(createSessionCredential).toHaveBeenCalledOnce()
|
|
917
|
+
expect(createSessionCredential).toHaveBeenCalledWith(
|
|
918
|
+
streamChallenge,
|
|
919
|
+
expect.objectContaining({ action: 'voucher', cumulativeAmountRaw: '2' }),
|
|
920
|
+
)
|
|
921
|
+
})
|
|
750
922
|
})
|
|
751
923
|
|
|
752
924
|
describe('WsDriver', () => {
|
|
@@ -869,6 +1041,7 @@ describe('WsDriver', () => {
|
|
|
869
1041
|
methodDetails: {
|
|
870
1042
|
chainId: 4217,
|
|
871
1043
|
escrowContract: tip20ChannelEscrow,
|
|
1044
|
+
sessionProtocol: Constants.SessionProtocols.v2,
|
|
872
1045
|
},
|
|
873
1046
|
recipient: '0x742d35cc6634c0532925a3b844bc9e7595f8fe00',
|
|
874
1047
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Hex } from 'ox'
|
|
2
2
|
|
|
3
3
|
import * as Challenge from '../../../Challenge.js'
|
|
4
4
|
import * as Fetch from '../../../client/internal/Fetch.js'
|
|
@@ -268,6 +268,8 @@ export type ApplyTopUpResultParameters = {
|
|
|
268
268
|
challengeId?: string | undefined
|
|
269
269
|
/** Current active machine state, used to preserve paid unit count when possible. */
|
|
270
270
|
currentState: SessionState
|
|
271
|
+
/** Highest deposit known before the top-up. */
|
|
272
|
+
knownDeposit?: bigint | undefined
|
|
271
273
|
/** Receipt returned by the top-up POST, when present. */
|
|
272
274
|
receipt?: SessionReceipt | undefined
|
|
273
275
|
/** Latest locally observed spend in raw units. */
|
|
@@ -362,26 +364,36 @@ export function resolveManualTopUp(parameters: ResolveManualTopUpParameters): Ma
|
|
|
362
364
|
/**
|
|
363
365
|
* Applies local deposit and state bookkeeping for a top-up response.
|
|
364
366
|
*
|
|
365
|
-
*
|
|
366
|
-
* update accepted spend authorization; they do not replace deposit.
|
|
367
|
+
* Deposit advances from the highest known baseline. Receipt cumulative values
|
|
368
|
+
* only update accepted spend authorization; they do not replace deposit.
|
|
367
369
|
*/
|
|
368
370
|
export function applyTopUpResult(
|
|
369
371
|
parameters: ApplyTopUpResultParameters,
|
|
370
372
|
): AppliedTopUpResult | undefined {
|
|
371
|
-
const {
|
|
372
|
-
|
|
373
|
+
const {
|
|
374
|
+
additionalDeposit,
|
|
375
|
+
channel,
|
|
376
|
+
channelId,
|
|
377
|
+
challengeId,
|
|
378
|
+
currentState,
|
|
379
|
+
knownDeposit,
|
|
380
|
+
receipt,
|
|
381
|
+
spent,
|
|
382
|
+
} = parameters
|
|
373
383
|
if (channel?.channelId !== channelId) return undefined
|
|
374
384
|
|
|
375
|
-
|
|
385
|
+
const baseline =
|
|
386
|
+
knownDeposit !== undefined && knownDeposit > channel.deposit ? knownDeposit : channel.deposit
|
|
387
|
+
const updated = { ...channel, deposit: baseline + additionalDeposit }
|
|
376
388
|
|
|
377
|
-
if (receipt) return { channel, state: activeStateFromReceipt(receipt,
|
|
378
|
-
if (!challengeId) return { channel }
|
|
389
|
+
if (receipt) return { channel: updated, state: activeStateFromReceipt(receipt, updated) }
|
|
390
|
+
if (!challengeId) return { channel: updated }
|
|
379
391
|
|
|
380
392
|
return {
|
|
381
|
-
channel,
|
|
393
|
+
channel: updated,
|
|
382
394
|
state: activeStateFromChannel({
|
|
383
395
|
challengeId,
|
|
384
|
-
entry:
|
|
396
|
+
entry: updated,
|
|
385
397
|
spent: spent.toString(),
|
|
386
398
|
units: currentState.status === 'active' ? currentState.units : 0,
|
|
387
399
|
}),
|
|
@@ -502,6 +514,8 @@ export type CloseHttpSessionParameters = {
|
|
|
502
514
|
fetch: typeof globalThis.fetch
|
|
503
515
|
/** Last paid resource URL; used as the management endpoint base. */
|
|
504
516
|
lastUrl: RequestInfo | URL | null
|
|
517
|
+
/** Resolves the close amount again when the server refreshes the challenge. */
|
|
518
|
+
resolveSignedCloseAmount?: ((challenge: TempoSessionChallenge) => string) | undefined
|
|
505
519
|
/** Final cumulative amount the client is willing to sign. */
|
|
506
520
|
signedCloseAmount: string
|
|
507
521
|
/** Stores a fresh close challenge when the first close credential expired. */
|
|
@@ -519,6 +533,19 @@ export function isTempoSessionChallenge(
|
|
|
519
533
|
)
|
|
520
534
|
}
|
|
521
535
|
|
|
536
|
+
/** Returns true when a challenge selects the TIP-1034 session protocol. */
|
|
537
|
+
export function isTip1034SessionChallenge(
|
|
538
|
+
challenge: Challenge.Challenge,
|
|
539
|
+
): challenge is TempoSessionChallenge {
|
|
540
|
+
return (
|
|
541
|
+
isTempoSessionChallenge(challenge) &&
|
|
542
|
+
Constants.getMethodDetail(
|
|
543
|
+
challenge.request.methodDetails,
|
|
544
|
+
Constants.MethodDetailKeys.sessionProtocol,
|
|
545
|
+
) === Constants.SessionProtocols.v2
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
|
|
522
549
|
/** Reads a server-provided session snapshot from challenge method details. */
|
|
523
550
|
export function getSessionSnapshot(challenge: TempoSessionChallenge): SessionSnapshot | undefined {
|
|
524
551
|
return Constants.getMethodDetail<SessionSnapshot>(
|
|
@@ -544,7 +571,7 @@ export function requestInitWithAuthorization(
|
|
|
544
571
|
}
|
|
545
572
|
}
|
|
546
573
|
|
|
547
|
-
/** Returns the URL used for out-of-band management POSTs,
|
|
574
|
+
/** Returns the exact resource URL used for out-of-band management POSTs, without its fragment. */
|
|
548
575
|
export function managementInput(input: RequestInfo | URL): RequestInfo | URL {
|
|
549
576
|
try {
|
|
550
577
|
const base =
|
|
@@ -555,7 +582,7 @@ export function managementInput(input: RequestInfo | URL): RequestInfo | URL {
|
|
|
555
582
|
: input instanceof URL
|
|
556
583
|
? new URL(input)
|
|
557
584
|
: new URL(String(input), base)
|
|
558
|
-
url.
|
|
585
|
+
url.hash = ''
|
|
559
586
|
return url
|
|
560
587
|
} catch {
|
|
561
588
|
return input
|
|
@@ -592,15 +619,17 @@ export async function postTopUp(
|
|
|
592
619
|
throw new Error('Cannot top up session: no local channel descriptor available.')
|
|
593
620
|
}
|
|
594
621
|
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
622
|
+
const response = await postManagementCredential({
|
|
623
|
+
challenge: parameters.challenge,
|
|
624
|
+
context: {
|
|
625
|
+
action: 'topUp',
|
|
626
|
+
channelId,
|
|
627
|
+
descriptor: channel.descriptor,
|
|
628
|
+
additionalDepositRaw: parameters.additionalDeposit.toString(),
|
|
629
|
+
},
|
|
630
|
+
createSessionCredential: parameters.createSessionCredential,
|
|
631
|
+
fetch: parameters.fetch,
|
|
632
|
+
input: parameters.input,
|
|
604
633
|
})
|
|
605
634
|
if (!response.ok) throw new Error(`Top-up POST failed with status ${response.status}`)
|
|
606
635
|
|
|
@@ -608,6 +637,30 @@ export async function postTopUp(
|
|
|
608
637
|
return receiptHeader ? deserializeSessionReceipt(receiptHeader) : undefined
|
|
609
638
|
}
|
|
610
639
|
|
|
640
|
+
/** Posts a management credential and retries once with a refreshed TIP-1034 challenge. */
|
|
641
|
+
async function postManagementCredential(parameters: {
|
|
642
|
+
challenge: TempoSessionChallenge
|
|
643
|
+
context: SessionContext
|
|
644
|
+
createSessionCredential: CreateSessionCredential
|
|
645
|
+
fetch: typeof globalThis.fetch
|
|
646
|
+
input: RequestInfo | URL
|
|
647
|
+
}): Promise<Response> {
|
|
648
|
+
const post = async (challenge: TempoSessionChallenge) =>
|
|
649
|
+
parameters.fetch(managementInput(parameters.input), {
|
|
650
|
+
method: 'POST',
|
|
651
|
+
headers: {
|
|
652
|
+
[Constants.Headers.authorization]: await parameters.createSessionCredential(
|
|
653
|
+
challenge,
|
|
654
|
+
parameters.context,
|
|
655
|
+
),
|
|
656
|
+
},
|
|
657
|
+
})
|
|
658
|
+
const response = await post(parameters.challenge)
|
|
659
|
+
if (response.status !== 402) return response
|
|
660
|
+
const challenge = Challenge.fromResponseList(response).find(isTip1034SessionChallenge)
|
|
661
|
+
return challenge ? post(challenge) : response
|
|
662
|
+
}
|
|
663
|
+
|
|
611
664
|
/** Retries an HTTP 402 when the server snapshot asks for more voucher headroom. */
|
|
612
665
|
export async function retryHttpPaymentRequired(
|
|
613
666
|
parameters: RetryHttpPaymentRequiredParameters,
|
|
@@ -660,7 +713,7 @@ export function resolveRetryHttpPaymentContext(parameters: {
|
|
|
660
713
|
channel: ChannelEntry | null
|
|
661
714
|
response: Response
|
|
662
715
|
}): RetryHttpPaymentContext | undefined {
|
|
663
|
-
const challenge = Challenge.fromResponseList(parameters.response).find(
|
|
716
|
+
const challenge = Challenge.fromResponseList(parameters.response).find(isTip1034SessionChallenge)
|
|
664
717
|
if (!challenge) return undefined
|
|
665
718
|
|
|
666
719
|
const snapshot = getSessionSnapshot(challenge)
|
|
@@ -682,12 +735,18 @@ export async function closeHttpSession(
|
|
|
682
735
|
)
|
|
683
736
|
}
|
|
684
737
|
|
|
685
|
-
|
|
738
|
+
let closeChallenge = parameters.target.challenge
|
|
739
|
+
let signedCloseAmount = parameters.signedCloseAmount
|
|
740
|
+
const postClose = async (challenge: TempoSessionChallenge, refreshed = false) => {
|
|
741
|
+
closeChallenge = challenge
|
|
742
|
+
signedCloseAmount =
|
|
743
|
+
(refreshed ? parameters.resolveSignedCloseAmount?.(challenge) : undefined) ??
|
|
744
|
+
parameters.signedCloseAmount
|
|
686
745
|
const credential = await parameters.createSessionCredential(challenge, {
|
|
687
746
|
action: 'close',
|
|
688
747
|
channelId: parameters.target.channelId,
|
|
689
748
|
descriptor: parameters.target.channel.descriptor,
|
|
690
|
-
cumulativeAmountRaw:
|
|
749
|
+
cumulativeAmountRaw: signedCloseAmount,
|
|
691
750
|
})
|
|
692
751
|
return parameters.fetch(parameters.lastUrl!, {
|
|
693
752
|
method: 'POST',
|
|
@@ -697,10 +756,10 @@ export async function closeHttpSession(
|
|
|
697
756
|
|
|
698
757
|
let response = await postClose(parameters.target.challenge)
|
|
699
758
|
if (response.status === 402) {
|
|
700
|
-
const challenge = Challenge.fromResponseList(response).find(
|
|
759
|
+
const challenge = Challenge.fromResponseList(response).find(isTip1034SessionChallenge)
|
|
701
760
|
if (challenge) {
|
|
702
761
|
parameters.setChallenge?.(challenge)
|
|
703
|
-
response = await postClose(challenge)
|
|
762
|
+
response = await postClose(challenge, true)
|
|
704
763
|
}
|
|
705
764
|
}
|
|
706
765
|
if (!response.ok) {
|
|
@@ -712,7 +771,21 @@ export async function closeHttpSession(
|
|
|
712
771
|
}
|
|
713
772
|
|
|
714
773
|
const receiptHeader = response.headers.get(Constants.Headers.paymentReceipt)
|
|
715
|
-
|
|
774
|
+
if (!receiptHeader) return undefined
|
|
775
|
+
const receipt = deserializeSessionReceipt(receiptHeader)
|
|
776
|
+
if (
|
|
777
|
+
!receipt.txHash ||
|
|
778
|
+
!Hex.validate(receipt.txHash, { strict: true }) ||
|
|
779
|
+
Hex.size(receipt.txHash) !== 32 ||
|
|
780
|
+
!isExpectedCloseReceipt({
|
|
781
|
+
challengeId: closeChallenge.id,
|
|
782
|
+
channelId: parameters.target.channelId,
|
|
783
|
+
expectedCloseAmount: signedCloseAmount,
|
|
784
|
+
receipt,
|
|
785
|
+
})
|
|
786
|
+
)
|
|
787
|
+
throw new Error('Session close response included a mismatched payment receipt.')
|
|
788
|
+
return receipt
|
|
716
789
|
}
|
|
717
790
|
|
|
718
791
|
/** Options accepted by the auto-driving SSE session flow. */
|
|
@@ -723,6 +796,12 @@ export type SseDriverOptions = RequestInit & {
|
|
|
723
796
|
signal?: AbortSignal | undefined
|
|
724
797
|
}
|
|
725
798
|
|
|
799
|
+
/** @internal Options used when consuming an already-paid SSE response. */
|
|
800
|
+
export type SseResponseOptions = {
|
|
801
|
+
onReceipt?: ((receipt: SessionReceipt) => void) | undefined
|
|
802
|
+
signal?: AbortSignal | undefined
|
|
803
|
+
}
|
|
804
|
+
|
|
726
805
|
/** Dependencies the SSE driver needs from `SessionManager`. */
|
|
727
806
|
export type OpenSseSessionParameters = {
|
|
728
807
|
/** Creates a session credential for the selected challenge/context. */
|
|
@@ -740,8 +819,6 @@ export type OpenSseSessionParameters = {
|
|
|
740
819
|
getChallenge(): TempoSessionChallenge | null
|
|
741
820
|
/** Validates a cumulative amount against local client policy. */
|
|
742
821
|
assertVoucherWithinLocalLimit(cumulativeAmount: bigint): void
|
|
743
|
-
/** Converts a resource URL to the server's session management URL. */
|
|
744
|
-
managementInput(input: RequestInfo | URL): RequestInfo | URL
|
|
745
822
|
/** Applies an incoming receipt to manager state. */
|
|
746
823
|
acceptReceipt(receipt: SessionReceipt): void
|
|
747
824
|
/** Performs an automatic channel top-up when deposit is insufficient. */
|
|
@@ -755,7 +832,6 @@ export type SsePaymentDriver = Pick<
|
|
|
755
832
|
| 'createSessionCredential'
|
|
756
833
|
| 'fetch'
|
|
757
834
|
| 'getChannel'
|
|
758
|
-
| 'managementInput'
|
|
759
835
|
| 'topUpIfNeeded'
|
|
760
836
|
>
|
|
761
837
|
|
|
@@ -784,18 +860,28 @@ export async function openSseSession(
|
|
|
784
860
|
if (!isEventStream(response) && driver.getChannel()?.opened)
|
|
785
861
|
response = await driver.doFetch(input, sseInit)
|
|
786
862
|
|
|
787
|
-
|
|
863
|
+
return consumeSseSessionResponse(input, response, { onReceipt, signal }, driver)
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/** @internal Consumes an already-paid SSE response with the session transport driver. */
|
|
867
|
+
export function consumeSseSessionResponse(
|
|
868
|
+
input: RequestInfo | URL,
|
|
869
|
+
response: Response,
|
|
870
|
+
options: SseResponseOptions | undefined,
|
|
871
|
+
driver: OpenSseSessionParameters,
|
|
872
|
+
): AsyncIterable<string> {
|
|
788
873
|
if (!isEventStream(response)) throw new Error('SSE response is not an event stream.')
|
|
789
874
|
if (!response.body) throw new Error('Response has no body.')
|
|
875
|
+
const challenge = driver.getChallenge()
|
|
790
876
|
|
|
791
877
|
return iterateSseMessages({
|
|
792
878
|
onNeedVoucher: (event) => handleSseNeedVoucher({ challenge, driver, input }, event),
|
|
793
879
|
onReceipt(receipt) {
|
|
794
880
|
driver.acceptReceipt(receipt)
|
|
795
|
-
onReceipt?.(receipt)
|
|
881
|
+
options?.onReceipt?.(receipt)
|
|
796
882
|
},
|
|
797
883
|
response,
|
|
798
|
-
signal,
|
|
884
|
+
signal: options?.signal,
|
|
799
885
|
})
|
|
800
886
|
}
|
|
801
887
|
|
|
@@ -980,17 +1066,13 @@ export async function handleSseNeedVoucher(
|
|
|
980
1066
|
})
|
|
981
1067
|
if (resolution.status !== 'ready') return
|
|
982
1068
|
|
|
983
|
-
const
|
|
984
|
-
parameters.challenge,
|
|
985
|
-
resolution.context,
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
parameters.
|
|
989
|
-
|
|
990
|
-
method: 'POST',
|
|
991
|
-
headers: { [Constants.Headers.authorization]: credential },
|
|
992
|
-
},
|
|
993
|
-
)
|
|
1069
|
+
const voucherResponse = await postManagementCredential({
|
|
1070
|
+
challenge: parameters.challenge,
|
|
1071
|
+
context: resolution.context,
|
|
1072
|
+
createSessionCredential: parameters.driver.createSessionCredential,
|
|
1073
|
+
fetch: parameters.driver.fetch,
|
|
1074
|
+
input: parameters.input,
|
|
1075
|
+
})
|
|
994
1076
|
if (!voucherResponse.ok) {
|
|
995
1077
|
throw new Error(`Voucher POST failed with status ${voucherResponse.status}`)
|
|
996
1078
|
}
|
|
@@ -1148,7 +1230,7 @@ export async function prepareWebSocketSession(
|
|
|
1148
1230
|
)
|
|
1149
1231
|
}
|
|
1150
1232
|
|
|
1151
|
-
const challenge = Challenge.fromResponseList(probe).find(
|
|
1233
|
+
const challenge = Challenge.fromResponseList(probe).find(isTip1034SessionChallenge)
|
|
1152
1234
|
if (!challenge) {
|
|
1153
1235
|
throw new Error(
|
|
1154
1236
|
'No payment challenge received from HTTP endpoint for this WebSocket URL. The server may not require payment or did not advertise a challenge.',
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ChannelEntry } from '../ChannelOps.js'
|
|
2
|
+
import type { PaymentResponse, SessionManager } from '../SessionManager.js'
|
|
3
|
+
import type { SseResponseOptions, TempoSessionChallenge } from '../Transports.js'
|
|
4
|
+
|
|
5
|
+
type RehydrateParameters = {
|
|
6
|
+
channel: ChannelEntry
|
|
7
|
+
challenge: TempoSessionChallenge
|
|
8
|
+
input: RequestInfo | URL
|
|
9
|
+
spent: bigint
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type SessionManagerInternals = {
|
|
13
|
+
consumeSseResponse(
|
|
14
|
+
input: RequestInfo | URL,
|
|
15
|
+
response: PaymentResponse,
|
|
16
|
+
options?: SseResponseOptions | undefined,
|
|
17
|
+
): AsyncIterable<string>
|
|
18
|
+
rehydrate(parameters: RehydrateParameters): void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const internals = new WeakMap<SessionManager, SessionManagerInternals>()
|
|
22
|
+
|
|
23
|
+
/** @internal Registers private transport and recovery hooks for a session manager. */
|
|
24
|
+
export function registerSessionManagerInternals(
|
|
25
|
+
manager: SessionManager,
|
|
26
|
+
value: SessionManagerInternals,
|
|
27
|
+
): void {
|
|
28
|
+
internals.set(manager, value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @internal Returns private transport and recovery hooks for a session manager. */
|
|
32
|
+
export function getSessionManagerInternals(manager: SessionManager): SessionManagerInternals {
|
|
33
|
+
const value = internals.get(manager)
|
|
34
|
+
if (!value) throw new Error('Session manager internals are unavailable.')
|
|
35
|
+
return value
|
|
36
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { Hex } from 'ox'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type Address,
|
|
4
|
+
encodeFunctionData,
|
|
5
|
+
isAddressEqual,
|
|
6
|
+
maxUint256,
|
|
7
|
+
parseEventLogs,
|
|
8
|
+
zeroAddress,
|
|
9
|
+
} from 'viem'
|
|
3
10
|
import { sendTransaction, waitForTransactionReceipt } from 'viem/actions'
|
|
4
11
|
import { Transaction } from 'viem/tempo'
|
|
5
12
|
import { describe, expect, test } from 'vp/test'
|
|
@@ -42,6 +49,15 @@ function getSingleEvent(receipt: { logs: readonly unknown[] }, name: string) {
|
|
|
42
49
|
return logs[0] as unknown as { args: Record<string, unknown> }
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
function expectExpiringNonce(serializedTransaction: Hex.Hex) {
|
|
53
|
+
const transaction = Transaction.deserialize(
|
|
54
|
+
serializedTransaction as Transaction.TransactionSerializedTempo,
|
|
55
|
+
) as unknown as { nonce: number; nonceKey: bigint; validBefore?: bigint }
|
|
56
|
+
expect(transaction.nonce).toBe(0)
|
|
57
|
+
expect(transaction.nonceKey).toBe(maxUint256)
|
|
58
|
+
expect(transaction.validBefore).toBeDefined()
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
async function openChannel(parameters: { deposit?: bigint | undefined } = {}) {
|
|
46
62
|
const deposit = uint96(parameters.deposit ?? 1_000n)
|
|
47
63
|
const salt = Hex.random(32)
|
|
@@ -120,6 +136,7 @@ describe.runIf(isPrecompileTestnet)('TIP20EscrowChannel precompile chain operati
|
|
|
120
136
|
token: asset,
|
|
121
137
|
})
|
|
122
138
|
if (payload.action !== 'open') throw new Error('expected open payload')
|
|
139
|
+
expectExpiringNonce(payload.transaction)
|
|
123
140
|
|
|
124
141
|
const transaction = Transaction.deserialize(
|
|
125
142
|
payload.transaction as Transaction.TransactionSerializedTempo,
|
|
@@ -224,6 +241,7 @@ describe.runIf(isPrecompileTestnet)('TIP20EscrowChannel precompile chain operati
|
|
|
224
241
|
chain.id,
|
|
225
242
|
)
|
|
226
243
|
if (topUp.action !== 'topUp') throw new Error('expected topUp payload')
|
|
244
|
+
expectExpiringNonce(topUp.transaction)
|
|
227
245
|
|
|
228
246
|
const result = await Chain.broadcastTopUpTransaction({
|
|
229
247
|
additionalDeposit,
|