Appearance
TypeScript (web) SDK
ESM-only TypeScript SDK that speaks gRPC-Web directly to the upstream flint-server (tonic + tonic_web). Works in any modern browser and in Node 18.14+. No build-time codegen required by consumers — the package ships pre-generated TypeScript.
Surfaces
| Surface | How |
|---|---|
Public (MarketDataService, StatsService, HistoricalService, NodeService) | createClient, createNodeClient, createServiceClient |
Authenticated (MakerService) | createMakerClient + AuthFlow (passwordless email) or apiKeyInterceptor(...) (org API key) |
TxService + wallet signing | Not exposed — drive a wallet adapter or use the Rust / Python SDK |
Install
sh
npm install @superis-labs/flint-api-client @bufbuild/protobufThe only runtime dependency is @bufbuild/protobuf for serialization. The gRPC-Web transport ships in-tree.
Quickstart
ts
import {
GrpcWebTransport,
MarketDataService,
NodeService,
StatsService,
createServiceClient,
} from "@superis-labs/flint-api-client";
import { FeedLevel } from "@superis-labs/flint-api-client/gen/flint/spot/v1/common_pb.js";
// baseUrl defaults to the mainnet public endpoint.
const transport = new GrpcWebTransport();
// Public RPC — no auth needed.
const market = createServiceClient(MarketDataService, transport);
const stats = createServiceClient(StatsService, transport);
const node = createServiceClient(NodeService, transport);
const pairs = await market.listPairs({});
const summary = await stats.getSummary({});
const info = await node.getNodeInfo({});
console.log(pairs.pairs);
console.log(summary);
console.log(info);
// Server-streaming RPC — `for await` works directly.
const pair = { baseId: 1n, quoteId: 0n };
for await (const event of market.subscribe({
pairs: [{ pair, level: FeedLevel.L2, snapshotOnly: false }],
})) {
console.log(event);
}Where to go from here
| You want | Page |
|---|---|
| Stream books and fills | Market data |
| Pull historical fills / candles | Historical queries |
Authenticate against MakerService | Auth flow |
MakerService (authenticated)
MakerService requires organization-scoped credentials. Build a maker client via createMakerClient, passing either an AuthFlow (passwordless email login) or apiKeyInterceptor(key) (organization API key).
ts
import {
AuthFlow,
createMakerClient,
} from "@superis-labs/flint-api-client";
// Persist the bearer across reloads. Any localStorage-shaped store works
// — pass a sessionStorage, an IndexedDB-backed shim, or your own.
// baseUrl defaults to the mainnet public endpoint.
const auth = new AuthFlow({
storage: globalThis.localStorage,
onExpired: () => router.push("/login"),
});
// Login form:
await auth.requestLoginCode("trader@example.com");
// …prompt user, then…
await auth.verifyLoginCode("trader@example.com", code);
// Bind credentials to a maker client (defaults to the mainnet maker endpoint).
const maker = createMakerClient({ auth });
const { balances } = await maker.getBalance({ spotIds: [] });See Auth flow → TypeScript for the full walk-through, including API key auth.
HistoricalService (public)
HistoricalService is exposed as a generated descriptor. Build a service client with the public transport:
ts
import {
GrpcWebTransport,
HistoricalService,
createServiceClient,
} from "@superis-labs/flint-api-client";
const historical = createServiceClient(
HistoricalService,
new GrpcWebTransport(),
);
const { fills } = await historical.getFills({ pair, limit: 100 });Decimal handling
Live book and fill price / size fields come back as Decimal { value: string }. Parse with bignumber.js or decimal.js:
ts
import BigNumber from "bignumber.js";
const price = new BigNumber(trade.price.value);
const size = new BigNumber(trade.size.value);
const notional = price.multipliedBy(size);Historical FillEvent and Candle responses use the same Decimal { value: string } wrapper for price, size, and OHLCV fields.
Errors
ts
import { GrpcError, GrpcCode } from "@superis-labs/flint-api-client";
try {
await market.listPairs({});
} catch (err) {
if (err instanceof GrpcError) {
switch (err.code) {
case GrpcCode.ResourceExhausted: await sleep(backoff); break;
case GrpcCode.Unavailable: await sleep(backoff); break;
}
}
}See Errors for the full code map.
Logging
The TypeScript SDK stays quiet unless you pass a logger. The logger interface is simple. When debug is present, transport construction logs the SDK version, proto hash, and proto revision stamped on outbound requests.
ts
type Logger = {
debug?: (message: string, fields?: Record<string, unknown>) => void;
trace?: (message: string, fields?: Record<string, unknown>) => void;
};To enable lifecycle logging plus full protobuf message payload logs:
ts
const logger = {
debug: (message: string, fields?: Record<string, unknown>) => console.debug(message, fields),
trace: (message: string, fields?: Record<string, unknown>) => console.debug(message, fields),
};
const client = createClient({
logger,
logMessages: {
enabled: true,
maxMessageChars: 50_000,
},
});You can pass the same options to createMakerClient, AuthFlow, or GrpcWebTransport.
Notes:
- Message payload logs are opt-in.
- Payload logging covers any client or auth flow using
GrpcWebTransport. - Payloads are emitted through
logger.trace(...). maxMessageCharscaps each payload in characters. Use0to disable truncation.- Built-in redaction scrubs
nonce,signature,code, andsession_tokenonAuthServicepayloads. logMessages.redactcan rewrite the serialized payload after the built-in redaction.- Bearer headers and API key headers are not logged.
Wire format
The transport speaks application/grpc-web+proto over HTTP/1.1, terminating at the server's tonic_web::GrpcWebLayer. Server streaming is supported via AsyncIterable<Output> on every streaming RPC. There is no gRPC-streaming-bidi or client-streaming over gRPC-Web — those RPCs throw at call time.
Browser compatibility
Modern browsers (Chrome 90+, Firefox 90+, Safari 14+). Requires fetch and ReadableStream — both baseline in every release-channel browser since 2022.
