From bb47e8d53a21c1622c9470ad82b0ac07c65be491 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Sat, 20 Jul 2024 12:29:36 +0900 Subject: [PATCH 01/30] feat: add reason when voting --- .changeset/breezy-owls-clean.md | 5 + .../src/components/IndicatorVotingPower.vue | 47 +++--- apps/ui/src/components/MessageVotingPower.vue | 47 ++++++ apps/ui/src/components/Modal/Vote.vue | 143 ++++++++++++++++++ apps/ui/src/components/Modal/VotingPower.vue | 37 ++--- .../src/components/ProposalVoteApproval.vue | 8 +- apps/ui/src/components/ProposalVoteBasic.vue | 4 - .../components/ProposalVoteRankedChoice.vue | 8 +- .../components/ProposalVoteSingleChoice.vue | 4 +- .../src/components/ProposalVoteWeighted.vue | 8 +- apps/ui/src/components/ProposalsListItem.vue | 34 +++-- apps/ui/src/components/Ui/Button.vue | 19 ++- apps/ui/src/composables/useActions.ts | 5 +- apps/ui/src/composables/useVotingPower.ts | 52 +++++++ apps/ui/src/helpers/utils.ts | 31 +++- apps/ui/src/networks/evm/actions.ts | 8 +- apps/ui/src/networks/offchain/actions.ts | 6 +- apps/ui/src/networks/starknet/actions.ts | 9 +- apps/ui/src/networks/types.ts | 3 +- apps/ui/src/stores/votingPowers.ts | 92 +++++++++++ apps/ui/src/views/Editor.vue | 66 +++----- apps/ui/src/views/Proposal.vue | 129 +++++++--------- apps/ui/src/views/Space/Proposals.vue | 59 +++----- .../clients/offchain/ethereum-sig/index.ts | 2 +- packages/sx.js/src/clients/offchain/types.ts | 1 + .../clients/starknet/ethereum-sig/index.ts | 4 +- .../clients/starknet/starknet-sig/index.ts | 4 +- .../src/clients/starknet/starknet-tx/index.ts | 2 +- packages/sx.js/src/types/index.ts | 1 + .../__snapshots__/index.test.ts.snap | 1 + .../starknet/ethereum-sig/index.test.ts | 1 + .../starknet/ethereum-tx/index.test.ts | 3 +- .../__snapshots__/index.test.ts.snap | 10 +- .../starknet/starknet-sig/index.test.ts | 1 + 34 files changed, 587 insertions(+), 267 deletions(-) create mode 100644 .changeset/breezy-owls-clean.md create mode 100644 apps/ui/src/components/MessageVotingPower.vue create mode 100644 apps/ui/src/components/Modal/Vote.vue create mode 100644 apps/ui/src/composables/useVotingPower.ts create mode 100644 apps/ui/src/stores/votingPowers.ts diff --git a/.changeset/breezy-owls-clean.md b/.changeset/breezy-owls-clean.md new file mode 100644 index 000000000..6b9d144e8 --- /dev/null +++ b/.changeset/breezy-owls-clean.md @@ -0,0 +1,5 @@ +--- +"@snapshot-labs/sx": patch +--- + +support submitting a reason when voting diff --git a/apps/ui/src/components/IndicatorVotingPower.vue b/apps/ui/src/components/IndicatorVotingPower.vue index 47d9fed84..01edd8b3e 100644 --- a/apps/ui/src/components/IndicatorVotingPower.vue +++ b/apps/ui/src/components/IndicatorVotingPower.vue @@ -1,38 +1,33 @@ + + diff --git a/apps/ui/src/components/Modal/Vote.vue b/apps/ui/src/components/Modal/Vote.vue new file mode 100644 index 000000000..2f293a31a --- /dev/null +++ b/apps/ui/src/components/Modal/Vote.vue @@ -0,0 +1,143 @@ + + + diff --git a/apps/ui/src/components/Modal/VotingPower.vue b/apps/ui/src/components/Modal/VotingPower.vue index 95f616914..f220375a5 100644 --- a/apps/ui/src/components/Modal/VotingPower.vue +++ b/apps/ui/src/components/Modal/VotingPower.vue @@ -1,4 +1,5 @@ -
-
- There was an error fetching your voting power. - - Retry - -
+
+
@@ -57,12 +60,12 @@ const error = computed(() => props.votingPowerStatus === 'error'); />
diff --git a/apps/ui/src/components/ProposalVoteApproval.vue b/apps/ui/src/components/ProposalVoteApproval.vue index c8bb54e3b..8ef901f34 100644 --- a/apps/ui/src/components/ProposalVoteApproval.vue +++ b/apps/ui/src/components/ProposalVoteApproval.vue @@ -2,7 +2,6 @@ import { Choice, Proposal } from '@/types'; defineProps<{ - sendingType: Choice | null; proposal: Proposal; }>(); @@ -37,12 +36,7 @@ function toggleSelectedChoice(choice: number) {
- + Vote
diff --git a/apps/ui/src/components/ProposalVoteBasic.vue b/apps/ui/src/components/ProposalVoteBasic.vue index fece1d9e1..ea89d5a07 100644 --- a/apps/ui/src/components/ProposalVoteBasic.vue +++ b/apps/ui/src/components/ProposalVoteBasic.vue @@ -3,7 +3,6 @@ import { Choice } from '@/types'; withDefaults( defineProps<{ - sendingType: Choice | null; size?: number; }>(), { size: 48 } @@ -20,7 +19,6 @@ const emit = defineEmits<{ @@ -30,7 +28,6 @@ const emit = defineEmits<{ @@ -40,7 +37,6 @@ const emit = defineEmits<{ diff --git a/apps/ui/src/components/ProposalVoteRankedChoice.vue b/apps/ui/src/components/ProposalVoteRankedChoice.vue index cd86aa2fb..716514990 100644 --- a/apps/ui/src/components/ProposalVoteRankedChoice.vue +++ b/apps/ui/src/components/ProposalVoteRankedChoice.vue @@ -3,7 +3,6 @@ import Draggable from 'vuedraggable'; import { Choice, Proposal } from '@/types'; const props = defineProps<{ - sendingType: Choice | null; proposal: Proposal; }>(); @@ -38,12 +37,7 @@ const selectedChoices = ref(props.proposal.choices.map((_, i) => i + 1 - + Vote
diff --git a/apps/ui/src/components/ProposalVoteSingleChoice.vue b/apps/ui/src/components/ProposalVoteSingleChoice.vue index dc8f211ff..8ed122d7a 100644 --- a/apps/ui/src/components/ProposalVoteSingleChoice.vue +++ b/apps/ui/src/components/ProposalVoteSingleChoice.vue @@ -1,8 +1,7 @@ - + @@ -134,6 +129,13 @@ async function handleVoteClick(choice: Choice) { :proposal="proposal" @close="modalOpenTimeline = false" /> + diff --git a/apps/ui/src/components/Ui/Button.vue b/apps/ui/src/components/Ui/Button.vue index 19d97d0d0..fe1451972 100644 --- a/apps/ui/src/components/Ui/Button.vue +++ b/apps/ui/src/components/Ui/Button.vue @@ -27,7 +27,7 @@ withDefaults( - diff --git a/apps/ui/src/composables/useActions.ts b/apps/ui/src/composables/useActions.ts index acf3ba2ae..f2b03647a 100644 --- a/apps/ui/src/composables/useActions.ts +++ b/apps/ui/src/composables/useActions.ts @@ -210,7 +210,7 @@ export function useActions() { await wrapPromise(space.network, network.actions.setMetadata(auth.web3, space, metadata)); } - async function vote(proposal: Proposal, choice: Choice) { + async function vote(proposal: Proposal, choice: Choice, reason: string) { if (!web3.value.account) return await forceLogin(); const network = getNetwork(proposal.network); @@ -222,7 +222,8 @@ export function useActions() { web3.value.type as Connector, web3.value.account, proposal, - choice + choice, + reason ) ); diff --git a/apps/ui/src/composables/useVotingPower.ts b/apps/ui/src/composables/useVotingPower.ts new file mode 100644 index 000000000..7af8ac67f --- /dev/null +++ b/apps/ui/src/composables/useVotingPower.ts @@ -0,0 +1,52 @@ +import type { Space, Proposal } from '@/types'; + +export function useVotingPower() { + const { web3 } = useWeb3(); + const votingPowersStore = useVotingPowersStore(); + + const item = ref(); + + const space = computed(() => + item.value && 'space' in item.value ? (item.value?.space as Space) : item.value + ); + + const snapshot = computed(() => + item.value && 'snapshot' in item.value ? item.value.snapshot : undefined + ); + + const votingPower = computed( + () => space.value && votingPowersStore.get(space.value, snapshot.value) + ); + + const hasVoteVp = computed( + () => (votingPower.value && votingPower.value.totalVotingPower > 0n) || false + ); + + const hasProposeVp = computed( + () => + (votingPower.value && + space.value && + votingPower.value.totalVotingPower >= BigInt(space.value.proposal_threshold)) || + false + ); + + function reset() { + votingPowersStore.reset(); + } + + function fetch(spaceOrProposal: Space | Proposal) { + if (!web3.value.account) return; + + item.value = spaceOrProposal; + votingPowersStore.fetch(item.value, web3.value.account, snapshot.value); + } + + watch( + () => web3.value.account, + account => { + if (!account) reset(); + } + ); + + return { votingPower, hasVoteVp, hasProposeVp, fetch, reset }; +} diff --git a/apps/ui/src/helpers/utils.ts b/apps/ui/src/helpers/utils.ts index ab170c655..b0ca1bd95 100644 --- a/apps/ui/src/helpers/utils.ts +++ b/apps/ui/src/helpers/utils.ts @@ -11,7 +11,7 @@ import { upload as pin } from '@snapshot-labs/pineapple'; import networks from '@/helpers/networks.json'; import pkg from '@/../package.json'; import type { Web3Provider } from '@ethersproject/providers'; -import type { Proposal, SpaceMetadata } from '@/types'; +import type { Choice, Proposal, SpaceMetadata } from '@/types'; import { MAX_SYMBOL_LENGTH } from './constants'; import ICX from '~icons/c/x'; import ICDiscord from '~icons/c/discord'; @@ -436,18 +436,24 @@ export function getChoiceWeight(selectedChoices: Record, index: return isNaN(percent) ? 0 : percent; } -export function getChoiceText( - availableChoices: string[], - choice: number | number[] | Record -) { +export function getChoiceText(availableChoices: string[], choice: Choice) { + if (typeof choice === 'string') { + return ['for', 'against', 'abstain'].includes(choice) + ? choice.charAt(0).toUpperCase() + choice.slice(1) + : 'Invalid choice'; + } + if (typeof choice === 'number') { - return availableChoices[choice - 1]; + return availableChoices[choice - 1] || 'Invalid choice'; } if (Array.isArray(choice)) { + if (!choice.length) return 'Blank vote'; return choice.map(index => availableChoices[index - 1]).join(', '); } + if (!Object.keys(choice).length) return 'Blank vote'; + const total = Object.values(choice).reduce((acc, weight) => acc + weight, 0); return Object.entries(choice) @@ -484,3 +490,16 @@ export function getSocialNetworksLink(data: any) { }) .filter(social => social.href); } + +export function getFormattedVotingPower(votingPower?: { + totalVotingPower: bigint; + decimals: number; + symbol: string; +}) { + if (!votingPower) return; + + const { totalVotingPower, decimals, symbol } = votingPower; + const value = _vp(Number(totalVotingPower) / 10 ** decimals); + + return symbol ? `${value} ${symbol}` : value; +} diff --git a/apps/ui/src/networks/evm/actions.ts b/apps/ui/src/networks/evm/actions.ts index 59ba5dde8..d3f98db46 100644 --- a/apps/ui/src/networks/evm/actions.ts +++ b/apps/ui/src/networks/evm/actions.ts @@ -347,7 +347,8 @@ export function createActions( connectorType: Connector, account: string, proposal: Proposal, - choice: Choice + choice: Choice, + reason: string ) => { await verifyNetwork(web3, chainId); @@ -376,13 +377,16 @@ export function createActions( }) ); + let pinned: { cid: string; provider: string } | null = null; + if (reason) pinned = await helpers.pin({ reason }); + const data = { space: proposal.space.id, authenticator, strategies: strategiesWithMetadata, proposal: proposal.proposal_id as number, choice: getSdkChoice(choice), - metadataUri: '', + metadataUri: pinned ? `ipfs://${pinned.cid}` : '', chainId }; diff --git a/apps/ui/src/networks/offchain/actions.ts b/apps/ui/src/networks/offchain/actions.ts index c51a0553c..d2778402e 100644 --- a/apps/ui/src/networks/offchain/actions.ts +++ b/apps/ui/src/networks/offchain/actions.ts @@ -139,7 +139,8 @@ export function createActions( connectorType: Connector, account: string, proposal: Proposal, - choice: Choice + choice: Choice, + reason: string ): Promise { const data = { space: proposal.space.id, @@ -149,7 +150,8 @@ export function createActions( authenticator: '', strategies: [], metadataUri: '', - privacy: proposal.privacy + privacy: proposal.privacy, + reason }; return client.vote({ diff --git a/apps/ui/src/networks/starknet/actions.ts b/apps/ui/src/networks/starknet/actions.ts index 9d58b400d..9530c2ed8 100644 --- a/apps/ui/src/networks/starknet/actions.ts +++ b/apps/ui/src/networks/starknet/actions.ts @@ -334,7 +334,8 @@ export function createActions( connectorType: Connector, account: string, proposal: Proposal, - choice: Choice + choice: Choice, + reason: string ) => { const isContract = await getIsContract(connectorType, account); @@ -365,12 +366,16 @@ export function createActions( }) ); + let pinned: { cid: string; provider: string } | null = null; + pinned = await helpers.pin({ reason }); + const data = { space: proposal.space.id, authenticator, strategies: strategiesWithMetadata, proposal: proposal.proposal_id as number, - choice: getSdkChoice(choice) + choice: getSdkChoice(choice), + metadataUri: pinned ? `ipfs://${pinned.cid}` : '' }; if (relayerType === 'starknet') { diff --git a/apps/ui/src/networks/types.ts b/apps/ui/src/networks/types.ts index f4abe9186..30a5229cf 100644 --- a/apps/ui/src/networks/types.ts +++ b/apps/ui/src/networks/types.ts @@ -127,7 +127,8 @@ export type ReadOnlyNetworkActions = { connectorType: Connector, account: string, proposal: Proposal, - choice: Choice + choice: Choice, + metadataCid?: string ): Promise; followSpace(web3: Web3Provider | Wallet, networkId: NetworkID, spaceId: string, from?: string); unfollowSpace(web3: Web3Provider | Wallet, networkId: NetworkID, spaceId: string, from?: string); diff --git a/apps/ui/src/stores/votingPowers.ts b/apps/ui/src/stores/votingPowers.ts new file mode 100644 index 000000000..d27ea72de --- /dev/null +++ b/apps/ui/src/stores/votingPowers.ts @@ -0,0 +1,92 @@ +import { defineStore } from 'pinia'; +import { utils } from '@snapshot-labs/sx'; +import { getNetwork, supportsNullCurrent } from '@/networks'; +import type { Proposal, Space } from '@/types'; +import type { VotingPower, VotingPowerStatus } from '@/networks/types'; + +type VotingPowerItem = { + votingPowers: VotingPower[]; + totalVotingPower: bigint; + status: VotingPowerStatus; + symbol: string; + decimals: number; + error: utils.errors.VotingPowerDetailsError | null; +}; + +export const useVotingPowersStore = defineStore('votingPowers', () => { + const { getCurrent } = useMetaStore(); + + const votingPowers = reactive>(new Map()); + + function getIndex(space: Space, block: string | number) { + return `${space.id}:${block}`; + } + + function get(space: Space, block: string | number = 'latest') { + return votingPowers.get(getIndex(space, block)); + } + + async function fetch(item: Space | Proposal, account: string, block: string | number = 'latest') { + const space: Space = 'space' in item ? (item.space as Space) : item; + + const existingVotingPower = get(space, block); + if (existingVotingPower && existingVotingPower.status === 'success') return; + + const network = getNetwork(item.network); + + let vpItem: VotingPowerItem = { + status: 'loading', + votingPowers: [], + totalVotingPower: 0n, + decimals: 18, + symbol: space.voting_power_symbol, + error: null + }; + + if (existingVotingPower) { + existingVotingPower.status = 'loading'; + votingPowers.set(getIndex(space, block), existingVotingPower); + } + + try { + const vp = await network.actions.getVotingPower( + space.id, + item.strategies, + item.strategies_params, + space.strategies_parsed_metadata, + account, + { + at: supportsNullCurrent(item.network) ? null : getCurrent(item.network) || 0, + chainId: space.snapshot_chain_id + } + ); + + vpItem = { + ...vpItem, + votingPowers: vp, + totalVotingPower: vp.reduce((acc, b) => acc + b.value, 0n), + status: 'success', + decimals: Math.max(...vp.map(votingPower => votingPower.decimals), 0) + }; + } catch (e: unknown) { + if (e instanceof utils.errors.VotingPowerDetailsError) { + vpItem.error = e; + } else { + console.warn('Failed to load voting power', e); + } + + vpItem.status = 'error'; + } + votingPowers.set(getIndex(space, block), vpItem); + } + + function reset() { + votingPowers.clear(); + } + + return { + get, + fetch, + reset + }; +}); diff --git a/apps/ui/src/views/Editor.vue b/apps/ui/src/views/Editor.vue index 6dcd5c7c8..a583ccd33 100644 --- a/apps/ui/src/views/Editor.vue +++ b/apps/ui/src/views/Editor.vue @@ -1,5 +1,5 @@ -
+
Date: Mon, 5 Aug 2024 10:36:06 +0900 Subject: [PATCH 22/30] fix(ui): improve error message --- apps/ui/src/components/Modal/Vote.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/ui/src/components/Modal/Vote.vue b/apps/ui/src/components/Modal/Vote.vue index c0826558c..c98e6a5aa 100644 --- a/apps/ui/src/components/Modal/Vote.vue +++ b/apps/ui/src/components/Modal/Vote.vue @@ -112,13 +112,16 @@ watchEffect(async () => { />
Choice
-
+
- No choice selected +
+ + No choice selected +
Voting power
From 5287a1ec5826c503f5cc4aa299a66c1ca9463863 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:19:42 +0900 Subject: [PATCH 23/30] fix: fix variable not reactive --- apps/ui/src/composables/useVotingPower.ts | 7 ++++++- apps/ui/src/stores/votingPowers.ts | 19 ++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/ui/src/composables/useVotingPower.ts b/apps/ui/src/composables/useVotingPower.ts index 6371081fc..35098a8ec 100644 --- a/apps/ui/src/composables/useVotingPower.ts +++ b/apps/ui/src/composables/useVotingPower.ts @@ -1,4 +1,5 @@ import { supportsNullCurrent } from '@/networks'; +import { getIndex as getVotingPowerIndex } from '@/stores/votingPowers'; import { Proposal, Space } from '@/types'; export function useVotingPower() { @@ -28,7 +29,11 @@ export function useVotingPower() { }); const votingPower = computed( - () => space.value && votingPowersStore.get(space.value, block.value) + () => + space.value && + votingPowersStore.votingPowers.get( + getVotingPowerIndex(space.value, block.value) + ) ); const hasVoteVp = computed( diff --git a/apps/ui/src/stores/votingPowers.ts b/apps/ui/src/stores/votingPowers.ts index 6100ca469..0d84796da 100644 --- a/apps/ui/src/stores/votingPowers.ts +++ b/apps/ui/src/stores/votingPowers.ts @@ -16,20 +16,13 @@ export type VotingPowerItem = { error: utils.errors.VotingPowerDetailsError | null; }; +export function getIndex(space: SpaceDetails, block: number | null): string { + return `${space.id}:${block ?? LATEST_BLOCK_NAME}`; +} + export const useVotingPowersStore = defineStore('votingPowers', () => { const votingPowers = reactive>(new Map()); - function getIndex(space: SpaceDetails, block: number | null): string { - return `${space.id}:${block ?? LATEST_BLOCK_NAME}`; - } - - function get( - space: SpaceDetails, - block: number | null - ): VotingPowerItem | undefined { - return votingPowers.get(getIndex(space, block)); - } - async function fetch( item: Space | Proposal, account: string, @@ -37,7 +30,7 @@ export const useVotingPowersStore = defineStore('votingPowers', () => { ) { const space = 'space' in item ? item.space : item; - const existingVotingPower = get(space, block); + const existingVotingPower = votingPowers.get(getIndex(space, block)); if (existingVotingPower && existingVotingPower.status === 'success') return; const network = getNetwork(item.network); @@ -93,7 +86,7 @@ export const useVotingPowersStore = defineStore('votingPowers', () => { } return { - get, + votingPowers, fetch, reset }; From 4c6d306da5532e75fd71da9db6e2c68c5ffea499 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:35:30 +0900 Subject: [PATCH 24/30] chore: update tests to include metadataUri --- .../ethereum-sig/__snapshots__/index.test.ts.snap | 13 ++++++++----- .../clients/starknet/ethereum-sig/index.test.ts | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/sx.js/test/unit/clients/starknet/ethereum-sig/__snapshots__/index.test.ts.snap b/packages/sx.js/test/unit/clients/starknet/ethereum-sig/__snapshots__/index.test.ts.snap index e61f6fac9..34f8b7002 100644 --- a/packages/sx.js/test/unit/clients/starknet/ethereum-sig/__snapshots__/index.test.ts.snap +++ b/packages/sx.js/test/unit/clients/starknet/ethereum-sig/__snapshots__/index.test.ts.snap @@ -100,7 +100,7 @@ exports[`EthereumSig > should create vote envelope 1`] = ` "data": { "authenticator": "0x53d98050a9738da0eac7498d909ea03f6eb03d07fb95877b54ff8acf7712276", "choice": 1, - "metadataUri": "", + "metadataUri": "ipfs://QmNrm6xKuib1THtWkiN5CKtBEerQCDpUtmgDqiaU2xDmca", "proposal": 1, "space": "0x06330d3e48f59f5411c201ee2e9e9ccdc738fb3bb192b0e77e4eda26fa1a22f8", "strategies": [ @@ -116,7 +116,10 @@ exports[`EthereumSig > should create vote envelope 1`] = ` "authenticator": "0x53d98050a9738da0eac7498d909ea03f6eb03d07fb95877b54ff8acf7712276", "chainId": "0x534e5f5345504f4c4941", "choice": "0x1", - "metadataUri": [], + "metadataUri": [ + "0x697066733a2f2f516d4e726d36784b75696231544874576b694e35434b7442", + "0x4565725143447055746d6744716961553278446d6361", + ], "proposalId": "0x1", "space": "0x06330d3e48f59f5411c201ee2e9e9ccdc738fb3bb192b0e77e4eda26fa1a22f8", "userVotingStrategies": [], @@ -124,9 +127,9 @@ exports[`EthereumSig > should create vote envelope 1`] = ` }, "primaryType": "Vote", "signature": [ - "0x2986292e9bf23c0f9f738fd32556f8096631c883dec6c4d771d2ce65d69649c7", - "0x2b068bd532570c93e54ac22b0c1e8683229e26c6677c0a68bc677da1a05668ad", - "0x1b", + "0x6e565e214090a3845caca4290e4cad24186c45c945faeeb148649139228f6057", + "0x6197a3f635219831616276b977718d56e666ed4d2d74b7ba82d8d7c39bd7d29b", + "0x1c", ], }, } diff --git a/packages/sx.js/test/unit/clients/starknet/ethereum-sig/index.test.ts b/packages/sx.js/test/unit/clients/starknet/ethereum-sig/index.test.ts index ce55c3ee7..a9549d91a 100644 --- a/packages/sx.js/test/unit/clients/starknet/ethereum-sig/index.test.ts +++ b/packages/sx.js/test/unit/clients/starknet/ethereum-sig/index.test.ts @@ -85,7 +85,7 @@ describe('EthereumSig', () => { index: 0 } ], - metadataUri: '', + metadataUri: 'ipfs://QmNrm6xKuib1THtWkiN5CKtBEerQCDpUtmgDqiaU2xDmca', proposal: 1, choice: 1 } From 0292d23ddaf85aaaa476acc6aa96275e8726a3c6 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:36:42 +0900 Subject: [PATCH 25/30] fix: fix metadataUri being ignored from voteHash --- packages/sx.js/src/clients/starknet/ethereum-tx/index.ts | 2 +- .../sx.js/test/unit/clients/starknet/ethereum-tx/index.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sx.js/src/clients/starknet/ethereum-tx/index.ts b/packages/sx.js/src/clients/starknet/ethereum-tx/index.ts index 27264258f..85fa4c177 100644 --- a/packages/sx.js/src/clients/starknet/ethereum-tx/index.ts +++ b/packages/sx.js/src/clients/starknet/ethereum-tx/index.ts @@ -305,7 +305,7 @@ export class EthereumTx { data.proposal, getChoiceEnum(data.choice), userVotingStrategies, - shortString.splitLongString('') // metadataUri + shortString.splitLongString(data.metadataUri) ]); return `0x${poseidonHashMany(compiled.map(v => BigInt(v))).toString(16)}`; diff --git a/packages/sx.js/test/unit/clients/starknet/ethereum-tx/index.test.ts b/packages/sx.js/test/unit/clients/starknet/ethereum-tx/index.test.ts index a2c1b89de..d0b67ef96 100644 --- a/packages/sx.js/test/unit/clients/starknet/ethereum-tx/index.test.ts +++ b/packages/sx.js/test/unit/clients/starknet/ethereum-tx/index.test.ts @@ -62,7 +62,7 @@ describe('EthereumTx', () => { const result = await ethereumTx.getVoteHash(wallet, data); expect(result).toEqual( - '0x4affc1f0108aecdd06a9c2b8402ffe11ba37e27dfcbb98964ff68f83899367b' + '0x15c3ec5ebb1e82803db2d695eb12a902d5bb0d52c63e1536015e6d3debe70' ); }); From af1e616f4114d3f734efcdfe5c394962e3bcb3e0 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:08:48 +0900 Subject: [PATCH 26/30] fix: check that voting power is successful, in case threshold is 0 --- apps/ui/src/composables/useVotingPower.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/ui/src/composables/useVotingPower.ts b/apps/ui/src/composables/useVotingPower.ts index 35098a8ec..dd45088e0 100644 --- a/apps/ui/src/composables/useVotingPower.ts +++ b/apps/ui/src/composables/useVotingPower.ts @@ -45,6 +45,7 @@ export function useVotingPower() { () => (votingPower.value && space.value && + votingPower.value.status === 'success' && votingPower.value.totalVotingPower >= BigInt(space.value.proposal_threshold)) || false From a2f8d674f1413a7fcef25a9dbfd2e876d90dc456 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:07:20 +0900 Subject: [PATCH 27/30] refactor: save propose and vote potential in vp store --- apps/ui/src/components/MessageVotingPower.vue | 41 ++++++++----------- apps/ui/src/components/Modal/Vote.vue | 8 ++-- apps/ui/src/composables/useVotingPower.ts | 17 +------- apps/ui/src/stores/votingPowers.ts | 15 ++++++- apps/ui/src/views/Editor.vue | 8 ++-- 5 files changed, 41 insertions(+), 48 deletions(-) diff --git a/apps/ui/src/components/MessageVotingPower.vue b/apps/ui/src/components/MessageVotingPower.vue index 838e1177a..26937e85e 100644 --- a/apps/ui/src/components/MessageVotingPower.vue +++ b/apps/ui/src/components/MessageVotingPower.vue @@ -1,29 +1,14 @@ diff --git a/apps/ui/src/components/Modal/Vote.vue b/apps/ui/src/components/Modal/Vote.vue index c98e6a5aa..9555b9afd 100644 --- a/apps/ui/src/components/Modal/Vote.vue +++ b/apps/ui/src/components/Modal/Vote.vue @@ -27,8 +27,7 @@ const { web3 } = useWeb3(); const { votingPower, fetch: fetchVotingPower, - reset: resetVotingPower, - hasVoteVp + reset: resetVotingPower } = useVotingPower(); const loading = ref(false); @@ -56,7 +55,7 @@ const canSubmit = computed( formValidated && !!props.choice && Object.keys(formErrors.value).length === 0 && - hasVoteVp.value + votingPower.value?.canVote ); async function handleSubmit() { @@ -102,12 +101,11 @@ watchEffect(async () => { -
diff --git a/apps/ui/src/composables/useVotingPower.ts b/apps/ui/src/composables/useVotingPower.ts index dd45088e0..52a508dc2 100644 --- a/apps/ui/src/composables/useVotingPower.ts +++ b/apps/ui/src/composables/useVotingPower.ts @@ -36,21 +36,6 @@ export function useVotingPower() { ) ); - const hasVoteVp = computed( - () => - (votingPower.value && votingPower.value.totalVotingPower > 0n) || false - ); - - const hasProposeVp = computed( - () => - (votingPower.value && - space.value && - votingPower.value.status === 'success' && - votingPower.value.totalVotingPower >= - BigInt(space.value.proposal_threshold)) || - false - ); - function latestBlock(space: Space) { return supportsNullCurrent(space.network) ? null @@ -79,5 +64,5 @@ export function useVotingPower() { } ); - return { votingPower, hasVoteVp, hasProposeVp, fetch, reset }; + return { votingPower, fetch, reset }; } diff --git a/apps/ui/src/stores/votingPowers.ts b/apps/ui/src/stores/votingPowers.ts index 0d84796da..f908708f6 100644 --- a/apps/ui/src/stores/votingPowers.ts +++ b/apps/ui/src/stores/votingPowers.ts @@ -14,6 +14,8 @@ export type VotingPowerItem = { symbol: string; decimals: number; error: utils.errors.VotingPowerDetailsError | null; + canPropose: boolean; + canVote: boolean; }; export function getIndex(space: SpaceDetails, block: number | null): string { @@ -41,7 +43,9 @@ export const useVotingPowersStore = defineStore('votingPowers', () => { totalVotingPower: 0n, decimals: 18, symbol: space.voting_power_symbol, - error: null + error: null, + canPropose: false, + canVote: false }; if (existingVotingPower) { @@ -69,6 +73,15 @@ export const useVotingPowersStore = defineStore('votingPowers', () => { status: 'success', decimals: Math.max(...vp.map(votingPower => votingPower.decimals), 0) }; + + if ('proposal_threshold' in space) { + vpItem.canPropose = + vpItem.totalVotingPower >= BigInt(space.proposal_threshold); + } else { + vpItem.canVote = vpItem.totalVotingPower > 0n; + } + + console.log(vpItem); } catch (e: unknown) { if (e instanceof utils.errors.VotingPowerDetailsError) { vpItem.error = e; diff --git a/apps/ui/src/views/Editor.vue b/apps/ui/src/views/Editor.vue index 99b46bcfd..e450a70b1 100644 --- a/apps/ui/src/views/Editor.vue +++ b/apps/ui/src/views/Editor.vue @@ -63,7 +63,7 @@ const { } = useWalletConnectTransaction(); const spacesStore = useSpacesStore(); const proposalsStore = useProposalsStore(); -const { votingPower, fetch: fetchVotingPower, hasProposeVp } = useVotingPower(); +const { votingPower, fetch: fetchVotingPower } = useVotingPower(); const modalOpen = ref(false); const previewEnabled = ref(false); @@ -225,7 +225,9 @@ const formErrors = computed(() => { ); }); const canSubmit = computed(() => { - return hasProposeVp.value && Object.keys(formErrors.value).length === 0; + return ( + votingPower.value?.canPropose && Object.keys(formErrors.value).length === 0 + ); }); async function handleProposeClick() { @@ -420,7 +422,7 @@ export default defineComponent({ v-if="votingPower && space" class="mb-4" :voting-power="votingPower" - :min-proposal-threshold="BigInt(space.proposal_threshold)" + action="propose" @fetch-voting-power="handleFetchVotingPower" /> From 4362cee232dfa7f8c0da3d026251c57fad90d861 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:11:39 +0900 Subject: [PATCH 28/30] chore: remove console output --- apps/ui/src/stores/votingPowers.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/ui/src/stores/votingPowers.ts b/apps/ui/src/stores/votingPowers.ts index f908708f6..6360cfc10 100644 --- a/apps/ui/src/stores/votingPowers.ts +++ b/apps/ui/src/stores/votingPowers.ts @@ -80,8 +80,6 @@ export const useVotingPowersStore = defineStore('votingPowers', () => { } else { vpItem.canVote = vpItem.totalVotingPower > 0n; } - - console.log(vpItem); } catch (e: unknown) { if (e instanceof utils.errors.VotingPowerDetailsError) { vpItem.error = e; From 583004b04db91873c4ba623a0887326f6146d5e4 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:14:40 +0900 Subject: [PATCH 29/30] fix: remove unecessary template wrapper --- apps/ui/src/components/MessageVotingPower.vue | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/apps/ui/src/components/MessageVotingPower.vue b/apps/ui/src/components/MessageVotingPower.vue index 26937e85e..0193b979c 100644 --- a/apps/ui/src/components/MessageVotingPower.vue +++ b/apps/ui/src/components/MessageVotingPower.vue @@ -28,20 +28,18 @@ defineEmits<{ Retry
- + + You do not have enough voting power to vote. + + + You do not have enough voting power to create proposal in this space. + From a19627f85aa14f1c4cab34913a1b9066d106d070 Mon Sep 17 00:00:00 2001 From: Wan <495709+wa0x6e@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:43:25 +0400 Subject: [PATCH 30/30] Update apps/ui/src/views/Space/Proposals.vue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Wiktor Tkaczyński --- apps/ui/src/views/Space/Proposals.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ui/src/views/Space/Proposals.vue b/apps/ui/src/views/Space/Proposals.vue index 43ea741ea..e10db8769 100644 --- a/apps/ui/src/views/Space/Proposals.vue +++ b/apps/ui/src/views/Space/Proposals.vue @@ -48,7 +48,7 @@ watch( watch( [props.space, () => web3.value.account, () => web3.value.authLoading], - ([toSpace, toAccount, toAuthLoading], [, fromAccount, , ,]) => { + ([toSpace, toAccount, toAuthLoading], [, fromAccount]) => { if (fromAccount && toAccount && fromAccount !== toAccount) { resetVotingPower(); }