kleros/kleros-v2

View on GitHub
web/src/hooks/useClassicAppealContext.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import React, { useMemo, useState, createContext, useContext } from "react";

import { useParams } from "react-router-dom";

import { ONE_BASIS_POINT } from "consts/index";
import { Periods } from "consts/periods";
import { usePopulatedDisputeData } from "hooks/queries/usePopulatedDisputeData";
import { useCountdown } from "hooks/useCountdown";
import { getLocalRounds } from "utils/getLocalRounds";

import { useAppealCost } from "queries/useAppealCost";
import { useClassicAppealQuery, ClassicAppealQuery } from "queries/useClassicAppealQuery";
import { useDisputeKitClassicMultipliers } from "queries/useDisputeKitClassicMultipliers";

interface ICountdownContext {
  loserSideCountdown?: number;
  winnerSideCountdown?: number;
}
const CountdownContext = createContext<ICountdownContext>({});

const OptionsContext = createContext<string[] | undefined>(undefined);

interface ISelectedOptionContext {
  selectedOption: number | undefined;
  setSelectedOption: (arg0: number) => void;
}
const SelectedOptionContext = createContext<ISelectedOptionContext>({
  selectedOption: undefined,
  //eslint-disable-next-line @typescript-eslint/no-empty-function
  setSelectedOption: () => {},
});

interface IFundingContext {
  winningChoice: string | undefined;
  paidFees: bigint[] | undefined;
  loserRequiredFunding: bigint | undefined;
  winnerRequiredFunding: bigint | undefined;
  fundedChoices: string[] | undefined;
}
const FundingContext = createContext<IFundingContext>({
  winningChoice: undefined,
  paidFees: undefined,
  loserRequiredFunding: undefined,
  winnerRequiredFunding: undefined,
  fundedChoices: undefined,
});

export const ClassicAppealProvider: React.FC<{
  children: React.ReactNode;
}> = ({ children }) => {
  const { id } = useParams();
  const { data } = useClassicAppealQuery(id);
  const dispute = data?.dispute;
  const paidFees = getPaidFees(data?.dispute);
  const winningChoice = getWinningChoice(data?.dispute);
  const { data: appealCost } = useAppealCost(id);
  const arbitrable = data?.dispute?.arbitrated.id;
  const { data: disputeDetails } = usePopulatedDisputeData(id, arbitrable);
  const { data: multipliers } = useDisputeKitClassicMultipliers();
  const options = ["Refuse to Arbitrate"].concat(
    disputeDetails?.answers?.map((answer: { title: string; description: string }) => {
      return answer.title;
    })
  );
  const loserSideCountdown = useLoserSideCountdown(
    dispute?.lastPeriodChange,
    dispute?.court.timesPerPeriod[Periods.appeal],
    multipliers?.loser_appeal_period_multiplier.toString()
  );

  const winnerSideCountdown = useWinnerSideCountdown(
    dispute?.lastPeriodChange,
    dispute?.court.timesPerPeriod[Periods.appeal]
  );

  const { loserRequiredFunding, winnerRequiredFunding } = useMemo(
    () => ({
      loserRequiredFunding: getRequiredFunding(appealCost, multipliers?.loser_stake_multiplier),
      winnerRequiredFunding: getRequiredFunding(appealCost, multipliers?.winner_stake_multiplier),
    }),
    [appealCost, multipliers]
  );
  const fundedChoices = getFundedChoices(data?.dispute);
  const [selectedOption, setSelectedOption] = useState<number | undefined>();

  return (
    <CountdownContext.Provider
      value={useMemo(() => ({ loserSideCountdown, winnerSideCountdown }), [loserSideCountdown, winnerSideCountdown])}
    >
      <SelectedOptionContext.Provider
        value={useMemo(() => ({ selectedOption, setSelectedOption }), [selectedOption, setSelectedOption])}
      >
        <FundingContext.Provider
          value={useMemo(
            () => ({
              winningChoice,
              paidFees,
              loserRequiredFunding,
              winnerRequiredFunding,
              fundedChoices,
            }),
            [winningChoice, paidFees, loserRequiredFunding, winnerRequiredFunding, fundedChoices]
          )}
        >
          <OptionsContext.Provider value={options}>{children}</OptionsContext.Provider>
        </FundingContext.Provider>
      </SelectedOptionContext.Provider>
    </CountdownContext.Provider>
  );
};

export const useCountdownContext = () => useContext(CountdownContext);
export const useSelectedOptionContext = () => useContext(SelectedOptionContext);
export const useFundingContext = () => useContext(FundingContext);
export const useOptionsContext = () => useContext(OptionsContext);

const getCurrentLocalRound = (dispute?: ClassicAppealQuery["dispute"]) => {
  if (!dispute) return undefined;

  const period = dispute.period;
  const currentLocalRoundIndex = dispute.disputeKitDispute.at(-1)?.currentLocalRoundIndex;
  const adjustedRoundIndex = ["appeal", "execution"].includes(period)
    ? currentLocalRoundIndex
    : currentLocalRoundIndex - 1;

  return getLocalRounds(dispute.disputeKitDispute)[adjustedRoundIndex];
};

const getPaidFees = (dispute?: ClassicAppealQuery["dispute"]) => {
  const currentLocalRound = getCurrentLocalRound(dispute);
  return currentLocalRound?.paidFees.map((amount: string) => BigInt(amount));
};

const getFundedChoices = (dispute?: ClassicAppealQuery["dispute"]) => {
  const currentLocalRound = getCurrentLocalRound(dispute);
  return currentLocalRound?.fundedChoices;
};

const getWinningChoice = (dispute?: ClassicAppealQuery["dispute"]) => {
  const currentLocalRound = getCurrentLocalRound(dispute);
  return currentLocalRound?.winningChoice;
};

export const getRequiredFunding = (appealCost = 0n, stake_multiplier = 0n): bigint =>
  appealCost + (stake_multiplier * appealCost) / ONE_BASIS_POINT;

function useLoserSideCountdown(lastPeriodChange = "0", appealPeriodDuration = "0", loserTimeMultiplier = "0") {
  const deadline = useMemo(
    () => getDeadline(lastPeriodChange, appealPeriodDuration, loserTimeMultiplier),
    [lastPeriodChange, appealPeriodDuration, loserTimeMultiplier]
  );
  return useCountdown(deadline);
}

function useWinnerSideCountdown(lastPeriodChange = "0", appealPeriodDuration = "0") {
  const deadline = useMemo(
    () => Number(BigInt(lastPeriodChange) + BigInt(appealPeriodDuration)),
    [lastPeriodChange, appealPeriodDuration]
  );
  return useCountdown(deadline);
}

const getDeadline = (lastPeriodChange: string, appealPeriodDuration: string, loserTimeMultiplier: string): number => {
  const parsedLastPeriodChange = BigInt(lastPeriodChange);
  const parsedAppealPeriodDuration = BigInt(appealPeriodDuration);
  const parsedLoserTimeMultiplier = BigInt(loserTimeMultiplier);
  const loserAppealPeriodDuration = (parsedAppealPeriodDuration * parsedLoserTimeMultiplier) / ONE_BASIS_POINT;
  return Number(loserAppealPeriodDuration + parsedLastPeriodChange);
};