All files / src/services/DelegationTracker DelegationDistributionTracker.ts

100% Statements 38/38
75% Branches 3/4
100% Functions 23/23
100% Lines 33/33

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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 11143x 43x     43x                     43x 43x 43x 43x           43x         129x   129x   141x   147x         129x     141x 142x 168x   142x     157x                 129x   141x 141x       142x   158x                             157x 159x   158x                   147x     189x 189x 190x     190x   64x 173x      
import { BigIntMath, Percent, calcPercentages, sameArrayItems } from '@cardano-sdk/util';
import { Cardano } from '@cardano-sdk/core';
import { DelegatedStake } from '../types';
import { DelegationTrackerProps } from './DelegationTracker';
import {
  Observable,
  combineLatest,
  combineLatestWith,
  distinctUntilChanged,
  iif,
  map,
  of,
  switchMap,
  withLatestFrom
} from 'rxjs';
import { createUtxoBalanceByAddressTracker } from '../BalanceTracker';
import { delegatedStakeEquals } from '../util';
import _groupBy from 'lodash/groupBy.js';
import _map from 'lodash/map.js';
 
type DelegationDistributionTrackerProps = Pick<DelegationTrackerProps, 'knownAddresses$' | 'utxoTracker'> & {
  rewardAccounts$: Observable<Cardano.RewardAccountInfo[]>;
};
 
export const createDelegationDistributionTracker = ({
  rewardAccounts$,
  knownAddresses$,
  utxoTracker
}: DelegationDistributionTrackerProps): Observable<Map<Cardano.PoolId, DelegatedStake>> => {
  const balanceAllUtxosTracker = createUtxoBalanceByAddressTracker(utxoTracker);
 
  const delegatedAccounts$ = rewardAccounts$.pipe(
    map((rewardsAccounts) =>
      rewardsAccounts.filter(
        (account) =>
          account.credentialStatus === Cardano.StakeCredentialStatus.Registered && account.delegatee?.nextNextEpoch
      )
    )
  );
 
  const hydratedDelegatedAccounts$ = delegatedAccounts$.pipe(
    withLatestFrom(knownAddresses$),
    map(([delegatedAccounts, knownAddresses]) =>
      delegatedAccounts.map((delegatedAccount) => {
        const groupedAddresses = knownAddresses.filter(
          (knownAddr) => knownAddr.rewardAccount === delegatedAccount.address
        );
        return {
          balance: createUtxoBalanceByAddressTracker(
            utxoTracker,
            groupedAddresses.map(({ address }) => address)
          ),
          delegatedAccount,
          groupedAddresses
        };
      })
    )
  );
 
  return hydratedDelegatedAccounts$.pipe(
    switchMap((accts) =>
      iif(
        () => accts.length === 0,
        of([]),
        combineLatest(
          accts.map((acct) =>
            acct.balance.utxo.total$.pipe(
              map(
                (perAccountTotal): DelegatedStake => ({
                  // Percentage will be calculated in the next step
                  percentage: Percent(0),
                  pool: acct.delegatedAccount.delegatee!.nextNextEpoch!,
                  rewardAccounts: [acct.delegatedAccount.address],
                  stake: perAccountTotal.coins + acct.delegatedAccount.rewardBalance
                })
              )
            )
          )
        )
      )
    ),
    // Merge rewardAccounts delegating to the same pool
    map((delegatedStakes) =>
      _map(
        _groupBy(delegatedStakes, ({ pool: { id } }) => id),
        (pools): DelegatedStake =>
          pools.reduce((mergedPool, pool) => ({
            ...pool,
            rewardAccounts: [...new Set([...mergedPool.rewardAccounts, ...pool.rewardAccounts])],
            stake: pool.stake + mergedPool.stake || 0n
          }))
      )
    ),
    // calculate percentages
    combineLatestWith(
      balanceAllUtxosTracker.utxo.total$,
      rewardAccounts$.pipe(map((accts) => BigIntMath.sum(accts.map(({ rewardBalance }) => rewardBalance))))
    ),
    map(([delegatedStakes, utxoBalance, totalRewards]) => {
      const totalBalance = utxoBalance.coins + totalRewards;
      const percentages = calcPercentages(
        delegatedStakes.map(({ stake: value }) => Number(value)),
        Number(totalBalance)
      );
      return delegatedStakes.map((pool, idx) => ({ ...pool, percentage: percentages[idx] }));
    }),
    distinctUntilChanged((a, b) => sameArrayItems(a, b, delegatedStakeEquals)),
    map((delegatedStakes) => new Map(delegatedStakes.map((delegation) => [delegation.pool.id, delegation])))
  );
};