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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Address, parseUnits } from 'viem'
|
|
2
2
|
import { tempo as tempo_chain } from 'viem/chains'
|
|
3
3
|
|
|
4
|
+
import * as MethodChallenge from '../../../client/internal/MethodChallenge.js'
|
|
4
5
|
import * as MethodResponse from '../../../client/internal/MethodResponse.js'
|
|
5
|
-
import * as Constants from '../../../Constants.js'
|
|
6
6
|
import * as Credential from '../../../Credential.js'
|
|
7
7
|
import * as Method from '../../../Method.js'
|
|
8
8
|
import * as Account from '../../../viem/Account.js'
|
|
@@ -11,26 +11,36 @@ import type {
|
|
|
11
11
|
ResolveAccount as ResolveAccount_,
|
|
12
12
|
ResolveAccountInfo as ResolveAccountInfo_,
|
|
13
13
|
} from '../../client/ResolveAccount.js'
|
|
14
|
+
import * as AutoSwap from '../../internal/auto-swap.js'
|
|
14
15
|
import * as defaults from '../../internal/defaults.js'
|
|
15
16
|
import * as Methods from '../../Methods.js'
|
|
16
|
-
import
|
|
17
|
+
import * as Channel from '../precompile/Channel.js'
|
|
18
|
+
import {
|
|
19
|
+
isEventStream,
|
|
20
|
+
readSessionChallengeAmount,
|
|
21
|
+
requireSessionCredentialContext,
|
|
22
|
+
} from '../precompile/Protocol.js'
|
|
17
23
|
import { serializeCredential, type ChannelEntry } from './ChannelOps.js'
|
|
18
24
|
import { createChannelStore, type ChannelStore } from './ChannelStore.js'
|
|
19
25
|
import {
|
|
26
|
+
canSignDescriptor,
|
|
20
27
|
executeCredentialPlan,
|
|
28
|
+
hasSessionAction,
|
|
21
29
|
planCredential,
|
|
22
30
|
resolveChallengeContext,
|
|
23
31
|
resolveRecoverContext,
|
|
24
32
|
sessionContextSchema,
|
|
33
|
+
type ChallengeContext,
|
|
34
|
+
type SessionContext as CredentialContext,
|
|
25
35
|
} from './CredentialState.js'
|
|
26
|
-
import { assertWithinMaxDeposit } from './Runtime.js'
|
|
36
|
+
import { assertWithinMaxDeposit, resolveAutomaticTopUp } from './Runtime.js'
|
|
27
37
|
import {
|
|
28
38
|
handleSseNeedVoucher,
|
|
29
|
-
|
|
30
|
-
managementInput,
|
|
39
|
+
isTip1034SessionChallenge,
|
|
31
40
|
postTopUp,
|
|
32
41
|
wrapSseResponse,
|
|
33
42
|
type SsePaymentDriver,
|
|
43
|
+
type TempoSessionChallenge,
|
|
34
44
|
} from './Transports.js'
|
|
35
45
|
|
|
36
46
|
export { sessionContextSchema, type SessionContext } from './CredentialState.js'
|
|
@@ -45,11 +55,13 @@ export { sessionContextSchema, type SessionContext } from './CredentialState.js'
|
|
|
45
55
|
export function session(parameters: session.Parameters = {}) {
|
|
46
56
|
const {
|
|
47
57
|
account,
|
|
58
|
+
autoSwap: autoSwapParameter,
|
|
48
59
|
channelStore,
|
|
49
60
|
decimals = defaults.decimals,
|
|
50
61
|
escrow: escrowOverride,
|
|
51
62
|
getClient: getClientParameter,
|
|
52
63
|
maxDeposit: maxDepositParameter,
|
|
64
|
+
topUpAmount: topUpAmountParameter,
|
|
53
65
|
onChannelUpdate,
|
|
54
66
|
resolveAccount,
|
|
55
67
|
} = parameters
|
|
@@ -61,18 +73,35 @@ export function session(parameters: session.Parameters = {}) {
|
|
|
61
73
|
const getAccount = Account.getResolver({ account })
|
|
62
74
|
const maxDeposit =
|
|
63
75
|
maxDepositParameter !== undefined ? parseUnits(maxDepositParameter, decimals) : undefined
|
|
76
|
+
const topUpAmount =
|
|
77
|
+
topUpAmountParameter !== undefined ? parseUnits(topUpAmountParameter, decimals) : undefined
|
|
64
78
|
const store = channelStore ?? createChannelStore()
|
|
65
79
|
const sink = { store, notifyUpdate: (entry: ChannelEntry) => onChannelUpdate?.(entry) }
|
|
66
80
|
|
|
81
|
+
const resolveCredentialAccount = async (
|
|
82
|
+
resolved: ChallengeContext,
|
|
83
|
+
context: CredentialContext | undefined,
|
|
84
|
+
entry: ChannelEntry | undefined,
|
|
85
|
+
) => {
|
|
86
|
+
const defaultAccount = getAccount(resolved.client, context)
|
|
87
|
+
const descriptor = context?.action
|
|
88
|
+
? context.descriptor
|
|
89
|
+
: (entry?.descriptor ??
|
|
90
|
+
resolveRecoverContext({ context, snapshot: resolved.snapshot })?.descriptor)
|
|
91
|
+
return (
|
|
92
|
+
(await resolveAccount?.({
|
|
93
|
+
account: defaultAccount,
|
|
94
|
+
chainId: resolved.chainId,
|
|
95
|
+
operation: {
|
|
96
|
+
kind: 'authorizePaymentChannel',
|
|
97
|
+
...(descriptor ? { authority: Channel.resolveAuthorizedSigner(descriptor) } : {}),
|
|
98
|
+
},
|
|
99
|
+
})) ?? defaultAccount
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
67
103
|
const method = Method.toClient(Methods.session, {
|
|
68
|
-
canHandleChallenge({ challenge })
|
|
69
|
-
return (
|
|
70
|
-
Constants.getMethodDetail(
|
|
71
|
-
challenge.request.methodDetails,
|
|
72
|
-
Constants.MethodDetailKeys.sessionProtocol,
|
|
73
|
-
) === Constants.SessionProtocols.v2
|
|
74
|
-
)
|
|
75
|
-
},
|
|
104
|
+
canHandleChallenge: ({ challenge }) => isTip1034SessionChallenge(challenge),
|
|
76
105
|
context: sessionContextSchema,
|
|
77
106
|
async createCredential({ challenge, context }) {
|
|
78
107
|
const resolved = await resolveChallengeContext({
|
|
@@ -80,22 +109,8 @@ export function session(parameters: session.Parameters = {}) {
|
|
|
80
109
|
escrowOverride,
|
|
81
110
|
getClient,
|
|
82
111
|
})
|
|
83
|
-
const defaultAccount = getAccount(resolved.client, context)
|
|
84
112
|
const entry = await store.get(resolved.key)
|
|
85
|
-
|
|
86
|
-
const recoverContext = resolveRecoverContext({ context, snapshot: resolved.snapshot })
|
|
87
|
-
const descriptor = context?.action
|
|
88
|
-
? context.descriptor
|
|
89
|
-
: (entry?.descriptor ?? recoverContext?.descriptor)
|
|
90
|
-
const account =
|
|
91
|
-
(await resolveAccount?.({
|
|
92
|
-
account: defaultAccount,
|
|
93
|
-
chainId: resolved.chainId,
|
|
94
|
-
operation: {
|
|
95
|
-
kind: 'authorizePaymentChannel',
|
|
96
|
-
...(descriptor ? { authority: descriptor.authorizedSigner } : {}),
|
|
97
|
-
},
|
|
98
|
-
})) ?? defaultAccount
|
|
113
|
+
const account = await resolveCredentialAccount(resolved, context, entry)
|
|
99
114
|
const payload = await executeCredentialPlan(
|
|
100
115
|
planCredential({
|
|
101
116
|
account,
|
|
@@ -106,15 +121,89 @@ export function session(parameters: session.Parameters = {}) {
|
|
|
106
121
|
resolved,
|
|
107
122
|
}),
|
|
108
123
|
sink,
|
|
124
|
+
AutoSwap.resolve(context?.autoSwap ?? autoSwapParameter, AutoSwap.defaultCurrencies),
|
|
109
125
|
)
|
|
110
126
|
return serializeCredential(challenge, payload, resolved.chainId, account)
|
|
111
127
|
},
|
|
112
128
|
})
|
|
113
129
|
|
|
130
|
+
const topUpChannelIfNeeded = async ({
|
|
131
|
+
challenge,
|
|
132
|
+
channel,
|
|
133
|
+
deposit,
|
|
134
|
+
fetch,
|
|
135
|
+
input,
|
|
136
|
+
requiredCumulative,
|
|
137
|
+
}: {
|
|
138
|
+
challenge: TempoSessionChallenge
|
|
139
|
+
channel: ChannelEntry
|
|
140
|
+
deposit: bigint
|
|
141
|
+
fetch: typeof globalThis.fetch
|
|
142
|
+
input: RequestInfo | URL
|
|
143
|
+
requiredCumulative: bigint
|
|
144
|
+
}) => {
|
|
145
|
+
const knownDeposit = channel.deposit > deposit ? channel.deposit : deposit
|
|
146
|
+
const additionalDeposit = resolveAutomaticTopUp({
|
|
147
|
+
deposit: knownDeposit,
|
|
148
|
+
maxDeposit,
|
|
149
|
+
requiredCumulative,
|
|
150
|
+
suggestedDeposit:
|
|
151
|
+
challenge.request.suggestedDeposit === undefined
|
|
152
|
+
? undefined
|
|
153
|
+
: BigInt(challenge.request.suggestedDeposit),
|
|
154
|
+
topUpAmount,
|
|
155
|
+
})
|
|
156
|
+
if (additionalDeposit > 0n)
|
|
157
|
+
await postTopUp({
|
|
158
|
+
additionalDeposit,
|
|
159
|
+
challenge,
|
|
160
|
+
channel,
|
|
161
|
+
channelId: channel.channelId,
|
|
162
|
+
createSessionCredential: (challenge, context) =>
|
|
163
|
+
method.createCredential({ challenge, context }),
|
|
164
|
+
fetch,
|
|
165
|
+
input,
|
|
166
|
+
})
|
|
167
|
+
const nextDeposit = knownDeposit + additionalDeposit
|
|
168
|
+
if (nextDeposit === channel.deposit) return
|
|
169
|
+
const updated = { ...channel, deposit: nextDeposit }
|
|
170
|
+
await store.set(updated)
|
|
171
|
+
sink.notifyUpdate(updated)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
MethodChallenge.register(method, async ({ challenge, context, fetch, input }) => {
|
|
175
|
+
if (!isTip1034SessionChallenge(challenge)) return
|
|
176
|
+
const sessionContext = context === undefined ? undefined : sessionContextSchema.parse(context)
|
|
177
|
+
if (hasSessionAction(sessionContext)) return
|
|
178
|
+
const resolved = await resolveChallengeContext({ challenge, escrowOverride, getClient })
|
|
179
|
+
const channel = await store.get(resolved.key)
|
|
180
|
+
if (!channel?.opened) return
|
|
181
|
+
const snapshot =
|
|
182
|
+
resolved.snapshot?.channelId.toLowerCase() === channel.channelId.toLowerCase()
|
|
183
|
+
? resolved.snapshot
|
|
184
|
+
: undefined
|
|
185
|
+
const nextCumulative = channel.cumulativeAmount + readSessionChallengeAmount(challenge)
|
|
186
|
+
const snapshotRequired = snapshot ? BigInt(snapshot.requiredCumulative) : nextCumulative
|
|
187
|
+
const requiredCumulative = snapshotRequired > nextCumulative ? snapshotRequired : nextCumulative
|
|
188
|
+
const snapshotDeposit = snapshot ? BigInt(snapshot.deposit) : channel.deposit
|
|
189
|
+
const deposit = snapshotDeposit > channel.deposit ? snapshotDeposit : channel.deposit
|
|
190
|
+
if (requiredCumulative <= deposit && deposit === channel.deposit) return
|
|
191
|
+
const account = await resolveCredentialAccount(resolved, sessionContext, channel)
|
|
192
|
+
if (!canSignDescriptor(account, channel.descriptor)) return
|
|
193
|
+
await topUpChannelIfNeeded({
|
|
194
|
+
challenge,
|
|
195
|
+
channel,
|
|
196
|
+
deposit,
|
|
197
|
+
fetch,
|
|
198
|
+
input,
|
|
199
|
+
requiredCumulative,
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
114
203
|
return MethodResponse.register(
|
|
115
204
|
method,
|
|
116
205
|
async ({ challenge, credential, fetch, headers, input, refetch, response, signal }) => {
|
|
117
|
-
if (!
|
|
206
|
+
if (!isTip1034SessionChallenge(challenge)) return response
|
|
118
207
|
if (!isEventStream(response)) {
|
|
119
208
|
const credentialContext = requireSessionCredentialContext(
|
|
120
209
|
Credential.deserialize(credential).payload,
|
|
@@ -142,23 +231,16 @@ export function session(parameters: session.Parameters = {}) {
|
|
|
142
231
|
method.createCredential({ challenge, context }),
|
|
143
232
|
fetch,
|
|
144
233
|
getChannel: () => channel ?? null,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const additionalDeposit = requiredCumulative - deposit
|
|
149
|
-
await postTopUp({
|
|
150
|
-
additionalDeposit,
|
|
234
|
+
async topUpIfNeeded({ deposit, requiredCumulative }) {
|
|
235
|
+
if (!channel) return
|
|
236
|
+
await topUpChannelIfNeeded({
|
|
151
237
|
challenge,
|
|
152
238
|
channel,
|
|
153
|
-
|
|
154
|
-
createSessionCredential: (challenge, context) =>
|
|
155
|
-
method.createCredential({ challenge, context }),
|
|
239
|
+
deposit: channel.deposit > deposit ? channel.deposit : deposit,
|
|
156
240
|
fetch,
|
|
157
241
|
input,
|
|
242
|
+
requiredCumulative,
|
|
158
243
|
})
|
|
159
|
-
channel.deposit += additionalDeposit
|
|
160
|
-
await store.set(channel)
|
|
161
|
-
sink.notifyUpdate(channel)
|
|
162
244
|
},
|
|
163
245
|
} satisfies SsePaymentDriver
|
|
164
246
|
|
|
@@ -179,6 +261,8 @@ export declare namespace session {
|
|
|
179
261
|
|
|
180
262
|
type Parameters = Account.getResolver.Parameters &
|
|
181
263
|
Client.getResolver.Parameters & {
|
|
264
|
+
/** Automatically acquire the session currency from fallback stablecoins before open/top-up. */
|
|
265
|
+
autoSwap?: AutoSwap.resolve.Value | undefined
|
|
182
266
|
/** Pluggable persistence for reusable channels. Defaults to an in-memory store. */
|
|
183
267
|
channelStore?: ChannelStore | undefined
|
|
184
268
|
/** Token decimals for parsing human-readable amounts (default: 6). */
|
|
@@ -187,6 +271,11 @@ export declare namespace session {
|
|
|
187
271
|
escrow?: Address | undefined
|
|
188
272
|
/** Maximum channel deposit in human-readable units. Caps server-suggested opens and automatic top-ups. */
|
|
189
273
|
maxDeposit?: string | undefined
|
|
274
|
+
/**
|
|
275
|
+
* Preferred automatic top-up size in human-readable units. When omitted,
|
|
276
|
+
* a bounded server `suggestedDeposit` is preferred, then the exact shortfall.
|
|
277
|
+
*/
|
|
278
|
+
topUpAmount?: string | undefined
|
|
190
279
|
/** Called whenever channel state changes. */
|
|
191
280
|
onChannelUpdate?: ((entry: ChannelEntry) => void) | undefined
|
|
192
281
|
/** Selects the account that signs this session credential after the challenge is known. */
|
|
@@ -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)
|
|
@@ -477,6 +609,54 @@ describe('Session', () => {
|
|
|
477
609
|
expect(s.channelId).not.toBe(storedChannelId)
|
|
478
610
|
})
|
|
479
611
|
|
|
612
|
+
test('keeps a persisted channel after its committed top-up when the paid retry fails', async () => {
|
|
613
|
+
const seeded = channelEntry({ cumulativeAmount: 10_000_000n, deposit: 10_000_000n })
|
|
614
|
+
const { store, delete: remove, map } = makeChannelStore([seeded])
|
|
615
|
+
const postedPayloads: SessionCredentialPayload[] = []
|
|
616
|
+
let probes = 0
|
|
617
|
+
const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
|
|
618
|
+
const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
|
|
619
|
+
const payload = authorization
|
|
620
|
+
? Credential.deserialize<SessionCredentialPayload>(authorization).payload
|
|
621
|
+
: undefined
|
|
622
|
+
if (!payload) {
|
|
623
|
+
probes++
|
|
624
|
+
if (probes > 1) throw new Error('unexpected fresh-channel retry')
|
|
625
|
+
return Promise.resolve(make402Response())
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
postedPayloads.push(payload)
|
|
629
|
+
if (payload.action === 'topUp') return Promise.resolve(new Response(null, { status: 204 }))
|
|
630
|
+
if (payload.action === 'voucher')
|
|
631
|
+
return Promise.resolve(new Response('upstream failed', { status: 500 }))
|
|
632
|
+
throw new Error(`unexpected ${payload.action} credential`)
|
|
633
|
+
})
|
|
634
|
+
const s = sessionManager({
|
|
635
|
+
account,
|
|
636
|
+
client,
|
|
637
|
+
fetch: mockFetch as typeof globalThis.fetch,
|
|
638
|
+
maxDeposit: '20',
|
|
639
|
+
channelStore: store,
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
const response = await s.fetch('https://api.example.com/data')
|
|
643
|
+
|
|
644
|
+
expect(response.status).toBe(500)
|
|
645
|
+
expect(postedPayloads.map((payload) => payload.action)).toEqual(['topUp', 'voucher'])
|
|
646
|
+
expect(remove).not.toHaveBeenCalled()
|
|
647
|
+
expect(map.get(entryKey(seeded))).toMatchObject({
|
|
648
|
+
channelId: storedChannelId,
|
|
649
|
+
cumulativeAmount: 10_000_000n,
|
|
650
|
+
deposit: 11_000_000n,
|
|
651
|
+
})
|
|
652
|
+
expect(s.channelId).toBe(storedChannelId)
|
|
653
|
+
expect(s.state).toMatchObject({
|
|
654
|
+
status: 'active',
|
|
655
|
+
channelId: storedChannelId,
|
|
656
|
+
deposit: '11000000',
|
|
657
|
+
})
|
|
658
|
+
})
|
|
659
|
+
|
|
480
660
|
test('rolls back an optimistic open when a replacement challenge does not reference it', async () => {
|
|
481
661
|
const { store, delete: remove } = makeChannelStore()
|
|
482
662
|
const postedPayloads: SessionCredentialPayload[] = []
|
|
@@ -621,10 +801,11 @@ describe('Session', () => {
|
|
|
621
801
|
await restarted.fetch('https://api.example.com/data')
|
|
622
802
|
|
|
623
803
|
// The first request carries no hint header; after the 402 the restarted
|
|
624
|
-
// manager resumes the persisted channel
|
|
625
|
-
//
|
|
804
|
+
// manager tops up and resumes the full persisted channel rather than
|
|
805
|
+
// opening a new one.
|
|
626
806
|
expect(new Headers(resumeFetch.mock.calls[0]?.[1]?.headers).get('Payment-Session')).toBeNull()
|
|
627
|
-
expect(posted
|
|
807
|
+
expect(posted.map((payload) => payload.action)).toEqual(['topUp', 'voucher'])
|
|
808
|
+
expect(posted[1]).toMatchObject({ channelId })
|
|
628
809
|
})
|
|
629
810
|
|
|
630
811
|
test('persists opened channels and deletes closed channels when supported', async () => {
|
|
@@ -855,6 +1036,7 @@ describe('Session', () => {
|
|
|
855
1036
|
client,
|
|
856
1037
|
fetch: mockFetch as typeof globalThis.fetch,
|
|
857
1038
|
maxDeposit: '10',
|
|
1039
|
+
topUpAmount: '5',
|
|
858
1040
|
})
|
|
859
1041
|
|
|
860
1042
|
await s.fetch('https://api.example.com/data')
|
|
@@ -862,10 +1044,13 @@ describe('Session', () => {
|
|
|
862
1044
|
|
|
863
1045
|
expect(response.status).toBe(200)
|
|
864
1046
|
expect(postedPayloads.map((payload) => payload.action)).toEqual(['open', 'topUp', 'voucher'])
|
|
1047
|
+
const topUp = postedPayloads[1]
|
|
1048
|
+
if (topUp?.action !== 'topUp') throw new Error('expected top-up payload')
|
|
1049
|
+
expect(topUp.additionalDeposit).toBe('5000000')
|
|
865
1050
|
expect(s.state).toMatchObject({
|
|
866
1051
|
status: 'active',
|
|
867
1052
|
acceptedCumulative: '2000000',
|
|
868
|
-
deposit: '
|
|
1053
|
+
deposit: '6000000',
|
|
869
1054
|
spent: '2000000',
|
|
870
1055
|
units: 2,
|
|
871
1056
|
})
|
|
@@ -1082,7 +1267,7 @@ describe('Session', () => {
|
|
|
1082
1267
|
expect(voucherPayload.channelId).toBe(openPayload.channelId)
|
|
1083
1268
|
expect(voucherPayload.descriptor).toEqual(openPayload.descriptor)
|
|
1084
1269
|
expect(voucherPayload.cumulativeAmount).toBe('2000000')
|
|
1085
|
-
expect(requestedUrls[2]).toBe('https://api.example.com/stream')
|
|
1270
|
+
expect(requestedUrls[2]).toBe('https://api.example.com/stream?prompt=paid')
|
|
1086
1271
|
})
|
|
1087
1272
|
|
|
1088
1273
|
test('ignores precompile SSE voucher requests for a different channel', async () => {
|
|
@@ -1266,6 +1451,51 @@ describe('Session', () => {
|
|
|
1266
1451
|
})
|
|
1267
1452
|
})
|
|
1268
1453
|
|
|
1454
|
+
describe('.ws()', () => {
|
|
1455
|
+
test('tops up a full persisted channel before constructing the socket', async () => {
|
|
1456
|
+
const events: string[] = []
|
|
1457
|
+
const { map, set, store } = makeChannelStore([
|
|
1458
|
+
channelEntry({ cumulativeAmount: 1_000_000n, deposit: 1_000_000n }),
|
|
1459
|
+
])
|
|
1460
|
+
set.mockImplementation((entry) => {
|
|
1461
|
+
map.set(entryKey(entry), entry)
|
|
1462
|
+
events.push(`set:${entry.deposit}:${entry.cumulativeAmount}`)
|
|
1463
|
+
})
|
|
1464
|
+
const fetch = vi.fn(async (_input, init?: RequestInit) => {
|
|
1465
|
+
const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
|
|
1466
|
+
if (!authorization) {
|
|
1467
|
+
events.push('probe')
|
|
1468
|
+
return make402Response(makeChallenge({ suggestedDeposit: '1000000' }))
|
|
1469
|
+
}
|
|
1470
|
+
const payload = Credential.deserialize<SessionCredentialPayload>(authorization).payload
|
|
1471
|
+
if (payload.action !== 'topUp') throw new Error(`unexpected ${payload.action} fetch`)
|
|
1472
|
+
events.push('topUp')
|
|
1473
|
+
return new Response(null, { status: 204 })
|
|
1474
|
+
})
|
|
1475
|
+
function WebSocket() {
|
|
1476
|
+
events.push('socket')
|
|
1477
|
+
throw new Error('stop after preparation')
|
|
1478
|
+
}
|
|
1479
|
+
const s = sessionManager({
|
|
1480
|
+
account,
|
|
1481
|
+
client,
|
|
1482
|
+
fetch: fetch as typeof globalThis.fetch,
|
|
1483
|
+
maxDeposit: '3',
|
|
1484
|
+
channelStore: store,
|
|
1485
|
+
webSocket: WebSocket as never,
|
|
1486
|
+
})
|
|
1487
|
+
|
|
1488
|
+
await expect(s.ws('wss://api.example.com/socket')).rejects.toThrow('stop after preparation')
|
|
1489
|
+
expect(events).toEqual([
|
|
1490
|
+
'probe',
|
|
1491
|
+
'topUp',
|
|
1492
|
+
'set:2000000:1000000',
|
|
1493
|
+
'set:2000000:2000000',
|
|
1494
|
+
'socket',
|
|
1495
|
+
])
|
|
1496
|
+
})
|
|
1497
|
+
})
|
|
1498
|
+
|
|
1269
1499
|
describe('.close()', () => {
|
|
1270
1500
|
test('is no-op when not opened', async () => {
|
|
1271
1501
|
const mockFetch = vi.fn()
|
|
@@ -1321,6 +1551,7 @@ describe('Session', () => {
|
|
|
1321
1551
|
challengeId,
|
|
1322
1552
|
channelId: payload.channelId,
|
|
1323
1553
|
spent: BigInt(payload.cumulativeAmount),
|
|
1554
|
+
txHash: `0x${'aa'.repeat(32)}` as Hex,
|
|
1324
1555
|
units: 1,
|
|
1325
1556
|
}),
|
|
1326
1557
|
),
|