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

@sentico-labs/sdk

v0.1.4

Published

Institutional TypeScript SDK for Senticore HTTP, WebSocket, and Order Entry.

Readme

Senticore TypeScript SDK

Official TypeScript SDK for Senticore HTTP, WebSocket, and Institutional Order Entry.

Package name: @sentico-labs/sdk Current version: 0.1.1

Install

npm install @sentico-labs/[email protected]

Use 0.1.1 or newer for BSL Direct TCP order encoding. 0.1.0 includes the first high-level encoder; preview packages such as 0.1.0-preview.4 include the BSL TCP handshake/session helpers but not encodeBslOrderFromSignedAction.

For local development from the repository:

npm ci
npm run verify

Quick Start

import { SenticoreClient } from "@sentico-labs/sdk";

const client = new SenticoreClient({
  publicHttpBaseUrl: "https://api.sentico-labs.xyz",
  tradingHttpBaseUrl: "https://api.sentico-labs.xyz",
  publicWsUrl: "wss://api.sentico-labs.xyz/api/v1/ws/public",
  privateWsUrl: "wss://api.sentico-labs.xyz/api/v1/ws/private/{account}",
  orderEntryHttpBaseUrl: "https://api.sentico-labs.xyz",
  orderEntryBinaryPath: "/api/order-entry/binary",
  bearerToken: process.env.SENTICORE_BEARER_TOKEN,
  orderEntryApiKey: process.env.SENTICORE_ORDER_ENTRY_API_KEY,
  machineAuth: process.env.SENTICORE_API_KEY_ID
    ? {
        apiKeyId: process.env.SENTICORE_API_KEY_ID,
        apiSecret: process.env.SENTICORE_API_SECRET!,
        apiPassphrase: process.env.SENTICORE_API_PASSPHRASE!
      }
    : undefined
});

const markets = await client.public.listMarkets();
console.log(markets.data, markets.requestId, markets.rateLimit);

Transport Reuse

Create one SenticoreClient per process or strategy shard and reuse it for the whole session. Do not construct a new client or HTTP transport for every quote. Node's built-in fetch keeps an internal connection pool; if you pass config.fetch, use a fetch implementation backed by a persistent agent or dispatcher.

Client Planes

  • client.public: anonymous public HTTP reads.
  • client.trading: authenticated private reads and trading writes.
  • client.orderEntry: Institutional Order Entry.
  • client.mm: deprecated alias for client.orderEntry.
  • client.raw: escape hatch for endpoints not promoted to stable helpers yet.
  • client.ws: public and private WebSocket helpers.
  • Direct BSL TCP helpers: BslTcpSession, bslCompactRoutingFromConnectivityBundle, encodeBslOrderFromSignedAction, encodeBslTcpHandshakeHello, decodeBslTcpHandshakeAck, plus low-level frame wrappers.
  • FIX helpers: deriveFixSenderCompId, buildFixLogonFields, encodeFixFrame.
  • FIXP/SBE helpers: FixpSession, encodeFixpNegotiate, encodeFixpEstablish, encodeFixpNewOrderSingle, decodeFixpFrame, FIXP_SBE_SCHEMA_URL.

Legacy SenticoreHttpClient and SenticoreWsClient exports are still present for existing integrations. New integrations should use SenticoreClient.

Public Market Data

const markets = await client.public.listMarkets();
const book = await client.public.getMarketOrderbook(1, { book: "YES", depth: 20 });
const trades = await client.public.listMarketTrades(1, { limit: 50 });

Trading HTTP

import {
  actionSigningHashHex,
  deriveOrderIdHex,
  signAction,
  type LocalActionPayload
} from "@sentico-labs/sdk";

const account = "0x00000000000000000000000000000000000000d3" as const;
const privateKey = process.env.SENTICORE_PRIVATE_KEY!;

const payload: LocalActionPayload = {
  account,
  nonce: 4810,
  ts: Date.now(),
  action: {
    kind: "SpotPlaceOrder",
    market: 7,
    side: "Bid",
    price: 998400,
    qty: 1000,
    timeInForce: "post_only"
  }
};

// Fetch once at startup and cache. Do not call /actions/hash per order.
const chainBinding = (await client.orderEntry.getActionChainBinding()).data;
const signedAction = signAction(payload, privateKey, { chainBinding });

const accepted = await client.trading.submitSignedAction(signedAction, {
  idempotencyKey: "client-order-1"
});

console.log({
  signingHash: actionSigningHashHex(payload, { chainBinding }),
  derivedOrderId: deriveOrderIdHex(payload),
  accepted: accepted.data
});

If you use nonce reservations, include the returned token in the payload before signing:

const reserved = await client.trading.reserveNonce(account, {
  count: 1,
  ttlMs: 30_000,
  idempotencyKey: "nonce-1"
});

const reservedPayload: LocalActionPayload = {
  account,
  nonce: Number((reserved.data as { startNonce: number }).startNonce),
  nonceReservationId: (reserved.data as { reservationId: string }).reservationId,
  ts: Date.now(),
  action: {
    kind: "SpotPlaceOrder",
    market: 7,
    side: "Bid",
    price: 998400,
    qty: 1000,
    timeInForce: "post_only"
  }
};

Institutional Order Entry

The native BSL path for latency-sensitive market makers is Direct TCP/TLS. Read GET /api/v1/bsl/connectivity first and use its bslTcp.host, bslTcp.port, bslTcp.tlsSni, frame version, and message-kind values. This is not an HTTPS URL and should not be sent through Cloudflare.

The SDK includes a session state machine plus a high-level order encoder. Use the connectivity bundle once at startup to extract the chain-binding domain, compact account index, compact market mapping, and BSL gateway id:

import {
  bslCompactRoutingFromConnectivityBundle,
  encodeBslOrderFromSignedAction,
  signAction,
  type LocalActionPayload
} from "@sentico-labs/sdk";

const bundle = await client.orderEntry.getConnectivityBundle();
const routing = bslCompactRoutingFromConnectivityBundle(bundle.data);
if (routing.accountIdx === undefined) {
  throw new Error("compactAccountIdx is not ready for this account");
}

let gatewaySeq = 1n;
const sessionId = BigInt(Date.now()) * 1_000_000n;
const payload: LocalActionPayload = {
  account,
  nonce,
  ts: Date.now(),
  action: {
    kind: "SpotPlaceOrder",
    market: 7,
    side: "Bid",
    price: 998400,
    qty: 1000,
    timeInForce: "post_only"
  }
};

const signedAction = signAction(payload, privateKey, {
  chainBinding: routing.chainBinding
});

const encoded = encodeBslOrderFromSignedAction(signedAction, {
  ...routing,
  accountIdx: routing.accountIdx,
  sessionId,
  gatewaySeq
});

for (const message of encoded.tcpMessages) {
  socket.write(message);
}
gatewaySeq = encoded.nextGatewaySeq;

encoded.tcpMessages is ready to write to the TLS socket. Single place actions produce AuthSidecar then CompactActionFrame; QuoteReplace and SpotQuoteReplace actions produce one CompactFrameGroup.

Use QuoteReplace/SpotQuoteReplace for production cancel/replace loops over Direct TCP. Standalone Cancel and AmendOrder actions only carry order_id, while the live Direct TCP gateway routes compact frames by market index. The SDK therefore rejects standalone cancel/amend encoding by default; pass allowStandaloneCancelAmend: true only for diagnostics against a gateway that explicitly supports order-id based routing. For production standalone cancel/amend, use HTTP, FIX 4.4, or FIXP/SBE.

If the gateway returns a sequenced reject like {"type":"reject","reason":202,"detail":"backpressure"}, the frame decoded and reached the Direct TCP lane, but that submit attempt was not accepted/applied. Back off, widen, refresh state, and reconcile open orders before resubmitting; do not blindly retry the same signed action.

The low-level wrappers encodeBslTcpAuthSidecar, encodeBslTcpCompactActionFrame, and encodeBslTcpCompactFrameGroup only wrap already prepared bytes with the BSL TCP kind,len header. They do not build the 192-byte compact frame or the cold AuthSidecar.

Session-Key Fast Lane (Ed25519)

Per-order wallet ECDSA signing costs ~1.8 ms client-side. The session-key lane replaces it with a one-time EIP-712 wallet delegation to a locally generated Ed25519 key, then ~0.05 ms Ed25519 signs per order:

import {
  SESSION_DELEGATION_SCHEME_EIP712_V2,
  WALLET_ADMIN_ACTION_REGISTER_SESSION_KEY,
  createSessionKey,
  encodeBslSessionOrder,
  signSessionDelegationEip712,
  signWalletAdminAuth
} from "@sentico-labs/sdk";

// 1) Local Ed25519 session key. sessionKeyId is blake3-derived and must match
//    the venue's registry id (asserted by the SDK tests).
const sessionKey = createSessionKey();

// 2) One-time registration: EIP-712 SessionDelegationV2 signed by the owner
//    wallet, wrapped in a short-lived typed wallet-admin auth.
const delegationSignature = signSessionDelegationEip712(delegationInput, walletKey);
const walletAuth = signWalletAdminAuth(adminInput, WALLET_ADMIN_ACTION_REGISTER_SESSION_KEY, walletKey);
const registered = await client.orderEntry.registerSessionKey({
  walletAuth,
  accountIdHex, // 32-byte trading account id
  sessionPublicKey: sessionKey.publicKey,
  policy: { allowedMarkets: [7], allowedActions: ["spot_place", "spot_quote_replace"] },
  validFrom: Date.now(),
  expiresAt: Date.now() + 12 * 60 * 60 * 1000,
  delegationNonce: "unique-nonce-1",
  delegationScheme: SESSION_DELEGATION_SCHEME_EIP712_V2,
  delegationSignature
});

// 3) Per order: no wallet key involved. sessionSeq is strictly increasing per
//    session key (persist it across restarts like a FIX MsgSeqNum).
const encoded = encodeBslSessionOrder(payload, {
  ...routing,
  accountIdx: routing.accountIdx,
  sessionId,
  gatewaySeq,
  sessionPrivateKey: sessionKey.privateKey,
  sessionKeyId: sessionKey.sessionKeyId,
  sessionSeq,
  policyHash: (registered.data as { policyHash: `0x${string}` }).policyHash
});
for (const message of encoded.tcpMessages) socket.write(message);

Sign the delegation and register with the same policy object; the SDK normalizes it identically on both sides (normalizedSessionKeyPolicy), and the venue recomputes the policy hash from the registered JSON. Always put the venue-returned policyHash into per-order options. The wallet keeps custody: delegation scope/lifetime is policy-bounded (24 h max), keys are revocable via client.orderEntry.revokeSessionKey, and the venue enforces market/action/qty policy plus a per-key replay window before persist.

Runnable end-to-end example (env vars documented in the file header):

npm run build && node dist/examples/bsl-tcp-session-key.js

Phase-3 shadow validation is also supported: keep wallet ECDSA as the apply authority and attach sessionKeyShadow to encodeBslOrderFromSignedAction so the sequencer records whether the same frame would pass session-key auth.

Runnable handshake example:

SENTICORE_BSL_TCP_HOST=bsl.sentico-labs.xyz \
SENTICORE_BSL_TCP_PORT=9001 \
SENTICORE_BSL_TCP_TLS_SNI=bsl.sentico-labs.xyz \
npm run build && node dist/examples/bsl-tcp-handshake.js

The HTTP compact order-entry path remains available for compatibility and backfill-friendly integration. It uses compact JSON bytes on the wire:

{"version":1,"actions":[...],"idempotencyKey":"..."}

The SDK sends this payload with Content-Type: application/x-senticore-order-entry-batch and X-Senticore-Order-Entry-Key.

The canonical private-beta HTTP endpoint is POST /api/order-entry/binary. The live edge maps that alias to the trading plane's /api/v1/mm/orders/batch.bin handler. If you connect directly to a trading-plane service without the edge alias, set orderEntryBinaryPath: "/api/v1/mm/orders/batch.bin".

const response = await client.orderEntry.submitActions([signedAction], {
  idempotencyKey: "order-entry-batch-1",
  responseMode: "summary"
});

console.log(response.data.ok, response.data.seqs, response.data.ackMode);

Low-latency clients that already produce venue payload bytes can call:

await client.orderEntry.submitEncoded(encodedPayload, {
  idempotencyKey: "order-entry-raw-1"
});

Reconcile ack responses through receipts or execution gap-fill instead of polling bootstrap in a hot loop:

const receipts = await client.orderEntry.getActionReceipts(account, response.data.seqs, {
  waitMs: 250
});

const replay = await client.orderEntry.getAccountExecutions(account, {
  fromSeq: response.data.lastSeq ?? response.data.seqs.at(-1)
});

FIX Logon

Create an institutional_agent HMAC credential, then read GET /api/v1/bsl/connectivity for the direct FIX endpoint and CompIDs. The SDK includes helpers for the Logon frame:

import {
  buildFixLogonFields,
  deriveFixSenderCompId,
  encodeFixFrame
} from "@sentico-labs/sdk";

const senderCompId = deriveFixSenderCompId(account, agentId, "order_entry");
const frame = encodeFixFrame(buildFixLogonFields({
  account,
  apiKeyId,
  apiSecret,
  apiPassphrase,
  senderCompId,
  targetCompId: "SENTICORE",
  targetSubId: "order_entry"
}));

Runnable example:

SENTICORE_ACCOUNT=0x... \
SENTICORE_AGENT_ID=agt_... \
SENTICORE_API_KEY_ID=spk_... \
SENTICORE_API_SECRET=... \
SENTICORE_API_PASSPHRASE=... \
npm run build && node dist/examples/fix-logon.js

FIXP / SBE

FIXP is raw TCP/TLS on the direct FIXP host, not HTTPS. Read GET /api/v1/fixp/connectivity; the published SBE schema is available at GET /api/v1/fixp/schema and in the bundle as fixp.wire.schemaUrl.

The SDK exposes the wire codec and a small sans-I/O session helper:

import {
  FixpSession,
  FixpSide,
  decodeFixpFrame,
  encodeFixpNewOrderSingle
} from "@sentico-labs/sdk";

const session = new FixpSession({
  apiKeyId,
  apiSecret,
  apiPassphrase,
  account,
});

socket.write(Buffer.concat(session.start())); // Negotiate + Establish

socket.write(encodeFixpNewOrderSingle({
  clOrdId: "mm-1",
  account,
  marketId: 1,
  price: 1_000_000,
  orderQty: 1,
  side: FixpSide.Bid,
}));

const message = decodeFixpFrame(frameFromSocket);

WebSocket

const socket = client.ws.connectPublic((frame) => {
  console.log(frame);
});

socket.addEventListener("open", () => {
  client.ws.subscribe(socket, {
    type: "l2Book",
    marketId: 1,
    depth: 20
  });
});

Private user streams:

const token = await client.trading.issuePrivateWsToken(account, {
  ttlMs: 60_000
});

const socket = client.ws.connectPrivate(account, token.data.token, (frame) => {
  console.log(frame);
});

Replay gaps raise ReplayGapError during frame parsing. Production clients should resync from an HTTP snapshot and resubscribe from a known sequence.

Error Handling

import { RateLimitError, SenticoreApiError } from "@sentico-labs/sdk";

try {
  await client.public.listMarkets();
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log("retry after", error.retryAfter);
  } else if (error instanceof SenticoreApiError) {
    console.log(error.status, error.requestId, error.payload);
  }
}

Release Compatibility

0.1.1 is the recommended public npm release for BSL Direct TCP order encoding. It is still a pre-1.0 SDK, so minor releases may refine beta-only surfaces before public mainnet. The stable namespace split is public, trading, orderEntry, raw, and ws. mm remains as a deprecated compatibility alias.