All files / src data-feed-values.ts

30.3% Statements 10/33
0% Branches 0/13
0% Functions 0/6
25.8% Lines 8/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 853x 3x 3x                     3x 3x   3x                                                               3x                                     3x                                  
import { MathUtils, RedstoneTypes } from "@redstone-finance/utils";
import axios from "axios";
import { resolveDataServiceUrls } from "./data-services-urls";
 
export interface GetDataFeedValuesInput {
  aggregationAlgorithm?: "median" | "min" | "max"; // median by default
  dataServiceId?: string; // "redstone-main-demo" by default
  gatewayUrls?: string[]; // if not specified, use default for dataServiceId
}
 
export type GetDataFeedValuesOutput = Record<string, number | undefined>;
type GatewayResponse = RedstoneTypes.DataPackageFromGatewayResponse;
 
const DEFAULT_DATA_SERVICE_ID = "redstone-main-demo";
const DEFAULT_AGGREGATION_ALGORITHM = "median";
 
export const getDataFeedValues = async (
  args: GetDataFeedValuesInput = {}
): Promise<GetDataFeedValuesOutput> => {
  const dataServiceId = args.dataServiceId ?? DEFAULT_DATA_SERVICE_ID;
  const aggregationAlgorithm =
    args.aggregationAlgorithm ?? DEFAULT_AGGREGATION_ALGORITHM;
  const gatewayUrls = args.gatewayUrls ?? resolveDataServiceUrls(dataServiceId);
 
  const dataPackagesPerFeed = await Promise.any<GatewayResponse>(
    gatewayUrls.map((url) => getDataPackagesFromGateway(url, dataServiceId))
  );
 
  const result: GetDataFeedValuesOutput = {};
 
  for (const [dataFeedId, dataPackages] of Object.entries(
    dataPackagesPerFeed
  )) {
    const plainValues = dataPackages!
      .filter((dp) => dp.dataPoints.length === 1)
      .map((dp) => Number(dp.dataPoints[0].value));
 
    // case for ___ALL_FEEDS___
    Iif (plainValues.length === 0) {
      continue;
    }
 
    result[dataFeedId] = aggregateValues(plainValues, aggregationAlgorithm);
  }
 
  return result;
};
 
const getDataPackagesFromGateway = async (
  url: string,
  dataServiceId: string
): Promise<GatewayResponse> => {
  const response = await axios.get<GatewayResponse>(
    `${url}/data-packages/latest/${dataServiceId}`
  );
 
  Iif (typeof response.data === "string") {
    throw new Error(
      `Failed to fetch data package from ${url}. Data service ID responded with: ${String(
        response.data
      )}`
    );
  }
 
  return response.data;
};
 
export const aggregateValues = (
  plainValues: number[],
  aggregationAlgorithm: "median" | "min" | "max"
) => {
  switch (aggregationAlgorithm) {
    case "max":
      return Math.max(...plainValues);
    case "min":
      return Math.min(...plainValues);
    case "median":
      return MathUtils.getMedian(plainValues);
    default:
      throw new Error(
        `Unsupported aggregationAlgorithm ${String(aggregationAlgorithm)}`
      );
  }
};