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.
270 lines • 11.4 kB
TypeScript
/**
* Named wallets (profiles) and wallet selection, owned by the Node SDK.
*
* Each named wallet is a self-contained profile directory under
* `{config_dir}/profiles/<name>/` with its own key, project keystore, and
* non-secret `meta.json`; the reserved `default` wallet lives at the
* config-dir root. This module is the one implementation of:
*
* - the SELECTION precedence chain (`resolveWalletSelection`), highest first:
* 1. an explicit flag value (`--wallet <name>` / `--profile <name>`)
* 2. `RUN402_WALLET` / `RUN402_PROFILE`
* 3. the nearest `.run402.local.json` / `.run402.json` binding, walking up
* 4. the global default recorded by `wallets use` (base `config.json`)
* 5. `default`
* An env value that disagrees with a directory binding is a hard error
* (`WALLET_SELECTION_CONFLICT`) unless the caller is the wallet-management
* surface itself, which must keep working while selection is ambiguous.
* - the MANAGEMENT verbs on `r.wallets` (`list`, `current`, `create`, `use`,
* `rename`, `bind`, `unbind`, `import`, `remove`), which operate on explicit
* named targets and are independent of the active selection.
*
* The CLI (`run402 wallets …`, the `--wallet` pre-resolution in `cli.mjs`) and
* the MCP server consume these; neither adds a precedence rule of its own.
*
* A rejected name is a value we know nothing about — a private key pasted
* into `RUN402_WALLET` is a demonstrated case — so every refusal routes the
* value through `describeRejectedValue()`: a short typo shows in full, anything
* secret-shaped is redacted.
*/
import { type WalletData } from "../../core-dist/wallet.js";
import { LocalError, type NextAction } from "../errors.js";
import type { Client } from "../kernel.js";
import { Wallets, type WalletCreateResult } from "../namespaces/wallets.js";
import { type KmsAccountOptions } from "./signers/kms.js";
import { type SendDeps, type SendOptions, type SendPreview, type SendResult } from "./wallets-send.js";
/**
* The active profile's wallet, or null when there is none. A file that exists
* but cannot be read as a wallet is `BAD_WALLET_FILE`, naming the file and
* the way to recreate it, never the parser's own failure.
*/
export declare function readLocalWallet(): WalletData | null;
/** Where the active wallet came from, highest precedence first. `config` is the global `wallets use` default. */
export type WalletSelectionSource = "flag" | "env" | "binding" | "config" | "default";
/** A flag value handed to the resolver, e.g. `{ flag: "--wallet", value: "kychon" }`; `value` undefined when the flag had none. */
export interface WalletFlag {
flag: string;
value: string | undefined;
}
export interface WalletSelection {
name: string;
source: WalletSelectionSource;
/** The flag, env var, binding file, or `wallets use` that decided; null for the bare default. */
sourceDetail: string | null;
}
export interface ResolveWalletSelectionOptions {
walletFlag?: WalletFlag | null;
env?: Record<string, string | undefined>;
/** Directory the binding walk starts from. Default `process.cwd()`. */
cwd?: string;
/** Skip the env-vs-binding conflict error (the wallet-management surface itself). */
allowConflict?: boolean;
}
/**
* A wallet selection refused: bad name, env-vs-binding conflict, or a named
* wallet that does not exist locally. A {@link LocalError} carrying `code`,
* `hint`, and `details`, never exiting the process, so a caller mid-protocol
* (the git remote helper) can report it on its own channel.
*/
export declare class WalletSelectionError extends LocalError {
constructor({ code, message, hint, details }: {
code: string;
message: string;
hint?: string;
details?: unknown;
});
}
/** Nearest wallet binding walking up from `startDir` to the filesystem root. */
export declare function findWalletBinding(startDir: string): {
wallet: string;
file: string;
} | null;
/**
* The one precedence chain. Pure: reads the environment it is handed, the
* binding files above `cwd`, and the global default; never prompts, never
* exits. Throws {@link WalletSelectionError}.
*/
export declare function resolveWalletSelection({ walletFlag, env, cwd, allowConflict, }?: ResolveWalletSelectionOptions): WalletSelection;
/**
* Fail closed when a non-default selection names a wallet that does not exist
* locally. Throws {@link WalletSelectionError} `WALLET_NOT_FOUND`.
*/
export declare function assertWalletExists({ name, source }: Pick<WalletSelection, "name" | "source">): void;
/** The environment variable carrying the published selection's provenance. */
export declare const ACTIVE_WALLET_CONTEXT_ENV = "RUN402_ACTIVE_WALLET_JSON";
export interface ActiveWalletContext {
name: string;
source: WalletSelectionSource;
sourceDetail: string | null;
binding: {
wallet: string;
file: string;
} | null;
envName: string | null;
diverged: boolean;
}
export interface SelectWalletOptions extends ResolveWalletSelectionOptions {
/** Skip the fail-closed existence check (surfaces that create wallets). */
allowMissing?: boolean;
}
/**
* Resolve, fail closed, and PUBLISH the selection to `env` so every core path
* function resolves under it: `RUN402_WALLET` names the wallet and
* `RUN402_ACTIVE_WALLET_JSON` carries how it was chosen (read back by
* `r.wallets.current()`, since the env var alone no longer can say). Call
* once per process, before any client reads a path.
*/
export declare function selectWallet(opts?: SelectWalletOptions): WalletSelection;
/** The published selection context, or null when no surface published one. */
export declare function readActiveWalletContext(env?: Record<string, string | undefined>): ActiveWalletContext | null;
export type WalletRail = "x402" | "mpp" | "lightning";
export interface WalletDescriptor {
local_label: string;
server_label: string | null;
address: string | null;
address_short: string | null;
rail: string | null;
/** How the wallet signs: `local` (a key in wallet.json) or `aws_kms`; null when unknown. */
signer: "local" | "aws_kms" | null;
active: boolean;
}
export interface WalletWarning {
code: string;
message: string;
hint: string;
}
export interface CurrentWalletResult {
local_label: string;
source: WalletSelectionSource | "unknown";
source_detail: string | null;
address: string | null;
server_label: string | null;
configured: boolean;
created: string | null;
rail: string | null;
/** How the wallet signs: `local` or `aws_kms`; null with no wallet. */
signer: "local" | "aws_kms" | null;
faucet_used: boolean;
path: string;
next_actions?: NextAction[];
warnings: WalletWarning[];
}
export interface CreatedWalletProfile {
local_label: string;
address: string;
rail: WalletRail;
created: true;
/** The first next action's command, kept beside `next_actions` for plain readers. */
next: string;
next_actions: NextAction[];
}
export interface WalletBindResult {
wallet: string;
file: ".run402.json";
bound: true;
safe_to_commit: true;
note: string;
binding: Record<string, unknown> | null;
warning?: string;
}
export interface WalletUnbindResult {
file: ".run402.json";
unbound: boolean;
removed: boolean;
binding: Record<string, unknown> | null;
}
/** Builds a client signing as another named wallet (the label push signs as its target). */
export type WalletClientFactory = (paths: {
walletPath: string;
keystorePath: string;
}) => {
wallet(address: string): {
setLabel(label: string): Promise<{
ok: boolean;
}>;
};
};
/**
* `r.wallets` on the Node entry: the isomorphic local-wallet and label verbs
* plus the named-wallet (profile) management verbs.
*/
export declare class NodeWallets extends Wallets {
#private;
constructor(client: Client, opts?: {
clientFor?: WalletClientFactory;
});
/** Every local wallet, without loading any private key. */
list(): Promise<WalletDescriptor[]>;
/** The active wallet: its name, how it was selected, and the local wallet file's facts. */
current(): Promise<CurrentWalletResult>;
/**
* With no name: create the ACTIVE profile's wallet file (the isomorphic
* behaviour). With a name: create a new named wallet (its key stays local)
* on `rail` (default `x402`); `default` creates the root wallet. Refused on
* the sandbox surface: it writes a private key.
*/
create(): Promise<WalletCreateResult>;
create(name: string, opts?: {
rail?: string;
}): Promise<CreatedWalletProfile>;
/** Set the global default wallet (`wallets use`). */
use(name: string): Promise<{
local_label: string;
active: true;
}>;
/** Rename a wallet; renaming `default` moves it under `profiles/`. */
rename(oldName: string, newName: string): Promise<{
from: string;
to: string;
renamed: true;
}>;
/** Bind a directory (default cwd) to a wallet by writing its name into `./.run402.json`. */
bind(name?: string, opts?: {
cwd?: string;
}): Promise<WalletBindResult>;
/** Remove only the `wallet` key from `./.run402.json`; the file goes when nothing else is left in it. */
unbind(opts?: {
cwd?: string;
}): Promise<WalletUnbindResult>;
/**
* Adopt an existing signing authority as a new named wallet: a private key,
* or an AWS KMS key by reference (`{ kms: { keyId, awsProfile? } }`).
*
* A private key may be a function, called only after the name checks pass,
* so a caller reading the key from a file or stdin reads nothing for a
* refused name. A KMS key is adopted read-only: its public key and spec are
* read (`GetPublicKey`), nothing on the key is created, changed, or granted,
* and the profile holds the key's ARN, never a secret. Refused on the sandbox
* surface: it adopts a signing authority.
*/
import(name: string, source: string | (() => string) | {
kms: {
keyId: string;
awsProfile?: string;
};
}, opts?: KmsAccountOptions): Promise<{
local_label: string;
address: string;
imported: true;
signer: "local" | "aws_kms";
key_arn?: string;
}>;
/**
* Send a stablecoin from the selected wallet (key-backed or KMS-backed):
* USDC on `base` (default) / `base-sepolia`, optionally `from` a
* CoinbaseSmartWallet the wallet owns; pathUSD on `tempo` /
* `tempo-moderato`. Without `confirm: true` it runs every check and returns
* a preview without signing. Refused on the sandbox surface with
* `VALUE_TRANSFER_REQUIRES_CLI`: a transfer to an arbitrary address is a
* person's decision at the CLI.
*/
send(opts: SendOptions, deps?: SendDeps & KmsAccountOptions): Promise<SendPreview | SendResult>;
/** Delete a named wallet and its keys. Requires `{ confirm: true }`; `default` is protected. */
remove(name: string, opts?: {
confirm?: boolean;
}): Promise<{
local_label: string;
removed: true;
}>;
}
//# sourceMappingURL=wallets.d.ts.map