import type { OrderBase } from '@lifi/perps-types' import { isHex } from 'viem' import { HYPERLIQUID_EXPLORER_RPC_URL } from '../constants.js' import { type HlExplorerTx, isHlExplorerTx } from '../types/explorer.js' import { hlPostJson, type InfoRequestOptions } from './infoClient.js' const isRecord = (value: unknown): value is Record => typeof value === 'object' && value !== null /** * Read the address's recent HyperCore transactions from the explorer RPC. * * The surface is undocumented, so it is read defensively: an address the * explorer has never indexed answers `200` with no `txs` array, and each entry * is checked on its own so one drifted entry cannot drop the rest. * * @throws {PerpsError} On a non-2xx status or a transport failure. * @public */ export const fetchUserTransactions = async ( address: string, options?: InfoRequestOptions ): Promise => { const body = await hlPostJson<{ txs?: unknown } | null>( HYPERLIQUID_EXPLORER_RPC_URL, 'explorer userDetails', { type: 'userDetails', user: address }, options ) const txs = body?.txs return Array.isArray(txs) ? txs.filter(isHlExplorerTx) : [] } /** * Every client order id an action names: `orders[].c` on a placement, `order.c` * on a single modify, and `modifies[].order.c` on a batch modify. A batched * placement names one id per leg, so every id is returned, not just the first. * * @public */ export const getClientOrderIds = ( action: Record ): string[] => { const wires: unknown[] = [ ...(Array.isArray(action.orders) ? action.orders : []), ...(Array.isArray(action.modifies) ? action.modifies.map((modify) => isRecord(modify) ? modify.order : undefined ) : []), action.order, ] const ids: string[] = [] for (const wire of wires) { if (!isRecord(wire)) { continue } const cloid = wire.c if ( typeof cloid === 'string' && cloid !== '0x' && isHex(cloid, { strict: true }) ) { ids.push(cloid) } } return ids } /** * The hash of the transaction whose action names the order's client order id. * * @returns The transaction hash, or `undefined` when the order carries no * client order id or no transaction in the window names it. * @public */ export const matchOrderActionHash = ( txs: HlExplorerTx[], order: Pick ): string | undefined => { const { clientOrderId } = order if (clientOrderId === undefined) { return undefined } // A client order id is hex, so the venue may echo a case the placer did not send. const wanted = clientOrderId.toLowerCase() const match = txs.find( (tx) => isRecord(tx.action) && getClientOrderIds(tx.action).some((id) => id.toLowerCase() === wanted) ) return match?.hash }