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

Static site after finalize #613

Merged
merged 66 commits into from
Jan 13, 2023
Merged
Changes from 1 commit
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
1863b1b
script to fetch round information
yuetloo Dec 15, 2022
7b91f1e
round static data
yuetloo Dec 15, 2022
325e3f1
fix error when endBlock is not provided
yuetloo Dec 15, 2022
572ba0a
refactor script to pull historical round data
yuetloo Dec 19, 2022
d27dbb9
refactor fetchRound
yuetloo Dec 20, 2022
572f4bc
handle file not found error
yuetloo Dec 20, 2022
a9a5d8c
Merge remote-tracking branch 'origin/develop' into feat/static-leader…
yuetloo Dec 20, 2022
5684fb2
allow previous rounds to be displayed using static JSON data
yuetloo Dec 2, 2022
f897521
remove unused imports
yuetloo Dec 30, 2022
26c18fa
fix error about tally can be null
yuetloo Dec 30, 2022
67e5d42
only show links if available
yuetloo Dec 31, 2022
8e961b1
add getProjects
yuetloo Dec 31, 2022
7ea2f0e
fix build error caused by console.error
yuetloo Dec 31, 2022
b36a723
app loads based on round status
yuetloo Jan 4, 2023
9617610
remove unused import
yuetloo Jan 4, 2023
d289295
get current round from subgraph
yuetloo Jan 4, 2023
2803736
remove unused import
yuetloo Jan 4, 2023
50e4168
assign default matching pool size if missing
yuetloo Jan 4, 2023
a04635e
add menu item for rounds
yuetloo Jan 4, 2023
0d71219
add comment to tell vue-i18n-extract about dynamic key
yuetloo Jan 4, 2023
150051a
throw if missing critical information
yuetloo Jan 6, 2023
6051c91
remove const warning
yuetloo Jan 6, 2023
c689129
add token info to factory and funding round
yuetloo Jan 6, 2023
fec277e
add token ABI
yuetloo Jan 6, 2023
5496cb0
wait for round information to load before rendering
yuetloo Jan 6, 2023
a665c47
add tokenInfo to subgraph
yuetloo Jan 6, 2023
4933bf2
add missing mapping for token address
yuetloo Jan 6, 2023
48c1bcf
use etherscan api from hardhat config and throw on missing critical i…
yuetloo Jan 6, 2023
1359413
add script to generate the subgraph uml file
yuetloo Jan 6, 2023
3c8eca8
add script to generate the subgraph uml file
yuetloo Jan 6, 2023
c1f8c01
added nativeTokenInfo to factory and round
yuetloo Jan 6, 2023
349978d
code refracting to read from subgraph instead of contract and update …
yuetloo Jan 6, 2023
35ff7c4
refactor get maci info
yuetloo Jan 7, 2023
a880692
redirect to project lists if cannot show leaderboard view
yuetloo Jan 7, 2023
843090a
subgraph query keys are lowercase
yuetloo Jan 7, 2023
b28e879
more project details from newer rounds
yuetloo Jan 7, 2023
bf6ba69
fix missing translation keys
yuetloo Jan 7, 2023
a07ad5f
remove unused imports and variables
yuetloo Jan 7, 2023
6926701
revert back to local time to match maci timestamp
yuetloo Jan 9, 2023
72f0c30
styling leaderboard for light mode
yuetloo Jan 9, 2023
ca8ebf0
load tally file if not loaded already
yuetloo Jan 9, 2023
af4c2ee
only show image on desktop
yuetloo Jan 10, 2023
b766be2
contract address can be different case if read from subgraph
yuetloo Jan 10, 2023
25606d0
script to merge allocations for cancelled round
yuetloo Jan 10, 2023
71dfbad
refactor code
yuetloo Jan 10, 2023
9518da0
show leaderboard page for cancelled rounds if allocations were issued
yuetloo Jan 10, 2023
8cddc95
show the claim amount even for cancelled rounds
yuetloo Jan 10, 2023
386f9ea
fix eslint warning
yuetloo Jan 10, 2023
849012d
add blog url to round
yuetloo Jan 11, 2023
f4d2d27
use default link color
yuetloo Jan 11, 2023
eb65e8b
cleanup unused environment vars
yuetloo Jan 11, 2023
bc1e81f
add messages and max messages to static round data to avoid showing n…
yuetloo Jan 11, 2023
625974b
Merge branch 'develop' into feat/static-leaderboard
yuetloo Jan 11, 2023
ed97467
set totalSpent from tally file if not available from the contract
yuetloo Jan 11, 2023
1a1f946
get proof file for debugging
yuetloo Jan 12, 2023
97a5b80
export key before running test
yuetloo Jan 12, 2023
ebacd56
disable known host check
yuetloo Jan 12, 2023
3d79860
use openssl to export key
yuetloo Jan 12, 2023
f0fb469
another export
yuetloo Jan 12, 2023
438a159
cleanup test file
yuetloo Jan 12, 2023
e48fec4
try export key again
yuetloo Jan 12, 2023
5c74bb9
revert changes
yuetloo Jan 12, 2023
b584b2b
remove the push trigger on test-e2e.yml
yuetloo Jan 13, 2023
bdd4f28
update round information review url
yuetloo Jan 13, 2023
82deb31
updated leaderboard heading
yuetloo Jan 13, 2023
98a39cf
updated leaderboard notice
yuetloo Jan 13, 2023
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
Prev Previous commit
Next Next commit
code refracting to read from subgraph instead of contract and update …
…readme with instructions to enable leaderboard view
yuetloo committed Jan 6, 2023

Verified

This commit was signed with the committer’s verified signature.
yuetloo yuetloo
commit 349978d92ad25d7b83ba1418bf15440fd1fbd7a8
21 changes: 21 additions & 0 deletions docs/tally-verify.md
Original file line number Diff line number Diff line change
@@ -233,6 +233,27 @@ From the clrfund contracts folder, run the following command to verify the resul
yarn ts-node scripts/verify.ts tally.json
```

# How to enable the leaderboard view

After finalizing the round, enable the leaderboard view in the vue-app as follow:

1) Set the etherscan API key in the hardhat.config.ts file in the contracts folder
2) Export the round and tally result

```sh
cd contracts

yarn hardhat fetch-round --output-dir <output-directory> --network xdai --round-address <round address>

```

3) Upload the rounds.json and round data file somewhere accessible by the vue-app. For example, clr.fund currently stores them on the github repository: https://github.com/clrfund/rounds

4) Set the VUE_APP_STATIC_ROUNDS_BASE_URL environment variable in the vue-app folder to the location in previous step. For example:
```
VUE_APP_STATIC_ROUNDS_BASE_URL=https://raw.githubusercontent.com/clrfund/rounds/main/
```


## Troubleshooting
If you encountered `core dumped` while running the genProofs script as seen in this [issue](https://github.com/clrfund/monorepo/issues/383), make sure the files are not corrupted due to disk space issue, e.g. check file sizes, checksum, and missing files.
7 changes: 7 additions & 0 deletions subgraph/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
[See the subgraph documentation](../docs/subgraph.md) for instructions on how to run the subgraph in your local environment.

Following UML diagram is generated using [graphqlviz](https://github.com/sheerun/graphqlviz)

```sh
cd subgraph
graphqlviz https://api.thegraph.com/subgraphs/name/clrfund/clrfund | dot -Tpng -o subgraphUML.png
```

![Subgraph UML](subgraphUML.png)
22 changes: 14 additions & 8 deletions vue-app/src/api/factory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Contract } from 'ethers'
import { ERC20 } from './abi'
import { factory, provider } from './core'
import { BigNumber } from 'ethers'
import { factory } from './core'
import sdk from '@/graphql/sdk'

export interface Factory {
fundingRoundAddress: string
@@ -11,13 +11,19 @@ export interface Factory {
}

export async function getFactoryInfo() {
const nativeTokenAddress = await factory.nativeToken()
const { fundingRoundFactory } = await sdk.GetFactoryInfo({
factoryAddress: factory.address.toLowerCase(),
})

const nativeToken = new Contract(nativeTokenAddress, ERC20, provider)
const nativeTokenSymbol = await nativeToken.symbol()
const nativeTokenDecimals = await nativeToken.decimals()
const nativeTokenAddress = fundingRoundFactory?.nativeTokenInfo?.tokenAddress

const userRegistryAddress = await factory.userRegistry()
const nativeTokenSymbol = fundingRoundFactory?.nativeTokenInfo?.symbol
const decimals = BigNumber.from(
fundingRoundFactory?.nativeTokenInfo?.decimals || 0
)
const nativeTokenDecimals = decimals.toNumber()

const userRegistryAddress = fundingRoundFactory?.contributorRegistryAddress

return {
fundingRoundAddress: factory.address,
14 changes: 0 additions & 14 deletions vue-app/src/api/projects.ts
Original file line number Diff line number Diff line change
@@ -45,20 +45,6 @@ export interface Project {
extra?: any // Registry-specific data
}

//TODO: update anywhere this is called to take factory address as a parameter
//NOTE: why isn't this included in the vuex state schema?
export async function getRecipientRegistryAddress(
roundAddress: string | null
): Promise<string> {
if (roundAddress !== null) {
const fundingRound = new Contract(roundAddress, FundingRound, provider)
return await fundingRound.recipientRegistry()
} else {
//TODO: upgrade factory to take it's address as a parameter
return await factory.recipientRegistry()
}
}

export async function getProjects(
registryAddress: string,
startTime?: number,
32 changes: 27 additions & 5 deletions vue-app/src/api/recipient-registry-optimistic.ts
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import { Recipient } from '@/graphql/API'
import { hasDateElapsed } from '@/utils/dates'

export interface RegistryInfo {
registryAddress: string
deposit: BigNumber
depositToken: string
challengePeriodDuration: number
@@ -25,15 +26,36 @@ export interface RegistryInfo {
}

export async function getRegistryInfo(
registryAddress: string
): Promise<RegistryInfo> {
factoryAddress: string
): Promise<RegistryInfo | null> {
const { fundingRoundFactory } = await sdk.GetRecipientRegistryInfo({
factoryAddress: factoryAddress.toLowerCase(),
})

if (!fundingRoundFactory) {
return null
}

const recipientRegistry = fundingRoundFactory.currentRound
? fundingRoundFactory.currentRound.recipientRegistry
: fundingRoundFactory.recipientRegistry

if (!recipientRegistry) {
return null
}

const registryAddress = recipientRegistry.id
const owner = recipientRegistry.owner
const deposit = BigNumber.from(recipientRegistry.baseDeposit || 0)
const challengePeriodDuration = BigNumber.from(
recipientRegistry.challengePeriodDuration || 0
)

const registry = new Contract(
registryAddress,
OptimisticRecipientRegistry,
provider
)
const deposit = await registry.baseDeposit()
const challengePeriodDuration = await registry.challengePeriodDuration()
let recipientCount
try {
recipientCount = await registry.getRecipientCount()
@@ -43,8 +65,8 @@ export async function getRegistryInfo(
// used during current round for space calculation
recipientCount = BigNumber.from(0)
}
const owner = await registry.owner()
return {
registryAddress,
deposit,
depositToken: chain.currency,
challengePeriodDuration: challengePeriodDuration.toNumber(),
8 changes: 5 additions & 3 deletions vue-app/src/api/round-base.ts
Original file line number Diff line number Diff line change
@@ -25,10 +25,12 @@ export abstract class BaseRound {
}

abstract getTokenInfo(): Promise<Token>
abstract getRoundInfo(cachedRound?: RoundInfo): Promise<RoundInfo>
abstract getLeaderboardProjects(): Promise<LeaderboardProject[]>
abstract getRoundInfo(cachedRound?: RoundInfo): Promise<RoundInfo | null>
abstract getLeaderboardProjects(): LeaderboardProject[] | null
abstract getProject(projectId: string): Promise<Project | null>
abstract getProjects(): Promise<Project[]>
abstract getAllocatedAmount(projectId: string): Promise<BigNumber | null>
abstract getAllocatedAmountByProjectIndex(
projectIndex: number
): Promise<BigNumber | null>
abstract getTally(): Promise<Tally | null>
}
211 changes: 88 additions & 123 deletions vue-app/src/api/round-dynamic.ts
Original file line number Diff line number Diff line change
@@ -5,18 +5,18 @@ import { Token } from './token'
import {
getProject,
getProjects,
getRecipientRegistryAddress,
LeaderboardProject,
Project,
} from './projects'
import { getAllocatedAmount } from './claims'
import { FundingRound, ERC20, MACI } from './abi'
import { FundingRound, MACI } from './abi'
import { provider, factory } from './core'
import { BigNumber, Contract, FixedNumber } from 'ethers'
import { isSameAddress } from '@/utils/accounts'
import { getTotalContributed } from './contributions'
import { PubKey } from 'maci-domainobjs'
import { DateTime } from 'luxon'
import sdk from '@/graphql/sdk'
import { getTokenBalance } from './user'
import { getMaciInfo } from './maci'

/**
* DynamicRound loads round information from smart contract or subgraph
@@ -26,43 +26,45 @@ export class DynamicRound extends BaseRound {
super(fundingRoundAddress, isFinalized)
}

private async getProjectAllocatedAmount(
registryAddress: string,
projectId: string,
tally: Tally
async getAllocatedAmountByProjectIndex(
projectIndex: number
): Promise<BigNumber | null> {
const project = await getProject(registryAddress, projectId)
if (!project) {
if (!this.isFinalized) {
return null
}

const tallyResult = tally.results.tally[project.index]
const spent = tally.totalVoiceCreditsPerVoteOption.tally[project.index]

return getAllocatedAmount(this.address, tallyResult, spent)
}
try {
const tally = await getTally(this.address)
const tallyResult = tally.results.tally[projectIndex]
const spent = tally.totalVoiceCreditsPerVoteOption.tally[projectIndex]

async getAllocatedAmount(projectId: string): Promise<BigNumber | null> {
if (!this.isFinalized) {
return getAllocatedAmount(this.address, tallyResult, spent)
} catch (err) {
const errorMessage = err instanceof Error ? err.message : ''
// eslint-disable-next-line no-console
console.error('Failed to get allocated amount', errorMessage)
return null
}

const registryAddress = await getRecipientRegistryAddress(this.address)
const tally = await getTally(this.address)
return this.getProjectAllocatedAmount(registryAddress, projectId, tally)
}

async getTokenInfo(): Promise<Token> {
const roundContract = new Contract(this.address, FundingRound, provider)
const address = await roundContract.nativeToken()
const nativeToken = new Contract(address, ERC20, provider)
const symbol = await nativeToken.symbol()
const decimals = await nativeToken.decimals()
const data = await sdk.GetTokenInfo({
fundingRoundAddress: this.address.toLowerCase(),
})

const address = data.fundingRound?.nativeTokenInfo?.tokenAddress || ''
const symbol = data.fundingRound?.nativeTokenInfo?.symbol || ''
const decimals = data.fundingRound?.nativeTokenInfo?.decimals || 0

return { address, symbol, decimals }
}

async getRoundInfo(cachedRound?: RoundInfo): Promise<RoundInfo> {
/**
* Get round information for round information and leaderboard views
* @param cachedRound previously cached round, usually is the current round
* @returns round information
*/
async getRoundInfo(cachedRound?: RoundInfo): Promise<RoundInfo | null> {
if (
cachedRound &&
isSameAddress(this.address, cachedRound.fundingRoundAddress)
@@ -71,65 +73,58 @@ export class DynamicRound extends BaseRound {
return cachedRound
}

const fundingRound = new Contract(this.address, FundingRound, provider)
const [
maciAddress,
nativeTokenAddress,
recipientRegistryAddress,
userRegistryAddress,
voiceCreditFactor,
isFinalized,
isCancelled,
] = await Promise.all([
fundingRound.maci(),
fundingRound.nativeToken(),
fundingRound.recipientRegistry(),
fundingRound.userRegistry(),
fundingRound.voiceCreditFactor(),
fundingRound.isFinalized(),
fundingRound.isCancelled(),
])
const { fundingRound } = await sdk.GetRoundInfo({
fundingRoundAddress: this.address.toLowerCase(),
})
if (!fundingRound) {
return null
}

const maci = new Contract(maciAddress, MACI, provider)
const [
maciTreeDepths,
signUpTimestamp,
signUpDurationSeconds,
votingDurationSeconds,
coordinatorPubKeyRaw,
messages,
] = await Promise.all([
maci.treeDepths(),
maci.signUpTimestamp(),
maci.signUpDurationSeconds(),
maci.votingDurationSeconds(),
maci.coordinatorPubKey(),
maci.numMessages(),
])
const startTime = DateTime.fromSeconds(signUpTimestamp.toNumber())
const signUpDeadline = DateTime.fromSeconds(
signUpTimestamp.add(signUpDurationSeconds).toNumber()
)
const votingDeadline = DateTime.fromSeconds(
signUpTimestamp
.add(signUpDurationSeconds)
.add(votingDurationSeconds)
.toNumber()
const maciAddress = fundingRound.maci || ''
const maciInfo = await getMaciInfo(maciAddress)
if (!maciInfo) {
// this should not happen as MACI is deployed as part of the round
return null
}

const recipientRegistryAddress = fundingRound.recipientRegistryAddress || ''
const userRegistryAddress = fundingRound.contributorRegistryAddress
const voiceCreditFactor = BigNumber.from(
fundingRound.voiceCreditFactor || 0
)
const coordinatorPubKey = new PubKey([
BigInt(coordinatorPubKeyRaw.x),
BigInt(coordinatorPubKeyRaw.y),
])
const isFinalized = fundingRound.isFinalized || false
const isCancelled = fundingRound.isCancelled || false
const totalSpent = BigNumber.from(fundingRound.totalSpent || 0)
const matchingPoolSize = BigNumber.from(fundingRound.matchingPoolSize || 0)

const nativeTokenAddress = fundingRound.nativeTokenInfo?.tokenAddress || ''
const nativeTokenSymbol = fundingRound.nativeTokenInfo?.symbol || ''
const nativeTokenDecimals = BigNumber.from(
fundingRound.nativeTokenInfo?.decimals || 0
).toNumber()

const now = DateTime.utc()
const {
messages,
maxMessages,
signUpDeadline,
maxContributors,
votingDeadline,
recipientTreeDepth,
maxRecipients,
coordinatorPubKey,
startTime,
} = maciInfo

const nativeToken = new Contract(nativeTokenAddress, ERC20, provider)
const nativeTokenSymbol = await nativeToken.symbol()
const nativeTokenDecimals = await nativeToken.decimals()
const contributors = BigNumber.from(
fundingRound?.contributorCount || 0
).toNumber()

const tokenBalance =
contributors > 0
? await getTokenBalance(nativeTokenAddress, this.address)
: BigNumber.from(0)

const maxContributors = 2 ** maciTreeDepths.stateTreeDepth - 1
const maxMessages = 2 ** maciTreeDepths.messageTreeDepth - 1
const now = DateTime.local()
const contributionsInfo = await getTotalContributed(this.address)
const contributors = contributionsInfo.count
let status: string
let contributions: BigNumber
let matchingPool: BigNumber
@@ -139,11 +134,11 @@ export class DynamicRound extends BaseRound {
matchingPool = BigNumber.from(0)
} else if (isFinalized) {
status = RoundStatus.Finalized
contributions = (await fundingRound.totalSpent()).mul(voiceCreditFactor)
matchingPool = await fundingRound.matchingPoolSize()
contributions = totalSpent.mul(voiceCreditFactor)
matchingPool = matchingPoolSize
} else if (messages >= maxMessages) {
status = RoundStatus.Tallying
contributions = contributionsInfo.amount
contributions = tokenBalance
matchingPool = await factory.getMatchingFunds(nativeTokenAddress)
} else {
if (now < signUpDeadline && contributors < maxContributors) {
@@ -153,7 +148,7 @@ export class DynamicRound extends BaseRound {
} else {
status = RoundStatus.Tallying
}
contributions = contributionsInfo.amount
contributions = tokenBalance
//TODO: update to take factory address as a parameter, default to env. variable
matchingPool = await factory.getMatchingFunds(nativeTokenAddress)
}
@@ -165,9 +160,9 @@ export class DynamicRound extends BaseRound {
recipientRegistryAddress,
userRegistryAddress,
maciAddress,
recipientTreeDepth: maciTreeDepths.voteOptionTreeDepth,
recipientTreeDepth,
maxContributors,
maxRecipients: 5 ** maciTreeDepths.voteOptionTreeDepth - 1,
maxRecipients,
maxMessages,
coordinatorPubKey,
nativeTokenAddress,
@@ -182,7 +177,7 @@ export class DynamicRound extends BaseRound {
matchingPool: FixedNumber.fromValue(matchingPool, nativeTokenDecimals),
contributions: FixedNumber.fromValue(contributions, nativeTokenDecimals),
contributors,
messages: messages.toNumber(),
messages,
}
}

@@ -211,40 +206,10 @@ export class DynamicRound extends BaseRound {
return projects.filter((project) => !project.isHidden && !project.isLocked)
}

async getLeaderboardProjects(): Promise<LeaderboardProject[]> {
const projects = await this.getProjects()
let allocations: BigNumber[] = projects.map(() => BigNumber.from(0))
const tally = this.isFinalized ? await getTally(this.address) : null

if (this.isFinalized && tally) {
const registryAddress = await getRecipientRegistryAddress(this.address)
allocations = await Promise.all(
projects.map(async (project) => {
const amount = await this.getProjectAllocatedAmount(
registryAddress,
project.id,
tally
)
return amount ?? BigNumber.from(0)
})
)
}

return projects.map((project, idx) => {
return {
id: project.id,
name: project.name,
index: project.index,
bannerImageUrl: project.bannerImageUrl,
thumbnailImageUrl: project.thumbnailImageUrl,
imageUrl: project.imageUrl,
allocatedAmount: allocations[idx] ?? BigNumber.from(0),
donation: BigNumber.from(
tally?.totalVoiceCreditsPerVoteOption.tally[project.index] ?? 0
),
votes: BigNumber.from(tally?.results.tally[project.index] ?? 0),
}
})
getLeaderboardProjects(): LeaderboardProject[] | null {
// return null because we only want to show the leaderboard if we have
// the static round tally data
return null
}

async getProject(projectId: string): Promise<Project | null> {
27 changes: 18 additions & 9 deletions vue-app/src/api/round-static.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,9 @@ import { IPFS } from './ipfs'
interface StaticProject extends Project {
allocatedAmount: string
state: string
recipientIndex: number
metadata?: any
recipientAddress: string
}

export type StaticRoundData = {
@@ -82,7 +85,7 @@ function mapTwitterUrl(metadata: any = {}): string {
return new URL(`${twitterBaseUrl}${twitter}`).href
}

function toProjectInterface(project: any): Project {
function toProjectInterface(project: StaticProject): Project {
const imageUrl = IPFS.formatUrl(project.metadata?.imageHash)
const twitterUrl = mapTwitterUrl(project.metadata)

@@ -119,7 +122,7 @@ function toProjectInterface(project: any): Project {
export class StaticRound extends BaseRound {
round: RoundInfo
projects: Record<string, Project>
allocations: Record<string, BigNumber>
allocations: Record<number, BigNumber>
tally: Tally | null

constructor(data: StaticRoundData, isFinalized: boolean) {
@@ -137,7 +140,7 @@ export class StaticRound extends BaseRound {
const BigNumberZero = BigNumber.from(0)

this.allocations = data.projects.reduce((allocations, project) => {
allocations[project.id] =
allocations[project.recipientIndex] =
this.round.status === RoundStatus.Finalized
? BigNumber.from(project.allocatedAmount || '0')
: BigNumberZero
@@ -159,22 +162,26 @@ export class StaticRound extends BaseRound {
}
}

async getRoundInfo(): Promise<RoundInfo> {
async getRoundInfo(): Promise<RoundInfo | null> {
return this.round
}

/**
* retrieve project information for the leaderboard view
*/
async getLeaderboardProjects(): Promise<LeaderboardProject[]> {
getLeaderboardProjects(): LeaderboardProject[] | null {
if (!this.isFinalized) {
return null
}

const projects = Object.values(this.projects).map((project) => ({
id: project.id,
name: project.name,
index: project.index,
bannerImageUrl: project.bannerImageUrl,
thumbnailImageUrl: project.thumbnailImageUrl,
imageUrl: project.imageUrl,
allocatedAmount: this.allocations[project.id] ?? BigNumber.from(0),
allocatedAmount: this.allocations[project.index] ?? BigNumber.from(0),
donation: BigNumber.from(
this.tally?.totalVoiceCreditsPerVoteOption.tally[project.index] ?? 0
),
@@ -199,12 +206,14 @@ export class StaticRound extends BaseRound {

/**
* Return the amount allocated to the project
* @param projectId project id
* @param projectIndex project index used to access the tally result array
* @returns the amount allocated to a project according to the tally result
* NULL if the project is not found or the round is not finalized
*/
async getAllocatedAmount(projectId: string): Promise<BigNumber | null> {
return this.allocations[projectId] ?? null
async getAllocatedAmountByProjectIndex(
projectIndex: number
): Promise<BigNumber | null> {
return this.allocations[projectIndex] ?? null
}

/**
16 changes: 11 additions & 5 deletions vue-app/src/api/rounds.ts
Original file line number Diff line number Diff line change
@@ -65,8 +65,10 @@ export class Rounds {
return new Rounds(rounds)
}

private get(roundAddress = ''): Round | undefined {
return this.rounds.get(roundAddress.toLowerCase())
private get(roundAddress: string): Round | undefined {
return roundAddress
? this.rounds.get(roundAddress.toLowerCase())
: undefined
}

list(): Round[] {
@@ -80,13 +82,17 @@ export class Rounds {
return round?.index
}

async getRound(roundAddress: string): Promise<BaseRound> {
async getRound(roundAddress: string): Promise<BaseRound | null> {
const round = this.get(roundAddress)
if (round?.url) {
if (!round) {
return null
}

if (round.url) {
const data = await utils.fetchJson(round.url)
return new StaticRound(data, round.isFinalized)
} else {
return new DynamicRound(roundAddress, Boolean(round?.isFinalized))
return new DynamicRound(roundAddress, Boolean(round.isFinalized))
}
}

24 changes: 13 additions & 11 deletions vue-app/src/components/AppLink.vue
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ export default class extends Vue {
// otherwise show the static text 'App'
@Prop() dynamic!: boolean
showLeaderboard = false
loading = true
async created() {
@@ -37,30 +38,31 @@ export default class extends Vue {
if (!this.$store.state.currentRoundAddress) {
const currentRoundAddress = await getCurrentRound()
await this.$store.dispatch(SELECT_ROUND, currentRoundAddress)
await this.$store.dispatch(LOAD_ROUND_INFO)
}
if (
this.$store.state.currentRoundAddress &&
!this.$store.state.currentRound
) {
if (!this.$store.state.currentRound) {
await this.$store.dispatch(LOAD_ROUND_INFO)
}
const count = await this.getProjectCount()
this.showLeaderboard = count > 0
this.loading = false
}
get isRoundFinalized(): boolean {
async getProjectCount(): Promise<number> {
const roundAddress = this.$store.state.currentRoundAddress
const rounds = this.$store.state.rounds
return roundAddress && rounds
? rounds.isRoundFinalized(roundAddress)
: false
const round = await this.$store.state.rounds?.getRound(roundAddress)
const projects = round?.getLeaderboardProjects()
const count = projects?.length || 0
return count
}
get targetUrl(): string {
const roundAddress = this.$store.state.currentRoundAddress
return this.isRoundFinalized
return this.showLeaderboard
? `/rounds/${roundAddress}/leaderboard`
: '/projects'
}
@@ -70,7 +72,7 @@ export default class extends Vue {
}
get label(): string {
return this.isRoundFinalized
return this.showLeaderboard
? this.translate('dynamic.appLink.leaderboard')
: this.translate('dynamic.appLink.getStarted')
}
9 changes: 7 additions & 2 deletions vue-app/src/components/ClaimButton.vue
Original file line number Diff line number Diff line change
@@ -99,9 +99,14 @@ export default class ClaimButton extends Vue {
const selectedRound = await this.$store.state.rounds.getRound(
this.roundAddress
)
if (!selectedRound) {
this.isLoading = false
return
}
this.token = await selectedRound.getTokenInfo(this.roundAddress)
this.allocatedAmount = await selectedRound.getAllocatedAmount(
this.project.id
this.allocatedAmount = await selectedRound.getAllocatedAmountByProjectIndex(
this.project.index
)
this.claimed = this.$store.getters.isCurrentRound(this.roundAddress)
10 changes: 8 additions & 2 deletions vue-app/src/components/LeaderboardSimpleView.vue
Original file line number Diff line number Diff line change
@@ -27,7 +27,13 @@
</div>
<div class="funding">
<div class="amount">~{{ formatAmount(project.allocatedAmount) }}</div>
<div class="symbol">{{ tokenSymbol }} funded</div>
<div class="symbol">
{{
$t('leaderboardSimpleView.funded', {
tokenSymbol,
})
}}
</div>
</div>
</div>
</links>
@@ -99,7 +105,7 @@ export default class LeaderboardSimpleView extends Vue {
}
get tokenLogo(): string {
return getTokenLogo(this.$store.getters.nativeTokenSymbol)
return getTokenLogo(this.tokenSymbol)
}
}
</script>
12 changes: 6 additions & 6 deletions vue-app/src/components/NavBar.vue
Original file line number Diff line number Diff line change
@@ -120,21 +120,21 @@ export default class NavBar extends Vue {
text: 'navBar.dropdown.sybil',
emoji: '👤',
},
{
to: 'https://github.com/clrfund/monorepo/',
text: 'navBar.dropdown.code',
emoji: '👾',
},
{
to: '/recipients',
text: 'navBar.dropdown.recipients',
emoji: '💎',
},
{
to: 'rounds',
to: '/rounds',
text: 'navBar.dropdown.rounds',
emoji: '',
},
{
to: 'https://github.com/clrfund/monorepo/',
text: 'navBar.dropdown.code',
emoji: '👾',
},
]
langs: string[] = Trans.supportedLocales
7 changes: 6 additions & 1 deletion vue-app/src/components/RoundInformation.vue
Original file line number Diff line number Diff line change
@@ -374,8 +374,13 @@ export default class RoundInformation extends Vue {
if (!this.$store.state.rounds) {
await this.$store.dispatch(LOAD_ROUNDS)
}
const round = await this.$store.state.rounds.getRound(this.roundAddress)
this.roundInfo = await round.getRoundInfo(this.$store.state.currentRound)
if (round) {
this.roundInfo = await round.getRoundInfo(
this.$store.state.currentRound
)
}
}
this.isLoading = false
}
151 changes: 151 additions & 0 deletions vue-app/src/graphql/API.ts
Original file line number Diff line number Diff line change
@@ -644,6 +644,7 @@ export type FundingRound = {
contributorRegistry: Maybe<ContributorRegistry>;
contributorRegistryAddress: Maybe<Scalars['Bytes']>;
nativeToken: Maybe<Scalars['Bytes']>;
nativeTokenInfo: Maybe<Token>;
startTime: Maybe<Scalars['BigInt']>;
signUpDeadline: Maybe<Scalars['BigInt']>;
votingDeadline: Maybe<Scalars['BigInt']>;
@@ -716,6 +717,7 @@ export type FundingRoundFactory = {
owner: Maybe<Scalars['Bytes']>;
coordinator: Maybe<Scalars['Bytes']>;
nativeToken: Maybe<Scalars['Bytes']>;
nativeTokenInfo: Maybe<Token>;
contributorRegistry: Maybe<ContributorRegistry>;
contributorRegistryAddress: Maybe<Scalars['Bytes']>;
recipientRegistry: Maybe<RecipientRegistry>;
@@ -776,6 +778,27 @@ export type FundingRoundFactory_Filter = {
nativeToken_not_in: Maybe<Array<Scalars['Bytes']>>;
nativeToken_contains: Maybe<Scalars['Bytes']>;
nativeToken_not_contains: Maybe<Scalars['Bytes']>;
nativeTokenInfo: Maybe<Scalars['String']>;
nativeTokenInfo_not: Maybe<Scalars['String']>;
nativeTokenInfo_gt: Maybe<Scalars['String']>;
nativeTokenInfo_lt: Maybe<Scalars['String']>;
nativeTokenInfo_gte: Maybe<Scalars['String']>;
nativeTokenInfo_lte: Maybe<Scalars['String']>;
nativeTokenInfo_in: Maybe<Array<Scalars['String']>>;
nativeTokenInfo_not_in: Maybe<Array<Scalars['String']>>;
nativeTokenInfo_contains: Maybe<Scalars['String']>;
nativeTokenInfo_contains_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_not_contains: Maybe<Scalars['String']>;
nativeTokenInfo_not_contains_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_starts_with: Maybe<Scalars['String']>;
nativeTokenInfo_starts_with_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_not_starts_with: Maybe<Scalars['String']>;
nativeTokenInfo_not_starts_with_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_ends_with: Maybe<Scalars['String']>;
nativeTokenInfo_ends_with_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_not_ends_with: Maybe<Scalars['String']>;
nativeTokenInfo_not_ends_with_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_: Maybe<Token_Filter>;
contributorRegistry: Maybe<Scalars['String']>;
contributorRegistry_not: Maybe<Scalars['String']>;
contributorRegistry_gt: Maybe<Scalars['String']>;
@@ -1019,6 +1042,7 @@ export enum FundingRoundFactory_OrderBy {
Owner = 'owner',
Coordinator = 'coordinator',
NativeToken = 'nativeToken',
NativeTokenInfo = 'nativeTokenInfo',
ContributorRegistry = 'contributorRegistry',
ContributorRegistryAddress = 'contributorRegistryAddress',
RecipientRegistry = 'recipientRegistry',
@@ -1140,6 +1164,27 @@ export type FundingRound_Filter = {
nativeToken_not_in: Maybe<Array<Scalars['Bytes']>>;
nativeToken_contains: Maybe<Scalars['Bytes']>;
nativeToken_not_contains: Maybe<Scalars['Bytes']>;
nativeTokenInfo: Maybe<Scalars['String']>;
nativeTokenInfo_not: Maybe<Scalars['String']>;
nativeTokenInfo_gt: Maybe<Scalars['String']>;
nativeTokenInfo_lt: Maybe<Scalars['String']>;
nativeTokenInfo_gte: Maybe<Scalars['String']>;
nativeTokenInfo_lte: Maybe<Scalars['String']>;
nativeTokenInfo_in: Maybe<Array<Scalars['String']>>;
nativeTokenInfo_not_in: Maybe<Array<Scalars['String']>>;
nativeTokenInfo_contains: Maybe<Scalars['String']>;
nativeTokenInfo_contains_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_not_contains: Maybe<Scalars['String']>;
nativeTokenInfo_not_contains_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_starts_with: Maybe<Scalars['String']>;
nativeTokenInfo_starts_with_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_not_starts_with: Maybe<Scalars['String']>;
nativeTokenInfo_not_starts_with_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_ends_with: Maybe<Scalars['String']>;
nativeTokenInfo_ends_with_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_not_ends_with: Maybe<Scalars['String']>;
nativeTokenInfo_not_ends_with_nocase: Maybe<Scalars['String']>;
nativeTokenInfo_: Maybe<Token_Filter>;
startTime: Maybe<Scalars['BigInt']>;
startTime_not: Maybe<Scalars['BigInt']>;
startTime_gt: Maybe<Scalars['BigInt']>;
@@ -1304,6 +1349,7 @@ export enum FundingRound_OrderBy {
ContributorRegistry = 'contributorRegistry',
ContributorRegistryAddress = 'contributorRegistryAddress',
NativeToken = 'nativeToken',
NativeTokenInfo = 'nativeTokenInfo',
StartTime = 'startTime',
SignUpDeadline = 'signUpDeadline',
VotingDeadline = 'votingDeadline',
@@ -2743,6 +2789,13 @@ export type GetCurrentRoundQueryVariables = Exact<{

export type GetCurrentRoundQuery = { __typename?: 'Query', fundingRoundFactory: Maybe<{ __typename?: 'FundingRoundFactory', currentRound: Maybe<{ __typename?: 'FundingRound', id: string }> }> };

export type GetFactoryInfoQueryVariables = Exact<{
factoryAddress: Scalars['ID'];
}>;


export type GetFactoryInfoQuery = { __typename?: 'Query', fundingRoundFactory: Maybe<{ __typename?: 'FundingRoundFactory', contributorRegistryAddress: Maybe<any>, nativeTokenInfo: Maybe<{ __typename?: 'Token', tokenAddress: Maybe<any>, symbol: Maybe<string>, decimals: Maybe<any> }> }> };

export type GetProjectQueryVariables = Exact<{
recipientId: Scalars['ID'];
}>;
@@ -2766,18 +2819,39 @@ export type GetRecipientDonationsQueryVariables = Exact<{

export type GetRecipientDonationsQuery = { __typename?: 'Query', donations: Array<{ __typename?: 'Donation', id: string }> };

export type GetRecipientRegistryInfoQueryVariables = Exact<{
factoryAddress: Scalars['ID'];
}>;


export type GetRecipientRegistryInfoQuery = { __typename?: 'Query', fundingRoundFactory: Maybe<{ __typename?: 'FundingRoundFactory', recipientRegistry: Maybe<{ __typename?: 'RecipientRegistry', id: string, owner: Maybe<any>, baseDeposit: Maybe<any>, challengePeriodDuration: Maybe<any> }>, currentRound: Maybe<{ __typename?: 'FundingRound', id: string, recipientRegistry: Maybe<{ __typename?: 'RecipientRegistry', id: string, owner: Maybe<any>, baseDeposit: Maybe<any>, challengePeriodDuration: Maybe<any> }> }> }> };

export type GetRecipientsQueryVariables = Exact<{
registryAddress: Scalars['String'];
}>;


export type GetRecipientsQuery = { __typename?: 'Query', recipients: Array<{ __typename?: 'Recipient', id: string, recipientIndex: Maybe<any>, requestType: Maybe<string>, requester: Maybe<string>, recipientAddress: Maybe<any>, recipientMetadata: Maybe<string>, requestSubmittedHash: Maybe<any>, requestResolvedHash: Maybe<any>, submissionTime: Maybe<string>, rejected: Maybe<boolean>, verified: Maybe<boolean> }> };

export type GetRoundInfoQueryVariables = Exact<{
fundingRoundAddress: Scalars['ID'];
}>;


export type GetRoundInfoQuery = { __typename?: 'Query', fundingRound: Maybe<{ __typename?: 'FundingRound', id: string, maci: Maybe<any>, recipientRegistryAddress: Maybe<any>, contributorRegistryAddress: Maybe<any>, voiceCreditFactor: Maybe<any>, isFinalized: Maybe<boolean>, isCancelled: Maybe<boolean>, contributorCount: any, totalSpent: Maybe<any>, matchingPoolSize: Maybe<any>, nativeTokenInfo: Maybe<{ __typename?: 'Token', tokenAddress: Maybe<any>, symbol: Maybe<string>, decimals: Maybe<any> }> }> };

export type GetRoundsQueryVariables = Exact<{ [key: string]: never; }>;


export type GetRoundsQuery = { __typename?: 'Query', fundingRounds: Array<{ __typename?: 'FundingRound', id: string, isFinalized: Maybe<boolean>, isCancelled: Maybe<boolean>, startTime: Maybe<any> }> };

export type GetTokenInfoQueryVariables = Exact<{
fundingRoundAddress: Scalars['ID'];
}>;


export type GetTokenInfoQuery = { __typename?: 'Query', fundingRound: Maybe<{ __typename?: 'FundingRound', nativeTokenInfo: Maybe<{ __typename?: 'Token', tokenAddress: Maybe<any>, symbol: Maybe<string>, decimals: Maybe<any> }> }> };

export type GetTotalContributedQueryVariables = Exact<{
fundingRoundAddress: Scalars['ID'];
}>;
@@ -2818,6 +2892,18 @@ export const GetCurrentRoundDocument = gql`
}
}
`;
export const GetFactoryInfoDocument = gql`
query GetFactoryInfo($factoryAddress: ID!) {
fundingRoundFactory(id: $factoryAddress) {
nativeTokenInfo {
tokenAddress
symbol
decimals
}
contributorRegistryAddress
}
}
`;
export const GetProjectDocument = gql`
query GetProject($recipientId: ID!) {
recipients(where: {id: $recipientId}) {
@@ -2856,6 +2942,27 @@ export const GetRecipientDonationsDocument = gql`
}
}
`;
export const GetRecipientRegistryInfoDocument = gql`
query GetRecipientRegistryInfo($factoryAddress: ID!) {
fundingRoundFactory(id: $factoryAddress) {
recipientRegistry {
id
owner
baseDeposit
challengePeriodDuration
}
currentRound {
id
recipientRegistry {
id
owner
baseDeposit
challengePeriodDuration
}
}
}
}
`;
export const GetRecipientsDocument = gql`
query GetRecipients($registryAddress: String!) {
recipients(where: {recipientRegistry: $registryAddress}) {
@@ -2873,6 +2980,27 @@ export const GetRecipientsDocument = gql`
}
}
`;
export const GetRoundInfoDocument = gql`
query GetRoundInfo($fundingRoundAddress: ID!) {
fundingRound(id: $fundingRoundAddress) {
id
maci
nativeTokenInfo {
tokenAddress
symbol
decimals
}
recipientRegistryAddress
contributorRegistryAddress
voiceCreditFactor
isFinalized
isCancelled
contributorCount
totalSpent
matchingPoolSize
}
}
`;
export const GetRoundsDocument = gql`
query GetRounds {
fundingRounds(orderBy: startTime, orderDirection: asc) {
@@ -2883,6 +3011,17 @@ export const GetRoundsDocument = gql`
}
}
`;
export const GetTokenInfoDocument = gql`
query GetTokenInfo($fundingRoundAddress: ID!) {
fundingRound(id: $fundingRoundAddress) {
nativeTokenInfo {
tokenAddress
symbol
decimals
}
}
}
`;
export const GetTotalContributedDocument = gql`
query GetTotalContributed($fundingRoundAddress: ID!) {
fundingRound(id: $fundingRoundAddress) {
@@ -2907,6 +3046,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
GetCurrentRound(variables: GetCurrentRoundQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetCurrentRoundQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetCurrentRoundQuery>(GetCurrentRoundDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetCurrentRound');
},
GetFactoryInfo(variables: GetFactoryInfoQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetFactoryInfoQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetFactoryInfoQuery>(GetFactoryInfoDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetFactoryInfo');
},
GetProject(variables: GetProjectQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetProjectQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetProjectQuery>(GetProjectDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetProject');
},
@@ -2916,12 +3058,21 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
GetRecipientDonations(variables: GetRecipientDonationsQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetRecipientDonationsQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetRecipientDonationsQuery>(GetRecipientDonationsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetRecipientDonations');
},
GetRecipientRegistryInfo(variables: GetRecipientRegistryInfoQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetRecipientRegistryInfoQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetRecipientRegistryInfoQuery>(GetRecipientRegistryInfoDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetRecipientRegistryInfo');
},
GetRecipients(variables: GetRecipientsQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetRecipientsQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetRecipientsQuery>(GetRecipientsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetRecipients');
},
GetRoundInfo(variables: GetRoundInfoQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetRoundInfoQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetRoundInfoQuery>(GetRoundInfoDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetRoundInfo');
},
GetRounds(variables?: GetRoundsQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetRoundsQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetRoundsQuery>(GetRoundsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetRounds');
},
GetTokenInfo(variables: GetTokenInfoQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetTokenInfoQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetTokenInfoQuery>(GetTokenInfoDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetTokenInfo');
},
GetTotalContributed(variables: GetTotalContributedQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetTotalContributedQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetTotalContributedQuery>(GetTotalContributedDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'GetTotalContributed');
}
10 changes: 10 additions & 0 deletions vue-app/src/graphql/queries/GetFactoryInfo.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
query GetFactoryInfo($factoryAddress: ID!) {
fundingRoundFactory(id: $factoryAddress) {
nativeTokenInfo {
tokenAddress
symbol
decimals
}
contributorRegistryAddress
}
}
19 changes: 19 additions & 0 deletions vue-app/src/graphql/queries/GetRecipientRegistryInfo.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
query GetRecipientRegistryInfo($factoryAddress: ID!) {
fundingRoundFactory(id: $factoryAddress) {
recipientRegistry {
id
owner
baseDeposit
challengePeriodDuration
}
currentRound {
id
recipientRegistry {
id
owner
baseDeposit
challengePeriodDuration
}
}
}
}
19 changes: 19 additions & 0 deletions vue-app/src/graphql/queries/GetRoundInfo.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
query GetRoundInfo($fundingRoundAddress: ID!) {
fundingRound(id: $fundingRoundAddress) {
id
maci
nativeTokenInfo {
tokenAddress
symbol
decimals
}
recipientRegistryAddress
contributorRegistryAddress
voiceCreditFactor
isFinalized
isCancelled
contributorCount
totalSpent
matchingPoolSize
}
}
9 changes: 9 additions & 0 deletions vue-app/src/graphql/queries/GetTokenInfo.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
query GetTokenInfo($fundingRoundAddress: ID!) {
fundingRound(id: $fundingRoundAddress) {
nativeTokenInfo {
tokenAddress
symbol
decimals
}
}
}
4 changes: 4 additions & 0 deletions vue-app/src/locales/cn.json
Original file line number Diff line number Diff line change
@@ -762,6 +762,7 @@
"div1": "😢 没有找到符合您要求的项目。请尝试使用过滤器来缩小您的搜索范围"
},
"leaderboard": {
"no_round": "出错了!找不到筹款回合信息,如果问题持续请报告。",
"no_project": "这一回合没有项目",
"more": "更多",
"less": "简少",
@@ -774,6 +775,9 @@
},
"message": "总额是按照 quadradic funding 公式计算的总拨款金额。捐款是社区捐款。票数是每笔捐款的平方根的总和,并经过 MACI voice credit factor 和代币小数缩放"
},
"leaderboardSimpleView": {
"funded": "{tokenSymbol}"
},
"recipientRegistry": {
"h2": "项目注册表",
"th1": "项目",
4 changes: 4 additions & 0 deletions vue-app/src/locales/en.json
Original file line number Diff line number Diff line change
@@ -762,6 +762,7 @@
"div1": "😢 No projects match your search. Try using the filter to narrow down what you're looking for."
},
"leaderboard": {
"no_round": "Something went wrong! Round information not found. Please report if issue persists.",
"no_project": "No projects in this round",
"more": "more",
"less": "less",
@@ -774,6 +775,9 @@
},
"message": "The funding amount is the total amount awarded to each project, calculated using the quadratic funding formula. The donation amount is the community donation. Votes is the sum of square root of each donation, scaled by the MACI voice credit factor and the token decimals."
},
"leaderboardSimpleView": {
"funded": "{tokenSymbol} funded"
},
"recipientRegistry": {
"h2": "Recipient registry",
"th1": "Project",
4 changes: 4 additions & 0 deletions vue-app/src/locales/es.json
Original file line number Diff line number Diff line change
@@ -762,6 +762,7 @@
"div1": "😢 No projects match your search. Try using the filter to narrow down what you're looking for."
},
"leaderboard": {
"no_round": "Something went wrong! Round information not found. Please report if issue persists.",
"no_project": "No projects in this round",
"more": "more",
"less": "less",
@@ -774,6 +775,9 @@
},
"message": "The funding amount is the total amount awarded to each project, calculated using the quadratic funding formula. The donation amount is the community donation. Votes is the sum of square root of each donation, scaled by the MACI voice credit factor and the token decimals."
},
"leaderboardSimpleView": {
"funded": "{tokenSymbol} funded"
},
"recipientRegistry": {
"h2": "Recipient registry",
"th1": "Project",
30 changes: 15 additions & 15 deletions vue-app/src/store/actions.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ import {
hasContributorVoted,
} from '@/api/contributions'
import { loginUser, logoutUser } from '@/api/gun'
import { getRecipientRegistryAddress } from '@/api/projects'
import { RoundStatus } from '@/api/round'
import { Rounds } from '@/api/rounds'
import { storage } from '@/api/storage'
@@ -65,7 +64,7 @@ import {

// Utils
import { ensLookup } from '@/utils/accounts'
import { UserRegistryType, userRegistryType } from '@/api/core'
import { factory, UserRegistryType, userRegistryType } from '@/api/core'
import { BrightId, getBrightId } from '@/api/bright-id'
import { getFactoryInfo } from '@/api/factory'
import { getMACIFactoryInfo } from '@/api/maci-factory'
@@ -112,8 +111,12 @@ const actions = {
}

const round = await rounds.getRound(roundAddress)
const roundInfo = await round.getRoundInfo()
commit(SET_CURRENT_ROUND, roundInfo)
if (round) {
const roundInfo = await round.getRoundInfo()
commit(SET_CURRENT_ROUND, roundInfo)
} else {
commit(SET_CURRENT_ROUND, null)
}
},
async [LOAD_TALLY]({ commit, state }) {
const currentRound = state.currentRound
@@ -123,18 +126,15 @@ const actions = {
}
},
async [LOAD_RECIPIENT_REGISTRY_INFO]({ commit, state }) {
//TODO: update call to getRecipientRegistryAddress to take factory address as a parameter
const recipientRegistryAddress =
state.recipientRegistryAddress ||
(await getRecipientRegistryAddress(state.currentRoundAddress))
commit(SET_RECIPIENT_REGISTRY_ADDRESS, recipientRegistryAddress)

if (recipientRegistryAddress) {
const info = await getRegistryInfo(recipientRegistryAddress)
commit(SET_RECIPIENT_REGISTRY_INFO, info)
} else {
commit(SET_RECIPIENT_REGISTRY_INFO, null)
const info = await getRegistryInfo(factory.address)
if (!info) {
commit(SET_RECIPIENT_REGISTRY_ADDRESS, null)
return
}

const recipientRegistryAddress = info.registryAddress
commit(SET_RECIPIENT_REGISTRY_ADDRESS, recipientRegistryAddress)
commit(SET_RECIPIENT_REGISTRY_INFO, info)
},
async [LOAD_USER_INFO]({ commit, state }) {
if (!state.currentUser) {
19 changes: 14 additions & 5 deletions vue-app/src/views/Leaderboard.vue
Original file line number Diff line number Diff line change
@@ -2,9 +2,7 @@
<div>
<loader v-if="isLoading"></loader>
<div v-else>
<div class="info" v-if="!isRoundFinalized">
🤚 The round is not finalized, please check back later
</div>
<div class="info" v-if="!round">🤚 {{ $t('leaderboard.no_round') }}</div>
<div class="info" v-else-if="projects.length === 0">
😢 {{ $t('leaderboard.no_project') }}
</div>
@@ -72,14 +70,25 @@ export default class Leaderboard extends Vue {
const round = await this.$store.state.rounds.getRound(address)
this.round = await round.getRoundInfo()
this.projects = await round.getLeaderboardProjects()
if (round) {
this.round = await round.getRoundInfo()
const projects = await round.getLeaderboardProjects()
if (projects) {
this.projects = projects
}
}
}
async created() {
const { address } = this.$route.params
await this.loadRound(address)
// redirect to projects view if not finalized
if (!this.isRoundFinalized) {
this.$router.push({ name: 'round-project', params: this.$route.params })
}
this.isLoading = false
}
4 changes: 3 additions & 1 deletion vue-app/src/views/Project.vue
Original file line number Diff line number Diff line change
@@ -81,7 +81,9 @@ export default class ProjectView extends Vue {
const rounds = this.$store.state.rounds
const selectedRound = await rounds.getRound(this.roundAddress)
const project = await selectedRound.getProject(this.$route.params.id)
const project = selectedRound
? await selectedRound.getProject(this.$route.params.id)
: null
if (project === null || project.isHidden) {
// Project not found
14 changes: 8 additions & 6 deletions vue-app/src/views/ProjectList.vue
Original file line number Diff line number Diff line change
@@ -143,12 +143,14 @@ export default class ProjectList extends Vue {
}
const round = await this.$store.state.rounds.getRound(roundAddress)
const projects = await round.getProjects()
const visibleProjects = projects.filter((project) => {
return !project.isHidden && !project.isLocked
})
shuffleArray(visibleProjects)
this.projects = visibleProjects
if (round) {
const projects = await round.getProjects()
const visibleProjects = projects.filter((project) => {
return !project.isHidden && !project.isLocked
})
shuffleArray(visibleProjects)
this.projects = visibleProjects
}
}
formatIntegerPart(value: FixedNumber): string {