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

ault-sdk-ts

v0.1.0

Published

TypeScript SDK for the Ault blockchain with high-level transaction methods and REST query clients

Downloads

1,996

Readme

Ault SDK for TypeScript

A TypeScript SDK for the Ault blockchain that provides high-level transaction methods and REST query clients for the current Ault modules, including License, Miner, Market, CLOB, Agent, Authority, RateLimit, Reward, and TokenRegistry.

Features

  • High-level client with simple transaction methods (no manual message building)
  • Flexible signer support (viem, ethers, Privy, MetaMask, private keys)
  • Automatic address format conversion (0x <-> ault1)
  • DEX API client for market data, orders, trades, and UDF charting
  • DEX WebSocket client for real-time streaming with type-safe subscriptions and auto-reconnect
  • REST query clients for the current Ault modules
  • EIP-712 typed data signing
  • Works across Node.js, Bun, and browser runtimes
  • Full TypeScript support with comprehensive type definitions
  • Automatic retry with exponential backoff

Installation

# npm
npm install ault-sdk-ts

# pnpm
pnpm add ault-sdk-ts

# bun
bun add ault-sdk-ts

Installing from the npm registry is the supported path for production use.

Quick Start

import { createClient, getNetworkConfig } from "ault-sdk-ts";
import { privateKeyToAccount } from "viem/accounts";

// Create a client with a viem account
const account = privateKeyToAccount("0x...");
const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: account,
});

// Query data
const licenses = await client.license.getOwnedBy(client.address);
const epoch = await client.miner.getCurrentEpoch();

// Execute transactions (no message building required!)
const result = await client.delegateMining({
  licenseIds: [1, 2, 3], // accepts numbers, strings, or bigints
  operator: "0xOperator...", // accepts 0x or ault1 format
});

console.log(
  `TX Hash: ${result.txHash}, Confirmed: ${result.confirmed}, Success: ${result.success}`,
);

Creating a Client

The createClient() function accepts multiple signer types and auto-detects the format:

With viem

import { createClient, getNetworkConfig } from "ault-sdk-ts";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount("0x...");
const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: account,
});

With viem WalletClient (Browser)

import { createClient, getNetworkConfig } from "ault-sdk-ts";
import { createWalletClient, custom } from "viem";

const walletClient = createWalletClient({
  transport: custom(window.ethereum),
});

const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: walletClient,
});

With ethers.js

import { createClient, getNetworkConfig } from "ault-sdk-ts";
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("...");
const signer = new ethers.Wallet("0x...", provider);

const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: { type: "ethers", signer },
});

With MetaMask (EIP-1193)

import { createClient, getNetworkConfig } from "ault-sdk-ts";

const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });

const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: {
    type: "eip1193",
    provider: window.ethereum,
    address: accounts[0],
  },
});

With Privy

import { createClient, getNetworkConfig } from "ault-sdk-ts";
import { useSignTypedData, useWallets } from "@privy-io/react-auth";

const { signTypedData } = useSignTypedData();
const { wallets } = useWallets();

const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: {
    type: "privy",
    signTypedData,
    address: wallets[0]?.address,
  },
});

With Private Key (Server-side)

import { createClient, getNetworkConfig } from "ault-sdk-ts";

const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: { type: "privateKey", key: "0x..." },
});

Client Options

interface ClientOptions {
  network: NetworkConfig; // Required: network configuration
  signer: FlexibleSignerInput; // Required: wallet/signer
  signerAddress?: string; // Optional: override signer address
  fetchFn?: typeof fetch; // Optional: custom fetch
  fetchOptions?: FetchWithRetryOptions;
  defaultGasLimit?: string; // Optional: default gas limit
  defaultMemo?: string; // Optional: default memo
  defaultConfirmationTimeoutMs?: number;
  manifestOverride?: Manifest; // Optional: caller-supplied manifest layer (see "SDK Manifest")
}

SDK Manifest

createClient resolves network endpoints and gas/EIP-712 defaults from three layers, in increasing precedence:

  1. The embedded baseline shipped with the SDK (NetworkConfig plus GAS_CONSTANTS).
  2. The manifest fetched from <dexApiUrl>/api/v1/sdk-manifest at bootstrap and refreshed every five minutes in the background. The operator rotates URLs, EIP-712 fee defaults, and per-module gas constants by editing the dex-api SDK_MANIFEST_JSON env var; older SDK versions pick up the new values within one refresh window without a package bump.
  3. An optional manifestOverride you pass to createClient. The override sits on top of both other layers, is frozen for the lifetime of the client, and is not touched by background refreshes.

The override layer covers cases the fetched manifest cannot: pointing the client at a private RPC during local development, pinning a deterministic manifest in tests, surviving a URL rotation cutover by keeping the old endpoint until you redeploy, or tuning gas constants without waiting on a manifest deploy.

import { createClient, getNetworkConfig, type Manifest } from "ault-sdk-ts";

const manifestOverride: Manifest = {
  schemaVersion: 1,
  urls: {
    rpc: "https://rpc.internal.example.com",
  },
  gas: {
    LIMIT_ORDER: 250000,
  },
};

const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: account,
  manifestOverride,
});

The override is sparse — every field is optional and only the keys you set take effect. Disjoint keys still flow through from the fetched manifest, so {gas: {LIMIT_ORDER: 250000}} does not erase a MARKET_ORDER value the dex-api emits. Unknown top-level keys pass through untouched for forward compatibility with future manifest fields.

Supported fields

The Manifest type has four top-level keys. schemaVersion is required; the three sub-objects are optional and each one is itself sparse.

interface Manifest {
  schemaVersion: number; // required, integer >= 1
  urls?: Partial<ManifestUrls>;
  eip712?: Partial<ManifestEip712>;
  gas?: Partial<ManifestGas>;
  // unknown top-level keys are preserved for forward compatibility
}

urls — endpoint overrides. Each value is a string and applies only if non-empty. The dexApiUrl itself is not overridable here (it is the trust root for the fetched manifest).

| Key | Maps to NetworkConfig field | | ---------- | ----------------------------- | | rpc | rpcUrl | | rest | restUrl | | evm | evmRpcUrl | | explorer | explorerUrl | | dexWs | dexWsUrl |

URL rotations propagate to the tx executor on the next call but not to the REST/WS contexts captured at createClient time. Re-create the client to pick up new endpoints across the board.

eip712 — fee defaults applied to every transaction unless per-call options override them.

| Key | Type | Notes | | ----------- | -------------------------------- | ------------------------------ | | feeDenom | non-empty string | replaces GAS_CONSTANTS.DENOM | | feeAmount | non-negative integer string | raw aault, "0" allowed | | gasLimit | strictly positive integer string | gas units, "0" rejected |

gas — per-message-type gas constants and the fee-free multiplier. Every value is a positive integer. Keys outside the supported list are dropped silently and the embedded baseline keeps applying.

The keys most consumers will touch are the CLOB ones plus the fee-free multiplier:

| Key | Used by | | ------------------------- | ---------------------------------------- | | FEE_FREE_GAS_MULTIPLIER | fee-free gas computation across all msgs | | LIMIT_ORDER | placeLimitOrder | | MARKET_ORDER | placeMarketOrder | | SCALE_ORDER_BASE | placeScaleOrder (base cost) | | SCALE_ORDER_PER_SUB | placeScaleOrder (per sub-order) | | CANCEL_ORDER_BASE | cancelOrders (base cost) | | CANCEL_ORDER_PER_ID | cancelOrders (per order id) |

The schema also accepts per-message gas constants for the license, miner, agent, reward, and accountx modules (PER_LICENSE, PER_DELEGATE_MINING, APPROVE_AGENT, SET_REFERRAL_CODE, OPEN_ACCOUNT, and the rest). These exist for operator-side deploy-cycle bypass and are rarely needed in consumer code. The full list lives in ManifestGasSchema in src/core/manifest-types.ts.

The gas blob never accepts EIP712_FEE_AMOUNT, EIP712_GAS_LIMIT, or DENOM — those live on eip712 as feeAmount, gasLimit, and feeDenom. Smuggling them through gas is dropped defensively because the broadcast layer never reads them off the gas object.

The value validates against ManifestSchema synchronously at createClient time. A malformed override throws straight away with the schema error instead of falling back to the baseline the way a broken fetched manifest does, since override data comes from your code and a typo is a bug worth surfacing loudly. The caller's object is deep-cloned at construction, so mutating it after createClient returns does not affect resolver state.

When a non-empty override is present, the resolver emits one console.info line at boot listing the override key paths (never values, since urls may carry private endpoints).

If you also use client.attachAgent(...), the agent surface reuses the same manifest resolver, so the override flows through to agent-signed transactions without separate plumbing.

The package also re-exports the low-level helpers if you need them: mergeManifests, fetchManifest, applyNetworkOverrides, applyGasOverrides, createManifestResolver, createStaticResolver.

Query Methods

The client provides pass-through access to all REST API methods:

// License queries
const license = await client.license.getLicense("1");
const licenses = await client.license.getOwnedBy(client.address);
const balance = await client.license.getBalance(client.address);
const isApproved = await client.license.isApprovedMember(address);

// Miner queries
const epoch = await client.miner.getCurrentEpoch();
const operators = await client.miner.getOperators();
const delegation = await client.miner.getLicenseDelegation("123");
const emission = await client.miner.getEmissionInfo();

// Market and CLOB queries
const markets = await client.market.getMarkets();
const market = await client.market.getMarket(1);
const orders = await client.clob.getOrders({ orderer: client.address, marketId: 1 });

// Reward queries
const rewardParams = await client.reward.getParams();
const referralCode = await client.reward.getReferralCode(client.address);
const builderAllowances = await client.reward.getBuilderAllowances({ user: client.address });

// TokenRegistry queries
const tokenRegistryParams = await client.tokenregistry.getParams();
const tokenPair = await client.tokenregistry.getTokenPair(
  "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
);
const tokenPairs = await client.tokenregistry.getTokenPairs();

DEX API

The SDK includes a client for the Ault DEX API — a REST API for market data, order books, trades, balances, and UDF charting. This is a separate service from the on-chain Cosmos REST endpoints.

Market Data

import { createAultClient, getNetworkConfig } from "ault-sdk-ts";

const client = createAultClient({
  network: { ...getNetworkConfig("<CHAIN_ID>"), dexApiUrl: "<DEX_API_URL>" },
});
const dex = client.rest.dex;

// Markets and currencies
const markets = await dex.listMarkets();
const currencies = await dex.listCurrencies();

// Tickers
const ticker = await dex.getTicker("AULT/USDT");
const allTickers = await dex.getAllTickers();

// Order book
const orderbook = await dex.getOrderbook("AULT/USDT", { limit: 50 });

// Recent trades
const trades = await dex.listTrades("AULT/USDT", { limit: 100, since: 1773600000000 });

// Trading fees
const fees = await dex.getTradingFees();

// OHLCV candles
const candles = await dex.getOHLCV("AULT/USDT", {
  timeframe: "1h",
  limit: 100,
});

// Protocol stats (24h window)
const stats = await dex.getStats(); // defaults to { window: "24h" }
console.log(stats.totals.quoteVolume); // "1234567.89" or null when markets span multiple quotes
console.log(stats.totals.tradeCount, stats.totals.traderCount);
for (const m of stats.markets) {
  console.log(m.symbol, m.quoteVolume, m.tradeCount);
}

Monetary fields like quoteVolume, estimatedFee, and averageSlippageBps come back as canonical decimal strings, so parse them with a decimal library rather than parseFloat. totals.quoteVolume and its related fee fields are null when the markets that traded in the window span more than one quote asset, since summing notionals across quotes is meaningless. markets[] can briefly lag totals while the symbol map updates, so the per-market trade counts need not add up to totals.tradeCount.

User Queries

// Orders for an address
const orders = await dex.listOrders({
  address: "ault1...",
  status: "open",
  symbol: "AULT/USDT",
  limit: 50,
});

// Single order by ID
const order = await dex.getOrder("010000000000000000000002");

// User's trade history
const myTrades = await dex.listMyTrades({
  address: "ault1...",
  symbol: "AULT/USDT",
});

// Balances
const balances = await dex.getBalance({ address: "ault1..." });
console.log(balances.free); // { AULT: 100, USDT: 500 }
console.log(balances.total); // { AULT: 150, USDT: 500 }

UDF charting

All UDF (TradingView) charting endpoints are available on dex:

const config = await dex.getUDFConfig();
const serverTime = await dex.getUDFTime();
const symbolInfo = await dex.getUDFSymbol({ symbol: "AULT/USDT" });

const results = await dex.searchUDFSymbols({ query: "AULT", limit: 10 });

const history = await dex.getUDFHistory({
  symbol: "AULT/USDT",
  resolution: "60",
  from: 1773500000,
  to: 1773600000,
});

const quotes = await dex.getUDFQuotes({ symbols: "AULT/USDT,BTC/USDT" });
const marks = await dex.getUDFMarks({ symbol: "AULT/USDT", from: 1773500000, to: 1773600000 });
const tsMarks = await dex.getUDFTimescaleMarks({
  symbol: "AULT/USDT",
  from: 1773500000,
  to: 1773600000,
});

DEX WebSocket

The SDK ships a WebSocket client for streaming live data from the DEX API. It speaks the v2 subscription protocol: open one connection, subscribe to channels, and receive { channel, data } frames. The payload types are WebSocket-native (they differ from the REST shapes) and are exported from the SDK.

Connecting

import { createAultClient, getNetworkConfig } from "ault-sdk-ts";

const client = createAultClient({
  network: { ...getNetworkConfig("<CHAIN_ID>"), dexApiUrl: "<DEX_API_URL>" },
});

// The WS client is created lazily from dexApiUrl (http→ws, appends /ws)
await client.ws.connect();

Or use the standalone factory:

import { createDexWs } from "ault-sdk-ts";

const ws = createDexWs("wss://my-dex-api.example.com/ws");
await ws.connect();

Subscribing to Channels

Pass a subscription descriptor and the onData callback is typed from its type. Market channels need a coin; the candle channel also needs an interval. Frames flow through onData as they arrive. For snapshot-gated channels the first frame is the snapshot and the rest are deltas, so don't assume the first frame is a delta.

// L2 order book — snapshot first, then per-block deltas (data typed as WsBook)
const sub = client.ws.subscribe(
  { type: "l2Book", coin: "AULT/USDC" },
  {
    onData: (book) => {
      const [bids, asks] = book.levels;
      console.log(bids[0], asks[0]); // { px, sz, n }
    },
  },
);

// Trades in each block (data typed as WsTrade[])
client.ws.subscribe(
  { type: "trades", coin: "AULT/USDC" },
  { onData: (trades) => trades.forEach((t) => console.log(t.side, t.px, t.sz)) },
);

// Best bid / offer (data typed as WsBbo)
client.ws.subscribe(
  { type: "bbo", coin: "AULT/USDC" },
  { onData: (b) => console.log(b.bbo[0], b.bbo[1]) },
);

// Candles (data typed as WsCandle[])
client.ws.subscribe(
  { type: "candle", coin: "AULT/USDC", interval: "1m" },
  { onData: (bars) => console.log(bars[bars.length - 1]) },
);

// Unsubscribe
sub.unsubscribe();

Subscription Options

ttl sets a subscription lifetime in seconds (the server caps it at 3,600). The ack carries the effective, clamped value.

client.ws.subscribe(
  { type: "l2Book", coin: "AULT/USDC" },
  {
    onData: (data) => {
      /* ... */
    },
    onSubscribed: (ack) => console.log("subscribed; effective ttl", ack.ttl),
    onExpired: () => console.log("subscription expired"),
    onError: (err) => console.error(err),
  },
  { ttl: 300 },
);

User channels

User channels key on an address (ault1... or 0x..., either case) and expose read-only projections of public on-chain state, so no authentication is required. The address round-trips in the ack in the format you sent.

// Order updates (data typed as WsOrderUpdate[])
client.ws.subscribe(
  { type: "orderUpdates", user: "ault1..." },
  {
    onData: (orders) => orders.forEach((o) => console.log(o.order.oid, o.status, o.chainStatus)),
  },
);

// Spot balances — first frame is the full set, later frames are per-coin deltas
client.ws.subscribe(
  { type: "spotState", user: "ault1..." },
  { onData: (state) => console.log(state.spotState.balances) },
);

// Fills — the snapshot frame carries isSnapshot: true, live frames false
client.ws.subscribe(
  { type: "userFills", user: "ault1..." },
  { onData: (f) => console.log(f.isSnapshot, f.fills) },
);

// userEvents data arrives on the wire "user" channel; the SDK routes it back here
client.ws.subscribe({ type: "userEvents", user: "ault1..." }, { onData: (ev) => console.log(ev) });

Connection Events

client.ws.on("open", () => console.log("connected"));
client.ws.on("close", (code, reason) => console.log("closed", code, reason));
client.ws.on("error", (err) => console.error(err));
client.ws.on("reconnecting", (attempt) => console.log("reconnecting...", attempt));
client.ws.on("reconnected", () => console.log("reconnected"));

// Raw message listener
client.ws.on("message", (msg) => console.log(msg.channel, msg.data));

Reconnection

Auto-reconnect is enabled by default with exponential backoff. On reconnect, every active subscription is re-sent with its original TTL.

const ws = createDexWs("wss://my-dex-api.example.com/ws", {
  reconnect: true, // default: true
  reconnectDelay: 1000, // default: 1000ms
  maxReconnectDelay: 30000, // default: 30000ms
  maxReconnectAttempts: Infinity, // default: Infinity
});

Disconnecting

client.ws.disconnect();

Parallel Query Helpers

For analyzing large numbers of licenses efficiently, use the parallel query helpers:

// Analyze all licenses for an owner in one call (parallel fetching)
const analysis = await client.analyzeLicenses("ault1...");
console.log(`Total: ${analysis.total}`);
console.log(`Active: ${analysis.active}`);
console.log(`Delegated: ${analysis.delegated}`);

// Group delegations by operator
const byOperator = new Map<string, number>();
for (const d of analysis.delegations) {
  byOperator.set(d.operator, (byOperator.get(d.operator) ?? 0) + 1);
}

The analyzeLicenses() method fetches all data in parallel batches, making it much faster than sequential queries. For an address with 464 licenses, it completes in ~15-30 seconds instead of several minutes.

Individual Parallel Helpers

For more control, use the individual parallel helpers:

// Get all license IDs owned by an address (parallel batched)
const licenseIds = await client.getAllLicenseIds("ault1...");

// Get license details for multiple IDs in parallel
const licenses = await client.getLicenseDetailsParallel(licenseIds);

// Get delegation status for multiple IDs in parallel
const delegations = await client.getLicenseDelegationsParallel(licenseIds);

// Process results
for (const d of delegations) {
  if (d.isDelegated) {
    console.log(`License ${d.licenseId} delegated to ${d.operator}`);
  }
}

Transaction Methods

All transaction methods return a TxResult:

interface TxResult {
  txHash: string; // Transaction hash
  code: number; // Result code (0 = success)
  rawLog?: string; // Raw log from transaction
  confirmed?: boolean; // true = DeliverTx observed; false = broadcast-only or poll timeout; undefined = confirmation lookup unavailable
  success: boolean; // True only when DeliverTx success was confirmed
}

The CLOB order methods (placeLimitOrder, placeMarketOrder, placeScaleOrder, cancelOrders) default to broadcast-only: they return once CheckTx accepts the transaction, so the result has confirmed: false and success: false. Pass wait: true to poll for DeliverTx and get back the decoded order IDs. See Market And CLOB Transactions.

License Transactions

// Mint a license
await client.mintLicense({
  to: "0x...", // EVM or ault1 address
  uri: "https://example.com/metadata.json",
  reason: "Minted via SDK", // Optional, defaults to ""
});

// Batch mint
await client.batchMintLicense({
  recipients: [
    { to: "0xAddr1...", uri: "https://example.com/1.json" },
    { to: "0xAddr2...", uri: "https://example.com/2.json" },
  ],
});

// Transfer
await client.transferLicense({
  licenseId: 123, // bigint, number, or string
  to: "0x...",
});

// Burn/Revoke (admin only)
await client.burnLicense({ licenseId: 123 });
await client.revokeLicense({ licenseId: 123 });

// KYC management
await client.approveMember({ member: "0x..." });
await client.revokeMember({ member: "0x..." });
await client.batchApproveMember({ members: ["0x...", "0x..."] });

// Admin functions
await client.setMinters({ add: ["0x..."], remove: [] });
await client.setKYCApprovers({ add: ["0x..."], remove: [] });

Miner Transactions

import { base64ToBytes, MarketType } from "ault-sdk-ts";

// Delegate licenses to an operator
await client.delegateMining({
  licenseIds: [1, 2, 3], // bigint[], number[], or string[]
  operator: "0xOperator...",
});

// Cancel mining delegation
await client.cancelMiningDelegation({
  licenseIds: [1, 2, 3],
});

// Redelegate to a new operator
await client.redelegateMining({
  licenseIds: [1, 2, 3],
  newOperator: "0xNewOperator...",
});

// Set VRF key (Uint8Array)
await client.setOwnerVrfKey({
  vrfPubkey: base64ToBytes("base64..."),
  possessionProof: base64ToBytes("base64..."),
  nonce: 1n,
});

// Submit mining work (Uint8Array)
await client.submitWork({
  licenseId: 123,
  epoch: 456,
  y: base64ToBytes("base64..."),
  proof: base64ToBytes("base64..."),
});

// Batch submit work
await client.batchSubmitWork({
  submissions: [
    { licenseId: 1, epoch: 100, y: base64ToBytes("..."), proof: base64ToBytes("...") },
    { licenseId: 2, epoch: 100, y: base64ToBytes("..."), proof: base64ToBytes("...") },
  ],
});

// Operator management
await client.registerOperator({
  commissionRate: 5, // Percentage (0-100)
  commissionRecipient: "0x...", // Optional, defaults to signer
});
await client.unregisterOperator();
await client.updateOperatorInfo({
  newCommissionRate: 6,
  newCommissionRecipient: "0x...",
});

Market And CLOB Transactions

import { base64ToBytes } from "ault-sdk-ts";

const marketId = base64ToBytes("base64MarketId...");

// Place a limit order (broadcast-only: returns once CheckTx accepts it).
await client.placeLimitOrder({
  marketId,
  isBuy: true,
  price: "1.5",
  quantity: "100",
  lifespan: { seconds: 3600n, nanos: 0 }, // 1 hour
});

// Pass `wait: true` to poll for DeliverTx and read back the order ID.
const placed = await client.placeLimitOrder({
  marketId,
  isBuy: true,
  price: "1.5",
  quantity: "100",
  lifespan: { seconds: 3600n, nanos: 0 },
  wait: true,
});
console.log(placed.orderId);

// Place a market order
await client.placeMarketOrder({
  marketId,
  isBuy: true,
  deposit: "100",
});

// Cancel specific orders. With `wait: true` the result lists which IDs
// succeeded and which failed.
await client.cancelOrders({ orderIds: [base64ToBytes("base64OrderId...")] });

// Client order id (cloid): attach an 8-byte id to an order so you can address
// it without the chain-assigned order id. It is echoed back (as hex) on the
// wait-mode result, and cancelOrders accepts a cloid or an order id
// interchangeably.
const cloid = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
const withCloid = await client.placeLimitOrder({
  marketId,
  isBuy: true,
  price: "1.5",
  quantity: "100",
  deadline: new Date(Date.now() + 3600_000),
  cloid,
  wait: true,
});
console.log(withCloid.cloid); // "0102030405060708"

await client.cancelOrders({ orderIds: [cloid] });

// Look up an order by (orderer, cloid), chain-first with a dex-api fallback.
// Live orders resolve from the chain; filled/cancelled ones from the indexer.
const found = await client.findOrderByCloid(client.address, cloid);
if (found.source !== "none") console.log(found.source, found.order);

// Create a market
await client.createMarket({
  marketType: MarketType.MARKET_TYPE_SPOT,
  baseDenom: "uatom",
  quoteDenom: "uusdc",
  tickPrecision: 6,
});

Reward And TokenRegistry Transactions

// Create a referral code as an eligible referrer
await client.setReferralCode({
  code: "RW18A001",
});

// Approve a builder fee allowance
await client.approveBuilder({
  builder: "0x0000000000000000000000000000000000000001",
  maxFeeRate: "0.005",
});

// Register an ERC20 token pair
await client.registerTokenPair({
  erc20Address: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
  enabled: true,
});

// Toggle module-wide token conversions
await client.disableTokenRegistryTransfers();
await client.enableTokenRegistryTransfers();

Transaction Options

All transaction methods accept optional gasLimit and memo:

await client.delegateMining({
  licenseIds: [1, 2, 3],
  operator: "0x...",
  gasLimit: "300000", // Override default gas limit
  memo: "Delegating via SDK", // Custom memo
});

Agent-Signed Transactions

The chain's agent module lets a master account approve a separate keypair to submit a restricted set of CLOB transactions on its behalf, without prompting the master signer for each one. Typical use cases: market-making bots that sign hot orders with a short-lived key, or trading UIs that sign orders without a wallet popup per click.

The SDK exposes this through client.attachAgent(...) rather than a separate client. The agent surface reuses the master client's manifest resolver, REST contexts, and account-info cache — one network state, one of every cached resource, no chance of drift between wallet-signed and agent-signed paths.

Approve and attach an agent

import { createClient, encodeEthSecp256k1PubKey, deriveAgentAddress } from "ault-sdk-ts";
import { secp256k1 } from "@noble/curves/secp256k1";
import { hexToBytes } from "viem";

const client = await createClient({ network, signer: masterSigner });

// 1. Generate (or load) the agent keypair.
const agentPrivateKey = `0x${"a".repeat(64)}`; // load from secret store in real usage
const compressed = secp256k1.getPublicKey(hexToBytes(agentPrivateKey), true);
const agentPubkey = {
  typeUrl: "/cosmos.evm.crypto.v1.ethsecp256k1.PubKey",
  value: encodeEthSecp256k1PubKey(compressed),
};

// 2. Master approves the agent on-chain (wallet-signed).
await client.approveAgent({
  agentPubkey,
  name: "market-maker-bot",
  expiry: { seconds: 86_400n * 30n, nanos: 0 }, // 30 days
});

// 3. Attach the agent surface to this client.
const agent = client.attachAgent({ agentPrivateKey });
console.log(agent.address); // bech32 agent address
console.log(agent.masterAddress); // = client.address
console.log(client.asAgent === agent); // true

Place agent-signed orders

// Same shape as the wallet-signed equivalents on `client.*`, including the
// broadcast-only default and the `wait: true` opt-in for a result with the
// decoded order ID.
const placed = await client.asAgent!.placeLimitOrder({
  marketId,
  isBuy: true,
  price: "1.0",
  quantity: "1000000000000000000",
  lifespan: { seconds: 3600n, nanos: 0 },
  wait: true,
});
console.log(placed.orderId);

await client.asAgent!.placeMarketOrder({
  marketId,
  isBuy: true,
  deposit: "5000000000000000000",
});

await client.asAgent!.cancelOrders({ orderIds: [...] });

await client.asAgent!.placeScaleOrder({
  marketId,
  isBuy: true,
  lowPrice: "0.9",
  highPrice: "1.1",
  totalQuantity: "10000000000000000000",
  weights: [3333, 3334, 3333],
  lifespan: { seconds: 3600n, nanos: 0 },
});

Rotate or detach the agent

// Rotate: attachAgent replaces the prior agent in place. The previous
// surface is overwritten — read from `client.asAgent` after attaching.
client.attachAgent({ agentPrivateKey: newKey });

// Detach: clears `client.asAgent`. Idempotent.
client.detachAgent();

// Revoke on-chain (wallet-signed) when the agent should no longer sign.
await client.revokeAgent({ agentAddress: agent.address });

What attachAgent shares with the master client

| Resource | Behavior | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ManifestResolver | One per Client. The agent path reads getNetwork() / getGas() from the same resolver, so URL and EIP-712 manifest overrides reach both signing paths atomically. | | AccountCache | The master's accountNumber is fetched once. The agent path reads it from the shared cache; it never increments nextSequence (agent txs always sign with sequence=0). | | REST/WS contexts | The master client's client.<module> query APIs are the only query surface — the agent surface is tx-only. | | Lifecycle | client.dispose() clears asAgent and stops the manifest refresh timer. Calling attachAgent after dispose() throws. |

Notes

  • client.asAgent is null until attachAgent is called. After detach or dispose it is null again. Always read it fresh; do not cache references across rotations.
  • attachAgent is synchronous (no network I/O). The first agent tx may trigger one queryAccount RPC if the cache is cold; subsequent agent txs reuse the cached accountNumber.
  • Two concurrent agents from the same master require two Client instances — attachAgent replaces, it does not coexist.

Development

Generating Protos

Proto and EIP-712 generation expects both the Ault core repo and the Cosmos SDK repo to be checked out alongside this repo:

../ault/proto
../cosmos-sdk/proto

From this repo root, run:

pnpm gen:proto

Note: pnpm gen:eip712 also reads Cosmos SDK protos (staking/distribution) and requires ../cosmos-sdk/proto.

Error Handling

import { ApiError, NetworkError, TimeoutError } from "ault-sdk-ts";

try {
  const result = await client.mintLicense({ to: "0x...", uri: "..." });
  if (result.success) {
    console.log(`Transaction committed: ${result.txHash}`);
  } else if (result.confirmed === false) {
    console.warn(`Transaction broadcast but not confirmed yet: ${result.txHash}`);
  } else if (result.confirmed === undefined) {
    console.warn(
      `Transaction accepted by CheckTx, but this node cannot confirm it: ${result.txHash}`,
    );
  } else {
    console.error(`Transaction failed: ${result.rawLog}`);
  }
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`API Error (${error.status}): ${error.message}`);
  } else if (error instanceof NetworkError) {
    console.error(`Network Error: ${error.message}`);
  } else if (error instanceof TimeoutError) {
    console.error("Request timed out");
  }
}

Advanced Usage

For advanced use cases, you can access the low-level client:

// Access low-level client
const lowLevel = client._lowLevel;

// Build EIP-712 typed data manually
const typedData = lowLevel.eip712.buildTypedData(context, msgs);

// Use the msg namespace directly
import { msg } from "ault-sdk-ts";
import { privateKeyToAccount } from "viem/accounts";

const mySigner = privateKeyToAccount("0x...");

const delegateMsg = msg.miner.delegateMining({
  owner: "ault1...",
  operator: "ault1...",
  licenseIds: [1n, 2n, 3n],
});

// Sign and broadcast manually
const result = await lowLevel.eip712.signAndBroadcast({
  signer: mySigner, // provide the signer explicitly
  signerAddress: "ault1...",
  msgs: [delegateMsg],
});

Low-Level Client

For more control, use createAultClient() directly:

import { createAultClient, getNetworkConfig, msg, signAndBroadcastEip712 } from "ault-sdk-ts";

const client = createAultClient({
  network: getNetworkConfig("ault_10904-1"),
});

// Query
const licenses = await client.rest.license.getLicenses();

// Build and broadcast transactions manually
const result = await signAndBroadcastEip712({
  network: client.network,
  signer: mySigner,
  signerAddress: "ault1...",
  msgs: [
    msg.miner.delegateMining({
      owner: "ault1...",
      operator: "ault1...",
      licenseIds: [1n, 2n, 3n],
    }),
  ],
});

Network Configuration

import { getNetworkConfig, NETWORKS } from "ault-sdk-ts";

// Use predefined networks
const testnet = getNetworkConfig("ault_10904-1");
const localnet = getNetworkConfig("ault_30904-1");

// Or define custom network
const customNetwork = {
  name: "My Network",
  type: "testnet" as const,
  chainId: "ault_10904-1",
  evmChainId: 10904,
  rpcUrl: "https://my-rpc.example.com",
  restUrl: "https://my-rest.example.com",
  evmRpcUrl: "https://my-evm.example.com",
  dexApiUrl: "https://my-dex-api.example.com",
  dexWsUrl: "wss://my-dex-api.example.com/ws", // optional, derived from dexApiUrl if omitted
  isProduction: false,
};

Utility Functions

import {
  evmToAult,
  aultToEvm,
  isValidAultAddress,
  isValidEvmAddress,
  parseEvmChainIdFromCosmosChainId,
} from "ault-sdk-ts";

// Address conversion
const aultAddr = evmToAult("0x1234...abcd"); // => "ault1..."
const evmAddr = aultToEvm("ault1..."); // => "0x1234...abcd"

// Validation
isValidAultAddress("ault1..."); // true/false
isValidEvmAddress("0x1234..."); // true/false

// Chain ID
const evmChainId = parseEvmChainIdFromCosmosChainId("ault_10904-1"); // => 10904

Constants

import { GAS_CONSTANTS, TIMING_CONSTANTS } from "ault-sdk-ts";

GAS_CONSTANTS.EIP712_FEE_AMOUNT; // '5000000000000000'
GAS_CONSTANTS.EIP712_GAS_LIMIT; // '200000'
GAS_CONSTANTS.DENOM; // 'aault'
GAS_CONSTANTS.PER_LICENSE; // 200000

TIMING_CONSTANTS.API_TIMEOUT_MS; // 30000
TIMING_CONSTANTS.API_RETRY_DELAY_MS; // 1000
TIMING_CONSTANTS.API_MAX_BACKOFF_MS; // 30000

Runtime Compatibility

  • Node.js: Requires Node.js 18+ (for native fetch) or provide custom fetch
  • Bun: Full support
  • Browser: Full support in modern browsers

For older Node.js versions:

import fetch from "node-fetch";

const client = await createClient({
  network: getNetworkConfig("ault_10904-1"),
  signer: mySigner,
  fetchFn: fetch as unknown as typeof globalThis.fetch,
});

Releases

This project uses Semantic Versioning and publishes to npm via GitHub Actions.

Publishing a stable release

  1. Update CHANGELOG.md with the changes since the last release.

  2. Bump the version in package.json:

# Patch release (bug fixes): 0.0.2 -> 0.0.3
npm version patch --no-git-tag-version

# Minor release (new features): 0.0.2 -> 0.1.0
npm version minor --no-git-tag-version

# Major release (breaking changes): 0.0.2 -> 1.0.0
npm version major --no-git-tag-version
  1. Commit, tag, and push:
git add package.json CHANGELOG.md
git commit -m "chore: release v0.1.0"
git tag v0.1.0
git push origin master v0.1.0
  1. The Publish workflow runs automatically: validates the build, publishes to npm with provenance, and creates a GitHub Release with auto-generated notes.

Publishing a pre-release (alpha / beta / rc)

Pre-release tags publish to a separate npm dist-tag so they never become the default install.

# Set the pre-release version
git checkout master
git pull
git checkout -b "release/v0.1.0-alpha.1"
npm version 0.1.0-alpha.1 --no-git-tag-version
git add package.json
git commit -m "chore: release v0.1.0-alpha.1"
git push origin release/v0.1.0-alpha.1

# Create PR and merge to master

git checkout master
git pull
git tag v0.1.0-alpha.1
git push origin v0.1.0-alpha.1

Consumers install with the corresponding tag:

npm install ault-sdk-ts@alpha   # latest alpha
npm install ault-sdk-ts@beta    # latest beta
npm install ault-sdk-ts@rc      # latest release candidate

| Tag pattern | npm dist-tag | GitHub Release | | ---------------- | ------------ | -------------- | | v0.1.0 | latest | stable | | v0.1.0-alpha.* | alpha | pre-release | | v0.1.0-beta.* | beta | pre-release | | v0.1.0-rc.* | rc | pre-release |

CI checks

All PRs and pushes to master run:

  • Lint (oxlint)
  • Typecheck (tsc)
  • Unit tests (vitest)
  • Build validation
  • Package contents check (npm pack --dry-run)

PR conventions

  • PR titles should follow Conventional Commits (e.g. feat: add batch orders, fix: WebSocket reconnect). A bot will comment if the title doesn't match — this is a warning, not a blocker.
  • PRs with user-facing changes (feat:, fix:) should update CHANGELOG.md.

Repository setup

Required GitHub Actions secrets:

  • NPM_TOKEN — npm access token with publish rights for ault-sdk-ts

License

MIT