Skip to content

Commit 537e818

Browse files
authored
Add getLedgers method to Rpc Server (#1231)
* add getLedgers method to Rpc Server * move GetEventsRequest to api file and make start/end ledger and cursor mutually exclusive * create alias for Api.GetEventsRequest in Server for backward compatibility
1 parent 7132fde commit 537e818

File tree

6 files changed

+482
-21
lines changed

6 files changed

+482
-21
lines changed

.git-blame-ignore-revs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
030c448da30ff159419dcc92e2921ef578cca426
1+
2c766e53ca4c47995905f43110719538c0d946b6

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ A breaking change will get clearly marked in this log.
88

99
### Added
1010
* `Spec.scValToNative` now supports parsing `Option`s ([#1228](https://github.com/stellar/js-stellar-sdk/pull/1228)).
11+
* Added `getLedgers` method to `rpc.Server` for retreiving ledger data. ([#1231](https://github.com/stellar/js-stellar-sdk/pull/1231)).
12+
1113
### Fixed
1214
* `Spec.scValToNative` returns `null` for `void`s or `Option`als instead of the ambiguous `undefined` ([#1228](https://github.com/stellar/js-stellar-sdk/pull/1228)).
15+
* The `getEvents` API now requires either a start and end ledger or a cursor to be provided ([#1231](https://github.com/stellar/js-stellar-sdk/pull/1231)).
16+
17+
### Deprecated
18+
* `GetEventsRequest` interface moved from `server.ts` to `api.ts` ([#1231](https://github.com/stellar/js-stellar-sdk/pull/1231)).
1319

1420
## [v14.2.0](https://github.com/stellar/js-stellar-sdk/compare/v14.1.1...v14.2.0)
1521

src/rpc/api.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,58 @@ export namespace Api {
224224
oldestLedgerCloseTime: string;
225225
}
226226

227+
/**
228+
* Request parameters for fetching events from the Stellar network.
229+
*
230+
* **Important**: This type enforces mutually exclusive pagination modes:
231+
* - **Ledger range mode**: Use `startLedger` and `endLedger` (cursor must be omitted)
232+
* - **Cursor pagination mode**: Use `cursor` (startLedger and endLedger must be omitted)
233+
*
234+
* @example
235+
* // ✅ Correct: Ledger range mode
236+
* const rangeRequest: GetEventsRequest = {
237+
* filters: [],
238+
* startLedger: 1000,
239+
* endLedger: 2000,
240+
* limit: 100
241+
* };
242+
*
243+
* @example
244+
* // ✅ Correct: Cursor pagination mode
245+
* const cursorRequest: GetEventsRequest = {
246+
* filters: [],
247+
* cursor: "some-cursor-value",
248+
* limit: 100
249+
* };
250+
*
251+
* @example
252+
* // ❌ Invalid: Cannot mix cursor with ledger range
253+
* const invalidRequest = {
254+
* filters: [],
255+
* startLedger: 1000, // ❌ Cannot use with cursor
256+
* endLedger: 2000, // ❌ Cannot use with cursor
257+
* cursor: "cursor", // ❌ Cannot use with ledger range
258+
* limit: 100
259+
* };
260+
*
261+
* @see {@link https://developers.stellar.org/docs/data/rpc/api-reference/methods/getEvents | getEvents API reference}
262+
*/
263+
export type GetEventsRequest =
264+
| {
265+
filters: Api.EventFilter[];
266+
startLedger: number;
267+
endLedger: number;
268+
cursor?: never; // explicitly exclude cursor
269+
limit?: number;
270+
}
271+
| {
272+
filters: Api.EventFilter[];
273+
cursor: string;
274+
startLedger?: never; // explicitly exclude startLedger
275+
endLedger?: never; // explicitly exclude endLedger
276+
limit?: number;
277+
};
278+
227279
export interface GetEventsResponse extends RetentionState {
228280
events: EventResponse[];
229281
cursor: string;
@@ -508,4 +560,94 @@ export namespace Api {
508560
liveUntilLedgerSeq?: number;
509561
};
510562
}
563+
564+
/**
565+
* Request parameters for fetching a sequential list of ledgers.
566+
*
567+
* This type supports two distinct pagination modes that are mutually exclusive:
568+
* - **Ledger-based pagination**: Use `startLedger` to begin fetching from a specific ledger sequence
569+
* - **Cursor-based pagination**: Use `cursor` to continue from a previous response's pagination token
570+
*
571+
* @typedef {object} GetLedgersRequest
572+
* @property {number} [startLedger] - Ledger sequence number to start fetching from (inclusive).
573+
* Must be omitted if cursor is provided. Cannot be less than the oldest ledger or greater
574+
* than the latest ledger stored on the RPC node.
575+
* @property {object} [pagination] - Pagination configuration for the request.
576+
* @property {string} [pagination.cursor] - Page cursor for continuing pagination from a previous
577+
* response. Must be omitted if startLedger is provided.
578+
* @property {number} [pagination.limit=100] - Maximum number of ledgers to return per page.
579+
* Valid range: 1-10000. Defaults to 100 if not specified.
580+
*
581+
* @example
582+
* // Ledger-based pagination - start from specific ledger
583+
* const ledgerRequest: GetLedgersRequest = {
584+
* startLedger: 36233,
585+
* pagination: {
586+
* limit: 10
587+
* }
588+
* };
589+
*
590+
* @example
591+
* // Cursor-based pagination - continue from previous response
592+
* const cursorRequest: GetLedgersRequest = {
593+
* pagination: {
594+
* cursor: "36234",
595+
* limit: 5
596+
* }
597+
* };
598+
*
599+
* @see {@link https://developers.stellar.org/docs/data/rpc/api-reference/methods/getLedgers | getLedgers API reference}
600+
*/
601+
export type GetLedgersRequest =
602+
| {
603+
startLedger: number;
604+
pagination?: {
605+
cursor?: never;
606+
limit?: number;
607+
};
608+
}
609+
| {
610+
startLedger?: never;
611+
pagination: {
612+
cursor: string;
613+
limit?: number;
614+
};
615+
};
616+
617+
/** @see https://developers.stellar.org/docs/data/rpc/api-reference/methods/getLedgers */
618+
export interface GetLedgersResponse {
619+
ledgers: LedgerResponse[];
620+
latestLedger: number;
621+
latestLedgerCloseTime: number;
622+
oldestLedger: number;
623+
oldestLedgerCloseTime: number;
624+
cursor: string;
625+
}
626+
627+
export interface RawGetLedgersResponse {
628+
ledgers: RawLedgerResponse[];
629+
latestLedger: number;
630+
latestLedgerCloseTime: number;
631+
oldestLedger: number;
632+
oldestLedgerCloseTime: number;
633+
cursor: string;
634+
}
635+
636+
export interface LedgerResponse {
637+
hash: string;
638+
sequence: number;
639+
ledgerCloseTime: string;
640+
headerXdr: xdr.LedgerHeaderHistoryEntry;
641+
metadataXdr: xdr.LedgerCloseMeta;
642+
}
643+
644+
export interface RawLedgerResponse {
645+
hash: string;
646+
sequence: number;
647+
ledgerCloseTime: string;
648+
/** a base-64 encoded {@link xdr.LedgerHeaderHistoryEntry} instance */
649+
headerXdr: string;
650+
/** a base-64 encoded {@link xdr.LedgerCloseMeta} instance */
651+
metadataXdr: string;
652+
}
511653
}

src/rpc/parsers.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,30 @@ export function parseRawSimulation(
267267

268268
return parseSuccessful(sim, base);
269269
}
270+
271+
export function parseRawLedger(raw: Api.RawLedgerResponse): Api.LedgerResponse {
272+
if (!raw.metadataXdr || !raw.headerXdr) {
273+
let missingFields: string;
274+
if (!raw.metadataXdr && !raw.headerXdr) {
275+
missingFields = "metadataXdr and headerXdr";
276+
} else if (!raw.metadataXdr) {
277+
missingFields = "metadataXdr";
278+
} else {
279+
missingFields = "headerXdr";
280+
}
281+
throw new TypeError(`invalid ledger missing fields: ${missingFields}`);
282+
}
283+
284+
const metadataXdr = xdr.LedgerCloseMeta.fromXDR(raw.metadataXdr, "base64");
285+
const headerXdr = xdr.LedgerHeaderHistoryEntry.fromXDR(
286+
raw.headerXdr,
287+
"base64",
288+
);
289+
return {
290+
hash: raw.hash,
291+
sequence: raw.sequence,
292+
ledgerCloseTime: raw.ledgerCloseTime,
293+
metadataXdr,
294+
headerXdr,
295+
};
296+
}

src/rpc/server.ts

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
parseRawEvents,
3030
parseRawTransactions,
3131
parseTransactionInfo,
32+
parseRawLedger,
3233
} from "./parsers";
3334
import { Utils } from "../utils";
3435

@@ -53,15 +54,6 @@ export enum Durability {
5354
Persistent = "persistent",
5455
}
5556

56-
/**
57-
* @typedef {object} GetEventsRequest Describes the complex filter combinations available for event queries.
58-
* @property {Array.<module:rpc.Api.EventFilter>} filters Filters to use when querying events from the RPC server.
59-
* @property {number} [startLedger] Ledger number (inclusive) to begin querying events.
60-
* @property {string} [cursor] Page cursor (exclusive) to begin querying events.
61-
* @property {number} [limit=100] The maximum number of events that should be returned in the RPC response.
62-
* @memberof module:rpc.Server
63-
*/
64-
6557
/**
6658
* @typedef {object} ResourceLeeway Describes additional resource leeways for transaction simulation.
6759
* @property {number} cpuInstructions Simulate the transaction with more CPU instructions available.
@@ -77,13 +69,11 @@ export enum Durability {
7769
*/
7870

7971
export namespace RpcServer {
80-
export interface GetEventsRequest {
81-
filters: Api.EventFilter[];
82-
startLedger?: number; // either this or cursor
83-
endLedger?: number; // either this or cursor
84-
cursor?: string; // either this or startLedger
85-
limit?: number;
86-
}
72+
/**
73+
* @deprecated Use `Api.GetEventsRequest` instead.
74+
* @see {@link Api.GetEventsRequest}
75+
*/
76+
export type GetEventsRequest = Api.GetEventsRequest;
8777

8878
export interface PollingOptions {
8979
attempts?: number;
@@ -755,20 +745,21 @@ export class RpcServer {
755745
/**
756746
* Fetch all events that match a given set of filters.
757747
*
758-
* The given filters (see {@link module:rpc.Api.EventFilter | Api.EventFilter}
748+
* The given filters (see {@link Api.EventFilter}
759749
* for detailed fields) are combined only in a logical OR fashion, and all of
760750
* the fields in each filter are optional.
761751
*
762752
* To page through events, use the `pagingToken` field on the relevant
763753
* {@link Api.EventResponse} object to set the `cursor` parameter.
764754
*
765-
* @param {module:rpc.Server.GetEventsRequest} request Event filters
755+
* @param {Api.GetEventsRequest} request Event filters {@link Api.GetEventsRequest},
766756
* @returns {Promise<Api.GetEventsResponse>} A paginatable set of the events
767757
* matching the given event filters
768758
*
769759
* @see {@link https://developers.stellar.org/docs/data/rpc/api-reference/methods/getEvents | getEvents docs}
770760
*
771761
* @example
762+
*
772763
* server.getEvents({
773764
* startLedger: 1000,
774765
* endLedger: 2000,
@@ -794,14 +785,14 @@ export class RpcServer {
794785
*/
795786
// eslint-disable-next-line require-await
796787
public async getEvents(
797-
request: RpcServer.GetEventsRequest,
788+
request: Api.GetEventsRequest,
798789
): Promise<Api.GetEventsResponse> {
799790
return this._getEvents(request).then(parseRawEvents);
800791
}
801792

802793
// eslint-disable-next-line require-await
803794
public async _getEvents(
804-
request: RpcServer.GetEventsRequest,
795+
request: Api.GetEventsRequest,
805796
): Promise<Api.RawGetEventsResponse> {
806797
return jsonrpc.postObject(this.serverURL.toString(), "getEvents", {
807798
filters: request.filters ?? [],
@@ -1279,4 +1270,66 @@ export class RpcServer {
12791270
},
12801271
};
12811272
}
1273+
1274+
/**
1275+
* Fetch a detailed list of ledgers starting from a specified point.
1276+
*
1277+
* Returns ledger data with support for pagination as long as the requested
1278+
* pages fall within the history retention of the RPC provider.
1279+
*
1280+
* @param {Api.GetLedgersRequest} request - The request parameters for fetching ledgers. {@link Api.GetLedgersRequest}
1281+
* @returns {Promise<Api.GetLedgersResponse>} A promise that resolves to the
1282+
* ledgers response containing an array of ledger data and pagination info. {@link Api.GetLedgersResponse}
1283+
*
1284+
* @throws {Error} If startLedger is less than the oldest ledger stored in this
1285+
* node, or greater than the latest ledger seen by this node.
1286+
*
1287+
* @see {@link https://developers.stellar.org/docs/data/rpc/api-reference/methods/getLedgers | getLedgers docs}
1288+
*
1289+
* @example
1290+
* // Fetch ledgers starting from a specific sequence number
1291+
* server.getLedgers({
1292+
* startLedger: 36233,
1293+
* limit: 10
1294+
* }).then((response) => {
1295+
* console.log("Ledgers:", response.ledgers);
1296+
* console.log("Latest Ledger:", response.latestLedger);
1297+
* console.log("Cursor:", response.cursor);
1298+
* });
1299+
*
1300+
* @example
1301+
* // Paginate through ledgers using cursor
1302+
* const firstPage = await server.getLedgers({
1303+
* startLedger: 36233,
1304+
* limit: 5
1305+
* });
1306+
*
1307+
* const nextPage = await server.getLedgers({
1308+
* cursor: firstPage.cursor,
1309+
* limit: 5
1310+
* });
1311+
*/
1312+
// eslint-disable-next-line require-await
1313+
public async getLedgers(
1314+
request: Api.GetLedgersRequest,
1315+
): Promise<Api.GetLedgersResponse> {
1316+
return this._getLedgers(request).then((raw) => {
1317+
const result: Api.GetLedgersResponse = {
1318+
ledgers: (raw.ledgers || []).map(parseRawLedger),
1319+
latestLedger: raw.latestLedger,
1320+
latestLedgerCloseTime: raw.latestLedgerCloseTime,
1321+
oldestLedger: raw.oldestLedger,
1322+
oldestLedgerCloseTime: raw.oldestLedgerCloseTime,
1323+
cursor: raw.cursor,
1324+
};
1325+
return result;
1326+
});
1327+
}
1328+
1329+
// eslint-disable-next-line require-await
1330+
public async _getLedgers(
1331+
request: Api.GetLedgersRequest,
1332+
): Promise<Api.RawGetLedgersResponse> {
1333+
return jsonrpc.postObject(this.serverURL.toString(), "getLedgers", request);
1334+
}
12821335
}

0 commit comments

Comments
 (0)