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.
Maintainers
Keywords
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
/infoAPI
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-sdkInstall from source
git clone https://github.com/MichaelHDesigns/universal-blockchain-sdk.git
cd universal-blockchain-sdk
npm installThe 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 startThe 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 sampleOpen 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'sgetExplorerDetail(chain, "address", address)read, which calls the connected adapter'sgetBalance(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: usesgetExplorerDetail(chain, "transaction", hash), which callsgetTransaction(hash), shows the normalized transaction fields, and exposes its chain-specificnativeresponse in a collapsible panel when present.block: usesgetExplorerDetail(chain, "block", block), which callsgetBlock(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:sampleBrowser 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 resultFor 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
weiDecimalssmallest 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"
);
// trueCapabilities 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
