Skip to content

Commit

Permalink
Merge pull request #771 from clrfund/merge/devconsea
Browse files Browse the repository at this point in the history
Export IPFS images to be loaded statically on the site
  • Loading branch information
yuetloo authored Jul 29, 2024
2 parents 674f55d + 172a191 commit 6826cf3
Show file tree
Hide file tree
Showing 84 changed files with 755 additions and 570 deletions.
1 change: 1 addition & 0 deletions contracts/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import './runners/finalize'
import './runners/claim'
import './runners/cancel'
import './runners/exportRound'
import './runners/exportImages'
import './runners/mergeAllocation'
import './runners/loadSimpleUsers'
import './runners/loadMerkleUsers'
Expand Down
83 changes: 83 additions & 0 deletions contracts/tasks/runners/exportImages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Export the project logo images in a ClrFund round.
*
* Sample usage:
* yarn hardhat export-images \
* --output-dir ../vue-apps/public/ipfs
* --gateway https://ipfs.io
* --round-file ../vue-app/src/rounds/arbitrum/0x4A2d90844EB9C815eF10dB0371726F0ceb2848B0.json
*
* Notes:
* 1) This script assumes the round has been exported using the `export-round` hardhat task
*/

import { task } from 'hardhat/config'
import { isPathExist, makeDirectory } from '../../utils/misc'
import { getIpfsContent } from '@clrfund/common'
import fs from 'fs'
import { dirname } from 'path'

/**
* Download the IPFS file with the ipfsHash to the output directory
* @param gateway IPFS gateway url
* @param ipfsHash IPFS hash of the file to download
* @param outputDir The directory to store the downloaded file
*/
async function download({
gateway,
ipfsHash,
outputDir,
}: {
gateway: string
ipfsHash: string
outputDir: string
}) {
if (!ipfsHash) return

const res = await getIpfsContent(ipfsHash, gateway)
if (res.hasBody()) {
console.log('Downloaded', ipfsHash)
const path = `${outputDir}/${ipfsHash}`
const folder = dirname(path)
if (!isPathExist(folder)) {
makeDirectory(folder)
}

fs.writeFileSync(path, res.body)
}
}

task('export-images', 'Export project logo images')
.addParam('outputDir', 'The output directory')
.addParam('roundFile', 'The exported funding round file path')
.addParam('gateway', 'The IPFS gateway url')
.setAction(async ({ outputDir, roundFile, gateway }) => {
console.log('Starting to download from ipfs')

const data = fs.readFileSync(roundFile, { encoding: 'utf-8' })
const round = JSON.parse(data)
const projects = round.projects
const images = projects.map((project: any) => {
const { bannerImageHash, thumbnailImageHash, imageHash } =
project.metadata
return { bannerImageHash, thumbnailImageHash, imageHash }
})

for (let i = 0; i < images.length; i++) {
await download({
gateway,
ipfsHash: images[i].bannerImageHash,
outputDir,
})
await download({
gateway,
ipfsHash: images[i].thumbnailImageHash,
outputDir,
})
await download({
gateway,
ipfsHash: images[i].imageHash,
outputDir,
})
}
})
6 changes: 6 additions & 0 deletions subgraph/config/clrfund-arbitrum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"network": "arbitrum-one",
"address": "0xc06349D95C30551Ea510bD5F35CfA2151499D60a",
"factoryStartBlock": 96912420,
"recipientRegistryStartBlock": 96912420
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
127 changes: 120 additions & 7 deletions vue-app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,20 @@
<div v-if="isSidebarShown" id="sidebar" :class="`${showCartPanel ? 'desktop-l' : 'desktop'}`">
<round-information />
</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
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>
</div>
<mobile-tabs v-if="isMobileTabsShown" />
</div>
Expand All @@ -20,22 +32,29 @@
<script setup lang="ts">
import NavBar from '@/components/NavBar.vue'
import MobileTabs from '@/components/MobileTabs.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 { operator, isActiveApp } from '@/api/core'
import { useAppStore } from '@/stores'
import { operator } from '@/api/core'
import { useAppStore, useRecipientStore, useUserStore, useWalletStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { useRoute } from 'vue-router'
import { useMeta } from 'vue-meta'
import { getCurrentRound } from './api/round'
import type { WalletUser } from '@/stores'
import type { BrowserProvider } from 'ethers'
const route = useRoute()
const appStore = useAppStore()
const { theme, showCartPanel } = storeToRefs(appStore)
const { theme, showCartPanel, currentRound } = storeToRefs(appStore)
const userStore = useUserStore()
const { currentUser } = storeToRefs(userStore)
const recipientStore = useRecipientStore()
const wallet = useWalletStore()
const { user: walletUser } = storeToRefs(wallet)
const appReady = ref(false)
// 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 Down Expand Up @@ -100,6 +119,100 @@ watch(theme, () => {
const savedTheme = theme.value
document.documentElement.setAttribute('data-theme', savedTheme || getDefaultColorScheme())
})
const intervals: { [key: string]: any } = {}
const isUserAndRoundLoaded = computed(() => !!currentUser.value && !!currentRound.value)
const isSideCartShown = computed(() => isUserAndRoundLoaded.value && isSidebarShown.value && routeName.value !== 'cart')
const isVerifyStep = computed(() => routeName.value === 'verify-step')
const isCartPadding = computed(() => {
const routes = ['cart']
return routes.includes(routeName.value)
})
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
45 changes: 21 additions & 24 deletions vue-app/src/api/clrFund.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clrfundContractAddress, clrFundContract, isActiveApp, provider } from './core'
import { clrfundContractAddress, clrFundContract, provider } from './core'
import sdk from '@/graphql/sdk'
import { ERC20 } from './abi'
import { Contract } from 'ethers'
Expand All @@ -21,34 +21,31 @@ export async function getClrFundInfo() {
let recipientRegistryAddress = ''

try {
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
}

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()
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
}

userRegistryAddress = data.clrFund?.contributorRegistryAddress || ''
recipientRegistryAddress = data.clrFund?.recipientRegistryAddress || ''
} catch (err) {
/* eslint-disable-next-line no-console */
console.error('Failed GetClrFundInfo', err)
nativeTokenAddress = await clrFundContract.nativeToken().catch(() => '')
const nativeTokenContract = new Contract(nativeTokenAddress, ERC20, provider)
nativeTokenSymbol = await nativeTokenContract.symbol().catch(() => '')
nativeTokenDecimals = await nativeTokenContract.decimals().catch(() => nativeTokenDecimals)
userRegistryAddress = await clrFundContract.userRegistry().catch(() => '')
recipientRegistryAddress = await clrFundContract.recipientRegistry().catch(() => '')
}

try {
matchingPool = await getMatchingFunds(nativeTokenAddress)
if (nativeTokenAddress) {
matchingPool = await getMatchingFunds(nativeTokenAddress)
}
} catch (err) {
/* eslint-disable-next-line no-console */
console.error('Failed to get matching pool', err)
Expand Down
41 changes: 29 additions & 12 deletions vue-app/src/api/contributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { clrFundContract, provider } from './core'
import type { Project } from './projects'
import sdk from '@/graphql/sdk'
import { Transaction } from '@/utils/transaction'
import type { GetContributorIndexQuery, GetContributorMessagesQuery, GetTotalContributedQuery } from '@/graphql/API'

export const DEFAULT_CONTRIBUTION_AMOUNT = 5
export const MAX_CONTRIBUTION_AMOUNT = 10000 // See FundingRound.sol
Expand Down Expand Up @@ -99,9 +100,14 @@ export async function getTotalContributed(fundingRoundAddress: string): Promise<
const nativeToken = new Contract(nativeTokenAddress, ERC20, provider)
const balance = await nativeToken.balanceOf(fundingRoundAddress)

const data = await sdk.GetTotalContributed({
fundingRoundAddress: fundingRoundAddress.toLowerCase(),
})
let data: GetTotalContributedQuery
try {
data = await sdk.GetTotalContributed({
fundingRoundAddress: fundingRoundAddress.toLowerCase(),
})
} catch {
return { count: 0, amount: 0n }
}

if (!data.fundingRound?.contributorCount) {
return { count: 0, amount: 0n }
Expand Down Expand Up @@ -149,10 +155,16 @@ export async function getContributorIndex(maciAddress: string, pubKey: PubKey):
if (!maciAddress) {
return null
}
const id = getPubKeyId(maciAddress, pubKey)
const data = await sdk.GetContributorIndex({
publicKeyId: id,
})

let data: GetContributorIndexQuery
try {
const id = getPubKeyId(maciAddress, pubKey)
data = await sdk.GetContributorIndex({
publicKeyId: id,
})
} catch {
return null
}

if (data.publicKeys.length === 0) {
return null
Expand Down Expand Up @@ -197,11 +209,16 @@ export async function getContributorMessages({
return []
}

const key = getPubKeyId(maciAddress, contributorKey.pubKey)
const result = await sdk.GetContributorMessages({
pubKey: key,
contributorAddress: contributorAddress.toLowerCase(),
})
let result: GetContributorMessagesQuery
try {
const key = getPubKeyId(maciAddress, contributorKey.pubKey)
result = await sdk.GetContributorMessages({
pubKey: key,
contributorAddress: contributorAddress.toLowerCase(),
})
} catch {
return []
}

if (!(result.messages && result.messages.length)) {
return []
Expand Down
5 changes: 1 addition & 4 deletions vue-app/src/api/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ if (!['simple', 'optimistic', 'kleros'].includes(recipientRegistryType as string
export const recipientRegistryPolicy = import.meta.env.VITE_RECIPIENT_REGISTRY_POLICY
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)
export const SUBGRAPH_ENDPOINT = import.meta.env.VITE_SUBGRAPH_URL || ''

// application theme
export enum ThemeMode {
Expand Down
Loading

0 comments on commit 6826cf3

Please sign in to comment.