cyb/src/hooks/useGetWarpPools.ts

import { Coin } from '@cosmjs/launchpad';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import BigNumber from 'bignumber.js';
import { useCallback, useEffect, useState } from 'react';
import { DENOM_LIQUID, LCD_URL } from 'src/constants/config';
import { useAppData } from 'src/contexts/appData';
import { useIbcDenom } from 'src/contexts/ibcDenom';
import { Nullable } from 'src/types';
import { ObjectKey } from 'src/types/data';
import { getDisplayAmount } from 'src/utils/utils';

export type responseWarpDexTickersItem = {
  base_currency: string;
  target_currency: string;
  pool_id: number;
  ticker_id: string;
  last_price: number;
  liquidity_in_usd: number;
  base_volume: number;
  target_volume: number;
};

type OnChainSwapVolume = {
  pool_id: number;
  denom: string;
  amount: number;
}[];

const SWAP_TXS_LIMIT = 500;
const VOLUME_WINDOW_DAYS = 30;

const getWarpDexTickers = async (): Promise<Nullable<responseWarpDexTickersItem[]>> => {
  try {
    const response = await axios({
      method: 'get',
      url: 'https://warp-dex.cybernode.ai/dev/tickers/',
      timeout: 5000,
    });

    if (!Array.isArray(response.data)) {
      return null;
    }

    return response.data as responseWarpDexTickersItem[];
  } catch (_e) {
    return null;
  }
};

type OnChainSwapResult = {
  volumes: OnChainSwapVolume;
  actualDays: number;
};

const getOnChainSwapVolume = async (): Promise<Nullable<OnChainSwapResult>> => {
  try {
    const response = await axios.get(`${LCD_URL}/cosmos/tx/v1beta1/txs`, {
      params: {
        'pagination.limit': SWAP_TXS_LIMIT,
        order_by: 2,
        events: "message.action='/cyber.liquidity.v1beta1.MsgSwapWithinBatch'",
      },
      timeout: 15000,
    });

    const { tx_responses: txResponses } = response.data;
    if (!txResponses || !txResponses.length) {
      return null;
    }

    const now = new Date();
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - VOLUME_WINDOW_DAYS);

    const volumes: OnChainSwapVolume = [];
    let oldestTxTime = now;

    for (const tx of txResponses) {
      const txTime = new Date(tx.timestamp);
      if (txTime < cutoff) {
        break;
      }

      if (txTime < oldestTxTime) {
        oldestTxTime = txTime;
      }

      const messages = tx.tx?.body?.messages;
      if (!messages) {
        continue;
      }

      for (const msg of messages) {
        if (msg['@type'] === '/cyber.liquidity.v1beta1.MsgSwapWithinBatch') {
          const poolId = Number(msg.pool_id);
          const offerCoin = msg.offer_coin;
          if (offerCoin) {
            volumes.push({
              pool_id: poolId,
              denom: offerCoin.denom,
              amount: Number(offerCoin.amount),
            });
          }
        }
      }
    }

    if (volumes.length === 0) {
      return null;
    }

    const actualDays = Math.max(
      1,
      (now.getTime() - oldestTxTime.getTime()) / (1000 * 60 * 60 * 24)
    );

    return { volumes, actualDays };
  } catch (_e) {
    return null;
  }
};

export default function useWarpDexTickers() {
  const { marketData } = useAppData();
  const [vol24Total, setVol24Total] = useState<Coin | undefined>(undefined);
  const [vol24ByPool, setVol24ByPool] = useState<ObjectKey<Coin>>({}); // key is pool_id
  const { tracesDenom } = useIbcDenom();

  const { data: apiData } = useQuery({
    queryKey: ['warp-dex-tickers'],
    queryFn: async () => {
      const response = await getWarpDexTickers();
      return response ?? undefined;
    },
  });

  const { data: onChainData } = useQuery({
    queryKey: ['warp-onchain-volume'],
    queryFn: async () => {
      const response = await getOnChainSwapVolume();
      return response ?? undefined;
    },
    enabled: !apiData,
    staleTime: 5 * 60 * 1000,
  });

  const getAmountVol = useCallback(
    (denom: string, amount: number): BigNumber => {
      if (tracesDenom && Object.keys(marketData).length && Object.hasOwn(marketData, denom)) {
        const pollPrice = new BigNumber(marketData[denom]);
        const [{ coinDecimals }] = tracesDenom(denom);
        const reduceAmount = getDisplayAmount(amount, coinDecimals);
        const amountVol = pollPrice.multipliedBy(reduceAmount);

        return amountVol;
      }
      return new BigNumber(0);
    },
    [tracesDenom, marketData]
  );

  // Process API data
  useEffect(() => {
    if (!apiData || !Object.keys(marketData).length || !tracesDenom) {
      return;
    }

    let vol24Temp = new BigNumber(0);
    const listVol24ByPools: ObjectKey<Coin> = {};

    apiData.forEach((item: responseWarpDexTickersItem) => {
      let vol24Item = new BigNumber(0);

      if (marketData[item.base_currency]) {
        const amount = getAmountVol(item.base_currency, item.base_volume);
        vol24Item = vol24Item.plus(amount);
      }

      if (marketData[item.target_currency]) {
        const amount = getAmountVol(item.target_currency, item.target_volume);
        vol24Item = vol24Item.plus(amount);
      }

      vol24Temp = vol24Temp.plus(vol24Item);

      listVol24ByPools[item.pool_id] = {
        denom: DENOM_LIQUID,
        amount: vol24Item.dp(0, BigNumber.ROUND_FLOOR).toString(10),
      };
    });

    setVol24ByPool(listVol24ByPools);
    setVol24Total({
      denom: DENOM_LIQUID,
      amount: vol24Temp.dp(0, BigNumber.ROUND_FLOOR).toString(10),
    });
  }, [marketData, apiData, tracesDenom, getAmountVol]);

  // Process on-chain fallback data
  useEffect(() => {
    if (apiData || !onChainData || !Object.keys(marketData).length || !tracesDenom) {
      return;
    }

    const { volumes, actualDays } = onChainData;
    const poolVolumes: ObjectKey<BigNumber> = {};
    let totalVol = new BigNumber(0);

    for (const swap of volumes) {
      const vol = getAmountVol(swap.denom, swap.amount);
      if (vol.gt(0)) {
        const key = String(swap.pool_id);
        poolVolumes[key] = (poolVolumes[key] || new BigNumber(0)).plus(vol);
        totalVol = totalVol.plus(vol);
      }
    }

    // Average daily volume from actual time span
    const listVol24ByPools: ObjectKey<Coin> = {};

    Object.entries(poolVolumes).forEach(([poolId, vol]) => {
      const dailyAvg = vol.dividedBy(actualDays);
      listVol24ByPools[poolId] = {
        denom: DENOM_LIQUID,
        amount: dailyAvg.dp(0, BigNumber.ROUND_FLOOR).toString(10),
      };
    });

    const dailyTotal = totalVol.dividedBy(actualDays);

    setVol24ByPool(listVol24ByPools);
    setVol24Total({
      denom: DENOM_LIQUID,
      amount: dailyTotal.dp(0, BigNumber.ROUND_FLOOR).toString(10),
    });
  }, [marketData, apiData, onChainData, tracesDenom, getAmountVol]);

  return { data: apiData, vol24Total, vol24ByPool };
}

Synonyms

pussy-ts/src/hooks/useGetWarpPools.ts

Neighbours