UNPKG

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.

291 lines • 13.4 kB
/** * 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"; import { type PaymentOffer, type PaymentRail } from "./payment-offers.js"; import { type TempoChain } from "./tempo.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; /** What the balance is of, for messages: "x402 USDC" unless set. */ balanceLabel?: string; } 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[], context?: string); } 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[]; /** One mppx challenge candidate; only the challenge's own fields are read. */ interface MppChallengeCandidate { challenge: { method: string; intent: string; request: unknown; }; } /** * MPP on Tempo: keep the Tempo challenges the wallet can cover, in the order * the server offered them (spec `mpp-tempo-client-surface`). Run402 offers * both Tempo chains on every SKU, mainnet first on paid SKUs and Moderato * first on the free tier; mppx pays the first candidate it is handed, so * dropping the chains this wallet cannot pay lets a faucet-funded wallet pay * Moderato and a mainnet-funded wallet pay mainnet with no chain setting. * * "Cover" is the amount plus the sender's Tempo fee headroom. When nothing is * covered this raises the same balance-preflight errors as x402: an RPC * failure when a read failed, else `X402_INSUFFICIENT_FUNDS` naming every * offered chain, before anything is signed. */ export declare function orderTempoCandidates<C extends MppChallengeCandidate>(candidates: readonly C[], address: string, readBalance: (chain: TempoChain) => Promise<bigint>): Promise<C[]>; 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>, offers?: readonly PaymentOffer[], configuredRail?: PaymentRail): 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