TypeScript & Rust
Both official SDKs validate the same versioned Strata contract. Money values cross the boundary as decimal strings in atomic units, so language-level number rounding cannot silently change an amount.
Review the source in the TypeScript SDK and Rust SDK repositories.
TypeScript
Install:
npm install @stratabook/sdk
Request a Sonar quote:
import { StrataClient } from "@stratabook/sdk";
const strata = new StrataClient();
const quote = await strata.quote({
market: "SOL/USDC",
side: "sell",
amountInAtoms: 10_000_000n,
});
console.log({
output: quote.amount_out_atoms,
minimum: quote.minimum_output_atoms,
fee: quote.output_fee_atoms,
expiresAt: quote.expires_at_ms,
});
amount_out_atoms is user-net. Gross pre-fee output is BigInt(amount_out_atoms) + BigInt(output_fee_atoms).
Pass amountOutAtoms instead of amountInAtoms to fix the output ("buy 1 SOL"): Strata inverts its best route at quote time and amount_in_atoms in the response is the input that delivers it — nothing added. maximumToleranceBps means what it always means: your lower floor, so minimum_output_atoms is the requested amount lowered by it (zero by default → the requested amount or the execution fails closed and you re-quote). Rust exposes the same choice through QuoteRequest { amount_in_atoms, amount_out_atoms, .. }. Either quote executes through the same quote_id flow.
The package supports Node 20+ and modern browsers with no runtime dependencies.
For market making, both SDKs expose the same high-level contract: market label, strand or current, spread in bps, exact decimal base size such as 0.01 SOL, optional duration and level count, and an external transaction signer. TypeScript uses marketMaking.start(...) / stop(...); Rust uses platform_maker_start(...) / platform_maker_stop(...). Both resolve IDs and decimals, derive arrays and expiry from the fresh Strata mark and tick grid, verify the exact transaction before signing, submit idempotently, and return only after chain-derived maker state matches. Split prepare/sign/submit helpers support browser wallets and MCP without accepting private keys.
Rust
Add the SDK:
cargo add strata-sdk strata-public-contract
Request the same quote:
use strata_public_contract::{QuoteRequest, QuoteSide, DEFAULT_MAXIMUM_TOLERANCE_BPS};
use strata_sdk::StrataClient;
let strata = StrataClient::production()?;
let quote = strata.quote(QuoteRequest {
market_id: "SOL/USDC".into(),
side: QuoteSide::Sell,
amount_in_atoms: Some("10000000".into()),
amount_out_atoms: None,
maximum_tolerance_bps: DEFAULT_MAXIMUM_TOLERANCE_BPS,
}).await?;
println!("{} atoms", quote.amount_out_atoms);
Complete platform surface
Both official SDKs expose the same live product graph: capability and workflow discovery, assets and markets, the Strata book, best prices, fees, status, trades, candles, marks, execution receipts, TWAP controls and history, the whole account in one public read by wallet (balances, positions, open orders, recent fills — account.read / platform_account), portfolio history, the complete fleet-wide Points program, legacy rewards, referrals, bugs, and resting-order controls. Public adapters use opaque Strata identities; they expose no private implementation details.
In TypeScript, these operations are grouped under StrataPlatformClient modules such as discovery, books, marketData, account, points, algos, and orders. Rust exposes the same typed operations as platform_* methods and uses connect_market_data(...), connect_account(...), connect_maker(...), and connect_order_commands(...) for the four live stream classes.
Rust market and account streams validate every contract version and request identity. Book changes and private account events must have a contiguous previous sequence; a gap fails closed so the caller can reconnect and replace state from a newly authenticated snapshot. The account signer is used only for the exact server challenge and is not retained by the SDK.
Tolerance is not price impact
Two numbers on every quote are easy to mix up and are unrelated:
price_impact_pctis measured: how far the quoted fills' average price
sits from reference_price, the best price before your order. It comes from the book. It is not a setting.
maximum_tolerance_bpsis yours: the most you accept below the quoted
output, 0 by default (the quoted output exactly). It is applied in minimum_output_atoms and echoed back on the quote.
A quote can show 0 impact with 25 bps of tolerance, or 40 bps of impact with 0 tolerance. Choosing a tolerance never changes the fills; it only sets the floor below which execution fails closed.
TypeScript takes maximumToleranceBps (slippageBps is the legacy name); Rust takes maximum_tolerance_bps.
What the clients validate
Before returning a quote, both SDKs enforce contract compatibility, market and request binding, exact atomic fields, quote lifetime, labelled fees, and internally consistent minimum output.
Unknown response fields are rejected. A public contract change therefore fails visibly instead of being interpreted loosely.
Authentication and execution
Both SDKs use the same Vault-session model as the Strata app. The owner wallet or external agent owner configures signer authority; the private key is never sent to Strata and remains inside the owner's runtime or keystore.
The live action graph is available through actionGraph() in TypeScript and action_graph() in Rust. When prepare and submit nodes are available, an application can execute an unexpired quote with executeQuote in TypeScript or execute_quote in Rust, or drive prepare and submit as separate primitives (challenge remains for the two-step path). One signature per trade: the quote binding itself is prepared, the SDK checks minimum_output_atoms and the echoed bindings, runs the transaction verifier (built-in unless the owner supplies a stricter one), and only then invokes the session signer for the one transaction signature. The signed transaction is submitted with an idempotency key.
Owner actions (Vault setup, deposit, withdrawal, session, policy, pause) follow the same shape: prepare returns the exact transaction with a preparation_id and sponsored flag, the owner wallet signs it, and vault.submit / platform_vault_submit hands it back — Strata pays the fee and any rent when sponsored, broadcasts, and vault.submission reports the durable outcome. The owner never needs SOL or an RPC endpoint. Owners with SOL pay their own fee; for owners without it Strata pays and recovers exactly what it spent from their deposits (network_cost_atoms, at most 1% of a deposit).
For latency-sensitive resting orders, TypeScript opens one authenticated socket:
const platform = new StrataPlatformClient();
const commands = await platform.orders.connect(marketId, ownerWallet, signer, {
onStatus: (status) => console.log("chain status", status.status),
});
await commands.ready;
const receipt = await commands.execute({
operation: {
action: "place",
ownerWallet,
clientOrderId: "agent-42",
side: "buy",
orderType: "post_only",
limitPriceAtoms: 150_000_000n,
sizeAtoms: 1_000_000n,
},
verifyTransaction,
});
const deadMan = await commands.armDeadMan({
timeoutMs: 5_000,
verifyTransaction,
});
receipt proves RPC broadcast; onStatus delivers terminal chain state. The order above uses normal placement with no proactive self-trade cancellation. To opt in, pass selfTradePrevention: "cancel_taker", "cancel_maker", "cancel_both", or "skip_own_liquidity". The dead-man helper heartbeats automatically and fails closed if the process or connection disappears. Call disarmDeadMan() only as an explicit shutdown decision. After reconnect, deadManStatus() retrieves the durable terminal state and cancellation signature without changing the ticket.
Rust uses connect_order_commands(...). Its cloneable OrderCommandStream serializes concurrent callers through one writer actor, broadcasts every sequenced event, and provides execute_order(...). For unattended long-lived exposure, maintain_dead_man(...) refreshes both heartbeat and blockhash and returns a DeadManGuard whose drop stops heartbeats without disarming. dead_man_status() reads the durable ticket after reconnect.
Both official SDKs automatically send bounded command batches and consume bounded event batches on the persistent WebSocket. Every command and event still carries an independently validated contiguous sequence and request ID. They negotiate compact result batches that carry shared stream metadata once; the SDK reconstructs and validates each independently sequenced event before delivering it to agent code. Clients that do not negotiate this retain the complete-event frame shape. Agents use the same command methods at any concurrency; frame batching never changes signing, ordering, idempotency, or receipt semantics.
Before release, TypeScript's certifyPlatformOrderCommandSlo(...) can create multiple authenticated connections and emit a JSON certificate from a dedicated non-trading probe. The probe only echoes a caller nonce; it cannot prepare, sign, submit, cancel, or otherwise mutate an order. The production profile checks authentication p99, command p50/p95/p99 at one outstanding command per connection, at least 25,000 commands/second in a separate saturated phase, zero sequence faults, and error rate. Separating controlled-load latency from saturation prevents queue depth from being mislabeled as execution latency while still failing releases that lack capacity. The packaged terminal runs the same probe with an ephemeral in-memory session key and never signs or submits a trade:
npx -y @stratabook/sdk order-slo \
--market-id market_... \
--owner-wallet OWNER_PUBLIC_KEY \
--json
Resting orders use the same boundary. TypeScript exposes orders.execute(...) plus orders.prepare, orders.submit, and orders.status for durable timeout recovery (orders.challenge remains for the two-step path). Rust exposes execute_order(...) plus order_prepare, order_submit, and order_status (order_challenge likewise). One signature per order: the high-level helpers send the operation itself to prepare, decode the returned transaction and require it to place or cancel exactly the requested orders on that market — session co-signing only delegated instructions, never paying, owner never signing — and only then ask the session for its one transaction signature. The account sequence is optional: omit it and Strata resolves the next one from the Vault's confirmed market account; supply it to pin a locally tracked sequence. An owner may replace the built-in verifier (verifyOrderTransaction / DefaultTransactionVerifier) with a stricter one. A Vault's first order in a market needs no separate step: that transaction also creates the Vault's market account with Strata-sponsored rent, so the verifier accepts one fee-payer rent transfer to that account ahead of the first order only.
The SDK interfaces accept signer and verifier implementations, not private key bytes. This lets browser Vault sessions, hardware-backed stores, and server keystores use the same contract without teaching Strata how their keys are stored.