Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion packages/maskbook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@openzeppelin/contracts": "^3.2.0",
"@popperjs/core": "*",
"@servie/events": "^3.0.0",
"@snapshot-labs/snapshot.js": "github:snapshot-labs/snapshot.js#f32bf0f5b4a27a23b0db3c6fdaced1abab25f55a",
"@dimensiondev/snapshot.js": "0.2.0",
"@types/bn.js": "^4.11.6",
"@types/d3": "5.16.4",
"@types/elliptic": "^6.4.13",
Expand Down
15 changes: 7 additions & 8 deletions packages/maskbook/src/plugins/Snapshot/SNSAdaptor/ResultCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,13 @@ function Content() {

const dataForCsv = useMemo(
() =>
Object.entries(votes).map((vote) => ({
address: vote[0],
choice: vote[1].msg.payload.choice,
balance: vote[1].balance,
timestamp: vote[1].msg.timestamp,
dateUtc: new Date(Number.parseInt(vote[1].msg.timestamp, 10) * 1e3).toUTCString(),
authorIpfsHash: vote[1].authorIpfsHash,
relayerIpfsHash: vote[1].relayerIpfsHash,
votes.map((vote) => ({
address: vote.address,
choice: vote.choiceIndex,
balance: vote.balance,
timestamp: vote.timestamp,
dateUtc: new Date(vote.timestamp * 1e3).toUTCString(),
authorIpfsHash: vote.authorIpfsHash,
})),
[votes],
)
Expand Down
29 changes: 11 additions & 18 deletions packages/maskbook/src/plugins/Snapshot/SNSAdaptor/VotesCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { EthereumBlockie } from '../../../web3/UI/EthereumBlockie'
import { SnapshotContext } from '../context'
import { useRetry } from './hooks/useRetry'
import { useVotes } from './hooks/useVotes'
import type { VoteItem } from '../types'
import { LoadingCard } from './LoadingCard'
import { LoadingFailCard } from './LoadingFailCard'
import { SnapshotCard } from './SnapshotCard'
Expand Down Expand Up @@ -66,47 +65,41 @@ function Content() {
const { payload: votes } = useVotes(identifier)
const classes = useStyles()
const { t } = useI18N()
const voteEntries = Object.entries(votes)

return (
<SnapshotCard
title={
<Badge
max={9999999}
classes={{ anchorOriginTopRightRectangular: classes.anchorTopRight }}
badgeContent={voteEntries.length}
badgeContent={votes.length}
color="primary">
{t('plugin_snapshot_votes_title')}
</Badge>
}>
<List className={classes.list}>
{voteEntries.map((voteEntry: [string, VoteItem]) => {
{votes.map((v) => {
return (
<ListItem className={classes.listItem} key={voteEntry[0]}>
<ListItem className={classes.listItem} key={v.address}>
<Link
className={classNames(classes.link, classes.ellipsisText)}
target="_blank"
rel="noopener"
href={resolveAddressLinkOnExplorer(chainId, voteEntry[0])}>
href={resolveAddressLinkOnExplorer(chainId, v.address)}>
<Box className={classes.avatarWrapper}>
{voteEntry[1].authorAvatar ? (
<Avatar
src={resolveIPFSLink(voteEntry[1].authorAvatar)}
className={classes.avatar}
/>
{v.authorAvatar ? (
<Avatar src={resolveIPFSLink(v.authorAvatar)} className={classes.avatar} />
) : (
<EthereumBlockie address={voteEntry[0]} />
<EthereumBlockie address={v.address} />
)}
</Box>
<Typography>
{voteEntry[1].authorName ?? formatEthereumAddress(voteEntry[0], 4)}
</Typography>
<Typography>{v.authorName ?? formatEthereumAddress(v.address, 4)}</Typography>
</Link>
<Typography className={classes.choice}>{voteEntry[1].choice}</Typography>
<Typography className={classes.choice}>{v.choice}</Typography>
<Typography>
{millify(voteEntry[1].balance, { precision: 2, lowercase: true }) +
{millify(v.balance, { precision: 2, lowercase: true }) +
' ' +
(voteEntry[1].strategySymbol ? voteEntry[1].strategySymbol.toUpperCase() : '')}
(v.strategySymbol ? v.strategySymbol.toUpperCase() : '')}
</Typography>
</ListItem>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ProposalIdentifier, ProposalResult, VoteItemList } from '../../types'
import type { ProposalIdentifier, ProposalResult, VoteItem } from '../../types'
import { useSuspense } from '../../../../utils/hooks/useSuspense'
import { useProposal } from './useProposal'
import { useVotes } from './useVotes'
Expand Down Expand Up @@ -34,7 +34,7 @@ async function Suspender(identifier: ProposalIdentifier) {
const powerDetailOfChoices = message.payload.choices.map((_choice, i) =>
strategies.map((_strategy, sI) => voteForChoice(votes, i).reduce((a, b) => a + b.scores[sI], 0)),
)
const totalPower = Object.values(votes).reduce((a, b) => a + b.balance, 0)
const totalPower = votes.reduce((a, b) => a + b.balance, 0)

const results: ProposalResult[] = powerOfChoices
.map((p, i) => ({
Expand All @@ -52,6 +52,6 @@ async function Suspender(identifier: ProposalIdentifier) {
return { results, totalPower }
}

function voteForChoice(votes: VoteItemList, i: number) {
return Object.values(votes).filter((vote) => vote.msg.payload.choice === i + 1)
function voteForChoice(votes: VoteItem[], i: number) {
return votes.filter((vote) => vote.choiceIndex === i + 1)
}
43 changes: 20 additions & 23 deletions packages/maskbook/src/plugins/Snapshot/SNSAdaptor/hooks/useVotes.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,42 @@
import { PluginSnapshotRPC } from '../../messages'
import type { VoteItemList, ProposalIdentifier, VoteItem } from '../../types'
import type { VoteItem, ProposalIdentifier } from '../../types'
import { useSuspense } from '../../../../utils/hooks/useSuspense'
import { useProposal } from './useProposal'
import { useBlockNumber } from '@masknet/web3-shared'

const cache = new Map<string, [0, Promise<void>] | [1, VoteItemList] | [2, Error]>()
const cache = new Map<string, [0, Promise<void>] | [1, VoteItem[]] | [2, Error]>()
export function votesRetry() {
for (const key of cache.keys()) {
cache.delete(key)
}
}
export function useVotes(identifier: ProposalIdentifier) {
return useSuspense<VoteItemList, [ProposalIdentifier]>(identifier.id, [identifier], cache, Suspender)
return useSuspense<VoteItem[], [ProposalIdentifier]>(identifier.id, [identifier], cache, Suspender)
}
async function Suspender(identifier: ProposalIdentifier) {
const blockNumber = useBlockNumber()
const {
payload: { message, proposal },
} = useProposal(identifier.id)

const rawVotes = await PluginSnapshotRPC.fetchAllVotesOfProposal(identifier.id, identifier.space)
const voters = Object.keys(rawVotes)
const voters = proposal.votes.map((v) => v.voter)
const scores = await PluginSnapshotRPC.getScores(message, voters, blockNumber, proposal.network)

const profiles = await PluginSnapshotRPC.fetch3BoxProfiles(voters)
const profileEntries = Object.fromEntries(profiles.map((p) => [p.contract_address, p]))
const votes = Object.fromEntries(
Object.entries(rawVotes)
.map((voteEntry: [string, VoteItem]) => {
voteEntry[1].scores = message.payload.metadata.strategies.map(
(_strategy, i) => scores[i][voteEntry[1].address] || 0,
)
voteEntry[1].strategySymbol = message.payload.metadata.strategies[0].params.symbol
voteEntry[1].balance = voteEntry[1].scores.reduce((a: number, b: number) => a + b, 0)
voteEntry[1].choice = message.payload.choices[voteEntry[1].msg.payload.choice - 1]
voteEntry[1].authorAvatar = profileEntries[voteEntry[0].toLowerCase()]?.image
voteEntry[1].authorName = profileEntries[voteEntry[0].toLowerCase()]?.name
return voteEntry
})
.sort((a, b) => b[1].balance - a[1].balance)
.filter((voteEntry) => voteEntry[1].balance > 0),
)
return votes
//#endregion
return proposal.votes
.map((v) => ({
choiceIndex: v.choice,
choice: message.payload.choices[v.choice - 1],
address: v.voter,
authorIpfsHash: v.id,
balance: scores.reduce((a, b) => a + (b[v.voter.toLowerCase()] ? b[v.voter.toLowerCase()] : 0), 0),
scores: message.payload.metadata.strategies.map((_strategy, i) => scores[i][v.voter] || 0),
strategySymbol: message.payload.metadata.strategies[0].params.symbol,
authorName: profileEntries[v.voter.toLowerCase()]?.name,
authorAvatar: profileEntries[v.voter.toLowerCase()]?.image,
timestamp: v.created,
}))
.sort((a, b) => b.balance - a.balance)
.filter((v) => v.balance > 0)
}
49 changes: 26 additions & 23 deletions packages/maskbook/src/plugins/Snapshot/Worker/apis/index.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,54 @@
import ss from '@snapshot-labs/snapshot.js'
import type { VoteItemList, Proposal, Profile3Box, ProposalMessage, ProposalIdentifier, VoteSuccess } from '../../types'
import ss from '@dimensiondev/snapshot.js'
import type { Proposal, Profile3Box, ProposalMessage, ProposalIdentifier, VoteSuccess, RawVote } from '../../types'
import Services from '../../../../extension/service'
import { resolveIPFSLink } from '@masknet/web3-shared'
import { transform } from 'lodash-es'

export async function fetchProposal(id: string) {
const response = await fetch(resolveIPFSLink(id), {
method: 'GET',
})
const network = await fetchProposalNetwork(id)
const { network, votes } = await fetchProposalFromGraphql(id)
const result = await response.json()

return { ...result, network } as Proposal
return { ...result, network, votes } as Proposal
}

async function fetchProposalNetwork(id: string) {
async function fetchProposalFromGraphql(id: string) {
const response = await fetch(`https://hub.snapshot.org/graphql`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
operationName: 'Proposal',
query: `query Proposal($id: String!) {
proposal(id: $id) {
network
}
votes(first: 10000, where: { proposal: $id }) {
id
voter
created
choice
}
}`,
variables: {
id,
},
}),
})

const {
interface Res {
data: {
proposal: { network },
},
} = await response.json()

return network as string
}
proposal: {
network: string
}
votes: RawVote[]
}
}

export function fetchAllProposalsOfSpace() {}
const { data }: Res = await response.json()

export async function fetchAllVotesOfProposal(id: string, space: string) {
const response = await fetch(`https://hub.snapshot.page/api/${space}/proposal/${id}`, {
method: 'GET',
})
const result: VoteItemList = await response.json()
return result
return { votes: data.votes, network: data.proposal.network }
}

export async function fetch3BoxProfiles(addresses: string[]): Promise<Profile3Box[]> {
Expand Down Expand Up @@ -83,7 +82,11 @@ export async function getScores(message: ProposalMessage, voters: string[], bloc
voters,
blockTag,
)
return scores
return scores.map((score) =>
transform(score, function (result: { [key in string]: number }, val, key: string) {
result[key.toString().toLowerCase()] = val
}),
)
}

export async function vote(identifier: ProposalIdentifier, choice: number, address: string) {
Expand Down
26 changes: 10 additions & 16 deletions packages/maskbook/src/plugins/Snapshot/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type snapshot from '@snapshot-labs/snapshot.js'

export interface ProposalIdentifier {
/**
* ENS domain name of space.
Expand All @@ -10,6 +8,12 @@ export interface ProposalIdentifier {
/** the identifier of proposal */
id: string
}
export interface RawVote {
choice: number
created: number
voter: string
id: string
}

export interface Proposal {
address: string
Expand All @@ -22,14 +26,15 @@ export interface Proposal {
authorName: string | null
authorAvatar: string | null
network: string
votes: RawVote[]
}

/**
* Strategy is the way to calculate voting power.
* https://docs.snapshot.org/strategies
*/
export interface Strategy {
name: keyof typeof snapshot.strategies
name: string
params: {
address: string
decimals?: number
Expand Down Expand Up @@ -66,26 +71,15 @@ export interface VoteItem {
choice: string
address: string
authorIpfsHash: string
relayerIpfsHash: string
/** the voting power of one voter */
balance: number
/** the consist detail of voting power */
scores: number[]
strategySymbol: string
sig: string
authorName: string | null
authorAvatar: string | null
msg: {
payload: {
choice: number
metadata: {}
proposal: string
}
space: string
timestamp: string
type: 'vote'
version: string
}
choiceIndex: number
timestamp: number
}

export type VoteItemList = {
Expand Down
Loading