mppx 0.8.13 → 0.8.15
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 +37 -0
- 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/validate/payment.d.ts.map +1 -1
- package/dist/cli/validate/payment.js +18 -4
- package/dist/cli/validate/payment.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +98 -5
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/MethodChallenge.d.ts +19 -0
- package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
- package/dist/client/internal/MethodChallenge.js +15 -0
- package/dist/client/internal/MethodChallenge.js.map +1 -0
- package/dist/client/internal/MethodResponse.d.ts +15 -0
- package/dist/client/internal/MethodResponse.d.ts.map +1 -1
- package/dist/client/internal/MethodResponse.js +10 -0
- package/dist/client/internal/MethodResponse.js.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/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 -0
- 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/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/server/Charge.d.ts +46 -77
- 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 +12 -1
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +4 -0
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Relay.d.ts +57 -0
- package/dist/tempo/server/Relay.d.ts.map +1 -0
- package/dist/tempo/server/Relay.js +179 -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 +150 -0
- package/dist/tempo/server/SponsorBudget.js.map +1 -0
- 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/client/ChannelOps.d.ts +7 -1
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +9 -4
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +5 -1
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +37 -19
- 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 +15 -5
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +216 -45
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +8 -1
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +114 -44
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +16 -6
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +84 -37
- package/dist/tempo/session/client/Transports.js.map +1 -1
- package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Chain.js +96 -8
- 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/RequestState.js +3 -1
- package/dist/tempo/session/server/RequestState.js.map +1 -1
- package/dist/tempo/session/server/Ws.d.ts.map +1 -1
- package/dist/tempo/session/server/Ws.js +3 -0
- package/dist/tempo/session/server/Ws.js.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +1 -829
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.js +14 -3
- package/dist/tempo/subscription/KeyAuthorization.js.map +1 -1
- package/dist/viem/Client.d.ts.map +1 -1
- package/dist/viem/Client.js +6 -1
- package/dist/viem/Client.js.map +1 -1
- package/package.json +2 -2
- 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/validate/payment.ts +18 -4
- package/src/cli/validate.test.ts +16 -0
- package/src/client/Mppx.test-d.ts +3 -3
- package/src/client/internal/Fetch.test.ts +77 -0
- package/src/client/internal/Fetch.ts +122 -11
- package/src/client/internal/MethodChallenge.ts +35 -0
- package/src/client/internal/MethodResponse.ts +28 -0
- package/src/server/Mppx.test-d.ts +2 -0
- package/src/server/Mppx.test.ts +205 -0
- package/src/server/Mppx.ts +167 -103
- 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/legacy/client/ChannelOps.test.ts +7 -0
- package/src/tempo/legacy/client/ChannelOps.ts +2 -1
- package/src/tempo/server/Charge.test.ts +337 -63
- package/src/tempo/server/Charge.ts +443 -267
- package/src/tempo/server/Methods.ts +11 -0
- package/src/tempo/server/Relay.test.ts +677 -0
- package/src/tempo/server/Relay.ts +297 -0
- package/src/tempo/server/SponsorBudget.test.ts +154 -0
- package/src/tempo/server/SponsorBudget.ts +219 -0
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/README.md +7 -0
- package/src/tempo/session/client/ChannelOps.test.ts +17 -2
- package/src/tempo/session/client/ChannelOps.ts +12 -4
- package/src/tempo/session/client/CredentialState.ts +61 -20
- 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 +506 -7
- package/src/tempo/session/client/Session.ts +279 -54
- package/src/tempo/session/client/SessionManager.test.ts +318 -13
- package/src/tempo/session/client/SessionManager.ts +132 -42
- package/src/tempo/session/client/Transports.test.ts +54 -1
- package/src/tempo/session/client/Transports.ts +137 -57
- package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
- package/src/tempo/session/precompile/Chain.test.ts +197 -10
- package/src/tempo/session/precompile/Chain.ts +102 -8
- package/src/tempo/session/precompile/Channel.test.ts +9 -0
- package/src/tempo/session/precompile/Channel.ts +5 -0
- package/src/tempo/session/server/RequestState.test.ts +8 -1
- package/src/tempo/session/server/RequestState.ts +2 -2
- package/src/tempo/session/server/Session.test.ts +66 -4
- package/src/tempo/session/server/Ws.ts +3 -0
- package/src/tempo/subscription/KeyAuthorization.test.ts +70 -1
- package/src/tempo/subscription/KeyAuthorization.ts +14 -3
- package/src/viem/Client.test.ts +18 -1
- package/src/viem/Client.ts +7 -1
|
@@ -1,6 +1,7 @@
|
|
|
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
6
|
import * as Constants from '../../../Constants.js'
|
|
6
7
|
import * as Credential from '../../../Credential.js'
|
|
@@ -11,30 +12,103 @@ import type {
|
|
|
11
12
|
ResolveAccount as ResolveAccount_,
|
|
12
13
|
ResolveAccountInfo as ResolveAccountInfo_,
|
|
13
14
|
} from '../../client/ResolveAccount.js'
|
|
15
|
+
import * as AutoSwap from '../../internal/auto-swap.js'
|
|
14
16
|
import * as defaults from '../../internal/defaults.js'
|
|
15
17
|
import * as Methods from '../../Methods.js'
|
|
16
|
-
import
|
|
18
|
+
import * as Channel from '../precompile/Channel.js'
|
|
19
|
+
import {
|
|
20
|
+
deserializeSessionReceipt,
|
|
21
|
+
isEventStream,
|
|
22
|
+
readSessionChallengeAmount,
|
|
23
|
+
requireSessionCredentialContext,
|
|
24
|
+
} from '../precompile/Protocol.js'
|
|
17
25
|
import { serializeCredential, type ChannelEntry } from './ChannelOps.js'
|
|
18
26
|
import { createChannelStore, type ChannelStore } from './ChannelStore.js'
|
|
19
27
|
import {
|
|
28
|
+
canSignDescriptor,
|
|
20
29
|
executeCredentialPlan,
|
|
30
|
+
hasSessionAction,
|
|
21
31
|
planCredential,
|
|
22
32
|
resolveChallengeContext,
|
|
23
33
|
resolveRecoverContext,
|
|
24
34
|
sessionContextSchema,
|
|
35
|
+
type ChallengeContext,
|
|
36
|
+
type SessionContext as CredentialContext,
|
|
25
37
|
} from './CredentialState.js'
|
|
26
|
-
import { assertWithinMaxDeposit } from './Runtime.js'
|
|
38
|
+
import { assertWithinMaxDeposit, resolveAutomaticTopUp } from './Runtime.js'
|
|
27
39
|
import {
|
|
40
|
+
getSessionSnapshot,
|
|
28
41
|
handleSseNeedVoucher,
|
|
29
|
-
|
|
30
|
-
managementInput,
|
|
42
|
+
isTip1034SessionChallenge,
|
|
31
43
|
postTopUp,
|
|
32
44
|
wrapSseResponse,
|
|
33
45
|
type SsePaymentDriver,
|
|
46
|
+
type TempoSessionChallenge,
|
|
34
47
|
} from './Transports.js'
|
|
35
48
|
|
|
36
49
|
export { sessionContextSchema, type SessionContext } from './CredentialState.js'
|
|
37
50
|
|
|
51
|
+
/** Tail promise for each payment scope, used to serialize automatic opens per store. */
|
|
52
|
+
const channelTails = new WeakMap<ChannelStore, Map<string, Promise<void>>>()
|
|
53
|
+
|
|
54
|
+
/** Serializes automatic opens for one payment scope across methods sharing a store. */
|
|
55
|
+
async function lockChannel(store: ChannelStore, key: string) {
|
|
56
|
+
const tails = channelTails.get(store) ?? new Map<string, Promise<void>>()
|
|
57
|
+
channelTails.set(store, tails)
|
|
58
|
+
const previous = tails.get(key)
|
|
59
|
+
let unlock!: () => void
|
|
60
|
+
const current = new Promise<void>((resolve) => {
|
|
61
|
+
unlock = resolve
|
|
62
|
+
})
|
|
63
|
+
tails.set(key, current)
|
|
64
|
+
await previous
|
|
65
|
+
return () => {
|
|
66
|
+
unlock()
|
|
67
|
+
if (tails.get(key) === current) tails.delete(key)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function matchesOpen(
|
|
72
|
+
opened: ChannelEntry,
|
|
73
|
+
acknowledgement: { acceptedCumulative: string; channelId: string } | null | undefined,
|
|
74
|
+
): boolean {
|
|
75
|
+
try {
|
|
76
|
+
return (
|
|
77
|
+
acknowledgement?.channelId.toLowerCase() === opened.channelId.toLowerCase() &&
|
|
78
|
+
BigInt(acknowledgement.acceptedCumulative) === opened.cumulativeAmount
|
|
79
|
+
)
|
|
80
|
+
} catch {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns whether the server acknowledged an open.
|
|
87
|
+
*
|
|
88
|
+
* Receipts must match the originating challenge, channel, and cumulative amount.
|
|
89
|
+
* Replacement challenges may acknowledge the same channel and amount through a
|
|
90
|
+
* session snapshot. Malformed acknowledgements are ignored.
|
|
91
|
+
*/
|
|
92
|
+
function acknowledgesOpen(
|
|
93
|
+
outcome: MethodResponse.AttemptOutcome,
|
|
94
|
+
challenge: TempoSessionChallenge,
|
|
95
|
+
opened: ChannelEntry,
|
|
96
|
+
): boolean {
|
|
97
|
+
try {
|
|
98
|
+
const receiptHeader = outcome.response?.headers.get(Constants.Headers.paymentReceipt)
|
|
99
|
+
if (receiptHeader) {
|
|
100
|
+
const receipt = deserializeSessionReceipt(receiptHeader)
|
|
101
|
+
if (receipt.challengeId === challenge.id && matchesOpen(opened, receipt)) return true
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
104
|
+
return (
|
|
105
|
+
outcome.challenges?.some((candidate) => {
|
|
106
|
+
if (!isTip1034SessionChallenge(candidate)) return false
|
|
107
|
+
return matchesOpen(opened, getSessionSnapshot(candidate))
|
|
108
|
+
}) ?? false
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
38
112
|
/**
|
|
39
113
|
* Creates the low-level TIP-1034 session payment method for use with `Mppx.create()`.
|
|
40
114
|
*
|
|
@@ -45,11 +119,13 @@ export { sessionContextSchema, type SessionContext } from './CredentialState.js'
|
|
|
45
119
|
export function session(parameters: session.Parameters = {}) {
|
|
46
120
|
const {
|
|
47
121
|
account,
|
|
122
|
+
autoSwap: autoSwapParameter,
|
|
48
123
|
channelStore,
|
|
49
124
|
decimals = defaults.decimals,
|
|
50
125
|
escrow: escrowOverride,
|
|
51
126
|
getClient: getClientParameter,
|
|
52
127
|
maxDeposit: maxDepositParameter,
|
|
128
|
+
topUpAmount: topUpAmountParameter,
|
|
53
129
|
onChannelUpdate,
|
|
54
130
|
resolveAccount,
|
|
55
131
|
} = parameters
|
|
@@ -61,60 +137,209 @@ export function session(parameters: session.Parameters = {}) {
|
|
|
61
137
|
const getAccount = Account.getResolver({ account })
|
|
62
138
|
const maxDeposit =
|
|
63
139
|
maxDepositParameter !== undefined ? parseUnits(maxDepositParameter, decimals) : undefined
|
|
140
|
+
const topUpAmount =
|
|
141
|
+
topUpAmountParameter !== undefined ? parseUnits(topUpAmountParameter, decimals) : undefined
|
|
64
142
|
const store = channelStore ?? createChannelStore()
|
|
65
143
|
const sink = { store, notifyUpdate: (entry: ChannelEntry) => onChannelUpdate?.(entry) }
|
|
66
144
|
|
|
145
|
+
const resolveCredentialAccount = async (
|
|
146
|
+
resolved: ChallengeContext,
|
|
147
|
+
context: CredentialContext | undefined,
|
|
148
|
+
entry: ChannelEntry | undefined,
|
|
149
|
+
) => {
|
|
150
|
+
const defaultAccount = getAccount(resolved.client, context)
|
|
151
|
+
const descriptor = context?.action
|
|
152
|
+
? context.descriptor
|
|
153
|
+
: (entry?.descriptor ??
|
|
154
|
+
resolveRecoverContext({ context, snapshot: resolved.snapshot })?.descriptor)
|
|
155
|
+
return (
|
|
156
|
+
(await resolveAccount?.({
|
|
157
|
+
account: defaultAccount,
|
|
158
|
+
chainId: resolved.chainId,
|
|
159
|
+
operation: {
|
|
160
|
+
kind: 'authorizePaymentChannel',
|
|
161
|
+
...(descriptor ? { authority: Channel.resolveAuthorizedSigner(descriptor) } : {}),
|
|
162
|
+
},
|
|
163
|
+
})) ?? defaultAccount
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const resolveCredentialPlan = async (
|
|
168
|
+
resolved: ChallengeContext,
|
|
169
|
+
context: CredentialContext | undefined,
|
|
170
|
+
entry: ChannelEntry | undefined,
|
|
171
|
+
) =>
|
|
172
|
+
planCredential({
|
|
173
|
+
account: await resolveCredentialAccount(resolved, context, entry),
|
|
174
|
+
entry,
|
|
175
|
+
context,
|
|
176
|
+
decimals,
|
|
177
|
+
maxDeposit,
|
|
178
|
+
resolved,
|
|
179
|
+
})
|
|
180
|
+
|
|
67
181
|
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
|
-
},
|
|
182
|
+
canHandleChallenge: ({ challenge }) => isTip1034SessionChallenge(challenge),
|
|
76
183
|
context: sessionContextSchema,
|
|
77
|
-
async createCredential(
|
|
184
|
+
async createCredential(parameters) {
|
|
185
|
+
const { challenge, context } = parameters
|
|
78
186
|
const resolved = await resolveChallengeContext({
|
|
79
187
|
challenge,
|
|
80
188
|
escrowOverride,
|
|
81
189
|
getClient,
|
|
82
190
|
})
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
191
|
+
const attempt = MethodResponse.getAttempt(parameters)
|
|
192
|
+
let release = () => {}
|
|
193
|
+
try {
|
|
194
|
+
let entry = await store.get(resolved.key)
|
|
195
|
+
let plan = await resolveCredentialPlan(resolved, context, entry)
|
|
196
|
+
if (attempt && plan.type === 'open') {
|
|
197
|
+
release = await lockChannel(store, resolved.key)
|
|
198
|
+
const nextEntry = await store.get(resolved.key)
|
|
199
|
+
if (nextEntry?.channelId !== entry?.channelId) {
|
|
200
|
+
entry = nextEntry
|
|
201
|
+
if (entry?.opened) {
|
|
202
|
+
await attempt.prepare()
|
|
203
|
+
entry = await store.get(resolved.key)
|
|
204
|
+
}
|
|
205
|
+
plan = await resolveCredentialPlan(resolved, context, entry)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
let pendingOpen: ChannelEntry | undefined
|
|
209
|
+
// Defer opens only when low-level Fetch owns the response lifecycle.
|
|
210
|
+
// SessionManager unregisters this hook and keeps its existing transaction boundary.
|
|
211
|
+
const credentialSink =
|
|
212
|
+
attempt && plan.type === 'open'
|
|
213
|
+
? {
|
|
214
|
+
store: {
|
|
215
|
+
get: (key: string) => store.get(key),
|
|
216
|
+
async set(next: ChannelEntry) {
|
|
217
|
+
pendingOpen = next
|
|
218
|
+
},
|
|
219
|
+
delete: (key: string) => store.delete(key),
|
|
220
|
+
},
|
|
221
|
+
notifyUpdate() {},
|
|
222
|
+
}
|
|
223
|
+
: sink
|
|
224
|
+
const payload = await executeCredentialPlan(
|
|
225
|
+
plan,
|
|
226
|
+
credentialSink,
|
|
227
|
+
AutoSwap.resolve(context?.autoSwap ?? autoSwapParameter, AutoSwap.defaultCurrencies),
|
|
228
|
+
)
|
|
229
|
+
const credential = await serializeCredential(
|
|
230
|
+
challenge,
|
|
231
|
+
payload,
|
|
232
|
+
resolved.chainId,
|
|
233
|
+
plan.account,
|
|
234
|
+
)
|
|
235
|
+
if (attempt && pendingOpen) {
|
|
236
|
+
const opened = pendingOpen
|
|
237
|
+
attempt.settle = async (outcome) => {
|
|
238
|
+
const accepted =
|
|
239
|
+
outcome.status === 'accepted' || acknowledgesOpen(outcome, challenge, opened)
|
|
240
|
+
if (!accepted && outcome.status === 'pending') return false
|
|
241
|
+
try {
|
|
242
|
+
if (accepted) {
|
|
243
|
+
const current = await store.get(resolved.key)
|
|
244
|
+
if (
|
|
245
|
+
!current?.opened ||
|
|
246
|
+
current.channelId.toLowerCase() !== opened.channelId.toLowerCase()
|
|
247
|
+
) {
|
|
248
|
+
await store.set(opened)
|
|
249
|
+
sink.notifyUpdate(opened)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return true
|
|
253
|
+
} finally {
|
|
254
|
+
release()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else release()
|
|
258
|
+
return credential
|
|
259
|
+
} catch (error) {
|
|
260
|
+
release()
|
|
261
|
+
throw error
|
|
262
|
+
}
|
|
111
263
|
},
|
|
112
264
|
})
|
|
113
265
|
|
|
266
|
+
const topUpChannelIfNeeded = async ({
|
|
267
|
+
challenge,
|
|
268
|
+
channel,
|
|
269
|
+
deposit,
|
|
270
|
+
fetch,
|
|
271
|
+
input,
|
|
272
|
+
requiredCumulative,
|
|
273
|
+
}: {
|
|
274
|
+
challenge: TempoSessionChallenge
|
|
275
|
+
channel: ChannelEntry
|
|
276
|
+
deposit: bigint
|
|
277
|
+
fetch: typeof globalThis.fetch
|
|
278
|
+
input: RequestInfo | URL
|
|
279
|
+
requiredCumulative: bigint
|
|
280
|
+
}) => {
|
|
281
|
+
const knownDeposit = channel.deposit > deposit ? channel.deposit : deposit
|
|
282
|
+
const additionalDeposit = resolveAutomaticTopUp({
|
|
283
|
+
deposit: knownDeposit,
|
|
284
|
+
maxDeposit,
|
|
285
|
+
requiredCumulative,
|
|
286
|
+
suggestedDeposit:
|
|
287
|
+
challenge.request.suggestedDeposit === undefined
|
|
288
|
+
? undefined
|
|
289
|
+
: BigInt(challenge.request.suggestedDeposit),
|
|
290
|
+
topUpAmount,
|
|
291
|
+
})
|
|
292
|
+
if (additionalDeposit > 0n)
|
|
293
|
+
await postTopUp({
|
|
294
|
+
additionalDeposit,
|
|
295
|
+
challenge,
|
|
296
|
+
channel,
|
|
297
|
+
channelId: channel.channelId,
|
|
298
|
+
createSessionCredential: (challenge, context) =>
|
|
299
|
+
method.createCredential({ challenge, context }),
|
|
300
|
+
fetch,
|
|
301
|
+
input,
|
|
302
|
+
})
|
|
303
|
+
const nextDeposit = knownDeposit + additionalDeposit
|
|
304
|
+
if (nextDeposit === channel.deposit) return
|
|
305
|
+
const updated = { ...channel, deposit: nextDeposit }
|
|
306
|
+
await store.set(updated)
|
|
307
|
+
sink.notifyUpdate(updated)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
MethodChallenge.register(method, async ({ challenge, context, fetch, input }) => {
|
|
311
|
+
if (!isTip1034SessionChallenge(challenge)) return
|
|
312
|
+
const sessionContext = context === undefined ? undefined : sessionContextSchema.parse(context)
|
|
313
|
+
if (hasSessionAction(sessionContext)) return
|
|
314
|
+
const resolved = await resolveChallengeContext({ challenge, escrowOverride, getClient })
|
|
315
|
+
const channel = await store.get(resolved.key)
|
|
316
|
+
if (!channel?.opened) return
|
|
317
|
+
const snapshot =
|
|
318
|
+
resolved.snapshot?.channelId.toLowerCase() === channel.channelId.toLowerCase()
|
|
319
|
+
? resolved.snapshot
|
|
320
|
+
: undefined
|
|
321
|
+
const nextCumulative = channel.cumulativeAmount + readSessionChallengeAmount(challenge)
|
|
322
|
+
const snapshotRequired = snapshot ? BigInt(snapshot.requiredCumulative) : nextCumulative
|
|
323
|
+
const requiredCumulative = snapshotRequired > nextCumulative ? snapshotRequired : nextCumulative
|
|
324
|
+
const snapshotDeposit = snapshot ? BigInt(snapshot.deposit) : channel.deposit
|
|
325
|
+
const deposit = snapshotDeposit > channel.deposit ? snapshotDeposit : channel.deposit
|
|
326
|
+
if (requiredCumulative <= deposit && deposit === channel.deposit) return
|
|
327
|
+
const account = await resolveCredentialAccount(resolved, sessionContext, channel)
|
|
328
|
+
if (!canSignDescriptor(account, channel.descriptor)) return
|
|
329
|
+
await topUpChannelIfNeeded({
|
|
330
|
+
challenge,
|
|
331
|
+
channel,
|
|
332
|
+
deposit,
|
|
333
|
+
fetch,
|
|
334
|
+
input,
|
|
335
|
+
requiredCumulative,
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
|
|
114
339
|
return MethodResponse.register(
|
|
115
340
|
method,
|
|
116
341
|
async ({ challenge, credential, fetch, headers, input, refetch, response, signal }) => {
|
|
117
|
-
if (!
|
|
342
|
+
if (!isTip1034SessionChallenge(challenge)) return response
|
|
118
343
|
if (!isEventStream(response)) {
|
|
119
344
|
const credentialContext = requireSessionCredentialContext(
|
|
120
345
|
Credential.deserialize(credential).payload,
|
|
@@ -142,23 +367,16 @@ export function session(parameters: session.Parameters = {}) {
|
|
|
142
367
|
method.createCredential({ challenge, context }),
|
|
143
368
|
fetch,
|
|
144
369
|
getChannel: () => channel ?? null,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const additionalDeposit = requiredCumulative - deposit
|
|
149
|
-
await postTopUp({
|
|
150
|
-
additionalDeposit,
|
|
370
|
+
async topUpIfNeeded({ deposit, requiredCumulative }) {
|
|
371
|
+
if (!channel) return
|
|
372
|
+
await topUpChannelIfNeeded({
|
|
151
373
|
challenge,
|
|
152
374
|
channel,
|
|
153
|
-
|
|
154
|
-
createSessionCredential: (challenge, context) =>
|
|
155
|
-
method.createCredential({ challenge, context }),
|
|
375
|
+
deposit: channel.deposit > deposit ? channel.deposit : deposit,
|
|
156
376
|
fetch,
|
|
157
377
|
input,
|
|
378
|
+
requiredCumulative,
|
|
158
379
|
})
|
|
159
|
-
channel.deposit += additionalDeposit
|
|
160
|
-
await store.set(channel)
|
|
161
|
-
sink.notifyUpdate(channel)
|
|
162
380
|
},
|
|
163
381
|
} satisfies SsePaymentDriver
|
|
164
382
|
|
|
@@ -179,6 +397,8 @@ export declare namespace session {
|
|
|
179
397
|
|
|
180
398
|
type Parameters = Account.getResolver.Parameters &
|
|
181
399
|
Client.getResolver.Parameters & {
|
|
400
|
+
/** Automatically acquire the session currency from fallback stablecoins before open/top-up. */
|
|
401
|
+
autoSwap?: AutoSwap.resolve.Value | undefined
|
|
182
402
|
/** Pluggable persistence for reusable channels. Defaults to an in-memory store. */
|
|
183
403
|
channelStore?: ChannelStore | undefined
|
|
184
404
|
/** Token decimals for parsing human-readable amounts (default: 6). */
|
|
@@ -187,6 +407,11 @@ export declare namespace session {
|
|
|
187
407
|
escrow?: Address | undefined
|
|
188
408
|
/** Maximum channel deposit in human-readable units. Caps server-suggested opens and automatic top-ups. */
|
|
189
409
|
maxDeposit?: string | undefined
|
|
410
|
+
/**
|
|
411
|
+
* Preferred automatic top-up size in human-readable units. When omitted,
|
|
412
|
+
* a bounded server `suggestedDeposit` is preferred, then the exact shortfall.
|
|
413
|
+
*/
|
|
414
|
+
topUpAmount?: string | undefined
|
|
190
415
|
/** Called whenever channel state changes. */
|
|
191
416
|
onChannelUpdate?: ((entry: ChannelEntry) => void) | undefined
|
|
192
417
|
/** Selects the account that signs this session credential after the challenge is known. */
|