mppx 0.9.2 → 0.10.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 +32 -0
- package/README.md +19 -0
- package/dist/Method.d.ts +2 -0
- package/dist/cli/Extension.d.ts +20 -0
- package/dist/cli/Extension.js +5 -0
- package/dist/cli/cli.js +137 -137
- package/dist/cli/config.d.ts +4 -0
- package/dist/cli/config.js +1 -0
- package/dist/cli/internal.d.ts +7 -1
- package/dist/cli/internal.js +51 -5
- package/dist/cli/plugins/index.js +12 -12
- package/dist/cli/plugins/plugin.d.ts +2 -2
- package/dist/cli/sessions/request.d.ts +1 -0
- package/dist/cli/sessions/request.js +1 -0
- package/dist/cli/validate/discovery.d.ts +3 -1
- package/dist/cli/validate/discovery.js +28 -1
- package/dist/cli/validate/helpers.d.ts +3 -2
- package/dist/cli/validate/helpers.js +3 -0
- package/dist/cli/validate/index.js +3 -1
- package/dist/cli/validate/payment.js +118 -59
- package/dist/client/Mppx.d.ts +32 -0
- package/dist/client/Mppx.js +180 -6
- package/dist/client/internal/Fetch.d.ts +6 -0
- package/dist/client/internal/Fetch.js +4 -2
- package/dist/evm/client/Charge.d.ts +2 -2
- package/dist/evm/client/Charge.js +1 -1
- package/dist/evm/server/Charge.d.ts +7 -2
- package/dist/evm/server/Charge.js +28 -2
- package/dist/evm/server/Methods.d.ts +1 -1
- package/dist/internal/version.d.ts +5 -0
- package/dist/internal/version.js +6 -0
- package/dist/server/Mppx.d.ts +7 -6
- package/dist/server/Mppx.js +84 -2
- package/dist/server/internal/html/config.js +1 -3
- package/dist/stripe/Methods.d.ts +4 -3
- package/dist/stripe/Methods.js +14 -11
- package/dist/stripe/client/Charge.d.ts +3 -3
- package/dist/stripe/client/Methods.d.ts +3 -3
- package/dist/stripe/internal/parse-units.d.ts +8 -0
- package/dist/stripe/internal/parse-units.js +20 -0
- package/dist/stripe/internal/payment-intent.d.ts +29 -0
- package/dist/stripe/internal/payment-intent.js +8 -0
- package/dist/stripe/server/Charge.d.ts +5 -3
- package/dist/stripe/server/Charge.js +17 -21
- package/dist/stripe/server/Methods.d.ts +6 -4
- package/dist/stripe/server/Methods.js +42 -14
- package/dist/stripe/server/internal/analytics.d.ts +6 -0
- package/dist/stripe/server/internal/analytics.js +16 -0
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/record-payment.d.ts +1 -0
- package/dist/stripe/server/internal/record-payment.js +7 -5
- package/dist/tempo/client/Charge.d.ts +8 -2
- package/dist/tempo/client/Charge.js +19 -11
- package/dist/tempo/client/Subscription.js +6 -2
- package/dist/tempo/internal/fee-payer.d.ts +2 -0
- package/dist/tempo/internal/fee-payer.js +4 -0
- package/dist/tempo/server/Subscription.js +1 -0
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +2 -0
- package/dist/tempo/session/client/CredentialState.js +9 -3
- package/dist/tempo/session/client/Session.d.ts +6 -0
- package/dist/tempo/session/client/Session.js +4 -1
- package/dist/tempo/session/client/SessionManager.d.ts +11 -0
- package/dist/tempo/session/client/SessionManager.js +23 -2
- package/dist/tempo/session/precompile/Chain.js +3 -0
- package/dist/tempo/subscription/KeyAuthorization.d.ts +7 -1
- package/dist/tempo/subscription/KeyAuthorization.js +21 -3
- package/dist/validation/core.d.ts +1 -0
- package/dist/validation/core.js +7 -2
- package/dist/viem/Client.d.ts +4 -0
- package/dist/viem/Client.js +13 -0
- package/dist/x402/client/Exact.d.ts +2 -2
- package/dist/x402/client/Exact.js +1 -1
- package/dist/x402/server/EvmCharge.d.ts +2 -0
- package/dist/x402/server/EvmCharge.js +34 -26
- package/package.json +1 -1
|
@@ -113,6 +113,8 @@ export type ResolveChallengeContextParameters = {
|
|
|
113
113
|
challenge: Challenge.Challenge;
|
|
114
114
|
/** Optional local escrow override. */
|
|
115
115
|
escrowOverride?: Address | undefined;
|
|
116
|
+
/** Permitted payment chains, checked before resolution when known and again before signing. */
|
|
117
|
+
allowedChainIds?: readonly number[] | undefined;
|
|
116
118
|
/** Resolves the viem client for the challenge chain. */
|
|
117
119
|
getClient(parameters: {
|
|
118
120
|
chainId?: number | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isAddress, parseUnits, } from 'viem';
|
|
2
2
|
import * as Constants from '../../../Constants.js';
|
|
3
|
+
import { assertAllowedChainId, assertChainId } from '../../../viem/Client.js';
|
|
3
4
|
import * as z from '../../../zod.js';
|
|
4
5
|
import * as AutoSwap from '../../internal/auto-swap.js';
|
|
5
6
|
import * as Chain from '../precompile/Chain.js';
|
|
@@ -126,10 +127,15 @@ function readSuggestedDeposit(value) {
|
|
|
126
127
|
}
|
|
127
128
|
/** Resolves raw challenge fields into the typed data required by client credential planning. */
|
|
128
129
|
export async function resolveChallengeContext(parameters) {
|
|
129
|
-
const { allowCustomEscrow, challenge, escrowOverride, getClient } = parameters;
|
|
130
|
+
const { allowCustomEscrow, challenge, escrowOverride, allowedChainIds, getClient } = parameters;
|
|
130
131
|
const methodDetails = readMethodDetails(challenge);
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
if (methodDetails.chainId !== undefined || allowedChainIds?.length === 0)
|
|
133
|
+
assertAllowedChainId(allowedChainIds, methodDetails.chainId);
|
|
134
|
+
const requestedChainId = methodDetails.chainId ?? (allowedChainIds?.length === 1 ? allowedChainIds[0] : undefined);
|
|
135
|
+
const client = await getClient({ chainId: requestedChainId });
|
|
136
|
+
assertChainId(client, requestedChainId);
|
|
137
|
+
const chainId = requestedChainId ?? client.chain?.id;
|
|
138
|
+
assertAllowedChainId(allowedChainIds, chainId);
|
|
133
139
|
if (!chainId)
|
|
134
140
|
throw new Error('No chainId configured for TIP-1034 session challenge.');
|
|
135
141
|
const escrow = resolveEscrow(challenge, escrowOverride, allowCustomEscrow);
|
|
@@ -137,6 +137,12 @@ export declare namespace session {
|
|
|
137
137
|
decimals?: number | undefined;
|
|
138
138
|
/** Exact TIP20EscrowChannel address pin. Takes precedence over `allowCustomEscrow`. */
|
|
139
139
|
escrow?: Address | undefined;
|
|
140
|
+
/**
|
|
141
|
+
* Chains permitted for session credentials. Omitted allows any chain; empty rejects all.
|
|
142
|
+
* A single entry supplies the chain when omitted by the challenge. Otherwise the
|
|
143
|
+
* challenge or resolved client must select an allowed chain.
|
|
144
|
+
*/
|
|
145
|
+
allowedChainIds?: readonly number[] | undefined;
|
|
140
146
|
/** Maximum channel deposit in human-readable units. Caps server-suggested opens and automatic top-ups. */
|
|
141
147
|
maxDeposit?: string | undefined;
|
|
142
148
|
/**
|
|
@@ -77,7 +77,7 @@ function acknowledgesOpen(outcome, challenge, opened) {
|
|
|
77
77
|
* channel descriptor).
|
|
78
78
|
*/
|
|
79
79
|
export function session(parameters = {}) {
|
|
80
|
-
const { account, allowCustomEscrow = false, autoSwap: autoSwapParameter, channelStore, decimals = defaults.decimals, escrow: escrowOverride, getClient: getClientParameter, maxDeposit: maxDepositParameter, topUpAmount: topUpAmountParameter, onChannelUpdate, resolveAccount, } = parameters;
|
|
80
|
+
const { account, allowCustomEscrow = false, autoSwap: autoSwapParameter, channelStore, decimals = defaults.decimals, escrow: escrowOverride, allowedChainIds, getClient: getClientParameter, maxDeposit: maxDepositParameter, topUpAmount: topUpAmountParameter, onChannelUpdate, resolveAccount, } = parameters;
|
|
81
81
|
const getClient = Client.getResolver({
|
|
82
82
|
chain: tempo_chain,
|
|
83
83
|
getClient: getClientParameter,
|
|
@@ -120,6 +120,7 @@ export function session(parameters = {}) {
|
|
|
120
120
|
allowCustomEscrow,
|
|
121
121
|
challenge,
|
|
122
122
|
escrowOverride,
|
|
123
|
+
allowedChainIds,
|
|
123
124
|
getClient,
|
|
124
125
|
});
|
|
125
126
|
const attempt = MethodResponse.getAttempt(parameters);
|
|
@@ -237,6 +238,7 @@ export function session(parameters = {}) {
|
|
|
237
238
|
allowCustomEscrow,
|
|
238
239
|
challenge,
|
|
239
240
|
escrowOverride,
|
|
241
|
+
allowedChainIds,
|
|
240
242
|
getClient,
|
|
241
243
|
});
|
|
242
244
|
const channel = await store.get(resolved.key);
|
|
@@ -278,6 +280,7 @@ export function session(parameters = {}) {
|
|
|
278
280
|
allowCustomEscrow,
|
|
279
281
|
challenge,
|
|
280
282
|
escrowOverride,
|
|
283
|
+
allowedChainIds,
|
|
281
284
|
getClient,
|
|
282
285
|
})).key;
|
|
283
286
|
let channel = await store.get(channelKey);
|
|
@@ -3,6 +3,7 @@ import { type Address } from 'viem';
|
|
|
3
3
|
import * as Account from '../../../viem/Account.js';
|
|
4
4
|
import * as Client from '../../../viem/Client.js';
|
|
5
5
|
import { type ChannelStore } from '../client/ChannelStore.js';
|
|
6
|
+
import { type SessionContext } from '../client/CredentialState.js';
|
|
6
7
|
import { session as sessionPlugin } from '../client/Session.js';
|
|
7
8
|
import type { SessionReceipt } from '../precompile/Protocol.js';
|
|
8
9
|
import { deserializeSnapshot as deserializeSessionSnapshot, serializeSnapshot as serializeSessionSnapshot } from '../Snapshot.js';
|
|
@@ -48,6 +49,8 @@ export type PaymentResponse = Response & {
|
|
|
48
49
|
/** Local cumulative voucher authorization in raw token units. */
|
|
49
50
|
cumulative: bigint;
|
|
50
51
|
};
|
|
52
|
+
/** @internal Merges manager-wide context without allowing it to replace an operation's fields. */
|
|
53
|
+
export declare function mergeSessionCredentialContext(credentialContext: unknown, operationContext: SessionContext): SessionContext;
|
|
51
54
|
/**
|
|
52
55
|
* Creates a session manager that handles the full client payment lifecycle:
|
|
53
56
|
* channel open, incremental vouchers, SSE streaming, and channel close.
|
|
@@ -76,8 +79,16 @@ export declare namespace sessionManager {
|
|
|
76
79
|
decimals?: number | undefined;
|
|
77
80
|
/** Exact TIP20EscrowChannel address pin. Takes precedence over `allowCustomEscrow`. */
|
|
78
81
|
escrow?: Address | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Chains permitted for sessions, bootstrap proofs, and snapshots. Omitted allows any
|
|
84
|
+
* chain; empty rejects all. A single entry supplies an omitted challenge chain;
|
|
85
|
+
* otherwise the challenge or resolved client must select an allowed chain.
|
|
86
|
+
*/
|
|
87
|
+
allowedChainIds?: readonly number[] | undefined;
|
|
79
88
|
/** Fetch implementation used for HTTP probes, management posts, and paid retries. */
|
|
80
89
|
fetch?: typeof globalThis.fetch | undefined;
|
|
90
|
+
/** Base context supplied to every session credential created by the manager. */
|
|
91
|
+
credentialContext?: unknown;
|
|
81
92
|
/** Maximum deposit in human-readable units (e.g. `'10'` for 10 tokens). Converted to raw units via `decimals`. */
|
|
82
93
|
maxDeposit?: string | undefined;
|
|
83
94
|
/**
|
|
@@ -10,7 +10,7 @@ import * as Client from '../../../viem/Client.js';
|
|
|
10
10
|
import { charge as chargePlugin } from '../../client/Charge.js';
|
|
11
11
|
import * as defaults from '../../internal/defaults.js';
|
|
12
12
|
import { createChannelStore, entryKey } from '../client/ChannelStore.js';
|
|
13
|
-
import { hydrateSessionSnapshot } from '../client/CredentialState.js';
|
|
13
|
+
import { hydrateSessionSnapshot, sessionContextSchema, } from '../client/CredentialState.js';
|
|
14
14
|
import { session as sessionPlugin } from '../client/Session.js';
|
|
15
15
|
import * as Channel from '../precompile/Channel.js';
|
|
16
16
|
import { deserializeSessionReceipt } from '../precompile/Protocol.js';
|
|
@@ -60,6 +60,12 @@ function requestInitWithSessionHint(input, init, channelId) {
|
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
/** @internal Merges manager-wide context without allowing it to replace an operation's fields. */
|
|
64
|
+
export function mergeSessionCredentialContext(credentialContext, operationContext) {
|
|
65
|
+
if (credentialContext === undefined)
|
|
66
|
+
return operationContext;
|
|
67
|
+
return { ...sessionContextSchema.parse(credentialContext), ...operationContext };
|
|
68
|
+
}
|
|
63
69
|
function resolveSessionManagerConfig(parameters) {
|
|
64
70
|
const decimals = parameters.decimals ?? 6;
|
|
65
71
|
const WebSocket = parameters.webSocket ??
|
|
@@ -83,6 +89,9 @@ function resolveSessionManagerConfig(parameters) {
|
|
|
83
89
|
* `channelStore` can persist reusable channels between manager instances.
|
|
84
90
|
*/
|
|
85
91
|
export function sessionManager(parameters) {
|
|
92
|
+
const clientChainId = parameters.client?.chain?.id;
|
|
93
|
+
if (clientChainId !== undefined || parameters.allowedChainIds?.length === 0)
|
|
94
|
+
Client.assertAllowedChainId(parameters.allowedChainIds, clientChainId);
|
|
86
95
|
const allowCustomEscrow = parameters.allowCustomEscrow ?? false;
|
|
87
96
|
const config = resolveSessionManagerConfig(parameters);
|
|
88
97
|
const getClient = Client.getResolver({
|
|
@@ -92,6 +101,9 @@ export function sessionManager(parameters) {
|
|
|
92
101
|
});
|
|
93
102
|
const getAccount = Account.getResolver({ account: parameters.account });
|
|
94
103
|
const runtime = createSessionManagerRuntime();
|
|
104
|
+
const credentialContext = parameters.credentialContext === undefined
|
|
105
|
+
? undefined
|
|
106
|
+
: mergeSessionCredentialContext(parameters.credentialContext, {});
|
|
95
107
|
const receipts = createSessionReceiptCoordinator({
|
|
96
108
|
getSocketSession: () => runtime.socketSession,
|
|
97
109
|
});
|
|
@@ -197,6 +209,7 @@ export function sessionManager(parameters) {
|
|
|
197
209
|
getClient: parameters.client ? () => parameters.client : parameters.getClient,
|
|
198
210
|
resolveAccount: parameters.resolveAccount,
|
|
199
211
|
escrow: parameters.escrow,
|
|
212
|
+
allowedChainIds: parameters.allowedChainIds,
|
|
200
213
|
decimals: config.decimals,
|
|
201
214
|
maxDeposit: parameters.maxDeposit,
|
|
202
215
|
topUpAmount: parameters.topUpAmount,
|
|
@@ -219,6 +232,7 @@ export function sessionManager(parameters) {
|
|
|
219
232
|
});
|
|
220
233
|
MethodResponse.unregister(method);
|
|
221
234
|
const chargeMethod = chargePlugin({
|
|
235
|
+
allowedChainIds: parameters.allowedChainIds,
|
|
222
236
|
account: parameters.account,
|
|
223
237
|
getClient: parameters.client ? () => parameters.client : parameters.getClient,
|
|
224
238
|
});
|
|
@@ -248,7 +262,10 @@ export function sessionManager(parameters) {
|
|
|
248
262
|
},
|
|
249
263
|
});
|
|
250
264
|
function createSessionCredential(challenge, context) {
|
|
251
|
-
return method.createCredential({
|
|
265
|
+
return method.createCredential({
|
|
266
|
+
challenge,
|
|
267
|
+
context: mergeSessionCredentialContext(credentialContext, context),
|
|
268
|
+
});
|
|
252
269
|
}
|
|
253
270
|
function updateSpentFromReceipt(receipt) {
|
|
254
271
|
applySessionReceiptToRuntime({
|
|
@@ -278,7 +295,9 @@ export function sessionManager(parameters) {
|
|
|
278
295
|
if (!header)
|
|
279
296
|
return undefined;
|
|
280
297
|
const snapshot = deserializeSessionSnapshot(header);
|
|
298
|
+
Client.assertAllowedChainId(parameters.allowedChainIds, snapshot.chainId);
|
|
281
299
|
const client = await getClient({ chainId: snapshot.chainId });
|
|
300
|
+
Client.assertChainId(client, snapshot.chainId);
|
|
282
301
|
const defaultAccount = getAccount(client);
|
|
283
302
|
const account = (await parameters.resolveAccount?.({
|
|
284
303
|
account: defaultAccount,
|
|
@@ -535,6 +554,8 @@ export function sessionManager(parameters) {
|
|
|
535
554
|
use.trackCreates = true;
|
|
536
555
|
const liveHint = runtime.channel?.opened ? runtime.channel.channelId : undefined;
|
|
537
556
|
let effectiveInit = requestInitWithSessionHint(input, init, liveHint);
|
|
557
|
+
if (credentialContext)
|
|
558
|
+
effectiveInit = { ...effectiveInit, context: credentialContext };
|
|
538
559
|
// Stored channels may be stale, so retry once after evicting the resumed entry.
|
|
539
560
|
let canRetryResumed = !previous.channel?.opened;
|
|
540
561
|
async function retryWithoutResumed() {
|
|
@@ -318,6 +318,7 @@ async function prepareFeePayerCallTransaction(client, parameters) {
|
|
|
318
318
|
account,
|
|
319
319
|
calls: [{ to, data }],
|
|
320
320
|
feePayer: true,
|
|
321
|
+
nonceKey: 'expiring',
|
|
321
322
|
...(feeToken ? { feeToken } : {}),
|
|
322
323
|
});
|
|
323
324
|
}
|
|
@@ -329,6 +330,8 @@ function sendPrecompileContractCall(client, parameters) {
|
|
|
329
330
|
...(account ? { account } : {}),
|
|
330
331
|
to,
|
|
331
332
|
data,
|
|
333
|
+
// Server processes can share a signer without sharing viem's concurrency detection.
|
|
334
|
+
nonceKey: 'expiring',
|
|
332
335
|
...(feePayer ? { feePayer } : {}),
|
|
333
336
|
...(feeToken ? { feeToken } : {}),
|
|
334
337
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Hex } from 'ox';
|
|
1
2
|
import { KeyAuthorization } from 'ox/tempo';
|
|
2
3
|
import { type Address } from 'viem';
|
|
3
4
|
import type * as Methods from '../Methods.js';
|
|
@@ -42,6 +43,8 @@ export declare function getSubscriptionRpcAllowedCalls(request: Pick<Subscriptio
|
|
|
42
43
|
readonly recipients: readonly [`0x${string}`];
|
|
43
44
|
}];
|
|
44
45
|
}];
|
|
46
|
+
/** Decodes the server-issued challenge identifier for use as a signed Tempo witness. */
|
|
47
|
+
export declare function getSubscriptionChallengeWitness(challengeId: string): Hex.Hex;
|
|
45
48
|
/**
|
|
46
49
|
* Creates and signs a Tempo key authorization for subscription payments when the account can sign
|
|
47
50
|
* arbitrary hashes locally.
|
|
@@ -53,6 +56,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
|
|
|
53
56
|
hash: `0x${string}`;
|
|
54
57
|
}) => Promise<`0x${string}`>) | undefined;
|
|
55
58
|
};
|
|
59
|
+
challengeId: string;
|
|
56
60
|
chainId: number;
|
|
57
61
|
request: Pick<SubscriptionRequest, 'amount' | 'currency' | 'periodCount' | 'periodUnit' | 'recipient' | 'subscriptionExpires'>;
|
|
58
62
|
}): Promise<{
|
|
@@ -96,7 +100,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
|
|
|
96
100
|
version?: undefined;
|
|
97
101
|
} | {
|
|
98
102
|
metadata: {
|
|
99
|
-
authenticatorData:
|
|
103
|
+
authenticatorData: Hex.Hex;
|
|
100
104
|
clientDataJSON: string;
|
|
101
105
|
};
|
|
102
106
|
signature: {
|
|
@@ -133,6 +137,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
|
|
|
133
137
|
readonly recipients: readonly `0x${string}`[];
|
|
134
138
|
}[];
|
|
135
139
|
readonly type: "p256" | "secp256k1" | "webAuthn";
|
|
140
|
+
readonly witness: `0x${string}`;
|
|
136
141
|
} | undefined>;
|
|
137
142
|
/**
|
|
138
143
|
* Verifies that a subscription credential contains a key authorization scoped to the requested
|
|
@@ -140,6 +145,7 @@ export declare function signSubscriptionKeyAuthorization(parameters: {
|
|
|
140
145
|
*/
|
|
141
146
|
export declare function verifySubscriptionKeyAuthorization(parameters: {
|
|
142
147
|
accessKey?: SubscriptionAccessKey | undefined;
|
|
148
|
+
challengeId: string;
|
|
143
149
|
chainId: number;
|
|
144
150
|
payload: SubscriptionCredentialPayload;
|
|
145
151
|
request: SubscriptionRequest;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Base64, Hex } from 'ox';
|
|
1
2
|
import { KeyAuthorization, SignatureEnvelope } from 'ox/tempo';
|
|
2
3
|
import { isAddress, isAddressEqual } from 'viem';
|
|
3
4
|
import { VerificationFailedError } from '../../Errors.js';
|
|
@@ -104,16 +105,29 @@ export function getSubscriptionRpcAllowedCalls(request) {
|
|
|
104
105
|
},
|
|
105
106
|
];
|
|
106
107
|
}
|
|
108
|
+
/** Decodes the server-issued challenge identifier for use as a signed Tempo witness. */
|
|
109
|
+
export function getSubscriptionChallengeWitness(challengeId) {
|
|
110
|
+
try {
|
|
111
|
+
const witness = Hex.fromBytes(Base64.toBytes(challengeId));
|
|
112
|
+
if (Hex.size(witness) !== 32)
|
|
113
|
+
throw new Error('invalid challenge id size');
|
|
114
|
+
return witness;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new VerificationFailedError({ reason: 'challenge id must encode 32 bytes' });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
107
120
|
/**
|
|
108
121
|
* Creates and signs a Tempo key authorization for subscription payments when the account can sign
|
|
109
122
|
* arbitrary hashes locally.
|
|
110
123
|
*/
|
|
111
124
|
export async function signSubscriptionKeyAuthorization(parameters) {
|
|
112
|
-
const { accessKey, account, chainId, request } = parameters;
|
|
125
|
+
const { accessKey, account, challengeId, chainId, request } = parameters;
|
|
113
126
|
if (typeof account.sign !== 'function')
|
|
114
127
|
return undefined;
|
|
115
128
|
const authorization = createUnsignedAuthorization({
|
|
116
129
|
accessKey,
|
|
130
|
+
challengeId,
|
|
117
131
|
chainId,
|
|
118
132
|
request,
|
|
119
133
|
});
|
|
@@ -135,7 +149,7 @@ export async function signSubscriptionKeyAuthorization(parameters) {
|
|
|
135
149
|
* token, recipient, amount, period, expiry, chain, and server-issued access key.
|
|
136
150
|
*/
|
|
137
151
|
export function verifySubscriptionKeyAuthorization(parameters) {
|
|
138
|
-
const { accessKey, chainId, payload, request } = parameters;
|
|
152
|
+
const { accessKey, challengeId, chainId, payload, request } = parameters;
|
|
139
153
|
if (payload.type !== 'keyAuthorization') {
|
|
140
154
|
throw new VerificationFailedError({ reason: 'invalid keyAuthorization payload' });
|
|
141
155
|
}
|
|
@@ -146,6 +160,9 @@ export function verifySubscriptionKeyAuthorization(parameters) {
|
|
|
146
160
|
authorization,
|
|
147
161
|
chainId,
|
|
148
162
|
});
|
|
163
|
+
if (authorization.witness?.toLowerCase() !== getSubscriptionChallengeWitness(challengeId)) {
|
|
164
|
+
throw new VerificationFailedError({ reason: 'keyAuthorization challenge mismatch' });
|
|
165
|
+
}
|
|
149
166
|
assertAuthorizationExpiry(authorization, request);
|
|
150
167
|
assertAuthorizationLimit(getSingleTokenLimit(authorization), request);
|
|
151
168
|
assertAuthorizationScopes(authorization.scopes, request);
|
|
@@ -159,7 +176,7 @@ export function verifySubscriptionKeyAuthorization(parameters) {
|
|
|
159
176
|
};
|
|
160
177
|
}
|
|
161
178
|
function createUnsignedAuthorization(parameters) {
|
|
162
|
-
const { accessKey, chainId, request } = parameters;
|
|
179
|
+
const { accessKey, challengeId, chainId, request } = parameters;
|
|
163
180
|
return KeyAuthorization.from({
|
|
164
181
|
address: normalizeAddress(accessKey.accessKeyAddress, 'accessKeyAddress'),
|
|
165
182
|
chainId: BigInt(chainId),
|
|
@@ -173,6 +190,7 @@ function createUnsignedAuthorization(parameters) {
|
|
|
173
190
|
],
|
|
174
191
|
scopes: getSubscriptionScopes(request),
|
|
175
192
|
type: accessKey.keyType,
|
|
193
|
+
witness: getSubscriptionChallengeWitness(challengeId),
|
|
176
194
|
});
|
|
177
195
|
}
|
|
178
196
|
function deserializeAuthorization(signature) {
|
package/dist/validation/core.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as viemChains from 'viem/chains';
|
|
2
2
|
import { validateChallenge, validateErrorHandling } from '../cli/validate/challenge.js';
|
|
3
|
-
import { buildUrl, extractEndpointsFromDiscovery, extractRequestBodyFromDiscovery, fetchDiscoveryDoc, } from '../cli/validate/discovery.js';
|
|
3
|
+
import { buildUrl, extractEndpointsFromDiscovery, extractRequestBodyFromDiscovery, fetchDiscoveryDoc, validateLlmsDoc, } from '../cli/validate/discovery.js';
|
|
4
4
|
import { isValidIntegerAmount, parseEndpointArg, resolveBodyForEndpoint, } from '../cli/validate/helpers.js';
|
|
5
5
|
import { missingDiscoverySuggestion } from '../cli/validate/messages.js';
|
|
6
6
|
import { validatePaymentFlow } from '../cli/validate/payment.js';
|
|
@@ -123,7 +123,7 @@ export async function validate(options) {
|
|
|
123
123
|
break;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
const summary = { passed: 0, failed: 0, warnings: 0, skipped: 0 };
|
|
126
|
+
const summary = { passed: 0, failed: 0, warnings: 0, suggested: 0, skipped: 0 };
|
|
127
127
|
if (discovery) {
|
|
128
128
|
for (const r of discovery.checks) {
|
|
129
129
|
if (r.severity === 'pass')
|
|
@@ -132,6 +132,8 @@ export async function validate(options) {
|
|
|
132
132
|
summary.failed++;
|
|
133
133
|
else if (r.severity === 'warn')
|
|
134
134
|
summary.warnings++;
|
|
135
|
+
else if (r.severity === 'suggested')
|
|
136
|
+
summary.suggested++;
|
|
135
137
|
else if (r.severity === 'skip')
|
|
136
138
|
summary.skipped++;
|
|
137
139
|
}
|
|
@@ -144,6 +146,8 @@ export async function validate(options) {
|
|
|
144
146
|
summary.failed++;
|
|
145
147
|
else if (r.severity === 'warn')
|
|
146
148
|
summary.warnings++;
|
|
149
|
+
else if (r.severity === 'suggested')
|
|
150
|
+
summary.suggested++;
|
|
147
151
|
else if (r.severity === 'skip')
|
|
148
152
|
summary.skipped++;
|
|
149
153
|
}
|
|
@@ -189,6 +193,7 @@ async function runDiscovery(baseUrl, options) {
|
|
|
189
193
|
let discoveryDoc = null;
|
|
190
194
|
let found = false;
|
|
191
195
|
let valid = false;
|
|
196
|
+
checks.push(await validateLlmsDoc(baseUrl));
|
|
192
197
|
// Try the user's path first, then root, then /api.
|
|
193
198
|
const origin = new URL(baseUrl).origin;
|
|
194
199
|
let candidates;
|
package/dist/viem/Client.d.ts
CHANGED
|
@@ -25,4 +25,8 @@ export declare namespace getResolver {
|
|
|
25
25
|
}) => MaybePromise<Client>) | undefined;
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
/** Rejects a resolved client whose known chain conflicts with the requested payment chain. */
|
|
29
|
+
export declare function assertChainId(client: Client, chainId: number | undefined): void;
|
|
30
|
+
/** Rejects disallowed or unknown payment chains when an allowlist is configured. */
|
|
31
|
+
export declare function assertAllowedChainId(allowedChainIds: readonly number[] | undefined, chainId: number | undefined): void;
|
|
28
32
|
//# sourceMappingURL=Client.d.ts.map
|
package/dist/viem/Client.js
CHANGED
|
@@ -50,6 +50,8 @@ export function getResolver(parameters) {
|
|
|
50
50
|
return Object.assign({}, resolvedClient, {
|
|
51
51
|
chain: {
|
|
52
52
|
...chain,
|
|
53
|
+
// Serializer defaults must not replace an explicitly requested network.
|
|
54
|
+
...(params.chainId !== undefined ? { id: params.chainId } : {}),
|
|
53
55
|
...resolvedClient.chain,
|
|
54
56
|
formatters: resolvedClient.chain?.formatters ?? chain.formatters,
|
|
55
57
|
prepareTransactionRequest: resolvedClient.chain?.prepareTransactionRequest ?? chain.prepareTransactionRequest,
|
|
@@ -76,4 +78,15 @@ export function getResolver(parameters) {
|
|
|
76
78
|
});
|
|
77
79
|
};
|
|
78
80
|
}
|
|
81
|
+
/** Rejects a resolved client whose known chain conflicts with the requested payment chain. */
|
|
82
|
+
export function assertChainId(client, chainId) {
|
|
83
|
+
if (chainId !== undefined && client.chain?.id !== undefined && client.chain.id !== chainId)
|
|
84
|
+
throw new Error(`Chain ID mismatch: expected ${chainId}, got ${client.chain.id}.`);
|
|
85
|
+
}
|
|
86
|
+
/** Rejects disallowed or unknown payment chains when an allowlist is configured. */
|
|
87
|
+
export function assertAllowedChainId(allowedChainIds, chainId) {
|
|
88
|
+
if (allowedChainIds !== undefined &&
|
|
89
|
+
(chainId === undefined || !allowedChainIds.includes(chainId)))
|
|
90
|
+
throw new Error(`Chain ID not allowed: ${chainId ?? 'unknown'}.`);
|
|
91
|
+
}
|
|
79
92
|
//# sourceMappingURL=Client.js.map
|
|
@@ -28,8 +28,8 @@ export type Signer = Account & {
|
|
|
28
28
|
signTypedData?: (parameters: any) => Promise<`0x${string}`>;
|
|
29
29
|
};
|
|
30
30
|
export type Config = {
|
|
31
|
-
/** Account used to sign exact EVM payment payloads. */
|
|
32
|
-
account
|
|
31
|
+
/** Account used to sign exact EVM payment payloads, unless supplied in credential context. */
|
|
32
|
+
account?: Account | undefined;
|
|
33
33
|
/** Optional token decimals used to parse `maxAmount` when currency metadata is not provided. */
|
|
34
34
|
decimals?: number | undefined;
|
|
35
35
|
/** Optional maximum display-unit amount the client is willing to pay. */
|
|
@@ -8,7 +8,7 @@ import * as Types from '../Types.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export async function createCredential(parameters) {
|
|
10
10
|
const account = (parameters.context?.account ?? parameters.config.account);
|
|
11
|
-
if (!account
|
|
11
|
+
if (!account?.signTypedData)
|
|
12
12
|
throw new Error('x402 exact requires a typed-data signer.');
|
|
13
13
|
const request = parameters.challenge.request;
|
|
14
14
|
const accepted = Types.toPaymentRequirements(request);
|
|
@@ -52,6 +52,8 @@ export declare function resolveOptions(parameters: {
|
|
|
52
52
|
export declare function createPath(config: ResolvedOptions): Path;
|
|
53
53
|
/** Settles a verified EVM authorization through an x402 facilitator. */
|
|
54
54
|
export declare function settleWithFacilitator(parameters: ResolvedOptions): SettleWithFacilitator;
|
|
55
|
+
/** Checks an EVM authorization with the facilitator without settling it. */
|
|
56
|
+
export declare function verifyWithFacilitator(parameters: ResolvedOptions, authorization: Parameters<SettleWithFacilitator>[0]): Promise<void>;
|
|
55
57
|
export type SettleWithFacilitator = (parameters: {
|
|
56
58
|
credential: Credential.Credential<Types.AuthorizationPayload>;
|
|
57
59
|
payload: Types.AuthorizationPayload;
|
|
@@ -174,34 +174,11 @@ export function createPath(config) {
|
|
|
174
174
|
}
|
|
175
175
|
/** Settles a verified EVM authorization through an x402 facilitator. */
|
|
176
176
|
export function settleWithFacilitator(parameters) {
|
|
177
|
-
const { facilitator
|
|
177
|
+
const { facilitator } = parameters;
|
|
178
178
|
if (!facilitator)
|
|
179
179
|
throw new Error('EVM authorization x402 requires `facilitator`.');
|
|
180
|
-
return async (
|
|
181
|
-
const paymentRequirements =
|
|
182
|
-
...parameters,
|
|
183
|
-
maxTimeoutSeconds,
|
|
184
|
-
});
|
|
185
|
-
const paymentPayload = {
|
|
186
|
-
accepted: paymentRequirements,
|
|
187
|
-
payload: {
|
|
188
|
-
authorization: {
|
|
189
|
-
from: payload.from,
|
|
190
|
-
nonce: payload.nonce,
|
|
191
|
-
to: payload.to,
|
|
192
|
-
validAfter: payload.validAfter,
|
|
193
|
-
validBefore: payload.validBefore,
|
|
194
|
-
value: payload.value,
|
|
195
|
-
},
|
|
196
|
-
signature: payload.signature,
|
|
197
|
-
},
|
|
198
|
-
x402Version: 2,
|
|
199
|
-
};
|
|
200
|
-
const verified = await facilitator.verify(paymentPayload, paymentRequirements);
|
|
201
|
-
if (!verified.isValid)
|
|
202
|
-
throw new VerificationFailedError({
|
|
203
|
-
reason: verified.invalidMessage ?? verified.invalidReason ?? 'EVM facilitator verify failed',
|
|
204
|
-
});
|
|
180
|
+
return async (authorization) => {
|
|
181
|
+
const { paymentPayload, paymentRequirements } = facilitatorPayment(parameters, authorization);
|
|
205
182
|
const settled = await facilitator.settle(paymentPayload, paymentRequirements);
|
|
206
183
|
if (!settled.success)
|
|
207
184
|
throw new VerificationFailedError({
|
|
@@ -212,6 +189,37 @@ export function settleWithFacilitator(parameters) {
|
|
|
212
189
|
};
|
|
213
190
|
};
|
|
214
191
|
}
|
|
192
|
+
/** Checks an EVM authorization with the facilitator without settling it. */
|
|
193
|
+
export async function verifyWithFacilitator(parameters, authorization) {
|
|
194
|
+
const { facilitator } = parameters;
|
|
195
|
+
if (!facilitator)
|
|
196
|
+
throw new Error('EVM authorization x402 requires `facilitator`.');
|
|
197
|
+
const { paymentPayload, paymentRequirements } = facilitatorPayment(parameters, authorization);
|
|
198
|
+
const verified = await facilitator.verify(paymentPayload, paymentRequirements);
|
|
199
|
+
if (!verified.isValid)
|
|
200
|
+
throw new VerificationFailedError({
|
|
201
|
+
reason: verified.invalidMessage ?? verified.invalidReason ?? 'EVM facilitator verify failed',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function facilitatorPayment(parameters, { payload, request }) {
|
|
205
|
+
const paymentRequirements = toPaymentRequirements(request, parameters);
|
|
206
|
+
const paymentPayload = {
|
|
207
|
+
accepted: paymentRequirements,
|
|
208
|
+
payload: {
|
|
209
|
+
authorization: {
|
|
210
|
+
from: payload.from,
|
|
211
|
+
nonce: payload.nonce,
|
|
212
|
+
to: payload.to,
|
|
213
|
+
validAfter: payload.validAfter,
|
|
214
|
+
validBefore: payload.validBefore,
|
|
215
|
+
value: payload.value,
|
|
216
|
+
},
|
|
217
|
+
signature: payload.signature,
|
|
218
|
+
},
|
|
219
|
+
x402Version: 2,
|
|
220
|
+
};
|
|
221
|
+
return { paymentPayload, paymentRequirements };
|
|
222
|
+
}
|
|
215
223
|
/** Returns whether a credential was converted from an x402 payment payload. */
|
|
216
224
|
export function isCredential(credential) {
|
|
217
225
|
return credential[x402Credential] === true;
|