Skip to content

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

SurfaceHow
Public (MarketDataService, StatsService, HistoricalService, NodeService)createClient, createNodeClient, createServiceClient
Authenticated (MakerService)createMakerClient + AuthFlow (passwordless email) or apiKeyInterceptor(...) (org API key)
TxService + wallet signingNot exposed — drive a wallet adapter or use the Rust / Python SDK

Install

sh
npm install @superis-labs/flint-api-client @bufbuild/protobuf

The 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 wantPage
Stream books and fillsMarket data
Pull historical fills / candlesHistorical queries
Authenticate against MakerServiceAuth 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(...).
  • maxMessageChars caps each payload in characters. Use 0 to disable truncation.
  • Built-in redaction scrubs nonce, signature, code, and session_token on AuthService payloads.
  • logMessages.redact can 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.

Built on Solana