import { type MethodContext } from '../daemon/index.js'; import { type JsonRpcUpstream } from './upstream.js'; /** * Local JSON-RPC signing proxy — the endpoint `forge script --unlocked`, * hardhat, cast, or any web3 tool points at. Serves three account methods * with the portal key and transparently proxies everything else to the * configured upstream node: * * eth_accounts / eth_requestAccounts → [portal address] * eth_signTransaction → fill nonce/gas/fees, sign, return raw tx hex * eth_sendTransaction → same, then broadcast via upstream * eth_sendRawTransaction, return the tx hash * everything else → proxied verbatim to the upstream * * Security model — this is a NEW way to reach the portal key, so: * * - Signing goes through the daemon's `dispatch()` — the byte-identical * path the MCP tools use. Policy evaluation, the out-of-band confirm * gate, and the hash-chained audit log apply to every request from this * surface; there is no proxy-only signing branch that could drift. * - The listener binds 127.0.0.1 only, and every request must carry the * shared token from config (`Authorization: Bearer `, or the * password half of HTTP Basic — i.e. an rpc URL of the form * http://sigil:@127.0.0.1:8547 works out of the box with forge). * Tokens are compared in constant time. Without the token, any local * process could ask a permissive-mode portal to sign a drain. * - The Host header must be a loopback name. A DNS-rebinding page makes * the victim browser send its own hostname there; rejecting non-loopback * Hosts kills that class even if the token somehow leaked into a URL. * - Message-signing methods (eth_sign, personal_sign, eth_signTypedData*) * are rejected, not proxied — they'd silently fail upstream, and sigil's * MCP tools already cover them with their own policy toggles. * - Fail-closed inheritance: locked table, policy deny, confirm deny or * timeout, missing confirm transport — every one surfaces as a JSON-RPC * error to the client and never yields a signature. * * No TLS: loopback-only, same rationale as the ack server. */ export interface RpcServerConfig { portal: string; upstream: string; token: string; port?: number; } export interface StartRpcServerOpts { config: RpcServerConfig; /** The SAME MethodContext the MCP loop uses — shared handles/audit/policy/confirm. */ ctx: MethodContext; /** Injectable upstream for tests. Default: HttpUpstream(config.upstream). */ upstream?: JsonRpcUpstream; /** Port override for tests (0 = kernel-assigned). Default: config.port ?? 8547. */ port?: number; onLog?: (event: Record) => void; } export interface RpcProxyServer { /** e.g. "http://127.0.0.1:8547" — no credentials embedded. */ readonly url: string; readonly port: number; close(): Promise; } export declare const DEFAULT_RPC_PORT = 8547; export declare function startRpcServer(opts: StartRpcServerOpts): Promise; //# sourceMappingURL=server.d.ts.map