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
  • Loading branch information
yuetloo authored May 8, 2024
2 parents 65b4c96 + 716ccd3 commit 217049b
Show file tree
Hide file tree
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
Expand Up @@ -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`.
Expand Down
139 changes: 11 additions & 128 deletions vue-app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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
Expand All @@ -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',
Expand All @@ -107,6 +75,7 @@ const isSidebarShown = computed(() => {
]
return !excludedRoutes.includes(routeName.value)
})
const isMobileTabsShown = computed(() => {
const excludedRoutes = [
'landing',
Expand All @@ -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)
Expand All @@ -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">
Expand Down
22 changes: 11 additions & 11 deletions vue-app/src/api/claims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
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'

Expand Down Expand Up @@ -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 }> {
Expand Down
2 changes: 2 additions & 0 deletions vue-app/src/api/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion vue-app/src/api/leaderboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions vue-app/src/api/maci-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.