/** * Generic JSON-RPC 2.0 HTTP Client * * Framework-agnostic client using `fetch()` — works in browsers and Node.js 18+. * Includes configurable retry policy and AbortSignal passthrough. */ export interface JsonRpcRequest { jsonrpc: "2.0"; method: string; params: T; id: number | string; } export interface JsonRpcSuccessResponse { jsonrpc: "2.0"; result: T; id: number | string; } export interface JsonRpcErrorResponse { jsonrpc: "2.0"; error: { code: number; message: string; data?: unknown; }; id: number | string; } export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse; /** * Injects bearer tokens into requests for auth-gated methods, and is * notified when the server rejects a bearer so it can invalidate its cache. * * The `JsonRpcClient` is agnostic to which methods are auth-gated — * the provider's `getToken(method)` decides. Returning `null` means * "no auth required for this method"; the client then sends the * request with no `Authorization` header. */ export interface BearerTokenProvider { /** * Return the bearer token to inject for `method`, or `null` if the * method does not require auth. */ getToken(method: string): Promise; /** * Drop the cached token. Next call to `getToken` must re-acquire. * Called by the client on reactive-refresh-trigger responses. */ invalidate(): void; } export interface JsonRpcClientConfig { /** Base URL of the RPC service */ baseUrl: string; /** Timeout in milliseconds per request attempt */ timeout: number; /** Optional custom headers */ headers?: Record; /** Number of retry attempts for transient errors (default: 3) */ retries?: number; /** Initial retry delay in milliseconds (default: 1000) */ retryDelay?: number; /** * Maximum response body size, in bytes, for typed JSON-RPC calls. * `callRaw` intentionally returns the unparsed Response and is not capped here. * Default: 2 MiB. */ maxResponseBytes?: number; /** * Predicate that decides which methods retry on transient errors. * Default retries only `getPeginStatus`, `batchGetPeginStatus`, * `batchGetPegoutStatus`, and `requestDepositorPresignTransactions`. * Write methods are not retried by default. */ retryableFor?: (method: string) => boolean; /** * Per-request bearer-token source. A non-null return attaches * `Authorization: Bearer `; `null` skips auth. `call` * additionally retries once when the server rejects the bearer * (invalidate + refetch + retry) — see {@link isAuthRejectedError}. * `callRaw` skips reactive refresh. */ tokenProvider?: BearerTokenProvider; } export type JsonRpcErrorSource = "wire" | "local"; export declare class JsonRpcError extends Error { code: number; /** "wire" for server-returned envelopes; "local" for SDK-side failures. */ source: JsonRpcErrorSource; /** Structured data from the server `error.data` field, if any. */ data?: unknown | undefined; constructor(code: number, message: string, /** "wire" for server-returned envelopes; "local" for SDK-side failures. */ source?: JsonRpcErrorSource, /** Structured data from the server `error.data` field, if any. */ data?: unknown | undefined); } export declare const JSON_RPC_ERROR_CODES: { readonly TIMEOUT: -32000; readonly NETWORK: -32001; /** VP proxy: request timed out at proxy level */ readonly PROXY_TIMEOUT: -32002; /** VP proxy: VP unreachable / DNS failure / response too large */ readonly PROXY_UNAVAILABLE: -32003; /** SDK client: response missing "result" field (malformed JSON-RPC) */ readonly INVALID_RESPONSE: -32700; /** SDK client: response body exceeded the configured byte limit */ readonly RESPONSE_TOO_LARGE: -32701; }; /** * JSON-RPC error code the vault provider returns for every bearer-token * rejection: expired, not-yet-valid, missing bearer, invalid signature, * invalid claims, invalid structure, subject mismatch, issuer mismatch. * All eight variants collapse onto this one code, distinguished only by * message text — see btc-vault `crates/btc-auth/src/rpc.rs` * (`auth_error_to_rpc_error`). Operationally they all mean the same * thing: this bearer is dead, mint a new one. * * Numerically equal to {@link JSON_RPC_ERROR_CODES.NETWORK}, which this * client throws for local network failures. `source` is what separates * them — see {@link isAuthRejectedError}. */ export declare const AUTH_REJECTED_RPC_CODE = -32001; /** * True when `error` is the vault provider rejecting our bearer token. * * Classified on the error code, which is the only thing the server * guarantees: its auth errors carry `data: null` unconditionally * (`rpc_error` passes `None::<()>`), so any predicate keyed on an * `error.data` field can never match a real response. * * `source === "wire"` is load-bearing: this client reuses -32001 * internally as {@link JSON_RPC_ERROR_CODES.NETWORK}, always with * source "local". * * Known, bounded collision: the vault-provider proxy reuses -32001 for * "Provider not found". A call to a deregistered provider therefore * costs one wasted token-mint round-trip, which fails against the same * registry check and surfaces the same message. */ export declare function isAuthRejectedError(error: unknown): boolean; /** * Generic JSON-RPC 2.0 HTTP client with safe retry policy. */ export declare class JsonRpcClient { private baseUrl; private timeout; private headers; private requestId; private retries; private retryDelay; private maxResponseBytes; private retryableFor; private tokenProvider?; constructor(config: JsonRpcClientConfig); private buildHeaders; /** * Make a JSON-RPC request with optional retry for safe methods. * * If the server rejects the bearer token and a `tokenProvider` is * configured, the client invalidates its cached token and retries the * request once with a freshly-acquired bearer. * * @param method - The RPC method name * @param params - The method parameters * @param signal - Optional AbortSignal for caller-controlled cancellation * @returns The result from the RPC method * @throws JsonRpcError if the RPC call fails */ call(method: string, params: TParams, signal?: AbortSignal): Promise; private callOnce; /** * Make a JSON-RPC request returning the raw Response (unparsed body). * * Bearer tokens are injected identically to `call`. **Reactive refresh * is NOT performed here** — the response body may be unbounded (e.g. * claimer-artifact downloads), so the client refuses to parse it to * detect auth errors. Callers relying on token-expired retries for * large downloads must read the body themselves and re-invoke * `callRaw` after `tokenProvider.invalidate()`. */ callRaw(method: string, params: TParams, signal?: AbortSignal): Promise; private fetchWithRetry; private sleep; getBaseUrl(): string; } //# sourceMappingURL=json-rpc-client.d.ts.map