mppx 0.9.3 → 0.10.1

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.
@@ -1,10 +1,12 @@
1
- import type { EndpointSpec } from './helpers.js';
1
+ import type { CheckResult, EndpointSpec } from './helpers.js';
2
2
  export declare function fetchDiscoveryDoc(baseUrl: string): Promise<{
3
3
  doc: unknown;
4
4
  raw: string;
5
5
  } | {
6
6
  error: string;
7
7
  }>;
8
+ /** Checks that the server publishes nonempty text documentation at /llms.txt. */
9
+ export declare function validateLlmsDoc(baseUrl: string): Promise<CheckResult>;
8
10
  export declare function extractEndpointsFromDiscovery(doc: Record<string, unknown>): EndpointSpec[];
9
11
  export declare function extractRequestBodyFromDiscovery(doc: Record<string, unknown>, endpoint: EndpointSpec): string | undefined;
10
12
  export declare function buildUrl(baseUrl: string, endpoint: EndpointSpec, query?: string[]): string;
@@ -1,4 +1,4 @@
1
- import { fetchWithTimeout, HTTP_METHODS } from './helpers.js';
1
+ import { check, fetchWithTimeout, HTTP_METHODS } from './helpers.js';
2
2
  export async function fetchDiscoveryDoc(baseUrl) {
3
3
  // Trailing slash makes URL treat baseUrl as a directory, so relative resolution appends rather than replaces the last segment.
4
4
  const url = new URL('openapi.json', baseUrl.replace(/\/?$/, '/')).href;
@@ -18,6 +18,33 @@ export async function fetchDiscoveryDoc(baseUrl) {
18
18
  return { error: error.message };
19
19
  }
20
20
  }
21
+ /** Checks that the server publishes nonempty text documentation at /llms.txt. */
22
+ export async function validateLlmsDoc(baseUrl) {
23
+ const url = new URL('/llms.txt', baseUrl).href;
24
+ const label = 'llms.txt found and nonempty';
25
+ try {
26
+ const response = await fetchWithTimeout(url, {});
27
+ if (!response.ok)
28
+ throw new Error(`HTTP ${response.status}`);
29
+ const body = (await response.text()).trim();
30
+ if (!body)
31
+ throw new Error('Empty document');
32
+ const contentType = response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase();
33
+ if ((contentType && !contentType.startsWith('text/')) ||
34
+ contentType === 'text/html' ||
35
+ /^<(?:!doctype html|html|head|body)\b/i.test(body))
36
+ throw new Error('Expected text documentation, received non-text or HTML content');
37
+ return check(label, url);
38
+ }
39
+ catch (error) {
40
+ return {
41
+ label,
42
+ detail: `${url}: ${error.message}`,
43
+ severity: 'suggested',
44
+ hint: 'Serve nonempty text documentation at /llms.txt.',
45
+ };
46
+ }
47
+ }
21
48
  // Extracts testable endpoints from an OpenAPI doc. Prefers endpoints with
22
49
  // explicit x-payment-info (the server declares them as paid). Falls back to
23
50
  // endpoints that list a 402 response (weaker signal, but still worth testing).
@@ -2,7 +2,7 @@ export type CheckResult = {
2
2
  label: string;
3
3
  detail?: string | undefined;
4
4
  hint?: string | undefined;
5
- severity: 'pass' | 'fail' | 'warn' | 'skip';
5
+ severity: 'pass' | 'fail' | 'warn' | 'suggested' | 'skip';
6
6
  };
7
7
  export type EndpointSpec = {
8
8
  method: string;
@@ -33,10 +33,11 @@ export type Counts = {
33
33
  passed: number;
34
34
  failed: number;
35
35
  warnings: number;
36
+ suggested: number;
36
37
  skipped: number;
37
38
  };
38
39
  export declare function printResults(results: CheckResult[], counts: Counts): void;
39
- export declare function fetchWithTimeout(url: string, init: RequestInit, timeoutMs?: number): Promise<Response>;
40
+ export declare function fetchWithTimeout(url: RequestInfo | URL, init: RequestInit, timeoutMs?: number): Promise<Response>;
40
41
  export declare function formatBytes(bytes: number): string;
41
42
  export declare const HTTP_METHODS: Set<string>;
42
43
  export declare function isValidAddress(addr: unknown): boolean;
@@ -28,6 +28,7 @@ const SEVERITY_ICONS = {
28
28
  pass: pc.green('✓'),
29
29
  fail: pc.red('✗'),
30
30
  warn: pc.yellow('⚠'),
31
+ suggested: pc.cyan('◇'),
31
32
  skip: pc.dim('○'),
32
33
  };
33
34
  export function printCheck(result) {
@@ -50,6 +51,8 @@ export function printResults(results, counts) {
50
51
  counts.failed++;
51
52
  else if (result.severity === 'warn')
52
53
  counts.warnings++;
54
+ else if (result.severity === 'suggested')
55
+ counts.suggested++;
53
56
  else if (result.severity === 'skip')
54
57
  counts.skipped++;
55
58
  }
@@ -53,7 +53,7 @@ const validate = Cli.create('validate', {
53
53
  // Streaming human-readable output
54
54
  const baseUrl = c.args.url.replace(/\/$/, '').replace(/\/openapi\.json$/i, '');
55
55
  console.log(`\n${pc.bold('mppx validate')} ${pc.dim(baseUrl)}\n`);
56
- const counts = { passed: 0, failed: 0, warnings: 0, skipped: 0 };
56
+ const counts = { passed: 0, failed: 0, warnings: 0, suggested: 0, skipped: 0 };
57
57
  let sawMppEndpoint = false;
58
58
  let sawNonMppPaymentEndpoint = false;
59
59
  let sawMalformedChallenge = false;
@@ -165,6 +165,8 @@ function printSummary(counts, flags, endpointsLength) {
165
165
  parts.push(pc.red(`${counts.failed} failed`));
166
166
  if (counts.warnings > 0)
167
167
  parts.push(pc.yellow(`${counts.warnings} warning(s)`));
168
+ if (counts.suggested > 0)
169
+ parts.push(pc.cyan(`${counts.suggested} suggested`));
168
170
  if (counts.skipped > 0)
169
171
  parts.push(pc.yellow(`${counts.skipped} skipped`));
170
172
  console.log(`${pc.bold('Summary:')} ${parts.join(', ')}`);
@@ -11,8 +11,9 @@ import * as Constants from '../../Constants.js';
11
11
  import * as Receipt from '../../Receipt.js';
12
12
  import { tempo as tempoMethods } from '../../tempo/client/index.js';
13
13
  import { chainId as tempoChainIds } from '../../tempo/internal/defaults.js';
14
+ import { isTempoSessionChallenge } from '../../tempo/session/client/Transports.js';
14
15
  import { resolveAccount, resolveAccountName } from '../account.js';
15
- import { loadConfig, preparePayment, resolvePlugin } from '../internal.js';
16
+ import { assertSamePaymentRequest, flattenConfigMethods, loadConfig, preparePayment, resolvePlugin, } from '../internal.js';
16
17
  import { fetchTokenInfo, confirm, pc } from '../utils.js';
17
18
  import { buildUrl } from './discovery.js';
18
19
  import { check, fail, fetchWithTimeout, formatBytes, isValidIntegerAmount, parseHeaders, skip, warn, } from './helpers.js';
@@ -156,6 +157,8 @@ export async function validatePaymentFlow(baseUrl, endpoint, verbose, options) {
156
157
  const tempoTestnetChallenge = challenges.find((ch) => {
157
158
  if (ch.method !== Constants.Methods.tempo)
158
159
  return false;
160
+ if (flattenConfigMethods(loaded?.config)?.some((method) => method.name === ch.method && method.intent === ch.intent))
161
+ return false;
159
162
  const req = ch.request;
160
163
  const md = req.methodDetails;
161
164
  return typeof md?.chainId === 'number' && md.chainId !== tempoChainIds.mainnet;
@@ -174,7 +177,7 @@ export async function validatePaymentFlow(baseUrl, endpoint, verbose, options) {
174
177
  const credentialContext = await preparePayment(tempoTestnetChallenge, loaded?.config.extensions);
175
178
  const cred = await mppx.createCredential(fakeResp, credentialContext);
176
179
  results.push(check('Payment: submitted', 'ephemeral testnet wallet'));
177
- await sendAndValidateResponse(results, url, endpoint, cred, fetchHeaders, fetchBody, verbose, tempoModerato);
180
+ await sendAndValidateResponse(results, url, endpoint, cred, fetchHeaders, fetchBody, verbose, tempoModerato, Challenge.credentialHeader(tempoTestnetChallenge));
178
181
  }
179
182
  catch (error) {
180
183
  results.push(fail('Payment: create credential', error.message));
@@ -256,31 +259,42 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
256
259
  : undefined;
257
260
  const decimals = methodDetails?.decimals ?? request.decimals ?? 6;
258
261
  const currency = request.currency;
259
- // Resolve wallet
260
- let walletAddress;
261
- try {
262
- walletAddress = await resolveWalletAddress();
263
- }
264
- catch { }
265
- if (!walletAddress) {
266
- results.push(skip(tag, 'no wallet configured. Run "mppx account create" to create one.'));
262
+ const { plugin, method: directMethod } = resolvePlugin(challenge, loaded?.config);
263
+ if (!plugin && !directMethod) {
264
+ results.push(skip(tag, methodSetupHint(challenge)));
267
265
  return;
268
266
  }
267
+ // Direct methods own their payer; the CLI wallet may be unrelated or absent.
268
+ let walletAddress;
269
+ if (!directMethod) {
270
+ try {
271
+ walletAddress = await resolveWalletAddress();
272
+ }
273
+ catch { }
274
+ if (!walletAddress) {
275
+ results.push(skip(tag, 'no wallet configured. Run "mppx account create" to create one.'));
276
+ return;
277
+ }
278
+ }
269
279
  // Pre-flight balance check and chain resolution
270
280
  let paymentChain;
271
- if (challenge.method === Constants.Methods.tempo)
272
- paymentChain = tempoMainnetChain;
281
+ if (challenge.method === Constants.Methods.tempo) {
282
+ const chainId = methodDetails?.chainId ??
283
+ (directMethod ? undefined : tempoMainnetChain.id);
284
+ if (chainId !== undefined)
285
+ paymentChain = chainId === tempoModerato.id ? tempoModerato : resolveEvmChain(chainId);
286
+ }
273
287
  else if (challenge.method === Constants.Methods.evm) {
274
288
  const chainId = methodDetails?.chainId;
275
289
  if (chainId)
276
290
  paymentChain = resolveEvmChain(chainId);
277
291
  }
278
292
  let tokenSymbol;
279
- if (requiredAmount && currency && paymentChain) {
293
+ if (walletAddress && requiredAmount && currency && paymentChain) {
280
294
  try {
281
295
  let balance;
282
296
  if (challenge.method === Constants.Methods.tempo) {
283
- const client = createClient({ chain: tempoMainnetChain, transport: http() });
297
+ const client = createClient({ chain: paymentChain, transport: http() });
284
298
  const info = await fetchTokenInfo(client, currency, walletAddress);
285
299
  balance = info.balance;
286
300
  tokenSymbol = info.symbol;
@@ -309,8 +323,10 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
309
323
  const chainName = paymentChain?.name;
310
324
  const paymentDesc = `${amountDisplay}${tokenDisplay ? ` ${tokenDisplay}` : ''}${chainName ? ` on ${chainName}` : ''}`;
311
325
  if (!options.silent)
312
- console.log(pc.dim(` Attempting payment with wallet ${walletAddress}`));
313
- if (paymentChain?.testnet) {
326
+ console.log(pc.dim(walletAddress
327
+ ? ` Attempting payment with wallet ${walletAddress}`
328
+ : ' Attempting payment with configured method'));
329
+ if (!directMethod && paymentChain?.testnet) {
314
330
  if (!options.silent)
315
331
  console.log(pc.dim(` Auto-approved: ${paymentDesc} (testnet)`));
316
332
  }
@@ -329,14 +345,6 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
329
345
  if (!options.silent)
330
346
  console.log(pc.dim(` Auto-approved: ${paymentDesc}`));
331
347
  }
332
- // Resolve plugin and pay
333
- const resolved = resolvePlugin(challenge, loaded?.config);
334
- const plugin = resolved.plugin;
335
- const directMethod = resolved.method;
336
- if (!plugin && !directMethod) {
337
- results.push(skip(tag, methodSetupHint(challenge)));
338
- return;
339
- }
340
348
  let methods;
341
349
  let createCredentialFn;
342
350
  let credentialContext;
@@ -359,14 +367,58 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
359
367
  else {
360
368
  methods = [directMethod];
361
369
  }
370
+ if (directMethod && isTempoSessionChallenge(challenge)) {
371
+ let initial = true;
372
+ const mppx = Mppx.create({
373
+ methods: [directMethod],
374
+ polyfill: false,
375
+ async onChallenge(retry, { createCredential }) {
376
+ const context = await preparePayment(retry, loaded?.config.extensions);
377
+ assertSamePaymentRequest(challenge, retry);
378
+ const credential = await createCredential(context);
379
+ results.push(check(`${tag}: submitted`));
380
+ return credential;
381
+ },
382
+ fetch: async (input, init) => {
383
+ if (initial) {
384
+ initial = false;
385
+ return new Response(null, {
386
+ status: 402,
387
+ headers: {
388
+ [Constants.Headers.wwwAuthenticate]: Challenge.serialize(challenge),
389
+ },
390
+ });
391
+ }
392
+ return fetchWithTimeout(input, init ?? {}, 30_000);
393
+ },
394
+ });
395
+ try {
396
+ const response = await mppx.fetch(url, {
397
+ method: endpoint.method,
398
+ headers: fetchHeaders,
399
+ body: fetchBody ?? null,
400
+ });
401
+ await validatePaymentResponse(results, response, verbose, paymentChain);
402
+ }
403
+ catch (error) {
404
+ results.push(fail(tag, error.message));
405
+ }
406
+ return;
407
+ }
362
408
  const credential = await createAndSend(challenge, methods, createCredentialFn, tag, results, loaded?.config.extensions, credentialContext);
363
409
  if (!credential)
364
410
  return;
365
411
  plugin?.prepareCredentialRequest?.({ challenge, credential, headers: fetchHeaders });
366
- await sendAndValidateResponse(results, url, endpoint, credential, fetchHeaders, fetchBody, verbose, paymentChain);
412
+ await sendAndValidateResponse(results, url, endpoint, credential, fetchHeaders, fetchBody, verbose, paymentChain, Challenge.credentialHeader(challenge));
367
413
  }
368
414
  async function attemptStripePayment(challenge, tag, ctx) {
369
- const { results, url, endpoint, fetchHeaders, fetchBody, verbose, loaded, options, isStripeTestKey, stripeKey, } = ctx;
415
+ const { results, url, endpoint, fetchHeaders, fetchBody, verbose, loaded, options, stripeKey } = ctx;
416
+ const { plugin, method: directMethod } = resolvePlugin(challenge, loaded?.config);
417
+ if (!plugin && !directMethod) {
418
+ results.push(skip(tag, 'no Stripe payment method available'));
419
+ return;
420
+ }
421
+ const isStripeTestKey = Boolean(plugin && !loaded?.config.plugins?.includes(plugin) && ctx.isStripeTestKey);
370
422
  const request = challenge.request;
371
423
  const requiredAmount = isValidIntegerAmount(request.amount)
372
424
  ? BigInt(request.amount)
@@ -395,42 +447,40 @@ async function attemptStripePayment(challenge, tag, ctx) {
395
447
  if (!options.silent)
396
448
  console.log(pc.dim(` Auto-approved: ${paymentDesc}`));
397
449
  }
398
- // Resolve plugin
399
- const resolved = resolvePlugin(challenge, loaded?.config);
400
- const plugin = resolved.plugin;
401
- if (!plugin) {
402
- results.push(skip(tag, 'no Stripe plugin available'));
403
- return;
404
- }
405
450
  let methods;
406
451
  let createCredentialFn;
407
452
  let credentialContext;
408
- try {
409
- const methodOpts = { paymentMethod: 'pm_card_visa' };
410
- if (stripeKey)
411
- methodOpts.secretKey = stripeKey;
412
- const pluginResult = await plugin.setup({
413
- challenge,
414
- options: {},
415
- methodOpts,
416
- });
417
- methods = pluginResult.methods;
418
- createCredentialFn = pluginResult.createCredential;
419
- credentialContext = pluginResult.credentialContext;
453
+ if (plugin) {
454
+ try {
455
+ const methodOpts = { paymentMethod: 'pm_card_visa' };
456
+ if (stripeKey)
457
+ methodOpts.secretKey = stripeKey;
458
+ const pluginResult = await plugin.setup({
459
+ challenge,
460
+ options: {},
461
+ methodOpts,
462
+ });
463
+ methods = pluginResult.methods;
464
+ createCredentialFn = pluginResult.createCredential;
465
+ credentialContext = pluginResult.credentialContext;
466
+ }
467
+ catch (error) {
468
+ results.push(skip(tag, error.message));
469
+ return;
470
+ }
420
471
  }
421
- catch (error) {
422
- results.push(skip(tag, error.message));
423
- return;
472
+ else {
473
+ methods = [directMethod];
424
474
  }
425
475
  const credential = await createAndSend(challenge, methods, createCredentialFn, tag, results, loaded?.config.extensions, credentialContext);
426
476
  if (!credential)
427
477
  return;
428
- plugin.prepareCredentialRequest?.({ challenge, credential, headers: fetchHeaders });
478
+ plugin?.prepareCredentialRequest?.({ challenge, credential, headers: fetchHeaders });
429
479
  // Stripe testmode: detect livemode rejection gracefully
430
480
  if (isStripeTestKey) {
431
481
  const resp = await fetchWithTimeout(url, {
432
482
  method: endpoint.method,
433
- headers: { ...fetchHeaders, [Constants.Headers.authorization]: credential },
483
+ headers: { ...fetchHeaders, [Challenge.credentialHeader(challenge)]: credential },
434
484
  body: fetchBody ?? null,
435
485
  }, 30_000);
436
486
  if (resp.status >= 200 && resp.status < 300) {
@@ -441,7 +491,7 @@ async function attemptStripePayment(challenge, tag, ctx) {
441
491
  }
442
492
  return;
443
493
  }
444
- await sendAndValidateResponse(results, url, endpoint, credential, fetchHeaders, fetchBody, verbose, undefined);
494
+ await sendAndValidateResponse(results, url, endpoint, credential, fetchHeaders, fetchBody, verbose, undefined, Challenge.credentialHeader(challenge));
445
495
  }
446
496
  async function createAndSend(challenge, methods, createCredentialFn, tag, results, extensions, initialCredentialContext) {
447
497
  const fakeResponse = new Response(null, {
@@ -472,12 +522,12 @@ async function createAndSend(challenge, methods, createCredentialFn, tag, result
472
522
  return undefined;
473
523
  }
474
524
  }
475
- async function sendAndValidateResponse(results, url, endpoint, credential, baseHeaders, fetchBody, verbose, explorerChain) {
525
+ async function sendAndValidateResponse(results, url, endpoint, credential, baseHeaders, fetchBody, verbose, explorerChain, credentialHeader = Constants.Headers.authorization) {
476
526
  let paymentResponse;
477
527
  try {
478
528
  paymentResponse = await fetchWithTimeout(url, {
479
529
  method: endpoint.method,
480
- headers: { ...baseHeaders, [Constants.Headers.authorization]: credential },
530
+ headers: { ...baseHeaders, [credentialHeader]: credential },
481
531
  body: fetchBody ?? null,
482
532
  }, 30_000);
483
533
  }
@@ -485,6 +535,9 @@ async function sendAndValidateResponse(results, url, endpoint, credential, baseH
485
535
  results.push(fail('Payment: send credential', error.message));
486
536
  return results;
487
537
  }
538
+ return validatePaymentResponse(results, paymentResponse, verbose, explorerChain);
539
+ }
540
+ async function validatePaymentResponse(results, paymentResponse, verbose, explorerChain) {
488
541
  if (paymentResponse.status === 402) {
489
542
  const body = await paymentResponse.text().catch(() => '');
490
543
  let detail = 'Payment rejected';
@@ -24,6 +24,30 @@ export type PreparedPayment<methods extends readonly Method.AnyClient[] = readon
24
24
  /** Attaches a credential using the protocol that produced the selected challenge. */
25
25
  setCredential: (request: Transport.RequestOf<transport>, credential: string) => Transport.RequestOf<transport>;
26
26
  }>;
27
+ /** An HTTP request prepared together with its response and optional payment. */
28
+ export type PreparedRequest<methods extends readonly Method.AnyClient[], requirePayment extends boolean = false> = Readonly<{
29
+ /** Exact request that produced {@link response}. */
30
+ request: Request;
31
+ /** Response returned for {@link request}. */
32
+ response: Response;
33
+ /** Redirects followed before receiving {@link response}. */
34
+ redirects: readonly PreparedRequest.Redirect[];
35
+ /** Selected payment when the response requires payment. */
36
+ payment: requirePayment extends true ? PreparedRequest.Payment<methods> : PreparedRequest.Payment<methods> | undefined;
37
+ }>;
38
+ export declare namespace PreparedRequest {
39
+ /** A request-bound payment that can be inspected or paid. */
40
+ type Payment<methods extends readonly Method.AnyClient[]> = Readonly<PreparedPayment<methods, Transport.Transport<RequestInit, Response>> & {
41
+ /** Creates and sends a credential to the prepared request without following redirects. */
42
+ pay: (context?: AnyContextFor<methods> | undefined) => Promise<Response>;
43
+ }>;
44
+ /** A redirect followed while discovering a payment challenge. */
45
+ type Redirect = Readonly<{
46
+ from: string;
47
+ status: number;
48
+ to: string;
49
+ }>;
50
+ }
27
51
  /**
28
52
  * Client-side payment handler.
29
53
  */
@@ -50,6 +74,18 @@ export type Mppx<methods extends Methods = Methods, transport extends Transport.
50
74
  * ```
51
75
  */
52
76
  preparePayment: (response: Transport.ResponseOf<transport>, options?: preparePayment.Options<FlattenMethods<methods>, transport> | undefined) => Promise<PreparedPayment<FlattenMethods<methods>, transport>>;
77
+ /**
78
+ * Follows safe pre-payment redirects and returns the response with its exact request. When the
79
+ * response requires payment, prepares its selected challenge without creating a credential.
80
+ * Credential-bearing requests never follow redirects. Requires a runtime that exposes manual
81
+ * redirect responses; browsers return opaque redirects and are not supported.
82
+ */
83
+ prepareRequest: transport extends Transport.Transport<RequestInit, Response> ? {
84
+ <const requirePayment extends boolean>(input: RequestInfo | URL, init: RequestInit | undefined, options: prepareRequest.Options<FlattenMethods<methods>, requirePayment> & {
85
+ requirePayment: requirePayment;
86
+ }): Promise<PreparedRequest<FlattenMethods<methods>, requirePayment>>;
87
+ (input: RequestInfo | URL, init?: RequestInit | undefined, options?: prepareRequest.Options<FlattenMethods<methods>> | undefined): Promise<PreparedRequest<FlattenMethods<methods>>>;
88
+ } : never;
53
89
  /** Creates a credential from a payment-required response by routing to the correct method. */
54
90
  createCredential: (response: Transport.ResponseOf<transport>, context?: AnyContextFor<FlattenMethods<methods>> | undefined, options?: createCredential.Options<FlattenMethods<methods>, transport> | undefined) => Promise<string>;
55
91
  /** Register a client event handler by canonical event name. */
@@ -89,6 +125,15 @@ export declare namespace preparePayment {
89
125
  /** Options for selecting a payment without creating its credential. */
90
126
  type Options<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[], transport extends Transport.AnyTransport = Transport.Transport> = createCredential.Options<methods, transport>;
91
127
  }
128
+ export declare namespace prepareRequest {
129
+ /** Options for preparing a request-bound payment. */
130
+ type Options<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[], requirePayment extends boolean = false> = Omit<preparePayment.Options<methods, Transport.Transport<RequestInit, Response>>, 'request'> & {
131
+ /** Maximum redirects followed before rejecting the request. @default 20 */
132
+ maxRedirects?: number | undefined;
133
+ /** Throw when the response does not require payment. @default false */
134
+ requirePayment?: requirePayment | undefined;
135
+ };
136
+ }
92
137
  export declare namespace createCredential {
93
138
  type Options<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[], transport extends Transport.AnyTransport = Transport.Transport> = {
94
139
  /** Request-local Accept-Payment override for manual rawFetch + createCredential flows. */