import { NetworkDefinition } from "@anthem/utils" ; import { CosmosAccountHistoryQueryResult } from "graphql/queries" ; import moment from "moment-timezone" ; import { PortfolioChartData } from "ui/portfolio/CosmosPortfolio" ; import { ChartData , ChartSeries , FiatPriceMap , WithdrawalEventDates , } from "./chart-utils" ; import { denomToUnit } from "./currency-utils" ; import { toDateKey , toDateKeyBackOneDay } from "./date-utils" ; import { add , addValuesInList , isLessThan , multiply , subtract , } from "./math-utils" ; /** ===========================================================================
 * Types & Config
 * ============================================================================
 */ interface BalanceUnbondingOrDelegationItem { balance : string ; timestamp : string ; } interface ChartDataItem { timestamp : string ; balance : string ; fiatPrice : number ; } export interface PortfolioHistoryChartData { availableChartData : ChartData ; rewardsChartData : ChartData ; rewardsDailySummary : ChartData ; delegationsChartData : ChartData ; unbondingChartData : ChartData ; validatorRewardsChartData : ChartData ; validatorDailySummary : ChartData ; } /** ===========================================================================
 * Chart Utils
 * ============================================================================
 */ /**
 * Process all the portfolio API data to prepare it for rendering in the
 * chart.
 */ export const processPortfolioHistoryData = ( accountHistory , displayFiat , network , selectedDenom ) : PortfolioHistoryChartData | null => { if ( ! accountHistory || ! accountHistory . cosmosAccountHistory ) { return null ; } const { balanceHistory , delegations , unbondings , delegatorRewards , validatorCommissions , fiatPriceHistory , } = accountHistory . cosmosAccountHistory ; const fiatPriceMap = fiatPriceHistory . reduce ( ( map , price ) => { return { ... map , [ toDateKey ( price . timestamp ) ] : price . price , } ; } , { } ) ; const firstBalance = balanceHistory [ 0 ] || delegatorRewards [ 0 ] || validatorCommissions [ 0 ] ; const startingDate = firstBalance && firstBalance . timestamp ; const filterByDenom = ( item ) => item . denom === selectedDenom ; const adjustedBalances = populateMissingDatesInDataSeries ( startingDate , balanceHistory . filter ( filterByDenom ) , fiatPriceMap ) ; const availableChartData = mapBalancesToChartData ( adjustedBalances , displayFiat , network ) ; const adjustedDelegations = populateMissingDatesInDataSeries ( startingDate , delegations , fiatPriceMap ) ; const delegationsChartData = mapDelegationsToChartData ( adjustedDelegations , displayFiat , network , true ) ; const adjustedUnbondings = populateMissingDatesInDataSeries ( startingDate , unbondings , fiatPriceMap ) ; const unbondingChartData = mapDelegationsToChartData ( adjustedUnbondings , displayFiat , network ) ; const rewardsWithFiatPrices = delegatorRewards . filter ( filterByDenom ) . map ( rewards => ( { ... rewards , fiatPrice : fiatPriceMap [ toDateKey ( rewards . timestamp ) ] , } ) ) ; const rewardsChartData = mapRewardsToChartData ( rewardsWithFiatPrices , displayFiat , network , "REWARDS" ) ; const rewardsDailySummary = mapRewardsToDailySummary ( rewardsWithFiatPrices , displayFiat , network ) ; const commissionsWithFiatPrices = validatorCommissions . filter ( filterByDenom ) . map ( commissions => ( { ... commissions , fiatPrice : fiatPriceMap [ toDateKey ( commissions . timestamp ) ] , } ) ) ; const validatorRewardsChartData = mapRewardsToChartData ( commissionsWithFiatPrices , displayFiat , network , "COMMISSIONS" ) ; const validatorDailySummary = mapRewardsToDailySummary ( commissionsWithFiatPrices , displayFiat , network ) ; return { availableChartData , rewardsChartData , rewardsDailySummary , delegationsChartData , unbondingChartData , validatorRewardsChartData , validatorDailySummary , } ; } ; /**
 * Return data for rendering the portfolio balances chart.
 */ export const mapBalancesToChartData = ( balanceHistory , displayFiat , network ) : ChartData => { const data = { } ; balanceHistory . forEach ( ( { balance , timestamp , fiatPrice } ) => { const x = denomToUnit ( balance , network . denominationSize , Number ) ; const value = displayFiat ? Number ( fiatPrice ) * x : x ; const reward = Number ( value ) ; const time = toDateKey ( timestamp ) ; data [ time ] = reward ; } ) ; return { type : "AVAILABLE" , data , withdrawalsMap : { } , withdrawalEventDates : { } , } ; } ; /**
 * Maps the delegations data to chart data to render.
 */ export const mapDelegationsToChartData = ( delegationsData , displayFiat , network , renderUnbondingLines = false ) : ChartData => { let lastRewards = "0" ; const data = { } ; const withdrawalEventDates = { } ; const withdrawalsMap = { } ; let rewardsByTime = new Set ( ) ; for ( const { balance , timestamp , fiatPrice } of delegationsData ) { const x = denomToUnit ( balance , network . denominationSize , Number ) ; const value = displayFiat ? multiply ( fiatPrice , x , Number ) : x ; const time = toDateKey ( timestamp ) ; data [ time ] = value ; if ( isLessThan ( balance , lastRewards ) ) { const diff = denomToUnit ( subtract ( lastRewards , balance ) , network . denominationSize ) ; const displayDiff = displayFiat ? multiply ( fiatPrice , diff ) : diff ; withdrawalsMap [ time ] = displayDiff ; } lastRewards = balance ; rewardsByTime = rewardsByTime . add ( time ) ; } const rewardsByTimeList = Array . from ( rewardsByTime ) ; /**
     * Map the withdrawal amount totals by date to the index of the
     * associated rewards data point to plot vertical lines for withdrawals.
     */ Object . entries ( withdrawalsMap ) . forEach ( ( [ key , value ] ) => { const index = rewardsByTimeList . indexOf ( key ) ; withdrawalEventDates [ index ] = value ; } ) ; return { data , type : "STAKING" , withdrawalsMap : renderUnbondingLines ? withdrawalsMap : { } , withdrawalEventDates : renderUnbondingLines ? withdrawalEventDates : { } , } ; } ; /**
 * Return data for rendering the portfolio rewards chart.
 */ export const mapRewardsToChartData = ( rewardsData , displayFiat , network , type ) : ChartData => { let lastRewards = 0 ; const data = { } ; const withdrawalEventDates = { } ; const withdrawalsMap = { } ; if ( rewardsData . length === 0 ) { return { type , data , withdrawalEventDates , withdrawalsMap : { } } ; } /**
     * Create a map of all withdrawal amounts by date.
     */ for ( const { timestamp , balance , fiatPrice } of rewardsData ) { const value = Number ( balance ) ; if ( isLessThan ( value , lastRewards ) ) { const denoms = subtract ( lastRewards , value ) ; const atoms = denomToUnit ( denoms , network . denominationSize ) ; const displayValue = displayFiat ? multiply ( fiatPrice , atoms ) : atoms ; const time = toDateKeyBackOneDay ( timestamp ) ; withdrawalsMap [ time ] = add ( withdrawalsMap [ time ] || "0" , displayValue ) ; } lastRewards = value ; } lastRewards = 0 ; let lastValue = 0 ; let runningAtomTotal = 0 ; let rewardsByTime = new Set ( ) ; for ( const { balance , timestamp , fiatPrice } of rewardsData ) { const atomReward = denomToUnit ( balance , network . denominationSize , Number ) ; /**
         * Determine the timestamp string based on the duration of the
         * available rewards history. If the history is less than a week
         * use the full timestamp otherwise convert it to the MM-DD-YYYY format.
         */ const time = toDateKeyBackOneDay ( timestamp ) ; const diff = subtract ( atomReward , lastValue , Number ) ; lastValue = atomReward ; runningAtomTotal = runningAtomTotal + ( diff > 0 ? diff : 0 ) ; const value = displayFiat ? multiply ( runningAtomTotal , fiatPrice , Number ) : runningAtomTotal ; if ( diff !== 0 ) { data [ time ] = value ; rewardsByTime = rewardsByTime . add ( time ) ; } lastRewards = Number ( balance ) ; } const rewardsByTimeList = Array . from ( rewardsByTime ) ; Object . entries ( withdrawalsMap ) . forEach ( ( [ key , value ] ) => { const index = rewardsByTimeList . indexOf ( key ) ; withdrawalEventDates [ index ] = value ; } ) ; return { type , data , withdrawalEventDates , withdrawalsMap } ; } ; /**
 * Map rewards or commissions data and only include the final daily summary
 * value, not the cumulative total.
 */ export const mapRewardsToDailySummary = ( rewardsHistory , displayFiat , network ) : ChartData => { const data = { } ; rewardsHistory . forEach ( ( { balance , timestamp , fiatPrice } ) => { const x = denomToUnit ( balance , network . denominationSize , Number ) ; const value = displayFiat ? Number ( fiatPrice ) * x : x ; const reward = Number ( value ) ; const time = toDateKeyBackOneDay ( timestamp ) ; data [ time ] = reward ; } ) ; return { type : "REWARDS" , data , withdrawalsMap : { } , withdrawalEventDates : { } , } ; } ; /**
 * Combine all the portfolio graph results to assemble the total graph to
 * represent all ATOM holdings over time.
 */ export const getChartTotalGraph = ( portfolioChartData ) : ChartData => { const { availableChartData , delegationsChartData , unbondingChartData , validatorDailySummary , rewardsDailySummary , } = portfolioChartData ; const combined = { } ; /**
     * Choose the chart data to use for collecting the summary.
     */ const chartData = Object . keys ( availableChartData . data ) . length > 0 ? availableChartData : Object . keys ( rewardsDailySummary . data ) . length > 0 ? rewardsDailySummary : validatorDailySummary ; for ( const [ timestamp , value ] of Object . entries ( chartData . data ) ) { const availableValue = value ; const rewardsValue = rewardsDailySummary . data [ timestamp ] ; const delegationsValue = delegationsChartData . data [ timestamp ] ; const unbondingValue = unbondingChartData . data [ timestamp ] ; const commissionsValue = validatorDailySummary . data [ timestamp ] ; const values = [ availableValue , rewardsValue , delegationsValue , unbondingValue , commissionsValue , ] . filter ( Boolean ) ; combined [ timestamp ] = addValuesInList ( values , Number ) ; } return { type : "TOTAL" , data : combined , withdrawalsMap : { } , withdrawalEventDates : { } , } ; } ; const populateMissingDatesInDataSeries = ( startingDate , dataSet , fiatPriceMap ) => { const firstItem = dataSet [ 0 ] ; if ( ! firstItem ) { return [ ] ; } const shouldStartWithZero = moment ( startingDate ) . isBefore ( firstItem . timestamp ) ; const startingBalance = shouldStartWithZero ? "0" : firstItem . balance ; let currentBalance = startingBalance ; const endingDate = new Date ( ) ; const dataSetMap = { } ; for ( const item of dataSet ) { const { timestamp , balance } = item ; const dateKey = toDateKey ( timestamp ) ; dataSetMap [ dateKey ] = balance ; } let dateRange = [ ] ; for ( const d = new Date ( startingDate ) ; d <= new Date ( endingDate ) ; d . setDate ( d . getDate ( ) + 1 ) ) { dateRange = dateRange . concat ( toDateKey ( d ) ) ; } let result = [ ] ; for ( const date of dateRange ) { if ( date in dataSetMap ) { currentBalance = dataSetMap [ date ] ; } result = [ ... result , { timestamp : date , balance : currentBalance , fiatPrice : fiatPriceMap [ date ] , } , ] ; } return result ; } ;