{"version":3,"file":"blockchair.mjs","names":["createChain"],"sources":["../../src/providers/blockchair.ts"],"sourcesContent":["/**\n * Blockchair provider — multi-chain block explorer\n *\n * Supports Bitcoin, Ethereum and eCash. Free tier: limited requests, dashboard queries. Auth:\n * optional BLOCKCHAIR_API_KEY for higher limits.\n */\n\nimport type {\n  ProviderCapabilities,\n  ProviderConfig,\n  ChainKey,\n  Balance,\n  Transaction,\n  TxHistoryOptions,\n  TxStatus,\n  BlockInfo,\n} from \"../core/types.js\";\nimport { Provider } from \"../core/provider.js\";\nimport { NotFoundError, UnsupportedChainError } from \"../core/errors.js\";\nimport { buildQuery, normalizeBaseUrl } from \"../core/client.js\";\nimport { create as createChain } from \"@agntn/chains\";\nimport { formatWei, clampMaxResults } from \"../core/types.js\";\nimport { assertSafePathSegment } from \"../core/path-safety.js\";\n\nconst CHAIN_NAMES: Partial<Record<ChainKey, string>> = {\n  bitcoin: \"bitcoin\",\n  ethereum: \"ethereum\",\n  ecash: \"ecash\",\n};\n\n/** Blockchair's UTXO-shaped chains and their base-unit decimals. */\nconst UTXO_DECIMALS: Partial<Record<ChainKey, number>> = {\n  bitcoin: 8,\n  ecash: 2,\n};\n\nconst DEFAULT_BASE = \"https://api.blockchair.com\";\n\ninterface BlockchairResponse<T> {\n  readonly data: T;\n  readonly context: {\n    readonly code: number;\n    readonly error?: string;\n    readonly limit?: string;\n    readonly offset?: string;\n    readonly state?: number;\n  };\n}\n\ninterface BlockchairAddressData {\n  readonly address: {\n    readonly type: string;\n    readonly address: string;\n    readonly balance: string | number;\n    readonly balance_usd?: number;\n    readonly received: string | number;\n    readonly spent: string | number;\n    readonly unspent_output_count: number;\n    readonly first_seen_receiving?: string;\n    readonly last_seen_receiving?: string;\n    readonly transaction_count: number;\n    readonly output_count: number;\n    // ETH-specific\n    readonly call_count?: number;\n    readonly type_is_contract?: boolean;\n  };\n  readonly transactions?: readonly string[];\n  readonly utxo?: ReadonlyArray<{\n    readonly block_id: number;\n    readonly transaction_hash: string;\n    readonly index: number;\n    readonly value: number;\n  }>;\n}\n\ninterface BlockchairTxData {\n  readonly transaction: {\n    readonly hash: string;\n    readonly block_id: number;\n    readonly time: string;\n    readonly output_total?: string | number;\n    readonly fee: string | number;\n    readonly sender?: string;\n    readonly recipient?: string | null;\n    readonly value?: string;\n    readonly gas_used?: number;\n    readonly gas_price?: string | number;\n    readonly failed?: boolean;\n    readonly input_hex?: string;\n  };\n}\n\ninterface BlockchairBlockData {\n  readonly block: {\n    readonly id: number;\n    readonly hash: string;\n    readonly parent_hash?: string;\n    readonly time: string;\n    readonly miner?: string;\n    readonly transaction_count: number;\n    readonly gas_used?: string | number;\n    readonly gas_limit?: string | number;\n    readonly base_fee_per_gas?: string | number;\n  };\n}\n\nfunction chainName(chain: ChainKey): string {\n  const name = CHAIN_NAMES[chain];\n  if (!name) throw new UnsupportedChainError(chain, \"blockchair\");\n  return name;\n}\n\nfunction toIsoTimestamp(timestamp: string): string {\n  return new Date(`${timestamp.replace(\" \", \"T\")}Z`).toISOString();\n}\n\nfunction firstRecord<T>(data: Readonly<Record<string, T>>): T | undefined {\n  const key = Object.keys(data)[0];\n  return key === undefined ? undefined : data[key];\n}\n\nasync function fetchTransactionDetails(\n  hashes: readonly string[],\n  read: (hash: string) => Promise<Transaction>,\n): Promise<Transaction[]> {\n  const transactions: Transaction[] = [];\n  for (const hash of hashes) {\n    try {\n      transactions.push(await read(hash));\n    } catch (error) {\n      if (!(error instanceof NotFoundError)) throw error;\n    }\n  }\n  return transactions;\n}\n\nfunction blockchairStatus(data: Readonly<BlockchairTxData[\"transaction\"]>): TxStatus {\n  if (data.block_id < 0) return \"pending\";\n  return data.failed === true ? \"failed\" : \"success\";\n}\n\nfunction transactionValue(\n  data: Readonly<BlockchairTxData[\"transaction\"]>,\n  decimals: number | undefined,\n): string {\n  return decimals === undefined ? (data.value ?? \"0\") : String(data.output_total ?? 0);\n}\n\nfunction isBlockchairContractCall(\n  data: Readonly<BlockchairTxData[\"transaction\"]>,\n  decimals: number | undefined,\n): boolean {\n  return decimals === undefined && (data.input_hex?.length ?? 0) > 0;\n}\n\nfunction mapTransactionData(\n  data: Readonly<BlockchairTxData[\"transaction\"]>,\n  chain: ChainKey,\n): Transaction {\n  const decimals = UTXO_DECIMALS[chain];\n  const value = transactionValue(data, decimals);\n  return {\n    hash: data.hash,\n    blockNumber: data.block_id,\n    timestamp: toIsoTimestamp(data.time),\n    from: data.sender ?? \"\",\n    to: data.recipient ?? null,\n    value,\n    valueFormatted: formatWei(value, decimals ?? 18),\n    gasUsed: data.gas_used?.toString(),\n    gasPrice: data.gas_price?.toString(),\n    fee: String(data.fee),\n    status: blockchairStatus(data),\n    isContractInteraction: isBlockchairContractCall(data, decimals),\n    tokenTransfers: [],\n    raw: data as unknown as Record<string, unknown>,\n  };\n}\n\nexport class Blockchair extends Provider {\n  static readonly key = \"blockchair\";\n\n  private apiKey: string | undefined;\n  private readonly baseUrl: string;\n  private defaultChain: ChainKey;\n\n  constructor(config: Readonly<ProviderConfig>) {\n    super(config);\n    this.apiKey = config.apiKey ?? process.env.BLOCKCHAIR_API_KEY;\n    this.baseUrl = normalizeBaseUrl(config.baseUrl ?? DEFAULT_BASE);\n    this.defaultChain = config.defaultChain ?? \"ethereum\";\n  }\n  get capabilities(): ProviderCapabilities {\n    return {\n      balances: true,\n      txHistory: true,\n      txDetail: true,\n      utxos: false,\n      contractInfo: false,\n      tokenBalances: false,\n      tokenTransfers: false,\n      gasData: false,\n      blockInfo: true,\n    };\n  }\n\n  private buildUrl(\n    chain: ChainKey,\n    path: string,\n    params: Readonly<Record<string, string | number | undefined>> = {},\n  ): string {\n    const cn = chainName(chain);\n    const base = `${this.baseUrl}/${cn}`;\n    const query = buildQuery({\n      key: this.apiKey,\n      ...params,\n    });\n    return `${base}${path}${query}`;\n  }\n\n  async getBalance(address: string, chain?: ChainKey): Promise<Balance> {\n    const c = chain ?? this.defaultChain;\n    assertSafePathSegment(address, \"address\");\n    const url = this.buildUrl(c, `/dashboards/address/${encodeURIComponent(address)}`);\n    const res = await this.getJSON<BlockchairResponse<Record<string, BlockchairAddressData>>>(url);\n\n    const key = Object.keys(res.data)[0];\n    if (!key) throw new NotFoundError(`Address ${address}`, \"blockchair\");\n    const data = res.data[key];\n    if (!data) throw new NotFoundError(`Address ${address}`, \"blockchair\");\n    const balance = String(data.address.balance);\n    const utxoTotals =\n      UTXO_DECIMALS[c] === undefined\n        ? {}\n        : {\n            funded: String(data.address.received),\n            spent: String(data.address.spent),\n          };\n\n    return this.snapshotBalance(\n      {\n        address,\n        chain: c,\n        balance,\n        balanceFormatted: formatWei(balance, UTXO_DECIMALS[c] ?? 18),\n        ...utxoTotals,\n        symbol: createChain(c).symbol,\n      },\n      res.context.state === undefined ? {} : { blockNumber: res.context.state },\n    );\n  }\n\n  async getTxHistory(\n    address: string,\n    chain?: ChainKey,\n    options?: Readonly<TxHistoryOptions>,\n  ): Promise<Transaction[]> {\n    const c = chain ?? this.defaultChain;\n    assertSafePathSegment(address, \"address\");\n    const limit = clampMaxResults(options?.limit);\n    const url = this.buildUrl(c, `/dashboards/address/${encodeURIComponent(address)}`, {\n      limit,\n    });\n    const res = await this.getJSON<BlockchairResponse<Record<string, BlockchairAddressData>>>(url);\n\n    const transactions = firstRecord(res.data)?.transactions;\n    if (!transactions?.length) return [];\n\n    return fetchTransactionDetails(transactions.slice(0, limit), (hash) =>\n      this.getTxDetail(hash, c),\n    );\n  }\n\n  override async getTxDetail(hash: string, chain?: ChainKey): Promise<Transaction> {\n    const c = chain ?? this.defaultChain;\n    assertSafePathSegment(hash, \"tx hash\");\n    const url = this.buildUrl(c, `/dashboards/transaction/${encodeURIComponent(hash)}`);\n    const res = await this.getJSON<BlockchairResponse<Record<string, BlockchairTxData>>>(url);\n\n    const entry = firstRecord(res.data);\n    if (!entry) throw new NotFoundError(`Transaction ${hash}`, \"blockchair\");\n    return mapTransactionData(entry.transaction, c);\n  }\n\n  override async getBlockInfo(blockNumber: number, chain?: ChainKey): Promise<BlockInfo> {\n    const c = chain ?? this.defaultChain;\n    assertSafePathSegment(String(blockNumber), \"block number\");\n    const url = this.buildUrl(c, `/dashboards/blocks/${encodeURIComponent(String(blockNumber))}`);\n    const res = await this.getJSON<BlockchairResponse<Record<string, BlockchairBlockData>>>(url);\n\n    const block = res.data[blockNumber]?.block;\n    if (!block) throw new NotFoundError(`Block ${blockNumber}`, \"blockchair\");\n\n    return {\n      number: block.id,\n      hash: block.hash,\n      parentHash: block.parent_hash ?? \"\",\n      timestamp: toIsoTimestamp(block.time),\n      miner: block.miner ?? \"\",\n      gasUsed: (block.gas_used ?? 0).toString(),\n      gasLimit: (block.gas_limit ?? 0).toString(),\n      txCount: block.transaction_count,\n      baseFee: block.base_fee_per_gas?.toString(),\n    };\n  }\n}\n"],"mappings":"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"}