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

risex-client

v0.1.8

Published

[UNOFFICIAL - NOT PRODUCTION READY] TypeScript SDK for the RISEx perpetuals DEX on RISE Chain

Downloads

316

Readme

RISEx Client

Unofficial, not production ready. This is a community SDK and is not maintained or endorsed by the RISEx team. Use at your own risk.

TypeScript SDK for RISEx, a fully onchain CLOB perpetuals DEX on RISE Chain (Ethereum L2).

import { ExchangeClient } from 'risex-ts';

const client = new ExchangeClient({
  account: process.env.ACCOUNT_ADDRESS,
  signerKey: process.env.SIGNER_PRIVATE_KEY,
});

await client.init();

const order = await client.marketBuy(2, 1); // 1 step of ETH-PERP
console.log('Order:', order.order_id, 'tx:', order.tx_hash);

Install

npm install risex-ts

Requires Node 18+. No peer dependencies.

Setup

  1. Go to the RISEx web app and create an API signer key under Settings > API Keys
  2. Set your environment variables:
# Your wallet address
ACCOUNT_ADDRESS=0x...

# API signer private key (from the web app)
SIGNER_PRIVATE_KEY=0x...

Why use this?

  • Two clients, clean separationInfoClient for public reads, ExchangeClient for authenticated writes
  • Signing handled for you — EIP-712 order encoding, bitmap nonces, and permit signing all built in
  • Dual ESM/CJS — works in any Node.js project
  • Rate limiting included — token bucket (500 req/10s REST, 10 req/s WS) so you don't get throttled
  • WebSocket with auto-reconnect — orderbook, trades, and user data streams

Quickstart

Read-only: fetch markets and orderbook

import { InfoClient } from 'risex-ts';

const info = new InfoClient();

const markets = await info.getMarkets();
for (const m of markets) {
  console.log(`${m.display_name}: ${m.last_price}`);
}

const book = await info.getOrderbook(1); // BTC-PERP
console.log('Best bid:', book.bids[0]?.price);
console.log('Best ask:', book.asks[0]?.price);

Trading: place and close a position

import { ExchangeClient } from 'risex-ts';

const client = new ExchangeClient({
  account: process.env.ACCOUNT_ADDRESS,
  signerKey: process.env.SIGNER_PRIVATE_KEY,
});

// Required: fetches EIP-712 domain and contract addresses
await client.init();

// Place a market buy on ETH-PERP (market_id=2), 1 step = 0.001 ETH
const order = await client.marketBuy(2, 1);
console.log('Filled:', order.order_id, 'tx:', order.tx_hash);

// Check position
const pos = await client.info.getPosition(2, client.account);
if (pos) {
  console.log('Position:', pos.size, pos.side === 0 ? 'Long' : 'Short');
}

// Close it
await client.closePosition(2);

WebSocket: stream orderbook updates

import { WebSocketClient } from 'risex-ts';

const ws = new WebSocketClient();

ws.onChannel('orderbook', (msg) => {
  const data = msg.data as {
    bids?: Array<{ price: string; quantity: string }>;
    asks?: Array<{ price: string; quantity: string }>;
  };
  if (data.bids?.[0]) console.log('Top bid:', data.bids[0].price);
  if (data.asks?.[0]) console.log('Top ask:', data.asks[0].price);
});

await ws.connect();
ws.subscribe({ channel: 'orderbook', market_ids: [1] }); // BTC-PERP

// Channels: 'orderbook' | 'trades' | 'orders' | 'positions' | 'oracle' | 'ticker'

API

InfoClient

Public, read-only endpoints. No keys required.

const info = new InfoClient(options?)

| Option | Type | Default | |--------|------|---------| | baseUrl | string | https://api.testnet.rise.trade | | timeout | number | 30000 | | logLevel | 'debug' \| 'info' \| 'warn' \| 'error' \| 'none' | 'warn' |

These options are shared by ExchangeClient and WebSocketClient. The WebSocketClient also accepts wsUrl (default: wss://ws.testnet.rise.trade/ws).

Markets

| Method | Returns | |--------|---------| | getMarkets() | Market[] | | getOrderbook(marketId, limit?) | Orderbook | | getTradeHistory(marketId, limit?) | Trade[] | | getCandles(marketId, resolution, from?, to?) | Candle[] | | getFundingRateHistory(marketId, limit?) | FundingRate[] |

Account (read)

| Method | Returns | |--------|---------| | getBalance(account) | string | | getPosition(marketId, account) | Position \| null | | getAllPositions(account) | Position[] | | getOpenOrders(account, marketId?) | OpenOrder[] | | getOrderHistory(account, marketId?, limit?) | OrderHistoryEntry[] | | getAccountTradeHistory(account, marketId?, limit?) | Fill[] | | getFundingPaymentHistory(account, limit?) | FundingPayment[] | | getTransferHistory(account, limit?) | Transfer[] | | getRealizedPnl(account) | RealizedPnl | | getNonceState(account) | NonceState |

System

| Method | Returns | |--------|---------| | getSystemConfig() | SystemConfig | | getEip712Domain() | Eip712Domain | | getSessionKeyStatus(account, signer) | SessionKeyStatus | | listSigners(account) | SignerInfo[] |


ExchangeClient

Authenticated client for trading. Holds an InfoClient at client.info.

const client = new ExchangeClient({
  account: '0x...',    // your wallet address
  signerKey: '0x...',  // API signer private key (from web app)
})

await client.init()  // required before any authenticated call

| Option | Type | Required | Description | |--------|------|----------|-------------| | account | string | Yes* | Your wallet address | | signerKey | string | Yes | API signer private key | | accountKey | string | No | Wallet private key (only for programmatic signer registration) | | baseUrl | string | No | API base URL |

*Either account or accountKey must be provided. If accountKey is given, account is derived from it.

Properties:

  • client.account — wallet address
  • client.signer — signer address
  • client.info — the underlying InfoClient

Orders

| Method | Description | |--------|-------------| | placeOrder(params) | Place an order with full OrderParams. Returns { order_id, sc_order_id, tx_hash }. | | cancelOrder({ market_id, order_id }) | Cancel a specific order. | | cancelAllOrders(marketId?) | Cancel all open orders. Pass 0 or omit for all markets. |

Convenience methods

| Method | Description | |--------|-------------| | marketBuy(marketId, sizeSteps) | Market buy. Size in steps (integer). | | marketSell(marketId, sizeSteps, reduceOnly?) | Market sell. | | limitBuy(marketId, sizeSteps, priceTicks, postOnly?) | Limit buy. | | limitSell(marketId, sizeSteps, priceTicks, postOnly?) | Limit sell. | | closePosition(marketId) | Close entire position. Returns null if no position. |

Price and size use ticks/steps (compact integers), not WAD. Check market.config.step_price and market.config.step_size for the conversion factor.

Account management

| Method | Description | |--------|-------------| | deposit(amount) | Deposit USDC. Amount in plain decimal (e.g. "100"). Gas-sponsored. | | updateLeverage(marketId, leverage) | Set leverage (wad bigint, e.g. parseWad("10") for 10x). | | updateMarginMode(marketId, mode) | Set MarginMode.Cross or MarginMode.Isolated. | | updateIsolatedMargin(marketId, amount) | Add/remove isolated margin (positive to add, negative to remove). |

Auth

| Method | Description | |--------|-------------| | isSignerRegistered() | Check if signer is active. | | registerSigner(label?) | Register a signer on-chain. Requires accountKey. | | revokeSigner(address?) | Revoke a signer. Requires accountKey. |

registerSigner and revokeSigner require the accountKey option since they need the wallet's private key to sign the on-chain registration. Most users should create their signer via the RISEx web app instead.


WebSocketClient

Extends EventEmitter. Auto-reconnects with exponential backoff.

const ws = new WebSocketClient(options?)

await ws.connect()
ws.subscribe({ channel, market_ids?, account? })
ws.unsubscribe({ channel, market_ids?, account? })
ws.disconnect()

| Property / Method | Description | |-------------------|-------------| | ws.isConnected | Current connection state. | | ws.on('message', handler) | All messages. | | ws.on('open' \| 'close', handler) | Connection lifecycle. | | ws.on('error', handler) | Errors (safe — won't throw if no listener). | | ws.onChannel(channel, handler) | Messages for a specific channel only. | | ws.offChannel(channel, handler) | Remove a channel handler. | | WebSocketClient.orderbookChecksum(bids, asks) | Static CRC32 checksum for orderbook validation. |

Channels: 'orderbook' 'trades' 'orders' 'positions' 'oracle' 'ticker'


Enums

import { Side, OrderType, TimeInForce, StpMode, MarginMode } from 'risex-ts';

Side.Long         // 0
Side.Short        // 1
OrderType.Market  // 0
OrderType.Limit   // 1
TimeInForce.GoodTillCancelled  // 0
TimeInForce.ImmediateOrCancel  // 3
StpMode.None      // 3
MarginMode.Cross  // 0
MarginMode.Isolated // 1

Errors

import { RiseApiError, RiseRateLimitError } from 'risex-ts';

try {
  await client.marketBuy(2, 1);
} catch (err) {
  if (err instanceof RiseApiError) {
    console.log(err.status, err.path, err.message);
  }
  if (err instanceof RiseRateLimitError) {
    console.log('Retry after', err.retryAfterMs, 'ms');
  }
}

Advanced: signing primitives

For custom integrations, the signing internals are exported:

import {
  encodeOrder,
  encodeCancelOrder,
  encodeCancelAll,
  encodeLeverage,
  createPermitParams,
  createRegisterSignerSignatures,
  fixSignatureV,
  REGISTER_SIGNER_TYPES,
  VERIFY_WITNESS_TYPES,
} from 'risex-ts';

Defaults and sharp edges

  • init() is requiredExchangeClient will throw if you call authenticated methods before init(). It fetches the EIP-712 domain and contract addresses from the API.
  • Sizes use sizeSteps — integer steps, not decimals. Check market.config.step_size for the step-to-decimal conversion (e.g. step_size: "0.001" means 1 step = 0.001).
  • Prices use priceTicks — integer ticks. Check market.config.step_price for the tick-to-decimal conversion (e.g. step_price: "0.1" means 1 tick = $0.10).
  • Market orders use priceTicks: 0 — the matching engine ignores the price field for market orders.
  • Bitmap nonces — the SDK fetches nonce state automatically via GET /v1/nonce-state/{account}. You don't need to manage nonces manually.
  • Rate limiting is automatic — the client will wait (not throw) when the rate limit is approached. If you exhaust the bucket entirely, RiseRateLimitError is thrown.
  • Deposit amount is plain decimaldeposit('100') deposits 100 USDC.
  • All timestamps from the API are in nanoseconds unless documented otherwise.
  • WebSocket subscriptions use market_ids — pass an array of market IDs (e.g. [1, 2]). Omit to subscribe to all markets.
  • Orderbook prices are decimal strings — not WAD. e.g. "68750.5", not "68750500000000000000000".

Compatibility

| Runtime | Support | |---------|---------| | Node.js 18+ | Full (ESM and CJS) | | Node.js < 18 | Not supported (requires native fetch) | | Browsers | Not tested; signing works but ws dependency needs polyfill | | Bun / Deno | Should work (untested) |

Troubleshooting

ExchangeClient not initialized. Call init() first.

You called an authenticated method before await client.init(). Always init first:

const client = new ExchangeClient({ account, signerKey });
await client.init();

accountKey is required for this operation

registerSigner() and revokeSigner() need the wallet's private key. Either:

  • Create your signer via the RISEx web app (recommended), or
  • Pass accountKey in the constructor options

SignerNotAuthorized

The signer key isn't registered for this account. Create one via the RISEx web app, or call registerSigner() with accountKey provided.

Could not find router/orders_manager in system config

The API returned a system config without the expected contract addresses. Check your baseUrl.

API /v1/orders/place → 400: ...

Common causes:

  • Signer not registered for this account
  • Insufficient balance — check with client.info.getBalance(client.account)
  • Size below minimum — check market.config.min_order_size
  • Invalid market ID

Rate limit errors

The SDK handles rate limiting automatically by waiting. If you see RiseRateLimitError, you've exhausted the full budget (500 requests in 10 seconds). Back off and retry.

WebSocket won't connect

The default WS endpoint is wss://ws.testnet.rise.trade/ws. Pass a custom URL if needed:

const ws = new WebSocketClient({ wsUrl: 'wss://ws.risex.trade/ws' });

Contributing

git clone https://github.com/SmoothBot/risex-ts
cd risex-ts
npm install
npm test              # unit tests
npm run lint          # type-check
npm run build         # build ESM + CJS

Integration tests hit the testnet API:

RUN_INTEGRATION=true npm test

License

MIT