npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

universal-blockchain-sdk

v1.0.0

Published

Universal multi-chain blockchain SDK for JavaScript and TypeScript dApps. One API for wallets, RPC data, balances, transactions, and explicit mainnet, testnet, and devnet profiles across 11 blockchain ecosystems.

Readme

Universal Blockchain SDK

Universal Blockchain SDK is an open-source multi-chain blockchain SDK for JavaScript and TypeScript dApps. It gives applications one consistent API for wallet connections, blockchain RPC communication, account information, balances, transactions, and live data across 11 blockchain ecosystems.

Build multi-chain dApps without implementing a different integration for every network. The package includes a unified adapter API, browser wallet extension support, normalized blockchain data, provider health and failover controls, asset workflows, and a fully functional live blockchain explorer sample.

One API. 11 blockchains. Build multi-chain dApps.

It currently supports:

  • Ethereum, BNB Smart Chain, Avalanche C-Chain, and HyperEVM through ethers
  • Bitcoin through compatible Bitcoin HTTP APIs and bitcoinjs-lib
  • Solana through @solana/web3.js
  • XRP Ledger, TRON, Cardano, and Sui through their current public APIs
  • Hyperliquid native L1 through the official native /info API

The library does not recreate blockchain protocols. It provides a small application-facing API and delegates chain-specific work to established ecosystem libraries.

Every canonical chain has a mainnet profile and an explicit testnet or devnet profile. Mainnet remains the default for backwards compatibility; development networks are always an intentional opt-in.

Network profiles and safety

Mainnet remains the default for every chain. To select a non-mainnet network, set an explicit per-chain network profile; the SDK never silently falls back between mainnet, testnet, or devnet providers.

import { createUniversalChainConnection } from "universal-blockchain-sdk";

const connection = createUniversalChainConnection({
  ethereum: { network: "sepolia" },
  solana: { network: "devnet" },
  bitcoin: { network: "testnet4" }
});

const sepolia = await connection.connect("ethereum");
console.log(sepolia.getNetwork());
// { chain: "ethereum", name: "Ethereum Sepolia", network: "testnet", chainId: 11155111 }

Every profile starts with a free, no-key public endpoint. Configure your own endpoint by combining it with the explicit profile, for example { network: "sepolia", rpcUrl: "https://your-provider.example" }; an EVM provider is rejected if its returned chain ID differs from the selected profile. Public endpoints are for development and may be rate limited or unavailable. Testnet/devnet balances have no real-world USD value. Faucet links are documentation resources, not SDK functionality.

| Chain | Explicit development profile | Public default | | --- | --- | --- | | Ethereum | sepolia | PublicNode Sepolia RPC | | Bitcoin | testnet4 | mempool.space Testnet4 API | | Solana | devnet | Solana Devnet RPC | | BNB Smart Chain | testnet | PublicNode BSC Testnet RPC | | Avalanche | fuji | Avalanche Fuji RPC | | XRP Ledger | testnet | Ripple Testnet server | | TRON | nile | TRON Nile | | Cardano | preprod | Koios Preprod | | Sui | testnet | Sui Testnet GraphQL | | Native Hyperliquid L1 | testnet | Hyperliquid Testnet /info | | HyperEVM | testnet | HyperEVM Testnet RPC |

Sui profiles intentionally use GraphQL. The legacy public JSON-RPC endpoint is deprecated and is not exposed as a supported Sui profile.

The library reads real network data. It does not create balances, blocks, or transactions locally. Public endpoints are provided for development and experimentation; production applications should configure their own RPC/API services.

Supported mainnet connections

Use these canonical names with Blockchain.connect(name). bsc, avax, and hyperevm are accepted aliases; evm remains an alias for Ethereum.

| Connection name | Network | Native balance unit | Transport | | --- | --- | --- | --- | | ethereum | Ethereum Mainnet | wei | EVM JSON-RPC | | bitcoin | Bitcoin Mainnet | satoshis | Bitcoin HTTP API | | solana | Solana Mainnet | lamports | Solana JSON-RPC | | bnb | BNB Smart Chain Mainnet | wei | EVM JSON-RPC | | avalanche | Avalanche C-Chain Mainnet | wei | EVM JSON-RPC | | xrp | XRP Ledger Mainnet | drops | XRP Ledger JSON-RPC | | tron | TRON Mainnet | sun | Full-node HTTP API | | cardano | Cardano Mainnet | lovelace | Koios-compatible REST API | | sui | Sui Mainnet | MIST | Sui GraphQL | | hyperliquid | Hyperliquid native L1 | HYPE smallest unit | Hyperliquid /info | | hyperliquid-evm | HyperEVM Mainnet | wei | EVM JSON-RPC |

hyperliquid and hyperliquid-evm are deliberately different adapters. The first reads HyperCore's native L1 data; the second is the EVM-compatible HyperEVM chain (chain ID 999).

Use getNetworkProfiles(chain) or connection.getNetworkProfiles(chain) to discover the supported names without hard-coding them.

Compatibility matrix

Use the read-only compatibility API when an application needs a formal, profile-aware support decision. It is composed from the SDK's network profile, adapter capability, and wallet protocol registries, so it does not expose provider endpoints or reproduce provider configuration.

import {
  getCompatibility,
  getCompatibleProfiles,
  supportsCompatibility
} from "universal-blockchain-sdk";

const solanaDevnet = getCompatibility("solana", "devnet");
console.log(solanaDevnet.profile);
// { id: "devnet", network: "devnet", transport: "json-rpc", ... }
console.log(solanaDevnet.wallet.standard); // "wallet-standard"

const profiles = getCompatibleProfiles("ethereum");
const supportsContracts = supportsCompatibility(
  "ethereum",
  "contractCall",
  "sepolia"
);

COMPATIBILITY_MATRIX and getCompatibilityMatrix() return the same frozen source-of-truth data. A row describes static adapter/profile support. The wallet.runtimeCapabilities value is always wallet-dependent: features such as signing, switching, and asset transfers must be confirmed from the installed wallet descriptor or connected session at runtime.

| Ecosystem | Profiles | Wallet protocol | | --- | --- | --- | | Ethereum | mainnet, sepolia | EIP-1193 | | Bitcoin | mainnet, testnet4 | Bitcoin wallet | | Solana | mainnet, devnet | Wallet Standard | | BNB Smart Chain | mainnet, testnet | EIP-1193 | | Avalanche C-Chain | mainnet, fuji | EIP-1193 | | XRP Ledger | mainnet, testnet | XRP wallet bridge | | TRON | mainnet, nile | TronLink | | Cardano | mainnet, preprod | CIP-30 | | Sui | mainnet, testnet | Wallet Standard | | Native Hyperliquid L1 | mainnet, testnet | EIP-1193 identity | | HyperEVM | mainnet, testnet | EIP-1193 |

The matrix's caveats field keeps native Hyperliquid's documented latest-height limitation and development-network valuation caveat separate from the capability and runtime-wallet fields.

Requirements

  • Node.js 18 or newer
  • An ES module-compatible JavaScript environment
  • No credentials are required when using the public development endpoints
  • Production applications should supply their own provider URLs

Security and release posture

The SDK is non-custodial: it does not create or retain private keys, seed phrases, wallet credentials, or raw provider objects in browser-facing API responses. Wallet use requires a browser user gesture, and execution requires an explicit wallet approval.

Read the current security audit before production adoption. It covers the SDK, the sample server, dependency findings, threat boundaries, remediation status, and a self-audit checklist. It is an engineering review—not a guarantee that software is invulnerable. Production operators remain responsible for their own RPC credentials, deployment hardening, monitoring, rate limiting, and chain-specific transaction review.

Installation

Install from npm

npm install universal-blockchain-sdk

Install from source

git clone https://github.com/MichaelHDesigns/universal-blockchain-sdk.git
cd universal-blockchain-sdk
npm install

The package is an ES module, so use import syntax:

import { Blockchain } from "universal-blockchain-sdk";

Server framework examples and provider metrics

Runnable server examples live in examples/. Each one uses an explicit profile, exposes only same-origin API responses to a browser, and reads an optional provider URL from a server environment variable. They do not contain keys, browser wallet code, or production deployment claims.

cd examples/express
npm install
npm start

The Next.js example has its own npm install and npm run dev commands under examples/next. The dependency-free examples/node-http pattern transfers to other Node frameworks such as Fastify, Hono, and Koa.

Provider metrics are an opt-in subpath with no Prometheus, framework, or telemetry dependency:

import { createClient } from "universal-blockchain-sdk";
import {
  createProviderMetrics
} from "universal-blockchain-sdk/metrics";

const client = createClient({
  connectionOptions: {
    ethereum: { network: "sepolia" }
  }
});
const metrics = createProviderMetrics({ maxSeries: 250 });
const stopMetrics = await metrics.attachClient(client, {
  chains: ["ethereum"]
});

await client.getHeight("ethereum");
console.log(metrics.snapshot());
console.log(metrics.toPrometheus());

stopMetrics();
metrics.dispose();

The stable snapshot contains request outcome series, latency buckets, opaque active-provider labels, failover counts, and droppedSeries. Labels are chain, operation, opaque provider, and outcome; failovers also include opaque from and to labels. Provider values are deterministic opaque IDs, not URLs, so credentials and endpoint paths cannot enter metrics. maxSeries bounds in-memory cardinality; once reached, unseen label combinations are counted in droppedSeries rather than retained. Attach before performing requests, and retain the cleanup function or call dispose() during server shutdown.

Live explorer sample app

This repository ships a responsive, read-only explorer example under sample-app/. It consumes the reusable explorer reads from the library and presents live mainnet metadata, provider health, balances, recent blocks, and normalized transactions through a same-origin browser interface.

npm install
npm run sample

Open http://localhost:5000 to choose from all 11 canonical mainnet connections. The explorer can optionally connect a locally installed wallet to read the balance of its public address. It never asks for a private key, seed phrase, signature, or transaction. Native Hyperliquid L1 remains separate from HyperEVM. Native Hyperliquid’s current-height and latest-block limitations are presented as unavailable rather than invented values.

Click through live details

Every rendered address, transaction hash, block hash, and block height is clickable. The resulting shareable URL requests one of three same-origin detail reads:

  • address: uses the library's getExplorerDetail(chain, "address", address) read, which calls the connected adapter's getBalance(address) method and displays the exact native-unit and formatted balance. Address transaction history is shown as unavailable because it is not part of the universal API.
  • transaction: uses getExplorerDetail(chain, "transaction", hash), which calls getTransaction(hash), shows the normalized transaction fields, and exposes its chain-specific native response in a collapsible panel when present.
  • block: uses getExplorerDetail(chain, "block", block), which calls getBlock(block) and shows normalized block metadata plus up to 50 clickable transaction IDs returned by that block.

The browser only calls the sample app's /api/* routes. Those routes add browser-safe serialization and provider redaction around the library result, so provider URLs remain server-side. Browser wallet modules receive only wallet-facing network identifiers, never the provider registry. The sample does not contain a second explorer-domain implementation.

The included sample server sends a restrictive Content Security Policy and other browser security headers, accepts only GET requests, limits request URL length, and applies a per-IP in-memory API rate limit (120 requests per minute). Wallet portfolio rendering is capped at 100 discovered assets per request; omitted assets are reported as a partial result. For a public deployment, place it behind HTTPS, an edge rate limiter or WAF, and provider accounts with quotas appropriate for expected traffic.

The running explorer does not render mock balances, blocks, or transactions. Its public read-only addresses are real mainnet addresses and every runtime value is fetched from the active provider. The deterministic test suite does use adapter fixtures only to verify route shaping without requiring a network connection.

For a network-free check of the sample’s route shaping and UI data contract:

npm run test:sample

Browser wallet connections

Browser wallet connectivity is a separate, browser-only package entry. The default import remains safe for Node.js read services and does not access window, extensions, or browser APIs.

import {
  createWalletManager
} from "universal-blockchain-sdk/wallet";

const wallets = createWalletManager();
const options = await wallets.discover(
  "ethereum",
  { network: "sepolia" }
);

// Call only from an explicit user action.
const session = await wallets.connect({
  chain: "ethereum",
  walletId: options[0].id,
  network: "sepolia"
});

console.log(session.address);
console.log(session.isExpectedNetwork);

The wallet manager asks an installed wallet for public accounts only after a direct user gesture. It does not create wallets, store credentials, custody assets, initiate a signature, or broadcast a transaction automatically. signMessage() and sendTransaction() are available only after a session is connected and only when the selected wallet advertises that capability.

Wallet discovery, active sessions, and reconnect hints are keyed by both chain and network profile. Pass the same explicit network to discover(), connect(), restore(), getSession(), and disconnect() when using a non-mainnet profile. Persisted reconnect hints contain only public account and profile metadata; they never contain provider objects or credentials.

EVM sessions verify the active chain ID before execution. Other ecosystems may execute only when their connector supplies a trustworthy profile verification signal (for example, Wallet Standard account-chain filtering); otherwise preparation and execution fail closed with WALLET_NETWORK_UNVERIFIED.

Application workflows

createClient() layers an application-focused context over configured chain profiles. It coordinates read APIs, explorer reads, optional browser wallet sessions, validated native amounts, request observability, provider policies, and normalized polling subscriptions. The default package entry is still Node-safe: browser wallet discovery is dynamically loaded only after an application explicitly requests it.

import {
  createClient
} from "universal-blockchain-sdk";

const client = createClient({
  connectionOptions: {
    ethereum: {
      rpcUrls: [
        "https://your-primary-rpc.example",
        "https://your-backup-rpc.example"
      ]
    }
  },
  policies: {
    ethereum: {
      timeoutMs: 10_000,
      retries: 1,
      retryDelayMs: 300,
      health: {
        failureThreshold: 3,
        cooldownMs: 5_000
      },
      rateLimit: {
        requestsPerSecond: 5
      }
    }
  },
  onRequest(event) {
    // Emit to your observability system. Never log secrets or raw provider URLs.
    console.info(event.phase, event.chain, event.operation);
  }
});

const address = client.normalizeAddress(
  "ethereum",
  "0x0000000000000000000000000000000000000000"
);
const balance = await client.getBalance("ethereum", address);
const url = client.explorerUrl("ethereum", "address", address);

Native transfer preparation

Native transfers use an explicit two-step boundary. prepareNativeTransfer() performs local validation plus supported public reads (balance, fee data, and an optional simulation); it does not ask a wallet to approve anything. Only the returned object's explicit execute() method asks the connected wallet to submit a transaction.

// Call connectWallet only from an actual click or other user gesture.
const wallets = await client.discoverWallets("ethereum");
await client.connectWallet({
  chain: "ethereum",
  walletId: wallets[0].id
});

const prepared = await client.prepareNativeTransfer({
  chain: "ethereum",
  to: "0x0000000000000000000000000000000000000001",
  amount: "0.01",
  simulate: true
});

console.log(prepared.fee, prepared.warnings);

if (!prepared.ready) {
  throw new Error("Resolve the transfer warnings before continuing.");
}

// This is the single explicit wallet-approval boundary.
const operation = await prepared.execute();
const result = await operation.wait({
  requiredConfirmations: 1
});
console.log(result.status); // finalized, failed, or a timeout result

For EVM execution, the client refreshes and verifies the wallet's active network and account while preparing the intent and again immediately before submission. A network or account change, an explicit sender that does not match the wallet, or an unrefreshable wallet state blocks submission. Preparation estimates gas for the exact value-bearing transfer and simulates that same request when simulation is enabled; it does not assume a fixed 21,000-gas limit for contract recipients.

Native transfer preparation creates chain-scoped values for the supported execution paths and an inspectable intent for other native networks. A transfer is executable only when the connected wallet advertises a documented native transaction capability and the application can validate the sender and fee model. Solana uses a real SystemProgram.transfer transaction (serialized for Wallet Standard) and its network fee query; XRP uses a native Payment with the open-ledger fee. Bitcoin, TRON, Cardano, Sui, and native Hyperliquid remain explicitly blocked until each has a dedicated adapter that can create its signed-transaction format and obtain a safe account/fee preflight; the library never guesses a wallet RPC method or forwards an intent object as a transaction.

Token and asset transfer preparation

prepareAssetTransfer() uses the same explicit approval boundary for non-native assets. Amounts are exact base units, not floating-point display values. It validates both addresses, checks the asset balance when the chain can report an exact value, prepares the chain-native unsigned payload, and performs the available fee and simulation preflights before any wallet prompt.

const transfer = await client.prepareAssetTransfer({
  chain: "ethereum",
  asset: "0x0000000000000000000000000000000000000002",
  to: "0x0000000000000000000000000000000000000001",
  amount: 1_500_000n, // exact token base units
  simulate: true
});

if (transfer.ready) {
  await transfer.execute(); // one explicit wallet approval, never retried
}

EVM paths create an ERC-20 transfer(address,uint256) contract call and recheck the active EVM wallet network and account immediately before submission. Solana paths create a real SPL Token TransferChecked transaction and deliberately require both parties to already have an account for the mint—this API never creates an associated token account implicitly. XRP issued-currency paths create a native Payment only when the installed wallet advertises raw XRP transaction submission. A wallet descriptor exposes capabilities.assetTransfer for these non-EVM paths only when it can submit that documented payload; prepareAssetTransfer() remains blocked otherwise. Cardano native assets, Sui coins, TRON token families, Bitcoin protocol assets, native Hyperliquid spot actions, and any asset family without a documented unsigned wallet payload remain explicitly blocked; no intent object is silently forwarded to a wallet.

Token-2022 safeguards

Compatible Solana wallets can submit a Token-2022 transfer when the mint and both existing token accounts are owned by the Token-2022 program. The adapter reads parsed mint metadata before returning a ready approval, validates the mint decimals, exact positive u64 base-unit amount, sender and recipient ownership, matching mint, initialized account state, and the active wallet account. The unsigned instruction uses the Token-2022 program with the documented source, mint, destination, and signing owner accounts. A mint with TransferFeeConfig uses TransferCheckedWithFee and calculates the current-epoch fee before the normal network-fee preflight.

The supported mint extensions are transfer-fee, interest-bearing, metadata, group, mint-close-authority, permanent-delegate, initialized default-account-state, and verified transfer hooks. For a transfer hook, the adapter reads the hook program from parsed mint metadata, derives the documented extra-account-metas PDA, requires that PDA to be owned by the hook program, and resolves its exact binary account-meta list before approval. It rejects malformed, unavailable, unstable, or extra-signer hook metadata rather than giving a wallet an unverified account list. The resulting TransferChecked instruction contains the resolved entries followed by the verified validation-state PDA and then the hook program in documented order.

Non-transferable, confidential-transfer, pausable, memo-required, frozen-state, and unknown extensions are returned as clear blockers rather than receiving a generic wallet payload. Asset discovery labels Solana assets with their observed tokenProgram and parsed account extension names; callers may also set tokenProgram: "token-2022" on a known asset descriptor to select the dedicated builder directly.

The library never accepts private keys, seed phrases, or automatic signing/broadcasting through this application API. execute() is always the single direct approval boundary, is never retried, and refreshes the active wallet account when the provider exposes a safe account read.

Events and framework bindings

Use polling subscriptions for blocks/heights, balances, provider health, and transaction records. Wallet account and network changes are forwarded from the wallet manager. Every call returns a cleanup function.

const stop = client.subscribe({
  event: "balance",
  chain: "bitcoin",
  address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
  intervalMs: 15_000
}, update => {
  console.log(update.value);
});

// Stop polling when the view or request is disposed.
stop();

Optional bindings are separate subpath imports and do not add React, Vue, or Svelte to the core package:

import { createReactBindings } from "universal-blockchain-sdk/react";
import React from "react";

const chain = createReactBindings(client, React).useChain("ethereum");

universal-blockchain-sdk/vue and universal-blockchain-sdk/svelte offer equivalent composition/store factories. Pass your application's framework runtime to the Vue/React factory; the package intentionally does not bundle a framework.

Deterministic workflow fixtures

Use the testkit to exercise reads, provider failures, balances, and transaction state transitions without touching a public provider or broadcasting a transaction:

import {
  createTestAdapter,
  createTestClient,
  createTransactionFixture
} from "universal-blockchain-sdk/testkit";

const fixture = createTestAdapter({
  chain: "ethereum",
  balances: {
    "0x0000000000000000000000000000000000000001": "1000000000000000000"
  }
});
fixture.setTransaction(
  "0x" + "a".repeat(64),
  createTransactionFixture({ status: "confirmed" })
);
const { client } = createTestClient({ fixture });

The live explorer sample's server also uses this application client for its read-only explorer routes. It intentionally does not expose a transaction preparation or broadcast endpoint; wallet approval stays in a browser user-gesture flow.

Wallet discovery and standards

| Connection | Browser support path | Notes | | --- | --- | --- | | Ethereum, BNB, Avalanche, HyperEVM | EIP-6963 discovery and EIP-1193 | Supports multiple installed EVM wallets, account events, and requested network switching. | | Native Hyperliquid L1 | EIP-6963/EIP-1193 account identity | Uses the wallet's EVM address as the native Hyperliquid account identity. It is not treated as HyperEVM signing. | | Solana and Sui | Wallet Standard | Uses registered Wallet Standard wallets first; Solana injected wallet APIs are a compatibility fallback. | | Cardano | CIP-30 | Converts the wallet's returned CBOR-hex payment address to a mainnet Bech32 address for explorer reads. | | Bitcoin | UniSat, OKX Wallet, and Leather injected APIs | Uses the public address returned by the wallet; applications can add Xverse through its official Sats Connect connector. | | TRON | TronLink provider | Uses TronLink's tron_requestAccounts authorization path and the wallet's TRON address. | | XRP Ledger | GemWallet bridge | Xaman is supported through an application-provided official SDK connector because it requires an app-specific browser configuration. |

Some wallet ecosystems intentionally use an app SDK or mobile/deep-link flow instead of a global browser provider. Register the official client as a connector without leaking application secrets:

const wallets = createWalletManager({
  connectors: [{
    id: "xaman",
    chain: "xrp",
    name: "Xaman",
    standard: "xaman-sdk",
    async connect() {
      const address = await xaman.user.account;
      return { address };
    }
  }]
});

Do not place SDK secrets, seed phrases, private keys, raw provider objects, or transaction payloads into server requests, URL parameters, logs, or persistent browser storage. A connected public address may be handed to getExplorerSnapshot() or the sample explorer's same-origin read endpoint to load its live balance.

Token and asset reads

The application client exposes a capability-driven asset layer. It keeps known asset reads separate from asset discovery and indexed history:

const client = Blockchain.createApplicationClient();

const usdc = await client.assets("ethereum").getBalance({
  owner: "0x...",
  asset: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
});

const solanaAssets = await client.assets("solana").discover({
  owner: "..."
});

Known EVM assets use contract reads; Solana uses SPL token accounts; XRP uses issued-currency trust lines; Cardano uses native multi-assets; Sui uses coin balances; TRON exposes configured native account assets; and native Hyperliquid uses spot balances. The result always identifies its source (rpc, contract, protocol, or provider) and retains the native chain response for chain-specific fields.

getMetadata() includes metadataSource and metadataConfidence. Contract metadata is intentionally unverified; protocol metadata is marked verified only when it comes from the chain's native protocol response; an optional provider remains unverified and is identified as provider. Metadata, remote URLs, collection claims, and logos are never inferred.

discover() returns a partial result when a base RPC can only prove the native balance, returns a page marker, or cannot complete the chain-specific portfolio read. Complete EVM portfolios, token/NFT metadata, transfer history, spam filtering, and Bitcoin protocol assets need an indexed provider. Supply one deliberately rather than treating a provider extension as a universal RPC:

const client = Blockchain.createApplicationClient({
  assets: {
    providers: {
      ethereum: {
        async discover({ owner }) {
          return {
            status: "available",
            assets: [],
            warnings: [`Portfolio provider queried for ${owner}`]
          };
        }
      }
    }
  }
});

Asset reads follow the same provider policies, timeouts, retries, rate limits, circuit handling, and request events as the rest of the application client. The asset layer is read-only: it never creates wallets, stores keys, signs, or broadcasts transfers.

Use client.assets(chain).getCapabilityExplanation("discovery") to identify whether the configured application can read a capability directly or needs an optional provider. The live sample labels asset capabilities as DIRECT or PROVIDER for the same reason.

Quick start

Each call to Blockchain.connect() returns a connected chain adapter. The adapter uses the configured provider selection and failover behavior automatically.

import { Blockchain } from "universal-blockchain-sdk";

const ethereum = await Blockchain.connect("ethereum");
const bitcoin = await Blockchain.connect("bitcoin");
const solana = await Blockchain.connect("solana");
const bnb = await Blockchain.connect("bnb");
const xrp = await Blockchain.connect("xrp");

const ethereumBalance =
  await ethereum.getBalance(
    "0x0000000000000000000000000000000000000000"
  );

const bitcoinBalance =
  await bitcoin.getBalance(
    "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
  );

const solanaBalance =
  await solana.getBalance(
    "11111111111111111111111111111111"
  );

console.log("Ethereum wei:", ethereumBalance.toString());
console.log("Bitcoin satoshis:", bitcoinBalance.toString());
console.log("Solana lamports:", solanaBalance.toString());

The addresses above are public example addresses. In a real application, replace them with addresses supplied by your user or application.

Universal API

The following methods are available on every connected adapter:

| Method | Description | Return value | | --- | --- | --- | | getChain() | Returns the normalized chain identifier | One of the names in the supported-connections table | | getNetwork() | Returns normalized network metadata | An object containing chain, name, network, and chainId | | getNetworkType() | Returns the network type | "mainnet" | | getHeight() | Returns the current chain height where the protocol exposes one | A JavaScript number, or an explicit unsupported error for native Hyperliquid | | getBalance(address) | Reads the native balance for an address | A bigint | | getBlock(block) | Reads a block | Provider-specific block data | | getTransaction(hash) | Reads and normalizes a transaction | A normalized transaction object or null | | getProvider() | Returns the active provider endpoint | A URL string or null before connection | | getProviders() | Returns the configured provider endpoints | An array of URL strings | | getCapabilities() | Describes supported operations | Capability flags and native method names | | getProviderHealth() | Shows provider successes, failures, latency, and active endpoint | A health snapshot |

UniversalChainConnection and the static Blockchain API also expose getCapabilities(chain), supports(chain, capability), getProviderHealth(chain), getMultiChainSnapshot(), batchGetBalances(), batchGetBlocks(), and batchGetTransactions().

Public API reference

The following reference covers the public methods shipped by the package. The same methods are available in the TypeScript declarations in src/index.d.ts and src/wallet/index.d.ts.

Connection managers and static access

UniversalChainConnection is an isolated connection manager. Blockchain extends it and also exposes static convenience methods backed by one shared manager. Static methods accept the chain name as their first argument; instance methods on a connection manager use the same return values.

| Method | Description | Return value | | --- | --- | --- | | new UniversalChainConnection(options?) | Creates an isolated manager with optional per-chain adapter configuration | A UniversalChainConnection | | getSupportedChains() | Lists the canonical supported chain names | ChainName[] | | getAdapter(chain, options?) | Resolves and connects the chain-specific adapter | Promise<ChainAdapter> | | connect(chain, options?) | Alias for getting a connected chain adapter | Promise<ChainAdapter> | | getChain(chain) | Normalizes a chain name or alias | Promise<ChainName> | | getNetwork(chain) | Reads normalized network metadata | Promise<NetworkMetadata> | | getNetworkType(chain) | Returns the supported network type | Promise<"mainnet"> | | getCapabilities(chain) | Reads universal and chain-specific capability flags | Promise<ChainCapabilities> | | supports(chain, capability) | Tests one capability before using it | Promise<boolean> | | getHeight(chain) | Reads the latest block/ledger/slot/checkpoint height | Promise<number>; native Hyperliquid explicitly rejects this unsupported operation | | getBlock(chain, block?) | Reads a latest or requested block-like record | Promise<NormalizedBlock \| unknown \| null> | | getTransaction(chain, hash) | Reads a transaction and normalizes common fields | Promise<NormalizedTransaction \| null> | | getBalance(chain, address) | Reads the native balance in the chain's smallest unit | Promise<bigint> | | getProvider(chain) | Returns the active provider after connection | Promise<string \| null> | | getProviders(chain) | Returns configured, normalized provider endpoints | Promise<string[]> | | getProviderHealth(chain) | Reads provider status, counts, latency, and recent errors | Promise<ProviderHealthSnapshot> | | getExplorerDetail(chain, kind, value, options?) | Validates and reads an address, block, or transaction detail | Promise<ExplorerDetailResult \| null> | | getExplorerSnapshot(chain, options?) | Assembles a live explorer summary | Promise<ExplorerSnapshot> | | getMultiChainSnapshot(options?) | Reads bounded per-chain explorer snapshots with optional public addresses | Promise<MultiChainSnapshotResult> | | batchGetBalances(requests, options?) | Reads multiple native balances with bounded concurrency | Promise<BalanceBatchResult[]> in input order | | batchGetBlocks(requests, options?) | Reads multiple blocks with per-item success/failure results | Promise<BlockBatchResult[]> in input order | | batchGetTransactions(requests, options?) | Reads multiple transactions with per-item success/failure results | Promise<TransactionBatchResult[]> in input order | | createApplicationClient(options?) | Creates an application client from a manager | ApplicationClient |

Blockchain provides static versions of connect, getChain, getNetwork, getNetworkType, getCapabilities, supports, getHeight, getBlock, getTransaction, getBalance, getProvider, getProviders, getProviderHealth, getExplorerDetail, getExplorerSnapshot, getMultiChainSnapshot, batchGetBalances, batchGetBlocks, batchGetTransactions, and createApplicationClient.

The factory functions provide equivalent composition without using the static manager:

| Function | Description | Return value | | --- | --- | --- | | createUniversalChainConnection(options?) | Creates an isolated connection manager | UniversalChainConnection | | createApplicationClient(connection, options?) | Adds application workflows to an existing manager | ApplicationClient | | createClient(options?) | Creates a manager and application client together | ApplicationClient |

Application client methods

ApplicationClient adds validation, wallet coordination, asset workflows, provider policies, lifecycle tracking, and subscriptions around the universal reads.

| Method | Description | Return value | | --- | --- | --- | | on(event, listener) | Registers an application, wallet, request, or transaction listener | An unsubscribe () => void function | | chain(chain) | Creates a chain-scoped facade | ChainClient | | assets(chain) | Creates a chain-scoped asset facade | Asset chain client with asset read methods | | getWalletManager() | Loads or returns the browser wallet manager | Promise<WalletManager>; rejects outside a browser unless a window is supplied | | discoverWallets(chain) | Finds compatible installed or registered wallets | Promise<WalletDescriptor[]> | | connectWallet({ chain, walletId }) | Requests a wallet connection after a user gesture | Promise<WalletSession> | | getWalletSession(chain) | Gets the active session for a chain | WalletSession \| null | | disconnectWallet(chain) | Ends the active wallet session | void | | setProviderPolicy(chain, policy) | Applies timeout, retry, rate-limit, and health settings | The normalized ProviderPolicy | | getProviderPolicy(chain) | Reads the effective provider policy | The normalized ProviderPolicy | | chainInfo(chain) | Collects network, units, capabilities, providers, and health | Promise<ChainInfo> | | validateAddress(chain, address) | Validates and normalizes a chain address | A normalized address string; throws on invalid input | | normalizeAddress(chain, address) | Normalizes a valid chain address | A normalized address string | | formatAmount(chain, value) | Formats smallest native units for display | A decimal string | | parseAmount(chain, value) | Parses a decimal display amount exactly | A bigint | | explorerUrl(chain, kind, value) | Builds the preferred public explorer URL | string \| null | | explorerUrls(chain, kind, value) | Builds all configured explorer URLs | string[] | | getHeight(chain) | Application-managed height read with policy controls | Promise<number> | | getBalance(chain, address) | Application-managed native balance read | Promise<bigint> | | getAssetBalance(chain, request) | Reads one normalized asset balance | Promise<AssetBalance> | | getAssetMetadata(chain, request) | Reads normalized asset metadata and provenance | Promise<AssetMetadataResult> | | discoverAssets(chain, request) | Discovers assets for an owner when supported | Promise<AssetDiscoveryResult> | | getAssetHistory(chain, request) | Reads indexed asset history when configured | Promise<AssetHistoryResult> | | getBlock(chain, block?) | Application-managed block read | Promise<NormalizedBlock \| unknown \| null> | | getTransaction(chain, hash) | Application-managed normalized transaction read | Promise<NormalizedTransaction \| null> | | getExplorerSnapshot(chain, options?) | Reads a policy-aware explorer summary | Promise<ExplorerSnapshot> | | getMultiChainSnapshot(options?) | Reads policy-aware per-chain snapshots with bounded concurrency | Promise<MultiChainSnapshotResult> | | getExplorerDetail(chain, kind, value, options?) | Reads a validated explorer detail record | Promise<ExplorerDetailResult \| null> | | estimateNativeTransfer(request) | Runs native-transfer fee and balance preflight without wallet approval | Promise<NativeTransferFee> | | prepareNativeTransfer(request) | Validates and prepares an unsigned native transfer | Promise<PreparedNativeTransfer> | | prepareAssetTransfer(request) | Validates and prepares an unsigned token/asset transfer | Promise<PreparedAssetTransfer> | | waitForTransaction(chain, hash, options?) | Polls a submitted transaction until finality, failure, or timeout | Promise<TransactionLifecycle> | | subscribe(options, listener) | Polls reads or listens to wallet/application events | An unsubscribe () => void function | | dispose() | Stops subscriptions and wallet event forwarding | void |

ChainClient is returned by client.chain(chain). Its methods are the chain-scoped versions of getNetwork, getCapabilities, getProviderHealth, getHeight, getBalance, getBlock, getTransaction, getExplorerSnapshot, getExplorerDetail, validateAddress, formatAmount, parseAmount, explorerUrl, assets, prepareNativeTransfer, prepareAssetTransfer, and subscribe. Each omits the repeated chain argument and returns the corresponding value listed above.

Cross-chain snapshots

Use getMultiChainSnapshot() when a dashboard needs a small, bounded view of several chains. It preserves the requested chain order, retains native units and network metadata inside each ExplorerSnapshot, and returns failures for one chain without discarding successful reads from the others. Supply only public addresses; this API does not discover wallets, value assets in USD, index token portfolios, sign, or broadcast.

const summary = await connection.getMultiChainSnapshot({
  chains: ["ethereum", "bitcoin", "solana"],
  addresses: {
    ethereum: "0x0000000000000000000000000000000000000001",
    bitcoin: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
  },
  concurrency: 2
});

for (const result of summary.results) {
  if (result.ok) {
    console.log(result.chain, result.snapshot.balance);
  } else {
    console.warn(result.chain, result.error.message);
  }
}

The method is also available as Blockchain.getMultiChainSnapshot(options) and client.getMultiChainSnapshot(options). Omit chains to use the keys in addresses; omit both to request all supported chains. An empty chains array returns an empty, successful summary.

Asset client methods and return values

AssetClient is available through client.assets(chain) or through the top-level asset methods on ApplicationClient.

| Method | Description | Return value | | --- | --- | --- | | getCapabilities(chain) | Reports known-balance, discovery, metadata, and history support | AssetCapabilities | | getCapabilityExplanation(chain, capability) | Explains whether a capability is direct or provider-backed | CapabilityExplanation with optional source | | chain(chain) | Creates a chain-scoped asset facade | AssetChainClient | | getBalance(chain, request) | Reads a known asset balance | Promise<AssetBalance> | | getMetadata(chain, request) | Reads metadata for a normalized asset descriptor | Promise<AssetMetadataResult> | | discover(chain, { owner }) | Reads the supported asset portfolio for an owner | Promise<AssetDiscoveryResult> | | getHistory(chain, { owner, asset? }) | Reads provider-backed asset history | Promise<AssetHistoryResult> |

Asset result objects preserve chain-native data under native and identify provenance. AssetBalance includes amount as a bigint when available, formattedAmount, displayAmount, source, status, warnings, and the normalized asset. AssetMetadataResult includes source and confidence fields. Discovery returns an assets array plus status, source, and warnings. History returns provider-sourced items; it is not fabricated when an indexed provider is unavailable.

Transfer preparation and transaction lifecycle

Transfer preparation never signs or broadcasts. It returns an inspectable object before any wallet prompt:

| Method or property | Description | Return value | | --- | --- | --- | | prepared.fee | Fee preflight for the native network fee | NativeTransferFee with status, native, formatted, gasLimit, gasPrice, and raw | | prepared.balance | Sender balance when the adapter can read it | bigint \| null | | prepared.simulation | Optional simulation result | not_requested, passed, failed, or unsupported, plus an error when applicable | | prepared.warnings | Non-blocking conditions discovered during preparation | string[] | | prepared.blockers | Conditions that prevent execution | string[] | | prepared.supported | Whether the chain and payload format are supported | boolean | | prepared.ready | Whether execution can proceed | boolean | | prepared.execute(options?) | Performs the one explicit wallet approval/submission | Promise<TransactionOperation> |

PreparedNativeTransfer also contains the exact native amount, formatted amount, sender, recipient, chain, unsigned transaction, fee, total, and simulation state. PreparedAssetTransfer additionally contains the normalized asset descriptor. Unsupported asset families remain blocked instead of being forwarded to a wallet as an unknown payload.

| Lifecycle method | Description | Return value | | --- | --- | --- | | operation.snapshot() | Reads the current lifecycle state | TransactionLifecycle | | operation.wait(options?) | Waits for the requested confirmation count | Promise<TransactionLifecycle> | | operation.onStatus(listener) | Subscribes to lifecycle updates | An unsubscribe () => void function |

TransactionLifecycle includes chain, hash, status, confirmations, requiredConfirmations, block, and error. Status is pending, confirmed, finalized, failed, or unsupported. A polling timeout returns pending with a structured TRANSACTION_TIMEOUT error; it does not imply that the transaction was rejected.

Wallet API

Import wallet functionality from universal-blockchain-sdk/wallet, or use the application client methods above. Wallet functionality is browser-only and never creates wallets, stores private keys, handles seed phrases, signs automatically, or broadcasts automatically.

| Method | Description | Return value | | --- | --- | --- | | createWalletManager(options?) | Creates a wallet manager with optional connectors | WalletManager | | discoverBrowserProviders(chain, options?) | Discovers compatible installed browser providers | Promise<WalletDescriptor[]> | | normalizeWalletChain(chain) | Normalizes a wallet chain name | WalletChain | | getWalletChainConfig(chain, network?) | Reads profile-specific wallet standard and expected network metadata | Wallet chain configuration | | normalizeWalletError(error, context?) | Converts a provider error to a stable wallet error | WalletError | | manager.registerConnector(connector) | Adds an application-provided official connector | An unregister () => boolean function | | manager.discover(chain, { network? }) | Discovers wallets for a chain/profile | Promise<WalletDescriptor[]> | | manager.connect({ chain, walletId, network? }) | Requests a selected wallet connection for a profile | Promise<WalletSession> | | manager.restore(chain, { network? }) | Silently restores a matching public session hint | Promise<WalletSession \| null> | | manager.getSession(chain, { network? }) | Returns the current profile-specific session | WalletSession \| null | | manager.disconnect(chain, { network? }) | Disconnects a chain/profile session | void | | manager.on(event, listener) | Listens for wallet/account/network changes | An unsubscribe () => boolean function | | session.snapshot() | Returns the public wallet state | WalletSessionSnapshot | | session.refreshNetwork() | Refreshes the wallet's active network | Promise<WalletSessionSnapshot> | | session.refreshAccounts() | Refreshes the wallet's public accounts | Promise<WalletSessionSnapshot> | | session.switchNetwork() | Requests the expected network when supported | Promise<WalletSessionSnapshot> | | session.signMessage(message) | Requests a user-approved message signature | Promise<unknown> | | session.sendTransaction(transaction) | Requests a user-approved wallet transaction | Promise<unknown> | | session.disconnect() | Ends the current session | void |

Wallet descriptors expose connect, disconnect, signMessage, sendTransaction, assetTransfer, and switchNetwork capability flags. WalletSessionSnapshot exposes the public address, accounts, active and selected profile, active and expected chain IDs, network status, wallet identity, and those capabilities.

Unit, address, explorer, and capability helpers

| Export | Description | Return value | | --- | --- | --- | | getUnit(unit) | Resolves a native unit definition | UnitDefinition | | getNativeUnit(chain) | Resolves the canonical unit for a chain | UnitDefinition | | parseUnits(value, unit) | Converts a decimal string to exact base units | bigint | | formatUnits(value, unit) | Converts exact base units to a decimal string | string | | parseNativeUnits(chain, value) | Parses an amount using the chain's native unit | bigint | | formatNativeUnits(chain, value) | Formats an amount using the chain's native unit | string | | getChainCapabilities(chain) | Reads static capability metadata | ChainCapabilities | | supportsCapability(chain, capability) | Tests static capability metadata | boolean | | getCapabilityExplanation(chain, capability) | Explains static capability metadata | CapabilityExplanation | | isValidAddress(chain, address) | Checks an address without returning a normalized value | boolean | | validateAddress(chain, address) | Validates and normalizes an address | string; throws when invalid | | normalizeAddress(chain, address) | Normalizes a valid address | string | | getAddressValidation(chain, address) | Validates an address and returns its normalized record | { chain, address, valid: true } | | getExplorerBaseUrl(chain) | Returns the configured explorer base URL | string \| null | | getExplorerUrl(chain, kind, value) | Builds one explorer URL | string \| null | | getExplorerUrls(chain, kind, value) | Builds all explorer URLs | string[] | | toErrorDetails(error) | Converts an unknown error to a serializable error record | ErrorDetails | | extractTransactionHash(transaction) | Finds a transaction hash in a native record | string \| null | | extractTransactionValue(transaction) | Finds a transaction value in a native record | bigint \| number \| string \| null | | findTransactionHash(block) | Finds a transaction hash in a block record | string \| null | | findBlockHash(block) | Finds a block hash in a native record | string \| null | | countBlockTransactions(block) | Counts transactions in a block-like record | number \| null | | findBlockTimestamp(block) | Finds a block timestamp | string \| number \| null | | findBlockHeight(block) | Finds a block height or slot | string \| number \| null | | findCardanoAddress(transaction) | Finds a Cardano address in a native transaction | string \| null | | serializeExplorerTransaction(transaction, chain?) | Adds explorer-friendly formatting to a normalized transaction | Serialized transaction or null | | serializeExplorerBlock(block, requested?, chain?) | Adds explorer-friendly fields to a block | ExplorerBlock \| null | | validateExplorerChain(chain) | Validates an explorer chain name | ChainName | | validateExplorerDetail(chain, kind, value) | Validates an explorer detail request | { kind, value } | | getExplorerBlockReference(value) | Converts an explorer block input to a number or string reference | string \| number |

Adapter factories are also available when an application needs a direct chain-specific adapter instead of a manager:

| Factory | Description | Return value | | --- | --- | --- | | createEvmAdapter(options?) | Creates an Ethereum, BNB, Avalanche, or HyperEVM adapter | Promise<EVMAdapter> | | createBitcoinAdapter(options?) | Creates a Bitcoin adapter | BitcoinAdapter | | createSolanaAdapter(options?) | Creates a Solana adapter | SolanaAdapter | | createXrpAdapter(options?) | Creates an XRP Ledger adapter | XrpAdapter | | createTronAdapter(options?) | Creates a TRON adapter | TronAdapter | | createCardanoAdapter(options?) | Creates a Cardano adapter | CardanoAdapter | | createSuiAdapter(options?) | Creates a Sui adapter | SuiAdapter | | createHyperliquidAdapter(options?) | Creates a native Hyperliquid adapter | HyperliquidAdapter |

Framework bindings and testkit

Framework bindings are optional subpath exports. They accept the host framework's runtime instead of bundling React, Vue, or Svelte into the core package.

| Import | Method | Return value | | --- | --- | --- | | universal-blockchain-sdk/react | createReactBindings(client, react) | Object with useClient, useChain, useSubscription, and useWalletSession | | universal-blockchain-sdk/vue | createVueBindings(client, vue) | Object with useClient, useChain, useSubscription, and useWalletSession | | universal-blockchain-sdk/svelte | createSvelteBindings(client) | Object with client, chain, subscription, and walletSession stores | | universal-blockchain-sdk/testkit | createTestAdapter(options?) | Deterministic adapter fixture with mutable state and setters | | universal-blockchain-sdk/testkit | createTestClient(options?) | { client, fixture } for isolated application tests | | universal-blockchain-sdk/testkit | createTestAssetProvider(options?) | Deterministic asset provider fixture | | universal-blockchain-sdk/testkit | createTestWallet(options?) | Deterministic wallet fixture | | universal-blockchain-sdk/testkit | createTransactionFixture(options?) | NormalizedTransaction test record |

All amount helpers reject JavaScript floating-point number inputs. Use decimal strings or bigint values to preserve exact blockchain amounts.

Chain-specific adapter methods

Every adapter also implements the universal ChainAdapter methods. The following methods are available when the connected adapter supports that protocol-specific surface:

| Adapter | Method | Return value | | --- | --- | --- | | EVM | getBlockNumber() | Promise<number> | | EVM | getBlock(block?, includeTransactions?) | Promise<NormalizedBlock \| unknown \| null> | | EVM | getTransactionReceipt(hash) | Promise<unknown \| null> | | EVM | call(to, data, from?) | Promise<string> containing call data | | EVM | contract(address, abi) | An ethers contract instance | | EVM | wallet(privateKey) | An ethers wallet instance; use only in explicitly controlled code | | EVM | sendTransaction(privateKey, transaction) | Promise<unknown> containing the provider response | | EVM | isAddress(address) | boolean | | EVM | toChecksumAddress(address) | string | | Bitcoin | request(path, init?) | Promise<unknown> | | Bitcoin | getBlockHeight() | Promise<number> | | Bitcoin | getTipHash() | Promise<string> | | Bitcoin | getBlockByHeight(height) / getBlockByHash(hash) | Promise<NormalizedBlock \| unknown \| null> | | Bitcoin | getAddress(address) / getAddressTransactions(address) | Promise<unknown> | | Bitcoin | getFeeEstimates() | Promise<unknown> | | Bitcoin | getRawTransaction(txid) | Promise<string> | | Bitcoin | getTransactionStatus(txid) | Promise<unknown> | | Bitcoin | broadcastTransaction(rawHex) | Promise<string> transaction ID | | Solana | getSlot() | Promise<number> | | Solana | getLatestBlockhash() | Promise<unknown> | | Solana | getAccountInfo(address) / getSignaturesForAddress(address, options?) | Promise<unknown> | | Solana | sendRawTransaction(rawTransaction, options?) | Promise<string> signature | | Solana | confirmTransaction(signature, commitment?) | Promise<unknown> | | Solana | getEpochInfo() / getSupply() / getVersion() | Promise<unknown> | | Solana | getBlockTime(slot) | Promise<number \| null> | | Solana | getMinimumBalanceForRentExemption(dataLength) | Promise<number> lamports | | XRP | request(method, params?) | Promise<unknown> native JSON-RPC result | | TRON | request(path, body?) | Promise<unknown> native HTTP result | | Cardano | request(path, init?) / post(path, body) | Promise<unknown> | | Cardano | getBlockTransactions(hash) | Promise<string[]> transaction hashes | | Sui | query(query, variables?) | Promise<unknown> GraphQL result | | Native Hyperliquid | requestInfo(payload) | Promise<unknown> official /info result | | Native Hyperliquid | getSpotMeta() | Promise<unknown> spot metadata | | Native Hyperliquid | getSpotClearinghouseState(address) | Promise<unknown> spot account state |

Native Hyperliquid deliberately does not expose a latest-height method: getHeight() rejects because the official native /info API does not provide a documented authoritative latest block-height request. Use hyperliquid-evm when an EVM block-height or EVM transaction workflow is required.

Reusable explorer reads

Use getExplorerDetail() for validated address, transaction, and block detail reads, or getExplorerSnapshot() to assemble the live summary view used by the sample. Both methods preserve native transaction and block data, return provider-health context, preserve native amounts as bigint, and report partial provider failures as "degraded" results. A valid identifier that returns no record resolves to null; malformed identifiers reject before an adapter is called.

import {
  UniversalChainConnection
} from "universal-blockchain-sdk";

const chains =
  new UniversalChainConnection();

const address =
  await chains.getExplorerDetail(
    "ethereum",
    "address",
    "0x0000000000000000000000000000000000000000"
  );

const transaction =
  await chains.getExplorerDetail(
    "solana",
    "transaction",
    "2ZYCZ8YBxbZt7ZLnNDs83ZeRYgA4hzpVwFm5H72BLupQ"
  );

const snapshot =
  await chains.getExplorerSnapshot(
    "bitcoin",
    {
      label: "Bitcoin",
      symbol: "BTC",
      address:
        "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  );

Network metadata

const chain = await Blockchain.connect("ethereum");

console.log(chain.getChain());
// "ethereum"

console.log(chain.getNetwork());
// {
//   chain: "ethereum",
//   name: "Ethereum Mainnet",
//   network: "mainnet",
//   chainId: 1
// }

console.log(chain.getNetworkType());
// "mainnet"

Bitcoin and Solana use chainId: null because they do not use EVM chain IDs:

{
  chain: "bitcoin",
  name: "Bitcoin Mainnet",
  network: "mainnet",
  chainId: null
}

Heights

getHeight() is normalized across block-addressable chains. It represents an EVM block number, Bitcoin height, Solana slot, XRP ledger index, TRON block number, Cardano block number, or Sui checkpoint number as appropriate.

const ethereumHeight =
  await ethereum.getHeight();

const bitcoinHeight =
  await bitcoin.getHeight();

const solanaHeight =
  await solana.getHeight();

console.log({
  ethereum: ethereumHeight,
  bitcoin: bitcoinHeight,
  solana: solanaHeight
});

The underlying meaning is the latest EVM block number, latest Bitcoin block height, latest Solana slot, latest XRP ledger index, latest TRON block number, latest Cardano block number, or latest Sui checkpoint number.

Balances and units

getBalance() returns a bigint in the smallest native unit:

  • Ethereum: wei
  • BNB Smart Chain, Avalanche C-Chain, and HyperEVM: wei
  • Bitcoin: satoshis
  • Solana: lamports
  • XRP Ledger: drops
  • TRON: sun
  • Cardano: lovelace
  • Sui: MIST
  • Hyperliquid native L1: the HYPE token's current weiDecimals smallest unit
const wei =
  await ethereum.getBalance(
    "0x0000000000000000000000000000000000000000"
  );

const bitcoinSatoshis =
  await bitcoin.getBalance(
    "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
  );

const lamports =
  await solana.getBalance(
    "11111111111111111111111111111111"
  );

console.log(wei.toString());
console.log(bitcoinSatoshis.toString());
console.log(lamports.toString());

Use the built-in exact conversion helpers for display values and user input. They only accept decimal strings or bigint values, never JavaScript number, so they preserve full precision:

import {
  formatNativeUnits,
  formatUnits,
  parseNativeUnits,
  parseUnits
} from "universal-blockchain-sdk";

const ethereumAmount =
  formatUnits(wei, "wei");

const bitcoinAmount =
  formatNativeUnits(
    "bitcoin",
    bitcoinSatoshis
  );

const oneSatoshi =
  parseNativeUnits(
    "bitcoin",
    "0.00000001"
  );
// 1n

const hypeSmallestUnits =
  parseUnits("1.25", "hype");
// 125000000n

console.log(
  formatNativeUnits(
    "hyperliquid",
    hypeSmallestUnits
  )
);
// "1.25"

Supported unit names are wei, satoshis, lamports, drops, sun, lovelace, mist, and hype. Hyperliquid uses HYPE's current weiDecimals value of 8. parseUnits() accepts a plain decimal string, while formatUnits() accepts a bigint or integer string.

Bitcoin balances represent the confirmed on-chain balance returned by the configured Bitcoin API.

TypeScript support

The npm package ships declarations for the universal connection, public adapters, normalized responses, provider health, batch results, capabilities, structured errors, and unit helpers.

import {
  Blockchain,
  formatNativeUnits,
  type BalanceBatchResult
} from "universal-blockchain-sdk";

const results: BalanceBatchResult[] =
  await Blockchain.batchGetBalances([
    {
      chain: "bitcoin",
      address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  ]);

if (results[0].ok) {
  console.log(
    formatNativeUnits(
      "bitcoin",
      results[0].value
    )
  );
}

Capabilities

Check a chain's feature support before choosing an operation. This prevents applications from assuming that EVM capabilities apply to every protocol and makes native Hyperliquid's latest-height limitation explicit.

const capabilities =
  await Blockchain.getCapabilities(
    "hyperliquid"
  );

console.log(capabilities.latestBlock);
// false

const canReadReceipt =
  await Blockchain.supports(
    "avalanche",
    "transactionReceipt"
  );
// true

Capabilities include getHeight, getBalance, getBlock, getTransaction, latestBlock, transactionReceipt, contractCall, broadcastTransaction, and a nativeMethods list.

Provider diagnostics and errors

Read requests automatically rotate through configured providers. When every provider fails, the library throws a ProviderRequestError with stable fields, rather than forcing applications to parse a message:

import {
  Blockchain,
  ProviderRequestError
} from "universal-blockchain-sdk";

try {
  await Blockchain.getBalance(
    "bitcoin",
    address
  );
} catch (error) {
  if (error instanceof ProviderRequestError) {
    console.error({
      chain: error.chain,
      operation: error.operation,
      provider: error.provider,
      attemptedProviders: error.attemptedProviders,
      retryable: error.retryable
    });
  }
}

const health =
  await Blockchain.getProviderHealth(
    "bitcoin"
  );

console.table(health.providers);

Each health record includes status, success and failure counts, the most recent latency, the last operation, timestamps, and a structured last error. Provider health is observational: it never causes a broadcast retry.

Batch reads

Use bounded batch helpers for dashboards, portfolios, and monitoring. Results always preserve input order. One failed item produces { ok: false, error }; successful items remain available.

const balances =
  await Blockchain.batchGetBalances(
    [
      {
        chain: "ethereum",
        address:
          "0x0000000000000000000000000000000000000000"
      },
      {
        chain: "bitcoin",
        address:
          "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
      }
    ],
    {
      concurrency: 2
    }
  );

for (const result of balances) {
  if (result.ok) {
    console.log(
      result.chain,
      result.value.toString()
    );
  } else {
    console.warn(
      result.chain,
      result.error.message
    );
  }
}

batchGetBlocks([{ chain, block }]) and batchGetTransactions([{ chain, hash }]) use the same { concurrency } option and per-item result format. Batches only call read methods; they never broadcast transactions.

Blocks

Use "latest" to request the most recent block where the adapter supports that tag:

const ethereumBlock =
  await ethereum.getBlock("latest");

const bitcoinBlock =
  await bitcoin.getBlock("latest");

const solanaBlock =
  await solana.getBlock("latest");

Bitcoin blocks include a tx array of transaction IDs. Ethereum blocks expose the provider's transaction list. Solana blocks expose the provider's transaction records.

You can also request a specific block:

const ethereumBlockByNumber =
  await ethereum.getBlock(20000000);

const bitcoinBlockByHeight =
  await bitcoin.getBlock(900000);

const solanaBlockBySlot =
  await solana.getBlock(250000000);

Transactions

getTransaction() returns a normalized object and preserves the complete chain-specific response under native.

const transaction =
  await ethereum.getTransaction(
    transactionHash
  );

if (transaction) {
  console.log(transaction.chain);
  console.log(transaction.hash);
  console.log(transaction.status);
  console.log(transaction.block);
  console.log(transaction.value.toString());
  console.log(transaction.native);
}

The normalized fields are:

{
  chain,
  hash,
  status,
  from,
  to,
  value,
  block,
  blockHash,
  timestamp,
  native
}

native should be used whenever your application needs chain-specific fields. Some normalized fields can legitimately be null when a transaction does not have one universal representation. For example, Solana transactions can contain many instructions and do not always represent one native transfer amount, so value is null when no single amount can be determined honestly.

EVM API: Ethereum, BNB, Avalanche, and HyperEVM

The EVM adapter has profiles for `"ethereum