import { WsProvider, ApiPromise } from "@polkadot/api"; import { types } from "@joystream/types"; import { Active, Approved, ExecutionFailed, Finalized, Proposal, ProposalDetails, ProposalId } from "@joystream/types/proposals"; import { StorageKey, Vec } from "@polkadot/types"; import { Membership } from "@joystream/types/members"; import { Stake, Staked, StakeId } from "@joystream/types/stake"; import { AnyJson } from "@polkadot/types/types"; interface ProposalOverview { id: number, type: string, title: string, createdBy: [number,string], stakeId: number, stake: number, created: number, status: string, finalizedAt?: number, result?: string, feePaid?: number, executionStatus?: string executeAt?: number, voteResult?: AnyJson, execution?: AnyJson, } async function main() { // Initialise the provider to connect to the local node const provider = new WsProvider('ws://127.0.0.1:9944'); //If you want to play around on our staging network, go ahead and connect to this staging network instead. //const provider = new WsProvider('wss://testnet-rpc-2-singapore.joystream.org'); // Create the API and wait until ready const api = await ApiPromise.create({ provider, types }) const allProposals: ProposalOverview[] = [] // get all proposalIds // sort them by id const proposalKeys = await api.query.proposalsEngine.proposals.keys() const proposalIds = proposalKeys.map(({ args: [proposalId]}) => proposalId) as Vec proposalIds.sort((a,b) => a.toNumber()-b.toNumber()) console.log("number of proposals", proposalIds.length); // get all stakeIds associated with proposalIds const stakeIdOfProposalId = await api.query.proposalsEngine.stakesProposals.keys() as StorageKey[] const stakeIdsOfProposalIds = stakeIdOfProposalId.map(({ args: [stakeId]}) => stakeId) as Vec stakeIdsOfProposalIds.sort((a,b) => a.toNumber()-b.toNumber()) console.log("number of stakeIdsOfProposalIds:", stakeIdsOfProposalIds.length); for (let id of proposalIds) { const proposal = await api.query.proposalsEngine.proposals(id) as Proposal const proposerHandle = (await api.query.members.membershipById(proposal.proposerId) as Membership).handle.toString() const proposalStatus = proposal.status.value const proposalDetails = await api.query.proposalsCodex.proposalDetailsByProposalId(id) as ProposalDetails let stakeId = (stakeIdsOfProposalIds[proposalIds.indexOf(id)]).toNumber() let stake = 0 if ((await api.query.proposalsEngine.stakesProposals(stakeId) as ProposalId).toNumber() === id.toNumber()) { const blockHash = await api.rpc.chain.getBlockHash(proposal.createdAt) const proposalStake = await api.query.stake.stakes.at(blockHash,stakeId) as Stake if (proposalStake.staking_status instanceof Staked) { stake = proposalStake.staking_status.staked_amount.toNumber() } } else { // This should never be the case... stakeId = -1 } const proposalData: ProposalOverview = { id: id.toNumber(), type: proposalDetails.type.toString(), title: proposal.title.toString(), createdBy: [proposal.proposerId.toNumber(),proposerHandle], stakeId, stake, created: proposal.createdAt.toNumber(), status: proposal.status.value.toString(), } // these proposals will have an annoyngly large 'execution' if (proposalDetails.type != "Text" && proposalDetails.type!= "RuntimeUpgrade") { proposalData.execution = proposalDetails.value.toHuman() } // check if the proposal is "Active" if (proposalStatus instanceof Active) { proposalData.status = proposalStatus.value.toString() } else { // There really isn't any other options here... if (proposalStatus instanceof Finalized) { proposalData.status = proposalStatus.proposalStatus.type proposalData.finalizedAt = proposalStatus.finalizedAt.toNumber() proposalData.voteResult = proposal.votingResults.toHuman() const proposalResult = proposalStatus.proposalStatus.value // check if the proposal is "Approved" if (proposalResult instanceof Approved) { proposalData.feePaid = 0 const gracePeriod = proposal.parameters.gracePeriod.toNumber() proposalData.executeAt = proposalStatus.finalizedAt.toNumber() + gracePeriod proposalData.executionStatus = proposalResult.type // "Executed" and "PendingExecution" works differently than "ExecutionFailed" // the latter will have some information on what went wrong if (proposalResult.isOfType("Executed")) { } else if (proposalResult.isOfType("PendingExecution")) { } else if (proposalResult.value instanceof ExecutionFailed) { proposalData.executionStatus = proposalResult.type + `:` + proposalResult.value.error.toString() } } else { // If not finalized, but not approved, it must be one of... if (proposalStatus.proposalStatus.isOfType("Canceled")) { proposalData.feePaid = 10000 } else if (proposalStatus.proposalStatus.isOfType("Slashed")) { proposalData.feePaid = stake } else if (proposalStatus.proposalStatus.isOfType("Vetoed")) { proposalData.feePaid = 0 // .. "Expired" or "Rejected", which are treated as the same. } else { proposalData.feePaid = 5000 } } } } allProposals.push(proposalData) } console.log("allProposals",JSON.stringify(allProposals, null, 4)) api.disconnect() } main()