@triton-one/triton-sdk
v0.0.1
Published
Solana web3.js compatible SDK client for Triton One services
Keywords
Readme
yellowstone-account-sync TypeScript SDK
This SDK provides a web3.js-compatible Connection focused on local account reads from a realtime state buffer fed by Yellowstone account-sync subscriptions.
Current method coverage is intentionally narrow:
getAccountInfois implemented and resolved from local buffered state.getMultipleAccountsInfois implemented as repeated local buffered account reads.getParsedAccountInfoandgetMultipleParsedAccountsare implemented with local parsing and client-side context fetches.- Account data conversion helpers are exported for callers that need raw encoding conversion.
- Runtime subscription management is implemented (
addAccounts,removeAccounts,setAccounts).
Purpose and Scope
The client is designed for applications that want to reduce polling RPC reads by maintaining local account state from a push stream.
Design goals:
- Minimize integration changes for existing
@solana/web3.jsusers. - Keep browser and Node transport behavior explicit and safe.
- Keep transport internals separate from buffer/business logic.
- Make runtime behavior deterministic and auditable.
Non-goals (current version):
- Full web3.js method parity.
- Historical state queries.
- Multi-endpoint failover orchestration.
Public API
Constructor
The constructor keeps web3.js style:
new Connection(endpoint, commitmentOrConfig?)endpoint: base endpoint used for defaults.commitmentOrConfig: either a commitment string or a config object.
Methods
getAccountInfo(publicKey, commitmentOrConfig?) -> Promise<AccountInfo<Buffer> | null>getParsedAccountInfo(publicKey, commitmentOrConfig?) -> Promise<RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>>getMultipleAccountsInfo(publicKeys, commitmentOrConfig?) -> Promise<Array<AccountInfo<Buffer> | null>>getMultipleAccountsInfoAndContext(publicKeys, commitmentOrConfig?) -> Promise<RpcResponseAndContext<Array<AccountInfo<Buffer> | null>>>getMultipleParsedAccounts(publicKeys, rawConfig?) -> Promise<RpcResponseAndContext<Array<AccountInfo<Buffer | ParsedAccountData> | null>>>addAccounts(accountIds) -> Promise<void>removeAccounts(accountIds) -> Promise<void>setAccounts(accountIds) -> Promise<void>close() -> Promise<void>getLastTransportError() -> Error | null
Compatibility Notes
- Input and output shape of
getAccountInfomatches web3.js expectations (AccountInfo<Buffer> | null). - Input and output shape of
getMultipleAccountsInfomatches web3.js expectations (Array<AccountInfo<Buffer> | null>). - The optional
commitmentOrConfigargument ongetAccountInfosupportscommitment,dataSlice, andminContextSlot; reads are from local buffer. getMultipleAccountsInfosupports the samecommitment,dataSlice, andminContextSlotoptions.- Raw account responses include top-level
spaceat runtime, matching current web3.js RPC behavior, though the public web3.jsAccountInfotype does not declare it.spaceis the full account data size in bytes.dataSliceslices onlydata;spaceremains the full unsliced size. getParsedAccountInfoandgetMultipleParsedAccountsreturn web3.js-style parsed account info and fall back to raw base64 data when parsing context is unavailable.getMultipleAccountsInforeturns entries in input order, including duplicate keys.minContextSlotis evaluated against the buffered account update slot in this SDK. Direct web3.js RPC treatsminContextSlotas a node read-context requirement and can throwMIN_CONTEXT_SLOT_NOT_REACHED; this SDK waits on the local buffer and returnsnullif no matching update arrives beforemissTimeoutMs.
Account Encoding
Account data conversion is backed by a small Rust WASM library under
account-encoding-wasm. The Rust side is deterministic and does not open HTTP,
gRPC, or RPC connections.
Supported encodings:
"binary": legacy Solana account data form, encoded as base58."base58": base58 account data."base64": base64 account data."base64+zstd": zstd-compressed base64 account data. This build enables it in WASM."jsonParsed": Solana program-aware parsed account data where supported.
jsonParsed sometimes needs extra data. SPL token account parsing needs the
mint account to compute token amounts. The WASM parser reports the missing mint,
then the TypeScript connection calls this same client's existing getAccountInfo
method for that mint. The fetched mint account is cached and in-flight fetches
are shared so concurrent parses do not start duplicate reads.
Default parser context cache settings:
maxEntries:256ttlMs:300000
Known limits:
- WASM does not do network I/O.
- Unsupported program parsers fall back to base64 account data, matching RPC-style behavior.
- If
fallbackOnParseFailureis set tofalse, parser and context errors are thrown as typed errors. - Token-2022 parsing depends on the Solana account decoder version compiled into the WASM crate.
Discovery notes for maintainers:
- Account updates are decoded in
src/codec/geyser_codec.ts. - Latest account state is buffered in
src/core/account_sync_core.tsandsrc/core/account_buffer.ts. - Web3 account conversion is in
src/connection/utils.ts. - Node and browser read APIs are wired in
src/connection/node_connection.tsandsrc/connection/browser_connection.ts. - WASM build output is generated into
src/wasm/account_encodingand copied intodist/wasm/account_encodingduringnpm run build. - Building from source requires the Rust
wasm32-unknown-unknowntarget and thewasm-bindgenCLI.
Configuration
The SDK extends web3.js config with accountSync options:
{
accountSync?: {
transport?: "ws" | "grpc" // Node
// browser build only accepts "ws"
subscriptionEndpoint?: string
commitment?: "processed" | "confirmed" | "finalized"
initialAccounts?: Array<string | PublicKey>
autoSubscribeOnMiss?: boolean
missTimeoutMs?: number
}
}Example:
const connection = new Connection("https://example.com/<TOKEN>", {
accountSync: {
transport: "grpc",
commitment: "finalized",
initialAccounts: [publicKey],
},
});getAccountInfo also accepts the web3.js commitment argument:
await connection.getAccountInfo(publicKey, "finalized");
await connection.getAccountInfo(publicKey, { commitment: "processed" });
await connection.getAccountInfo(publicKey, {
commitment: "confirmed",
dataSlice: { offset: 8, length: 32 },
minContextSlot: 123456,
});getMultipleAccountsInfo accepts the same account read options:
const accounts = await connection.getMultipleAccountsInfo(
[publicKeyA, publicKeyB],
{
commitment: "confirmed",
dataSlice: { offset: 8, length: 32 },
minContextSlot: 123456,
},
);getMultipleAccountsInfoAndContext returns the same values with one context:
const response = await connection.getMultipleAccountsInfoAndContext(
[publicKeyA, publicKeyB],
{ commitment: "confirmed" },
);
console.log(response.context.slot, response.value);Parsed account examples:
const parsed = await connection.getParsedAccountInfo(publicKey, {
commitment: "confirmed",
});
console.log(parsed.value?.data);const parsedMany = await connection.getMultipleParsedAccounts(
[publicKeyA, publicKeyB],
{
commitment: "confirmed",
minContextSlot: 123456,
},
);
console.log(parsedMany.context.slot, parsedMany.value);Raw conversion helper example:
import { convertAccountData } from "@triton-one/triton-sdk";
const asBase58 = await convertAccountData(base64Data, "base64", "base58");getParsedAccountInfo and getMultipleParsedAccounts accept the same account
read options:
await connection.getParsedAccountInfo(publicKey, {
commitment: "confirmed",
});For local-buffer multiple-account reads, the context slot is the minimum slot
among non-null returned accounts. If all returned entries are null, the context
slot is 0.
If this commitment differs from the connection's default account-sync commitment, the SDK opens a separate subscription stream for that commitment and keeps a separate buffer for it.
Defaults:
transport:"ws"(Node),"ws"only (browser).subscriptionEndpoint:- WS transport: uses
wsEndpointfrom config, otherwise derives fromendpoint(http->ws,https->wss), then normalizes path to/[<token>/]YellowstoneAccountSyncService/ws. - gRPC transport: defaults to
endpoint; endpoint path is used as token prefix and subscribe method is called at/<token>/YellowstoneAccountSyncGrpcService/Subscribe(or/YellowstoneAccountSyncGrpcService/Subscribewhen endpoint has no path). If endpoint scheme is omitted, SDK defaults tohttps://for non-local hosts andhttp://for localhost-style hosts.
- WS transport: uses
initialAccounts:[].commitment:"confirmed".autoSubscribeOnMiss:true.missTimeoutMs:5000.
Live Release Tests
The live SDK integration test uses the real SDK against a configured server. It
is skipped unless both TEST_ENDPOINT and TEST_TOKEN are set.
TEST_ENDPOINT=https://example.com \
TEST_TOKEN=token-value \
npm test -- test/sdk_live_integration.test.tsThe test runs the same workflow over gRPC and WebSocket transports. It loads
accounts from ../../k6/accounts.txt, subscribes to sets of 100, 100, and
1000 accounts, and performs seeded random reads through the SDK methods.
TEST_TOKEN is appended to TEST_ENDPOINT for both SDK JSON-RPC hydration and
stream subscription endpoints.
Optional settings:
TEST_RANDOM_SEED: seed used for repeatable random account reads.TEST_RANDOM_READS: random reads per account set.TEST_MISS_TIMEOUT_MS: SDK read wait timeout for live account reads.TEST_UNREACHABLE_MISS_TIMEOUT_MS: short timeout for the unreachableminContextSlotcheck.TEST_CLOSE_TIMEOUT_MS: timeout for closing each SDK connection.TEST_LIVE_TIMEOUT_MS: Vitest timeout for each transport workflow.
Runtime Architecture
The implementation is split into independent components:
- Connection facade
- Node facade and browser facade preserve constructor/method ergonomics.
- Facades inject the selected transport into the shared core.
- Core orchestration
AccountSyncCoremanages startup, control operation serialization, and lifecycle.SubscriptionRegistrystores desired tracked-account set.AccountBufferstores latest account state by account id.
- Transport adapters
WsAccountSubscriptionTransportfor websocket streams.GrpcAccountSubscriptionTransportfor grpc-js bidi streams (Node only).
- Codec layer
- Encodes subscribe requests and decodes account updates.
- Uses
@triton-one/yellowstone-grpcgenerated types as type-only references.
Data and Consistency Semantics
Buffered state uses one record per account per commitment with:
accountIdlamportsownerexecutablerentEpochdataslotwriteVersionupdatedAtMs
Update ordering rules:
- Newer
slotwins. - If
slotis equal, higher or equalwriteVersionwins. - Older updates are ignored.
Subscription Semantics
initialAccountsare pushed when transport starts.- Subscription commitment is pushed with every subscribe request (defaults to
confirmedunless configured). addAccountsupdates the desired set additively, then transports send the full desired set.setAccountsandremoveAccountscan shrink the tracked set.getAccountInfo(publicKey, commitmentOrConfig)usescommitmentOrConfig.commitmentwhen provided.getMultipleAccountsInfo(publicKeys, commitmentOrConfig)applies the same rules to each requested account.- A requested commitment different from the connection default uses a separate stream and buffer.
dataSliceapplies to the returneddatabuffer only.minContextSlotis the minimum buffered account update slot. If the local buffer does not receive an update at or above that slot beforemissTimeoutMs, the returned account entry isnull.
Important behavior:
- The server supports replace semantics for session account filters.
- Transport adapters send the full desired account set on each change over the same live stream.
- Account removals are applied in-place on the existing stream (no reconnect required).
- Upstream receives the updated full tracked-account set from the server and updates subscriptions in realtime.
Cache Miss Behavior
getAccountInfo and each entry in getMultipleAccountsInfo follow the same behavior:
- If account exists in local buffer, return immediately.
- If missing and
autoSubscribeOnMiss = false, returnnull. - If missing and
autoSubscribeOnMiss = true, add account to tracked set and wait up tomissTimeoutMsfor first update. - If timeout expires before first update, return
null. - If
minContextSlotis set and only older buffered updates are available, wait up tomissTimeoutMsfor a newer update; if none arrives, returnnull.
Error Model
Synchronous/constructor-path errors:
- Invalid transport selection in browser (
grpc) throws. - Invalid account input (bad public key) throws during normalization.
Async transport/runtime errors:
- Stream disconnects and transport-level failures are captured.
- Latest observed error is available via
getLastTransportError(). - Methods that run after
close()reject withaccount-sync connection is closed.
Lifecycle
- Call
close()to stop subscriptions and release transport resources. close()is idempotent.
Packaging and Environment Isolation
The package uses conditional exports:
- Browser entry: exposes WS-capable connection only.
- Node entry: exposes WS and gRPC transport options.
Safety rules:
- Browser bundle does not expose gRPC transport.
- gRPC adapter has a runtime guard that throws when loaded in browser context.
@triton-one/yellowstone-grpcis a dev-only dependency and used for type-only imports in SDK code.
Examples
Runnable examples are available at:
examples/clients/typescript/src/get_account_info_ws.tsexamples/clients/typescript/src/get_account_info_grpc.ts
Example package:
examples/clients/typescript/package.json
