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