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", }; 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`; // fetch reward and stake const w: WorkerReward = { id, status, handle, account, memberId }; if (worker.role_stake_profile.isSome) { const roleStakeProfile = worker.role_stake_profile.unwrap(); w.stake = await getStake(api, roleStakeProfile.stake_id); } if (worker.reward_relationship.isSome) { const id: RewardRelationshipId = worker.reward_relationship.unwrap(); w.reward = await getWorkerReward(api, hash, id); } workers.push(w); } 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; if (!reward) return ``; 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 spendingProposals: SpendingProposal[] = []; for (const [key, events] of blocks) { for (const event of events) { let statusUpdateData = event.data[1] as any; const finalizedAt = statusUpdateData.finalized.finalizedAt; if (!(statusUpdateData.finalized && finalizedAt)) continue; const proposalId = event.data[0] as ProposalId; const id = +proposalId; const proposalInfo: ProposalOf = await getProposalInfo(api, proposalId); const finalizedData = proposalInfo.status.asFinalized; const details: ProposalDetailsOf = await getProposalDetails( api, proposalId ); if (!finalizedData.proposalStatus.isApproved || !details.isSpending) continue; let approvedData = finalizedData.proposalStatus.asApproved; if (!approvedData.isExecuted) continue; if (spendingProposals.some((proposal) => +proposal.id === +id)) continue; const title = String(proposalInfo.title.toHuman()); const amount = +details.asSpending[0]; console.log(`Spent ${amount.toFixed().padStart(8)} on ${id} ${title}`); spendingProposals.push({ id, title, amount }); } } 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[] = []; while (!activeValidators.length) { 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; } return activeValidators; };