xian-js
xian-js is the JavaScript / TypeScript SDK workspace for integrating Xian from browser apps, wallets, dapps, and Node.js code that prefers TS.
It currently ships three public packages:
@xian-tech/client: typed RPC client, tx helpers, Ed25519 signing helpers, and websocket subscriptions@xian-tech/provider: browser wallet-provider contract and a simple in-memory provider implementation for demos, tests, injected-wallet discovery, and early integrations@xian-tech/types: the shared transaction, signer, number, and broadcast-mode types used by both public packages
The browser wallet product line now lives in the sibling xian-wallet-browser repo, which consumes these SDK packages. The current wallet product surface is documented here under xian-wallet-browser.
Current Status
xian-js is available both as a maintained repo and as published npm packages:
@xian-tech/client@xian-tech/provider@xian-tech/types
The browser wallet product line is documented separately under xian-wallet-browser. The source repo publishes @xian-tech/wallet-core.
Workspace Setup
cd ~/xian/xian-js
npm install
npm run validateThis runs:
- package type-checks
- package builds
- test suites
Package Overview
@xian-tech/client
The client package owns:
- Xian RPC and ABCI query reads
- tx payload building
- canonical payload signing
- tx broadcast helpers
- tx wait / polling helpers
- token and contract convenience helpers
- dashboard websocket subscriptions
@xian-tech/provider
The provider package owns:
- the browser-facing provider request contract
- provider events such as account and chain changes
- a simple in-memory provider that delegates signing and broadcast to
@xian-tech/client - injected-wallet registration on
window.xianandwindow.xianProviders - discovery helpers for late-loaded browser wallets
- a dapp-facing wrapper around an injected provider
@xian-tech/types
The types package owns the shared TypeScript contracts used across the JS workspace:
- transaction payload and envelope types
- signer interfaces
- broadcast-mode and numeric helper types
That gives Xian a clean split between:
- low-level network and tx logic in
@xian-tech/client - wallet-provider integration in
@xian-tech/provider - shared transaction and signer types in
@xian-tech/types - browser wallet product code in
xian-wallet-browser
Installing In Another JS Project
Install the packages directly from npm:
npm install @xian-tech/client @xian-tech/provider @xian-tech/typesFor local development against the monorepo itself, you can still work from the workspace checkout.
Inside the xian-js repo itself, the packages are already linked through the workspace. examples/browser-dapp/ demonstrates the dapp side of the contract. The browser wallet implementation now lives in the sibling xian-wallet-browser repo.
Public Imports
Current top-level imports from @xian-tech/client include:
import {
AbciError,
BroadcastMode,
ContractClient,
Ed25519Signer,
RpcError,
SimulationError,
TokenClient,
TransactionError,
TransportError,
TxTimeoutError,
WatchApi,
XianClient,
XianClientError,
XianShieldedRelayerClient,
XianShieldedRelayerPoolClient,
canonicalizeRuntime,
decodeRuntime,
encodeRuntime,
generatePrivateKey,
isValidEd25519Key,
isValidEd25519Signature,
normalizeShieldedRelayerCatalogEntry,
parseXianNumber,
publicKeyFromPrivateKey,
shieldedSyncHintFromViewingPrivateKey,
shieldedSyncHintFromViewingPublicKey,
signMessage,
sortShieldedRelayerCatalog,
sortKeysDeep,
verifyMessage,
} from "@xian-tech/client";Current top-level imports from @xian-tech/provider include:
import {
InjectedXianWallet,
InMemoryXianProvider,
ProviderBackedXianSigner,
ProviderChainMismatchError,
ProviderDisconnectedError,
ProviderUnauthorizedError,
ProviderUnsupportedMethodError,
XianProviderError,
XIAN_INITIALIZED_EVENT,
getInjectedXianProvider,
listInjectedXianProviders,
registerInjectedXianProvider,
waitForInjectedXianProvider,
} from "@xian-tech/provider";When you want the shared core types directly, import them from @xian-tech/types:
import type {
BroadcastMode,
XianNumber,
XianSignedTransaction,
XianSigner,
XianTxPayload,
XianUnsignedTransaction,
} from "@xian-tech/types";Ed25519 Signers
xian-js includes a built-in Ed25519 signer primarily for:
- local development
- tests
- Node.js service usage where local key custody is acceptable
Basic usage:
import { Ed25519Signer } from "@xian-tech/client";
const signer = new Ed25519Signer();
console.log(signer.privateKey);
console.log(signer.address);Restore from an existing private key:
const signer = new Ed25519Signer("your_private_key_hex");The signer exposes:
addressprivateKeygetAddress()signMessage(message)verifyMessage(message, signature)
For production browser wallet flows, prefer provider-backed signing rather than raw private-key handling in the app itself.
Constructing A Client
import { XianClient } from "@xian-tech/client";
const client = new XianClient({
rpcUrl: "http://127.0.0.1:26657",
dashboardUrl: "http://127.0.0.1:8080",
chainId: "xian-local",
});Constructor fields:
rpcUrl: requireddashboardUrl: optional, but needed for websocket subscriptionschainId: optional; if omitted, the client fetches it from/genesisfetchFn: optional custom fetch implementationwebSocketFactory: optional custom websocket factoryrequestTimeoutMs: optional default HTTP timeout; defaults to30_000
Common Read Methods
Chain And Node Reads
await client.getGenesis();
await client.getChainId();
await client.getStatus();
await client.getBlock(123);
await client.getTx("ABC123");Nonce And State Reads
await client.getNonce(address);
await client.getState("currency", "balances", [address]);
await client.getBalance(address);
await client.getTokenMetadata("currency");
await client.getTokenBalances(address, { limit: 100, includeZero: false });
await client.getChiRate();
await client.getContract("currency");
await client.getContractCode("currency");
await client.getContractMethods("currency");
await client.getShieldedWalletHistory(tagValue, { afterNoteIndex: 0 });getBalance(...) first tries the readonly balance_of simulation path and falls back to the direct state key if the simulation path fails.
getTokenBalances(...) and getShieldedWalletHistory(...) require the BDS-backed ABCI query surface on the connected node.
Simulation And Estimation
Use readonly simulation before signing or sending:
const simulation = await client.simulate({
sender: address,
contract: "currency",
function: "transfer",
kwargs: { to: "bob", amount: "5" },
});Estimate chi:
const estimate = await client.estimateChi({
sender: address,
contract: "currency",
function: "transfer",
kwargs: { to: "bob", amount: "5" },
});
console.log(estimate.estimated);
console.log(estimate.suggested);The current default policy adds proportional headroom plus a minimum floor, matching the browser-first SDK design note for explicit but ergonomic tx submission.
Building, Signing, And Broadcasting Transactions
Build an unsigned tx:
const tx = await client.buildTx({
sender: signer.address,
contract: "currency",
function: "transfer",
kwargs: { to: "bob", amount: "5" },
chi: 50_000,
});Sign it:
const signedTx = await client.signTx(tx, signer);Broadcast it:
const submission = await client.broadcastTx(signedTx, {
mode: "checktx",
waitForTx: true,
});Supported broadcast modes:
"async""checktx""commit"
Wait for a tx explicitly:
const receipt = await client.waitForTx("ABC123", {
timeoutMs: 30_000,
pollIntervalMs: 500,
});Convenience Helpers
Contract Helper
const currency = client.contract("currency");
await currency.call("balance_of", { address: signer.address }, signer.address);Send through the contract helper:
await currency.send(
"transfer",
{ to: "bob", amount: "5" },
{ signer, chi: 50_000, mode: "checktx" },
);Token Helper
const token = client.token("currency");
await token.metadata();
await token.balanceOf(signer.address);
await token.allowance(signer.address, "con_dex");
await token.transfer({
signer,
to: "bob",
amount: "5",
chi: 50_000,
});
await token.approve({
signer,
spender: "con_dex",
amount: "100",
chi: 50_000,
});Runtime Encoding Helpers
The client package also exposes deterministic runtime helpers that mirror the Xian JSON encoding rules closely enough for JS-side tx construction:
import {
canonicalizeRuntime,
decodeRuntime,
encodeRuntime,
sortKeysDeep,
} from "@xian-tech/client";These are useful when you need:
- canonical payload strings for signing
- stable sorted object encoding
- runtime wrapper handling for big integers and byte arrays
Websocket Subscriptions
Dashboard websocket support lives under client.watch.
Watch blocks:
const sub = client.watch.blocks((message) => {
console.log(message.height, message.hash);
});Watch a state key:
const sub = client.watch.state(
`currency.balances:${signer.address}`,
(message) => {
console.log(message.key, message.value);
},
{
onError(error) {
console.error("watch error", error);
},
},
);Watch contract events:
const sub = client.watch.events(
{ contract: "currency", event: "Transfer" },
(message) => {
console.log(message.event, message.data);
},
);Unsubscribe explicitly:
await sub.unsubscribe();dashboardUrl must be configured on the client for websocket usage. Malformed websocket payloads and async listener failures are surfaced through that optional onError callback instead of becoming unhandled promise rejections.
Shielded Relayer Clients
Use the relayer clients for proof-bound shielded submissions instead of hand-rolling HTTP calls:
import { XianShieldedRelayerClient } from "@xian-tech/client";
const relayer = new XianShieldedRelayerClient({
relayerUrl: "http://127.0.0.1:8090",
authToken: "local-dev-token",
});
const info = await relayer.getInfo();
const quote = await relayer.getQuote({
kind: "shielded_note_relay_transfer",
contract: "con_private_usd",
});XianShieldedRelayerPoolClient accepts an ordered relayer catalog and follows the same route semantics as xian-py: info and quote calls can fail over, while submissions and job lookup stay routed to the relayer selected by the proof.
Provider Contract
@xian-tech/provider defines a simple request-driven provider shape:
interface XianProvider {
request(args: { method: string; params?: unknown[] | object }): Promise<unknown>;
on(event: string, listener: (...args: unknown[]) => void): void;
removeListener(event: string, listener: (...args: unknown[]) => void): void;
}The current in-memory provider is a reference implementation for:
- tests
- demos
- local browser examples
It is not a replacement for a production injected or hosted wallet.
Injected Wallet Contract
@xian-tech/provider includes the initial injected-wallet convention for Xian.
Wallets register into:
window.xian: namespace for the default injected providerwindow.xianProviders: array of all discovered injected providers
Wallet registration dispatches the xian#initialized browser event so dapps can wait for late-loading providers.
Wallet-Side Injection
import { registerInjectedXianProvider } from "@xian-tech/provider";
registerInjectedXianProvider({
provider,
metadata: {
id: "xian-wallet",
name: "Xian Wallet",
rdns: "org.xian.wallet",
},
setAsDefault: true,
});Each registered wallet carries metadata:
id: stable wallet identifiername: display name for wallet pickersicon: optional icon URLrdns: optional reverse-DNS identifier
Dapp-Side Discovery
Resolve the default injected wallet:
import { InjectedXianWallet } from "@xian-tech/provider";
const wallet = InjectedXianWallet.getInjected();Wait for a late-loading wallet:
const wallet = await InjectedXianWallet.waitForInjected({
timeoutMs: 1_000,
});List all discovered wallets:
import { listInjectedXianProviders } from "@xian-tech/provider";
const wallets = listInjectedXianProviders();Resolve a specific wallet by id:
import { getInjectedXianProvider } from "@xian-tech/provider";
const record = getInjectedXianProvider({ id: "xian-wallet" });Dapp-Side Wallet Wrapper
InjectedXianWallet exposes a higher-level surface around a raw provider:
const wallet = await InjectedXianWallet.waitForInjected({
timeoutMs: 1_000,
});
const accounts = await wallet?.connect();
const info = await wallet?.getWalletInfo();
const chainId = await wallet?.getChainId();
const accepted = await wallet?.watchAsset({
type: "token",
options: { contract: "currency", symbol: "XIAN", name: "Xian" },
});
const signature = await wallet?.signMessage("hello from xian-js");
const tx = await wallet?.prepareTransaction({
contract: "currency",
function: "transfer",
kwargs: { to: "bob", amount: "5" },
});
const signedTx = await wallet?.signTransaction(tx);
const submission = await wallet?.sendTransaction(tx, {
mode: "checktx",
waitForTx: true,
});
const quickSubmission = await wallet?.sendCall(
{
contract: "currency",
function: "transfer",
kwargs: { to: "bob", amount: "5" },
},
{ mode: "checktx", waitForTx: true },
);It also forwards provider events:
wallet?.on("accountsChanged", (accounts) => {
console.log(accounts);
});Provider-Backed Signer Adapter
When another helper only needs getAddress() and signMessage(), wrap the wallet as a signer:
const signer = wallet?.asSigner();
const address = await signer?.getAddress();
const signature = await signer?.signMessage("hello");That lets dapp code keep provider-backed signing without storing raw private keys in the app.
Recommended Transaction Model
For real browser wallets, prefer the intent-based path over prebuilding every payload in the dapp:
prepareTransaction(...)lets the wallet fill sender, active chain, nonce, and default chisendCall(...)lets the wallet do prepare + sign + broadcast in one request
That avoids stale nonces and wrong-account or wrong-chain mismatches better than forcing every dapp to build the full unsigned tx upfront.
In-Memory Provider Example
import { Ed25519Signer, XianClient } from "@xian-tech/client";
import { InMemoryXianProvider } from "@xian-tech/provider";
const signer = new Ed25519Signer();
const client = new XianClient({
rpcUrl: "http://127.0.0.1:26657",
dashboardUrl: "http://127.0.0.1:8080",
});
const provider = new InMemoryXianProvider({ signer, client });Connect:
await provider.request({ method: "xian_requestAccounts" });Read accounts and chain:
await provider.request({ method: "xian_getWalletInfo" });
await provider.request({ method: "xian_accounts" });
await provider.request({ method: "xian_chainId" });Watch an asset:
await provider.request({
method: "xian_watchAsset",
params: [{ type: "token", options: { contract: "currency", symbol: "XIAN" } }],
});Prepare a transaction inside the wallet:
const tx = await provider.request({
method: "xian_prepareTransaction",
params: [
{
intent: {
contract: "currency",
function: "transfer",
kwargs: { to: "bob", amount: "5" },
},
},
],
});Sign a message:
await provider.request({
method: "xian_signMessage",
params: [{ message: "hello from xian-js" }],
});Sign a transaction:
await provider.request({
method: "xian_signTransaction",
params: [{ tx }],
});Send a transaction:
await provider.request({
method: "xian_sendTransaction",
params: [{ tx, mode: "checktx" }],
});Send a contract call without prebuilding the tx:
await provider.request({
method: "xian_sendCall",
params: [
{
intent: {
contract: "currency",
function: "transfer",
kwargs: { to: "bob", amount: "5" },
},
mode: "checktx",
waitForTx: true,
},
],
});Provider Events
The current provider emits:
connectdisconnectaccountsChangedchainChanged
Example:
provider.on("accountsChanged", (accounts) => {
console.log(accounts);
});Example App
The xian-js repo includes a runnable example app under:
examples/browser-dapp/
That example demonstrates:
- client setup
- dev-signer generation or restore
- provider connection
- provider wallet-info reads
- chain id, nonce, and balance reads
- provider-managed tx preparation
- tx build / sign / send flows
- quick intent-based send flows
- block and balance subscriptions
- injecting the demo wallet into
window - rediscovering and using the injected-wallet path
Run it from the xian-js repo:
npm install
npm run build
npm run dev --workspace example-browser-dappFor the walkthrough of the maintained browser playground itself, see xian-js Playground.
Browser Wallet Companion Repo
The concrete browser wallet implementation now lives in the sibling xian-wallet-browser repo.
That repo currently owns:
@xian-tech/wallet-corefor wallet-domain logicapps/wallet-extension/for the MV3 extension wallet
During local development, xian-wallet-browser consumes @xian-tech/client and @xian-tech/provider from the xian-js checkout.
See xian-wallet-browser for the current wallet scope, build flow, and extension loading steps.
Validation
From the xian-js repo root:
npm install
npm run validate