import { ApiPromise } from "@polkadot/api"; // types import { Bounty, CacheEvent, WorkerReward, SpendingProposal } from "./types"; import { AccountId, Balance } from "@polkadot/types/interfaces"; import { Hash } from "@polkadot/types/interfaces"; import { Membership } from "@joystream/types/members"; import { Mint, MintId } from "@joystream/types/mint"; import { Proposal, ProposalId, SpendingParams, } from "@joystream/types/proposals"; import { ProposalDetails, ProposalOf } from "@joystream/types/augment/types"; import { Stake } from "@joystream/types/stake"; import { RewardRelationship, RewardRelationshipId, } from "@joystream/types/recurring-rewards"; // lib import { getPercent, getTotalMinted } from "./"; import { getBlock, getBlockHash, getMint, getNextWorker, getMember, getWorker, getWorkerReward, getProposalInfo, getProposalDetails, getStake, getValidators, getValidatorCount, } from "./api"; import { WorkerOf } from "@joystream/types/augment-codec/all"; import { ProposalDetailsOf } from "@joystream/types/augment/types"; export const filterMethods = { getBurnedTokens: ({ section, method }: CacheEvent) => section === "balances" && method === "Transfer", newValidatorsRewards: ({ section, method }: CacheEvent) => section === "staking" && method === "Reward", finalizedSpendingProposals: ({ section, method }: CacheEvent) => section === "proposalsEngine" && method === "ProposalStatusUpdated", sudoSetBalance: ({ section, method }: CacheEvent) => section === "balances" && method === "BalanceSet", proposalStatusUpdated: ({ section, method }: CacheEvent) => section === "proposalsEngine" && method === "ProposalStatusUpdated", }; export const getWorkerRewards = async ( api: ApiPromise, group: string, hash: Hash ): Promise => { let workers = Array(); const nextWorkerId = await getNextWorker(api, group, hash); for (let id = 0; id < nextWorkerId; ++id) { const worker: WorkerOf = await getWorker(api, group, hash, id); const account = worker.role_account_id as AccountId; const memberId = worker.member_id; const member: Membership = await getMember(api, memberId, hash); const handle = member ? String(member.handle) : account.toString(); const status = worker.is_active ? `active` : `inactive`; let stake: Stake; let reward: RewardRelationship; if (worker.role_stake_profile.isSome) { const roleStakeProfile = worker.role_stake_profile.unwrap(); stake = await getStake(api, roleStakeProfile.stake_id, hash); } if (worker.reward_relationship.isSome) { // TODO changing salaries are not reflected const rewardId: RewardRelationshipId = worker.reward_relationship.unwrap(); reward = await getWorkerReward(api, hash, rewardId); } workers.push({ id, stake, reward, status, handle, account, memberId }); } return workers; }; export const getWorkerRow = ( worker: WorkerReward, earnedStart: number ): string => { const mtjoy = (mtjoy: number): string => (mtjoy / 1000000).toFixed(1); const { id, memberId, account, handle, status, reward } = worker; const earnedEnd = Number(reward.total_reward_received.toBigInt()); if (!earnedEnd) return ``; const totalEarned = mtjoy(earnedEnd); const earnedTerm = mtjoy(earnedEnd - earnedStart); const amount = Number(reward.amount_per_payout.toBigInt()); const rewardPerBlock = (amount / Number(reward.payout_interval)).toFixed(); const url = `https://pioneer.joystreamstats.live/#/members/${handle}`; // TODO return `| ${id} | [@${handle}](${url}) | ${status} | ${rewardPerBlock} | ${earnedTerm} | ${totalEarned} |\n`; }; export const getBurnedTokens = ( burnAddress: string, blocks: [number, CacheEvent[]][] ): number => { let tokensBurned = 0; blocks.forEach(([key, transfers]) => transfers.forEach((transfer) => { let receiver = transfer.data[1] as AccountId; let amount = transfer.data[2] as Balance; if (receiver.toString() === burnAddress) tokensBurned += Number(amount); }) ); return tokensBurned; }; export const getFinalizedSpendingProposals = async ( api: ApiPromise, blocks: [number, CacheEvent[]][] ): Promise => { const selectedEvents: CacheEvent[] = []; blocks.map(([key, proposalEvents]) => proposalEvents.map((proposalEvent) => selectedEvents.push(proposalEvent)) ); let spendingProposals: SpendingProposal[] = []; for (const proposalEvent of selectedEvents) { let statusUpdateData = proposalEvent.data[1] as any; const finalizedAt = statusUpdateData.finalized.finalizedAt; if (!(statusUpdateData.finalized && finalizedAt)) continue; const proposalId = proposalEvent.data[0] as ProposalId; const proposalInfo: ProposalOf = await getProposalInfo(api, proposalId); if (!proposalInfo) continue; try { const finalizedData = proposalInfo.status.asFinalized; const details: ProposalDetailsOf = await getProposalDetails( api, proposalId ); if (finalizedData.proposalStatus.isApproved && details.isSpending) { let approvedData = finalizedData.proposalStatus.asApproved; if (!approvedData.isExecuted) continue; if ( !spendingProposals.some((proposal) => proposal.id === +proposalId) ) { const title = proposalInfo.title.toString(); const amount = +details.asSpending[0]; spendingProposals.push({ id: +proposalId, title, amount }); } } } catch (e) { console.error(`Failed to fetch proposal info: ${e.message}`); continue; } } return spendingProposals; }; export const getValidatorsRewards = ( blocks: [number, CacheEvent[]][] ): number => { let newValidatorRewards = 0; blocks.forEach(([key, validatorRewards]) => validatorRewards.forEach( (reward: CacheEvent) => (newValidatorRewards += Number(reward.data[1])) ) ); return newValidatorRewards; }; export const getActiveValidators = async ( api: ApiPromise, hash: Hash, searchPreviousBlocks: boolean = false ): Promise => { const block = await getBlock(api, hash); let currentBlockNr = block.block.header.number.toNumber(); let activeValidators: AccountId[]; do { const hash: Hash = await getBlockHash(api, currentBlockNr); const validators: AccountId[] = await getValidators(api, hash); if (validators.length) { let max = await getValidatorCount(api, hash); activeValidators = validators.slice(0, max); } if (searchPreviousBlocks) --currentBlockNr; else ++currentBlockNr; } while (!activeValidators); return activeValidators; };