> For the complete documentation index, see [llms.txt](https://emeditweb.gitbook.io/pulsar-stellar-sdk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://emeditweb.gitbook.io/pulsar-stellar-sdk/sdk-reference/client.md).

# Client

`PulsarClient` is the entry point for the indexer path. One client is configured once, validated at construction, and reused. Its methods read a contract's decoded event history from a running Pulsar indexer.

For the RPC path, which needs no indexer, see [Live RPC](/pulsar-stellar-sdk/sdk-reference/rpc.md). Both paths return the same [`DecodedEvent`](/pulsar-stellar-sdk/sdk-reference/schemas-and-types.md#decodedevent).

## `new PulsarClient(config)`

```ts
new PulsarClient(config: PulsarConfig): PulsarClient
```

Validates the whole configuration and keeps the parsed, frozen result. A misconfiguration throws here, where you made it, rather than at the first request.

* **`config`**: see [`PulsarConfigSchema`](/pulsar-stellar-sdk/sdk-reference/schemas-and-types.md#pulsarconfigschema). `indexerUrl` is required (http or https). `rpcUrl`, `network`, `fetchImpl`, and `timeoutMs` are optional; `timeoutMs` defaults to `15000`.
* **Throws** [`PulsarValidationError`](/pulsar-stellar-sdk/sdk-reference/errors.md#pulsarvalidationerror) if the configuration is invalid, carrying every schema issue with the underlying `ZodError` as its `cause`.

```ts
import { PulsarClient } from '@pulsar-stellar/sdk';

const client = new PulsarClient({
  indexerUrl: 'https://indexer.example.com',
  rpcUrl: 'https://soroban-testnet.stellar.org', // only the RPC path uses this
  network: 'testnet',
});
```

`indexerUrl` is required even if you only use the RPC path, which never calls it. There is no hosted public indexer yet, so pass your own.

## `client.config`

```ts
get config(): ResolvedPulsarConfig
```

The configuration in force, with defaults filled in. Frozen: a client's config is fixed at construction. This getter is also how you hand the config to the RPC functions, which take a `ResolvedPulsarConfig`:

```ts
import { fetchLiveEvents } from '@pulsar-stellar/sdk';
const page = await fetchLiveEvents(client.config, { startLedger: 1_000_000 });
```

## `client.ping()`

```ts
ping(): Promise<PingResult>
```

Checks that the indexer is reachable and reports what it says about itself. Use it to fail fast at startup. Round-trip latency is measured by the SDK and reported separately from the indexer's own `took_ms`.

A reachable indexer that reports `ok: false` resolves to a `PingResult` with `ok: false`. It is a successful call with bad news, not a throw. See [Error handling](/pulsar-stellar-sdk/concepts/error-handling.md#ping-reports-bad-news-without-throwing).

* **Returns** [`PingResult`](/pulsar-stellar-sdk/sdk-reference/schemas-and-types.md#pingresult).
* **Throws** [`PulsarNetworkError`](/pulsar-stellar-sdk/sdk-reference/errors.md#pulsarnetworkerror) if the indexer is unreachable, times out, returns a non-success status, answers with non-JSON, or returns the error envelope.
* **Throws** [`PulsarValidationError`](/pulsar-stellar-sdk/sdk-reference/errors.md#pulsarvalidationerror) if the response is JSON but not the health shape this SDK version expects.

## `client.registerContract(contractId)`

```ts
registerContract(contractId: string): Promise<ContractInfo>
```

Asks the indexer to start tracking a contract. Idempotent per ADR-018: registering an already-tracked contract succeeds and returns the existing record with its indexing progress untouched, so a caller that lost the response to a network failure can simply call again. The contract ID is validated before any request is sent.

* **Returns** [`ContractInfo`](/pulsar-stellar-sdk/sdk-reference/schemas-and-types.md#contractinfo).
* **Throws** `PulsarValidationError` if the ID is malformed or the response is the wrong shape; `PulsarNetworkError` on any transport failure or the error envelope.

## `client.getContract(contractId)`

```ts
getContract(contractId: string): Promise<ContractInfo | null>
```

Looks up one tracked contract. Returns `null` when the indexer says it is not tracking that contract, which per ADR-019 means a 404 carrying a `not_found` envelope. A bare 404, the kind a proxy or misroute produces, throws instead.

* **Returns** `ContractInfo`, or `null` for a well-formed absence.
* **Throws** `PulsarValidationError` if the ID is malformed or the response is the wrong shape; `PulsarNetworkError` on any failure other than a well-formed absence.

## `client.listContracts()`

```ts
listContracts(): Promise<ContractInfo[]>
```

Lists every contract the indexer tracks. Not paginated: the tracked list is bounded by what an operator registered, unlike event history. An empty list is `[]`, not `null`: the indexer answered, and the answer is that it tracks nothing yet.

* **Returns** `ContractInfo[]`, possibly empty.
* **Throws** `PulsarNetworkError` on any transport failure; `PulsarValidationError` if the response is the wrong shape.

## `client.events(contractId, query?)`

```ts
events(contractId: string, query?: EventQuery): Promise<EventsPage>
```

Queries one contract's decoded event history. Paging is by opaque cursor: pass [`nextCursor`](/pulsar-stellar-sdk/sdk-reference/schemas-and-types.md#eventspage) from a page back as `query.cursor`, and stop when it is `null`.

A contract the indexer is not tracking throws rather than returning an empty page, per ADR-021. "Not indexed" and "no matching events" are different facts.

* **`query`**: see [`EventQuerySchema`](/pulsar-stellar-sdk/sdk-reference/schemas-and-types.md#eventqueryschema). Every field is optional; `limit` defaults to `50` (max `500`) and `order` to `'desc'`. The query is strict: an unknown key is a `PulsarValidationError`, not a silently ignored field.
* **Returns** [`EventsPage`](/pulsar-stellar-sdk/sdk-reference/schemas-and-types.md#eventspage): `{ items, nextCursor }`.
* **Throws** `PulsarValidationError` if the ID or query is malformed, or the response is the wrong shape; `PulsarNetworkError` on any transport failure, including the 404 for an untracked contract.

```ts
let cursor: string | undefined;
do {
  const page = await client.events(contractId, cursor === undefined ? undefined : { cursor });
  for (const event of page.items) handle(event);
  cursor = page.nextCursor ?? undefined;
} while (cursor !== undefined);
```

## `client.eventStream(contractId, query?)`

```ts
eventStream(contractId: string, query?: EventQuery): AsyncIterable<DecodedEvent>
```

Iterates one contract's history, fetching pages only as the previous one is exhausted. Nothing is prefetched, so leaving the loop early stops the traversal without fetching a page nobody will read. It ends on its own when the history runs out, unlike the RPC stream.

Each iteration starts a fresh traversal. Iterating the same value twice, including concurrently, replays from the beginning rather than sharing a position. `query.cursor` sets the starting point, so you can resume from a cursor you stored earlier.

* **Throws**, out of the `for await` loop, `PulsarValidationError` or `PulsarNetworkError` on whichever page fails. Events already yielded stay valid; only the rest is lost.

```ts
for await (const event of client.eventStream(contractId, { name: 'transfer' })) {
  console.log(event.ledger, event.name);
}
```

## `client.event(eventId)`

```ts
event(eventId: string): Promise<DecodedEvent | null>
```

Fetches one decoded event by its id. Event ids are unique across the indexer, so no contract is needed to resolve one. Returns `null` for a well-formed absence (a 404 with a `not_found` envelope); a bare 404 throws.

* **`eventId`**: a string of digits. It is opaque and never parsed into a number, since the underlying key can exceed `2^53`.
* **Returns** `DecodedEvent`, or `null`.
* **Throws** `PulsarValidationError` if the id is malformed or the response is the wrong shape; `PulsarNetworkError` on any failure other than a well-formed absence.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://emeditweb.gitbook.io/pulsar-stellar-sdk/sdk-reference/client.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
