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         132x   132x   144x   150x         132x     144x 145x 171x   145x     160x                 132x   144x 144x       145x   161x                             160x 162x   161x                   150x     192x 192x 193x     193x   64x 176x      
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])))
  );
};