import { type Chain, type PublicClient, type WebSocketTransport } from "viem"; /** Chain client over the WebSocket transport, so `transport.subscribe` is typed. * {@link makePublicClient} builds no other transport. */ export type SocketClient = PublicClient; /** * The two views of one chain connection: `decorated` for the SDK's own reads, * `raw` for a caller who wants viem's semantics. * * **Details** * * Both ride ONE WebSocket. `decorated` is `{ ...raw, readContract, call }` — a * spread copy — so it shares `raw`'s `transport` and `uid` and issues requests * down the same socket. Handing out `raw` therefore costs no extra connection. * * **Gotchas** * * Which one a caller holds decides what a reverting read throws: `decorated` * gives a typed {@link ContractRevertError}, `raw` gives viem's own error * untouched. Every read INSIDE the SDK must go through `decorated` — see * `createClient`'s `getClient()`, which is the only internal door. */ export interface ChainClients { /** Undecorated viem client — viem's error contract, verbatim. */ readonly raw: SocketClient; /** The SDK's client: `readContract` / `call` rethrow as typed SDK errors. */ readonly decorated: PublicClient; } /** * Build the WebSocket chain connection for a chain + RPC, in both views. * * **Details** * * `readContract` and `call` are wrapped on the `decorated` view so a failing * chain read reaches callers as one of OUR typed errors — a decoded * {@link ContractRevertError} when the contract rejected the call, an * {@link RpcError} when the request never got an answer. This is the read-side * counterpart of the write path's funnel in trade.ts, and doing it at the ONE * place the client is constructed covers every chain read in the SDK (the * concept modules' point reads, oracleHub.ts, system.ts, operatorReads.ts — * ~83 call sites) without a wrap at each one. * * Returns {@link ChainClients} rather than one client because the SDK owes * callers an undecorated escape hatch (`client.getViemClient()`) that does not * open a second socket. `raw` is that client; it is NOT for internal use. * * **Gotchas** * * Only the error's TYPE changes here, never the control flow. Chain reads keep * throwing rather than returning `null` — see CONVENTIONS.md "Return + error * contract". Modules that deliberately swallow a read (the `getErc20Decimals` * fallback, for one) still catch and still fall back: a typed error is caught * by the same `catch`. */ export declare function makePublicClient(chain: Chain, wsRpcUrl: string): ChainClients; /** * Decorates a public client so contract reads reject with SDK errors. * * **When to use** * * Use to give a caller-supplied `config.publicClient` the same typed-error * treatment {@link makePublicClient} builds in. Kept separate for exactly that * case. * * **Details** * * Only the two methods that execute contract calls are touched; everything else * on the client is untouched, so this stays a thin boundary rather than a * re-implementation. * * Returns a NEW client and never mutates the argument. That matters because * `createTrader({ publicClient })` passes an object the CALLER owns and may use * elsewhere (a shared app-wide viem client, say) — decorating it in place would * reach outside this SDK and change how that caller's own reads throw. A viem * client is a plain `Object.prototype` object of data properties, so the spread * copy is faithful; there are no accessors or symbol keys to lose. * * **Gotchas** * * Because it returns a new client, the return value is the decorated one — a * caller that ignores it and keeps using the argument gets raw viem errors. * Applying this twice is harmless: {@link Revert.toSdkError} returns an * already-SDK error untouched, so a re-decorated client does not nest errors. */ export declare function withTypedReadErrors(client: PublicClient): PublicClient; /** The height and time the tail takes from a chain head. */ export interface ChainHead { number: bigint; timestamp: bigint; } /** Height and time from a `newHeads` frame — null unless BOTH arrive as hex * quantities. All-or-nothing: a frame missing either field, or carrying one that * is not hex or is the empty quantity `"0x"`, is rejected whole rather than * half-read, because a head with only one of the two is no use to the tail. * Total: a malformed frame is contained here, never thrown at the subscription * callback. * @internal Exported for tests. */ export declare function headOfFrame(result: unknown): ChainHead | null; /** Starts a `newHeads` stream and returns its release. See {@link subscribeChainHeads}. */ export type ChainHeadSubscriber = (onHead: (head: ChainHead | undefined) => void, onError: () => void) => () => void; /** * Subscribe to `newHeads`, delivering each pushed header already parsed. The * returned function releases the stream. * * **Details** * * The head comes from the pushed frame itself. viem's `watchBlocks` answers a * frame by re-fetching the very block it was handed, which costs a round-trip * per block and returns nothing the frame did not already carry. * * `onHead` receives `undefined` for a frame that could not be read. That is * deliberately still a delivery: an unreadable frame proves the stream is alive, * which is what the caller's stall detection needs to know. * * **Gotchas** * * The socket opens asynchronously, so a release can land before the subscription * exists; it is remembered and applied on arrival. Every continuation is gated on * that release (SDK-OWN-004) — a released stream never calls back, so a frame * already queued cannot revive a stopped caller and a late rejection cannot fire * at whatever replaced it. * * `onError` fires AT MOST ONCE. A failed `eth_subscribe` reaches both the * transport callback and the rejected promise, which is one failure arriving * twice, not two failures; a caller that reconnects or notifies subscribers on * it must not do so twice for the same dead stream. */ export declare function subscribeChainHeads(client: SocketClient, onHead: (head: ChainHead | undefined) => void, onError: () => void): () => void;