mppx 0.13.5 → 0.14.0
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 +41 -0
- package/dist/Credential.d.ts +3 -0
- package/dist/Credential.js +9 -0
- package/dist/cli/cli.js +131 -131
- package/dist/cli/plugins/evm.js +2 -2
- package/dist/cli/plugins/index.js +11 -11
- package/dist/client/internal/MethodChallenge.d.ts +2 -0
- package/dist/client/internal/MethodChallenge.js +4 -0
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/server/Mppx.js +31 -5
- package/dist/stripe/server/Methods.js +4 -1
- package/dist/tempo/client/Charge.js +1 -0
- package/dist/tempo/internal/auto-swap.d.ts +10 -0
- package/dist/tempo/internal/auto-swap.js +35 -8
- package/dist/tempo/internal/fee-payer.d.ts +8 -0
- package/dist/tempo/internal/fee-payer.js +20 -12
- package/dist/tempo/legacy/session/Chain.js +8 -2
- package/dist/tempo/server/Methods.d.ts +7 -2
- package/dist/tempo/server/Methods.js +20 -2
- package/dist/tempo/server/Relay.js +6 -22
- package/dist/tempo/server/Subscription.js +3 -0
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.d.ts +3 -0
- package/dist/tempo/server/internal/transport.js +5 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +2 -0
- package/dist/tempo/session/client/ChannelStore.d.ts +12 -1
- package/dist/tempo/session/client/ChannelStore.js +47 -0
- package/dist/tempo/session/client/CredentialState.js +8 -3
- package/dist/tempo/session/client/Session.js +50 -33
- package/dist/tempo/session/client/SessionManager.js +186 -65
- package/dist/tempo/session/client/Transports.d.ts +6 -0
- package/dist/tempo/session/client/Transports.js +52 -7
- package/dist/tempo/session/precompile/Chain.d.ts +2 -2
- package/dist/tempo/session/precompile/Chain.js +8 -4
- package/dist/tempo/session/server/ChannelStore.d.ts +186 -3
- package/dist/tempo/session/server/ChannelStore.js +312 -12
- package/dist/tempo/session/server/CloseSupervisor.d.ts +98 -0
- package/dist/tempo/session/server/CloseSupervisor.js +289 -0
- package/dist/tempo/session/server/CredentialVerification.d.ts +7 -1
- package/dist/tempo/session/server/CredentialVerification.js +220 -48
- package/dist/tempo/session/server/MeteredStream.d.ts +6 -5
- package/dist/tempo/session/server/MeteredStream.js +10 -26
- package/dist/tempo/session/server/Session.d.ts +70 -4
- package/dist/tempo/session/server/Session.js +156 -36
- package/dist/tempo/session/server/Settlement.d.ts +18 -4
- package/dist/tempo/session/server/Settlement.js +119 -32
- package/dist/tempo/session/server/SponsorshipLimit.d.ts +52 -0
- package/dist/tempo/session/server/SponsorshipLimit.js +107 -0
- package/dist/tempo/session/server/Sse.d.ts +4 -3
- package/dist/tempo/session/server/Sse.js +3 -3
- package/dist/tempo/session/server/Ws.d.ts +9 -1
- package/dist/tempo/session/server/Ws.js +1 -0
- package/dist/tempo/session/server/index.d.ts +9 -1
- package/dist/tempo/session/server/index.js +5 -1
- package/package.json +1 -1
|
@@ -298,12 +298,22 @@ export async function readProblemDetail(response) {
|
|
|
298
298
|
return body;
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
+
/** A dispatched deposit may have executed despite a failed or cancelled response. */
|
|
302
|
+
export class UncertainFundingError extends Error {
|
|
303
|
+
constructor(reason) {
|
|
304
|
+
const detail = reason instanceof Error || reason instanceof DOMException ? reason.message : String(reason);
|
|
305
|
+
super(`${detail}; payment outcome unknown`);
|
|
306
|
+
this.name = 'UncertainFundingError';
|
|
307
|
+
}
|
|
308
|
+
}
|
|
301
309
|
/** Posts a top-up management credential and returns its receipt, when present. */
|
|
302
310
|
export async function postTopUp(parameters) {
|
|
303
311
|
const { channel, channelId } = parameters;
|
|
304
312
|
if (!channel?.descriptor || channel.channelId !== channelId) {
|
|
305
313
|
throw new Error('Cannot top up session: no local channel descriptor available.');
|
|
306
314
|
}
|
|
315
|
+
if (channel.uncertainTopUp)
|
|
316
|
+
throw new Error('Session funding outcome is unknown; reconcile the pending payment before another top-up.');
|
|
307
317
|
const response = await postManagementCredential({
|
|
308
318
|
challenge: parameters.challenge,
|
|
309
319
|
context: {
|
|
@@ -316,6 +326,12 @@ export async function postTopUp(parameters) {
|
|
|
316
326
|
fetch: parameters.fetch,
|
|
317
327
|
input: parameters.input,
|
|
318
328
|
signal: parameters.signal,
|
|
329
|
+
}).catch(async (error) => {
|
|
330
|
+
if (error instanceof UncertainFundingError) {
|
|
331
|
+
channel.uncertainTopUp = true;
|
|
332
|
+
await parameters.onUncertainFunding?.(channel);
|
|
333
|
+
}
|
|
334
|
+
throw error;
|
|
319
335
|
});
|
|
320
336
|
if (!response.ok)
|
|
321
337
|
throw new Error(`Top-up POST failed with status ${response.status}`);
|
|
@@ -324,13 +340,42 @@ export async function postTopUp(parameters) {
|
|
|
324
340
|
}
|
|
325
341
|
/** Posts a management credential and retries once with a refreshed TIP-1034 challenge. */
|
|
326
342
|
async function postManagementCredential(parameters) {
|
|
327
|
-
const post = async (challenge) =>
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
343
|
+
const post = async (challenge) => {
|
|
344
|
+
parameters.signal?.throwIfAborted();
|
|
345
|
+
const authorization = await parameters.createSessionCredential(challenge, parameters.context);
|
|
346
|
+
parameters.signal?.throwIfAborted();
|
|
347
|
+
const controller = new AbortController();
|
|
348
|
+
const abort = () => controller.abort(parameters.signal?.reason);
|
|
349
|
+
parameters.signal?.addEventListener('abort', abort, { once: true });
|
|
350
|
+
const timeout = setTimeout(() => controller.abort(new DOMException('Session management request timed out; payment outcome unknown', 'TimeoutError')), 30_000);
|
|
351
|
+
let onAbort;
|
|
352
|
+
const aborted = new Promise((_, reject) => {
|
|
353
|
+
onAbort = () => reject(controller.signal.reason);
|
|
354
|
+
controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
355
|
+
});
|
|
356
|
+
try {
|
|
357
|
+
// Custom fetch implementations may ignore abort. Do not let their late
|
|
358
|
+
// response update funding state or trigger a refreshed-challenge retry.
|
|
359
|
+
return await Promise.race([
|
|
360
|
+
parameters.fetch(managementInput(parameters.input), {
|
|
361
|
+
method: 'POST',
|
|
362
|
+
signal: controller.signal,
|
|
363
|
+
headers: { [Constants.Headers.authorization]: authorization },
|
|
364
|
+
}),
|
|
365
|
+
aborted,
|
|
366
|
+
]);
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
if (parameters.context.action === 'topUp')
|
|
370
|
+
throw new UncertainFundingError(error);
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
clearTimeout(timeout);
|
|
375
|
+
parameters.signal?.removeEventListener('abort', abort);
|
|
376
|
+
controller.signal.removeEventListener('abort', onAbort);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
334
379
|
const response = await post(parameters.challenge);
|
|
335
380
|
if (response.status !== 402)
|
|
336
381
|
return response;
|
|
@@ -146,9 +146,9 @@ export declare function getChannel(client: Client, descriptor: ChannelDescriptor
|
|
|
146
146
|
*/
|
|
147
147
|
export declare function getChannelState(client: Client, channelId: Hex, escrow?: Address, blockNumber?: bigint): Promise<ChannelState>;
|
|
148
148
|
/**
|
|
149
|
-
* Read channel states from the TIP20EscrowChannel precompile.
|
|
149
|
+
* Read channel states from the TIP20EscrowChannel precompile, optionally at one block.
|
|
150
150
|
*/
|
|
151
|
-
export declare function getChannelStatesBatch(client: Client, channelIds: readonly Hex[], escrow?: Address): Promise<ChannelState[]>;
|
|
151
|
+
export declare function getChannelStatesBatch(client: Client, channelIds: readonly Hex[], escrow?: Address, blockNumber?: bigint): Promise<ChannelState[]>;
|
|
152
152
|
/** Tuning for {@link readbackWithRetry}. */
|
|
153
153
|
export type ReadbackRetryOptions = {
|
|
154
154
|
/** Additional attempts after the first, before giving up. @default 5 */
|
|
@@ -141,14 +141,15 @@ export async function getChannelState(client, channelId, escrow = tip20ChannelEs
|
|
|
141
141
|
return stateFromTuple(state);
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
|
-
* Read channel states from the TIP20EscrowChannel precompile.
|
|
144
|
+
* Read channel states from the TIP20EscrowChannel precompile, optionally at one block.
|
|
145
145
|
*/
|
|
146
|
-
export async function getChannelStatesBatch(client, channelIds, escrow = tip20ChannelEscrow) {
|
|
146
|
+
export async function getChannelStatesBatch(client, channelIds, escrow = tip20ChannelEscrow, blockNumber) {
|
|
147
147
|
const states = await readContract(client, {
|
|
148
148
|
address: escrow,
|
|
149
149
|
abi: escrowAbi,
|
|
150
150
|
functionName: 'getChannelStatesBatch',
|
|
151
151
|
args: [channelIds],
|
|
152
|
+
...(blockNumber !== undefined ? { blockNumber } : {}),
|
|
152
153
|
});
|
|
153
154
|
return states.map(stateFromTuple);
|
|
154
155
|
}
|
|
@@ -263,8 +264,8 @@ function validateAutoSwapPrefix(parameters) {
|
|
|
263
264
|
fail('approval amount does not match swap maximum input');
|
|
264
265
|
if (!isAddressEqual(tokenOut, currency))
|
|
265
266
|
fail('output token does not match channel currency');
|
|
266
|
-
if (swapAmountOut
|
|
267
|
-
fail('output amount
|
|
267
|
+
if (swapAmountOut <= 0n || swapAmountOut > amountOut)
|
|
268
|
+
fail('output amount must be positive and not exceed channel deposit');
|
|
268
269
|
if (isAddressEqual(tokenIn, tokenOut))
|
|
269
270
|
fail('input and output tokens must differ');
|
|
270
271
|
const canonicalApprove = encodeFunctionData({
|
|
@@ -444,6 +445,7 @@ export async function sendCredentialTransaction(parameters) {
|
|
|
444
445
|
// execution as the sender. It intentionally omits fee fields here; the
|
|
445
446
|
// transport is responsible for validating its final sponsored envelope.
|
|
446
447
|
await simulateTempoTransaction(client, FeePayer.simulationTransaction(transaction, { feePayer: true }));
|
|
448
|
+
FeePayer.assertRemainingValidity({ transaction, details });
|
|
447
449
|
const txHash = await sendTransaction(client, serializedTransaction);
|
|
448
450
|
return waitForSuccessfulReceipt(client, txHash);
|
|
449
451
|
}
|
|
@@ -467,7 +469,9 @@ export async function sendCredentialTransaction(parameters) {
|
|
|
467
469
|
return { feePayer: feePayer.address, transaction: sponsored };
|
|
468
470
|
},
|
|
469
471
|
});
|
|
472
|
+
FeePayer.assertRemainingValidity({ transaction: completed.transaction, details });
|
|
470
473
|
const serialized = await signTempoTransaction(client, completed.transaction);
|
|
474
|
+
FeePayer.assertRemainingValidity({ transaction: completed.transaction, details });
|
|
471
475
|
const receipt = await sendRawTransactionSync(client, {
|
|
472
476
|
serializedTransaction: serialized,
|
|
473
477
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Address, Hex } from 'viem';
|
|
2
2
|
import type * as Challenge from '../../../Challenge.js';
|
|
3
|
+
import { InsufficientBalanceError } from '../../../Errors.js';
|
|
3
4
|
import type * as Store from '../../../Store.js';
|
|
4
5
|
import type * as Chain from '../precompile/Chain.js';
|
|
5
6
|
import type * as PrecompileChannel from '../precompile/Channel.js';
|
|
@@ -23,10 +24,11 @@ import type { SessionMethodDetails } from './RequestState.js';
|
|
|
23
24
|
* - `deposit` reflects the latest on-chain value
|
|
24
25
|
*/
|
|
25
26
|
export type State = BaseState & BackendState;
|
|
26
|
-
/** Result of an atomic channel deduction attempt. */
|
|
27
|
+
/** Result of an atomic channel deduction attempt. `replayed` marks a keyed charge recorded earlier. */
|
|
27
28
|
export type DeductResult = {
|
|
28
29
|
ok: true;
|
|
29
30
|
channel: State;
|
|
31
|
+
replayed?: boolean | undefined;
|
|
30
32
|
} | {
|
|
31
33
|
ok: false;
|
|
32
34
|
channel: State;
|
|
@@ -109,6 +111,8 @@ export type AcceptVoucherStateUpdateParameters = {
|
|
|
109
111
|
channelState: Chain.ChannelState;
|
|
110
112
|
/** Existing channel state being updated. */
|
|
111
113
|
current: State | null;
|
|
114
|
+
/** When `channelState` was read from chain; omitted when it came from the store. */
|
|
115
|
+
verifiedAt?: number | undefined;
|
|
112
116
|
/** Verified signed voucher. */
|
|
113
117
|
voucher: SessionSignedVoucher;
|
|
114
118
|
};
|
|
@@ -193,8 +197,14 @@ export type VerifyAndAcceptVoucherParameters = {
|
|
|
193
197
|
requireAdvance?: boolean | undefined;
|
|
194
198
|
/** Server channel store. */
|
|
195
199
|
store: ChannelStore;
|
|
200
|
+
/** When `channelState` was read from chain; omitted when it came from the store. */
|
|
201
|
+
verifiedAt?: number | undefined;
|
|
196
202
|
/** Signed cumulative voucher to verify and accept. */
|
|
197
203
|
voucher: SignedVoucher;
|
|
204
|
+
/** Request price to charge in the same store update that accepts the voucher. */
|
|
205
|
+
chargeAmount?: bigint | undefined;
|
|
206
|
+
/** Called with the stored channel after `chargeAmount` was charged. */
|
|
207
|
+
onCharged?: ((channel: State) => void) | undefined;
|
|
198
208
|
};
|
|
199
209
|
/** Inputs for validating a cumulative voucher without recording it. */
|
|
200
210
|
export type ValidateVoucherParameters = Omit<VerifyAndAcceptVoucherParameters, 'challenge' | 'store'>;
|
|
@@ -257,6 +267,8 @@ export interface BaseState {
|
|
|
257
267
|
token: Address;
|
|
258
268
|
/** Number of charge operations (API requests) fulfilled in the current session. */
|
|
259
269
|
units: number;
|
|
270
|
+
/** Unix milliseconds when the channel's on-chain deposit, settlement and close state were last read. */
|
|
271
|
+
onChainVerifiedAt?: number | undefined;
|
|
260
272
|
/** Uncommitted stream charges shared across workers. */
|
|
261
273
|
streamReservations?: Record<string, {
|
|
262
274
|
amount: bigint;
|
|
@@ -271,12 +283,58 @@ export interface BaseState {
|
|
|
271
283
|
lastSettlementSpent?: bigint | undefined;
|
|
272
284
|
/** Charge operation count when the last server-scheduled settlement ran. */
|
|
273
285
|
lastSettlementUnits?: number | undefined;
|
|
286
|
+
/**
|
|
287
|
+
* Accepted vouchers below `highestVoucher`, ascending, that a settlement may capture once
|
|
288
|
+
* `spent` covers them. See {@link resolveSettleableVoucher}.
|
|
289
|
+
*/
|
|
290
|
+
retainedVouchers?: readonly RetainedVoucher[] | undefined;
|
|
291
|
+
/** Settlement transaction that may still land; credits keep `spent` at or above its amount. */
|
|
292
|
+
pendingSettlement?: PendingSettlement | undefined;
|
|
293
|
+
/** Outcomes of keyed charges and credits, by `charge:<key>` and `credit:<key>`, until they expire. */
|
|
294
|
+
idempotencyMarkers?: Record<string, IdempotencyMarker> | undefined;
|
|
295
|
+
/** First block at which the close supervisor read this channel open on-chain. */
|
|
296
|
+
closeSupervisedFromBlock?: bigint | undefined;
|
|
274
297
|
}
|
|
275
298
|
/** Expiring ownership claim for one server-scheduled settlement attempt. */
|
|
276
299
|
export type ScheduledSettlementLease = {
|
|
277
300
|
expiresAt: number;
|
|
278
301
|
owner: string;
|
|
279
302
|
};
|
|
303
|
+
/** An accepted voucher kept below the highest one. */
|
|
304
|
+
export type RetainedVoucher = {
|
|
305
|
+
/** Cumulative amount the voucher authorizes. */
|
|
306
|
+
cumulativeAmount: bigint;
|
|
307
|
+
/** Voucher signature. */
|
|
308
|
+
signature: Hex;
|
|
309
|
+
};
|
|
310
|
+
/** A settlement transaction that may land until `expiresAt`. */
|
|
311
|
+
export type PendingSettlement = {
|
|
312
|
+
/** Cumulative amount the transaction captures. */
|
|
313
|
+
amount: bigint;
|
|
314
|
+
/** Unix milliseconds after which the transaction can no longer be included. */
|
|
315
|
+
expiresAt: number;
|
|
316
|
+
};
|
|
317
|
+
/** Why a credit was not applied. */
|
|
318
|
+
export type CreditDeclineReason =
|
|
319
|
+
/** Crediting would take `spent` below what settled, or may still settle, on-chain. */
|
|
320
|
+
'belowSettled'
|
|
321
|
+
/** A close is pending, so its capture is already fixed. */
|
|
322
|
+
| 'closing'
|
|
323
|
+
/** The amount is larger than `spent`. */
|
|
324
|
+
| 'exceedsSpent'
|
|
325
|
+
/** The channel is closed. */
|
|
326
|
+
| 'finalized';
|
|
327
|
+
/** Recorded outcome of one keyed charge or credit. */
|
|
328
|
+
export type IdempotencyMarker = {
|
|
329
|
+
/** Amount the operation charged or credited. */
|
|
330
|
+
amount: bigint;
|
|
331
|
+
/** Unix milliseconds until which a retry with the same key replays this outcome. */
|
|
332
|
+
expiresAt: number;
|
|
333
|
+
/** `charged`, `credited`, or why the credit was declined. */
|
|
334
|
+
outcome: 'charged' | 'credited' | CreditDeclineReason;
|
|
335
|
+
/** Charge operations a credit returned, when any. */
|
|
336
|
+
units?: number | undefined;
|
|
337
|
+
};
|
|
280
338
|
/** Returns whether a channel is backed by the TIP20EscrowChannel precompile. */
|
|
281
339
|
export declare function isPrecompileState(state: State): state is BaseState & PrecompileBackendState;
|
|
282
340
|
/** Returns the greater bigint value without decreasing persisted monotonic state. */
|
|
@@ -293,6 +351,30 @@ export declare function resolvePrecompileChannelIdentity(parameters: ResolvePrec
|
|
|
293
351
|
export declare function openChannelState(parameters: OpenChannelStateParameters): State;
|
|
294
352
|
/** Merges top-up on-chain state into persisted channel state without decreasing monotonic fields. */
|
|
295
353
|
export declare function topUpChannelState(parameters: TopUpStateParameters): State | null;
|
|
354
|
+
/**
|
|
355
|
+
* Number of accepted vouchers kept below the highest one. Clients sign every voucher above their
|
|
356
|
+
* last one, so once a credit lowers `spent` the highest voucher stays above it for good and only
|
|
357
|
+
* earlier vouchers can be settled; those fall at or below `spent` as later charges accumulate.
|
|
358
|
+
* Each costs about 200 bytes of the channel record. In a simulated 20,000-request channel crediting
|
|
359
|
+
* up to 80% of each request back, which left the highest voucher 80,000 units above `spent`,
|
|
360
|
+
* settling every 50 requests left on average 1,290 units unsettled with 16 retained vouchers and
|
|
361
|
+
* 784 with 32.
|
|
362
|
+
*/
|
|
363
|
+
export declare const maxRetainedVouchers = 16;
|
|
364
|
+
/**
|
|
365
|
+
* Returns the vouchers to keep once `highestVoucherAmount` replaces the highest voucher: the
|
|
366
|
+
* retained ones and the superseded highest voucher, above `settledOnChain`. Beyond
|
|
367
|
+
* {@link maxRetainedVouchers} it repeatedly drops the voucher whose neighbours are closest, so the
|
|
368
|
+
* kept ones stay spread over the unsettled range and some keep falling at or below `spent` however
|
|
369
|
+
* far credits have moved it below the highest voucher.
|
|
370
|
+
*/
|
|
371
|
+
export declare function retainVouchers(current: Pick<State, 'highestVoucher' | 'retainedVouchers' | 'settledOnChain'>, highestVoucherAmount: bigint, settledOnChain?: bigint): RetainedVoucher[];
|
|
372
|
+
/**
|
|
373
|
+
* Returns the largest accepted voucher a settlement may capture. The escrow's `settle` captures a
|
|
374
|
+
* voucher's whole cumulative amount, and a credit may leave the highest voucher above `spent`, so
|
|
375
|
+
* the voucher must be at most `spent`; one at or below `settledOnChain` captures nothing.
|
|
376
|
+
*/
|
|
377
|
+
export declare function resolveSettleableVoucher(state: Pick<State, 'channelId' | 'highestVoucher' | 'retainedVouchers' | 'settledOnChain' | 'spent'>): SessionSignedVoucher | null;
|
|
296
378
|
/** Merges an accepted voucher into persisted channel state after signature and on-chain checks pass. */
|
|
297
379
|
export declare function acceptVoucherStateUpdate(parameters: AcceptVoucherStateUpdateParameters): State;
|
|
298
380
|
/**
|
|
@@ -302,6 +384,8 @@ export declare function acceptVoucherStateUpdate(parameters: AcceptVoucherStateU
|
|
|
302
384
|
export declare function resolveCloseCaptureAmount(parameters: ResolveCloseCaptureAmountParameters): bigint;
|
|
303
385
|
/** Seconds a server close transaction stays valid after its pending-close marker is written. */
|
|
304
386
|
export declare const pendingCloseValiditySeconds = 25;
|
|
387
|
+
/** Extra seconds allowed for clock differences between the server and the chain. */
|
|
388
|
+
export declare const pendingCloseClockSkewSeconds = 30;
|
|
305
389
|
/** Clears a server pending-close marker whose close transaction expired without closing the channel. */
|
|
306
390
|
export declare function reconcileExpiredPendingClose(parameters: {
|
|
307
391
|
current: State | null;
|
|
@@ -316,6 +400,29 @@ export declare function finalizeClosedChannelState(parameters: FinalizeClosedCha
|
|
|
316
400
|
export declare function loadPrecompileChannel(parameters: LoadPrecompileChannelParameters): Promise<StoredPrecompileChannel>;
|
|
317
401
|
/** Verifies a cumulative voucher without changing persisted channel state. */
|
|
318
402
|
export declare function validateVoucher(parameters: ValidateVoucherParameters): Promise<VoucherValidation>;
|
|
403
|
+
/**
|
|
404
|
+
* Checks a cumulative voucher's amounts against channel state, without its signature, so the
|
|
405
|
+
* check can run inside the store update that accepts it.
|
|
406
|
+
*/
|
|
407
|
+
export declare function assertVoucherAmounts(parameters: {
|
|
408
|
+
channel: Pick<State, 'highestVoucherAmount'>;
|
|
409
|
+
channelState: Chain.ChannelState;
|
|
410
|
+
minVoucherDelta: bigint;
|
|
411
|
+
voucher: Pick<SignedVoucher, 'cumulativeAmount'>;
|
|
412
|
+
}): VoucherValidation;
|
|
413
|
+
/**
|
|
414
|
+
* Charges `chargeAmount` to a channel state that just accepted a voucher, for the same store
|
|
415
|
+
* update. A charge the available balance cannot cover leaves the accepted state uncharged.
|
|
416
|
+
*/
|
|
417
|
+
export declare function planVoucherAcceptance(parameters: {
|
|
418
|
+
accepted: State;
|
|
419
|
+
chargeAmount?: bigint | undefined;
|
|
420
|
+
}): {
|
|
421
|
+
channel: State;
|
|
422
|
+
charged: boolean;
|
|
423
|
+
};
|
|
424
|
+
/** Reports a request charge that the channel's available balance could not cover. */
|
|
425
|
+
export declare function insufficientBalanceForCharge(channel: State, chargeAmount: bigint): InsufficientBalanceError;
|
|
319
426
|
/** Verifies a cumulative voucher and returns a session receipt after store reconciliation. */
|
|
320
427
|
export declare function verifyAndAcceptVoucher(parameters: VerifyAndAcceptVoucherParameters): Promise<SessionReceipt>;
|
|
321
428
|
/**
|
|
@@ -371,14 +478,90 @@ export type ChannelStore = {
|
|
|
371
478
|
export declare function normalizeChannelId(channelId: string): Hex;
|
|
372
479
|
/** Wraps a generic mppx store in the shared session channel-store interface. */
|
|
373
480
|
export declare function fromStore(store: Store.Store | Store.AtomicStore): ChannelStore;
|
|
481
|
+
/**
|
|
482
|
+
* Default time a keyed charge or credit outcome is kept: a day, longer than any retry within one
|
|
483
|
+
* request or durable job step. Callers whose retries span longer pass their own `retention`.
|
|
484
|
+
*/
|
|
485
|
+
export declare const defaultIdempotencyRetention: number;
|
|
486
|
+
/** Options for charging a channel at most once per key. */
|
|
487
|
+
export type ChargeOptions = {
|
|
488
|
+
/**
|
|
489
|
+
* Key of the operation being charged. A retry with the same key and amount returns the recorded
|
|
490
|
+
* charge without charging again; the same key with a different amount throws
|
|
491
|
+
* {@link IdempotencyKeyConflictError}. A refused charge records nothing, so it may succeed later.
|
|
492
|
+
*/
|
|
493
|
+
idempotencyKey?: string | undefined;
|
|
494
|
+
/**
|
|
495
|
+
* Milliseconds the charge stays recorded, which must cover every retry. The channel record keeps
|
|
496
|
+
* one marker per keyed charge and credit of this window; expired markers are removed by the next
|
|
497
|
+
* keyed charge or credit of the channel. @default 86_400_000
|
|
498
|
+
*/
|
|
499
|
+
retention?: number | undefined;
|
|
500
|
+
};
|
|
501
|
+
/** Options for returning an amount to a channel at most once per key. */
|
|
502
|
+
export type CreditOptions = {
|
|
503
|
+
/**
|
|
504
|
+
* Key of the credit. Its first outcome, credited or declined, is final: a retry with the same key
|
|
505
|
+
* and terms returns it without crediting again, and the same key with other terms throws
|
|
506
|
+
* {@link IdempotencyKeyConflictError}.
|
|
507
|
+
*/
|
|
508
|
+
idempotencyKey: string;
|
|
509
|
+
/** Milliseconds the outcome stays recorded; see {@link ChargeOptions.retention}. @default 86_400_000 */
|
|
510
|
+
retention?: number | undefined;
|
|
511
|
+
/** Charge operations to take back from `units`, e.g. `1` when refunding a whole request. @default 0 */
|
|
512
|
+
units?: number | undefined;
|
|
513
|
+
};
|
|
514
|
+
/**
|
|
515
|
+
* Outcome of a credit. `declined` leaves `spent` unchanged, so the caller refunds the amount
|
|
516
|
+
* another way; a finalized channel whose `settledOnChain` is below `spent` was withdrawn before
|
|
517
|
+
* its spend was captured. `unknown` means the store write failed and may have committed: retry
|
|
518
|
+
* with the same key to learn the outcome.
|
|
519
|
+
*/
|
|
520
|
+
export type CreditResult = {
|
|
521
|
+
status: 'credited';
|
|
522
|
+
channel: State;
|
|
523
|
+
replayed: boolean;
|
|
524
|
+
} | {
|
|
525
|
+
status: 'declined';
|
|
526
|
+
reason: CreditDeclineReason;
|
|
527
|
+
channel: State;
|
|
528
|
+
replayed: boolean;
|
|
529
|
+
} | {
|
|
530
|
+
status: 'unknown';
|
|
531
|
+
error: unknown;
|
|
532
|
+
};
|
|
533
|
+
/** Raised when an idempotency key is reused with terms other than those it recorded. */
|
|
534
|
+
export declare class IdempotencyKeyConflictError extends Error {
|
|
535
|
+
readonly name = "IdempotencyKeyConflictError";
|
|
536
|
+
/** The reused key. */
|
|
537
|
+
readonly idempotencyKey: string;
|
|
538
|
+
constructor(options: {
|
|
539
|
+
idempotencyKey: string;
|
|
540
|
+
operation: 'charge' | 'credit';
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/** Validates the idempotency options of a keyed charge or credit. */
|
|
544
|
+
export declare function assertIdempotencyOptions(options: {
|
|
545
|
+
idempotencyKey?: string | undefined;
|
|
546
|
+
retention?: number | undefined;
|
|
547
|
+
}): void;
|
|
374
548
|
/**
|
|
375
549
|
* Atomically deducts `amount` from a channel's available voucher balance.
|
|
376
550
|
*
|
|
377
551
|
* Returns `{ ok: true, channel }` with updated spend/unit counters when the
|
|
378
552
|
* deduction succeeds. Returns `{ ok: false, channel }` without mutating state
|
|
379
|
-
* when the channel exists but cannot currently be charged.
|
|
553
|
+
* when the channel exists but cannot currently be charged. With an
|
|
554
|
+
* `idempotencyKey`, a charge recorded under the key returns
|
|
555
|
+
* `{ ok: true, channel, replayed: true }` without charging again.
|
|
556
|
+
*/
|
|
557
|
+
export declare function deductFromChannel(store: ChannelStore, channelId: State['channelId'], amount: bigint, options?: ChargeOptions): Promise<DeductResult>;
|
|
558
|
+
/**
|
|
559
|
+
* Returns `amount` to a channel's `spent` at most once per idempotency key. The credit is declined
|
|
560
|
+
* when the channel is closing or finalized, or when it would take `spent` below zero or below
|
|
561
|
+
* what settled, or may still settle, on-chain, because a close captures `max(spent, settled)`.
|
|
562
|
+
* The first outcome for a key is final, so a declined credit is never applied later.
|
|
380
563
|
*/
|
|
381
|
-
export declare function
|
|
564
|
+
export declare function creditChannel(store: ChannelStore, channelId: State['channelId'], amount: bigint, options: CreditOptions): Promise<CreditResult>;
|
|
382
565
|
/** Returns unexpired stream reservations that ordinary deductions must respect. */
|
|
383
566
|
export declare function reservedStreamAmount(state: Pick<State, 'streamReservations'>): bigint;
|
|
384
567
|
//# sourceMappingURL=ChannelStore.d.ts.map
|