@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
209 lines (184 loc) • 5.98 kB
text/typescript
import { BigNumber } from "bignumber.js";
import invariant from "invariant";
import { ONE_TRX } from "@ledgerhq/coin-tron/logic/constants";
import { getTronSuperRepresentatives } from "@ledgerhq/coin-tron/network";
import type { SuperRepresentative, TronAccount, Vote } from "@ledgerhq/coin-tron/types/index";
import { useEffect, useMemo, useRef, useState } from "react";
import { useBridgeSync } from "../../bridge/react";
export type Action = {
type: "updateVote" | "resetVotes" | "clearVotes";
address: string;
value: string;
};
export type State = {
votes: Record<string, number>;
// formatted Map of votes
votesAvailable: number;
// total of available TP
votesUsed: number;
// total of TP used
votesSelected: number;
// number of SR votes selected
max: number;
// votes remaining
initialVotes: Record<string, number>; // initial Map of votes
};
export const MIN_TRANSACTION_AMOUNT = ONE_TRX;
export const SR_THRESHOLD = 27;
export const SR_MAX_VOTES = 5;
let __lastSeenSR: SuperRepresentative[] = [];
/** Fetch the list of super representatives */
export const useTronSuperRepresentatives = (): Array<SuperRepresentative> => {
const [sr, setSr] = useState(__lastSeenSR);
useEffect(() => {
let unsub = false;
getTronSuperRepresentatives().then((sr: SuperRepresentative[]) => {
__lastSeenSR = sr;
if (unsub) return;
setSr(sr);
});
return () => {
unsub = true;
};
}, []);
return sr;
};
/** Get last time voted */
export const getLastVotedDate = (account: TronAccount): Date | null | undefined => {
return account.tronResources && account.tronResources.lastVotedDate
? account.tronResources.lastVotedDate
: null;
};
/** Get next available date to claim rewards */
export const getNextRewardDate = (account: TronAccount): number | null | undefined => {
const lastWithdrawnRewardDate =
account.tronResources && account.tronResources.lastWithdrawnRewardDate
? account.tronResources.lastWithdrawnRewardDate
: null;
if (lastWithdrawnRewardDate) {
// add 24hours
const nextDate = lastWithdrawnRewardDate.getTime() + 24 * 60 * 60 * 1000;
if (nextDate > Date.now()) return nextDate;
}
return null;
};
/** format votes with superrepresentatives data */
export const formatVotes = (
votes: Array<Vote> | null | undefined,
superRepresentatives: Array<SuperRepresentative> | null | undefined,
): Array<
Vote & {
validator?: SuperRepresentative | null;
isSR: boolean;
rank: number;
}
> => {
return votes && superRepresentatives
? votes.map(({ address, voteCount }) => {
const srIndex = superRepresentatives.findIndex(sp => sp.address === address);
return {
validator: superRepresentatives[srIndex],
rank: srIndex + 1,
isSR: srIndex < SR_THRESHOLD,
address,
voteCount,
};
})
: [];
};
// wait an effect of a tron freeze until it effectively change
export function useTronPowerLoading(account: TronAccount): boolean {
const tronPower = (account.tronResources && account.tronResources.tronPower) || 0;
const initialTronPower = useRef(tronPower);
const initialAccount = useRef(account);
const [isLoading, setLoading] = useState<boolean>(true);
useEffect(() => {
if (initialTronPower.current !== tronPower) {
setLoading(false);
}
}, [tronPower]);
const sync = useBridgeSync();
useEffect(() => {
if (!isLoading) return;
const interval = setInterval(() => {
sync({
type: "SYNC_ONE_ACCOUNT",
priority: 10,
accountId: initialAccount.current.id,
reason: "tron-power-load",
});
}, 5000);
return () => clearInterval(interval);
}, [initialAccount, sync, isLoading]);
return isLoading;
}
/** Search filters for SR list */
const searchFilter =
(query?: string) =>
({ name, address }: { name: string | null | undefined; address: string }) => {
if (!query) return true;
const terms = `${name || ""} ${address}`;
return terms.toLowerCase().includes(query.toLowerCase().trim());
};
/** Hook to search and sort SR list according to initial votes and query */
export function useSortedSr(
search: string,
superRepresentatives: SuperRepresentative[],
votes: Vote[],
): {
sr: SuperRepresentative;
name: string | null | undefined;
address: string;
rank: number;
isSR: boolean;
}[] {
const { current: initialVotes } = useRef(votes.map(({ address }) => address));
const SR = useMemo(
() =>
superRepresentatives.map((sr, rank) => ({
sr,
name: sr.name,
address: sr.address,
rank: rank + 1,
isSR: rank < SR_THRESHOLD,
})),
[superRepresentatives],
);
const sortedVotes = useMemo(
() =>
SR.filter(({ address }) => initialVotes.includes(address)).concat(
SR.filter(({ address }) => !initialVotes.includes(address)),
),
[SR, initialVotes],
);
const sr = useMemo(
() => (search ? SR.filter(searchFilter(search)) : sortedVotes),
[search, SR, sortedVotes],
);
return sr;
}
/** format account to retrieve unfreeze data */
export const getUnfreezeData = (
account: TronAccount,
): {
unfreezeBandwidth: BigNumber;
unfreezeEnergy: BigNumber;
canUnfreezeBandwidth: boolean;
canUnfreezeEnergy: boolean;
} => {
const { tronResources } = account;
invariant(tronResources, "getUnfreezeData: tron account is expected");
const frozen = tronResources?.frozen ?? { bandwidth: null, energy: null };
const bandwidth = frozen.bandwidth;
const energy = frozen.energy;
const unfreezeBandwidth = new BigNumber(bandwidth ? bandwidth.amount : 0);
const canUnfreezeBandwidth = unfreezeBandwidth.gt(0);
const unfreezeEnergy = new BigNumber(energy ? energy.amount : 0);
const canUnfreezeEnergy = unfreezeEnergy.gt(0);
return {
unfreezeBandwidth,
unfreezeEnergy,
canUnfreezeBandwidth,
canUnfreezeEnergy,
};
};