| name | data |
| description | Querying Stellar chain data via Stellar RPC (preferred) and Horizon (legacy). Covers RPC JSON-RPC methods, Horizon REST endpoints, streaming, pagination, historical queries, Hubble/Galexie for deep history, and the RPC/Horizon migration story. Use when reading balances, transactions, operations, ledgers, contract events, or building any indexer/analytics workflow. |
| user-invocable | true |
| argument-hint | [data task] |
Stellar Data: RPC + Horizon
API access for reading chain state. Stellar RPC is the preferred entry point for new projects; Horizon remains for legacy and historical-query workflows. For deeper history beyond RPC's 7-day window, use Hubble/Galexie.
When to use this skill
- Calling Stellar RPC methods (
getLatestLedger, getLedgerEntries, getEvents, simulateTransaction, sendTransaction)
- Querying Horizon endpoints (accounts, transactions, operations, effects, ledgers)
- Streaming live events or operations
- Pulling historical data beyond RPC's 7-day window (Hubble, Galexie)
- Choosing between RPC and Horizon for a given workflow
Related skills
- Building transactions to send →
../dapp/SKILL.md
- Smart contract simulation and event emission →
../smart-contracts/SKILL.md
- Asset balance and trustline lookups →
../assets/SKILL.md
- Standards (SEP-7 deeplinks, SEP-10 auth) →
../standards/SKILL.md
Overview
Stellar provides two API paradigms:
| API | Status | Use Case |
|---|
| Stellar RPC | Preferred | Smart contracts, real-time state, new projects |
| Horizon | Legacy-focused | Historical data, legacy applications |
Recommendation: Use Stellar RPC for all new projects. Use Horizon mainly for historical queries and legacy compatibility paths.
Read the file that matches the task
Stellar RPC
Endpoints
Note: SDF directly provides Futurenet public RPC. For Mainnet RPC, select a provider from the RPC providers directory.
| Network | RPC URL |
|---|
| Mainnet | Provider-specific endpoint (see RPC providers directory) |
| Testnet | https://soroban-testnet.stellar.org |
| Futurenet | https://rpc-futurenet.stellar.org |
| Local | http://localhost:8000/soroban/rpc |
Setup
import * as StellarSdk from "@stellar/stellar-sdk";
const rpc = new StellarSdk.rpc.Server("https://soroban-testnet.stellar.org");
Key Methods
Get Account
const account = await rpc.getAccount(publicKey);
Get Health
const health = await rpc.getHealth();
Get Latest Ledger
const ledger = await rpc.getLatestLedger();
Get Ledger Entries
const key = StellarSdk.xdr.LedgerKey.contractData(
new StellarSdk.xdr.LedgerKeyContractData({
contract: new StellarSdk.Address(contractId).toScAddress(),
key: StellarSdk.xdr.ScVal.scvSymbol("Counter"),
durability: StellarSdk.xdr.ContractDataDurability.persistent(),
})
);
const entries = await rpc.getLedgerEntries(key);
if (entries.entries.length > 0) {
const value = StellarSdk.scValToNative(
entries.entries[0].val.contractData().val()
);
}
Simulate Transaction
const simulation = await rpc.simulateTransaction(transaction);
if (StellarSdk.rpc.Api.isSimulationError(simulation)) {
console.error("Simulation failed:", simulation.error);
} else if (StellarSdk.rpc.Api.isSimulationSuccess(simulation)) {
console.log("Cost:", simulation.cost);
console.log("Result:", simulation.result);
}
Send Transaction
const response = await rpc.sendTransaction(signedTransaction);
if (response.status === "PENDING") {
let result = await rpc.getTransaction(response.hash);
while (result.status === "NOT_FOUND") {
await new Promise(r => setTimeout(r, 1000));
result = await rpc.getTransaction(response.hash);
}
if (result.status === "SUCCESS") {
console.log("Success:", result.returnValue);
} else {
console.error("Failed:", result.status);
}
}
Get Transaction
const tx = await rpc.getTransaction(txHash);
Get Events
const events = await rpc.getEvents({
startLedger: 1000000,
filters: [
{
type: "contract",
contractIds: [contractId],
topics: [
["*", StellarSdk.xdr.ScVal.scvSymbol("transfer").toXDR("base64")],
],
},
],
});
for (const event of events.events) {
console.log("Event:", event.topic, event.value);
}
RPC Limitations
- 7-day history for most methods:
getTransaction, getEvents, etc. only cover recent data
getLedgers exception: on a data-lake-backed provider, "Infinite Scroll" pages back past the retention window — as far as that provider's data lake reaches (potentially genesis). On a plain RPC instance it is bounded by getHealth().oldestLedger; requests older than that fail with -32600. Check before assuming depth.
- No streaming: Poll for updates (no WebSocket)
- Contract-focused: Limited classic Stellar data
Migration: Horizon to RPC
Account Loading
const account = await horizonServer.loadAccount(publicKey);
const account = await rpc.getAccount(publicKey);
Transaction Submission
const result = await horizonServer.submitTransaction(tx);
const response = await rpc.sendTransaction(tx);
const result = await pollForResult(response.hash);
Historical Data
const allTxs = await horizonServer
.transactions()
.forAccount(publicKey)
.call();
const { oldestLedger } = await rpc.getHealth();
Streaming Replacement
server.payments().stream({ onmessage: handlePayment });
async function pollForUpdates() {
const lastLedger = await rpc.getLatestLedger();
}
setInterval(pollForUpdates, 5000);
Historical Data Access
For data older than the RPC retention window (~7 days — not available via most RPC methods; getLedgers reaches further only on data-lake-backed providers, see Data Lake below):
Hubble (BigQuery)
SELECT *
FROM `crypto-stellar.crypto_stellar.history_transactions`
WHERE source_account = 'G...'
ORDER BY created_at DESC
LIMIT 100
Galexie
Self-hosted data pipeline for processing Stellar ledger data:
Data Lake
RPC "Infinite Scroll" is powered by the Stellar data lake — a cloud-based object store (SEP-0054 format). Deep getLedgers history is a property of the provider, not the method: an instance only serves history past its retention window if its operator wired a data lake in. Instances without one (the public SDF testnet RPC included) reject older start ledgers with JSON-RPC -32600 — compare your target against getHealth().oldestLedger before paging back.
Third-Party Indexers
For complex queries, event streaming, or custom data pipelines beyond what RPC/Horizon provide:
See the full indexer directory: https://developers.stellar.org/docs/data/indexers
Network Configuration
For a React/Next.js-specific setup, see the dapp skill.
For mainnet RPC, set STELLAR_MAINNET_RPC_URL from a provider in the RPC providers directory.
Environment-Based Setup
import * as StellarSdk from "@stellar/stellar-sdk";
type NetworkConfig = {
rpcUrl: string;
horizonUrl: string;
networkPassphrase: string;
friendbotUrl: string | null;
};
const requireEnv = (name: string): string => {
const value = process.env[name];
if (!value) throw new Error(`Missing required env var: ${name}`);
return value;
};
const configs: Record<string, () => NetworkConfig> = {
mainnet: () => ({
rpcUrl: requireEnv("STELLAR_MAINNET_RPC_URL"),
horizonUrl: "https://horizon.stellar.org",
networkPassphrase: StellarSdk.Networks.PUBLIC,
: ,
}),
: ({
: ,
: ,
: ..,
: ,
}),
: ({
: ,
: ,
: ,
: ,
}),
};
network = process.. || ;
makeConfig = configs[network];
(!makeConfig) ();
config = ();
rpc = ..(config.);
horizon = ..(config.);
Best Practices
Use RPC for:
- New application development
- Smart contract interactions
- Transaction simulation and submission
- Real-time account state
Use Horizon for:
- Historical transaction queries
- Payment streaming
- Legacy application maintenance
- Rich account metadata
Error Handling
try {
const result = await rpc.sendTransaction(tx);
} catch (error) {
if (error.code === 400) {
} else if (error.code === 503) {
}
}
try {
const result = await horizon.submitTransaction(tx);
} catch (error) {
const extras = error.response?.data?.extras;
if (extras?.result_codes) {
console.log("Transaction:", extras.result_codes.transaction);
console.log("Operations:", extras.result_codes.operations);
}
}
Rate Limiting
Both RPC and Horizon have rate limits:
- Use exponential backoff for retries
- Cache responses where appropriate
- Consider running your own nodes for high-volume applications
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
let lastError: Error;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (error.response?.status === 429) {
await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
} else {
throw error;
}
}
}
throw lastError;
}