Overview

@ethernauta/transport

The transport layer. Four resolver factories, HTTP and WebSocket transports, multicall batching, contract address binding, and 1193-provider adaptation.

pnpm add @ethernauta/transport

This package defines the shapes that every method binding in the monorepo speaks: Readable<T>, Writable<T>, Signable<T>, Callable<T>. See Concepts → resolver shapes for the philosophy.

The four factories

import type { Provider } from "@ethernauta/eip/1193";
import {
  create_provider,
  create_reader,
  create_writer,
  contract,
  encode_chain_id,
  http,
} from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
import { eip155_8453 } from "@ethernauta/chain/eip155-8453";
import { AddressSchema } from "@ethernauta/core";
import { parse } from "valibot";

const CHAIN_ID_1 = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const CHAIN_ID_8453 = encode_chain_id({ namespace: "eip155", reference: eip155_8453.chainId });

const reader = create_reader([
  { chainId: CHAIN_ID_1, transports: [http("https://ethereum-rpc.publicnode.com")] },
  { chainId: CHAIN_ID_8453, transports: [http("https://base-rpc.publicnode.com")] },
]);
const writer = create_writer([
  { chainId: CHAIN_ID_1, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);
declare const provider: Provider; // see /eips/6963 for discovery
const { signer } = create_provider(provider);
const token = parse(AddressSchema, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
const ctx = contract({ chain_id: CHAIN_ID_1, to: token });

Each factory accepts a Chain[] and returns a function ({ chain_id, ... }) => ResolvedX. Pass the resolved object as the second curried argument to any method.

HTTP transport

import { http } from "@ethernauta/transport";

const transport = http("https://eth.llamarpc.com", {
  retry: { attempts: 3 },
  batch: { window_ms: 50, max_size: 100 },
});

Used internally by create_reader / create_writer / create_contract. Exposed if you want to construct a transport directly without going through the resolver factories.

Options:

  • urls — list of RPC endpoints. The transport rotates on failure.
  • retry: HttpRetryOptions{ attempts: number, backoff: "linear" | "exponential" }.
  • batch: HttpBatchOptions — enable JSON-RPC batch requests within a time window.

WebSocket transport

import { websocket } from "@ethernauta/transport";

const transport = websocket("wss://eth.llamarpc.com/ws");

For subscription-based methods (eth_newHeads, eth_newPendingTransactions). HTTP can’t carry subscriptions; switch to WebSocket when you need long-lived push updates.

Multicall

import {
  create_multicall,
  contract,
  encode_chain_id,
  http,
} from "@ethernauta/transport";
import { balanceOf } from "@ethernauta/erc/20";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
import { AddressSchema } from "@ethernauta/core";
import { parse } from "valibot";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });

const multicall = create_multicall([
  { chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);

const token = parse(AddressSchema, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
const holder = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");

const tokenCtx = contract({ chain_id: CHAIN_ID, to: token });
const [balance] = await multicall([
  balanceOf([holder])(tokenCtx),
]);

Aggregates multiple reads into a single JSON-RPC batch. Backed by the same HTTP transport; the batching is at the JSON-RPC layer ([{ id: 1, ... }, { id: 2, ... }]).

For on-chain Multicall3-style aggregation (where the contract aggregates calls), use the matching ERC binding in @ethernauta/erc.

Contract binding

import {
  contract,
  create_reader,
  encode_chain_id,
  http,
} from "@ethernauta/transport";
import { balanceOf } from "@ethernauta/erc/20";
import { eth_call } from "@ethernauta/eth";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
import { AddressSchema, type Bytes } from "@ethernauta/core";
import { parse } from "valibot";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
  { chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);

const token_address = parse(AddressSchema, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
const holder = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");

const callable = balanceOf([holder])(contract({ chain_id: CHAIN_ID, to: token_address }));
const result_bytes: Bytes = await eth_call([{ to: callable.to, input: callable.data }])(
  reader({ chain_id: CHAIN_ID }),
);
const balance = callable.decode(result_bytes);

contract({ chain_id, to }) builds a ContractContext. ERC method bindings consume it and return a Callable<T> — execute by passing the callable’s .data through eth_call and decoding the response with .decode.

EIP-1193 provider adapter

import { create_provider } from "@ethernauta/transport";
import { eth_blockNumber, eth_sendTransaction } from "@ethernauta/eth";
import type { Provider } from "@ethernauta/eip/1193";
import { AddressSchema, BytesSchema, UintSchema } from "@ethernauta/core";
import { parse } from "valibot";

declare const injected: Provider;
const provider = create_provider(injected);

// reads through the provider
const block = await eth_blockNumber()(provider.reader({ chain_id: "eip155:1" }));

const to = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const value = parse(UintSchema, "0x0");
const input = parse(BytesSchema, "0x");

// signing through the provider
const hash = await eth_sendTransaction([{ to, value, input }])(
  provider.signer({ chain_id: "eip155:1" }),
);

create_provider(provider) adapts any 1193-compliant source into Ethernauta’s { reader, signer } resolver pair. create_injected_transport is also exported for callers that want only the reader-side adapter.

JSON-RPC schemas

The wire schemas for JSON-RPC requests and responses:

SchemaType
MethodSchemathe method name
ParametersSchemathe params array
RequestSchemafull { jsonrpc, id, method, params }
ResponseSchemafull { jsonrpc, id, result } \| { ..., error }
IdSchemarequest ID

Use these if you’re building a custom transport (e.g. an in-memory mock for tests) and need to validate the wire shape.

Types

TypeDescription
Readable<T>(_resolved: ResolvedReader) => Promise<T>
Writable<T>(_resolved: ResolvedWriter) => Promise<T>
Callable<T>composable read against a contract
Http, HttpOptions, HttpRetryOptions, HttpBatchOptionsHTTP transport types
Reader, Writer, Multicallfactory return shapes
Call, ContractContextfor Callable<T> builders
RequestArguments, Request, ResponseJSON-RPC wire types
ProviderRpcErrorEIP-1193 error type

See also