Skip to content

Commit

Permalink
Merge pull request #742 from clrfund/feat/no-subgraph
Browse files Browse the repository at this point in the history
Enable closed funding rounds to be displayed statically without subgraph query
yuetloo authored May 8, 2024

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
2 parents 65b4c96 + 716ccd3 commit 217049b
Showing 28 changed files with 527 additions and 255 deletions.
2 changes: 2 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
@@ -162,6 +162,8 @@ VITE_GOOGLE_SPREADSHEET_ID=
```

Note: if VITE_SUBGRAPH_URL is not set, the app will try to get the round information from the vue-app/src/rounds.json file which can be generated using the `hardhat export-round` command.

##### Setup the netlify functions

1. Set the `functions directory` to `vue-app/dist/lambda`.
139 changes: 11 additions & 128 deletions vue-app/src/App.vue
Original file line number Diff line number Diff line change
@@ -4,25 +4,12 @@
</metainfo>
<div id="app" class="wrapper">
<nav-bar :in-app="isInApp" />
<loader v-if="!appReady"></loader>
<div v-else id="content-container">
<div id="content-container">
<div v-if="isSidebarShown" id="sidebar" :class="`${showCartPanel ? 'desktop-l' : 'desktop'}`">
<round-information />
</div>
<div
id="content"
:class="{
padded: isVerifyStep || (isSidebarShown && !isCartPadding),
'mr-cart-open': showCartPanel && isSideCartShown,
'mr-cart-closed': !showCartPanel && isSideCartShown,
}"
>
<breadcrumbs v-if="showBreadCrumb" />
<router-view :key="route.path" />
</div>
<div v-if="isSideCartShown" id="cart" :class="`desktop ${showCartPanel ? 'open-cart' : 'closed-cart'}`">
<cart-widget />
</div>
<active-app v-if="isActiveApp" :is-sidebar-shown="isSidebarShown" :show-bread-crumb="showBreadCrumb" />
<static-app v-else :is-sidebar-shown="isSidebarShown" :show-bread-crumb="showBreadCrumb" />
</div>
<mobile-tabs v-if="isMobileTabsShown" />
</div>
@@ -32,33 +19,23 @@

<script setup lang="ts">
import NavBar from '@/components/NavBar.vue'
import CartWidget from '@/components/CartWidget.vue'
import MobileTabs from '@/components/MobileTabs.vue'
import Breadcrumbs from '@/components/Breadcrumbs.vue'
import ActiveApp from './components/ActiveApp.vue'
import StaticApp from './components/StaticApp.vue'
// @ts-ignore
import { ModalsContainer } from 'vue-final-modal'
import { getDefaultColorScheme } from '@/utils/theme'
import { getCurrentRound } from '@/api/round'
import { operator } from '@/api/core'
import { useAppStore, useUserStore, useRecipientStore, useWalletStore } from '@/stores'
import { operator, isActiveApp } from '@/api/core'
import { useAppStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { useRoute } from 'vue-router'
import { useMeta } from 'vue-meta'
import type { WalletUser } from '@/stores'
import type { BrowserProvider } from 'ethers'
const route = useRoute()
const appStore = useAppStore()
const { theme, showCartPanel, currentRound } = storeToRefs(appStore)
const userStore = useUserStore()
const { currentUser } = storeToRefs(userStore)
const wallet = useWalletStore()
const { user: walletUser } = storeToRefs(wallet)
const recipientStore = useRecipientStore()
const { theme, showCartPanel } = storeToRefs(appStore)
// https://stackoverflow.com/questions/71785473/how-to-use-vue-meta-with-vue3
// https://www.npmjs.com/package/vue-meta/v/3.0.0-alpha.7
@@ -80,18 +57,9 @@ useMeta(
}),
)
const intervals: { [key: string]: any } = {}
// state
const routeName = computed(() => route.name?.toString() || '')
const isUserAndRoundLoaded = computed(() => !!currentUser.value && !!currentRound.value)
const isInApp = computed(() => routeName.value !== 'landing')
const isVerifyStep = computed(() => routeName.value === 'verify-step')
const isSideCartShown = computed(() => isUserAndRoundLoaded.value && isSidebarShown.value && routeName.value !== 'cart')
const isCartPadding = computed(() => {
const routes = ['cart']
return routes.includes(routeName.value)
})
const isSidebarShown = computed(() => {
const excludedRoutes = [
'landing',
@@ -107,6 +75,7 @@ const isSidebarShown = computed(() => {
]
return !excludedRoutes.includes(routeName.value)
})
const isMobileTabsShown = computed(() => {
const excludedRoutes = [
'landing',
@@ -121,6 +90,7 @@ const isMobileTabsShown = computed(() => {
]
return !excludedRoutes.includes(routeName.value)
})
const showBreadCrumb = computed(() => {
const excludedRoutes = ['landing', 'join', 'join-step', 'transaction-success', 'verify', 'project-added', 'verified']
return !excludedRoutes.includes(routeName.value)
@@ -130,93 +100,6 @@ watch(theme, () => {
const savedTheme = theme.value
document.documentElement.setAttribute('data-theme', savedTheme || getDefaultColorScheme())
})
const appReady = ref(false)
function setupLoadIntervals() {
intervals.round = setInterval(() => {
appStore.loadRoundInfo()
}, 60 * 1000)
intervals.recipient = setInterval(async () => {
recipientStore.loadRecipientRegistryInfo()
}, 60 * 1000)
intervals.user = setInterval(() => {
userStore.loadUserInfo()
}, 60 * 1000)
}
onMounted(async () => {
try {
await wallet.reconnect()
} catch (err) {
/* eslint-disable-next-line no-console */
console.warn('Unable to reconnect wallet', err)
}
try {
const roundAddress = appStore.currentRoundAddress || (await getCurrentRound())
if (roundAddress) {
appStore.selectRound(roundAddress)
/* eslint-disable-next-line no-console */
console.log('roundAddress', roundAddress)
}
} catch (err) {
/* eslint-disable-next-line no-console */
console.warn('Failed to get current round:', err)
}
appReady.value = true
try {
await appStore.loadClrFundInfo()
await appStore.loadMACIFactoryInfo()
await appStore.loadRoundInfo()
await recipientStore.loadRecipientRegistryInfo()
appStore.isAppReady = true
setupLoadIntervals()
} catch (err) {
/* eslint-disable-next-line no-console */
console.warn('Failed to load application data:', err)
}
})
onBeforeUnmount(() => {
for (const interval of Object.keys(intervals)) {
clearInterval(intervals[interval])
}
})
watch(walletUser, async () => {
try {
if (walletUser.value) {
const user: WalletUser = {
chainId: walletUser.value.chainId,
walletAddress: walletUser.value.walletAddress,
web3Provider: walletUser.value.web3Provider as BrowserProvider,
}
// make sure factory is loaded
await appStore.loadClrFundInfo()
userStore.loginUser(user)
await userStore.loadUserInfo()
await userStore.loadBrightID()
} else {
await userStore.logoutUser()
}
} catch (err) {
/* eslint-disable-next-line no-console */
console.log('error', err)
}
})
watch(isUserAndRoundLoaded, async () => {
if (!isUserAndRoundLoaded.value) {
return
}
// load contribution when we get round information
await userStore.loadUserInfo()
})
</script>

<style lang="scss">
22 changes: 11 additions & 11 deletions vue-app/src/api/claims.ts
Original file line number Diff line number Diff line change
@@ -15,16 +15,16 @@ export async function getAllocatedAmount(
return allocatedAmount
}

export async function isFundsClaimed(
fundingRoundAddress: string,
recipientAddress: string,
recipientIndex: number,
): Promise<boolean> {
const data = await sdk.GetRecipientDonations({
fundingRoundAddress: fundingRoundAddress.toLowerCase(),
recipientAddress,
recipientIndex,
})
export async function isFundsClaimed(fundingRoundAddress: string, recipientIndex: number): Promise<boolean> {
let claimed = false

return !!data.donations.length
try {
const fundingRound = new Contract(fundingRoundAddress, FundingRound, provider)
const recipients = await fundingRound.recipients(recipientIndex)
claimed = !!recipients.fundsClaimed
} catch {
// recipient status is not available in older contract interface
claimed = true
}
return claimed
}
34 changes: 22 additions & 12 deletions vue-app/src/api/clrFund.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { clrfundContractAddress, clrFundContract } from './core'
import { clrfundContractAddress, clrFundContract, isActiveApp, provider } from './core'
import sdk from '@/graphql/sdk'
import { ERC20 } from './abi'
import { Contract } from 'ethers'

export interface ClrFund {
nativeTokenAddress: string
@@ -19,19 +21,27 @@ export async function getClrFundInfo() {
let recipientRegistryAddress = ''

try {
const data = await sdk.GetClrFundInfo({
clrFundAddress: clrfundContractAddress.toLowerCase(),
})
if (isActiveApp) {
const data = await sdk.GetClrFundInfo({
clrFundAddress: clrfundContractAddress.toLowerCase(),
})
const nativeTokenInfo = data.clrFund?.nativeTokenInfo
if (nativeTokenInfo) {
nativeTokenAddress = nativeTokenInfo.tokenAddress || ''
nativeTokenSymbol = nativeTokenInfo.symbol || ''
nativeTokenDecimals = Number(nativeTokenInfo.decimals) || 0
}

const nativeTokenInfo = data.clrFund?.nativeTokenInfo
if (nativeTokenInfo) {
nativeTokenAddress = nativeTokenInfo.tokenAddress || ''
nativeTokenSymbol = nativeTokenInfo.symbol || ''
nativeTokenDecimals = Number(nativeTokenInfo.decimals) || 0
userRegistryAddress = data.clrFund?.contributorRegistryAddress || ''
recipientRegistryAddress = data.clrFund?.recipientRegistryAddress || ''
} else {
nativeTokenAddress = await clrFundContract.nativeToken()
const nativeTokenContract = new Contract(nativeTokenAddress, ERC20, provider)
nativeTokenSymbol = await nativeTokenContract.symbol()
nativeTokenDecimals = await nativeTokenContract.decimals()
userRegistryAddress = await clrFundContract.userRegistry()
recipientRegistryAddress = await clrFundContract.recipientRegistry()
}

userRegistryAddress = data.clrFund?.contributorRegistryAddress || ''
recipientRegistryAddress = data.clrFund?.recipientRegistryAddress || ''
} catch (err) {
/* eslint-disable-next-line no-console */
console.error('Failed GetClrFundInfo', err)
19 changes: 10 additions & 9 deletions vue-app/src/api/contributions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Contract, FixedNumber, parseUnits, id } from 'ethers'
import { Contract, FixedNumber, parseUnits, id, AbiCoder } from 'ethers'
import type { TransactionResponse, Signer } from 'ethers'
import { Keypair, PubKey, PrivKey, Message, Command } from '@clrfund/common'

@@ -81,16 +81,17 @@ export async function getContributionAmount(fundingRoundAddress: string, contrib
if (!fundingRoundAddress) {
return 0n
}
const data = await sdk.GetContributionsAmount({
fundingRoundAddress: fundingRoundAddress.toLowerCase(),
contributorAddress: contributorAddress.toLowerCase(),
})

if (!data.contributions.length) {
const fundingRound = new Contract(fundingRoundAddress, FundingRound, provider)
try {
const abiCoder = AbiCoder.defaultAbiCoder()
const userData = abiCoder.encode(['address'], [contributorAddress])
const voiceCredits = await fundingRound.getVoiceCredits(contributorAddress, userData)
const voiceCreditFactor = await fundingRound.voiceCreditFactor()
return BigInt(voiceCredits) * BigInt(voiceCreditFactor)
} catch {
// ignore error as older contract does not expose the contributors info
return 0n
}

return BigInt(data.contributions[0].amount)
}

export async function getTotalContributed(fundingRoundAddress: string): Promise<{ count: number; amount: bigint }> {
2 changes: 2 additions & 0 deletions vue-app/src/api/core.ts
Original file line number Diff line number Diff line change
@@ -61,6 +61,8 @@ export const operator: string = import.meta.env.VITE_OPERATOR || 'Clr.fund'
export const SUBGRAPH_ENDPOINT =
import.meta.env.VITE_SUBGRAPH_URL || 'https://api.thegraph.com/subgraphs/name/clrfund/clrfund'

export const isActiveApp = Boolean(import.meta.env.VITE_SUBGRAPH_URL)

// application theme
export enum ThemeMode {
LIGHT = 'light',
2 changes: 1 addition & 1 deletion vue-app/src/api/leaderboard.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ type LeaderboardRecord = {
export async function getLeaderboardData(roundAddress: string, network: string) {
const rounds = leaderboardRounds as LeaderboardRecord[]

const lowercaseRoundAddress = (roundAddress || '').toLocaleLowerCase()
const lowercaseRoundAddress = (roundAddress || '').toLowerCase()
const lowercaseNetwork = (network || '').toLowerCase()
const found = rounds.find((r: LeaderboardRecord) => {
return r.address.toLowerCase() === lowercaseRoundAddress && r.network.toLowerCase() === lowercaseNetwork
11 changes: 7 additions & 4 deletions vue-app/src/api/maci-factory.ts
Original file line number Diff line number Diff line change
@@ -7,14 +7,17 @@ export interface MACIFactory {
maxRecipients: number
}

export async function getMACIFactoryInfo(): Promise<MACIFactory> {
export async function getMACIFactoryInfo(maxRecipients?: number): Promise<MACIFactory> {
const maciFactoryAddress = await clrFundContract.maciFactory()

const maciFactory = new Contract(maciFactoryAddress, MACIFactoryABI, provider)
const treeDepths = await maciFactory.treeDepths()
if (maxRecipients === undefined) {
const maciFactory = new Contract(maciFactoryAddress, MACIFactoryABI, provider)
const treeDepths = await maciFactory.treeDepths()
maxRecipients = 5 ** getNumber(treeDepths.voteOptionTreeDepth) - 1
}

return {
maciFactoryAddress,
maxRecipients: 5 ** getNumber(treeDepths.voteOptionTreeDepth) - 1,
maxRecipients,
}
}
Loading

0 comments on commit 217049b

Please sign in to comment.