{"version":3,"file":"horizon.mjs","names":[],"sources":["../../src/providers/horizon.ts"],"sourcesContent":["/**\n * Horizon provider: Stellar accounts, payments, transactions, ledgers and fee stats.\n *\n * Horizon is the HTTP API the Stellar Development Foundation serves over the network. The public\n * instance at horizon.stellar.org needs no key and keeps one year of history; `baseUrl` points the\n * provider at any other Horizon.\n *\n * https://developers.stellar.org/docs/data/apis/horizon\n */\n\nimport { getChain } from \"@agntn/chains\";\nimport { z } from \"zod\";\nimport { Provider } from \"../core/provider.js\";\nimport { buildQuery, normalizeBaseUrl } from \"../core/client.js\";\nimport { HORIZON_URL } from \"../core/endpoints.js\";\nimport { ExplorerError, UnsupportedChainError } from \"../core/errors.js\";\nimport { clampMaxResults, formatWei } from \"../core/types.js\";\nimport type {\n  Balance,\n  BlockInfo,\n  ChainKey,\n  GasData,\n  ProviderCapabilities,\n  ProviderConfig,\n  TokenBalance,\n  TokenBalanceOptions,\n  TokenTransfer,\n  TokenTransferOptions,\n  Transaction,\n  TxHistoryOptions,\n} from \"../core/types.js\";\n\n/** Stellar amounts carry seven decimals; the smallest unit is the stroop. */\nconst STROOP_DECIMALS = 7;\n/** Horizon caps every collection page at 200 records. */\nconst PAGE_LIMIT = 200;\n/** A history `page` is reached by walking cursors, one request per page. */\nconst MAX_PAGE = 10;\n/** Token transfers are filtered out of payments, so a read scans at most this many full pages. */\nconst TRANSFER_SCAN_PAGES = 5;\n/** Operations Horizon lists under payments that move a native or issued asset between accounts. */\nconst PAYMENT_TYPES: readonly string[] = [\n  \"payment\",\n  \"path_payment_strict_send\",\n  \"path_payment_strict_receive\",\n];\n/** Operations Horizon lists under payments, in the order the row selection prefers them. */\nconst PARTY_TYPES: readonly string[] = [\n  ...PAYMENT_TYPES,\n  \"create_account\",\n  \"account_merge\",\n  \"invoke_host_function\",\n];\n/** Soroban operations, the only ones that run contract code. */\nconst SOROBAN_TYPES: readonly string[] = [\n  \"invoke_host_function\",\n  \"extend_footprint_ttl\",\n  \"restore_footprint\",\n];\n\nfunction assertChain(chain: ChainKey): void {\n  if (chain !== \"stellar\") throw new UnsupportedChainError(chain, Horizon.key);\n}\n\nfunction assertAccount(address: string): void {\n  try {\n    getChain(\"stellar\").assertAddress(address);\n  } catch {\n    throw new ExplorerError(\"Invalid Stellar account id\", Horizon.key);\n  }\n}\n\nfunction hashSchema() {\n  return z.string().regex(/^[a-fA-F0-9]{64}$/);\n}\n\nfunction amountSchema() {\n  return z.string().regex(/^\\d+(?:\\.\\d{1,7})?$/);\n}\n\nfunction stroopsSchema() {\n  return z.union([z.string().regex(/^\\d+$/), z.number().int().nonnegative()]);\n}\n\nfunction addressTextSchema() {\n  return z.string().regex(/^[A-Z0-9]{1,69}$/);\n}\n\nfunction timestampSchema() {\n  return z.string().refine((value) => !Number.isNaN(Date.parse(value)));\n}\n\nfunction transactionSchema() {\n  return z.looseObject({\n    hash: hashSchema(),\n    ledger: z.number().int().positive(),\n    created_at: timestampSchema(),\n    source_account: addressTextSchema(),\n    fee_charged: stroopsSchema(),\n    operation_count: z.number().int().positive(),\n    successful: z.boolean(),\n  });\n}\n\nfunction balanceChangeSchema() {\n  return z.looseObject({\n    asset_type: z.string(),\n    asset_code: z.string().optional(),\n    asset_issuer: addressTextSchema().optional(),\n    type: z.string(),\n    from: addressTextSchema().optional(),\n    to: addressTextSchema().optional(),\n    amount: amountSchema(),\n  });\n}\n\nfunction operationSchema() {\n  return z.looseObject({\n    paging_token: z.string().regex(/^\\d+$/),\n    transaction_successful: z.boolean(),\n    source_account: addressTextSchema(),\n    type: z.string(),\n    created_at: timestampSchema(),\n    transaction_hash: hashSchema(),\n    asset_type: z.string().optional(),\n    asset_code: z.string().optional(),\n    asset_issuer: addressTextSchema().optional(),\n    from: addressTextSchema().optional(),\n    to: addressTextSchema().optional(),\n    amount: amountSchema().optional(),\n    starting_balance: amountSchema().optional(),\n    funder: addressTextSchema().optional(),\n    account: addressTextSchema().optional(),\n    into: addressTextSchema().optional(),\n    function: z.string().optional(),\n    asset_balance_changes: z.array(balanceChangeSchema()).nullish(),\n    transaction: transactionSchema().optional(),\n  });\n}\n\nfunction pageSchema<T extends z.ZodTypeAny>(record: T) {\n  return z.looseObject({ _embedded: z.object({ records: z.array(record) }) });\n}\n\n/** The asset fields Horizon repeats on balances, payments and balance changes. */\ninterface AssetHolder {\n  readonly asset_type?: string;\n  readonly asset_code?: string;\n  readonly asset_issuer?: string;\n}\n\n/** One movement a contract call caused, as Horizon lists it under `asset_balance_changes`. */\ninterface AssetChange extends AssetHolder {\n  readonly [key: string]: unknown;\n  readonly type: string;\n  readonly from?: string;\n  readonly to?: string;\n  readonly amount: string;\n}\n\n/** The transaction fields the rows read, whether joined onto a payment or fetched alone. */\ninterface HorizonTransaction {\n  readonly [key: string]: unknown;\n  readonly hash: string;\n  readonly ledger: number;\n  readonly created_at: string;\n  readonly source_account: string;\n  readonly fee_charged: string | number;\n  readonly operation_count: number;\n  readonly successful: boolean;\n}\n\n/** One operation record, with the fields of every payment-like type optional. */\ninterface HorizonOperation extends AssetHolder {\n  readonly [key: string]: unknown;\n  readonly paging_token: string;\n  readonly transaction_successful: boolean;\n  readonly source_account: string;\n  readonly type: string;\n  readonly created_at: string;\n  readonly transaction_hash: string;\n  readonly from?: string;\n  readonly to?: string;\n  readonly amount?: string;\n  readonly starting_balance?: string;\n  readonly funder?: string;\n  readonly account?: string;\n  readonly into?: string;\n  readonly function?: string;\n  /** Absent outside Soroban, `null` for a contract call that moved no asset. */\n  readonly asset_balance_changes?: readonly AssetChange[] | null;\n  readonly transaction?: HorizonTransaction;\n}\n\n/** Where a transfer lands in the normalized rows. */\ninterface RowContext {\n  readonly txHash: string;\n  readonly blockNumber: number;\n  readonly timestamp: string;\n}\n\n/** The parties and amounts one operation moves, in the shape of a transaction row. */\ninterface Parties {\n  readonly from: string;\n  readonly to: string | null;\n  readonly value: string;\n  readonly tokenTransfers: TokenTransfer[];\n}\n\n/**\n * Convert a Horizon decimal amount to stroops without touching floats.\n * @param {string} amount - Amount with up to seven decimals, as Horizon prints it.\n * @returns {string} Exact integer stroops.\n */\nfunction toStroops(amount: string): string {\n  const match = /^(\\d+)(?:\\.(\\d{1,7}))?$/.exec(amount);\n  if (!match) throw new ExplorerError(\"Invalid Horizon amount\", Horizon.key);\n  const whole = BigInt(match[1] ?? \"0\") * 10n ** BigInt(STROOP_DECIMALS);\n  return (whole + BigInt((match[2] ?? \"\").padEnd(STROOP_DECIMALS, \"0\"))).toString();\n}\n\nfunction toIso(timestamp: string): string {\n  return new Date(timestamp).toISOString();\n}\n\n/**\n * Name an issued asset the way SEP-11 spells it.\n * @param {AssetHolder} asset - Record carrying `asset_code` and `asset_issuer`.\n * @returns {string} `CODE:ISSUER`.\n */\nfunction assetId(asset: AssetHolder): string {\n  return `${asset.asset_code ?? \"\"}:${asset.asset_issuer ?? \"\"}`;\n}\n\nfunction isNative(asset: AssetHolder): boolean {\n  return asset.asset_type === \"native\";\n}\n\nfunction tokenTransfer(\n  asset: AssetHolder,\n  amount: string,\n  from: string,\n  to: string,\n  context: RowContext,\n): TokenTransfer {\n  const value = toStroops(amount);\n  return {\n    contract: assetId(asset),\n    symbol: asset.asset_code ?? \"\",\n    decimals: STROOP_DECIMALS,\n    value,\n    valueFormatted: formatWei(value, STROOP_DECIMALS),\n    from,\n    to,\n    ...context,\n  };\n}\n\nfunction createAccountParties(op: HorizonOperation): Parties {\n  return {\n    from: op.funder ?? op.source_account,\n    to: op.account ?? null,\n    value: toStroops(op.starting_balance ?? \"0\"),\n    tokenTransfers: [],\n  };\n}\n\n/**\n * Horizon does not put the merged amount on the operation, so the row keeps zero.\n * @param {HorizonOperation} op - An `account_merge` record.\n * @returns {Parties} Merged account and its destination.\n */\nfunction mergeParties(op: HorizonOperation): Parties {\n  return {\n    from: op.account ?? op.source_account,\n    to: op.into ?? null,\n    value: \"0\",\n    tokenTransfers: [],\n  };\n}\n\nfunction paymentParties(op: HorizonOperation, context: RowContext): Parties {\n  const from = op.from ?? op.source_account;\n  const to = op.to ?? null;\n  const amount = op.amount ?? \"0\";\n  if (isNative(op)) return { from, to, value: toStroops(amount), tokenTransfers: [] };\n  return {\n    from,\n    to,\n    value: \"0\",\n    tokenTransfers: [tokenTransfer(op, amount, from, to ?? \"\", context)],\n  };\n}\n\n/**\n * A contract call moves assets through its balance changes; the first native one names the parties.\n * @param {HorizonOperation} op - An `invoke_host_function` record.\n * @param {RowContext} context - Hash, ledger and time of the transaction.\n * @returns {Parties} Native movement as the row, issued assets as token transfers.\n */\nfunction invocationParties(op: HorizonOperation, context: RowContext): Parties {\n  const changes = op.asset_balance_changes ?? [];\n  const native = changes.find(isNative);\n  return {\n    from: native?.from ?? op.source_account,\n    to: native?.to ?? null,\n    value: native === undefined ? \"0\" : toStroops(native.amount),\n    tokenTransfers: changes\n      .filter((change) => !isNative(change))\n      .map((change) =>\n        tokenTransfer(change, change.amount, change.from ?? \"\", change.to ?? \"\", context),\n      ),\n  };\n}\n\n/**\n * Name the Soroban call the way the operation type is spelled, `invoke_contract` for the usual one.\n * @param {HorizonOperation} op - Any operation; only Soroban ones get a name.\n * @returns {string | undefined} The host function in snake case, the operation type without one.\n */\nfunction sorobanFunction(op: HorizonOperation): string | undefined {\n  if (!SOROBAN_TYPES.includes(op.type)) return undefined;\n  const name = (op.function ?? \"\").replace(/^(?:HostFunctionType)+/, \"\");\n  return name === \"\" ? op.type : name.replaceAll(/(?<=[a-z])(?=[A-Z])/g, \"_\").toLowerCase();\n}\n\nfunction operationParties(op: HorizonOperation, context: RowContext): Parties {\n  if (PAYMENT_TYPES.includes(op.type)) return paymentParties(op, context);\n  if (op.type === \"create_account\") return createAccountParties(op);\n  if (op.type === \"account_merge\") return mergeParties(op);\n  if (op.type === \"invoke_host_function\") return invocationParties(op, context);\n  return { from: op.source_account, to: null, value: \"0\", tokenTransfers: [] };\n}\n\n/**\n * The fee belongs to the transaction, so a row carries it only when it is the whole transaction.\n * @param {HorizonTransaction} transaction - The joined transaction, when Horizon sent one.\n * @returns {{ fee?: string }} The fee field for a single-operation transaction, else nothing.\n */\nfunction operationFee(transaction?: HorizonTransaction) {\n  if (transaction === undefined || transaction.operation_count !== 1) return {};\n  return { fee: String(transaction.fee_charged) };\n}\n\nfunction mapOperation(op: HorizonOperation): Transaction {\n  const blockNumber = op.transaction?.ledger ?? 0;\n  const timestamp = toIso(op.created_at);\n  const parties = operationParties(op, { txHash: op.transaction_hash, blockNumber, timestamp });\n  return {\n    hash: op.transaction_hash,\n    blockNumber,\n    timestamp,\n    from: parties.from,\n    to: parties.to,\n    value: parties.value,\n    valueFormatted: formatWei(parties.value, STROOP_DECIMALS),\n    ...operationFee(op.transaction),\n    status: op.transaction_successful ? \"success\" : \"failed\",\n    ...(SOROBAN_TYPES.includes(op.type) ? { functionName: sorobanFunction(op) } : {}),\n    isContractInteraction: SOROBAN_TYPES.includes(op.type),\n    tokenTransfers: op.transaction_successful ? parties.tokenTransfers : [],\n    raw: { ...op },\n  };\n}\n\nfunction mapTransaction(tx: HorizonTransaction, ops: readonly HorizonOperation[]): Transaction {\n  const timestamp = toIso(tx.created_at);\n  const context = { txHash: tx.hash, blockNumber: tx.ledger, timestamp };\n  const primary = ops.find((op) => PARTY_TYPES.includes(op.type)) ?? ops[0];\n  const soroban = ops.find((op) => SOROBAN_TYPES.includes(op.type));\n  const parties =\n    primary === undefined\n      ? { from: tx.source_account, to: null, value: \"0\" }\n      : operationParties(primary, context);\n  return {\n    hash: tx.hash,\n    blockNumber: tx.ledger,\n    timestamp,\n    from: parties.from,\n    to: parties.to,\n    value: parties.value,\n    valueFormatted: formatWei(parties.value, STROOP_DECIMALS),\n    fee: String(tx.fee_charged),\n    status: tx.successful ? \"success\" : \"failed\",\n    ...(soroban === undefined ? {} : { functionName: sorobanFunction(soroban) }),\n    isContractInteraction: soroban !== undefined,\n    tokenTransfers: tx.successful\n      ? ops.flatMap((op) => operationParties(op, context).tokenTransfers)\n      : [],\n    raw: { ...tx },\n  };\n}\n\nfunction matchingTransfers(records: readonly HorizonOperation[], token?: string): TokenTransfer[] {\n  return records\n    .flatMap((op) => mapOperation(op).tokenTransfers)\n    .filter((transfer) => token === undefined || transfer.contract === token);\n}\n\nfunction historyWindow(options: Readonly<TxHistoryOptions>, max: number) {\n  const page = options.page ?? 1;\n  if (!Number.isInteger(page) || page < 1 || page > MAX_PAGE)\n    throw new ExplorerError(`Horizon history walks pages 1 to ${MAX_PAGE}`, Horizon.key);\n  if (options.startBlock !== undefined || options.endBlock !== undefined)\n    throw new ExplorerError(\"Horizon account history does not support ledger bounds\", Horizon.key);\n  return {\n    limit: clampMaxResults(options.limit, max),\n    order: options.sort === \"asc\" ? \"asc\" : \"desc\",\n    page,\n  };\n}\n\n/** Stellar accounts, payments, transactions, ledgers and fee stats from a Horizon server. */\nexport class Horizon extends Provider {\n  static readonly key = \"horizon\";\n  private readonly base: string;\n\n  constructor(config: Readonly<ProviderConfig> = {}) {\n    super(config);\n    this.base = normalizeBaseUrl(config.baseUrl ?? HORIZON_URL);\n  }\n\n  get capabilities(): ProviderCapabilities {\n    return {\n      balances: true,\n      txHistory: true,\n      txDetail: true,\n      utxos: false,\n      contractInfo: false,\n      tokenBalances: true,\n      tokenTransfers: true,\n      gasData: true,\n      blockInfo: true,\n    };\n  }\n\n  private async account(address: string) {\n    const raw = await this.getJSON<unknown>(`${this.base}/accounts/${address}`);\n    const parsed = z\n      .looseObject({\n        id: z.literal(address),\n        balances: z.array(\n          z.looseObject({\n            asset_type: z.string(),\n            balance: amountSchema(),\n            asset_code: z.string().optional(),\n            asset_issuer: addressTextSchema().optional(),\n          }),\n        ),\n      })\n      .safeParse(raw);\n    if (!parsed.success) throw new ExplorerError(\"Invalid Horizon account response\", this.name);\n    return parsed.data;\n  }\n\n  /**\n   * The whole native balance in stroops, reserve included; an account never funded is a 404.\n   * @param {string} address - Stellar account id, the `G...` form.\n   * @param {ChainKey} chain - Must be Stellar.\n   * @returns {Promise<Balance>} Amounts in stroops.\n   */\n  async getBalance(address: string, chain: ChainKey = \"stellar\"): Promise<Balance> {\n    assertChain(chain);\n    assertAccount(address);\n    const account = await this.account(address);\n    const balance = toStroops(account.balances.find(isNative)?.balance ?? \"0\");\n    return this.snapshotBalance({\n      address,\n      chain,\n      balance,\n      balanceFormatted: formatWei(balance, STROOP_DECIMALS),\n      symbol: \"XLM\",\n    });\n  }\n\n  /**\n   * The account's trustlines as `CODE:ISSUER` holdings; liquidity pool shares stay out.\n   * @param {string} address - Stellar account id.\n   * @param {ChainKey} chain - Must be Stellar.\n   * @param {Readonly<TokenBalanceOptions>} options - `nonZeroOnly` drops empty trustlines.\n   * @returns {Promise<TokenBalance[]>} Holdings with seven decimals each.\n   */\n  override async getTokenBalances(\n    address: string,\n    chain: ChainKey = \"stellar\",\n    options: Readonly<TokenBalanceOptions> = {},\n  ): Promise<TokenBalance[]> {\n    assertChain(chain);\n    assertAccount(address);\n    const account = await this.account(address);\n    const holdings = account.balances\n      .filter((entry) => entry.asset_type.startsWith(\"credit_alphanum\"))\n      .map((entry) => {\n        const balance = toStroops(entry.balance);\n        return {\n          contract: assetId(entry),\n          symbol: entry.asset_code ?? \"\",\n          decimals: STROOP_DECIMALS,\n          balance,\n          balanceFormatted: formatWei(balance, STROOP_DECIMALS),\n        };\n      });\n    return options.nonZeroOnly ? holdings.filter((holding) => holding.balance !== \"0\") : holdings;\n  }\n\n  private async paymentsPage(address: string, order: string, limit: number, cursor?: string) {\n    const query = buildQuery({\n      order,\n      limit,\n      include_failed: \"true\",\n      join: \"transactions\",\n      cursor,\n    });\n    const raw = await this.getJSON<unknown>(`${this.base}/accounts/${address}/payments${query}`);\n    const parsed = pageSchema(operationSchema()).safeParse(raw);\n    if (!parsed.success || parsed.data._embedded.records.length > limit)\n      throw new ExplorerError(\"Invalid Horizon payments response\", this.name);\n    return parsed.data._embedded.records;\n  }\n\n  private async walkPayments(address: string, order: string, limit: number, page: number) {\n    let cursor: string | undefined;\n    let records: HorizonOperation[] = [];\n    for (let index = 1; index <= page; index += 1) {\n      records = await this.paymentsPage(address, order, limit, cursor);\n      if (records.length < limit) return index === page ? records : [];\n      cursor = records.at(-1)?.paging_token;\n    }\n    return records;\n  }\n\n  /**\n   * Horizon's payments view, one row per operation, so rows of one transaction share its hash and\n   * only a single-operation transaction carries its fee. Issued assets ride in `tokenTransfers`.\n   * @param {string} address - Stellar account id.\n   * @param {ChainKey} chain - Must be Stellar.\n   * @param {Readonly<TxHistoryOptions>} options - `limit` up to 200, `page` up to 10, no bounds.\n   * @returns {Promise<Transaction[]>} Operation rows.\n   */\n  async getTxHistory(\n    address: string,\n    chain: ChainKey = \"stellar\",\n    options: Readonly<TxHistoryOptions> = {},\n  ): Promise<Transaction[]> {\n    assertChain(chain);\n    assertAccount(address);\n    const { limit, order, page } = historyWindow(options, PAGE_LIMIT);\n    const records = await this.walkPayments(address, order, limit, page);\n    return records.map(mapOperation);\n  }\n\n  /**\n   * Issued-asset payments, filtered here because Horizon has no asset filter: the read walks up to\n   * five pages of 200 payments and keeps what moved an asset, `token` one `CODE:ISSUER` of them.\n   * @param {string} address - Stellar account id.\n   * @param {ChainKey} chain - Must be Stellar.\n   * @param {Readonly<TokenTransferOptions>} options - `limit` up to 100, `page` up to 10, `token`.\n   * @returns {Promise<TokenTransfer[]>} Transfers found inside the scanned window.\n   */\n  override async getTokenTransfers(\n    address: string,\n    chain: ChainKey = \"stellar\",\n    options: Readonly<TokenTransferOptions> = {},\n  ): Promise<TokenTransfer[]> {\n    assertChain(chain);\n    assertAccount(address);\n    const { limit, order, page } = historyWindow(options, 100);\n    const wanted = page * limit;\n    let transfers: TokenTransfer[] = [];\n    let cursor: string | undefined;\n    for (\n      let scanned = 0;\n      scanned < TRANSFER_SCAN_PAGES && transfers.length < wanted;\n      scanned += 1\n    ) {\n      const records = await this.paymentsPage(address, order, PAGE_LIMIT, cursor);\n      transfers = [...transfers, ...matchingTransfers(records, options.token)];\n      if (records.length < PAGE_LIMIT) break;\n      cursor = records.at(-1)?.paging_token;\n    }\n    return transfers.slice(wanted - limit, wanted);\n  }\n\n  /**\n   * One transaction with its operations: the first payment-like one names the parties and the\n   * native value, every issued-asset movement lands in `tokenTransfers`.\n   * @param {string} hash - Transaction hash, 64 hex characters.\n   * @param {ChainKey} chain - Must be Stellar.\n   * @returns {Promise<Transaction>} The transaction, with Horizon's record in `raw`.\n   */\n  override async getTxDetail(hash: string, chain: ChainKey = \"stellar\"): Promise<Transaction> {\n    assertChain(chain);\n    if (!hashSchema().safeParse(hash).success)\n      throw new ExplorerError(\"Invalid Stellar transaction hash\", this.name);\n    const query = buildQuery({ limit: PAGE_LIMIT, include_failed: \"true\" });\n    const [rawTx, rawOps] = await Promise.all([\n      this.getJSON<unknown>(`${this.base}/transactions/${hash}`),\n      this.getJSON<unknown>(`${this.base}/transactions/${hash}/operations${query}`),\n    ]);\n    const tx = transactionSchema().safeParse(rawTx);\n    const ops = pageSchema(operationSchema()).safeParse(rawOps);\n    if (!tx.success || !ops.success || tx.data.hash.toLowerCase() !== hash.toLowerCase())\n      throw new ExplorerError(\"Invalid Horizon transaction response\", this.name);\n    return mapTransaction(tx.data, ops.data._embedded.records);\n  }\n\n  /**\n   * Per-operation fees over the last five ledgers: the base fee, the fee most operations paid and\n   * the 95th percentile, which Soroban resource fees own. No middle tier is worth quoting.\n   * @param {ChainKey} chain - Must be Stellar.\n   * @returns {Promise<GasData>} Fee figures in stroops.\n   */\n  override async getGasData(chain: ChainKey = \"stellar\"): Promise<GasData> {\n    assertChain(chain);\n    const raw = await this.getJSON<unknown>(`${this.base}/fee_stats`);\n    const digits = z.string().regex(/^\\d+$/);\n    const parsed = z\n      .looseObject({\n        last_ledger_base_fee: digits,\n        fee_charged: z.looseObject({ mode: digits, p95: digits }),\n      })\n      .safeParse(raw);\n    if (!parsed.success) throw new ExplorerError(\"Invalid Horizon fee stats response\", this.name);\n    const { fee_charged: charged, last_ledger_base_fee: baseFee } = parsed.data;\n    return {\n      chain,\n      unit: \"stroops\",\n      safeGasPrice: charged.mode,\n      fastGasPrice: charged.p95,\n      baseFee,\n    };\n  }\n\n  /**\n   * One ledger as a block: no miner, gas fields at zero, `baseFee` in stroops and `txCount` with\n   * the failed transactions counted in.\n   * @param {number} blockNumber - Ledger sequence.\n   * @param {ChainKey} chain - Must be Stellar.\n   * @returns {Promise<BlockInfo>} The ledger.\n   */\n  override async getBlockInfo(\n    blockNumber: number,\n    chain: ChainKey = \"stellar\",\n  ): Promise<BlockInfo> {\n    assertChain(chain);\n    if (!Number.isSafeInteger(blockNumber) || blockNumber < 1)\n      throw new ExplorerError(\"Stellar ledger sequence must be a positive safe integer\", this.name);\n    const raw = await this.getJSON<unknown>(`${this.base}/ledgers/${blockNumber}`);\n    const count = z.number().int().nonnegative();\n    const parsed = z\n      .looseObject({\n        sequence: z.literal(blockNumber),\n        hash: hashSchema(),\n        prev_hash: hashSchema().optional(),\n        closed_at: timestampSchema(),\n        successful_transaction_count: count,\n        failed_transaction_count: count,\n        base_fee_in_stroops: count,\n      })\n      .safeParse(raw);\n    if (!parsed.success) throw new ExplorerError(\"Invalid Horizon ledger response\", this.name);\n    const ledger = parsed.data;\n    return {\n      number: ledger.sequence,\n      hash: ledger.hash,\n      parentHash: ledger.prev_hash ?? \"\",\n      timestamp: toIso(ledger.closed_at),\n      miner: \"\",\n      gasUsed: \"0\",\n      gasLimit: \"0\",\n      txCount: ledger.successful_transaction_count + ledger.failed_transaction_count,\n      baseFee: String(ledger.base_fee_in_stroops),\n    };\n  }\n}\n"],"mappings":"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"}