@snapshot-labs/snapshot.js 0.12.50 → 0.12.52
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 +120 -3
- package/dist/snapshot.esm.js +120 -3
- 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/networks.json +4 -4
- package/src/schemas/proposal.json +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
|
@@ -1090,6 +1090,7 @@ var definitions$1 = {
|
|
|
1090
1090
|
"approval",
|
|
1091
1091
|
"ranked-choice",
|
|
1092
1092
|
"quadratic",
|
|
1093
|
+
"copeland",
|
|
1093
1094
|
"weighted",
|
|
1094
1095
|
"custom",
|
|
1095
1096
|
"basic"
|
|
@@ -1814,9 +1815,9 @@ var networks = {
|
|
|
1814
1815
|
},
|
|
1815
1816
|
"10": {
|
|
1816
1817
|
key: "10",
|
|
1817
|
-
name: "
|
|
1818
|
+
name: "OP Mainnet",
|
|
1818
1819
|
chainId: 10,
|
|
1819
|
-
network: "
|
|
1820
|
+
network: "OP Mainnet",
|
|
1820
1821
|
multicall: "0x35A6Cdb2C9AD4a45112df4a04147EB07dFA01aB7",
|
|
1821
1822
|
rpc: [
|
|
1822
1823
|
"https://opt-mainnet.g.alchemy.com/v2/JzmIL4Q3jBj7it2duxLFeuCa9Wobmm7D"
|
|
@@ -1826,7 +1827,7 @@ var networks = {
|
|
|
1826
1827
|
apiUrl: "https://api-optimistic.etherscan.io"
|
|
1827
1828
|
},
|
|
1828
1829
|
start: 657806,
|
|
1829
|
-
logo: "ipfs://
|
|
1830
|
+
logo: "ipfs://bafkreifu2remiqfpsb4hgisbwb3qxedrzpwsea7ik4el45znjcf56xf2ku"
|
|
1830
1831
|
},
|
|
1831
1832
|
"19": {
|
|
1832
1833
|
key: "19",
|
|
@@ -3944,6 +3945,121 @@ class RankedChoiceVoting {
|
|
|
3944
3945
|
}
|
|
3945
3946
|
}
|
|
3946
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
|
+
|
|
3947
4063
|
function percentageOfTotal(i, values, total) {
|
|
3948
4064
|
const reducedTotal = total.reduce((a, b) => a + b, 0);
|
|
3949
4065
|
const percent = (values[i] / reducedTotal) * 100;
|
|
@@ -4016,6 +4132,7 @@ var voting = {
|
|
|
4016
4132
|
approval: ApprovalVoting,
|
|
4017
4133
|
quadratic: QuadraticVoting,
|
|
4018
4134
|
'ranked-choice': RankedChoiceVoting,
|
|
4135
|
+
copeland: CopelandVoting,
|
|
4019
4136
|
weighted: WeightedVoting,
|
|
4020
4137
|
basic: SingleChoiceVoting
|
|
4021
4138
|
};
|
package/dist/snapshot.esm.js
CHANGED
|
@@ -1080,6 +1080,7 @@ var definitions$1 = {
|
|
|
1080
1080
|
"approval",
|
|
1081
1081
|
"ranked-choice",
|
|
1082
1082
|
"quadratic",
|
|
1083
|
+
"copeland",
|
|
1083
1084
|
"weighted",
|
|
1084
1085
|
"custom",
|
|
1085
1086
|
"basic"
|
|
@@ -1804,9 +1805,9 @@ var networks = {
|
|
|
1804
1805
|
},
|
|
1805
1806
|
"10": {
|
|
1806
1807
|
key: "10",
|
|
1807
|
-
name: "
|
|
1808
|
+
name: "OP Mainnet",
|
|
1808
1809
|
chainId: 10,
|
|
1809
|
-
network: "
|
|
1810
|
+
network: "OP Mainnet",
|
|
1810
1811
|
multicall: "0x35A6Cdb2C9AD4a45112df4a04147EB07dFA01aB7",
|
|
1811
1812
|
rpc: [
|
|
1812
1813
|
"https://opt-mainnet.g.alchemy.com/v2/JzmIL4Q3jBj7it2duxLFeuCa9Wobmm7D"
|
|
@@ -1816,7 +1817,7 @@ var networks = {
|
|
|
1816
1817
|
apiUrl: "https://api-optimistic.etherscan.io"
|
|
1817
1818
|
},
|
|
1818
1819
|
start: 657806,
|
|
1819
|
-
logo: "ipfs://
|
|
1820
|
+
logo: "ipfs://bafkreifu2remiqfpsb4hgisbwb3qxedrzpwsea7ik4el45znjcf56xf2ku"
|
|
1820
1821
|
},
|
|
1821
1822
|
"19": {
|
|
1822
1823
|
key: "19",
|
|
@@ -3934,6 +3935,121 @@ class RankedChoiceVoting {
|
|
|
3934
3935
|
}
|
|
3935
3936
|
}
|
|
3936
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
|
+
|
|
3937
4053
|
function percentageOfTotal(i, values, total) {
|
|
3938
4054
|
const reducedTotal = total.reduce((a, b) => a + b, 0);
|
|
3939
4055
|
const percent = (values[i] / reducedTotal) * 100;
|
|
@@ -4006,6 +4122,7 @@ var voting = {
|
|
|
4006
4122
|
approval: ApprovalVoting,
|
|
4007
4123
|
quadratic: QuadraticVoting,
|
|
4008
4124
|
'ranked-choice': RankedChoiceVoting,
|
|
4125
|
+
copeland: CopelandVoting,
|
|
4009
4126
|
weighted: WeightedVoting,
|
|
4010
4127
|
basic: SingleChoiceVoting
|
|
4011
4128
|
};
|