data

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Stellar Data: RPC + Horizon

Stellar数据: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.
用于读取链上状态的API访问方式。Stellar RPC是新项目的首选入口;Horizon仍适用于旧版及历史查询工作流。若需获取RPC 7天窗口之外的深度历史数据,可使用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
  • 调用Stellar RPC方法(
    getLatestLedger
    getLedgerEntries
    getEvents
    simulateTransaction
    sendTransaction
  • 查询Horizon端点(账户、交易、操作、影响、账本)
  • 流式获取实时事件或操作
  • 获取RPC 7天窗口之外的历史数据(Hubble、Galexie)
  • 为特定工作流选择RPC或Horizon

Related skills

相关技能

  • Building transactions to send →
    ../dapp/SKILL.md
  • Soroban contract simulation and event emission →
    ../soroban/SKILL.md
  • Asset balance and trustline lookups →
    ../assets/SKILL.md
  • Standards (SEP-7 deeplinks, SEP-10 auth) →
    ../standards/SKILL.md

  • 构建待发送交易 →
    ../dapp/SKILL.md
  • Soroban合约模拟与事件触发 →
    ../soroban/SKILL.md
  • 资产余额与信任线查询 →
    ../assets/SKILL.md
  • 标准协议(SEP-7深度链接、SEP-10认证) →
    ../standards/SKILL.md

Overview

概述

Stellar provides two API paradigms:
APIStatusUse Case
Stellar RPCPreferredSoroban, real-time state, new projects
HorizonLegacy-focusedHistorical data, legacy applications
Recommendation: Use Stellar RPC for all new projects. Use Horizon mainly for historical queries and legacy compatibility paths.
Stellar提供两种API范式:
API状态使用场景
Stellar RPC推荐使用Soroban、实时状态查询、新项目
Horizon聚焦旧版兼容历史数据查询、旧版应用
建议:所有新项目均使用Stellar RPC。Horizon主要用于历史查询和旧版兼容场景。

Quick Navigation

快速导航

Stellar RPC

Stellar RPC

Endpoints

端点

Note: SDF directly provides Futurenet public RPC. For Mainnet RPC, select a provider from the RPC providers directory.
NetworkRPC URL
MainnetProvider-specific endpoint (see RPC providers directory)
Testnet
https://soroban-testnet.stellar.org
Futurenet
https://rpc-futurenet.stellar.org
Local
http://localhost:8000/soroban/rpc
注意:SDF直接提供Futurenet公共RPC。主网RPC需从RPC服务商目录选择服务商。
网络RPC URL
主网服务商专属端点(详见RPC服务商目录
测试网
https://soroban-testnet.stellar.org
Futurenet
https://rpc-futurenet.stellar.org
本地节点
http://localhost:8000/soroban/rpc

Setup

配置

typescript
import * as StellarSdk from "@stellar/stellar-sdk";

const rpc = new StellarSdk.rpc.Server("https://soroban-testnet.stellar.org");
typescript
import * as StellarSdk from "@stellar/stellar-sdk";

const rpc = new StellarSdk.rpc.Server("https://soroban-testnet.stellar.org");

Key Methods

核心方法

Get Account

获取账户信息

typescript
const account = await rpc.getAccount(publicKey);
// Returns account with sequence number for transaction building
typescript
const account = await rpc.getAccount(publicKey);
// 返回包含交易构建所需序列号的账户信息

Get Health

检查服务健康状态

typescript
const health = await rpc.getHealth();
// { status: "healthy" }
typescript
const health = await rpc.getHealth();
// { status: "healthy" }

Get Latest Ledger

获取最新账本

typescript
const ledger = await rpc.getLatestLedger();
// { id: "...", sequence: 123456, protocolVersion: 25 }
typescript
const ledger = await rpc.getLatestLedger();
// { id: "...", sequence: 123456, protocolVersion: 25 }

Get Ledger Entries

获取账本条目

typescript
// Read contract storage
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()
  );
}
typescript
// 读取合约存储
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

模拟交易

typescript
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);
}
typescript
const simulation = await rpc.simulateTransaction(transaction);

if (StellarSdk.rpc.Api.isSimulationError(simulation)) {
  console.error("模拟失败:", simulation.error);
} else if (StellarSdk.rpc.Api.isSimulationSuccess(simulation)) {
  console.log("成本:", simulation.cost);
  console.log("结果:", simulation.result);
}

Send Transaction

发送交易

typescript
const response = await rpc.sendTransaction(signedTransaction);

if (response.status === "PENDING") {
  // Poll for result
  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);
  }
}
typescript
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("成功:", result.returnValue);
  } else {
    console.error("失败:", result.status);
  }
}

Get Transaction

获取交易信息

typescript
const tx = await rpc.getTransaction(txHash);
// status: "SUCCESS" | "FAILED" | "NOT_FOUND"
// returnValue: ScVal (for contract calls)
// ledger: number
typescript
const tx = await rpc.getTransaction(txHash);
// status: "SUCCESS" | "FAILED" | "NOT_FOUND"
// returnValue: ScVal(合约调用场景)
// ledger: 数字

Get Events

获取事件

typescript
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);
}
typescript
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.topic, event.value);
}

RPC Limitations

RPC限制

  • 7-day history for most methods:
    getTransaction
    ,
    getEvents
    , etc. only cover recent data
  • getLedgers
    exception
    : "Infinite Scroll" feature queries any ledger back to genesis via the data lake
  • No streaming: Poll for updates (no WebSocket)
  • Contract-focused: Limited classic Stellar data
  • 多数方法仅支持7天历史
    getTransaction
    getEvents
    等仅覆盖近期数据
  • getLedgers
    例外
    :"无限滚动"功能可通过数据湖查询从创世区块到当前的任意账本
  • 无原生流处理:需通过轮询获取更新(无WebSocket支持)
  • 聚焦合约场景:对经典Stellar数据的支持有限

Horizon API (Legacy)

Horizon API(旧版)

Endpoints

端点

NetworkHorizon URL
Mainnet
https://horizon.stellar.org
Testnet
https://horizon-testnet.stellar.org
Local
http://localhost:8000
网络Horizon URL
主网
https://horizon.stellar.org
测试网
https://horizon-testnet.stellar.org
本地节点
http://localhost:8000

Setup

配置

typescript
import * as StellarSdk from "@stellar/stellar-sdk";

const server = new StellarSdk.Horizon.Server("https://horizon-testnet.stellar.org");
typescript
import * as StellarSdk from "@stellar/stellar-sdk";

const server = new StellarSdk.Horizon.Server("https://horizon-testnet.stellar.org");

Common Operations

常见操作

Load Account

加载账户信息

typescript
const account = await server.loadAccount(publicKey);
// Full account details including balances, signers, data
typescript
const account = await server.loadAccount(publicKey);
// 包含余额、签名者、数据等完整账户详情

Get Account Balances

获取账户余额

typescript
const account = await server.loadAccount(publicKey);
for (const balance of account.balances) {
  if (balance.asset_type === "native") {
    console.log("XLM:", balance.balance);
  } else {
    console.log(`${balance.asset_code}:`, balance.balance);
  }
}
typescript
const account = await server.loadAccount(publicKey);
for (const balance of account.balances) {
  if (balance.asset_type === "native") {
    console.log("XLM:", balance.balance);
  } else {
    console.log(`${balance.asset_code}:`, balance.balance);
  }
}

Get Transactions

获取交易记录

typescript
// Account transactions
const transactions = await server
  .transactions()
  .forAccount(publicKey)
  .order("desc")
  .limit(10)
  .call();

// Specific transaction
const tx = await server
  .transactions()
  .transaction(txHash)
  .call();
typescript
// 账户交易记录
const transactions = await server
  .transactions()
  .forAccount(publicKey)
  .order("desc")
  .limit(10)
  .call();

// 特定交易详情
const tx = await server
  .transactions()
  .transaction(txHash)
  .call();

Get Operations

获取操作记录

typescript
const operations = await server
  .operations()
  .forAccount(publicKey)
  .order("desc")
  .limit(20)
  .call();

for (const op of operations.records) {
  console.log(op.type, op.created_at);
}
typescript
const operations = await server
  .operations()
  .forAccount(publicKey)
  .order("desc")
  .limit(20)
  .call();

for (const op of operations.records) {
  console.log(op.type, op.created_at);
}

Get Payments

获取支付记录

typescript
const payments = await server
  .payments()
  .forAccount(publicKey)
  .order("desc")
  .call();

for (const payment of payments.records) {
  if (payment.type === "payment") {
    console.log(
      `${payment.from} -> ${payment.to}: ${payment.amount} ${payment.asset_code || "XLM"}`
    );
  }
}
typescript
const payments = await server
  .payments()
  .forAccount(publicKey)
  .order("desc")
  .call();

for (const payment of payments.records) {
  if (payment.type === "payment") {
    console.log(
      `${payment.from} -> ${payment.to}: ${payment.amount} ${payment.asset_code || "XLM"}`
    );
  }
}

Get Effects

获取影响记录

typescript
const effects = await server
  .effects()
  .forAccount(publicKey)
  .limit(50)
  .call();
typescript
const effects = await server
  .effects()
  .forAccount(publicKey)
  .limit(50)
  .call();

Streaming (Server-Sent Events)

流处理(服务器发送事件)

typescript
// Stream transactions
const closeHandler = server
  .transactions()
  .forAccount(publicKey)
  .cursor("now")
  .stream({
    onmessage: (tx) => {
      console.log("New transaction:", tx.hash);
    },
    onerror: (error) => {
      console.error("Stream error:", error);
    },
  });

// Close stream when done
closeHandler();
typescript
// 流式获取交易
const closeHandler = server
  .transactions()
  .forAccount(publicKey)
  .cursor("now")
  .stream({
    onmessage: (tx) => {
      console.log("新交易:", tx.hash);
    },
    onerror: (error) => {
      console.error("流处理错误:", error);
    },
  });

// 使用完毕后关闭流
closeHandler();

Submit Transaction

提交交易

typescript
try {
  const result = await server.submitTransaction(signedTransaction);
  console.log("Success:", result.hash);
} catch (error) {
  if (error.response?.data?.extras?.result_codes) {
    console.error("Error codes:", error.response.data.extras.result_codes);
  }
}
typescript
try {
  const result = await server.submitTransaction(signedTransaction);
  console.log("成功:", result.hash);
} catch (error) {
  if (error.response?.data?.extras?.result_codes) {
    console.error("错误码:", error.response.data.extras.result_codes);
  }
}

Pagination

分页

typescript
// First page
let page = await server.transactions().forAccount(publicKey).limit(10).call();

// Next page
if (page.records.length > 0) {
  page = await page.next();
}

// Previous page
page = await page.prev();
typescript
// 第一页
let page = await server.transactions().forAccount(publicKey).limit(10).call();

// 下一页
if (page.records.length > 0) {
  page = await page.next();
}

// 上一页
page = await page.prev();

Migration: Horizon to RPC

迁移:从Horizon到RPC

Account Loading

账户加载

typescript
// Horizon (old)
const account = await horizonServer.loadAccount(publicKey);

// RPC (new)
const account = await rpc.getAccount(publicKey);
// Note: RPC returns less data, just what's needed for transactions
typescript
// Horizon(旧版)
const account = await horizonServer.loadAccount(publicKey);

// RPC(新版)
const account = await rpc.getAccount(publicKey);
// 注意:RPC返回的数据更少,仅包含交易构建所需内容

Transaction Submission

交易提交

typescript
// Horizon (for classic transactions)
const result = await horizonServer.submitTransaction(tx);

// RPC (for Soroban transactions)
const response = await rpc.sendTransaction(tx);
const result = await pollForResult(response.hash);
typescript
// Horizon(适用于经典交易)
const result = await horizonServer.submitTransaction(tx);

// RPC(适用于Soroban交易)
const response = await rpc.sendTransaction(tx);
const result = await pollForResult(response.hash);

Historical Data

历史数据

typescript
// Horizon - full history
const allTxs = await horizonServer
  .transactions()
  .forAccount(publicKey)
  .call();

// RPC - most methods limited to 7 days
// Exception: getLedgers can query back to genesis (Infinite Scroll)
// For full historical data, use:
// 1. Hubble (SDF's BigQuery dataset)
// 2. Galexie (data pipeline)
// 3. Your own indexer
typescript
// Horizon - 完整历史数据
const allTxs = await horizonServer
  .transactions()
  .forAccount(publicKey)
  .call();

// RPC - 多数方法仅支持7天数据
// 例外:getLedgers可通过无限滚动查询至创世区块
// 如需完整历史数据,可使用:
// 1. Hubble(SDF的BigQuery数据集)
// 2. Galexie(数据处理管道)
// 3. 自定义索引器

Streaming Replacement

流处理替代方案

typescript
// Horizon - native streaming
server.payments().stream({ onmessage: handlePayment });

// RPC - polling (no native streaming)
async function pollForUpdates() {
  const lastLedger = await rpc.getLatestLedger();
  // Check for new events/transactions
  // Repeat on interval
}
setInterval(pollForUpdates, 5000);
typescript
// Horizon - 原生流处理
server.payments().stream({ onmessage: handlePayment });

// RPC - 轮询(无原生流处理)
async function pollForUpdates() {
  const lastLedger = await rpc.getLatestLedger();
  // 检查新事件/交易
  // 按间隔重复执行
}
setInterval(pollForUpdates, 5000);

Historical Data Access

历史数据访问

For data older than 7 days (not available via most RPC methods;
getLedgers
can reach genesis via Infinite Scroll):
对于超过7天的数据(多数RPC方法无法获取;
getLedgers
可通过无限滚动查询至创世区块):

Hubble (BigQuery)

Hubble(BigQuery)

sql
-- Query Stellar data in BigQuery
SELECT *
FROM `crypto-stellar.crypto_stellar.history_transactions`
WHERE source_account = 'G...'
ORDER BY created_at DESC
LIMIT 100
sql
-- 在BigQuery中查询Stellar数据
SELECT *
FROM `crypto-stellar.crypto_stellar.history_transactions`
WHERE source_account = 'G...'
ORDER BY created_at DESC
LIMIT 100

Galexie

Galexie

Self-hosted data pipeline for processing Stellar ledger data:
自托管的数据处理管道,用于处理Stellar账本数据:

Data Lake

数据湖

RPC "Infinite Scroll" is powered by the Stellar data lake — a cloud-based object store (SEP-0054 format):
RPC的"无限滚动"功能由Stellar数据湖提供支持——一个基于云的对象存储(SEP-0054格式):

Third-Party Indexers

第三方索引器

For complex queries, event streaming, or custom data pipelines beyond what RPC/Horizon provide:
如需复杂查询、事件流或超出RPC/Horizon能力的自定义数据管道:

Network Configuration

网络配置

For a React/Next.js-specific setup, see frontend-stellar-sdk.md. For mainnet RPC, set
STELLAR_MAINNET_RPC_URL
from a provider in the RPC providers directory.
React/Next.js专属配置请参考frontend-stellar-sdk.md。 主网RPC需从RPC服务商目录获取服务商的
STELLAR_MAINNET_RPC_URL

Environment-Based Setup

基于环境变量的配置

typescript
// lib/stellar-config.ts
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,
    friendbotUrl: null,
  },
  testnet: {
    rpcUrl: "https://soroban-testnet.stellar.org",
    horizonUrl: "https://horizon-testnet.stellar.org",
    networkPassphrase: StellarSdk.Networks.TESTNET,
    friendbotUrl: "https://friendbot.stellar.org",
  },
  local: {
    rpcUrl: "http://localhost:8000/soroban/rpc",
    horizonUrl: "http://localhost:8000",
    networkPassphrase: "Standalone Network ; February 2017",
    friendbotUrl: "http://localhost:8000/friendbot",
  },
};

const network = process.env.STELLAR_NETWORK || "testnet";
export const config = configs[network];

export const rpc = new StellarSdk.rpc.Server(config.rpcUrl);
export const horizon = new StellarSdk.Horizon.Server(config.horizonUrl);
typescript
// lib/stellar-config.ts
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(`缺失必要环境变量: ${name}`);
  return value;
};

const configs: Record<string, NetworkConfig> = {
  mainnet: {
    rpcUrl: requireEnv("STELLAR_MAINNET_RPC_URL"),
    horizonUrl: "https://horizon.stellar.org",
    networkPassphrase: StellarSdk.Networks.PUBLIC,
    friendbotUrl: null,
  },
  testnet: {
    rpcUrl: "https://soroban-testnet.stellar.org",
    horizonUrl: "https://horizon-testnet.stellar.org",
    networkPassphrase: StellarSdk.Networks.TESTNET,
    friendbotUrl: "https://friendbot.stellar.org",
  },
  local: {
    rpcUrl: "http://localhost:8000/soroban/rpc",
    horizonUrl: "http://localhost:8000",
    networkPassphrase: "Standalone Network ; February 2017",
    friendbotUrl: "http://localhost:8000/friendbot",
  },
};

const network = process.env.STELLAR_NETWORK || "testnet";
export const config = configs[network];

export const rpc = new StellarSdk.rpc.Server(config.rpcUrl);
export const horizon = new StellarSdk.Horizon.Server(config.horizonUrl);

Best Practices

最佳实践

Use RPC for:

使用RPC的场景:

  • New application development
  • Soroban contract interactions
  • Transaction simulation and submission
  • Real-time account state
  • 新应用开发
  • Soroban合约交互
  • 交易模拟与提交
  • 实时账户状态查询

Use Horizon for:

使用Horizon的场景:

  • Historical transaction queries
  • Payment streaming
  • Legacy application maintenance
  • Rich account metadata
  • 历史交易查询
  • 支付流处理
  • 旧版应用维护
  • 丰富的账户元数据查询

Error Handling

错误处理

typescript
// RPC errors
try {
  const result = await rpc.sendTransaction(tx);
} catch (error) {
  if (error.code === 400) {
    // Invalid transaction
  } else if (error.code === 503) {
    // Service unavailable
  }
}

// Horizon errors
try {
  const result = await horizon.submitTransaction(tx);
} catch (error) {
  const extras = error.response?.data?.extras;
  if (extras?.result_codes) {
    // Detailed error codes
    console.log("Transaction:", extras.result_codes.transaction);
    console.log("Operations:", extras.result_codes.operations);
  }
}
typescript
// RPC错误处理
try {
  const result = await rpc.sendTransaction(tx);
} catch (error) {
  if (error.code === 400) {
    // 无效交易
  } else if (error.code === 503) {
    // 服务不可用
  }
}

// Horizon错误处理
try {
  const result = await horizon.submitTransaction(tx);
} catch (error) {
  const extras = error.response?.data?.extras;
  if (extras?.result_codes) {
    // 详细错误码
    console.log("交易:", extras.result_codes.transaction);
    console.log("操作:", 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
typescript
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) {
        // Rate limited - exponential backoff
        await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
      } else {
        throw error;
      }
    }
  }
  throw lastError;
}
RPC和Horizon均有速率限制:
  • 重试时使用指数退避策略
  • 合理缓存响应结果
  • 高流量应用建议自行部署节点
typescript
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;
}