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         133x   133x   143x   149x         133x     143x 121x 134x   121x     123x                 133x   143x 143x       121x   125x                             147x 126x   125x                   149x     179x 179x 148x     179x   52x 147x      
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])))
  );
};