RPC Client Cache
closeAllRpcClients() — the only way to evict the process-global RPC client caches (which may hold API keys or basic-auth in their URLs) when the process is done making RPC calls, deliberately separate from Workspace.close().
Every outbound RPC call — reading a balance, simulating, broadcasting, polling a tracker — goes through a cached client. The library keeps one client per distinct endpoint URL, process-wide, and reuses it for the life of the process. closeAllRpcClients() is the one function that empties those caches.
import { closeAllRpcClients } from "wative-core";
// … your process makes RPC calls through the library …
await closeAllRpcClients(); // call once, when the process is done with RPCWhy the cache exists
Opening a fresh JSON-RPC client for every call would be wasteful, so the library memoizes. Two module-global maps hold the cached clients:
- an EVM HTTP JSON-RPC client per endpoint, and
- a Solana (
@solana/web3.js)Connectionper endpoint.
The maps are populated automatically the first time the library talks to a given network. You never create these clients yourself and there is no per-call setup cost after the first — that is the point of the cache.
What it keys on
The cache key is the canonical form of the network's rpcUrl, not the raw string. Canonicalization runs the URL through new URL(...).toString(), which:
- lowercases the scheme and host —
https://RPC.X.com/v1andhttps://rpc.x.com/v1share one client; - elides a default port —
https://x.com:443/pcollapses tohttps://x.com/p; - resolves
..segments and supplies a canonical trailing slash for an empty path; - strips the fragment —
/v1#primaryand/v1#backupare one endpoint.
It does not collapse a doubled slash inside the path, so https://x.com/a//b stays distinct from https://x.com/a/b. Two networks are considered "the same endpoint" exactly when they canonicalize to the same key and therefore share a cached client.
The cache key is the whole canonical URL — including the path, and any userinfo. That path may carry a provider API key, and the userinfo may carry basic-auth credentials (https://user:pass@host/…). The cached entry keeps that credential-bearing URL in memory for as long as the process lives. This is why an explicit eviction exists: closeAllRpcClients() clears both maps so those URLs are dropped.
Errors and logs never echo the raw URL — the library redacts it to scheme://host before surfacing it — but the live cache entry is the unredacted key.
When to call
Call closeAllRpcClients() once, when the process is finished making RPC calls — typically as part of shutdown. It:
- clears the EVM client map and the Solana
Connectionmap, releasing the retained (possibly credential-bearing) endpoint URLs; - returns a
Promise<void>—awaitit so any release completes before exit; - releases every cached client of every kind, EVM and Solana alike.
It is safe to call when the caches are already empty (a fresh process that made no calls) — it simply resolves.
What actually accumulates is one client and one Connection per distinct endpoint, retained for the process lifetime. In this library every Solana call is plain HTTP, so no websocket or timer is left running — the reason to evict is credential retention in the cache key, not a leaked socket or handle.
Why it is not wired into close()
The caches are process-global and shared by every workspace. closeAllRpcClients() is deliberately not called by Workspace.close() (nor by lock()), because a per-workspace teardown that emptied a shared cache would evict clients another workspace in the same process is still using.
So the two responsibilities are kept separate:
| Scope | What tears it down |
|---|---|
| One workspace's in-memory state (keys, unlocked containers) | Workspace.lock() / Workspace.close() |
| The process-global RPC client caches | closeAllRpcClients() |
If your process opens and closes many workspaces over its lifetime, keep letting the RPC cache serve all of them, and call closeAllRpcClients() exactly once at the very end.