Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add single choice and approval voting type #46

Merged
merged 58 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
ac2907f
fix: show voting buttons for offchain proposals
wa0x6e Feb 12, 2024
bd8494c
refactor: move the network actions to actions.ts
wa0x6e Feb 12, 2024
c6eaf76
Merge branch 'master' into fix-15-add-voting-for-offchain-spaces
wa0x6e Feb 13, 2024
75315e0
feat: add support for basic voting for offchain proposals
wa0x6e Feb 14, 2024
30aa7f1
Merge branch 'master' into fix-15-add-voting-for-offchain-spaces
wa0x6e Feb 14, 2024
9e566b8
fix: fix invalid import type
wa0x6e Feb 14, 2024
f8436f0
fix: use correct sequencer network depending on chainId
wa0x6e Feb 14, 2024
6c2a88f
chore: add tests
wa0x6e Feb 14, 2024
872e294
Update packages/sx.js/src/clients/offchain/ethereum-sig/index.ts
wa0x6e Feb 15, 2024
6cb1830
refactor: use same function order definition as other networks
wa0x6e Feb 15, 2024
ae2bac2
Merge branch 'fix-15-add-voting-for-offchain-spaces' of https://githu…
wa0x6e Feb 15, 2024
4889b20
fix: handle offchain receipt without tx_hash
wa0x6e Feb 15, 2024
a056d19
Merge branch 'master' into fix-15-add-voting-for-offchain-spaces
wa0x6e Feb 15, 2024
737bfe8
fix: fix typing
wa0x6e Feb 15, 2024
5657dd8
fix: throw error on sequencer error response
wa0x6e Feb 15, 2024
ad14c0b
chore: add unit test
wa0x6e Feb 15, 2024
914594e
chore: fix tests
wa0x6e Feb 15, 2024
667b9e1
fix: show voting form only for supported voting type
wa0x6e Feb 15, 2024
4305829
Merge branch 'master' into fix-15-add-voting-for-offchain-spaces
wa0x6e Feb 16, 2024
88511db
Merge branch 'master' into fix-15-add-voting-for-offchain-spaces
wa0x6e Feb 16, 2024
ee3f273
chore: remove cumbersome describe block
wa0x6e Feb 16, 2024
1dea8ca
Merge branch 'fix-15-add-voting-for-offchain-spaces' of https://githu…
wa0x6e Feb 16, 2024
516e24f
fix: switch to correct chain for offchain actions
wa0x6e Feb 16, 2024
86b4011
fix: avoid changing type
wa0x6e Feb 16, 2024
1a188c4
chore: improve test
wa0x6e Feb 16, 2024
30a39d9
fix: revert network enforcer
wa0x6e Feb 16, 2024
e3353b7
fix: remove unused import
wa0x6e Feb 16, 2024
b3684c0
refactor: rename voteType
wa0x6e Feb 16, 2024
3e1a001
refactor: rename type
wa0x6e Feb 16, 2024
a8a7eef
feat: add support for single choice voting
wa0x6e Feb 16, 2024
143210f
fix: remove redundant tooltip
wa0x6e Feb 16, 2024
195ba64
feat: add support for approval voting
wa0x6e Feb 16, 2024
60c344d
chore: fix tests
wa0x6e Feb 16, 2024
4c29beb
refactor: extract list to constant
wa0x6e Feb 16, 2024
0534b42
chore: update changeset
wa0x6e Feb 19, 2024
9d09465
Merge branch 'master' into add-approval-and-single-choice-voting-type
wa0x6e Feb 19, 2024
d0277ae
Merge branch 'master' into add-approval-and-single-choice-voting-type
wa0x6e Feb 19, 2024
f3f2991
fix: UI fix
wa0x6e Feb 19, 2024
06a59ca
Update .changeset/chilly-avocados-teach.md
wa0x6e Feb 20, 2024
1e02f4b
Merge branch 'master' into add-approval-and-single-choice-voting-type
wa0x6e Feb 20, 2024
8372e61
Update apps/ui/src/networks/offchain/helpers.ts
wa0x6e Feb 20, 2024
92e30d3
Merge branch 'master' into add-approval-and-single-choice-voting-type
wa0x6e Feb 20, 2024
73c8b21
Merge branch 'add-approval-and-single-choice-voting-type' of https://…
wa0x6e Feb 20, 2024
9823f94
fix: fix missing parenthesis
wa0x6e Feb 20, 2024
f2b8737
fix: fix botched merged
wa0x6e Feb 20, 2024
793fa8f
Merge branch 'master' into add-approval-and-single-choice-voting-type
wa0x6e Feb 21, 2024
39df70a
refactor: extract vote form into their own component
wa0x6e Feb 21, 2024
1b72d78
refactor: add more type to Choice
wa0x6e Feb 21, 2024
0ec2642
fix(ui): fix glitched focus ring
wa0x6e Feb 21, 2024
aec7f63
fix(ui): remove unused class
wa0x6e Feb 21, 2024
2251433
fix(ui): DRY class
wa0x6e Feb 21, 2024
40cc7b8
fix: fix Choice type
wa0x6e Feb 21, 2024
c54dc65
Update packages/sx.js/src/clients/starknet/starknet-tx/index.ts
wa0x6e Feb 21, 2024
b45b3a0
refactor: rename events name
wa0x6e Feb 21, 2024
04b45ad
refactor: improve event naming
wa0x6e Feb 21, 2024
7933c9f
Merge branch 'master' into add-approval-and-single-choice-voting-type
wa0x6e Feb 21, 2024
afd0e86
fix: tentative type fix
wa0x6e Feb 21, 2024
c419d13
Merge branch 'add-approval-and-single-choice-voting-type' of https://…
wa0x6e Feb 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chilly-avocados-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@snapshot-labs/sx": patch
---

add approval and single choice vote support to OffchainEthereumSig
11 changes: 9 additions & 2 deletions apps/ui/src/components/ProposalVote.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { SUPPORTED_VOTING_TYPES } from '@/helpers/constants';
import { _t } from '@/helpers/utils';
import { getNetwork } from '@/networks';
import { Proposal as ProposalType } from '@/types';
Expand All @@ -21,7 +22,11 @@ const isSupported = computed(() => {
network.helpers.isStrategySupported(strategy)
);

return hasSupportedAuthenticator && hasSupportedStrategies && props.proposal.type === 'basic';
return (
hasSupportedAuthenticator &&
hasSupportedStrategies &&
SUPPORTED_VOTING_TYPES.includes(props.proposal.type)
);
});
</script>

Expand Down Expand Up @@ -50,5 +55,7 @@ const isSupported = computed(() => {
<slot v-else-if="!isSupported" name="unsupported">
Voting for this proposal is not supported
</slot>
<slot v-else></slot>
<div v-else class="py-2">
<slot />
</div>
</template>
49 changes: 49 additions & 0 deletions apps/ui/src/components/ProposalVoteApproval.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script setup lang="ts">
import { Choice, Proposal } from '@/types';

defineProps<{
sendingType: Choice | null;
proposal: Proposal;
}>();

const emit = defineEmits<{
(e: 'vote', value: Choice);
}>();

const selectedChoices = ref<number[]>([]);

function toggleSelectedChoice(choice: number) {
if (selectedChoices.value.includes(choice)) {
selectedChoices.value = selectedChoices.value.filter(c => c !== choice);
} else {
selectedChoices.value = [...selectedChoices.value, choice];
}
}
</script>

<template>
<div class="flex flex-col space-y-3">
<div class="flex flex-col space-y-2">
<UiButton
v-for="(choice, index) in proposal.choices"
:key="index"
class="!h-[48px] text-left w-full flex items-center"
:class="{ 'border-skin-text': selectedChoices.includes(index + 1) }"
@click="toggleSelectedChoice(index + 1)"
>
<div class="grow truncate">
{{ choice }}
</div>
<IH-check v-if="selectedChoices.includes(index + 1)" class="shrink-0" />
</UiButton>
</div>
<UiButton
primary
class="!h-[48px] w-full"
:loading="!!sendingType"
@click="emit('vote', selectedChoices)"
>
Vote
</UiButton>
</div>
</template>
50 changes: 50 additions & 0 deletions apps/ui/src/components/ProposalVoteBasic.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup lang="ts">
import { Choice } from '@/types';

withDefaults(
defineProps<{
sendingType: Choice | null;
size: number;
}>(),
{ size: 48 }
);

const emit = defineEmits<{
(e: 'vote', value: Choice);
}>();
</script>

<template>
<div class="flex space-x-2">
<UiTooltip title="For">
<UiButton
class="!text-skin-success !border-skin-success !px-0"
:class="{ '!w-[48px] !h-[48px]': size === 48, '!w-[40px] !h-[40px]': size === 40 }"
:loading="sendingType === 'for'"
@click="emit('vote', 'for')"
>
<IH-check class="inline-block" />
</UiButton>
</UiTooltip>
<UiTooltip title="Against">
<UiButton
class="!text-skin-danger !border-skin-danger !px-0"
:class="{ '!w-[48px] !h-[48px]': size === 48, '!w-[40px] !h-[40px]': size === 40 }"
:loading="sendingType === 'against'"
@click="emit('vote', 'against')"
>
<IH-x class="inline-block" />
</UiButton>
</UiTooltip>
<UiTooltip title="Abstain">
<UiButton
class="!text-gray-500 !border-gray-500 !px-0"
:class="{ '!w-[48px] !h-[48px]': size === 48, '!w-[40px] !h-[40px]': size === 40 }"
:loading="sendingType === 'abstain'"
@click="emit('vote', 'abstain')"
>
<IH-minus-sm class="inline-block" />
</UiButton>
</UiTooltip>
</div>
</template>
26 changes: 26 additions & 0 deletions apps/ui/src/components/ProposalVoteSingleChoice.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script setup lang="ts">
import { Choice, Proposal } from '@/types';

defineProps<{
sendingType: Choice | null;
proposal: Proposal;
}>();

const emit = defineEmits<{
(e: 'vote', value: number);
}>();
</script>

<template>
<div class="flex flex-col space-y-2">
<UiButton
v-for="(choice, index) in proposal.choices"
:key="index"
class="!h-[48px] text-left w-full truncate"
:loading="sendingType === index + 1"
@click="emit('vote', index + 1)"
>
{{ choice }}
</UiButton>
</div>
</template>
35 changes: 6 additions & 29 deletions apps/ui/src/components/ProposalsListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,35 +80,12 @@ async function handleVoteClick(choice: Choice) {
<ProposalResults v-if="proposal.type === 'basic'" :proposal="proposal" />
<div v-else />
</template>
<div class="flex space-x-2 py-2">
<UiTooltip title="For">
<UiButton
class="w-[40px] !text-skin-success !border-skin-success !h-[40px] !px-0"
:loading="sendingType === 'for'"
@click="handleVoteClick('for')"
>
<IH-check class="inline-block" />
</UiButton>
</UiTooltip>
<UiTooltip title="Against">
<UiButton
class="w-[40px] !text-skin-danger !border-skin-danger !h-[40px] !px-0"
:loading="sendingType === 'against'"
@click="handleVoteClick('against')"
>
<IH-x class="inline-block" />
</UiButton>
</UiTooltip>
<UiTooltip title="Abstain">
<UiButton
class="w-[40px] !text-gray-500 !border-gray-500 !h-[40px] !px-0"
:loading="sendingType === 'abstain'"
@click="handleVoteClick('abstain')"
>
<IH-minus-sm class="inline-block" />
</UiButton>
</UiTooltip>
</div>
<ProposalVoteBasic
v-if="proposal.type === 'basic'"
:sending-type="sendingType"
:size="40"
@vote="handleVoteClick"
/>
</ProposalVote>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions apps/ui/src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export const COINGECKO_BASE_ASSETS = {

export const MAX_SYMBOL_LENGTH = 12;
export const CHOICES = ['For', 'Against', 'Abstain'];
export const SUPPORTED_VOTING_TYPES = ['basic', 'single-choice', 'approval'];
3 changes: 2 additions & 1 deletion apps/ui/src/networks/offchain/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export function createActions(
const data = {
space: proposal.space.id,
proposal: proposal.proposal_id as string,
choice: getSdkChoice(choice),
type: proposal.type,
choice: getSdkChoice(proposal.type, choice),
authenticator: '',
strategies: [],
metadataUri: ''
Expand Down
20 changes: 16 additions & 4 deletions apps/ui/src/networks/offchain/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import { Choice } from '@/types';

export function getSdkChoice(choice: Choice): number {
if (choice === 'for') return 1;
if (choice === 'against') return 2;
return 3;
export function getSdkChoice(type: string, choice: Choice): number | number[] {
if (type === 'basic') {
if (choice === 'for') return 1;
if (choice === 'against') return 2;
return 3;
}

if (type === 'single-choice') {
return choice as number;
}

if (type === 'approval') {
return choice as number[];
}

throw new Error('Vote type not supported');
}
2 changes: 1 addition & 1 deletion apps/ui/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type NetworkID =
| 'matic'
| 'arb1';

export type Choice = 'for' | 'against' | 'abstain';
export type Choice = 'for' | 'against' | 'abstain' | number | number[];

export type SelectedStrategy = {
address: string;
Expand Down
46 changes: 17 additions & 29 deletions apps/ui/src/views/Proposal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,35 +162,23 @@ watchEffect(() => {

<h4 class="block eyebrow mb-2 mt-4 first:mt-1">Cast your vote</h4>
<ProposalVote v-if="proposal" :proposal="proposal">
<div class="flex space-x-2 py-2">
<UiTooltip title="For">
<UiButton
class="!text-skin-success !border-skin-success !w-[48px] !h-[48px] !px-0"
:loading="sendingType === 'for'"
@click="handleVoteClick('for')"
>
<IH-check class="inline-block" />
</UiButton>
</UiTooltip>
<UiTooltip title="Against">
<UiButton
class="!text-skin-danger !border-skin-danger !w-[48px] !h-[48px] !px-0"
:loading="sendingType === 'against'"
@click="handleVoteClick('against')"
>
<IH-x class="inline-block" />
</UiButton>
</UiTooltip>
<UiTooltip title="Abstain">
<UiButton
class="!text-gray-500 !border-gray-500 !w-[48px] !h-[48px] !px-0"
:loading="sendingType === 'abstain'"
@click="handleVoteClick('abstain')"
>
<IH-minus-sm class="inline-block" />
</UiButton>
</UiTooltip>
</div>
<ProposalVoteBasic
v-if="proposal.type === 'basic'"
:sending-type="sendingType"
@vote="handleVoteClick"
/>
<ProposalVoteSingleChoice
v-else-if="proposal.type === 'single-choice'"
:proposal="proposal"
:sending-type="sendingType"
@vote="handleVoteClick"
/>
<ProposalVoteApproval
v-else-if="proposal.type === 'approval'"
:proposal="proposal"
:sending-type="sendingType"
@vote="handleVoteClick"
/>
</ProposalVote>
</template>

Expand Down
9 changes: 7 additions & 2 deletions packages/sx.js/src/clients/offchain/ethereum-sig/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { offchainGoerli } from '../../../offchainNetworks';
import { domain, voteTypes } from './types';
import { domain, SingleChoiceVoteTypes, MultipleChoiceVoteTypes } from './types';
import type { Signer, TypedDataSigner, TypedDataField } from '@ethersproject/abstract-signer';
import type { Vote, Envelope, SignatureData, EIP712VoteMessage, EIP712Message } from '../types';
import type { OffchainNetworkConfig } from '../../../types';
Expand Down Expand Up @@ -93,7 +93,12 @@ export class EthereumSig {
metadata: ''
};

const signatureData = await this.sign(signer, message, voteTypes);
let voteType = SingleChoiceVoteTypes;
if (data.type === 'approval') {
voteType = MultipleChoiceVoteTypes;
}

const signatureData = await this.sign(signer, message, voteType);

return {
signatureData,
Expand Down
15 changes: 14 additions & 1 deletion packages/sx.js/src/clients/offchain/ethereum-sig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const domain = {
version: '0.1.4'
};

export const voteTypes = {
export const SingleChoiceVoteTypes = {
Vote: [
{ name: 'from', type: 'address' },
{ name: 'space', type: 'string' },
Expand All @@ -15,3 +15,16 @@ export const voteTypes = {
{ name: 'metadata', type: 'string' }
]
};

export const MultipleChoiceVoteTypes = {
Vote: [
{ name: 'from', type: 'address' },
{ name: 'space', type: 'string' },
{ name: 'timestamp', type: 'uint64' },
{ name: 'proposal', type: 'string' },
{ name: 'choice', type: 'uint32[]' },
{ name: 'reason', type: 'string' },
{ name: 'app', type: 'string' },
{ name: 'metadata', type: 'string' }
]
};
7 changes: 2 additions & 5 deletions packages/sx.js/src/clients/offchain/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import type { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer';

enum Choice {
Against = 2,
For = 1,
Abstain = 3
}
type Choice = number | number[];

export type SignatureData = {
address: string;
Expand Down Expand Up @@ -43,4 +39,5 @@ export type Vote = {
proposal: string;
choice: Choice;
metadataUri: string;
type: string;
};
3 changes: 2 additions & 1 deletion packages/sx.js/test/integration/offchain/fixtures/vote.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"strategies": [],
"proposal": "0xcc47146e5e0ac781e8976405a8da4468f2a0c4cdf0c7659353d728fafe46f801",
"choice": 1,
"metadataUri": ""
"metadataUri": "",
"type": "basic"
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports[`EthereumSig > should create vote envelope 1`] = `
"proposal": "0xcc47146e5e0ac781e8976405a8da4468f2a0c4cdf0c7659353d728fafe46f801",
"space": "wan-test.eth",
"strategies": [],
"type": "basic",
},
"signatureData": {
"address": "0xf1f09AdC06aAB740AA16004D62Dbd89484d3Be90",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ describe('EthereumSig', () => {
strategies: [],
proposal: '0xcc47146e5e0ac781e8976405a8da4468f2a0c4cdf0c7659353d728fafe46f801',
choice: 1,
metadataUri: ''
metadataUri: '',
type: 'basic'
};

const envelope = await client.vote({
Expand Down
Loading