Skip to content
For the complete documentation index optimized for AI agents, see llms.txt or llms-full.txt. A markdown version of this page is available by appending .md to the URL or sending Accept: text/markdown.

TypeScript SDK

For AI agents: the documentation index is at llms.txt (full corpus: llms-full.txt). A markdown source for this page is /sdks/typescript.md.

The @usearete/sdk package is a framework-agnostic client for consuming streaming data and preparing Solana operations with Arete. It uses AsyncIterable-based streaming and works in any JavaScript environment — Node.js, browsers, Deno, or Bun.


Terminal window
npm install @usearete/sdk

No peer dependencies. Works anywhere JavaScript runs.


import { createSession } from "@usearete/sdk";
import { ORE_STREAM_STACK } from "./generated/ore-stack";
const session = await createSession({
stacks: { ore: ORE_STREAM_STACK },
});
// Stream entities with full type safety
for await (const round of session.stacks.ore.views.OreRound.latest.use()) {
console.log("Round:", round.id.roundId);
}

Think of a generated stack as a cartridge: it packages a domain’s typed data and operations. Think of the Arete session as the console that connects those cartridges to shared chain reads and execution. These are mental models only—the API keeps the existing generated stack objects and types.

A connected stack at session.stacks.<name> has these stable namespaces:

NamespacePurpose
viewsTyped live and point-in-time entity views
queriesStack-level HTTP queries
programsPrograms packaged with the stack
addressesDomain address derivations, when defined
constantsDomain constants and enums, when defined
defaultsReusable domain defaults, when defined
mathPure domain calculations, when defined
readConnected domain reads, when defined
flowsStack-level multi-transaction operations, when defined

Programs at session.stacks.<name>.programs.<program> and session.programs.<name> have these stable namespaces: programId, schemas, pdas, addresses, accounts, queries, raw, instructions, transactions, flows, constants, defaults, and math.

Use session.chain for generic Solana reads and session.execute(prepared) for any prepared semantic instruction, transaction, or flow.

For transaction inspection and execution, connected sessions automatically use the authenticated Arete transaction transport. See Transactions for auto, direct, and custom transport modes, scopes, and safe failure handling.

Prefer semantic operations for application code. They accept normal camelCase objects, derive routine addresses, normalize values such as UI token amounts, and describe their cardinality through the namespace:

const prepared = await session.stacks.presale.programs.presale.flows.permissionlessDeposit.prepare({
presale,
owner,
quoteMint,
payerQuoteToken,
tokenProgram,
maxAmount: { ui: "10.5" },
});
await session.execute(prepared);

Use program.raw as an escape hatch when you need the exact generated IDL shape. Raw names and nested fields are not normalized; snake_case remains snake_case. Raw .build(...) returns one instruction for manual composition:

const instruction = session.stacks.presale.programs.presale.raw.deposit.build({
max_amount: 10_500_000n,
remaining_account_info: { slices: [] },
presale,
quote_mint: quoteMint,
escrow,
payer_quote_token: payerQuoteToken,
token_program: tokenProgram,
program: session.stacks.presale.programs.presale.programId,
});
await session.transaction([instruction]);

Each generated stack definition includes its endpoints, so inserting it into a session is simple:

import { createSession } from "@usearete/sdk";
import { ORE_STREAM_STACK } from "./generated/ore-stack";
const session = await createSession({
stacks: { ore: ORE_STREAM_STACK },
});
// Now you have fully typed views
const rounds = await session.stacks.ore.views.OreRound.latest.get();
// Generic chain reads belong to the session console
const balance = await session.chain.balance({ owner, mint });

Name each inserted stack or standalone program. Stack names become properties under session.stacks; programs packaged by those stacks and explicitly attached programs are available under session.programs:

const session = await createSession({
stacks: {
ore: ORE_STREAM_STACK,
presale: METEORA_PRESALE_STREAM_STACK,
},
programs: {
splToken: SPL_TOKEN_PROGRAM,
},
});
session.programs.presale;
session.programs.splToken;

Promotion reuses the stack’s connected program object and endpoint. If multiple stacks package the same key, the first stack owns the promoted alias and a warning is emitted; use session.stacks.<name>.programs.<program> to disambiguate. An explicit standalone program wins its top-level key.

Arete.connect(...) remains available for direct single-stack clients and advanced per-connection configuration:

import { Arete } from "@usearete/sdk";
const ore = await Arete.connect(ORE_STREAM_STACK, {
url: "wss://custom.endpoint.com",
});
const ore = await Arete.connect(ORE_STREAM_STACK, {
maxEntriesPerView: 5000, // Limit entries per view (default: 10000)
autoConnect: true, // Start the initial connection
autoReconnect: true, // Recover after an established socket is lost
});
OptionTypeDefaultDescription
urlstringstack.urlOverride the stack’s default URL
maxEntriesPerViewnumber | null10000Max entries per view before LRU eviction
autoConnectbooleantrueStart the initial WebSocket connection
autoReconnectbooleantrueRecover after an established WebSocket connection is lost
validateFramesbooleantrueLog generated-schema frame rejections
onFrameValidationErrorfunctionObserve structured generated-schema frame rejections

autoConnect and autoReconnect control separate lifecycle phases. Set autoConnect: false to create a disconnected client that the application connects later. Set autoReconnect: false when the application will handle a later connection loss itself.

// Check current state
console.log(session.stacks.ore.connectionState);
// 'connected' | 'connecting' | 'disconnected' | 'reconnecting' | 'error'
// Listen for changes
const unsubscribe = session.stacks.ore.onConnectionStateChange((state) => {
console.log("Connection state:", state);
});
// Later: stop listening
unsubscribe();
session.close();

Every view operates in one of two modes, which determines how you access data:

ModeDescriptionKey RequiredReturns
StateLookup individual entities by keyYesSingle entity (T | null)
ListAccess a collection of entitiesNoArray of entities (T[])

By default, each entity in a stack exposes two views:

View NameModeDescription
stateStateLookup any entity by its key (e.g., address)
listListCollection of all entities
// State view — get a specific round with its generated key
const round = await session.stacks.ore.views.OreRound.state.get({ roundId });
// List view — get all rounds
const rounds = await session.stacks.ore.views.OreRound.list.get();

Stacks can define additional views beyond the defaults. For example, the ORE stack includes a custom latest view that streams only recent rounds. Custom views are defined using the Rust DSL when building your stack — see Stack Definitions for details.

Custom views are accessed the same way as default views:

// Custom list view defined in the ORE stack
for await (const round of session.stacks.ore.views.OreRound.latest.use()) {
console.log("Recent round:", round.id.roundId);
}

Each view type supports both streaming (real-time updates) and one-shot (point-in-time snapshot) access patterns.


Streaming methods return AsyncIterable and continuously emit data as entities change. The connection stays open until you break out of the loop or abort.

MethodEmitsUse Case
.use()TSimplest — just the current entity state after each change
.watch()Update<T>When you need to know the operation type and membership change
.watchRich()RichUpdate<T>When you need before/after comparison

The simplest streaming method. Emits the full merged entity after each change:

// List view — no key required
for await (const round of session.stacks.ore.views.OreRound.latest.use()) {
console.log("Round:", round.id.roundId);
console.log("Motherlode:", round.state.motherlode);
}
// State view — key required
for await (const round of session.stacks.ore.views.OreRound.state.use({ roundId })) {
console.log("Round updated:", round.state.motherlode);
}

Signatures:

// State view; key type is generated from the entity primary key
use(key: TKey, options?: WatchOptions): AsyncIterable<T>
// List view
use(options?: WatchOptions): AsyncIterable<T>

Use when you need to know what operation occurred. These updates use the same canonical SDK-facing shape as .use() and .get(), including camelCase field names and bigint values for large integer families:

for await (const update of session.stacks.ore.views.OreRound.latest.watch()) {
switch (update.type) {
case "upsert":
console.log("Created or replaced:", update.data);
break;
case "patch":
console.log("Partial update:", update.data);
break;
case "remove":
console.log("Left this query:", update.key);
break;
case "delete":
console.log("Deleted:", update.key);
break;
}
}

Signatures:

// State view
watch(key: TKey, options?: WatchOptions): AsyncIterable<Update<T>>
// List view
watch(options?: WatchOptions): AsyncIterable<Update<T>>

Update types:

type Update<T> =
| { type: "upsert"; key: string; data: T } // Full entity create/replace
| { type: "patch"; key: string; data: Partial<T> } // Partial update
| { type: "remove"; key: string } // Left this query only
| { type: "delete"; key: string }; // Deleted from the source view

.watchRich() — Stream with Before/After Diffs

Section titled “.watchRich() — Stream with Before/After Diffs”

Use when you need to compare the previous and new canonical state:

for await (const update of session.stacks.ore.views.OreRound.latest.watchRich()) {
switch (update.type) {
case "created":
console.log("New entity:", update.data);
break;
case "updated":
console.log(`Changed: ${update.before.state.motherlode}${update.after.state.motherlode}`);
break;
case "removed":
console.log("Left this query:", update.lastKnown);
break;
case "deleted":
console.log("Deleted:", update.lastKnown);
break;
}
}

Signatures:

// State view
watchRich(key: TKey, options?: WatchOptions): AsyncIterable<RichUpdate<T>>
// List view
watchRich(options?: WatchOptions): AsyncIterable<RichUpdate<T>>

RichUpdate types:

type RichUpdate<T> =
| { type: "created"; key: string; data: T }
| { type: "updated"; key: string; before: T; after: T; patch?: unknown }
| { type: "removed"; key: string; lastKnown?: T }
| { type: "deleted"; key: string; lastKnown?: T };

One-shot methods return a point-in-time snapshot without subscribing to updates. Use these when you need the current state but don’t need real-time streaming.

MethodReturnsBehavior
.get()Promise<T>Async — waits for data if not yet loaded
.getSync()T | undefinedSync — returns immediately, undefined if not loaded

Fetches the current state. Returns a promise that resolves when data is available:

// List view — returns all entities
const rounds = await session.stacks.ore.views.OreRound.latest.get();
console.log(`Found ${rounds.length} rounds`);
// State view — returns single entity or null
const round = await session.stacks.ore.views.OreRound.state.get({ roundId });
if (round) {
console.log("Round:", round.id.roundId);
}

Signatures:

// State view — returns single entity or null if not found
get(key: TKey): Promise<T | null>
// List view — returns array of all entities
get(): Promise<T[]>

Returns immediately with cached data. Returns undefined if data hasn’t been loaded yet:

// List view
const rounds = session.stacks.ore.views.OreRound.latest.getSync();
if (rounds) {
console.log(`Cached: ${rounds.length} rounds`);
} else {
console.log("Data not yet loaded");
}
// State view
const round = session.stacks.ore.views.OreRound.state.getSync({ roundId });

Signatures:

// State view — returns entity, null (not found), or undefined (not loaded)
getSync(key: TKey): T | null | undefined
// List view — returns array or undefined (not loaded)
getSync(): T[] | undefined

Each view maintains an in-memory store of entities. By default, stores are limited to 10,000 entries to prevent memory issues on long-running clients. When the limit is reached, oldest entries are evicted (LRU).

const ore = await Arete.connect(ORE_STREAM_STACK, {
maxEntriesPerView: 5000, // Custom limit
});

To disable limiting (not recommended for production):

const ore = await Arete.connect(ORE_STREAM_STACK, {
maxEntriesPerView: null, // Unlimited
});

The streaming methods (.use(), .watch(), .watchRich()) accept options for pagination and validation:

interface WatchOptions {
partition?: string; // Exact _partition match
filters?: Record<string, unknown>; // Server-side dot-path equality filters
take?: number; // Live query window size
skip?: number; // Live query window offset
withSnapshot?: boolean; // Include initial snapshot (default true)
after?: string; // Incremental _seq cursor
snapshotLimit?: number; // Initial snapshot row cap
schema?: Schema<T>; // Client-side schema validation
}

Every options object defines exact protocol v2 query membership. Different windows and filters on one view can coexist without leaking entities, while equivalent normalized queries share one reference-counted wire subscription:

const rounds = session.stacks.ore.views.OreRound.latest;
const firstPage = rounds.watch({ take: 10 });
const secondPage = rounds.watch({ take: 10, skip: 10 });

After reconnect, a completed authoritative snapshot atomically replaces membership for its exact query. A query with after receives an incremental snapshot and merges without pruning.

// Only receive the first 10 entities
for await (const round of session.stacks.ore.views.OreRound.latest.use({ take: 10 })) {
console.log("Round:", round.id.roundId);
}
// Skip first 20, take next 10
for await (const round of session.stacks.ore.views.OreRound.latest.use({ skip: 20, take: 10 })) {
console.log("Round:", round.id.roundId);
}

Use exact-match dot-path filters for dynamic equality predicates:

for await (const order of session.stacks.market.views.Order.list.use({
filters: { "state.status": "open", "market.symbol": "SOL" },
take: 10,
})) {
console.log(order);
}

Filtering is case-sensitive and type-sensitive. Filters run before skip and take. Use a custom view when filtering, sorting, or aggregation needs to be declared as part of the stack rather than supplied by each client.

Filter keys are WebSocket wire-schema dot paths, so snake_case remains correct there. Generated TypeScript entity properties and generated state keys use camelCase.

See Filtering Feeds for details on all filtering options, or Stack Definitions for how to define custom views using the Rust DSL.


Use standard AsyncIterable patterns to control streams client-side.

for await (const update of session.stacks.ore.views.OreRound.latest.watch()) {
if (update.type === "upsert") {
const round = update.data;
if (round.state.motherlode && round.state.motherlode > 1_000_000_000) {
console.log("Found high-value round:", round.id.roundId);
break;
}
}
}

Use an AbortController to cancel from outside the loop:

const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000); // Cancel after 30s
try {
for await (const update of session.stacks.ore.views.OreRound.latest.watch()) {
if (controller.signal.aborted) break;
console.log("Update:", update.data);
}
} catch (e) {
if (!controller.signal.aborted) throw e;
}
for await (const update of session.stacks.ore.views.OreRound.latest.watch()) {
if (update.type !== "upsert") continue;
if ((update.data.metrics.deployCount ?? 0) < 100) continue;
console.log("Active round:", update.data.id.roundId);
}

Every stack ships with Zod schemas alongside its TypeScript interfaces. Use them to validate data at two levels:

Generated schemas always normalize and validate frames before storage. Invalid data is dropped and logged by default. Observe structured failures without scraping console output:

const ore = await Arete.connect(ORE_STREAM_STACK, {
onFrameValidationError(diagnostic) {
console.error('Rejected Arete frame', diagnostic);
},
});

Set validateFrames: false to suppress the default warning while keeping normalization, rejection, and onFrameValidationError active.

Pass a schema to .use() to filter out entities that don’t match:

import { OreRoundCompletedSchema } from "./generated/ore-stack";
// Only emit rounds where every field is present. Rejected entities are skipped.
for await (const round of session.stacks.ore.views.OreRound.latest.use({
schema: OreRoundCompletedSchema,
})) {
console.log(round.id.roundId, round.state.motherlode);
}

React view hooks additionally support onSchemaValidationError when an application needs diagnostics for caller-supplied schema rejections.

See Schema Validation for the full guide on generated schemas, custom schemas, and the “Completed” schema pattern.


Arete can automatically enrich your entities with data that doesn’t live on-chain. For example, the ORE stack includes token metadata (name, symbol, decimals, logo) resolved server-side from the DAS API:

for await (const round of session.stacks.ore.views.OreRound.latest.use()) {
// Token metadata is resolved automatically — no extra API calls
console.log(round.oreMetadata?.name); // "Ore"
console.log(round.oreMetadata?.symbol); // "ORE"
console.log(round.oreMetadata?.decimals); // 11
}

Resolved data arrives as part of the entity alongside on-chain fields. Your client code reads it the same way — no distinction between on-chain and resolved data.

See Resolvers for how to add resolved data when building your own stack.


toRawAmount(input, decimals) is convenient when invalid input should throw. Use safeToRawAmount(input, decimals) at user-input boundaries:

import { safeToRawAmount } from "@usearete/sdk";
const parsed = safeToRawAmount({ ui: amountText }, 9);
if (!parsed.success) {
console.error(parsed.error);
return;
}
console.log(parsed.data); // bigint in raw base units

safeToRawAmount returns { success: true, data } | { success: false, error } instead of throwing for invalid amount input. It is also re-exported by @usearete/react.


import { AreteError, createSession } from "@usearete/sdk";
import { ORE_STREAM_STACK } from "./generated/ore-stack";
try {
const session = await createSession({ stacks: { ore: ORE_STREAM_STACK } });
} catch (error) {
if (error instanceof AreteError) {
console.error("Arete error:", error.message);
console.error("Code:", error.code);
} else {
throw error;
}
}

Creates the console that connects generated stacks and standalone programs. Programs packaged by stacks are automatically promoted to session.programs by reference.

Parameters:

  • definition.stacks — Named generated stack definitions (optional)
  • definition.programs — Named standalone program definitions (optional)
  • options.wallet — Shared wallet for execution (optional)
  • options.signerRegistry — Shared additional signers (optional)
  • options.endpoints — Shared HTTP and WebSocket fallback endpoints (optional)
  • options.stacks — Per-stack connection overrides (optional)
  • options.stacks.<name>.autoConnect — Per-stack initial connection control; defaults to true (optional)
  • options.stacks.<name>.autoReconnect — Per-stack post-connection recovery control; defaults to true (optional)

At least one stack or program is required.

Returns: Promise<Session>

Returns the connected generated stack inserted under that name. Use its views, queries, programs, addresses, constants, defaults, math, read, and flows namespaces when present.

Returns a connected program packaged by a stack, attached to a stack, or declared standalone. Program namespaces are programId, schemas, pdas, addresses, accounts, queries, raw, instructions, transactions, flows, constants, defaults, and math.

The session-wide generic Solana read surface.

Executes a value returned by a semantic instruction, transaction, or flow’s .prepare(...) method.

Creates a direct single-stack client. This existing API remains useful for advanced per-connection options such as url, maxEntriesPerView, and validateFrames.

Typed views interface based on your stack definition. Access pattern:

session.stacks.<name>.views.<entity>.<viewName>.<method>()
// Examples with default views:
session.stacks.ore.views.OreRound.state.get(key) // State mode — requires key
session.stacks.ore.views.OreRound.list.get() // List mode — no key

For views in state mode (keyed lookup). All methods require a key parameter:

MethodSignatureReturns
useuse(key, options?)AsyncIterable<T>
watchwatch(key, options?)AsyncIterable<Update<T>>
watchRichwatchRich(key, options?)AsyncIterable<RichUpdate<T>>
getget(key)Promise<T | null>
getSyncgetSync(key)T | null | undefined

For views in list mode (collections). No key parameter:

MethodSignatureReturns
useuse(options?)AsyncIterable<T>
watchwatch(options?)AsyncIterable<Update<T>>
watchRichwatchRich(options?)AsyncIterable<RichUpdate<T>>
getget()Promise<T[]>
getSyncgetSync()T[] | undefined

Options for streaming methods:

interface WatchOptions {
partition?: string;
filters?: Record<string, unknown>;
take?: number;
skip?: number;
withSnapshot?: boolean;
after?: string;
snapshotLimit?: number;
schema?: Schema<T>;
}

See Schema Validation for details on using schemas to filter and validate streamed data.

Current connection state:

  • disconnected — Not connected
  • connecting — Establishing connection
  • connected — Active connection
  • reconnecting — Auto-reconnecting after failure
  • error — Connection failed

session.stacks.<name>.onConnectionStateChange(callback)

Section titled “session.stacks.<name>.onConnectionStateChange(callback)”

Subscribe to connection state changes. Returns unsubscribe function.

Closes every connection owned by the session.