SDKs
Senticore maintains SDKs for TypeScript, Python, and Rust. The TypeScript SDK is published on npm; Python and Rust remain repository previews until their public registry releases are completed. Endpoint contracts can still change before public mainnet.
Live private-beta API base URL: https://api.sentico-labs.xyz.
Sandbox API base URL: https://api.sentico-sandbox.xyz.
The TypeScript SDK is public on npm as @sentico-labs/sdk. Point it at the
sandbox first - same routes, same signing, mock USDC, no collateral at risk. See
Sandbox (launching — see Status).
Packages
The npm release 0.3.0, published September 12, includes signed DMS
controls, order opt-in metadata (encodeBslSessionOrder({ dmsEpoch })),
client.trading.issuePrivateWsToken, and public V2 oracle reads. Use
client.trading.controlDms / controlBslDms for signed controls and
client.public.getMarketOracleV2 / getOracleFeedV2 for the
/api/v2/public/... oracle routes. getMarketOracle retains its V1 route;
getMarketOracleV2(id) answers 404 for a market without a V2 oracle profile
and does not silently fall back to V1. See DMS integration and
Oracle contracts. Rust remains a repository distribution.
| Language | Package name | Repository version | Registry status | Repo path |
|---|---|---|---|---|
| TypeScript | @sentico-labs/sdk | 0.4.0 | npm latest is 0.3.0 (published 2026-09-12) | sdks/typescript |
| Python | senticore-sdk | 0.1.0rc2 | Repository preview | sdks/python |
| Rust | senticore-sdk | 0.1.0-rc.2 | Repository preview | sdks/rust |
Live Beta Configuration
export SENTICORE_PUBLIC_HTTP_URL="https://api.sentico-labs.xyz"
export SENTICORE_TRADING_HTTP_URL="https://api.sentico-labs.xyz"
export SENTICORE_PUBLIC_WS_URL="wss://api.sentico-labs.xyz/api/v1/ws/public"
export SENTICORE_PRIVATE_WS_URL="wss://api.sentico-labs.xyz/api/v1/ws/private/{account}"
export SENTICORE_ORDER_ENTRY_HTTP_URL="https://api.sentico-labs.xyz"
export SENTICORE_ORDER_ENTRY_BINARY_PATH="/api/order-entry/binary"
export SENTICORE_ORDER_ENTRY_API_KEY="..." # optional provisioned lane key
export SENTICORE_BEARER_TOKEN="..."
export SENTICORE_BSL_TCP_HOST="bsl.sentico-labs.xyz"
export SENTICORE_BSL_TCP_PORT="9001"
export SENTICORE_BSL_TCP_TLS_SNI="bsl.sentico-labs.xyz"
export SENTICORE_FIX_HOST="fix.sentico-labs.xyz"
export SENTICORE_FIX_PORT="9878"
export SENTICORE_FIX_TLS_SNI="fix.sentico-labs.xyz"
Sandbox Configuration
The same variables, one host substituted. Every port is unchanged.
export SENTICORE_PUBLIC_HTTP_URL="https://api.sentico-sandbox.xyz"
export SENTICORE_TRADING_HTTP_URL="https://api.sentico-sandbox.xyz"
export SENTICORE_PUBLIC_WS_URL="wss://api.sentico-sandbox.xyz/api/v1/ws/public"
export SENTICORE_PRIVATE_WS_URL="wss://api.sentico-sandbox.xyz/api/v1/ws/private/{account}"
export SENTICORE_ORDER_ENTRY_HTTP_URL="https://api.sentico-sandbox.xyz"
export SENTICORE_ORDER_ENTRY_BINARY_PATH="/api/order-entry/binary"
export SENTICORE_BSL_TCP_HOST="bsl.sentico-sandbox.xyz"
export SENTICORE_BSL_TCP_PORT="9001"
export SENTICORE_BSL_TCP_TLS_SNI="bsl.sentico-sandbox.xyz"
export SENTICORE_FIX_HOST="fix.sentico-sandbox.xyz"
export SENTICORE_FIX_PORT="9878"
export SENTICORE_FIX_TLS_SNI="fix.sentico-sandbox.xyz"
Sandbox credentials are separate: an API key, HMAC credential or session key
issued in one environment fails closed in the other, and signed actions carry
sandbox chain id 421614 instead of 42161. Read the chain binding from the
connectivity bundle on the host you connected to rather than hardcoding either
value.
The SDK release that accompanies the sandbox adds an environment preset, so the whole block above collapses to one value:
const client = new SenticoreClient({ environment: "sandbox" });
Explicit per-field URLs still override the preset. The Python and Rust SDKs
expose the same switch as SenticoreConfig.for_environment("sandbox") and
SenticoreConfig::sandbox().
/api/order-entry/binary is the tested private-beta order-entry alias. The
direct compatibility route is /api/v1/mm/orders/batch.bin. The canonical BSL
route is /api/v1/bsl/orders/compact; use institutional_agent HMAC auth or a
provisioned lane key from the connectivity bundle.
TypeScript
The install below resolves npm latest (0.3.0). The linked repository
examples currently target the unreleased 0.4.0 source; clone the repository and
run them from sdks/typescript when following those examples exactly.
npm install @sentico-labs/sdk
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",
orderEntryApiKey: process.env.SENTICORE_ORDER_ENTRY_API_KEY,
bearerToken: process.env.SENTICORE_BEARER_TOKEN,
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();
const book = await client.public.getMarketOrderbook(1, { book: "YES", depth: 20 });
console.log(markets.data, book.requestId);
Create the client once per process or strategy shard and reuse it. The default
Node fetch keeps an internal connection pool; custom fetch implementations
should use a persistent agent or dispatcher. A quote loop must not build a new
SDK client or HTTP transport per order.
Capabilities
| Namespace | Use |
|---|---|
client.public | Anonymous market data reads. |
client.trading | Private reads and signed/delegated trading writes. |
client.orderEntry | BSL compact HTTP action-batch submission. |
client.mm | Deprecated alias for client.orderEntry. |
client.raw | Escape hatch for endpoints not promoted to stable helpers. |
client.ws | Public and private WebSocket helpers. |
The SDKs also expose BSL Direct TCP and FIX helper functions for institutional onboarding:
| Helper | Use |
|---|---|
encodeBslTcpHandshakeHello | Build the 48-byte Direct TCP HandshakeHello. |
decodeBslTcpHandshakeAck | Decode the 48-byte Direct TCP HandshakeAck. |
encodeBslOrderFromSignedAction | Build executable BSL Direct TCP order messages from a signed action and connectivity-bundle routing context. |
sessionKeyShadow option | Attach optional Phase-3 session-key shadow auth to wallet-signed Direct TCP orders without changing apply behavior. |
createSessionKey | Create a local Ed25519 session key for the BSL Direct TCP hot path. |
sessionDelegationTypedData | Build the EIP-712 one-time delegation for a session key. |
signSessionDelegationEip712 | Sign the EIP-712 session delegation with a local private key for agent/test flows. |
client.orderEntry.registerSessionKey | Register a wallet-authorized BSL session key. |
encodeBslSessionOrder | Build executable BSL Direct TCP messages using AuthSidecarV2 and Ed25519 session-key signing; pass dmsEpoch to opt the order into the dead-man switch (compact-frame flag bit 4, epoch in bytes 20–23). |
client.trading.controlDms / controlBslDms | Signed dead-man-switch controls (arm / heartbeat / status / disarm); the session-key policy must allow "cancel". |
client.trading.issuePrivateWsToken | POST /api/v1/ws/token for the private WebSocket; the credential needs read scope. |
client.public.getMarketOracleV2 / getOracleFeedV2 | Public Oracle V2 reads on /api/v2/public/...; 404 for markets without a V2 profile. |
encodeBslTcpCompactActionFrame | Add the 8-byte TCP message header around a 192-byte compact frame. |
client.orderEntry.getActionChainBinding | Fetch the production v2 action-signing domain once at startup. |
buildMachineAuthHeadersV2 | Build SC-* HMAC headers for custom transports. |
deriveFixSenderCompId | Build stable SenderCompID(49) from account, agent id, and scope. |
buildFixLogonFields | Build FIX 4.4 Logon fields with 553/554 HMAC credentials. |
encodeFixFrame | Add 8, 9, and 10 framing around ordered FIX fields. |
FixpSession | Build FIXP Negotiate/Establish/Sequence frames and classify inbound frames. |
client.orderEntry.getFixpSbeSchema | Fetch the published SBE XML schema. |
encodeFixpNewOrderSingle | Encode an SBE/SOFH NewOrderSingle for FIXP order entry. |
encodeFixpOrderCancelRequest | Encode FIXP cancel by OrderID or OrigClOrdID. |
encodeFixpOrderCancelReplaceRequest | Encode FIXP atomic cancel/replace. |
encodeFixpOrderMassCancelRequest | Encode FIXP account/market/side mass-cancel. |
encodeFixpOrderStatusRequest | Encode FIXP read-only order status requests. |
encodeFixpRetransmitRequest | Request replay for a missed FIXP server sequence range after gap or reconnect. |
decodeFixpFrame | Decode SOFH-framed FIXP/SBE control and execution-report messages. |
BSL Direct TCP
Read /api/v1/bsl/connectivity and use bslTcp. The native path is raw
TCP/TLS, not HTTPS:
cd sdks/typescript
npm ci
npm run build
SENTICORE_BSL_TCP_HOST=bsl.sentico-labs.xyz \
SENTICORE_BSL_TCP_PORT=9001 \
SENTICORE_BSL_TCP_TLS_SNI=bsl.sentico-labs.xyz \
node dist/examples/bsl-tcp-handshake.js
Use @sentico-labs/[email protected] or newer for live order encoding. The supported
Direct TCP production shapes are single place actions and
QuoteReplace/SpotQuoteReplace atomic groups. Standalone Cancel and
AmendOrder are also supported when you pass routingMarket, the market id of
the referenced order. The signed action still only carries order_id; the
routing market is a Direct TCP shard-routing hint and is rejected if it conflicts
with known order state.
For the lowest local signing cost, use BSL session keys instead of wallet ECDSA per order:
Roll out in this order: first keep encodeBslOrderFromSignedAction as the
authoritative path and pass sessionKeyShadow while
BSL_SESSION_KEY_AUTH_SHADOW_ENABLED=true; after shadow logs are clean, switch
the account to encodeBslSessionOrder. The operator can temporarily set
BSL_SESSION_KEY_CANARY_ACCOUNTS during rollout, but general access uses an
empty canary list plus an active registered session key.
BSL session-key cancelOnDisconnect is supported for Direct TCP session keys.
The SDK policy flag enables a session-key scoped sweep: the gateway reports TCP
teardown to the sequencer, and the sequencer cancels only live order ids tracked
for that session key.
import {
bslCompactRoutingFromConnectivityBundle,
createSessionKey,
encodeBslSessionOrder,
sessionDelegationTypedData,
signSessionDelegationEip712,
signWalletAdminAuth,
SESSION_DELEGATION_SCHEME_EIP712_V2,
WALLET_ADMIN_ACTION_REGISTER_SESSION_KEY
} from "@sentico-labs/sdk";
const bundle = await client.orderEntry.getConnectivityBundle();
const routing = bslCompactRoutingFromConnectivityBundle(bundle.data);
const sessionKey = createSessionKey();
// Wallet-admin auth for the register call (verified 2026-09-13): the registry
// binds the Sentico app to https://app.sentico-labs.xyz, so appName is "Sentico"
// and the request must carry `Origin: https://app.sentico-labs.xyz`.
// registerSessionKey sends no SC-* machine-auth headers (machineAuth: false);
// adding them yields 403.
const walletAuth = signWalletAdminAuth(
{
domain: "app.sentico-labs.xyz",
appName: "Sentico",
environment: "Arbitrum One",
chainId: 42161,
walletAddress: owner,
nonce: delegationNonce, // at least 8 characters, single-use
issuedAt: Date.now(),
expiresAt: Date.now() + 5 * 60_000 // at most 5 minutes ahead
},
WALLET_ADMIN_ACTION_REGISTER_SESSION_KEY,
ownerPrivateKey
);
const policy = {
allowedMarkets: [3],
// "cancel" is required if you will arm the dead-man switch (DMS_AUTH_REJECTED otherwise).
allowedActions: ["place", "spot_place", "cancel"],
maxOrderQtyLots: 1_000_000,
maxNotionalMicro: 250_000_000,
gatewayId: routing.gatewayId,
cancelOnDisconnect: false
};
const delegation = {
walletAuth,
chainBinding: routing.chainBinding,
accountIdHex: account,
sessionPublicKey: sessionKey.publicKey,
policy,
validFrom: Date.now() - 1000,
expiresAt: Date.now() + 60 * 60_000,
delegationNonce
};
const typedDelegation = sessionDelegationTypedData(delegation);
const delegationSignature =
walletClient
? await walletClient.signTypedData(typedDelegation)
: signSessionDelegationEip712(delegation, ownerPrivateKey);
const registered = await client.orderEntry.registerSessionKey(
{
...delegation,
algorithm: "ed25519",
delegationScheme: SESSION_DELEGATION_SCHEME_EIP712_V2,
delegationSignature
},
{ headers: { Origin: "https://app.sentico-labs.xyz" } }
);
const encoded = encodeBslSessionOrder(payload, {
...routing,
accountIdx: routing.accountIdx!,
sessionId,
gatewaySeq,
sessionPrivateKey: sessionKey.privateKey,
sessionKeyId: sessionKey.sessionKeyId,
sessionSeq,
policyHash: (registered.data as any).policyHash,
dmsEpoch // optional: opt this order into an armed dead-man switch
});
See BSL Session Keys for the exact policy fields, rollout flags, and revocation flow.
BSL Compact HTTP Order Entry
The SDK encodes:
{"version":1,"actions":[...],"idempotencyKey":"client-batch-1"}
and submits it as:
POST /api/order-entry/binary
Content-Type: application/x-senticore-order-entry-batch
X-BSL-Result-Mode: ack
X-Senticore-Response-Mode: detailed
X-Senticore-Order-Entry-Key: <optional provisioned lane key>
The order-entry key authorizes a dedicated lane and rate tier when assigned. It does not replace the signed action inside the batch.
After an ack response, use the SDK reconciliation helpers instead of a
bootstrap polling 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)
});
console.log(receipts.data.receipts, replay.data);
FIX Logon Example
After creating an institutional_agent, read
GET /api/v1/bsl/connectivity and use the returned CompIDs. The TypeScript SDK
contains a runnable Logon example:
cd sdks/typescript
npm ci
npm run build
SENTICORE_ACCOUNT=0x... \
SENTICORE_AGENT_ID=agt_... \
SENTICORE_API_KEY_ID=spk_... \
SENTICORE_API_SECRET=... \
SENTICORE_API_PASSPHRASE=... \
node dist/examples/fix-logon.js
For code that only needs CompIDs and a 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"
}));
FIXP / SBE
FIXP is the binary-FIX lane on the direct TCP/TLS host. Read
GET /api/v1/fixp/connectivity; the SBE XML is published at
GET /api/v1/fixp/schema and referenced as fixp.wire.schemaUrl.
import {
FixpSession,
FixpSide,
decodeFixpFrame,
encodeFixpNewOrderSingle
} from "@sentico-labs/sdk";
const session = new FixpSession({
apiKeyId,
apiSecret,
apiPassphrase,
account
});
socket.write(Buffer.concat(session.start()));
socket.write(encodeFixpNewOrderSingle({
clOrdId: "mm-1",
account,
marketId: 1,
price: 1_000_000,
orderQty: 1,
side: FixpSide.Bid
}));
const inbound = decodeFixpFrame(frameFromSocket);
For FIXP reconnect/recovery, create the next FixpSession with the same
sessionId and account credentials, complete Negotiate/Establish again, then
call session.encodeRetransmitRequest(fromSeqNo, count) for any missed server
sequence range. The server resumes only same-account retained sessions.
Python
The Python SDK is a repository preview (alpha, not yet on a public registry).
The example below requires the declared dependencies (for example httpx) to
be installed by pip during the editable install.
git clone https://github.com/sentico-labs/senticore.git
cd senticore/sdks/python
python -m pip install -e .
from senticore import SenticoreClient, SenticoreConfig
client = SenticoreClient(
SenticoreConfig(
public_http_base_url="https://api.sentico-labs.xyz",
trading_http_base_url="https://api.sentico-labs.xyz",
public_ws_url="wss://api.sentico-labs.xyz/api/v1/ws/public",
private_ws_url="wss://api.sentico-labs.xyz/api/v1/ws/private/{account}",
order_entry_http_base_url="https://api.sentico-labs.xyz",
order_entry_binary_path="/api/order-entry/binary",
order_entry_api_key="...",
)
)
Rust
[dependencies]
senticore-sdk = { path = "sdks/rust" }
use senticore_sdk::{SenticoreClient, SenticoreConfig};
let client = SenticoreClient::new(
SenticoreConfig::new("https://api.sentico-labs.xyz", "https://api.sentico-labs.xyz")
.with_order_entry_api_key("optional-lane-key"),
)?;
Market Maker Verification
Use the repository audit script before handing SDK config to a market maker:
node scripts/e2e/mm-docs-contract-audit.cjs