run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
265 lines • 12 kB
TypeScript
/**
* Node-only x402-wrapped fetch. Reads the wallet file, checks on-chain
* USDC balances through independent RPC providers, and returns a fetch wrapper
* that auto-signs 402 responses only when the requested chain has confirmed
* funds.
*
* Balance reads are pre-payment, read-only operations. They may be retried and
* failed over safely; a payment payload has not been created or submitted yet.
* An exhausted RPC check is never represented as a zero balance.
*
* The viem / @x402/* / mppx imports live behind `./_paid-stack.ts` so the
* SDK's direct surface to those packages is auditable from one file and the
* packages can be declared as optional peer dependencies in package.json.
*
* Never calls `process.exit` — the SDK leaves exit-code decisions to the
* CLI edge.
*/
import type { CredentialsProvider } from "../credentials.js";
import { Run402Error } from "../errors.js";
import type { MppStack, X402Stack } from "./_paid-stack.js";
import { PaymentBuyerError, type PayExecutor } from "../namespaces/pay.js";
import { type PaymentAttemptStore } from "./payment-attempts.js";
type FetchFn = typeof globalThis.fetch;
interface RpcClient {
readContract: (args: unknown) => Promise<bigint>;
}
interface BalanceRetryOptions {
attemptsPerProvider?: number;
baseDelayMs?: number;
sleep?: (milliseconds: number) => Promise<void>;
random?: () => number;
}
interface PaymentRequirementLike {
network?: string;
amount?: string;
[key: string]: unknown;
}
export interface X402PaymentRequirements {
scheme: string;
network: string;
asset: string;
amount: string;
payTo: string;
maxTimeoutSeconds: number;
extra: Record<string, unknown>;
}
export interface X402PaymentRequired {
x402Version: number;
error?: string;
resource: {
url: string;
description?: string;
mimeType?: string;
};
accepts: X402PaymentRequirements[];
extensions?: Record<string, unknown>;
}
export interface X402PaymentPayload {
x402Version: number;
resource?: X402PaymentRequired["resource"];
accepted: X402PaymentRequirements;
payload: Record<string, unknown>;
extensions?: Record<string, unknown>;
}
export interface X402BuyerClient {
createPaymentPayload(required: X402PaymentRequired): Promise<X402PaymentPayload>;
}
interface KnownBalance {
status: "known";
balance: bigint;
}
interface UnknownBalance {
status: "unknown";
error: X402BalanceError;
}
type BalanceState = KnownBalance | UnknownBalance;
type BalanceStates = Record<string, BalanceState>;
export type X402BalanceErrorCode = "X402_RPC_TIMEOUT" | "X402_RPC_RATE_LIMITED" | "X402_RPC_UNAVAILABLE" | "X402_INSUFFICIENT_FUNDS";
/**
* A machine-readable x402 balance-preflight failure.
*
* `safeToRetry` is true only for read-only RPC failures. A confirmed balance
* miss is not retryable without changing wallet funds or payment requirements.
* Both states are emitted before payment payload creation, so
* `mutationState` is always `not_started`.
*/
/**
* The largest amount a first-party (api.run402.com) challenge may ask for:
* the team tier. Every gateway price is at or under it, so a challenge above
* it is not a price, it is a compromised or misconfigured host — refuse to
* sign rather than trust a number the server chose. Arbitrary-URL purchases
* through `pay.fetch` are bounded separately by the caller's `maxUsdMicros`.
*/
export declare const FIRST_PARTY_MAX_PAYMENT_USD_MICROS = 20000000;
/**
* Refuse a first-party challenge whose every accepted requirement exceeds the
* first-party cap. Requirements at or under the cap are left alone; the
* selector picks among them as usual.
*/
export declare function assertFirstPartyPaymentWithinCap(required: unknown, firstPartyOrigin: string | null): void;
/** A first-party challenge above the largest known price. Never retried blindly. */
export declare class PaymentBuyerCapError extends Run402Error {
readonly kind: "local_error";
readonly code: "X402_AMOUNT_EXCEEDS_FIRST_PARTY_CAP";
constructor(requestedUsdMicros: number);
}
export declare class X402BalanceError extends Run402Error {
readonly kind: "local_error";
readonly code: X402BalanceErrorCode;
readonly cause?: unknown;
constructor(code: X402BalanceErrorCode, message: string, details: Record<string, unknown>, cause?: unknown, nextActions?: import("../errors.js").NextAction[]);
}
export type X402PaymentNetwork = "eip155:8453" | "eip155:84532";
/** Public-chain operations exposed to an opaque signer provider. */
export interface PaymentPublicClient {
readContract(args: unknown): Promise<bigint>;
}
/**
* Minimum EVM signer shape needed by x402. Implementations may keep key
* material behind KMS/HSM boundaries; only the public payer address and
* signing operation cross into the SDK.
*/
export interface EvmPaymentSigner {
readonly address: `0x${string}`;
signTypedData(message: {
domain: Record<string, unknown>;
types: Record<string, unknown>;
primaryType: string;
message: Record<string, unknown>;
}): Promise<`0x${string}`>;
readContract?(args: unknown): Promise<unknown>;
signTransaction?(args: unknown): Promise<`0x${string}`>;
getTransactionCount?(args: {
address: `0x${string}`;
}): Promise<number>;
estimateFeesPerGas?(): Promise<{
maxFeePerGas: bigint;
maxPriorityFeePerGas: bigint;
}>;
}
/** Async, opaque x402 payer. Returning null means that network is unsupported. */
export interface EvmPaymentSignerProvider {
getSigner(context: {
network: X402PaymentNetwork;
publicClient: PaymentPublicClient;
}): Promise<EvmPaymentSigner | null>;
}
export type PaymentPayerSource = "payment_signer" | "wallet_path" | "credentials" | "default_wallet";
/** Safe, key-free provenance for the payer selected by paid fetch. */
export interface PaymentPayerProvenance {
readonly source: PaymentPayerSource;
readonly rail: "x402" | "mpp" | "lightning";
readonly payers: readonly {
readonly address: string;
readonly network?: X402PaymentNetwork;
}[];
}
export type ConfiguredPaidFetch = FetchFn & {
readonly payer: PaymentPayerProvenance;
readonly pay?: PayExecutor;
/** Refreshes mutable balance state without re-resolving the selected payer. */
refreshBalances(): Promise<void>;
};
export type LazyPaidFetch = FetchFn & {
/** Initializes the selected source if needed and returns public payer provenance only. */
getPayer(): Promise<PaymentPayerProvenance | null>;
/** Execute the receipt-bearing arbitrary-URL buyer flow. */
pay: PayExecutor;
};
export interface PaidFetchOptions {
/** Explicit local wallet file. When set, no other wallet is consulted. */
walletPath?: string;
/** Auth provider whose optional wallet capability may also fund payments. */
credentials?: Pick<CredentialsProvider, "readWallet">;
/** Explicit opaque x402 signer. Mutually exclusive with walletPath. */
paymentSigner?: EvmPaymentSignerProvider;
/**
* The run402 API origin this fetch is wired to (defaults to
* `getApiBase()`). Used ONLY to recognize a same-origin gateway response
* carrying one of its own terminal, never-settled refusal codes (live-proof
* defect B — see {@link isTerminalRoomInviteRefusal}) — never to route or
* validate requests. A response from any other origin is unaffected.
*/
apiBase?: string;
/** Build the buyer for this rail regardless of the persisted one (the Lightning buyer's x402 fallback). */
railOverride?: "x402";
}
/** @internal Test seam; not re-exported from `@run402/sdk/node`. */
export declare function _setPaidStackLoadersForTest(loaders?: {
x402?: () => Promise<X402Stack>;
mpp?: () => Promise<MppStack>;
}): void;
interface TrackedPaidFetchOptions {
store?: PaymentAttemptStore;
createAttemptId?: () => string;
now?: () => string;
fetch?: FetchFn;
classifyPaymentResponse?: (response: Response) => Promise<"completed" | "failed" | "already_settled" | "intent_pending" | "ambiguous">;
}
/** @internal Exported only for deterministic source-level tests; not re-exported by the package. */
export declare function checkBalanceAcrossProviders(clients: readonly RpcClient[], tokenAddress: string, walletAddress: string, network: string, options?: BalanceRetryOptions): Promise<bigint>;
/** @internal Exported only for deterministic source-level tests; not re-exported by the package. */
export declare function filterAffordableRequirements(requirements: PaymentRequirementLike[], balances: BalanceStates,
/**
* Payer address per network. Surfaced in the insufficient-funds error so the
* `fund_wallet` next_action names WHERE to send money — "fund the wallet" is
* not executable without an address. Optional: omitting it only omits the
* `payers` detail.
*/
payers?: Readonly<Record<string, string>>): PaymentRequirementLike[];
export declare function setupPaidFetch(options?: PaidFetchOptions): Promise<ConfiguredPaidFetch | null>;
declare function createLazyPaidFetchFrom(setup: () => Promise<FetchFn | null>): FetchFn;
/**
* Returns a fetch that lazily initializes the x402 wrapper on first call.
* Failed initialization is not cached; concurrent first calls share one
* attempt, and a later request can recover after transient RPC/setup failure.
*/
export declare function createLazyPaidFetch(options?: PaidFetchOptions): LazyPaidFetch;
/** @internal Source-level unit-test seam; not re-exported by the package. */
export declare const __paidFetchInternals: {
createLazyPaidFetchFrom: typeof createLazyPaidFetchFrom;
};
interface X402BuyerFetchOptions extends TrackedPaidFetchOptions {
supportedNetworks: readonly string[];
payerAddresses?: readonly string[];
offerReceipt?: X402OfferReceiptRuntime;
verifyAtSeconds?: () => number;
}
interface X402OfferReceiptRuntime {
extractOffersFromPaymentRequired: (required: unknown) => unknown[];
decodeSignedOffers: (offers: unknown[]) => unknown[];
findAcceptsObjectFromSignedOffer: (offer: unknown, accepts: unknown[]) => unknown | undefined;
isEIP712SignedOffer: (offer: unknown) => boolean;
isEIP712SignedReceipt: (receipt: unknown) => boolean;
verifyOfferSignatureEIP712: (offer: unknown) => Promise<{
signer: `0x${string}`;
payload: Record<string, unknown>;
}>;
verifyReceiptSignatureEIP712: (receipt: unknown) => Promise<{
signer: `0x${string}`;
payload: Record<string, unknown>;
}>;
extractReceiptFromResponse: (response: Response) => unknown | undefined;
}
/**
* Receipt-bearing buyer orchestration over an already-configured x402 client.
* Signed proofs live only in this SDK instance's memory. An ambiguous retry of
* the identical request re-presents that proof; it never mints a replacement.
*/
export declare function createX402BuyerFetch(client: X402BuyerClient, options: X402BuyerFetchOptions): PayExecutor;
export declare function paymentNetworkUnsupportedError(challengeNetworkValues: string[], walletNetworkValues: string[], extra?: Record<string, unknown>): PaymentBuyerError;
/**
* Request-scoped payment tracker around `@x402/fetch`.
*
* The wrapped package may call its base fetch twice: first without payment to
* obtain the 402 challenge, then with a signed payment authorization. The
* AsyncLocalStorage context lets the base-fetch boundary update the correct
* durable attempt even when multiple paid requests run concurrently.
*
* Exported from this module for deterministic boundary tests, but intentionally
* not re-exported from the package entry point.
*/
export declare function createTrackedX402Fetch(wrapFetchWithPayment: (fetch: FetchFn, client: unknown) => FetchFn, client: unknown, opts?: TrackedPaidFetchOptions): FetchFn;
export {};
//# sourceMappingURL=paid-fetch.d.ts.map