@snapshot-labs/snapshot.js 0.12.49 → 0.12.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/snapshot.cjs.js +119 -0
- package/dist/snapshot.esm.js +119 -0
- package/dist/snapshot.min.js +6 -6
- package/dist/src/index.d.ts +1 -0
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/voting/copeland.d.ts +18 -0
- package/dist/src/voting/index.d.ts +2 -0
- package/dist/src/voting/quadratic.d.ts +4 -4
- package/dist/src/voting/types.d.ts +10 -26
- package/package.json +1 -1
- package/src/schemas/proposal.json +1 -0
- package/src/sign/index.ts +1 -0
- package/src/voting/__snapshots__/copeland.spec.js.snap +134 -0
- package/src/voting/copeland.spec.js +129 -0
- package/src/voting/copeland.ts +155 -0
- package/src/voting/examples/copeland.json +32 -0
- package/src/voting/index.ts +2 -0
- package/src/voting/quadratic.ts +4 -4
- package/src/voting/types.ts +8 -29
package/dist/snapshot.cjs.js
CHANGED
|
@@ -351,6 +351,8 @@ class Client {
|
|
|
351
351
|
}
|
|
352
352
|
proposal(web3, address, message) {
|
|
353
353
|
return __awaiter(this, void 0, void 0, function* () {
|
|
354
|
+
if (!message.labels)
|
|
355
|
+
message.labels = [];
|
|
354
356
|
if (!message.discussion)
|
|
355
357
|
message.discussion = '';
|
|
356
358
|
if (!message.app)
|
|
@@ -1088,6 +1090,7 @@ var definitions$1 = {
|
|
|
1088
1090
|
"approval",
|
|
1089
1091
|
"ranked-choice",
|
|
1090
1092
|
"quadratic",
|
|
1093
|
+
"copeland",
|
|
1091
1094
|
"weighted",
|
|
1092
1095
|
"custom",
|
|
1093
1096
|
"basic"
|
|
@@ -3942,6 +3945,121 @@ class RankedChoiceVoting {
|
|
|
3942
3945
|
}
|
|
3943
3946
|
}
|
|
3944
3947
|
|
|
3948
|
+
// CopelandVoting implements ranked choice voting using Copeland's method
|
|
3949
|
+
// This method compares each pair of choices and awards points based on pairwise victories
|
|
3950
|
+
class CopelandVoting {
|
|
3951
|
+
constructor(proposal, votes, strategies, selected) {
|
|
3952
|
+
this.proposal = proposal;
|
|
3953
|
+
this.votes = votes;
|
|
3954
|
+
this.strategies = strategies;
|
|
3955
|
+
this.selected = selected;
|
|
3956
|
+
}
|
|
3957
|
+
// Validates if a vote choice is valid for the given proposal
|
|
3958
|
+
// Allows partial ranking (not all choices need to be ranked)
|
|
3959
|
+
static isValidChoice(voteChoice, proposalChoices) {
|
|
3960
|
+
if (!Array.isArray(voteChoice) ||
|
|
3961
|
+
voteChoice.length === 0 ||
|
|
3962
|
+
voteChoice.length > proposalChoices.length ||
|
|
3963
|
+
new Set(voteChoice).size !== voteChoice.length) {
|
|
3964
|
+
return false;
|
|
3965
|
+
}
|
|
3966
|
+
return voteChoice.every((choice) => Number.isInteger(choice) &&
|
|
3967
|
+
choice >= 1 &&
|
|
3968
|
+
choice <= proposalChoices.length);
|
|
3969
|
+
}
|
|
3970
|
+
// Returns only the valid votes
|
|
3971
|
+
getValidVotes() {
|
|
3972
|
+
return this.votes.filter((vote) => CopelandVoting.isValidChoice(vote.choice, this.proposal.choices));
|
|
3973
|
+
}
|
|
3974
|
+
// Calculates the Copeland scores for each choice
|
|
3975
|
+
getScores() {
|
|
3976
|
+
const validVotes = this.getValidVotes();
|
|
3977
|
+
const choicesCount = this.proposal.choices.length;
|
|
3978
|
+
const pairwiseComparisons = Array.from({ length: choicesCount }, () => Array(choicesCount).fill(0));
|
|
3979
|
+
// Calculate pairwise comparisons
|
|
3980
|
+
for (const vote of validVotes) {
|
|
3981
|
+
for (let i = 0; i < vote.choice.length; i++) {
|
|
3982
|
+
for (let j = i + 1; j < vote.choice.length; j++) {
|
|
3983
|
+
const winner = vote.choice[i] - 1;
|
|
3984
|
+
const loser = vote.choice[j] - 1;
|
|
3985
|
+
pairwiseComparisons[winner][loser] += vote.balance;
|
|
3986
|
+
pairwiseComparisons[loser][winner] -= vote.balance;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
// Calculate Copeland scores
|
|
3991
|
+
const scores = Array(choicesCount).fill(0);
|
|
3992
|
+
for (let i = 0; i < choicesCount; i++) {
|
|
3993
|
+
for (let j = 0; j < choicesCount; j++) {
|
|
3994
|
+
if (i !== j) {
|
|
3995
|
+
if (pairwiseComparisons[i][j] > 0) {
|
|
3996
|
+
scores[i]++;
|
|
3997
|
+
}
|
|
3998
|
+
else if (pairwiseComparisons[i][j] < 0) {
|
|
3999
|
+
scores[j]++;
|
|
4000
|
+
}
|
|
4001
|
+
else {
|
|
4002
|
+
scores[i] += 0.5;
|
|
4003
|
+
scores[j] += 0.5;
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
return scores;
|
|
4009
|
+
}
|
|
4010
|
+
// Calculates the Copeland scores for each choice, broken down by strategy
|
|
4011
|
+
getScoresByStrategy() {
|
|
4012
|
+
const validVotes = this.getValidVotes();
|
|
4013
|
+
const choicesCount = this.proposal.choices.length;
|
|
4014
|
+
const strategiesCount = this.strategies.length;
|
|
4015
|
+
const pairwiseComparisons = Array.from({ length: choicesCount }, () => Array.from({ length: choicesCount }, () => Array(strategiesCount).fill(0)));
|
|
4016
|
+
// Calculate pairwise comparisons for each strategy
|
|
4017
|
+
for (const vote of validVotes) {
|
|
4018
|
+
for (let i = 0; i < vote.choice.length; i++) {
|
|
4019
|
+
for (let j = i + 1; j < vote.choice.length; j++) {
|
|
4020
|
+
const winner = vote.choice[i] - 1;
|
|
4021
|
+
const loser = vote.choice[j] - 1;
|
|
4022
|
+
for (let s = 0; s < strategiesCount; s++) {
|
|
4023
|
+
pairwiseComparisons[winner][loser][s] += vote.scores[s];
|
|
4024
|
+
pairwiseComparisons[loser][winner][s] -= vote.scores[s];
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
// Calculate Copeland scores for each strategy
|
|
4030
|
+
const scores = Array.from({ length: choicesCount }, () => Array(strategiesCount).fill(0));
|
|
4031
|
+
for (let i = 0; i < choicesCount; i++) {
|
|
4032
|
+
for (let j = 0; j < choicesCount; j++) {
|
|
4033
|
+
if (i !== j) {
|
|
4034
|
+
for (let s = 0; s < strategiesCount; s++) {
|
|
4035
|
+
if (pairwiseComparisons[i][j][s] > 0) {
|
|
4036
|
+
scores[i][s]++;
|
|
4037
|
+
}
|
|
4038
|
+
else if (pairwiseComparisons[i][j][s] < 0) {
|
|
4039
|
+
scores[j][s]++;
|
|
4040
|
+
}
|
|
4041
|
+
else {
|
|
4042
|
+
scores[i][s] += 0.5;
|
|
4043
|
+
scores[j][s] += 0.5;
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
return scores;
|
|
4050
|
+
}
|
|
4051
|
+
// Calculates the total score (sum of all valid vote balances)
|
|
4052
|
+
getScoresTotal() {
|
|
4053
|
+
return this.getValidVotes().reduce((total, vote) => total + vote.balance, 0);
|
|
4054
|
+
}
|
|
4055
|
+
// Returns a string representation of the selected choices
|
|
4056
|
+
getChoiceString() {
|
|
4057
|
+
return this.selected
|
|
4058
|
+
.map((choice) => this.proposal.choices[choice - 1])
|
|
4059
|
+
.join(', ');
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
|
|
3945
4063
|
function percentageOfTotal(i, values, total) {
|
|
3946
4064
|
const reducedTotal = total.reduce((a, b) => a + b, 0);
|
|
3947
4065
|
const percent = (values[i] / reducedTotal) * 100;
|
|
@@ -4014,6 +4132,7 @@ var voting = {
|
|
|
4014
4132
|
approval: ApprovalVoting,
|
|
4015
4133
|
quadratic: QuadraticVoting,
|
|
4016
4134
|
'ranked-choice': RankedChoiceVoting,
|
|
4135
|
+
copeland: CopelandVoting,
|
|
4017
4136
|
weighted: WeightedVoting,
|
|
4018
4137
|
basic: SingleChoiceVoting
|
|
4019
4138
|
};
|
package/dist/snapshot.esm.js
CHANGED
|
@@ -341,6 +341,8 @@ class Client {
|
|
|
341
341
|
}
|
|
342
342
|
proposal(web3, address, message) {
|
|
343
343
|
return __awaiter(this, void 0, void 0, function* () {
|
|
344
|
+
if (!message.labels)
|
|
345
|
+
message.labels = [];
|
|
344
346
|
if (!message.discussion)
|
|
345
347
|
message.discussion = '';
|
|
346
348
|
if (!message.app)
|
|
@@ -1078,6 +1080,7 @@ var definitions$1 = {
|
|
|
1078
1080
|
"approval",
|
|
1079
1081
|
"ranked-choice",
|
|
1080
1082
|
"quadratic",
|
|
1083
|
+
"copeland",
|
|
1081
1084
|
"weighted",
|
|
1082
1085
|
"custom",
|
|
1083
1086
|
"basic"
|
|
@@ -3932,6 +3935,121 @@ class RankedChoiceVoting {
|
|
|
3932
3935
|
}
|
|
3933
3936
|
}
|
|
3934
3937
|
|
|
3938
|
+
// CopelandVoting implements ranked choice voting using Copeland's method
|
|
3939
|
+
// This method compares each pair of choices and awards points based on pairwise victories
|
|
3940
|
+
class CopelandVoting {
|
|
3941
|
+
constructor(proposal, votes, strategies, selected) {
|
|
3942
|
+
this.proposal = proposal;
|
|
3943
|
+
this.votes = votes;
|
|
3944
|
+
this.strategies = strategies;
|
|
3945
|
+
this.selected = selected;
|
|
3946
|
+
}
|
|
3947
|
+
// Validates if a vote choice is valid for the given proposal
|
|
3948
|
+
// Allows partial ranking (not all choices need to be ranked)
|
|
3949
|
+
static isValidChoice(voteChoice, proposalChoices) {
|
|
3950
|
+
if (!Array.isArray(voteChoice) ||
|
|
3951
|
+
voteChoice.length === 0 ||
|
|
3952
|
+
voteChoice.length > proposalChoices.length ||
|
|
3953
|
+
new Set(voteChoice).size !== voteChoice.length) {
|
|
3954
|
+
return false;
|
|
3955
|
+
}
|
|
3956
|
+
return voteChoice.every((choice) => Number.isInteger(choice) &&
|
|
3957
|
+
choice >= 1 &&
|
|
3958
|
+
choice <= proposalChoices.length);
|
|
3959
|
+
}
|
|
3960
|
+
// Returns only the valid votes
|
|
3961
|
+
getValidVotes() {
|
|
3962
|
+
return this.votes.filter((vote) => CopelandVoting.isValidChoice(vote.choice, this.proposal.choices));
|
|
3963
|
+
}
|
|
3964
|
+
// Calculates the Copeland scores for each choice
|
|
3965
|
+
getScores() {
|
|
3966
|
+
const validVotes = this.getValidVotes();
|
|
3967
|
+
const choicesCount = this.proposal.choices.length;
|
|
3968
|
+
const pairwiseComparisons = Array.from({ length: choicesCount }, () => Array(choicesCount).fill(0));
|
|
3969
|
+
// Calculate pairwise comparisons
|
|
3970
|
+
for (const vote of validVotes) {
|
|
3971
|
+
for (let i = 0; i < vote.choice.length; i++) {
|
|
3972
|
+
for (let j = i + 1; j < vote.choice.length; j++) {
|
|
3973
|
+
const winner = vote.choice[i] - 1;
|
|
3974
|
+
const loser = vote.choice[j] - 1;
|
|
3975
|
+
pairwiseComparisons[winner][loser] += vote.balance;
|
|
3976
|
+
pairwiseComparisons[loser][winner] -= vote.balance;
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
// Calculate Copeland scores
|
|
3981
|
+
const scores = Array(choicesCount).fill(0);
|
|
3982
|
+
for (let i = 0; i < choicesCount; i++) {
|
|
3983
|
+
for (let j = 0; j < choicesCount; j++) {
|
|
3984
|
+
if (i !== j) {
|
|
3985
|
+
if (pairwiseComparisons[i][j] > 0) {
|
|
3986
|
+
scores[i]++;
|
|
3987
|
+
}
|
|
3988
|
+
else if (pairwiseComparisons[i][j] < 0) {
|
|
3989
|
+
scores[j]++;
|
|
3990
|
+
}
|
|
3991
|
+
else {
|
|
3992
|
+
scores[i] += 0.5;
|
|
3993
|
+
scores[j] += 0.5;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
return scores;
|
|
3999
|
+
}
|
|
4000
|
+
// Calculates the Copeland scores for each choice, broken down by strategy
|
|
4001
|
+
getScoresByStrategy() {
|
|
4002
|
+
const validVotes = this.getValidVotes();
|
|
4003
|
+
const choicesCount = this.proposal.choices.length;
|
|
4004
|
+
const strategiesCount = this.strategies.length;
|
|
4005
|
+
const pairwiseComparisons = Array.from({ length: choicesCount }, () => Array.from({ length: choicesCount }, () => Array(strategiesCount).fill(0)));
|
|
4006
|
+
// Calculate pairwise comparisons for each strategy
|
|
4007
|
+
for (const vote of validVotes) {
|
|
4008
|
+
for (let i = 0; i < vote.choice.length; i++) {
|
|
4009
|
+
for (let j = i + 1; j < vote.choice.length; j++) {
|
|
4010
|
+
const winner = vote.choice[i] - 1;
|
|
4011
|
+
const loser = vote.choice[j] - 1;
|
|
4012
|
+
for (let s = 0; s < strategiesCount; s++) {
|
|
4013
|
+
pairwiseComparisons[winner][loser][s] += vote.scores[s];
|
|
4014
|
+
pairwiseComparisons[loser][winner][s] -= vote.scores[s];
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
// Calculate Copeland scores for each strategy
|
|
4020
|
+
const scores = Array.from({ length: choicesCount }, () => Array(strategiesCount).fill(0));
|
|
4021
|
+
for (let i = 0; i < choicesCount; i++) {
|
|
4022
|
+
for (let j = 0; j < choicesCount; j++) {
|
|
4023
|
+
if (i !== j) {
|
|
4024
|
+
for (let s = 0; s < strategiesCount; s++) {
|
|
4025
|
+
if (pairwiseComparisons[i][j][s] > 0) {
|
|
4026
|
+
scores[i][s]++;
|
|
4027
|
+
}
|
|
4028
|
+
else if (pairwiseComparisons[i][j][s] < 0) {
|
|
4029
|
+
scores[j][s]++;
|
|
4030
|
+
}
|
|
4031
|
+
else {
|
|
4032
|
+
scores[i][s] += 0.5;
|
|
4033
|
+
scores[j][s] += 0.5;
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
return scores;
|
|
4040
|
+
}
|
|
4041
|
+
// Calculates the total score (sum of all valid vote balances)
|
|
4042
|
+
getScoresTotal() {
|
|
4043
|
+
return this.getValidVotes().reduce((total, vote) => total + vote.balance, 0);
|
|
4044
|
+
}
|
|
4045
|
+
// Returns a string representation of the selected choices
|
|
4046
|
+
getChoiceString() {
|
|
4047
|
+
return this.selected
|
|
4048
|
+
.map((choice) => this.proposal.choices[choice - 1])
|
|
4049
|
+
.join(', ');
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
|
|
3935
4053
|
function percentageOfTotal(i, values, total) {
|
|
3936
4054
|
const reducedTotal = total.reduce((a, b) => a + b, 0);
|
|
3937
4055
|
const percent = (values[i] / reducedTotal) * 100;
|
|
@@ -4004,6 +4122,7 @@ var voting = {
|
|
|
4004
4122
|
approval: ApprovalVoting,
|
|
4005
4123
|
quadratic: QuadraticVoting,
|
|
4006
4124
|
'ranked-choice': RankedChoiceVoting,
|
|
4125
|
+
copeland: CopelandVoting,
|
|
4007
4126
|
weighted: WeightedVoting,
|
|
4008
4127
|
basic: SingleChoiceVoting
|
|
4009
4128
|
};
|