Those sorts of delegation to other groups of people besides validators would be included in the product / ux workflows of things that are specifically governance focused, not in the workflow for nominating validators.
The kind of thing your suggesting from a product perspective takes a lot longer to implement.
Adding in this sort of delegation checkbox to nomination workflows can be done within a ~week optimistically.
Ideally the sort of informed delegation decisions to people in the rest of the network might be good features for the kind of things @XyloDrone / Ordum want to make, or to subsquare/polkassembly, which can be done in parallel to features like nominator delegation, which would be much quicker to implement.
And IMO, just to highlight what I think both of these things look like from a product and integation standpoint:
Governance Products
For something like encourage delegation to anyone in the network, the userflow of this would first be:
1.) A user connects their wallet with the application via something like Polkadot js extension, talisman, subwallet, etc.
2.) Upon the user connecting a wallet, there would be a handful of queries does in the backround. We would first want to determine if the user has participated in governance, or if they’re are currently delegating to anyone for all or particular governance tracks.
a.) Right now the dev ex of some of these things is not ideal, as the storage items for this exist in convictionVoting.votingFor
.
You would query for all tracks via something like:
const addressVoting = await api.query.convictionVoting.entries(ADDRESS)
This will return all the tracks someone is either delegating for, or if they have voted.
Based on what this returns, if the amount of tracks, or amount of things they have voted for is not above some threshold, the app would display some kind of banner or toast saying something like: “You are not currently participating in governance as much, consider delegating by clicking here”.
b.) By clicking that they are brought to a new modal or page that starts the governance workflow
3.) The governance workflow begins with a list of say something like ~50 accounts that are reccomendations of whom they should delegate to.
This list of accounts is something that aligns with the goals of getting the most amount of tokens in the network participating in governance. That is, the suggested accounts are those that 1.) consistently vote on things in the network, and also possibley 2.) also have a lot of other delegations.
Aggregating this sort of information can be a bit tricky because you need to have a list of all accounts in the network that participate in things historically, of which querying this from the chain itself often leads to bad ux for the user as it can take a bit of time to actually put this together. So something like subsquid, subquery, or some type of indexer storing this in a relational db and served via an api might be needed.
I’ve written a query of this for the chain itself here in the process of indexing it:
// OpenGov Conviction Voting
getConvictionVoting = async () => {
const allVotes: ConvictionVote[] = [];
const allDelegations: ConvictionDelegation[] = [];
// Create a map to more easily check the status of a referenda, is it ongoing or finished
const referendaMap = new Map();
const { ongoingReferenda, finishedReferenda } =
await this.getOpenGovReferenda();
for (const ref of ongoingReferenda) {
referendaMap.set(ref.index, ref);
}
for (const ref of finishedReferenda) {
referendaMap.set(ref.index, ref);
}
const tracks = this.api.consts.referenda.tracks;
// Query the keys and storage of all the entries of `votingFor`
// These are all the accounts voting, for which tracks, for which referenda
// And whether they are delegating or not.
const votingFor = await this.api.query.convictionVoting.votingFor.entries();
for (const [key, entry] of votingFor) {
// Each of these is the votingFor for an account for a given governance track
// @ts-ignore
const [address, track] = key.toHuman();
// For each track, an account is either voting themselves, or delegating to another account
// The account is voting themselves
// @ts-ignore
if (entry.isCasting) {
// For each given track, these are the invididual votes for that track,
// as well as the total delegation amounts for that particular track
// @ts-ignore
const { votes, delegations } = entry.asCasting;
// The total delegation amounts.
// delegationVotes - the _total_ amount of tokens applied in voting. This takes the conviction into account
// delegationCapital - the base level of tokens delegated to this address
const { votes: delegationVotes, capital: delegationCapital } =
delegations;
// The list of votes for that track
for (const referendumVote of votes) {
// The vote for each referendum - this is the referendum index,the conviction, the vote type (aye,nay), and the balance
const [referendumIndex, voteType] = referendumVote;
if (voteType.isStandard) {
const { vote: refVote, balance } = voteType.asStandard;
const { conviction, vote: voteDirection } = refVote.toHuman();
// The formatted vote
const v: ConvictionVote = {
// The particular governance track
track: Number(track.toString()),
// The account that is voting
address: address.toString(),
// The index of the referendum
referendumIndex: Number(referendumIndex.toString()),
// The conviction being voted with, ie `None`, `Locked1x`, `Locked5x`, etc
conviction: conviction.toString(),
// The balance they are voting with themselves, sans delegated balance
balance: {
aye:
voteDirection.toString() == "Aye"
? Number(balance.toJSON())
: 0,
nay:
voteDirection.toString() == "Nay"
? Number(balance.toJSON())
: 0,
abstain: 0,
},
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance: Number(delegationVotes.toString()),
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: Number(delegationCapital.toString()),
// The vote type, either 'aye', or 'nay'
voteDirection: voteDirection.toString(),
// The vote direction type, either "Standard", "Split", or "SplitAbstain"
voteDirectionType: "Standard",
// Whether the person is voting themselves or delegating
voteType: "Casting",
// Who the person is delegating to
delegatedTo: null,
};
allVotes.push(v);
} else if (voteType.isSplit) {
const { aye, nay } = voteType.asSplit;
// The formatted vote
const v: ConvictionVote = {
// The particular governance track
track: Number(track.toString()),
// The account that is voting
address: address.toString(),
// The index of the referendum
referendumIndex: Number(referendumIndex.toString()),
// The conviction being voted with, ie `None`, `Locked1x`, `Locked5x`, etc
conviction: "Locked1x",
// The balance they are voting with themselves, sans delegated balance
balance: {
aye: Number(aye),
nay: Number(nay),
abstain: 0,
},
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance: Number(delegationVotes.toString()),
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: Number(delegationCapital.toString()),
// The vote type, either 'aye', or 'nay'
voteDirection: aye >= nay ? "Aye" : "Nay",
// The vote direction type, either "Standard", "Split", or "SplitAbstain"
voteDirectionType: "Split",
// Whether the person is voting themselves or delegating
voteType: "Casting",
// Who the person is delegating to
delegatedTo: null,
};
allVotes.push(v);
} else {
const voteJSON = voteType.toJSON();
if (voteJSON["splitAbstain"]) {
const { aye, nay, abstain } = voteJSON["splitAbstain"];
// The formatted vote
const v: ConvictionVote = {
// The particular governance track
track: Number(track.toString()),
// The account that is voting
address: address.toString(),
// The index of the referendum
referendumIndex: Number(referendumIndex.toString()),
// The conviction being voted with, ie `None`, `Locked1x`, `Locked5x`, etc
conviction: "Locked1x",
// The balance they are voting with themselves, sans delegated balance
balance: {
aye: Number(aye),
nay: Number(nay),
abstain: Number(abstain),
},
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance: Number(delegationVotes.toString()),
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: Number(delegationCapital.toString()),
// The vote type, either 'aye', or 'nay'
voteDirection:
abstain >= aye && abstain >= nay
? "Abstain"
: aye > +nay
? "Aye"
: "Nay",
// The vote direction type, either "Standard", "Split", or "SplitAbstain"
voteDirectionType: "SplitAbstain",
// Whether the person is voting themselves or delegating
voteType: "Casting",
// Who the person is delegating to
delegatedTo: null,
};
allVotes.push(v);
}
}
}
// @ts-ignore
} else if (entry.isDelegating) {
// The address is delegating to another address for this particular track
const {
balance,
target,
conviction,
delegations: { votes: delegationVotes, capital: delegationCapital },
prior,
// @ts-ignore
} = entry.asDelegating;
const delegation: ConvictionDelegation = {
track: track,
address: address.toString(),
target: target.toString(),
balance: balance.toString(),
conviction: conviction.toString(),
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance: delegationVotes.toString(),
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: delegationCapital.toString(),
prior: prior,
};
allDelegations.push(delegation);
}
}
// Create a vote entry for everyone that is delegating for current ongoing referenda
for (const delegation of allDelegations) {
// Find the vote of the person they are delegating to
const v = allVotes.filter((vote) => {
const isReferendumOngoing =
referendaMap.get(vote.referendumIndex)?.currentStatus == "Ongoing";
return (
isReferendumOngoing &&
vote.address == delegation.target &&
vote.track == delegation.track
);
});
if (v.length > 0) {
// There are votes for a given track that a person delegating will have votes for.
for (const vote of v) {
const voteDirectionType = vote.voteDirectionType;
let balance;
if (voteDirectionType == "Aye") {
balance = {
aye: Number(delegation.balance),
nay: Number(0),
abstain: Number(0),
};
} else if (voteDirectionType == "Nay") {
balance = {
aye: Number(0),
nay: Number(delegation.balance),
abstain: Number(0),
};
} else if (
voteDirectionType == "Split" ||
voteDirectionType == "SplitAbstain"
) {
balance = {
aye: Number(0),
nay: Number(0),
abstain: Number(0),
};
}
const delegatedVote: ConvictionVote = {
// The particular governance track
track: vote.track,
// The account that is voting
address: delegation.address,
// The index of the referendum
referendumIndex: vote.referendumIndex,
// The conviction being voted with, ie `None`, `Locked1x`, `Locked5x`, etc
conviction: delegation.conviction,
// The balance they are voting with themselves, sans delegated balance
balance: balance,
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance: delegation.delegatedConvictionBalance,
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: delegation.delegatedBalance,
// The vote type, either 'aye', or 'nay'
voteDirection: vote.voteDirection,
// Whether the person is voting themselves or delegating
voteType: "Delegating",
voteDirectionType: voteDirectionType,
// Who the person is delegating to
delegatedTo: vote.address,
};
allVotes.push(delegatedVote);
}
} else if (v.length == 0) {
// There are no direct votes from the person the delegator is delegating to,
// but that person may also be delegating, so search for nested delegations
let found = false;
// The end vote of the chain of delegations
let delegatedVote;
delegatedVote = delegation;
while (!found) {
// Find the delegation of the person who is delegating to
const d = allDelegations.filter((del) => {
return (
del.address == delegatedVote.target &&
del.track == delegatedVote.track
);
});
if (d.length == 0) {
// There are no additional delegations, try to find if there are any votes
found = true;
const v = allVotes.filter((vote) => {
return (
vote.address == delegatedVote.target &&
vote.track == delegatedVote.track
);
});
if (v.length > 0) {
// There are votes, ascribe them to the delegator
for (const vote of v) {
const voteDirectionType = vote.voteDirectionType;
let balance;
if (voteDirectionType == "Aye") {
balance = {
aye: Number(delegation.balance),
nay: Number(0),
abstain: Number(0),
};
} else if (voteDirectionType == "Nay") {
balance = {
aye: Number(0),
nay: Number(delegation.balance),
abstain: Number(0),
};
} else if (voteDirectionType == "Split") {
const ayePercentage =
vote.balance.aye / (vote.balance.aye + vote.balance.nay);
const nayPercentage =
vote.balance.nay / (vote.balance.aye + vote.balance.nay);
balance = {
aye: Number(delegation.balance) * ayePercentage,
nay: Number(delegation.balance) * nayPercentage,
abstain: Number(0),
};
} else if (voteDirectionType == "SplitAbstain") {
const ayePercentage =
vote.balance.aye /
(vote.balance.aye +
vote.balance.nay +
vote.balance.abstain);
const nayPercentage =
vote.balance.nay /
(vote.balance.aye +
vote.balance.nay +
vote.balance.abstain);
const abstainPercentage =
vote.balance.nay /
(vote.balance.aye +
vote.balance.nay +
vote.balance.abstain);
balance = {
aye: Number(delegation.balance) * ayePercentage,
nay: Number(delegation.balance) * nayPercentage,
abstain: Number(delegation.balance) * abstainPercentage,
};
}
const delegatedVote: ConvictionVote = {
// The particular governance track
track: vote.track,
// The account that is voting
address: delegation.address,
// The index of the referendum
referendumIndex: vote.referendumIndex,
// The conviction being voted with, ie `None`, `Locked1x`, `Locked5x`, etc
conviction: delegation.conviction,
// The balance they are voting with themselves, sans delegated balance
balance: balance,
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance:
delegation.delegatedConvictionBalance,
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: delegation.delegatedBalance,
// The vote type, either 'aye', or 'nay'
voteDirection: vote.voteDirection,
// Whether the person is voting themselves or delegating
voteType: "Delegating",
voteDirectionType: voteDirectionType,
// Who the person is delegating to
delegatedTo: vote.address,
};
allVotes.push(delegatedVote);
}
} else {
// The person they are delegating to does not have any votes.
}
} else if (d.length == 1) {
// There is a delegated delegation
delegatedVote = d[0];
}
}
}
}
// Query the delegations for finished referenda at previous block heights
for (const referendum of finishedReferenda) {
const apiAt = await this.getApiAt(referendum.confirmationBlockNumber - 1);
const votingFor = await apiAt.query.convictionVoting.votingFor.entries();
for (const [key, entry] of votingFor) {
// Each of these is the votingFor for an account for a given governance track
// @ts-ignore
const [address, track] = key.toHuman();
// @ts-ignore
if (entry.isDelegating) {
// The address is delegating to another address for this particular track
const {
balance,
target,
conviction,
delegations: { votes: delegationVotes, capital: delegationCapital },
prior,
// @ts-ignore
} = entry.asDelegating;
const delegation: ConvictionDelegation = {
track: track,
address: address.toString(),
target: target.toString(),
balance: balance.toString(),
conviction: conviction.toString(),
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance: delegationVotes.toString(),
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: delegationCapital.toString(),
prior: prior,
};
// Try and find the delegated vote from the existing votes
const v = allVotes.filter((vote) => {
return (
vote.address == delegation.target &&
vote.track == delegation.track
);
});
if (v.length > 0) {
// There are votes for a given track that a person delegating will have votes for.
for (const vote of v) {
const voteDirectionType = vote.voteDirectionType;
let balance;
if (voteDirectionType == "Aye") {
balance = {
aye: Number(delegation.balance),
nay: Number(0),
abstain: Number(0),
};
} else if (voteDirectionType == "Nay") {
balance = {
aye: Number(0),
nay: Number(delegation.balance),
abstain: Number(0),
};
} else if (
voteDirectionType == "Split" ||
voteDirectionType == "SplitAbstain"
) {
balance = {
aye: Number(0),
nay: Number(0),
abstain: Number(0),
};
}
const delegatedVote: ConvictionVote = {
// The particular governance track
track: vote.track,
// The account that is voting
address: delegation.address,
// The index of the referendum
referendumIndex: vote.referendumIndex,
// The conviction being voted with, ie `None`, `Locked1x`, `Locked5x`, etc
conviction: delegation.conviction,
// The balance they are voting with themselves, sans delegated balance
balance: balance,
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance:
delegation.delegatedConvictionBalance,
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: delegation.delegatedBalance,
// The vote type, either 'aye', or 'nay'
voteDirection: vote.voteDirection,
// Whether the person is voting themselves or delegating
voteType: "Delegating",
voteDirectionType: voteDirectionType,
// Who the person is delegating to
delegatedTo: vote.address,
};
allVotes.push(delegatedVote);
}
} else if (v.length == 0) {
// There are no direct votes from the person the delegator is delegating to,
// but that person may also be delegating, so search for nested delegations
let found = false;
// The end vote of the chain of delegations
let delegatedVote;
delegatedVote = delegation;
while (!found) {
// Find the delegation of the person who is delegating to
const d = allDelegations.filter((del) => {
return (
del.address == delegatedVote.target &&
del.track == delegatedVote.track
);
});
if (d.length == 0) {
// There are no additional delegations, try to find if there are any votes
found = true;
const v = allVotes.filter((vote) => {
return (
vote.address == delegatedVote.target &&
vote.track == delegatedVote.track
);
});
if (v.length > 0) {
// There are votes, ascribe them to the delegator
for (const vote of v) {
const voteDirectionType = vote.voteDirectionType;
let balance;
if (voteDirectionType == "Aye") {
balance = {
aye: Number(delegation.balance),
nay: Number(0),
abstain: Number(0),
};
} else if (voteDirectionType == "Nay") {
balance = {
aye: Number(0),
nay: Number(delegation.balance),
abstain: Number(0),
};
} else if (voteDirectionType == "Split") {
const ayePercentage =
vote.balance.aye /
(vote.balance.aye + vote.balance.nay);
const nayPercentage =
vote.balance.nay /
(vote.balance.aye + vote.balance.nay);
balance = {
aye: Number(delegation.balance) * ayePercentage,
nay: Number(delegation.balance) * nayPercentage,
abstain: Number(0),
};
} else if (voteDirectionType == "SplitAbstain") {
const ayePercentage =
vote.balance.aye /
(vote.balance.aye +
vote.balance.nay +
vote.balance.abstain);
const nayPercentage =
vote.balance.nay /
(vote.balance.aye +
vote.balance.nay +
vote.balance.abstain);
const abstainPercentage =
vote.balance.nay /
(vote.balance.aye +
vote.balance.nay +
vote.balance.abstain);
balance = {
aye: Number(delegation.balance) * ayePercentage,
nay: Number(delegation.balance) * nayPercentage,
abstain: Number(delegation.balance) * abstainPercentage,
};
}
const delegatedVote: ConvictionVote = {
// The particular governance track
track: vote.track,
// The account that is voting
address: delegation.address,
// The index of the referendum
referendumIndex: vote.referendumIndex,
// The conviction being voted with, ie `None`, `Locked1x`, `Locked5x`, etc
conviction: delegation.conviction,
// The balance they are voting with themselves, sans delegated balance
balance: balance,
// The total amount of tokens that were delegated to them (including conviction)
delegatedConvictionBalance:
delegation.delegatedConvictionBalance,
// the total amount of tokens that were delegated to them (without conviction)
delegatedBalance: delegation.delegatedBalance,
// The vote type, either 'aye', or 'nay'
voteDirection: vote.voteDirection,
// Whether the person is voting themselves or delegating
voteType: "Delegating",
voteDirectionType: voteDirectionType,
// Who the person is delegating to
delegatedTo: vote.address,
};
allVotes.push(delegatedVote);
}
} else {
// The person they are delegating to does not have any votes.
}
} else if (d.length == 1) {
// There is a delegated delegation
delegatedVote = d[0];
}
}
}
}
}
}
const convictionVoting = {
votes: allVotes,
delegations: allDelegations,
};
return convictionVoting;
};
This is a bit verbose and can probably be slimmed down and improved, but it addresses some trickyness of trying to find out how to put together delegations at historical block numbers. Some of the full queries (and some more useful ones) can be found here.