mppx 0.8.12 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +16 -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/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/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 +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/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 +3 -2
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +2 -23
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +2 -2
- package/dist/tempo/server/Methods.js.map +1 -1
- 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.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +11 -2
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +21 -1
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +66 -1
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +2 -2
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +128 -30
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +11 -2
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +33 -9
- 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.js +13 -4
- package/dist/tempo/session/precompile/Chain.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 +10 -4
- 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/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/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 +42 -0
- 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/internal/types.ts +13 -2
- 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.ts +6 -4
- package/src/tempo/server/Methods.ts +5 -4
- 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 +40 -0
- package/src/tempo/session/client/ChannelOps.ts +12 -2
- package/src/tempo/session/client/CredentialState.test.ts +2 -2
- package/src/tempo/session/client/CredentialState.ts +103 -1
- package/src/tempo/session/client/Session.test.ts +3 -3
- package/src/tempo/session/client/SessionManager.test.ts +137 -4
- package/src/tempo/session/client/SessionManager.ts +139 -33
- package/src/tempo/session/client/Transports.test.ts +150 -8
- package/src/tempo/session/client/Transports.ts +48 -10
- package/src/tempo/session/client/internal/SessionManager.ts +36 -0
- package/src/tempo/session/precompile/Chain.test.ts +26 -0
- package/src/tempo/session/precompile/Chain.ts +17 -4
- 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 +39 -3
- package/src/tempo/session/server/RequestState.ts +13 -6
- 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,
|
|
@@ -112,9 +114,15 @@ describe('HttpManagement', () => {
|
|
|
112
114
|
})
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
function receiptHeader(acceptedCumulative: bigint, spent: bigint) {
|
|
117
|
+
function receiptHeader(acceptedCumulative: bigint, spent: bigint, challengeId = 'challenge-1') {
|
|
116
118
|
return serializeSessionReceipt(
|
|
117
|
-
createSessionReceipt({
|
|
119
|
+
createSessionReceipt({
|
|
120
|
+
acceptedCumulative,
|
|
121
|
+
challengeId,
|
|
122
|
+
channelId,
|
|
123
|
+
spent,
|
|
124
|
+
txHash: `0x${'aa'.repeat(32)}`,
|
|
125
|
+
}),
|
|
118
126
|
)
|
|
119
127
|
}
|
|
120
128
|
|
|
@@ -139,9 +147,12 @@ describe('HttpManagement', () => {
|
|
|
139
147
|
)
|
|
140
148
|
})
|
|
141
149
|
|
|
142
|
-
test('
|
|
150
|
+
test('preserves query state and strips fragments from management URLs', () => {
|
|
143
151
|
expect(managementInput('https://example.test/resource?cursor=abc').toString()).toBe(
|
|
144
|
-
'https://example.test/resource',
|
|
152
|
+
'https://example.test/resource?cursor=abc',
|
|
153
|
+
)
|
|
154
|
+
expect(managementInput('https://example.test/resource?cursor=abc#result').toString()).toBe(
|
|
155
|
+
'https://example.test/resource?cursor=abc',
|
|
145
156
|
)
|
|
146
157
|
})
|
|
147
158
|
|
|
@@ -185,7 +196,7 @@ describe('HttpManagement', () => {
|
|
|
185
196
|
return 'top-up-credential'
|
|
186
197
|
})
|
|
187
198
|
const fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
188
|
-
expect(input.toString()).toBe('https://example.test/resource')
|
|
199
|
+
expect(input.toString()).toBe('https://example.test/resource?cursor=abc')
|
|
189
200
|
expect(init?.method).toBe('POST')
|
|
190
201
|
expect(authorizationHeader(init)).toBe('top-up-credential')
|
|
191
202
|
return new Response(null, {
|
|
@@ -301,16 +312,20 @@ describe('HttpManagement', () => {
|
|
|
301
312
|
const entry = channel()
|
|
302
313
|
const retryChallenge = { ...challenge(), id: 'challenge-2' } as TempoSessionChallenge
|
|
303
314
|
const setChallenge = vi.fn()
|
|
304
|
-
const
|
|
315
|
+
const closeAmounts: string[] = []
|
|
316
|
+
const createSessionCredential = vi.fn(async (challenge_, context: SessionContext) => {
|
|
317
|
+
if (context.action !== 'close') throw new Error('expected close context')
|
|
318
|
+
closeAmounts.push(context.cumulativeAmountRaw!)
|
|
305
319
|
return `close-${challenge_.id}`
|
|
306
320
|
})
|
|
321
|
+
const resolveSignedCloseAmount = vi.fn(() => '6')
|
|
307
322
|
const fetch = vi
|
|
308
323
|
.fn()
|
|
309
324
|
.mockResolvedValueOnce(response402(retryChallenge))
|
|
310
325
|
.mockResolvedValueOnce(
|
|
311
326
|
new Response(null, {
|
|
312
327
|
status: 200,
|
|
313
|
-
headers: { [Constants.Headers.paymentReceipt]: receiptHeader(
|
|
328
|
+
headers: { [Constants.Headers.paymentReceipt]: receiptHeader(6n, 6n, 'challenge-2') },
|
|
314
329
|
}),
|
|
315
330
|
)
|
|
316
331
|
|
|
@@ -318,18 +333,71 @@ describe('HttpManagement', () => {
|
|
|
318
333
|
createSessionCredential,
|
|
319
334
|
fetch,
|
|
320
335
|
lastUrl: 'https://example.test/resource',
|
|
336
|
+
resolveSignedCloseAmount,
|
|
321
337
|
signedCloseAmount: '5',
|
|
322
338
|
setChallenge,
|
|
323
339
|
target: { challenge: challenge(), channel: entry, channelId },
|
|
324
340
|
})
|
|
325
341
|
|
|
326
|
-
expect(receipt?.spent).toBe('
|
|
342
|
+
expect(receipt?.spent).toBe('6')
|
|
327
343
|
expect(setChallenge).toHaveBeenCalledWith(retryChallenge)
|
|
344
|
+
expect(resolveSignedCloseAmount).toHaveBeenCalledOnce()
|
|
345
|
+
expect(resolveSignedCloseAmount).toHaveBeenCalledWith(retryChallenge)
|
|
346
|
+
expect(closeAmounts).toEqual(['5', '6'])
|
|
328
347
|
expect(createSessionCredential).toHaveBeenCalledTimes(2)
|
|
329
348
|
expect(createSessionCredential.mock.calls[1]?.[0]).toMatchObject({ id: retryChallenge.id })
|
|
330
349
|
expect(authorizationHeader(fetch.mock.calls[1]?.[1])).toBe('close-challenge-2')
|
|
331
350
|
})
|
|
332
351
|
|
|
352
|
+
test('closeHttpSession rejects a receipt without settlement proof', async () => {
|
|
353
|
+
const receipt = serializeSessionReceipt(
|
|
354
|
+
createSessionReceipt({
|
|
355
|
+
acceptedCumulative: 5n,
|
|
356
|
+
challengeId: 'challenge-1',
|
|
357
|
+
channelId,
|
|
358
|
+
spent: 5n,
|
|
359
|
+
}),
|
|
360
|
+
)
|
|
361
|
+
await expect(
|
|
362
|
+
closeHttpSession({
|
|
363
|
+
createSessionCredential: async () => 'close-credential',
|
|
364
|
+
fetch: async () =>
|
|
365
|
+
new Response(null, {
|
|
366
|
+
headers: { [Constants.Headers.paymentReceipt]: receipt },
|
|
367
|
+
}),
|
|
368
|
+
lastUrl: 'https://example.test/resource',
|
|
369
|
+
signedCloseAmount: '5',
|
|
370
|
+
target: { challenge: challenge(), channel: channel(), channelId },
|
|
371
|
+
}),
|
|
372
|
+
).rejects.toThrow('Session close response included a mismatched payment receipt.')
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
test('closeHttpSession rejects an invalid settlement transaction hash', async () => {
|
|
376
|
+
for (const txHash of ['0x1234', `0x${'gg'.repeat(32)}`]) {
|
|
377
|
+
const receipt = serializeSessionReceipt(
|
|
378
|
+
createSessionReceipt({
|
|
379
|
+
acceptedCumulative: 5n,
|
|
380
|
+
challengeId: 'challenge-1',
|
|
381
|
+
channelId,
|
|
382
|
+
spent: 5n,
|
|
383
|
+
txHash: txHash as Hex.Hex,
|
|
384
|
+
}),
|
|
385
|
+
)
|
|
386
|
+
await expect(
|
|
387
|
+
closeHttpSession({
|
|
388
|
+
createSessionCredential: async () => 'close-credential',
|
|
389
|
+
fetch: async () =>
|
|
390
|
+
new Response(null, {
|
|
391
|
+
headers: { [Constants.Headers.paymentReceipt]: receipt },
|
|
392
|
+
}),
|
|
393
|
+
lastUrl: 'https://example.test/resource',
|
|
394
|
+
signedCloseAmount: '5',
|
|
395
|
+
target: { challenge: challenge(), channel: channel(), channelId },
|
|
396
|
+
}),
|
|
397
|
+
).rejects.toThrow('Session close response included a mismatched payment receipt.')
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
|
|
333
401
|
test('closeHttpSession includes problem detail and challenge header on failure', async () => {
|
|
334
402
|
await expect(
|
|
335
403
|
closeHttpSession({
|
|
@@ -747,6 +815,80 @@ describe('SseDriver', () => {
|
|
|
747
815
|
await vi.waitFor(() => expect(cancel).toHaveBeenCalledOnce())
|
|
748
816
|
},
|
|
749
817
|
)
|
|
818
|
+
|
|
819
|
+
test('keeps voucher events bound to the challenge that opened the stream', async () => {
|
|
820
|
+
const channelId = `0x${'01'.repeat(32)}` as Hex.Hex
|
|
821
|
+
const token = '0x20c0000000000000000000000000000000000001' as Address
|
|
822
|
+
const payee = '0x0000000000000000000000000000000000000002' as Address
|
|
823
|
+
const descriptor: ChannelDescriptor = {
|
|
824
|
+
authorizedSigner: '0x0000000000000000000000000000000000000001',
|
|
825
|
+
expiringNonceHash: `0x${'03'.repeat(32)}` as Hex.Hex,
|
|
826
|
+
operator: '0x0000000000000000000000000000000000000000',
|
|
827
|
+
payee,
|
|
828
|
+
payer: '0x0000000000000000000000000000000000000001',
|
|
829
|
+
salt: `0x${'02'.repeat(32)}` as Hex.Hex,
|
|
830
|
+
token,
|
|
831
|
+
}
|
|
832
|
+
const makeChallenge = (id: string) =>
|
|
833
|
+
Challenge.from({
|
|
834
|
+
id,
|
|
835
|
+
intent: Constants.Intents.session,
|
|
836
|
+
method: Constants.Methods.tempo,
|
|
837
|
+
realm: 'example.test',
|
|
838
|
+
request: {
|
|
839
|
+
amount: '1',
|
|
840
|
+
currency: token,
|
|
841
|
+
methodDetails: { chainId: 4217, escrowContract: tip20ChannelEscrow },
|
|
842
|
+
recipient: payee,
|
|
843
|
+
unitType: 'request',
|
|
844
|
+
},
|
|
845
|
+
}) as TempoSessionChallenge
|
|
846
|
+
const streamChallenge = makeChallenge('challenge-1')
|
|
847
|
+
let currentChallenge = streamChallenge
|
|
848
|
+
const createSessionCredential = vi.fn(async () => 'Payment credential')
|
|
849
|
+
const channel: ChannelEntry = {
|
|
850
|
+
chainId: 4217,
|
|
851
|
+
channelId,
|
|
852
|
+
cumulativeAmount: 1n,
|
|
853
|
+
deposit: 10n,
|
|
854
|
+
descriptor,
|
|
855
|
+
escrow: tip20ChannelEscrow,
|
|
856
|
+
opened: true,
|
|
857
|
+
}
|
|
858
|
+
const response = new Response(
|
|
859
|
+
formatNeedVoucherEvent({
|
|
860
|
+
acceptedCumulative: '1',
|
|
861
|
+
channelId,
|
|
862
|
+
deposit: '10',
|
|
863
|
+
requiredCumulative: '2',
|
|
864
|
+
}),
|
|
865
|
+
{ headers: { 'Content-Type': 'text/event-stream' } },
|
|
866
|
+
)
|
|
867
|
+
const stream = consumeSseSessionResponse('https://example.test/stream', response, undefined, {
|
|
868
|
+
acceptReceipt() {},
|
|
869
|
+
assertVoucherWithinLocalLimit() {},
|
|
870
|
+
createSessionCredential,
|
|
871
|
+
async doFetch() {
|
|
872
|
+
throw new Error('unexpected resource fetch')
|
|
873
|
+
},
|
|
874
|
+
fetch: vi.fn(async () => new Response(null, { status: 204 })),
|
|
875
|
+
getChallenge: () => currentChallenge,
|
|
876
|
+
getChannel: () => channel,
|
|
877
|
+
managementInput: (input) => input,
|
|
878
|
+
async topUpIfNeeded() {},
|
|
879
|
+
})
|
|
880
|
+
|
|
881
|
+
currentChallenge = makeChallenge('challenge-2')
|
|
882
|
+
const frames: string[] = []
|
|
883
|
+
for await (const frame of stream) frames.push(frame)
|
|
884
|
+
|
|
885
|
+
expect(frames).toEqual([])
|
|
886
|
+
expect(createSessionCredential).toHaveBeenCalledOnce()
|
|
887
|
+
expect(createSessionCredential).toHaveBeenCalledWith(
|
|
888
|
+
streamChallenge,
|
|
889
|
+
expect.objectContaining({ action: 'voucher', cumulativeAmountRaw: '2' }),
|
|
890
|
+
)
|
|
891
|
+
})
|
|
750
892
|
})
|
|
751
893
|
|
|
752
894
|
describe('WsDriver', () => {
|
|
@@ -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'
|
|
@@ -502,6 +502,8 @@ export type CloseHttpSessionParameters = {
|
|
|
502
502
|
fetch: typeof globalThis.fetch
|
|
503
503
|
/** Last paid resource URL; used as the management endpoint base. */
|
|
504
504
|
lastUrl: RequestInfo | URL | null
|
|
505
|
+
/** Resolves the close amount again when the server refreshes the challenge. */
|
|
506
|
+
resolveSignedCloseAmount?: ((challenge: TempoSessionChallenge) => string) | undefined
|
|
505
507
|
/** Final cumulative amount the client is willing to sign. */
|
|
506
508
|
signedCloseAmount: string
|
|
507
509
|
/** Stores a fresh close challenge when the first close credential expired. */
|
|
@@ -544,7 +546,7 @@ export function requestInitWithAuthorization(
|
|
|
544
546
|
}
|
|
545
547
|
}
|
|
546
548
|
|
|
547
|
-
/** Returns the URL used for out-of-band management POSTs,
|
|
549
|
+
/** Returns the exact resource URL used for out-of-band management POSTs, without its fragment. */
|
|
548
550
|
export function managementInput(input: RequestInfo | URL): RequestInfo | URL {
|
|
549
551
|
try {
|
|
550
552
|
const base =
|
|
@@ -555,7 +557,7 @@ export function managementInput(input: RequestInfo | URL): RequestInfo | URL {
|
|
|
555
557
|
: input instanceof URL
|
|
556
558
|
? new URL(input)
|
|
557
559
|
: new URL(String(input), base)
|
|
558
|
-
url.
|
|
560
|
+
url.hash = ''
|
|
559
561
|
return url
|
|
560
562
|
} catch {
|
|
561
563
|
return input
|
|
@@ -682,12 +684,18 @@ export async function closeHttpSession(
|
|
|
682
684
|
)
|
|
683
685
|
}
|
|
684
686
|
|
|
685
|
-
|
|
687
|
+
let closeChallenge = parameters.target.challenge
|
|
688
|
+
let signedCloseAmount = parameters.signedCloseAmount
|
|
689
|
+
const postClose = async (challenge: TempoSessionChallenge, refreshed = false) => {
|
|
690
|
+
closeChallenge = challenge
|
|
691
|
+
signedCloseAmount =
|
|
692
|
+
(refreshed ? parameters.resolveSignedCloseAmount?.(challenge) : undefined) ??
|
|
693
|
+
parameters.signedCloseAmount
|
|
686
694
|
const credential = await parameters.createSessionCredential(challenge, {
|
|
687
695
|
action: 'close',
|
|
688
696
|
channelId: parameters.target.channelId,
|
|
689
697
|
descriptor: parameters.target.channel.descriptor,
|
|
690
|
-
cumulativeAmountRaw:
|
|
698
|
+
cumulativeAmountRaw: signedCloseAmount,
|
|
691
699
|
})
|
|
692
700
|
return parameters.fetch(parameters.lastUrl!, {
|
|
693
701
|
method: 'POST',
|
|
@@ -700,7 +708,7 @@ export async function closeHttpSession(
|
|
|
700
708
|
const challenge = Challenge.fromResponseList(response).find(isTempoSessionChallenge)
|
|
701
709
|
if (challenge) {
|
|
702
710
|
parameters.setChallenge?.(challenge)
|
|
703
|
-
response = await postClose(challenge)
|
|
711
|
+
response = await postClose(challenge, true)
|
|
704
712
|
}
|
|
705
713
|
}
|
|
706
714
|
if (!response.ok) {
|
|
@@ -712,7 +720,21 @@ export async function closeHttpSession(
|
|
|
712
720
|
}
|
|
713
721
|
|
|
714
722
|
const receiptHeader = response.headers.get(Constants.Headers.paymentReceipt)
|
|
715
|
-
|
|
723
|
+
if (!receiptHeader) return undefined
|
|
724
|
+
const receipt = deserializeSessionReceipt(receiptHeader)
|
|
725
|
+
if (
|
|
726
|
+
!receipt.txHash ||
|
|
727
|
+
!Hex.validate(receipt.txHash, { strict: true }) ||
|
|
728
|
+
Hex.size(receipt.txHash) !== 32 ||
|
|
729
|
+
!isExpectedCloseReceipt({
|
|
730
|
+
challengeId: closeChallenge.id,
|
|
731
|
+
channelId: parameters.target.channelId,
|
|
732
|
+
expectedCloseAmount: signedCloseAmount,
|
|
733
|
+
receipt,
|
|
734
|
+
})
|
|
735
|
+
)
|
|
736
|
+
throw new Error('Session close response included a mismatched payment receipt.')
|
|
737
|
+
return receipt
|
|
716
738
|
}
|
|
717
739
|
|
|
718
740
|
/** Options accepted by the auto-driving SSE session flow. */
|
|
@@ -723,6 +745,12 @@ export type SseDriverOptions = RequestInit & {
|
|
|
723
745
|
signal?: AbortSignal | undefined
|
|
724
746
|
}
|
|
725
747
|
|
|
748
|
+
/** @internal Options used when consuming an already-paid SSE response. */
|
|
749
|
+
export type SseResponseOptions = {
|
|
750
|
+
onReceipt?: ((receipt: SessionReceipt) => void) | undefined
|
|
751
|
+
signal?: AbortSignal | undefined
|
|
752
|
+
}
|
|
753
|
+
|
|
726
754
|
/** Dependencies the SSE driver needs from `SessionManager`. */
|
|
727
755
|
export type OpenSseSessionParameters = {
|
|
728
756
|
/** Creates a session credential for the selected challenge/context. */
|
|
@@ -784,18 +812,28 @@ export async function openSseSession(
|
|
|
784
812
|
if (!isEventStream(response) && driver.getChannel()?.opened)
|
|
785
813
|
response = await driver.doFetch(input, sseInit)
|
|
786
814
|
|
|
787
|
-
|
|
815
|
+
return consumeSseSessionResponse(input, response, { onReceipt, signal }, driver)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/** @internal Consumes an already-paid SSE response with the session transport driver. */
|
|
819
|
+
export function consumeSseSessionResponse(
|
|
820
|
+
input: RequestInfo | URL,
|
|
821
|
+
response: Response,
|
|
822
|
+
options: SseResponseOptions | undefined,
|
|
823
|
+
driver: OpenSseSessionParameters,
|
|
824
|
+
): AsyncIterable<string> {
|
|
788
825
|
if (!isEventStream(response)) throw new Error('SSE response is not an event stream.')
|
|
789
826
|
if (!response.body) throw new Error('Response has no body.')
|
|
827
|
+
const challenge = driver.getChallenge()
|
|
790
828
|
|
|
791
829
|
return iterateSseMessages({
|
|
792
830
|
onNeedVoucher: (event) => handleSseNeedVoucher({ challenge, driver, input }, event),
|
|
793
831
|
onReceipt(receipt) {
|
|
794
832
|
driver.acceptReceipt(receipt)
|
|
795
|
-
onReceipt?.(receipt)
|
|
833
|
+
options?.onReceipt?.(receipt)
|
|
796
834
|
},
|
|
797
835
|
response,
|
|
798
|
-
signal,
|
|
836
|
+
signal: options?.signal,
|
|
799
837
|
})
|
|
800
838
|
}
|
|
801
839
|
|
|
@@ -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
|
+
}
|
|
@@ -62,6 +62,7 @@ function createMockClient(
|
|
|
62
62
|
parameters.rpcMethods?.push(args.method)
|
|
63
63
|
parameters.onRequest?.(args.method, args.params)
|
|
64
64
|
if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
|
|
65
|
+
if (args.method === 'eth_sendTransaction') return txHash
|
|
65
66
|
if (args.method === 'eth_sendRawTransaction') return txHash
|
|
66
67
|
if (args.method === 'eth_sendRawTransactionSync') return parameters.receipt ?? receipt([])
|
|
67
68
|
if (args.method === 'eth_getTransactionReceipt') return parameters.receipt ?? null
|
|
@@ -290,6 +291,31 @@ describe('precompile receipt wait', () => {
|
|
|
290
291
|
})
|
|
291
292
|
})
|
|
292
293
|
|
|
294
|
+
describe('precompile server transactions', () => {
|
|
295
|
+
test('does not add a fee-payer signature when the sender and fee payer are the same account', async () => {
|
|
296
|
+
const rpcMethods: string[] = []
|
|
297
|
+
const client = createMockClient({ rpcMethods })
|
|
298
|
+
const sender = { address: feePayer.address, type: 'json-rpc' } as const
|
|
299
|
+
|
|
300
|
+
await Chain.closeOnChain(
|
|
301
|
+
client,
|
|
302
|
+
descriptor,
|
|
303
|
+
1n,
|
|
304
|
+
1n,
|
|
305
|
+
`0x${'11'.repeat(65)}`,
|
|
306
|
+
tip20ChannelEscrow,
|
|
307
|
+
{
|
|
308
|
+
account: sender,
|
|
309
|
+
candidateFeeTokens: [descriptor.token],
|
|
310
|
+
feePayer,
|
|
311
|
+
},
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
expect(rpcMethods).toContain('eth_sendTransaction')
|
|
315
|
+
expect(rpcMethods).not.toContain('eth_sendRawTransactionSync')
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
293
319
|
describe('precompile open calldata parsing', () => {
|
|
294
320
|
test('parseOpenCall accepts TIP-1034 open calldata', () => {
|
|
295
321
|
const data = encodeFunctionData({
|
|
@@ -1001,8 +1001,11 @@ async function sendPrecompileTransaction(
|
|
|
1001
1001
|
label: string,
|
|
1002
1002
|
options?: ChannelTransactionOptions,
|
|
1003
1003
|
): Promise<Hex> {
|
|
1004
|
-
|
|
1005
|
-
|
|
1004
|
+
const account = options?.account ?? client.account
|
|
1005
|
+
const selfSponsored =
|
|
1006
|
+
account && options?.feePayer && isAddressEqual(account.address, options.feePayer.address)
|
|
1007
|
+
|
|
1008
|
+
if (options?.feePayer && !selfSponsored) {
|
|
1006
1009
|
if (!account) throw new Error(`Cannot ${label} precompile channel: no account available.`)
|
|
1007
1010
|
const feeToken =
|
|
1008
1011
|
options.feeToken ??
|
|
@@ -1033,10 +1036,20 @@ async function sendPrecompileTransaction(
|
|
|
1033
1036
|
return receipt.transactionHash
|
|
1034
1037
|
}
|
|
1035
1038
|
|
|
1039
|
+
const feeToken =
|
|
1040
|
+
options?.feeToken ??
|
|
1041
|
+
(selfSponsored
|
|
1042
|
+
? await resolveFeeToken({
|
|
1043
|
+
account: account.address,
|
|
1044
|
+
candidateTokens: options?.candidateFeeTokens,
|
|
1045
|
+
client,
|
|
1046
|
+
})
|
|
1047
|
+
: undefined)
|
|
1048
|
+
|
|
1036
1049
|
return sendPrecompileContractCall(client, {
|
|
1037
|
-
account
|
|
1050
|
+
account,
|
|
1038
1051
|
to,
|
|
1039
1052
|
data,
|
|
1040
|
-
feeToken
|
|
1053
|
+
feeToken,
|
|
1041
1054
|
})
|
|
1042
1055
|
}
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
import * as Voucher from '../precompile/Voucher.js'
|
|
31
31
|
import * as ChannelStore from './ChannelStore.js'
|
|
32
32
|
import { getChallengePaymentFields } from './RequestState.js'
|
|
33
|
-
import { assertSettlementSender, getClientAccount } from './Settlement.js'
|
|
33
|
+
import { assertSettlementSender, getClientAccount, type OnSessionSettlement } from './Settlement.js'
|
|
34
34
|
|
|
35
35
|
/** Returns the effective voucher signer for a TIP-1034 descriptor. */
|
|
36
36
|
export function authorizedSigner(descriptor: Channel.ChannelDescriptor): Address {
|
|
@@ -344,6 +344,8 @@ export type VerifyCredentialPayloadParameters = {
|
|
|
344
344
|
lastOnChainVerified: Map<Hex, number>
|
|
345
345
|
/** Minimum allowed voucher delta in raw units. */
|
|
346
346
|
minVoucherDelta: bigint
|
|
347
|
+
/** Callback invoked after an on-chain settlement or close transaction is confirmed. */
|
|
348
|
+
onSessionSettlement?: OnSessionSettlement | undefined
|
|
347
349
|
/** Discriminated session credential payload to verify. */
|
|
348
350
|
payload: SessionCredentialPayload
|
|
349
351
|
/** Server-side channel store. */
|
|
@@ -747,6 +749,21 @@ async function handleCloseCredential(
|
|
|
747
749
|
signature: payload.signature,
|
|
748
750
|
}),
|
|
749
751
|
)
|
|
752
|
+
if (parameters.onSessionSettlement && txHash) {
|
|
753
|
+
try {
|
|
754
|
+
await parameters.onSessionSettlement(
|
|
755
|
+
Object.freeze({
|
|
756
|
+
txHash,
|
|
757
|
+
channelId,
|
|
758
|
+
trigger: 'close' as const,
|
|
759
|
+
amount: settledToPayee,
|
|
760
|
+
delta: settledToPayee - state.settled,
|
|
761
|
+
}),
|
|
762
|
+
)
|
|
763
|
+
} catch {
|
|
764
|
+
// Errors are isolated — observers cannot break the settlement flow.
|
|
765
|
+
}
|
|
766
|
+
}
|
|
750
767
|
return createSessionReceipt({
|
|
751
768
|
challengeId: challenge.id,
|
|
752
769
|
channelId,
|
|
@@ -12,8 +12,14 @@ export type SessionController = {
|
|
|
12
12
|
* The reservation blocks until sufficient voucher headroom exists, but the
|
|
13
13
|
* charge is only committed once a chunk is actually emitted. If the stream
|
|
14
14
|
* ends or aborts before that emission, the reservation is dropped.
|
|
15
|
+
*
|
|
16
|
+
* Pass an explicit raw-unit `amount` for request-aware or otherwise dynamic
|
|
17
|
+
* pricing. When omitted, the session challenge's configured tick cost is
|
|
18
|
+
* used.
|
|
15
19
|
*/
|
|
16
|
-
charge(): Promise<void>
|
|
20
|
+
charge(amount?: bigint): Promise<void>
|
|
21
|
+
/** Aborted when the client closes or requests the final session receipt. */
|
|
22
|
+
signal: AbortSignal
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
/** Async stream source accepted by paid session transports. */
|
|
@@ -49,7 +55,7 @@ export async function* meterIterable(options: MeteredStreamOptions): AsyncGenera
|
|
|
49
55
|
let reservedAmount = 0n
|
|
50
56
|
let reservedUnits = 0
|
|
51
57
|
|
|
52
|
-
const charge = async () => {
|
|
58
|
+
const charge = async (amount = options.tickCost) => {
|
|
53
59
|
if (prepaidUnits > 0) {
|
|
54
60
|
prepaidUnits -= 1
|
|
55
61
|
return
|
|
@@ -58,19 +64,20 @@ export async function* meterIterable(options: MeteredStreamOptions): AsyncGenera
|
|
|
58
64
|
await reserveChargeOrWait({
|
|
59
65
|
store: options.store,
|
|
60
66
|
channelId: options.channelId,
|
|
61
|
-
amount
|
|
67
|
+
amount,
|
|
62
68
|
reservedAmount,
|
|
63
69
|
emit: options.emitNeedVoucher,
|
|
64
70
|
formatNeedVoucher: options.formatNeedVoucher,
|
|
65
71
|
pollIntervalMs: options.pollIntervalMs,
|
|
66
72
|
signal: options.signal,
|
|
67
73
|
})
|
|
68
|
-
reservedAmount +=
|
|
74
|
+
reservedAmount += amount
|
|
69
75
|
reservedUnits += 1
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
const signal = options.signal ?? new AbortController().signal
|
|
72
79
|
const iterable =
|
|
73
|
-
typeof options.generate === 'function' ? options.generate({ charge }) : options.generate
|
|
80
|
+
typeof options.generate === 'function' ? options.generate({ charge, signal }) : options.generate
|
|
74
81
|
|
|
75
82
|
for await (const value of iterable) {
|
|
76
83
|
if (options.signal?.aborted) break
|
|
@@ -316,7 +316,11 @@ describe('SessionSnapshotHints', () => {
|
|
|
316
316
|
escrowContract,
|
|
317
317
|
expiringNonceHash: descriptor.expiringNonceHash,
|
|
318
318
|
finalized: false,
|
|
319
|
-
highestVoucher:
|
|
319
|
+
highestVoucher: {
|
|
320
|
+
channelId,
|
|
321
|
+
cumulativeAmount: 500n,
|
|
322
|
+
signature: `0x${'44'.repeat(64)}`,
|
|
323
|
+
},
|
|
320
324
|
highestVoucherAmount: 500n,
|
|
321
325
|
operator: descriptor.operator,
|
|
322
326
|
payee: descriptor.payee,
|
|
@@ -451,6 +455,11 @@ describe('SessionSnapshotHints', () => {
|
|
|
451
455
|
deposit: '1000',
|
|
452
456
|
descriptor,
|
|
453
457
|
escrow: escrowContract,
|
|
458
|
+
highestVoucher: {
|
|
459
|
+
channelId,
|
|
460
|
+
cumulativeAmount: '500',
|
|
461
|
+
signature: `0x${'44'.repeat(64)}`,
|
|
462
|
+
},
|
|
454
463
|
requiredCumulative: '350',
|
|
455
464
|
settled: '100',
|
|
456
465
|
spent: '300',
|
|
@@ -458,7 +467,7 @@ describe('SessionSnapshotHints', () => {
|
|
|
458
467
|
})
|
|
459
468
|
})
|
|
460
469
|
|
|
461
|
-
test('
|
|
470
|
+
test('keeps accepted cumulative at the signed voucher when the next request needs more', async () => {
|
|
462
471
|
const snapshot = await resolveSessionSnapshot({
|
|
463
472
|
amount: 300n,
|
|
464
473
|
channelId,
|
|
@@ -466,7 +475,34 @@ describe('SessionSnapshotHints', () => {
|
|
|
466
475
|
})
|
|
467
476
|
|
|
468
477
|
expect(snapshot?.requiredCumulative).toBe('600')
|
|
469
|
-
expect(snapshot?.acceptedCumulative).toBe('
|
|
478
|
+
expect(snapshot?.acceptedCumulative).toBe('500')
|
|
479
|
+
expect(snapshot?.highestVoucher?.cumulativeAmount).toBe('500')
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
test('omits reusable channel hints without a matching signed highest voucher', async () => {
|
|
483
|
+
await expect(
|
|
484
|
+
resolveSessionSnapshot({
|
|
485
|
+
amount: 50n,
|
|
486
|
+
channelId,
|
|
487
|
+
store: store(channel({ highestVoucher: null })),
|
|
488
|
+
}),
|
|
489
|
+
).resolves.toBeUndefined()
|
|
490
|
+
|
|
491
|
+
await expect(
|
|
492
|
+
resolveSessionSnapshot({
|
|
493
|
+
amount: 50n,
|
|
494
|
+
channelId,
|
|
495
|
+
store: store(
|
|
496
|
+
channel({
|
|
497
|
+
highestVoucher: {
|
|
498
|
+
channelId,
|
|
499
|
+
cumulativeAmount: 499n,
|
|
500
|
+
signature: `0x${'44'.repeat(64)}`,
|
|
501
|
+
},
|
|
502
|
+
}),
|
|
503
|
+
),
|
|
504
|
+
}),
|
|
505
|
+
).resolves.toBeUndefined()
|
|
470
506
|
})
|
|
471
507
|
|
|
472
508
|
test('omits reusable channel hints when payment fields do not match', async () => {
|
|
@@ -80,7 +80,11 @@ export type ResolveSessionChannelIdParameters = {
|
|
|
80
80
|
store: ChannelStore.ChannelStore
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Application-owned lookup from authenticated identity and payment scope to an existing session
|
|
85
|
+
* channel. MPPx does not derive a storage key or scan the channel store; the returned channel ID is
|
|
86
|
+
* loaded and independently validated before its snapshot is advertised.
|
|
87
|
+
*/
|
|
84
88
|
export type ResolveSessionChannelId = (
|
|
85
89
|
parameters: ResolveSessionChannelIdParameters,
|
|
86
90
|
) => Promise<string | null | undefined> | string | null | undefined
|
|
@@ -139,14 +143,12 @@ export async function resolveSessionSnapshot(
|
|
|
139
143
|
const channel = await store.getChannel(ChannelStore.normalizeChannelId(channelId))
|
|
140
144
|
if (!channel || !ChannelStore.isPrecompileState(channel)) return undefined
|
|
141
145
|
if (channel.finalized) return undefined
|
|
146
|
+
if (!channel.highestVoucher) return undefined
|
|
147
|
+
if (channel.highestVoucher.cumulativeAmount !== channel.highestVoucherAmount) return undefined
|
|
142
148
|
if (expected && !matchesSnapshotPaymentFields(channel, expected)) return undefined
|
|
143
149
|
const requiredCumulative = channel.spent + amount
|
|
144
|
-
const acceptedCumulative =
|
|
145
|
-
channel.highestVoucherAmount > requiredCumulative
|
|
146
|
-
? channel.highestVoucherAmount
|
|
147
|
-
: requiredCumulative
|
|
148
150
|
return {
|
|
149
|
-
acceptedCumulative:
|
|
151
|
+
acceptedCumulative: channel.highestVoucherAmount.toString(),
|
|
150
152
|
chainId: channel.chainId,
|
|
151
153
|
channelId: channel.channelId,
|
|
152
154
|
closeRequestedAt:
|
|
@@ -154,6 +156,11 @@ export async function resolveSessionSnapshot(
|
|
|
154
156
|
deposit: channel.deposit.toString(),
|
|
155
157
|
descriptor: channel.descriptor,
|
|
156
158
|
escrow: channel.escrowContract,
|
|
159
|
+
highestVoucher: {
|
|
160
|
+
channelId: channel.highestVoucher.channelId,
|
|
161
|
+
cumulativeAmount: channel.highestVoucher.cumulativeAmount.toString(),
|
|
162
|
+
signature: channel.highestVoucher.signature,
|
|
163
|
+
},
|
|
157
164
|
requiredCumulative: requiredCumulative.toString(),
|
|
158
165
|
settled: channel.settledOnChain.toString(),
|
|
159
166
|
spent: channel.spent.toString(),
|