> For the complete documentation index, see [llms.txt](https://docs.solanavibestation.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.solanavibestation.com/api-reference/rewind/client-sdks.md).

# Client SDKs

Typed TypeScript and Rust clients for the Rewind API, with helpers for the common workflows.

Rewind ships two thin, typed clients. Both expose one method per RPC method, typed params and results, a typed error carrying the JSON-RPC code and `data`, and the same set of helpers on top. You can always call the JSON-RPC API directly — the SDKs add no server-side behaviour — but they remove the boilerplate around cursors, anchors, chunking and retries.

| Package                                                                                | Registry  | Language   | Runtime                                                              |
| -------------------------------------------------------------------------------------- | --------- | ---------- | -------------------------------------------------------------------- |
| [`@solanavibestation/rewind`](https://www.npmjs.com/package/@solanavibestation/rewind) | npm       | TypeScript | Node 18+ or any browser with `fetch`. Zero runtime dependencies.     |
| [`svs-rewind`](https://crates.io/crates/svs-rewind)                                    | crates.io | Rust       | async (`tokio`). Optional `zstd` feature for `base64+zstd` decoding. |

### Install

**TypeScript**

```bash
npm install @solanavibestation/rewind
```

**Rust**

```toml
[dependencies]
svs-rewind = "0.3"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

# Optional: decode base64+zstd account data
# svs-rewind = { version = "0.3", features = ["zstd"] }
```

### Create a client

Point the client at `https://rewind.solanavibestation.com` and pass your API key. The key is sent as the `Authorization` header — the same scheme as every other SVS endpoint.

**TypeScript**

```ts
import { RewindClient } from "@solanavibestation/rewind";

const rewind = new RewindClient("https://rewind.solanavibestation.com", {
  apiKey: process.env.SVS_API_KEY,
  timeoutMs: 30_000,          // default 30 s
});
```

Other options: `fetch` (custom implementation — useful in older Node or test harnesses), `retry` (see Retry policy), `apiKeyHeader` (only if you need something other than `Authorization`).

**Rust**

```rust
use svs_rewind::Client;

let client = Client::new("https://rewind.solanavibestation.com")?
    .with_api_key(std::env::var("SVS_API_KEY")?);
```

Other builders: `.with_http(reqwest::Client)` to supply your own HTTP client (timeouts, proxies, connection pooling), `.with_retry(RetryPolicy)` (see Retry policy), `.with_api_key_header(name, key)` if you need a header other than `Authorization`.

### Methods

Every RPC method has a one-to-one typed wrapper. Results keep the same shape the wire returns (`{ context, value }`) except where noted.

| RPC method                      | TypeScript                                                                          | Rust                                                                                        |
| ------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `getHistoricalAccountInfo`      | `rewind.getHistoricalAccountInfo(pubkey, config)`                                   | `client.get_historical_account_info(pubkey, &cfg)`                                          |
| `getMultipleHistoricalAccounts` | `rewind.getMultipleHistoricalAccounts(pubkeys, config)`                             | `client.get_multiple_historical_accounts(&pubkeys, &cfg)`                                   |
| `getAccountChanges`             | `rewind.getAccountChanges(pubkey, config)`                                          | `client.get_account_changes(pubkey, &cfg)`                                                  |
| `getAccountDiff`                | `rewind.getAccountDiff(pubkey, config)`                                             | `client.get_account_diff(pubkey, &cfg)`                                                     |
| `getRewindCoverage`             | `rewind.getRewindCoverage(config?)`                                                 | `client.get_rewind_coverage(&cfg)`                                                          |
| `getHistoricalFeatureSet`       | `rewind.getHistoricalFeatureSet(slot, includeInactive?)` → returns `value` directly | `client.get_historical_feature_set(slot, include_inactive)` → returns `FeatureSet` directly |
| `simulateHistoricalTransaction` | `rewind.simulateHistoricalTransaction(txBase64, config)`                            | `client.simulate_historical_transaction(tx_b64, &cfg)`                                      |
| `getHealth`                     | `rewind.getHealth()` → resolves when healthy, throws otherwise                      | `client.get_health()` → `Ok(())` when healthy                                               |

There is also an escape hatch — `rewind.call(method, params)` / `client.call(method, params)` — which sends any method with the retry policy applied. The typed wrappers are built on it.

#### Positioning

The three positioning modes from the overview map onto the SDKs like this:

**TypeScript**

Config objects are plain literals — the same JSON you'd send on the wire, with types.

```ts
// End of slot
await rewind.getHistoricalAccountInfo(pubkey, { slot: 250_000_000 });

// Inside a slot (writeVersion comes from a getAccountChanges row)
await rewind.getHistoricalAccountInfo(pubkey, { slot: 250_000_000, writeVersion: 4567 });

// Anchored to a transaction, decoded
await rewind.getHistoricalAccountInfo(pubkey, {
  anchor: { signature: sig, position: "before" },
  encoding: "jsonParsed",
});
```

**Rust**

Positions are an enum; `ReadConfig::at(position)` builds a read config you can chain options onto.

```rust
use svs_rewind::{AnchorPosition, Encoding, Position, ReadConfig};

// End of slot
let cfg = ReadConfig::at(Position::Slot(250_000_000));

// Inside a slot (write_version comes from a getAccountChanges row)
let cfg = ReadConfig::at(Position::Write { slot: 250_000_000, write_version: 4567 });

// Anchored to a transaction, decoded
let cfg = ReadConfig::at(Position::Anchor {
    signature: sig.to_string(),
    position: AnchorPosition::Before,
}).encoding(Encoding::JsonParsed);

let r = client.get_historical_account_info(pubkey, &cfg).await?;
```

Other chainable options: `.data_slice(offset, length)`, `.tolerate_gaps(true)`.

For the other methods: `ChangesConfig::range(from, to)`, `DiffConfig::between(a, b)` / `DiffConfig::around_transaction(sig)`, `SimulateConfig::at_slot(n)` / `SimulateConfig::around_transaction(sig, after)`. Remaining fields on each config struct are public and named after their wire counterparts (`limit`, `include_data`, `inner_instructions`, `account_overrides`, …).

### Helpers

Both SDKs ship the same six helpers. They compose the raw calls; nothing here needs server support.

#### 1. Change walker

Pages through `getAccountChanges` for you, handling the `next` cursor and collecting `gaps` as it goes.

**TypeScript**

```ts
import { walkAccountChanges, iterAccountChanges } from "@solanavibestation/rewind";

// One page at a time — each page carries its own gaps[]
for await (const page of walkAccountChanges(rewind, pubkey, { fromSlot, toSlot })) {
  for (const row of page.changes) { /* … */ }
  if (page.gaps.length) console.warn("discontinuities:", page.gaps);
}

// Or one row at a time
for await (const row of iterAccountChanges(rewind, pubkey, { fromSlot, toSlot })) {
  if (row.dataChanged) console.log(row.slot, row.writeVersion, row.txnSignature);
}
```

**Rust**

```rust
use svs_rewind::ChangesConfig;

let mut w = client.walk_account_changes(pubkey, ChangesConfig::range(from, to));
while let Some(page) = w.next_page().await? {
    for row in &page.changes { /* … */ }
}
if let Some(gaps) = w.gaps() { eprintln!("discontinuities: {gaps:?}"); }

// Or drain the whole range into a Vec
let all = client.walk_account_changes(pubkey, ChangesConfig::range(from, to)).collect_all().await?;
```

#### 2. Transaction view

"What did this transaction do to this account?" — before/after state plus the structural diff, in one call. The many-account variant makes two `getMultipleHistoricalAccounts` calls sharing the anchor.

**TypeScript**

```ts
import { transactionView, transactionViewMany, aroundTransaction } from "@solanavibestation/rewind";

const v = await transactionView(rewind, sig, pubkey);          // { context, value: { a, b, diff } }
console.log(v.value.diff?.lamportsDelta, v.value.diff?.dataChanged);

const m = await transactionViewMany(rewind, sig, [pk1, pk2]);  // { slot, before[], after[] }

// aroundTransaction(sig) is the underlying DiffConfig if you want to add options
const d = await rewind.getAccountDiff(pubkey, aroundTransaction(sig, { encoding: "jsonParsed" }));
```

**Rust**

```rust
let v = client.transaction_view(sig, pubkey, None).await?;             // DiffResponse
if let Some(diff) = v.value.diff { println!("{} {}", diff.lamports_delta, diff.data_changed); }

let m = client.transaction_view_many(sig, &[pk1, pk2], None).await?;  // { slot, before, after }

// DiffConfig::around_transaction(sig) is the underlying config
let d = client.get_account_diff(pubkey, &DiffConfig::around_transaction(sig)).await?;
```

#### 3. Coverage guard

Classify a slot offline before spending a request on it. Fetches `getRewindCoverage` once and refreshes on a timer; `classify` returns one of `answerable`, `aboveTip` (`-32014`), `belowFloor` (`-32015`), `inGap` (`-32018`, with the gap), or `unknown`.

The result is advisory — a gap can appear between refreshes — so still handle `-32018` on the actual call.

**TypeScript**

```ts
import { CoverageGuard } from "@solanavibestation/rewind";

const guard = await CoverageGuard.create(rewind, { refreshMs: 30_000 });
const cls = await guard.classify(slot);
switch (cls.kind) {
  case "answerable": /* query */ break;
  case "aboveTip":   /* route to live RPC */ break;
  case "belowFloor": /* outside your window */ break;
  case "inGap":      console.log("gap", cls.gap); break;
}
// guard.coverage — the cached Coverage; guard.classifyCached(slot) — no refresh
```

**Rust**

```rust
use std::time::Duration;
use svs_rewind::SlotClass;

let guard = client.coverage_guard(Duration::from_secs(30)).await?;
match guard.classify(slot).await? {
    SlotClass::Answerable => { /* query */ }
    SlotClass::AboveTip => { /* route to live RPC */ }
    SlotClass::BelowFloor => { /* outside your window */ }
    SlotClass::InGap(gap) => println!("gap {gap:?}"),
    SlotClass::Unknown => {}
}
// guard.coverage() — the cached Coverage; guard.classify_cached(slot) — no refresh
```

#### 4. Batch reader

Read any number of accounts at one position. Chunks to the server's 100-account limit, runs chunks concurrently, and — when a chunk comes back `truncatedByBytes` — resubmits the unattempted tail with a halved chunk size. Results are in request order.

**TypeScript**

```ts
import { readMany } from "@solanavibestation/rewind";

const all = await readMany(rewind, pubkeys, { slot }, { chunkSize: 100, concurrency: 4 });
// all: (HistoricalAccount | null)[]  — same length and order as pubkeys
```

**Rust**

```rust
use svs_rewind::{Position, ReadConfig, ReadManyOptions};

let all = client
    .read_many(&pubkeys, &ReadConfig::at(Position::Slot(slot)), ReadManyOptions::default())
    .await?;
// ReadManyOptions { chunk_size, concurrency }
```

#### 5. Data decoding

Turn the `[encoded, encoding]` data pair into bytes. `base64` and `base58` are built in; `base64+zstd` needs a zstd decoder (pass one in TypeScript; enable the `zstd` feature in Rust).

**TypeScript**

```ts
import { decodeData, toWeb3AccountInfo } from "@solanavibestation/rewind";
import { PublicKey } from "@solana/web3.js";

const bytes = decodeData(account.data);                    // Uint8Array
const bytes2 = decodeData(account.data, { zstd: myZstdDecode });

// Convert to a @solana/web3.js AccountInfo without the SDK depending on web3.js
const info = toWeb3AccountInfo(account, PublicKey);        // { lamports, owner: PublicKey, executable, rentEpoch, data }
```

**Rust**

```rust
let bytes = account.data.bytes()?;   // Vec<u8>; base64+zstd behind the `zstd` feature
```

#### 6. Retry policy

Off by default. When enabled: transport errors and `-32603` retry with exponential backoff and jitter; `-32018` retries with a fixed delay (the archive's healer debounces for \~15 s, so 15–30 s is sensible). The policy **never** retries `-32602`, `-32014`, `-32015`, `-32016` or `-32017` — those won't change on retry.

**TypeScript**

```ts
const rewind = new RewindClient(url, {
  apiKey,
  retry: {
    transport: 3,                                  // default 3
    internalError: 3,                              // default 3
    archiveGap: { attempts: 2, delayMs: 20_000 },  // off unless set
    baseDelayMs: 200,                              // default 200
  },
});
```

**Rust**

```rust
use std::time::Duration;
use svs_rewind::RetryPolicy;

let client = client.with_retry(RetryPolicy {
    archive_gap: Some((2, Duration::from_secs(20))),
    ..Default::default()
});
```

### Error handling

JSON-RPC errors surface as a typed error carrying the code, message and any structured `data` (for example the gap bounds on `-32018`). Transport failures surface separately.

**TypeScript**

```ts
import { RewindRpcError, ErrorCode } from "@solanavibestation/rewind";

try {
  await rewind.getHistoricalAccountInfo(pubkey, { slot });
} catch (e) {
  if (e instanceof RewindRpcError) {
    switch (e.code) {
      case ErrorCode.SlotNotAvailable:   /* -32014: too new, go to live RPC */ break;
      case ErrorCode.SlotBeforeEarliest: /* -32015: outside your window */ break;
      case ErrorCode.SignatureNotFound:  /* -32016 */ break;
      case ErrorCode.FilteredAccountStale: /* -32017: Vote account */ break;
      case ErrorCode.SlotInArchiveGap:   console.log(e.data); /* { queriedSlot, gapStart, gapEnd, gapStatus } */ break;
    }
  } else {
    // transport / timeout / invalid response
  }
}
```

**Rust**

```rust
use svs_rewind::{codes, Error};

match client.get_historical_account_info(pubkey, &cfg).await {
    Ok(r) => { /* … */ }
    Err(Error::Rpc { code: codes::SLOT_NOT_AVAILABLE, .. })   => { /* -32014: too new, go to live RPC */ }
    Err(Error::Rpc { code: codes::SLOT_BEFORE_EARLIEST, .. }) => { /* -32015: outside your window */ }
    Err(Error::Rpc { code: codes::SLOT_IN_ARCHIVE_GAP, data, .. }) => { println!("{data:?}"); }
    Err(Error::Rpc { code, message, .. }) => { eprintln!("rpc {code}: {message}"); }
    Err(Error::Transport(e)) => { /* DNS / TLS / connection / timeout */ }
    Err(e) => { /* invalid response */ }
}
```

The full code table is in the overview.

### Compatibility

* Account results are wire-compatible supersets of Solana's `AccountInfo`. `toWeb3AccountInfo` (TypeScript) converts one for `@solana/web3.js`; in Rust, `HistoricalAccount` has the same fields plus `rewind_slot` / `rewind_write_version`.
* SDK versions track the API: the `0.3.x` line targets `rewind-0.4`. `context.apiVersion` on every response tells you what the server is running.
* Both SDKs are Apache-2.0. Source lives alongside the service; issues and feature requests go through the SVS team via Discord or your customer portal.
