/** * x402 EVC authorization-evidence profile (spec/x402-evc-profile-v0.md). * * Bridges an x402 `PAYMENT-REQUIRED` flow into the External Verifier * Contract v1: the host builds one EVC §2.1 request carrying an `x402_evc` * extension member (envelope-level, §2.2 `additionalProperties` seam — the * `request` object and `bundle` are untouched, so every conformant v1 * verifier keeps working), dispatches it to a configured verifier, and fails * closed to an RFC 9457 problem+json denial. * * This is ADDITIVE to the existing `x402.ts` adapter: that path carries a * mutual ZK handshake bound to a server challenge; this path carries an * operator-signed spend mandate (`bvp/1`) verified through the EVC — gate 1 * (authorization evidence) of the two-gates model. Payee risk (gate 2) is out * of scope by design. * * Everything decision-shaped is reused from `@bolyra/mpp` (types, classical * verifier, EVC transports, denial vocabulary, nonce store) — this module * only owns the x402-shaped mapping and the host-side challenge checks. */ import { type DenyProblem, type NonceStoreLike, type Verdict, type VerifierConfig, type VerifierRequest } from '@bolyra/mpp'; /** Profile identifier carried in every extension object. */ export declare const X402_EVC_PROFILE: "x402_evc/0"; /** * Request header carrying the `bvp/1` presentation — the same header * `@bolyra/mpp` uses, so one mandate travels identically across MPP and x402. */ export declare const X402_EVC_AUTHORIZATION_HEADER = "x-bolyra-authorization"; /** 402 response headers advertising the challenge context (spec §2 step 1). */ export declare const X402_EVC_NONCE_HEADER = "x402-evc-nonce"; export declare const X402_EVC_EXPIRES_HEADER = "x402-evc-expires"; /** * The payment requirements the profile binds to — x402 v2 vocabulary * (`network` / `payTo` / atomic-unit string `amount`, per the x402 * specification's `accepts` entries), NOT the legacy `x402.ts` adapter shape. */ export interface X402EvcRequirements { /** x402 v2 network identifier (e.g. `base-sepolia`). */ network: string; /** Asset identifier — token address or ISO currency code. */ asset: string; /** Amount in atomic token units, as a decimal string (x402 v2 shape). */ amount: string; /** Payee address/identifier — x402 v2 `payTo`. */ payTo: string; /** * Token decimals used by the default USD mapping (assumes a 1:1 USD * stablecoin). Default 6 (USDC). Non-USD assets MUST supply `amountToUsd`. */ assetDecimals?: number; } /** The 402 challenge context the resource server issued (spec §2 step 1). */ export interface X402EvcContext { /** Identifier of the paid resource being accessed (URL or route). */ resource: string; /** The x402 payment requirements advertised in the 402. */ requirements: X402EvcRequirements; /** * Single-use challenge nonce (opaque string). Known to BOTH sides before * the decision, so it doubles as the receipt instance discriminator: * profile decision receipts MUST carry it as * `instance.preimage.requestNonce` (spec/x402-evc-profile-v0.md §4.1, * spec/receipt-instance-binding-v1.md §3.2). */ nonce: string; /** Unix seconds after which this challenge context is stale. */ expiresAt: number; } /** The envelope-level extension member (spec §3). */ export interface X402EvcExtension { profile: typeof X402_EVC_PROFILE; resource: string; /** Decimal USD string. */ amount: string; asset: string; network: string; payee: string; nonce: string; expires_at: number; verifier: VerifierConfig['kind']; } /** An EVC §2.1 request carrying the profile extension. */ export type X402EvcVerifierRequest = VerifierRequest & { x402_evc: X402EvcExtension; }; /** Options shared by {@link buildX402EvcRequest} and {@link verifyX402EvcAuthorization}. */ export interface X402EvcOptions { /** The 402 challenge context. */ context: X402EvcContext; /** * The audience/payee identity this host serves — compared byte-literally * against the mandate's signed `project_key` by the verifier; a mandate * signed for another payee denies `request_mismatch`. */ audience: string; /** Verifier backend (EVC classical | command | url). */ verifier: VerifierConfig; /** Binding `program` discriminator. Default `"x402"`. */ program?: string; /** Optional model pin; when omitted, the binding's own model is echoed. */ model?: string; /** * Resolve `requirements.amount` (atomic units) to a decimal USD value. * Default assumes a 1:1 USD stablecoin with `assetDecimals` (default 6, * USDC). Non-USD assets MUST provide this. Unresolvable amounts fail closed * (`internal_error`). */ amountToUsd?: (requirements: X402EvcRequirements) => string | number; /** * Decide whether the host's authorization audience covers the x402 payee. * Default: byte-literal equality `audience === requirements.payTo`. A * mismatch denies `request_mismatch` BEFORE any verifier runs — conformant * EVC verifiers ignore the profile extension, so the host must own this * check (spec §4). */ payeeMatches?: (audience: string, payTo: string) => boolean; /** Clock override (unix seconds). Tests only. */ now?: () => number; } /** Additional options for {@link verifyX402EvcAuthorization}. */ export interface X402EvcVerifyOptions extends X402EvcOptions { /** * Reserve-before-act nonce store (EVC §7.3) for the challenge nonce AND any * verifier `consume_nonces`. Default: a process-wide in-memory store — * replay is refused within one process, but protection does not survive * restarts or span instances; inject a shared, durable store in production. */ nonceStore?: NonceStoreLike; } /** The profile's decision result. */ export interface X402EvcDecision { allowed: boolean; /** 200 on allow; `DENY_STATUS[code]` on deny. */ status: number; /** The verdict (verifier-produced, or host-local for gate-side denials). */ verdict: Verdict; /** RFC 9457 problem body — present exactly when denied. */ problem?: DenyProblem; /** The request that was (or would have been) dispatched, for audit. */ request?: X402EvcVerifierRequest; } /** * Build the EVC §2.1 request + `x402_evc` extension for one x402 retry * (spec §3). Throws {@link VerifyDenial} on unusable inputs — callers either * let {@link verifyX402EvcAuthorization} convert that to a denial or handle * it themselves. Fail closed; never guess. */ export declare function buildX402EvcRequest(options: X402EvcOptions & { bundle: string; }): X402EvcVerifierRequest; /** * Verify one x402 retry's authorization evidence (spec §2 step 3). * * Order of checks — host obligations first, then the verifier's decision: * 1. header present (else `missing_authorization`, 401) * 2. challenge context fresh (else `expired`, 403 — host-owned) * 3. build request (malformed bundle / unusable amount fail closed) * 4. dispatch to the configured verifier (every transport failure is a deny) * 5. reserve the challenge nonce + any verifier `consume_nonces` * atomically, reserve-before-act (else `nonce_replayed`, 403) */ export declare function verifyX402EvcAuthorization(presentation: string | null | undefined, options: X402EvcVerifyOptions): Promise;