{"version":3,"file":"solscan.mjs","names":[],"sources":["../../src/providers/solscan.ts"],"sourcesContent":["/**\n * Solscan provider — indexed Solana explorer API.\n *\n * https://pro-api.solscan.io/pro-api-docs/v2.0\n */\n\nimport type {\n  Balance,\n  BlockInfo,\n  ChainKey,\n  ProviderCapabilities,\n  ProviderConfig,\n  Transaction,\n  TxHistoryOptions,\n  TxStatus,\n} from \"../core/types.js\";\nimport { Provider } from \"../core/provider.js\";\nimport { normalizeBaseUrl, buildQuery } from \"../core/client.js\";\nimport { AuthError, ExplorerError, UnsupportedChainError } from \"../core/errors.js\";\nimport { assertSafePathSegment } from \"../core/path-safety.js\";\nimport { clampMaxResults, formatWei, toTimestamp } from \"../core/types.js\";\n\nconst DEFAULT_BASE = \"https://pro-api.solscan.io/v2.0\";\nconst TRANSACTION_PAGE_SIZE = 40;\n\ninterface SolscanResponse<T> {\n  readonly success: boolean;\n  readonly data: T;\n  readonly errors?: string | { readonly message?: string };\n}\n\ninterface SolscanAccount {\n  readonly account: string;\n  readonly lamports: string | number;\n}\n\ninterface SolscanAccountTransaction {\n  readonly slot: number;\n  readonly fee: string | number;\n  readonly status: string;\n  readonly signer: string;\n  readonly block_time: number;\n  readonly tx_hash: string;\n  readonly program_ids?: readonly string[];\n}\n\ninterface SolscanTransactionDetail {\n  readonly tx_hash: string;\n  readonly block_id: number;\n  readonly block_time: number;\n  readonly fee: string | number;\n  readonly signer: string | readonly string[];\n  readonly status: number;\n  readonly compute_units_consumed?: string | number;\n  readonly programs_involved?: readonly string[];\n}\n\ninterface SolscanBlock {\n  readonly fee_rewards: string | number;\n  readonly transactions_count: number;\n  readonly current_slot: number;\n  readonly block_height: number;\n  readonly block_time: number;\n  readonly blockhash: string;\n  readonly parent_slot: number;\n}\n\nfunction mapAccountTransaction(raw: Readonly<SolscanAccountTransaction>): Transaction {\n  return {\n    hash: raw.tx_hash,\n    blockNumber: raw.slot,\n    timestamp: toTimestamp(raw.block_time),\n    from: raw.signer,\n    to: null,\n    value: \"0\",\n    valueFormatted: \"0\",\n    fee: String(raw.fee),\n    status: (raw.status.toLowerCase() === \"success\" ? \"success\" : \"failed\") as TxStatus,\n    isContractInteraction: (raw.program_ids?.length ?? 0) > 0,\n    tokenTransfers: [],\n    raw: raw as unknown as Record<string, unknown>,\n  };\n}\n\nasync function collectAccountTransactions(\n  limit: number,\n  readPage: (before: string | undefined) => Promise<SolscanAccountTransaction[]>,\n): Promise<SolscanAccountTransaction[]> {\n  const transactions: SolscanAccountTransaction[] = [];\n  const seenCursors = new Set<string>();\n  let before: string | undefined;\n\n  while (transactions.length < limit) {\n    const page = await readPage(before);\n    const cursor = page.at(-1)?.tx_hash;\n    if (cursor !== undefined && seenCursors.has(cursor)) break;\n    transactions.push(...page.slice(0, limit - transactions.length));\n    if (page.length < TRANSACTION_PAGE_SIZE || cursor === undefined) break;\n    seenCursors.add(cursor);\n    before = cursor;\n  }\n  return transactions;\n}\n\nfunction mapTransactionDetail(raw: Readonly<SolscanTransactionDetail>): Transaction {\n  const signer = typeof raw.signer === \"string\" ? raw.signer : (raw.signer[0] ?? \"\");\n  return {\n    hash: raw.tx_hash,\n    blockNumber: raw.block_id,\n    timestamp: toTimestamp(raw.block_time),\n    from: signer,\n    to: null,\n    value: \"0\",\n    valueFormatted: \"0\",\n    gasUsed:\n      raw.compute_units_consumed === undefined ? undefined : String(raw.compute_units_consumed),\n    fee: String(raw.fee),\n    status: (raw.status === 1 ? \"success\" : \"failed\") as TxStatus,\n    isContractInteraction: (raw.programs_involved?.length ?? 0) > 0,\n    tokenTransfers: [],\n    raw: raw as unknown as Record<string, unknown>,\n  };\n}\n\nexport class Solscan extends Provider {\n  static readonly key = \"solscan\";\n\n  private readonly apiKey: string;\n  private readonly baseUrl: string;\n\n  constructor(config: Readonly<ProviderConfig>) {\n    super(config);\n    const apiKey = config.apiKey ?? process.env.SOLSCAN_API_KEY ?? \"\";\n    if (!apiKey) {\n      throw new AuthError(\"solscan\", \"Set SOLSCAN_API_KEY or pass apiKey in config\");\n    }\n    this.apiKey = apiKey;\n    this.baseUrl = normalizeBaseUrl(config.baseUrl ?? DEFAULT_BASE);\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 async api<T>(\n    path: string,\n    params: Readonly<Record<string, string | number | undefined>>,\n  ): Promise<T> {\n    const response = await this.getJSON<SolscanResponse<T>>(\n      `${this.baseUrl}${path}${buildQuery(params)}`,\n      { headers: { token: this.apiKey } },\n    );\n    if (!response.success) {\n      const detail =\n        typeof response.errors === \"string\" ? response.errors : response.errors?.message;\n      throw new ExplorerError(`Solscan API error${detail ? `: ${detail}` : \"\"}`, this.name);\n    }\n    return response.data;\n  }\n\n  async getBalance(address: string, chain?: ChainKey): Promise<Balance> {\n    const c = chain ?? \"solana\";\n    if (c !== \"solana\") throw new UnsupportedChainError(c, this.name);\n    assertSafePathSegment(address, \"address\");\n\n    const data = await this.api<SolscanAccount>(\"/account/detail\", { address });\n    const balance = String(data.lamports);\n    return this.snapshotBalance({\n      address,\n      chain: \"solana\",\n      balance,\n      balanceFormatted: formatWei(balance, 9),\n      symbol: \"SOL\",\n    });\n  }\n\n  async getTxHistory(\n    address: string,\n    chain?: ChainKey,\n    options?: Readonly<TxHistoryOptions>,\n  ): Promise<Transaction[]> {\n    const c = chain ?? \"solana\";\n    if (c !== \"solana\") throw new UnsupportedChainError(c, this.name);\n    assertSafePathSegment(address, \"address\");\n\n    const limit = clampMaxResults(options?.limit);\n    const transactions = await collectAccountTransactions(limit, (before) =>\n      this.api<SolscanAccountTransaction[]>(\"/account/transactions\", {\n        address,\n        before,\n        limit: TRANSACTION_PAGE_SIZE,\n      }),\n    );\n    return transactions.map(mapAccountTransaction);\n  }\n\n  override async getTxDetail(hash: string, chain?: ChainKey): Promise<Transaction> {\n    const c = chain ?? \"solana\";\n    if (c !== \"solana\") throw new UnsupportedChainError(c, this.name);\n    assertSafePathSegment(hash, \"tx hash\");\n\n    const transaction = await this.api<SolscanTransactionDetail>(\"/transaction/detail\", {\n      tx: hash,\n    });\n    return mapTransactionDetail(transaction);\n  }\n\n  override async getBlockInfo(blockNumber: number, chain?: ChainKey): Promise<BlockInfo> {\n    const c = chain ?? \"solana\";\n    if (c !== \"solana\") throw new UnsupportedChainError(c, this.name);\n    assertSafePathSegment(String(blockNumber), \"block number\");\n\n    const block = await this.api<SolscanBlock>(\"/block/detail\", { block: blockNumber });\n    return {\n      number: block.current_slot,\n      hash: block.blockhash,\n      parentHash: String(block.parent_slot),\n      timestamp: toTimestamp(block.block_time),\n      miner: \"\",\n      gasUsed: String(block.fee_rewards),\n      gasLimit: \"0\",\n      txCount: block.transactions_count,\n    };\n  }\n}\n"],"mappings":"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"}