mppx 0.12.0 → 0.13.2
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 +64 -0
- package/dist/Method.d.ts +7 -1
- package/dist/Method.js +7 -2
- package/dist/Receipt.d.ts +2 -0
- package/dist/Receipt.js +2 -0
- package/dist/cli/cli.d.ts +8 -0
- package/dist/cli/cli.js +134 -136
- package/dist/cli/plugins/index.js +12 -14
- package/dist/cli/plugins/tempo.js +9 -2
- package/dist/cli/sessions/request.d.ts +1 -1
- package/dist/cli/sessions/request.js +6 -8
- package/dist/cli/utils.d.ts +11 -1
- package/dist/cli/utils.js +15 -2
- package/dist/cli/validate/challenge.d.ts +2 -0
- package/dist/cli/validate/challenge.js +19 -4
- package/dist/cli/validate/discovery.d.ts +1 -0
- package/dist/cli/validate/discovery.js +10 -1
- package/dist/cli/validate/payment.js +34 -12
- package/dist/client/Methods.d.ts +0 -2
- package/dist/client/Methods.js +0 -2
- package/dist/client/Mppx.js +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/internal/Fetch.d.ts +2 -0
- package/dist/client/internal/Fetch.js +22 -7
- package/dist/client/internal/MethodChallenge.d.ts +1 -0
- package/dist/evm/Types.js +1 -1
- package/dist/evm/server/Charge.js +5 -0
- package/dist/integrations/x402/Evm.js +4 -3
- package/dist/internal/HeaderCodec.d.ts +2 -2
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.js +4 -7
- package/dist/middlewares/elysia.d.ts +11 -8
- package/dist/middlewares/elysia.js +67 -14
- package/dist/middlewares/express.js +13 -6
- package/dist/middlewares/hono.js +4 -4
- package/dist/middlewares/nextjs.js +9 -5
- package/dist/proxy/internal/Headers.js +65 -0
- package/dist/proxy/services/stripe.js +1 -0
- package/dist/server/Mppx.d.ts +14 -2
- package/dist/server/Mppx.js +85 -44
- package/dist/server/NodeListener.js +71 -6
- package/dist/server/Request.d.ts +11 -1
- package/dist/server/Request.js +132 -13
- package/dist/server/Transport.d.ts +14 -1
- package/dist/server/Transport.js +1 -0
- package/dist/stripe/server/Methods.d.ts +10 -3
- package/dist/stripe/server/Methods.js +17 -2
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/tempo/Methods.d.ts +1 -0
- package/dist/tempo/Methods.js +1 -0
- package/dist/tempo/client/Charge.js +17 -13
- package/dist/tempo/client/Methods.d.ts +1 -5
- package/dist/tempo/client/Methods.js +10 -5
- package/dist/tempo/client/ResolveAccount.d.ts +3 -0
- package/dist/tempo/client/index.d.ts +0 -3
- package/dist/tempo/client/index.js +0 -2
- package/dist/tempo/internal/fee-payer.d.ts +6 -3
- package/dist/tempo/internal/fee-payer.js +23 -6
- package/dist/tempo/internal/machine-token-charge.d.ts +23 -0
- package/dist/tempo/internal/machine-token-charge.js +2 -1
- package/dist/tempo/legacy/index.d.ts +1 -3
- package/dist/tempo/legacy/index.js +1 -3
- package/dist/tempo/server/Charge.js +87 -53
- package/dist/tempo/server/Methods.d.ts +55 -10
- package/dist/tempo/server/Subscription.d.ts +1 -1
- package/dist/tempo/server/Subscription.js +31 -4
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.js +28 -11
- package/dist/tempo/session/client/ChannelOps.js +5 -0
- package/dist/tempo/session/client/Session.d.ts +1 -0
- package/dist/tempo/session/client/Session.js +7 -4
- package/dist/tempo/session/client/SessionManager.js +3 -0
- package/dist/tempo/session/client/Transports.d.ts +8 -1
- package/dist/tempo/session/client/Transports.js +12 -3
- package/dist/tempo/session/precompile/Chain.js +16 -1
- package/dist/tempo/session/precompile/Protocol.d.ts +2 -0
- package/dist/tempo/session/precompile/Voucher.d.ts +16 -0
- package/dist/tempo/session/precompile/Voucher.js +48 -0
- package/dist/tempo/session/server/ChannelStore.d.ts +20 -2
- package/dist/tempo/session/server/ChannelStore.js +25 -4
- package/dist/tempo/session/server/CredentialVerification.d.ts +4 -1
- package/dist/tempo/session/server/CredentialVerification.js +34 -6
- package/dist/tempo/session/server/MeteredStream.d.ts +3 -2
- package/dist/tempo/session/server/MeteredStream.js +85 -29
- package/dist/tempo/session/server/Session.d.ts +2 -1
- package/dist/tempo/session/server/Session.js +2 -1
- package/dist/tempo/session/server/Settlement.d.ts +21 -0
- package/dist/tempo/session/server/Settlement.js +92 -17
- package/dist/tempo/session/server/Sse.d.ts +3 -1
- package/dist/tempo/session/server/Sse.js +105 -32
- package/dist/tempo/session/server/Transports.d.ts +24 -19
- package/dist/tempo/session/server/Transports.js +169 -58
- package/dist/tempo/session/server/Ws.d.ts +5 -1
- package/dist/tempo/session/server/Ws.js +75 -20
- package/dist/tempo/subscription/Store.d.ts +2 -0
- package/dist/tempo/subscription/Store.js +3 -0
- package/dist/validation/core.js +2 -1
- package/dist/x402/server/EvmCharge.d.ts +3 -1
- package/dist/x402/server/EvmCharge.js +14 -1
- package/package.json +6 -6
- package/dist/tempo/legacy/client/ChannelOps.d.ts +0 -69
- package/dist/tempo/legacy/client/ChannelOps.js +0 -168
- package/dist/tempo/legacy/client/Session.d.ts +0 -185
- package/dist/tempo/legacy/client/Session.js +0 -279
- package/dist/tempo/legacy/client/SessionManager.d.ts +0 -100
- package/dist/tempo/legacy/client/SessionManager.js +0 -706
- package/dist/tempo/legacy/client/index.d.ts +0 -7
- package/dist/tempo/legacy/client/index.js +0 -5
|
@@ -49,6 +49,7 @@ export function sse(options) {
|
|
|
49
49
|
const base = Transport.http();
|
|
50
50
|
return Transport.from({
|
|
51
51
|
name: 'sse',
|
|
52
|
+
supportsStreamingReceipts: true,
|
|
52
53
|
captureRequest(request) {
|
|
53
54
|
return (base.captureRequest?.(request) ?? {
|
|
54
55
|
headers: new Headers(request.headers),
|
|
@@ -63,7 +64,7 @@ export function sse(options) {
|
|
|
63
64
|
respondChallenge(options) {
|
|
64
65
|
return base.respondChallenge(options);
|
|
65
66
|
},
|
|
66
|
-
respondReceipt({ credential, envelope, receipt, response, challengeId, input }) {
|
|
67
|
+
async respondReceipt({ credential, envelope, receipt, response, challengeId, input }) {
|
|
67
68
|
const verifiedCredential = envelope?.credential ?? credential;
|
|
68
69
|
const verifiedChallengeId = envelope?.challenge.id ?? challengeId;
|
|
69
70
|
const verifiedRequest = envelope?.request ?? verifiedCredential.challenge.request;
|
|
@@ -112,8 +113,7 @@ export function sse(options) {
|
|
|
112
113
|
if (hasPrepaidSessionTick(currentReceipt)) {
|
|
113
114
|
return baseResponse;
|
|
114
115
|
}
|
|
115
|
-
const
|
|
116
|
-
if (available < tickCost) {
|
|
116
|
+
const paymentRequired = (available) => {
|
|
117
117
|
const error = new Errors.InsufficientBalanceError({
|
|
118
118
|
reason: `requested ${tickCost}, available ${available}`,
|
|
119
119
|
});
|
|
@@ -125,7 +125,10 @@ export function sse(options) {
|
|
|
125
125
|
'Content-Type': 'application/problem+json',
|
|
126
126
|
},
|
|
127
127
|
});
|
|
128
|
-
}
|
|
128
|
+
};
|
|
129
|
+
const available = BigInt(currentReceipt.acceptedCumulative) - BigInt(currentReceipt.spent);
|
|
130
|
+
if (available < tickCost)
|
|
131
|
+
return paymentRequired(available);
|
|
129
132
|
const chargedReceipt = {
|
|
130
133
|
...currentReceipt,
|
|
131
134
|
spent: (BigInt(currentReceipt.spent) + tickCost).toString(),
|
|
@@ -150,7 +153,9 @@ export function sse(options) {
|
|
|
150
153
|
// For null-body statuses, the request shape determines whether the
|
|
151
154
|
// response is management (no charge) or plain content (charge one tick).
|
|
152
155
|
if (isNullBodyStatus(chargedResponse.status)) {
|
|
153
|
-
|
|
156
|
+
const result = await chargePlainResponse();
|
|
157
|
+
if (!result.ok)
|
|
158
|
+
return paymentRequired(result.channel.highestVoucherAmount - result.channel.spent);
|
|
154
159
|
return chargedResponse;
|
|
155
160
|
}
|
|
156
161
|
const stream = new ReadableStream({
|
|
@@ -193,20 +198,32 @@ export function sse(options) {
|
|
|
193
198
|
/** Default SSE serve: iterates values and emits `event: message` per value. */
|
|
194
199
|
export function defaultServe(options) {
|
|
195
200
|
const iterable = typeof options.generate === 'function' ? options.generate() : options.generate;
|
|
201
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
196
202
|
const encoder = new TextEncoder();
|
|
203
|
+
let closed = false;
|
|
197
204
|
const stream = new ReadableStream({
|
|
198
|
-
async
|
|
205
|
+
async pull(controller) {
|
|
206
|
+
if (closed)
|
|
207
|
+
return;
|
|
199
208
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
209
|
+
const next = await iterator.next();
|
|
210
|
+
if (next.done) {
|
|
211
|
+
closed = true;
|
|
212
|
+
controller.close();
|
|
213
|
+
return;
|
|
202
214
|
}
|
|
215
|
+
controller.enqueue(encoder.encode(Sse_core.formatMessageEvent(next.value)));
|
|
203
216
|
}
|
|
204
217
|
catch (e) {
|
|
218
|
+
closed = true;
|
|
205
219
|
controller.error(e);
|
|
206
220
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
},
|
|
222
|
+
cancel() {
|
|
223
|
+
closed = true;
|
|
224
|
+
void Promise.resolve()
|
|
225
|
+
.then(() => iterator.return?.())
|
|
226
|
+
.catch(() => { });
|
|
210
227
|
},
|
|
211
228
|
});
|
|
212
229
|
return new Response(stream, {
|
|
@@ -114,12 +114,17 @@ export async function createVoucherPayload(client, account, descriptor, cumulati
|
|
|
114
114
|
*/
|
|
115
115
|
export async function createClosePayload(client, account, descriptor, cumulativeAmount, chainId, escrow = tip20ChannelEscrow) {
|
|
116
116
|
const voucher = await createVoucherPayload(client, account, descriptor, cumulativeAmount, chainId, escrow);
|
|
117
|
+
const closeSignature = await Voucher.signCloseAuthorization(client, account, {
|
|
118
|
+
channelId: voucher.channelId,
|
|
119
|
+
cumulativeAmount: uint96(BigInt(voucher.cumulativeAmount)),
|
|
120
|
+
}, escrow, chainId);
|
|
117
121
|
return {
|
|
118
122
|
action: 'close',
|
|
119
123
|
channelId: voucher.channelId,
|
|
120
124
|
descriptor,
|
|
121
125
|
cumulativeAmount: voucher.cumulativeAmount,
|
|
122
126
|
signature: voucher.signature,
|
|
127
|
+
closeSignature,
|
|
123
128
|
};
|
|
124
129
|
}
|
|
125
130
|
/**
|
|
@@ -45,6 +45,7 @@ export declare function session(parameters?: session.Parameters): Method.Client<
|
|
|
45
45
|
}, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniObject<{
|
|
46
46
|
action: import("zod/mini").ZodMiniLiteral<"close">;
|
|
47
47
|
channelId: import("zod/mini").ZodMiniString<string>;
|
|
48
|
+
closeSignature: import("zod/mini").ZodMiniString<string>;
|
|
48
49
|
cumulativeAmount: import("zod/mini").ZodMiniString<string>;
|
|
49
50
|
descriptor: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniCustom<Channel.ChannelDescriptor, Channel.ChannelDescriptor>>;
|
|
50
51
|
signature: import("zod/mini").ZodMiniString<string>;
|
|
@@ -195,7 +195,7 @@ export function session(parameters = {}) {
|
|
|
195
195
|
}
|
|
196
196
|
},
|
|
197
197
|
});
|
|
198
|
-
const topUpChannelIfNeeded = async ({ challenge, channel, deposit, fetch, input, requiredCumulative, }) => {
|
|
198
|
+
const topUpChannelIfNeeded = async ({ challenge, channel, deposit, fetch, input, requiredCumulative, signal, }) => {
|
|
199
199
|
const knownDeposit = channel.deposit > deposit ? channel.deposit : deposit;
|
|
200
200
|
const additionalDeposit = resolveAutomaticTopUp({
|
|
201
201
|
deposit: knownDeposit,
|
|
@@ -215,6 +215,7 @@ export function session(parameters = {}) {
|
|
|
215
215
|
createSessionCredential: (challenge, context) => method.createCredential({ challenge, context }),
|
|
216
216
|
fetch,
|
|
217
217
|
input,
|
|
218
|
+
signal,
|
|
218
219
|
});
|
|
219
220
|
const nextDeposit = knownDeposit + additionalDeposit;
|
|
220
221
|
if (nextDeposit === channel.deposit)
|
|
@@ -228,7 +229,7 @@ export function session(parameters = {}) {
|
|
|
228
229
|
await store.set(updated);
|
|
229
230
|
sink.notifyUpdate(updated);
|
|
230
231
|
};
|
|
231
|
-
MethodChallenge.register(method, async ({ challenge, context, fetch, input }) => {
|
|
232
|
+
MethodChallenge.register(method, async ({ challenge, context, fetch, input, signal }) => {
|
|
232
233
|
if (!isTip1034SessionChallenge(challenge))
|
|
233
234
|
return;
|
|
234
235
|
const sessionContext = context === undefined ? undefined : sessionContextSchema.parse(context);
|
|
@@ -264,6 +265,7 @@ export function session(parameters = {}) {
|
|
|
264
265
|
fetch,
|
|
265
266
|
input,
|
|
266
267
|
requiredCumulative,
|
|
268
|
+
signal,
|
|
267
269
|
});
|
|
268
270
|
});
|
|
269
271
|
return MethodResponse.register(method, async ({ challenge, credential, fetch, headers, input, refetch, response, signal }) => {
|
|
@@ -289,7 +291,7 @@ export function session(parameters = {}) {
|
|
|
289
291
|
createSessionCredential: (challenge, context) => method.createCredential({ challenge, context }),
|
|
290
292
|
fetch,
|
|
291
293
|
getChannel: () => channel ?? null,
|
|
292
|
-
async topUpIfNeeded({ deposit, requiredCumulative }) {
|
|
294
|
+
async topUpIfNeeded({ deposit, requiredCumulative, signal: topUpSignal }) {
|
|
293
295
|
if (!channel)
|
|
294
296
|
return;
|
|
295
297
|
await topUpChannelIfNeeded({
|
|
@@ -299,11 +301,12 @@ export function session(parameters = {}) {
|
|
|
299
301
|
fetch,
|
|
300
302
|
input,
|
|
301
303
|
requiredCumulative,
|
|
304
|
+
signal: topUpSignal,
|
|
302
305
|
});
|
|
303
306
|
},
|
|
304
307
|
};
|
|
305
308
|
return wrapSseResponse({
|
|
306
|
-
onNeedVoucher: (event) => handleSseNeedVoucher({ challenge, driver, input }, event),
|
|
309
|
+
onNeedVoucher: (event, streamSignal) => handleSseNeedVoucher({ challenge, driver, input, signal: streamSignal }, event),
|
|
307
310
|
onReceipt() { },
|
|
308
311
|
response,
|
|
309
312
|
signal,
|
|
@@ -235,6 +235,7 @@ export function sessionManager(parameters) {
|
|
|
235
235
|
allowedChainIds: parameters.allowedChainIds,
|
|
236
236
|
account: parameters.account,
|
|
237
237
|
getClient: parameters.client ? () => parameters.client : parameters.getClient,
|
|
238
|
+
resolveAccount: parameters.resolveAccount,
|
|
238
239
|
});
|
|
239
240
|
const wrappedFetch = Fetch.from({
|
|
240
241
|
fetch: config.fetch,
|
|
@@ -486,6 +487,7 @@ export function sessionManager(parameters) {
|
|
|
486
487
|
channelId: parameters.channelId,
|
|
487
488
|
additionalDeposit,
|
|
488
489
|
knownDeposit: deposit,
|
|
490
|
+
signal: parameters.signal,
|
|
489
491
|
});
|
|
490
492
|
}
|
|
491
493
|
async function restoreCumulative(channelId, cumulativeAmount) {
|
|
@@ -700,6 +702,7 @@ export function sessionManager(parameters) {
|
|
|
700
702
|
context,
|
|
701
703
|
fetch: config.fetch,
|
|
702
704
|
input: probeUrl,
|
|
705
|
+
signal: init?.signal,
|
|
703
706
|
});
|
|
704
707
|
return createSessionCredential(challenge, context);
|
|
705
708
|
},
|
|
@@ -80,6 +80,8 @@ export type TopUpRequirement = {
|
|
|
80
80
|
input: RequestInfo | URL;
|
|
81
81
|
/** Minimum cumulative voucher amount the server needs. */
|
|
82
82
|
requiredCumulative: bigint;
|
|
83
|
+
/** Cancels nested management requests for this top-up. */
|
|
84
|
+
signal?: AbortSignal | undefined;
|
|
83
85
|
};
|
|
84
86
|
/** Inputs needed to validate a caller-requested manager top-up. */
|
|
85
87
|
export type ResolveManualTopUpParameters = {
|
|
@@ -156,6 +158,8 @@ export type ResolveNeedVoucherContextParameters = {
|
|
|
156
158
|
getChannel(): ChannelEntry | null;
|
|
157
159
|
/** Original paid resource URL; converted to management URL by the caller. */
|
|
158
160
|
input: RequestInfo | URL;
|
|
161
|
+
/** Cancels top-up work triggered by this event. */
|
|
162
|
+
signal?: AbortSignal | undefined;
|
|
159
163
|
/** Performs the deposit top-up when server-required cumulative exceeds deposit. */
|
|
160
164
|
topUpIfNeeded(parameters: TopUpRequirement): Promise<void>;
|
|
161
165
|
};
|
|
@@ -207,6 +211,8 @@ export type PostTopUpParameters = {
|
|
|
207
211
|
fetch: typeof globalThis.fetch;
|
|
208
212
|
/** Original paid resource URL; normalized to a management URL before posting. */
|
|
209
213
|
input: RequestInfo | URL;
|
|
214
|
+
/** Cancels the top-up management request. */
|
|
215
|
+
signal?: AbortSignal | undefined;
|
|
210
216
|
};
|
|
211
217
|
/** Inputs for retrying an HTTP 402 with a top-up/voucher management round trip. */
|
|
212
218
|
export type RetryHttpPaymentRequiredParameters = {
|
|
@@ -334,7 +340,7 @@ export type SseResponseFrame = {
|
|
|
334
340
|
/** Inputs for driving an open paid SSE response. */
|
|
335
341
|
export type DriveSseResponseParameters = {
|
|
336
342
|
/** Handles a request for the stream's next cumulative voucher. */
|
|
337
|
-
onNeedVoucher(event: NeedVoucherEvent): Promise<void>;
|
|
343
|
+
onNeedVoucher(event: NeedVoucherEvent, signal?: AbortSignal | undefined): Promise<void>;
|
|
338
344
|
/** Handles a payment receipt emitted by the stream. */
|
|
339
345
|
onReceipt(receipt: SessionReceipt): void;
|
|
340
346
|
/** Open SSE response to drive. */
|
|
@@ -351,6 +357,7 @@ export declare function handleSseNeedVoucher(parameters: {
|
|
|
351
357
|
challenge: TempoSessionChallenge | null;
|
|
352
358
|
driver: SsePaymentDriver;
|
|
353
359
|
input: RequestInfo | URL;
|
|
360
|
+
signal?: AbortSignal | undefined;
|
|
354
361
|
}, event: NeedVoucherEvent): Promise<void>;
|
|
355
362
|
/** Options accepted by the auto-driving WebSocket session flow. */
|
|
356
363
|
export type WebSocketDriverOptions = {
|
|
@@ -214,6 +214,7 @@ export async function resolveNeedVoucherContext(parameters) {
|
|
|
214
214
|
channelId: parameters.expectedChannelId,
|
|
215
215
|
deposit: eventAmounts.deposit,
|
|
216
216
|
requiredCumulative: eventAmounts.requiredCumulative,
|
|
217
|
+
...(parameters.signal ? { signal: parameters.signal } : {}),
|
|
217
218
|
});
|
|
218
219
|
const channel = parameters.getChannel();
|
|
219
220
|
if (!channel || channel.channelId !== parameters.expectedChannelId) {
|
|
@@ -314,6 +315,7 @@ export async function postTopUp(parameters) {
|
|
|
314
315
|
createSessionCredential: parameters.createSessionCredential,
|
|
315
316
|
fetch: parameters.fetch,
|
|
316
317
|
input: parameters.input,
|
|
318
|
+
signal: parameters.signal,
|
|
317
319
|
});
|
|
318
320
|
if (!response.ok)
|
|
319
321
|
throw new Error(`Top-up POST failed with status ${response.status}`);
|
|
@@ -324,6 +326,7 @@ export async function postTopUp(parameters) {
|
|
|
324
326
|
async function postManagementCredential(parameters) {
|
|
325
327
|
const post = async (challenge) => parameters.fetch(managementInput(parameters.input), {
|
|
326
328
|
method: 'POST',
|
|
329
|
+
...(parameters.signal ? { signal: parameters.signal } : {}),
|
|
327
330
|
headers: {
|
|
328
331
|
[Constants.Headers.authorization]: await parameters.createSessionCredential(challenge, parameters.context),
|
|
329
332
|
},
|
|
@@ -352,6 +355,8 @@ export async function retryHttpPaymentRequired(parameters) {
|
|
|
352
355
|
channelId: snapshot.channelId,
|
|
353
356
|
deposit: BigInt(snapshot.deposit),
|
|
354
357
|
requiredCumulative,
|
|
358
|
+
signal: parameters.init?.signal ??
|
|
359
|
+
(parameters.input instanceof Request ? parameters.input.signal : undefined),
|
|
355
360
|
});
|
|
356
361
|
const currentChannel = parameters.getChannel();
|
|
357
362
|
if (!currentChannel?.descriptor || currentChannel.channelId !== snapshot.channelId) {
|
|
@@ -453,7 +458,8 @@ export async function closeHttpSession(parameters) {
|
|
|
453
458
|
* credential creation, and top-up policy stay in `SessionManager`.
|
|
454
459
|
*/
|
|
455
460
|
export async function openSseSession(input, init, driver) {
|
|
456
|
-
const { onReceipt, signal, ...fetchInit } = init ?? {};
|
|
461
|
+
const { onReceipt, signal: explicitSignal, ...fetchInit } = init ?? {};
|
|
462
|
+
const signal = explicitSignal ?? (input instanceof Request ? input.signal : undefined);
|
|
457
463
|
const sseInit = {
|
|
458
464
|
...fetchInit,
|
|
459
465
|
headers: {
|
|
@@ -475,7 +481,7 @@ export function consumeSseSessionResponse(input, response, options, driver) {
|
|
|
475
481
|
throw new Error('Response has no body.');
|
|
476
482
|
const challenge = driver.getChallenge();
|
|
477
483
|
return iterateSseMessages({
|
|
478
|
-
onNeedVoucher: (event) => handleSseNeedVoucher({ challenge, driver, input }, event),
|
|
484
|
+
onNeedVoucher: (event) => handleSseNeedVoucher({ challenge, driver, input, signal: options?.signal }, event),
|
|
479
485
|
onReceipt(receipt) {
|
|
480
486
|
driver.acceptReceipt(receipt);
|
|
481
487
|
options?.onReceipt?.(receipt);
|
|
@@ -535,7 +541,7 @@ export async function* driveSseResponse(parameters) {
|
|
|
535
541
|
yield { data: event.data, raw };
|
|
536
542
|
break;
|
|
537
543
|
case 'payment-need-voucher':
|
|
538
|
-
await parameters.onNeedVoucher(event.data);
|
|
544
|
+
await parameters.onNeedVoucher(event.data, parameters.signal);
|
|
539
545
|
break;
|
|
540
546
|
case 'payment-receipt':
|
|
541
547
|
parameters.onReceipt(event.data);
|
|
@@ -632,6 +638,7 @@ export async function handleSseNeedVoucher(parameters, event) {
|
|
|
632
638
|
expectedChannelId: channel.channelId,
|
|
633
639
|
getChannel: parameters.driver.getChannel,
|
|
634
640
|
input: parameters.input,
|
|
641
|
+
signal: parameters.signal,
|
|
635
642
|
topUpIfNeeded: parameters.driver.topUpIfNeeded,
|
|
636
643
|
});
|
|
637
644
|
if (resolution.status !== 'ready')
|
|
@@ -642,6 +649,7 @@ export async function handleSseNeedVoucher(parameters, event) {
|
|
|
642
649
|
createSessionCredential: parameters.driver.createSessionCredential,
|
|
643
650
|
fetch: parameters.driver.fetch,
|
|
644
651
|
input: parameters.input,
|
|
652
|
+
signal: parameters.signal,
|
|
645
653
|
});
|
|
646
654
|
if (!voucherResponse.ok) {
|
|
647
655
|
throw new Error(`Voucher POST failed with status ${voucherResponse.status}`);
|
|
@@ -868,6 +876,7 @@ async function handleNeedVoucher(parameters, event) {
|
|
|
868
876
|
expectedChannelId: parameters.socketState.channelId,
|
|
869
877
|
getChannel: parameters.driver.getChannel,
|
|
870
878
|
input: parameters.driver.httpUrl,
|
|
879
|
+
signal: parameters.driver.options?.signal,
|
|
871
880
|
topUpIfNeeded: parameters.driver.topUpIfNeeded,
|
|
872
881
|
});
|
|
873
882
|
if (resolution.status === 'ignored') {
|
|
@@ -292,8 +292,23 @@ export async function simulateCredentialTransaction(parameters) {
|
|
|
292
292
|
}
|
|
293
293
|
function validateCredentialSponsorship(parameters) {
|
|
294
294
|
const { allowedFeeTokens, challengeExpires, chainId, details, feePayer, feePayerPolicy, transaction, } = parameters;
|
|
295
|
-
if (!feePayer
|
|
295
|
+
if (!feePayer)
|
|
296
296
|
return;
|
|
297
|
+
if (feePayer === true) {
|
|
298
|
+
assertPrecompileFeePayerPolicy({ prepared: transaction, policy: feePayerPolicy });
|
|
299
|
+
if (feePayerPolicy?.allowKeyAuthorization === false &&
|
|
300
|
+
transaction.keyAuthorization !== undefined)
|
|
301
|
+
throw new BadRequestError({ reason: 'fee-payer policy keyAuthorization is not allowed' });
|
|
302
|
+
FeePayer.assertAllowedFeeToken(transaction, allowedFeeTokens);
|
|
303
|
+
FeePayer.assertTransactionPolicy({
|
|
304
|
+
challengeExpires,
|
|
305
|
+
chainId,
|
|
306
|
+
details,
|
|
307
|
+
policy: feePayerPolicy,
|
|
308
|
+
transaction,
|
|
309
|
+
});
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
297
312
|
FeePayer.prepareSponsoredTransaction({
|
|
298
313
|
account: feePayer,
|
|
299
314
|
allowedFeeTokens,
|
|
@@ -106,6 +106,8 @@ export type CloseCredentialPayload = {
|
|
|
106
106
|
cumulativeAmount: RawAmountString;
|
|
107
107
|
/** Voucher signature for `cumulativeAmount`. */
|
|
108
108
|
signature: Hex;
|
|
109
|
+
/** Separate signature authorizing the cooperative close action. */
|
|
110
|
+
closeSignature: Hex;
|
|
109
111
|
};
|
|
110
112
|
/**
|
|
111
113
|
* TIP20EscrowChannel precompile session credential payload (discriminated union).
|
|
@@ -22,10 +22,26 @@ export declare const voucherTypes: {
|
|
|
22
22
|
readonly type: 'uint96';
|
|
23
23
|
}];
|
|
24
24
|
};
|
|
25
|
+
/** EIP-712 type that distinguishes cooperative close authorization from a voucher. */
|
|
26
|
+
export declare const closeAuthorizationTypes: {
|
|
27
|
+
readonly CloseAuthorization: readonly [{
|
|
28
|
+
readonly name: 'channelId';
|
|
29
|
+
readonly type: 'bytes32';
|
|
30
|
+
}, {
|
|
31
|
+
readonly name: 'cumulativeAmount';
|
|
32
|
+
readonly type: 'uint96';
|
|
33
|
+
}];
|
|
34
|
+
};
|
|
25
35
|
/**
|
|
26
36
|
* Sign a voucher with an account.
|
|
27
37
|
*/
|
|
28
38
|
export declare function signVoucher(client: Client, account: Account, voucher: Voucher, verifyingContract: Address, chainId: number): Promise<Hex>;
|
|
39
|
+
/**
|
|
40
|
+
* Signs a distinct EIP-712 authorization for cooperatively closing a channel.
|
|
41
|
+
*/
|
|
42
|
+
export declare function signCloseAuthorization(client: Client, account: Account, authorization: Voucher, verifyingContract: Address, chainId: number): Promise<Hex>;
|
|
43
|
+
/** Verifies that a cooperative-close signature belongs to a channel authority. */
|
|
44
|
+
export declare function verifyCloseAuthorization(escrowContract: Address, chainId: number, authorization: SignedVoucher, expectedSigners: readonly Address[]): boolean;
|
|
29
45
|
/**
|
|
30
46
|
* Verify a voucher signature matches the expected signer.
|
|
31
47
|
*
|
|
@@ -29,6 +29,21 @@ export const voucherTypes = {
|
|
|
29
29
|
{ name: 'cumulativeAmount', type: 'uint96' },
|
|
30
30
|
],
|
|
31
31
|
};
|
|
32
|
+
/** EIP-712 type that distinguishes cooperative close authorization from a voucher. */
|
|
33
|
+
export const closeAuthorizationTypes = {
|
|
34
|
+
CloseAuthorization: [
|
|
35
|
+
{ name: 'channelId', type: 'bytes32' },
|
|
36
|
+
{ name: 'cumulativeAmount', type: 'uint96' },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
function getCloseAuthorizationPayload(verifyingContract, chainId, authorization) {
|
|
40
|
+
return hashTypedData({
|
|
41
|
+
domain: getVoucherDomain(verifyingContract, chainId),
|
|
42
|
+
types: closeAuthorizationTypes,
|
|
43
|
+
primaryType: 'CloseAuthorization',
|
|
44
|
+
message: authorization,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
32
47
|
function getVoucherDigest(chainId, voucher) {
|
|
33
48
|
return Channel.getVoucherSignPayload({
|
|
34
49
|
chainId,
|
|
@@ -90,6 +105,39 @@ export async function signVoucher(client, account, voucher, verifyingContract, c
|
|
|
90
105
|
throw new Error(`TIP-1034 vouchers require a TIP-1020 primitive signature; received "${envelope.type}".`);
|
|
91
106
|
return SignatureEnvelope.serialize(envelope);
|
|
92
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Signs a distinct EIP-712 authorization for cooperatively closing a channel.
|
|
110
|
+
*/
|
|
111
|
+
export async function signCloseAuthorization(client, account, authorization, verifyingContract, chainId) {
|
|
112
|
+
const signature = await signTypedData(client, {
|
|
113
|
+
account,
|
|
114
|
+
domain: getVoucherDomain(verifyingContract, chainId),
|
|
115
|
+
types: closeAuthorizationTypes,
|
|
116
|
+
primaryType: 'CloseAuthorization',
|
|
117
|
+
message: authorization,
|
|
118
|
+
});
|
|
119
|
+
const envelope = SignatureEnvelope.from(signature);
|
|
120
|
+
if (!isPrimitiveEnvelope(envelope.type))
|
|
121
|
+
throw new Error(`TIP-1034 close authorizations require a TIP-1020 primitive signature; received "${envelope.type}".`);
|
|
122
|
+
return SignatureEnvelope.serialize(envelope);
|
|
123
|
+
}
|
|
124
|
+
/** Verifies that a cooperative-close signature belongs to a channel authority. */
|
|
125
|
+
export function verifyCloseAuthorization(escrowContract, chainId, authorization, expectedSigners) {
|
|
126
|
+
try {
|
|
127
|
+
const envelope = SignatureEnvelope.from(authorization.signature);
|
|
128
|
+
if (!isPrimitiveEnvelope(envelope.type))
|
|
129
|
+
return false;
|
|
130
|
+
if (SignatureEnvelope.serialize(envelope).toLowerCase() !== authorization.signature.toLowerCase())
|
|
131
|
+
return false;
|
|
132
|
+
const payload = getCloseAuthorizationPayload(escrowContract, chainId, authorization);
|
|
133
|
+
const signer = SignatureEnvelope.extractAddress({ payload, signature: envelope });
|
|
134
|
+
const valid = SignatureEnvelope.verify(envelope, { address: signer, payload });
|
|
135
|
+
return valid && expectedSigners.some((expected) => TempoAddress.isEqual(signer, expected));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
93
141
|
/**
|
|
94
142
|
* Verify a voucher signature matches the expected signer.
|
|
95
143
|
*
|
|
@@ -257,6 +257,14 @@ export interface BaseState {
|
|
|
257
257
|
token: Address;
|
|
258
258
|
/** Number of charge operations (API requests) fulfilled in the current session. */
|
|
259
259
|
units: number;
|
|
260
|
+
/** Uncommitted stream charges shared across workers. */
|
|
261
|
+
streamReservations?: Record<string, {
|
|
262
|
+
amount: bigint;
|
|
263
|
+
units: number;
|
|
264
|
+
expiresAt: number;
|
|
265
|
+
}> | undefined;
|
|
266
|
+
/** Active cross-worker claim for server-scheduled settlement. */
|
|
267
|
+
scheduledSettlementLease?: ScheduledSettlementLease | undefined;
|
|
260
268
|
/** ISO 8601 timestamp of the last server-scheduled settlement. */
|
|
261
269
|
lastSettlementAt?: string | undefined;
|
|
262
270
|
/** Cumulative spent value when the last server-scheduled settlement ran. */
|
|
@@ -264,6 +272,11 @@ export interface BaseState {
|
|
|
264
272
|
/** Charge operation count when the last server-scheduled settlement ran. */
|
|
265
273
|
lastSettlementUnits?: number | undefined;
|
|
266
274
|
}
|
|
275
|
+
/** Expiring ownership claim for one server-scheduled settlement attempt. */
|
|
276
|
+
export type ScheduledSettlementLease = {
|
|
277
|
+
expiresAt: number;
|
|
278
|
+
owner: string;
|
|
279
|
+
};
|
|
267
280
|
/** Returns whether a channel is backed by the TIP20EscrowChannel precompile. */
|
|
268
281
|
export declare function isPrecompileState(state: State): state is BaseState & PrecompileBackendState;
|
|
269
282
|
/** Returns the greater bigint value without decreasing persisted monotonic state. */
|
|
@@ -317,6 +330,8 @@ export declare function verifyAndAcceptVoucher(parameters: VerifyAndAcceptVouche
|
|
|
317
330
|
* - **D1 / SQL**: Database transactions
|
|
318
331
|
*/
|
|
319
332
|
export type ChannelStore = {
|
|
333
|
+
/** False when the backing store only serializes mutations within this process. */
|
|
334
|
+
atomic?: boolean | undefined;
|
|
320
335
|
getChannel(channelId: Hex): Promise<State | null>;
|
|
321
336
|
/**
|
|
322
337
|
* Atomic read-modify-write for channel state.
|
|
@@ -329,11 +344,12 @@ export type ChannelStore = {
|
|
|
329
344
|
* Returns a `Promise` that resolves once `updateChannel` is called for
|
|
330
345
|
* `channelId`. Implementations should resolve immediately if the channel
|
|
331
346
|
* was updated between the call to `waitForUpdate` and the `Promise`
|
|
332
|
-
* being awaited.
|
|
347
|
+
* being awaited. Implementations must reject and release the waiter when
|
|
348
|
+
* `signal` aborts.
|
|
333
349
|
*
|
|
334
350
|
* When not implemented, callers fall back to polling.
|
|
335
351
|
*/
|
|
336
|
-
waitForUpdate?(channelId: Hex): Promise<void>;
|
|
352
|
+
waitForUpdate?(channelId: Hex, signal?: AbortSignal): Promise<void>;
|
|
337
353
|
/**
|
|
338
354
|
* Atomic read-modify-write that returns the callback's `result` directly.
|
|
339
355
|
*
|
|
@@ -355,4 +371,6 @@ export declare function fromStore(store: Store.Store | Store.AtomicStore): Chann
|
|
|
355
371
|
* when the channel exists but cannot currently be charged.
|
|
356
372
|
*/
|
|
357
373
|
export declare function deductFromChannel(store: ChannelStore, channelId: State['channelId'], amount: bigint): Promise<DeductResult>;
|
|
374
|
+
/** Returns unexpired stream reservations that ordinary deductions must respect. */
|
|
375
|
+
export declare function reservedStreamAmount(state: Pick<State, 'streamReservations'>): bigint;
|
|
358
376
|
//# sourceMappingURL=ChannelStore.d.ts.map
|
|
@@ -357,6 +357,7 @@ export function fromStore(store) {
|
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
const cs = {
|
|
360
|
+
atomic: typeof atomicUpdate === 'function',
|
|
360
361
|
async getChannel(channelId) {
|
|
361
362
|
const normalizedChannelId = normalizeChannelId(channelId);
|
|
362
363
|
return normalizeMaybeState(normalizedChannelId, await stateStore.get(normalizedChannelId));
|
|
@@ -364,15 +365,30 @@ export function fromStore(store) {
|
|
|
364
365
|
async updateChannel(channelId, fn) {
|
|
365
366
|
return update(channelId, fn);
|
|
366
367
|
},
|
|
367
|
-
waitForUpdate(channelId) {
|
|
368
|
-
return new Promise((resolve) => {
|
|
368
|
+
waitForUpdate(channelId, signal) {
|
|
369
|
+
return new Promise((resolve, reject) => {
|
|
369
370
|
const normalizedChannelId = normalizeChannelId(channelId);
|
|
371
|
+
if (signal?.aborted) {
|
|
372
|
+
reject(signal.reason ?? new Error('aborted'));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
370
375
|
let set = runtime.waiters.get(normalizedChannelId);
|
|
371
376
|
if (!set) {
|
|
372
377
|
set = new Set();
|
|
373
378
|
runtime.waiters.set(normalizedChannelId, set);
|
|
374
379
|
}
|
|
375
|
-
|
|
380
|
+
const settle = () => {
|
|
381
|
+
signal?.removeEventListener('abort', onAbort);
|
|
382
|
+
resolve();
|
|
383
|
+
};
|
|
384
|
+
const onAbort = () => {
|
|
385
|
+
set?.delete(settle);
|
|
386
|
+
if (set?.size === 0)
|
|
387
|
+
runtime.waiters.delete(normalizedChannelId);
|
|
388
|
+
reject(signal?.reason ?? new Error('aborted'));
|
|
389
|
+
};
|
|
390
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
391
|
+
set.add(settle);
|
|
376
392
|
});
|
|
377
393
|
},
|
|
378
394
|
};
|
|
@@ -413,9 +429,14 @@ function planDeduction(current, amount) {
|
|
|
413
429
|
return { op: 'noop', result: { ok: false, channel: current } };
|
|
414
430
|
if (current.closeRequestedAt !== 0n)
|
|
415
431
|
return { op: 'noop', result: { ok: false, channel: current } };
|
|
416
|
-
if (current.highestVoucherAmount - current.spent < amount)
|
|
432
|
+
if (current.highestVoucherAmount - current.spent - reservedStreamAmount(current) < amount)
|
|
417
433
|
return { op: 'noop', result: { ok: false, channel: current } };
|
|
418
434
|
const next = { ...current, spent: current.spent + amount, units: current.units + 1 };
|
|
419
435
|
return { op: 'set', value: next, result: { ok: true, channel: next } };
|
|
420
436
|
}
|
|
437
|
+
/** Returns unexpired stream reservations that ordinary deductions must respect. */
|
|
438
|
+
export function reservedStreamAmount(state) {
|
|
439
|
+
const now = Date.now();
|
|
440
|
+
return Object.values(state.streamReservations ?? {}).reduce((total, reservation) => total + (reservation.expiresAt > now ? reservation.amount : 0n), 0n);
|
|
441
|
+
}
|
|
421
442
|
//# sourceMappingURL=ChannelStore.js.map
|
|
@@ -104,7 +104,10 @@ export type VoucherCredentialActionParameters = BroadcastCredentialActionParamet
|
|
|
104
104
|
/** Inputs for broadcasting and settling a cooperative close credential. */
|
|
105
105
|
export type CloseCredentialActionParameters = BroadcastCredentialActionParameters<'close'>;
|
|
106
106
|
/** Inputs for validating a session credential without applying its state transition. */
|
|
107
|
-
export type ValidateCredentialPayloadParameters = Pick<BroadcastCredentialPayloadParameters, 'account' | 'channelStateTtl' | 'chainId' | 'client' | 'credentialSource' | 'escrow' | 'expectedOperator' | 'feePayer' | 'feePayerPolicy' | 'feeToken' | 'lastOnChainVerified' | 'minVoucherDelta' | 'payload' | 'store' | 'challenge'
|
|
107
|
+
export type ValidateCredentialPayloadParameters = Pick<BroadcastCredentialPayloadParameters, 'account' | 'channelStateTtl' | 'chainId' | 'client' | 'credentialSource' | 'escrow' | 'expectedOperator' | 'feePayer' | 'feePayerPolicy' | 'feeToken' | 'lastOnChainVerified' | 'minVoucherDelta' | 'payload' | 'store' | 'challenge'> & {
|
|
108
|
+
/** Allows terminal broadcast to recover accepted funds before rejecting a pending close. */
|
|
109
|
+
allowPendingClose?: boolean | undefined;
|
|
110
|
+
};
|
|
108
111
|
/** Non-mutating result produced by session credential validation. */
|
|
109
112
|
export type CredentialPayloadValidation = {
|
|
110
113
|
/** Session action validated from the credential. */
|