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         134x   134x   146x   152x         134x     146x 147x 178x   147x     167x                 134x   146x 146x       147x   163x                             162x 164x   163x                   152x     194x 194x 195x     195x   64x 178x      
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])))
  );
};