{"version":3,"file":"helius.mjs","names":[],"sources":["../../src/providers/helius.ts"],"sourcesContent":["/**\n * Helius provider - enhanced Solana transaction indexer API.\n *\n * https://www.helius.dev/docs/api-reference/enhanced-transactions\n */\n\nimport type {\n  Balance,\n  ChainKey,\n  ProviderCapabilities,\n  ProviderConfig,\n  TokenBalance,\n  TokenBalanceOptions,\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 {\n  AuthError,\n  ExplorerError,\n  NotFoundError,\n  UnsupportedChainError,\n  UnsupportedOperationError,\n} from \"../core/errors.js\";\nimport { assertSafePathSegment } from \"../core/path-safety.js\";\nimport { clampMaxResults, formatWei, toTimestamp } from \"../core/types.js\";\n\nconst DEFAULT_BASE = \"https://mainnet.helius-rpc.com\";\n\n/** Largest page the DAS search endpoint accepts; it answers 1001 with a validation error. */\nconst DAS_PAGE_LIMIT = 1000;\n\n/** Pages the holdings walk visits, so one owner cannot fan out an unbounded number of requests. */\nconst DAS_MAX_PAGES = 20;\n\n/** Programs present in plain transfers that do not make a transaction a contract interaction. */\nconst SYSTEM_PROGRAMS = new Set([\n  \"11111111111111111111111111111111\",\n  \"ComputeBudget111111111111111111111111111111\",\n]);\n\ninterface HeliusTransaction {\n  readonly signature: string;\n  readonly slot: number;\n  readonly timestamp: number;\n  readonly fee: string | number;\n  readonly feePayer: string;\n  readonly transactionError?: unknown;\n  readonly instructions?: readonly { readonly programId: string }[];\n}\n\n/** JSON-RPC envelope shared by the DAS methods; failures arrive inside a 200 response. */\ninterface HeliusRpcResponse<T> {\n  readonly result?: T;\n  readonly error?: { readonly code: number; readonly message: string };\n}\n\ninterface HeliusAsset {\n  readonly id: string;\n  readonly content?: { readonly metadata?: { readonly name?: string; readonly symbol?: string } };\n  readonly token_info?: {\n    readonly balance?: string | number;\n    readonly decimals?: number;\n    readonly symbol?: string;\n    readonly price_info?: { readonly price_per_token?: number; readonly total_price?: number };\n  };\n}\n\nconst isNonSystemProgram = (instruction: Readonly<{ programId: string }>): boolean =>\n  !SYSTEM_PROGRAMS.has(instruction.programId);\n\nfunction tokenIdentity(asset: Readonly<HeliusAsset>): {\n  readonly name?: string;\n  readonly symbol: string;\n} {\n  const metadata = asset.content?.metadata;\n  return {\n    name: metadata?.name,\n    symbol: metadata?.symbol ?? asset.token_info?.symbol ?? \"\",\n  };\n}\n\n/* Read one holding off a DAS asset. Metaplex metadata names a token more often than its mint. */\nfunction mapTokenBalance(asset: Readonly<HeliusAsset>): TokenBalance {\n  const info = asset.token_info;\n  const identity = tokenIdentity(asset);\n  const decimals = info?.decimals ?? 0;\n  const balance = String(info?.balance ?? \"0\");\n  const price = info?.price_info;\n\n  return {\n    contract: asset.id,\n    symbol: identity.symbol,\n    name: identity.name,\n    decimals,\n    balance,\n    balanceFormatted: formatWei(balance, decimals),\n    priceUsd: price?.price_per_token,\n    valueUsd: price?.total_price,\n  };\n}\n\nfunction mapTransaction(raw: Readonly<HeliusTransaction>): Transaction {\n  return {\n    hash: raw.signature,\n    blockNumber: raw.slot,\n    timestamp: toTimestamp(raw.timestamp),\n    from: raw.feePayer,\n    to: null,\n    value: \"0\",\n    valueFormatted: \"0\",\n    fee: String(raw.fee),\n    status: (raw.transactionError === null || raw.transactionError === undefined\n      ? \"success\"\n      : \"failed\") as TxStatus,\n    isContractInteraction: raw.instructions?.some(isNonSystemProgram) ?? false,\n    tokenTransfers: [],\n    raw: raw as unknown as Record<string, unknown>,\n  };\n}\n\nexport class Helius extends Provider {\n  static readonly key = \"helius\";\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.HELIUS_API_KEY ?? \"\";\n    if (!apiKey) {\n      throw new AuthError(\"helius\", \"Set HELIUS_API_KEY or pass apiKey in config\");\n    }\n    this.apiKey = apiKey;\n    this.baseUrl = normalizeBaseUrl(config.baseUrl ?? DEFAULT_BASE);\n  }\n\n  get capabilities(): ProviderCapabilities {\n    return {\n      balances: false,\n      txHistory: true,\n      txDetail: true,\n      utxos: false,\n      contractInfo: false,\n      tokenBalances: true,\n      tokenTransfers: false,\n      gasData: false,\n      blockInfo: false,\n    };\n  }\n\n  private api<T>(\n    path: string,\n    params: Readonly<Record<string, string | number | undefined>> = {},\n  ): Promise<T> {\n    return this.getJSON<T>(\n      `${this.baseUrl}${path}${buildQuery({ \"api-key\": this.apiKey, ...params })}`,\n    );\n  }\n\n  private apiPost<T>(path: string, body: unknown): Promise<T> {\n    return this.postJSON<T>(\n      `${this.baseUrl}${path}${buildQuery({ \"api-key\": this.apiKey })}`,\n      body,\n    );\n  }\n\n  /* Call a DAS method on the RPC root and unwrap its JSON-RPC envelope. */\n  private async rpc<T>(method: string, params: unknown): Promise<T> {\n    const response = await this.apiPost<HeliusRpcResponse<T>>(\"/\", {\n      jsonrpc: \"2.0\",\n      id: \"explorers\",\n      method,\n      params,\n    });\n\n    if (response.error) {\n      throw new ExplorerError(`Helius API error: ${response.error.message}`, this.name);\n    }\n    if (response.result === null || response.result === undefined) {\n      throw new ExplorerError(`Helius returned no result for ${method}`, this.name);\n    }\n    return response.result;\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    throw new UnsupportedOperationError(\"getBalance\", this.name);\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 transactions = await this.api<HeliusTransaction[]>(\n      `/v0/addresses/${encodeURIComponent(address)}/transactions`,\n      { limit: clampMaxResults(options?.limit, 100) },\n    );\n    return transactions.map(mapTransaction);\n  }\n\n  /**\n   * List an owner's fungible holdings. Airdrop spam pushes ordinary wallets past one page.\n   *\n   * @param {string} address - The `address` value.\n   * @param {ChainKey} chain - The `chain` value.\n   * @param {Readonly<TokenBalanceOptions>} options - The `options` value.\n   * @returns {Promise<TokenBalance[]>} The resulting value.\n   */\n  override async getTokenBalances(\n    address: string,\n    chain?: ChainKey,\n    options?: Readonly<TokenBalanceOptions>,\n  ): Promise<TokenBalance[]> {\n    const c = chain ?? \"solana\";\n    if (c !== \"solana\") throw new UnsupportedChainError(c, this.name);\n\n    const tokens: TokenBalance[] = [];\n    for (let page = 1; page <= DAS_MAX_PAGES; page++) {\n      const result = await this.rpc<{ items?: HeliusAsset[] }>(\"searchAssets\", {\n        ownerAddress: address,\n        tokenType: \"fungible\",\n        limit: DAS_PAGE_LIMIT,\n        page,\n      });\n      const items = result.items ?? [];\n      tokens.push(...items.map(mapTokenBalance));\n      if (items.length < DAS_PAGE_LIMIT) break;\n    }\n\n    return options?.nonZeroOnly ? tokens.filter((token) => token.balance !== \"0\") : tokens;\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\n    const transactions = await this.apiPost<HeliusTransaction[]>(\"/v0/transactions\", {\n      transactions: [hash],\n    });\n    const transaction = transactions[0];\n    if (!transaction) throw new NotFoundError(hash, this.name);\n    return mapTransaction(transaction);\n  }\n}\n"],"mappings":"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"}