diff --git a/contracts/scripts/deployRecipientRegistry.ts b/contracts/scripts/deployRecipientRegistry.ts index 0ed29b470..582bdbaf2 100644 --- a/contracts/scripts/deployRecipientRegistry.ts +++ b/contracts/scripts/deployRecipientRegistry.ts @@ -19,8 +19,13 @@ import { RecipientRegistryFactory } from '../utils/recipient-registry-factory' async function main() { const recipientRegistryType = process.env.RECIPIENT_REGISTRY_TYPE || 'simple' const fundingRoundFactoryAddress = process.env.FUNDING_ROUND_FACTORY_ADDRESS - const challengePeriodDuration = process.env.CHALLENGE_PERIOD_IN_SECONDS || 300 - const baseDeposit = process.env.BASE_DEPOSIT || UNIT.div(10).toString() + let challengePeriodDuration = '0' + let baseDeposit = '0' + + if (recipientRegistryType === 'optimistic') { + challengePeriodDuration = process.env.CHALLENGE_PERIOD_IN_SECONDS || '300' + baseDeposit = process.env.BASE_DEPOSIT || UNIT.div(10).toString() + } if (!fundingRoundFactoryAddress) { console.log('Environment variable FUNDING_ROUND_FACTORY_ADDRESS not set') @@ -35,9 +40,9 @@ async function main() { console.log('*******************') console.log(`Deploying a new ${recipientRegistryType} recipient registry!`) console.log(` challenge period in seconds: ${challengePeriodDuration}`) - console.log(` baseDeposit ${baseDeposit}`) - console.log(` fundingRoundFactoryAddress ${fundingRoundFactoryAddress}`) - console.log(` fundingRoundFactoryOwner ${factoryOwner}`) + console.log(` baseDeposit: ${baseDeposit}`) + console.log(` fundingRoundFactoryAddress: ${fundingRoundFactoryAddress}`) + console.log(` fundingRoundFactoryOwner: ${factoryOwner}`) const [deployer] = await ethers.getSigners() const recipientRegistry = await RecipientRegistryFactory.deploy( diff --git a/subgraph/src/MACIMapping.ts b/subgraph/src/MACIMapping.ts index fa76a8ef3..bf84663c3 100644 --- a/subgraph/src/MACIMapping.ts +++ b/subgraph/src/MACIMapping.ts @@ -20,7 +20,7 @@ import { FundingRound, Message, PublicKey } from '../generated/schema' // - contract.verifier(...) export function handlePublishMessage(event: PublishMessage): void { - const fundingRoundId = event.transaction.to.toHexString() + let fundingRoundId = event.transaction.to.toHexString() if (fundingRoundId == null) { log.error( 'Error: handlePublishMessage failed fundingRound not registered', @@ -28,7 +28,7 @@ export function handlePublishMessage(event: PublishMessage): void { ) return } - const fundingRound = FundingRound.load(fundingRoundId) + let fundingRound = FundingRound.load(fundingRoundId) if (fundingRound == null) { log.error( 'Error: handlePublishMessage failed fundingRound not registered', @@ -37,23 +37,23 @@ export function handlePublishMessage(event: PublishMessage): void { return } - const messageID = event.transaction.hash.toHexString() + let messageID = event.transaction.hash.toHexString() - const timestamp = event.block.timestamp.toString() - const message = new Message(messageID) + let timestamp = event.block.timestamp.toString() + let message = new Message(messageID) message.data = event.params._message.data message.iv = event.params._message.iv - const publicKeyId = event.transaction.from.toHexString() - const publicKey = PublicKey.load(publicKeyId) + let publicKeyId = event.transaction.from.toHexString() + let publicKey = PublicKey.load(publicKeyId) //NOTE: If the public keys aren't being tracked initialize them if (publicKey == null) { - const publicKey = new PublicKey(publicKeyId) + let publicKey = new PublicKey(publicKeyId) publicKey.x = event.params._encPubKey.x publicKey.y = event.params._encPubKey.y - const _messages = [messageID] as string[] + let _messages = [messageID] as string[] publicKey.messages = _messages publicKey.fundingRound = fundingRoundId @@ -68,12 +68,12 @@ export function handlePublishMessage(event: PublishMessage): void { } export function handleSignUp(event: SignUp): void { - const publicKeyId = event.transaction.from.toHexString() - const publicKey = PublicKey.load(publicKeyId) + let publicKeyId = event.transaction.from.toHexString() + let publicKey = PublicKey.load(publicKeyId) //NOTE: If the public keys aren't being tracked initialize them if (publicKey == null) { - const publicKey = new PublicKey(publicKeyId) + let publicKey = new PublicKey(publicKeyId) publicKey.x = event.params._userPubKey.x publicKey.y = event.params._userPubKey.y publicKey.stateIndex = event.params._stateIndex diff --git a/subgraph/src/RecipientMapping.ts b/subgraph/src/RecipientMapping.ts index 64b48ef0e..614bdb305 100644 --- a/subgraph/src/RecipientMapping.ts +++ b/subgraph/src/RecipientMapping.ts @@ -1,10 +1,12 @@ import { Recipient } from '../generated/schema' +export const RECIPIENT_REQUEST_TYPE_REGISTRATION = '0' +export const RECIPIENT_REQUEST_TYPE_REMOVAL = '1' + export function removeRecipient(id: string, timestamp: string): void { let recipient = Recipient.load(id) if (recipient) { - // TODO: should we hard delete the recipient record? - recipient.rejected = true + recipient.requestType = RECIPIENT_REQUEST_TYPE_REMOVAL recipient.lastUpdatedAt = timestamp recipient.save() } diff --git a/subgraph/src/recipientRegistry/KlerosRecipientRegistryMapping.ts b/subgraph/src/recipientRegistry/KlerosRecipientRegistryMapping.ts index 6f04d3792..b09b88964 100644 --- a/subgraph/src/recipientRegistry/KlerosRecipientRegistryMapping.ts +++ b/subgraph/src/recipientRegistry/KlerosRecipientRegistryMapping.ts @@ -4,13 +4,19 @@ import { } from '../../generated/templates/KlerosRecipientRegistry/KlerosRecipientRegistry' import { Recipient } from '../../generated/schema' -import { removeRecipient } from '../RecipientMapping' +import { + removeRecipient, + RECIPIENT_REQUEST_TYPE_REGISTRATION, +} from '../RecipientMapping' export function handleRecipientAdded(event: RecipientAdded): void { let recipientRegistryId = event.address.toHexString() let recipientId = event.params._tcrItemId.toHexString() let recipient = new Recipient(recipientId) + recipient.requestType = RECIPIENT_REQUEST_TYPE_REGISTRATION + // recipient was verified by kleros + recipient.verified = true recipient.recipientRegistry = recipientRegistryId recipient.createdAt = event.block.timestamp.toString() recipient.recipientIndex = event.params._index diff --git a/subgraph/src/recipientRegistry/RecipientRegistryType.ts b/subgraph/src/recipientRegistry/RecipientRegistryType.ts index 40a6a702d..29cc86204 100644 --- a/subgraph/src/recipientRegistry/RecipientRegistryType.ts +++ b/subgraph/src/recipientRegistry/RecipientRegistryType.ts @@ -11,7 +11,7 @@ export enum RecipientRegistryType { let registryTypeMap = new TypedMap() registryTypeMap.set('simple', RecipientRegistryType.Simple) -registryTypeMap.set('klerso', RecipientRegistryType.Kleros) +registryTypeMap.set('kleros', RecipientRegistryType.Kleros) registryTypeMap.set('optimistic', RecipientRegistryType.Optimistic) /** diff --git a/subgraph/src/recipientRegistry/SimpleRecipientRegistryMapping.ts b/subgraph/src/recipientRegistry/SimpleRecipientRegistryMapping.ts index 583f0826c..9bbc6e9e9 100644 --- a/subgraph/src/recipientRegistry/SimpleRecipientRegistryMapping.ts +++ b/subgraph/src/recipientRegistry/SimpleRecipientRegistryMapping.ts @@ -4,7 +4,10 @@ import { } from '../../generated/templates/SimpleRecipientRegistry/SimpleRecipientRegistry' import { Recipient } from '../../generated/schema' -import { removeRecipient } from '../RecipientMapping' +import { + removeRecipient, + RECIPIENT_REQUEST_TYPE_REGISTRATION, +} from '../RecipientMapping' export function handleRecipientAdded(event: RecipientAdded): void { let recipientRegistryId = event.address.toHexString() @@ -13,12 +16,15 @@ export function handleRecipientAdded(event: RecipientAdded): void { let recipient = new Recipient(recipientId) recipient.requester = event.transaction.from.toHexString() + recipient.requestType = RECIPIENT_REQUEST_TYPE_REGISTRATION recipient.recipientRegistry = recipientRegistryId recipient.recipientMetadata = event.params._metadata recipient.recipientIndex = event.params._index recipient.recipientAddress = event.params._recipient recipient.submissionTime = event.params._timestamp.toString() recipient.requestResolvedHash = event.transaction.hash + // recipients are verified as they are added by the admin + recipient.verified = true recipient.createdAt = event.block.timestamp.toString() recipient.save() diff --git a/vue-app/.env.example b/vue-app/.env.example index 367d59b19..c3b1bdd93 100644 --- a/vue-app/.env.example +++ b/vue-app/.env.example @@ -44,16 +44,16 @@ VUE_APP_GOOGLE_SPREADSHEET_ID= # metadata registry configurations # The networks where metadata is stored; as comma separated strings -# i.e. rinkeby,ropsten,mainnet +# i.e. arbitrum-rinkeby,ropsten,mainnet VUE_APP_METADATA_NETWORKS= # metadata registry subgraph url prefix # Add the network part (VUE_APP_METADATA_NETWORKS) to form the complete url -# i.e. https://api.thegraph.com/subgraphs/name/yuetloo/metadata- -METADATA_SUBGRAPH_URL_PREFIX= +# i.e. https://api.thegraph.com/subgraphs/name/clrfund/metadata- +VUE_APP_METADATA_SUBGRAPH_URL_PREFIX= # subgraph query batch size, default to 30 -QUERY_BATCH_SIZE= +VUE_APP_QUERY_BATCH_SIZE= # Select the sheet's name to write the data, by default 'Raw' GOOGLE_SHEET_NAME= diff --git a/vue-app/src/api/core.ts b/vue-app/src/api/core.ts index 242335ce3..244cf7838 100644 --- a/vue-app/src/api/core.ts +++ b/vue-app/src/api/core.ts @@ -70,7 +70,8 @@ export const METADATA_NETWORKS = process.env.VUE_APP_METADATA_NETWORKS ? process.env.VUE_APP_METADATA_NETWORKS.split(',') : ['rinkeby'] -export const QUERY_BATCH_SIZE = Number(process.env.QUERY_BATCH_SIZE) || 30 +export const QUERY_BATCH_SIZE = + Number(process.env.VUE_APP_QUERY_BATCH_SIZE) || 30 export const MAX_RETRIES = Number(process.env.VUE_APP_MAX_RETRIES) || 10 diff --git a/vue-app/src/api/projects.ts b/vue-app/src/api/projects.ts index 358e0a944..0ab238d6d 100644 --- a/vue-app/src/api/projects.ts +++ b/vue-app/src/api/projects.ts @@ -7,7 +7,6 @@ import { recipientRegistryType, } from './core' -import SimpleRegistry from './recipient-registry-simple' import KlerosRegistry from './recipient-registry-kleros' import RecipientRegistry from './recipient-registry' @@ -57,9 +56,7 @@ export async function getProjects( startTime?: number, endTime?: number ): Promise { - if (recipientRegistryType === 'simple') { - return await SimpleRegistry.getProjects(registryAddress, startTime, endTime) - } else if (recipientRegistryType === 'kleros') { + if (recipientRegistryType === 'kleros') { return await KlerosRegistry.getProjects(registryAddress, startTime, endTime) } else { return await RecipientRegistry.getProjects( @@ -74,9 +71,7 @@ export async function getProject( registryAddress: string, recipientId: string ): Promise { - if (recipientRegistryType === 'simple') { - return await SimpleRegistry.getProject(registryAddress, recipientId) - } else if (recipientRegistryType === 'kleros') { + if (recipientRegistryType === 'kleros') { return await KlerosRegistry.getProject(registryAddress, recipientId) } else { return await RecipientRegistry.getProject(recipientId) diff --git a/vue-app/src/api/recipient-registry-kleros.ts b/vue-app/src/api/recipient-registry-kleros.ts index 1c2285378..694ca736b 100644 --- a/vue-app/src/api/recipient-registry-kleros.ts +++ b/vue-app/src/api/recipient-registry-kleros.ts @@ -226,8 +226,8 @@ export function create(): RecipientRegistryInterface { removeProject, registerProject, rejectProject, - isRegistrationOpen: true, - requireRegistrationDeposit: true, + isSelfRegistration: false, //TODO: add support for self registration + requireRegistrationDeposit: false, } } diff --git a/vue-app/src/api/recipient-registry-optimistic.ts b/vue-app/src/api/recipient-registry-optimistic.ts index 0499caf2c..a8c7d6682 100644 --- a/vue-app/src/api/recipient-registry-optimistic.ts +++ b/vue-app/src/api/recipient-registry-optimistic.ts @@ -127,7 +127,7 @@ export function create(): RecipientRegistryInterface { registerProject, removeProject, rejectProject, - isRegistrationOpen: true, + isSelfRegistration: true, requireRegistrationDeposit: true, } } diff --git a/vue-app/src/api/recipient-registry-simple.ts b/vue-app/src/api/recipient-registry-simple.ts index 0e12dd0fa..fc835e4f5 100644 --- a/vue-app/src/api/recipient-registry-simple.ts +++ b/vue-app/src/api/recipient-registry-simple.ts @@ -1,9 +1,10 @@ import { BigNumber, Contract, Event, Signer } from 'ethers' -import { TransactionResponse } from '@ethersproject/abstract-provider' +import { ContractTransaction } from '@ethersproject/contracts' + import { isHexString } from '@ethersproject/bytes' import { SimpleRecipientRegistry } from './abi' -import { provider, ipfsGatewayUrl } from './core' +import { provider, ipfsGatewayUrl, chain } from './core' import { RecipientRegistryInterface } from './types' import { Project, toProjectInterface } from './projects' @@ -122,18 +123,37 @@ export function addRecipient( recipientData: any, _deposit: BigNumber, signer: Signer -): Promise { +): Promise { const registry = new Contract( registryAddress, SimpleRecipientRegistry, signer ) - const { address, ...metadata } = recipientData - return registry.addRecipient(address, JSON.stringify(metadata)) + const { id, fund } = recipientData + if (!id) { + throw new Error('Missing metadata id') + } + + const { currentChainReceivingAddress: address } = fund + if (!address) { + throw new Error(`Missing recipient address for the ${chain.name} network`) + } + + const json = { id } + return registry.addRecipient(address, JSON.stringify(json)) } -function removeProject() { - throw new Error('removeProject not implemented') +function removeProject( + registryAddress: string, + recipientId: string, + signer: Signer +): Promise { + const registry = new Contract( + registryAddress, + SimpleRecipientRegistry, + signer + ) + return registry.removeRecipient(recipientId) } function rejectProject() { @@ -150,7 +170,7 @@ export function create(): RecipientRegistryInterface { removeProject, registerProject, rejectProject, - isRegistrationOpen: false, + isSelfRegistration: false, requireRegistrationDeposit: false, } } diff --git a/vue-app/src/api/recipient-registry.ts b/vue-app/src/api/recipient-registry.ts index 4a27d63e7..6c53b1333 100644 --- a/vue-app/src/api/recipient-registry.ts +++ b/vue-app/src/api/recipient-registry.ts @@ -1,4 +1,4 @@ -import { BigNumber, Contract } from 'ethers' +import { BigNumber, Contract, Signer } from 'ethers' import sdk from '@/graphql/sdk' import { BaseRecipientRegistry } from './abi' import { @@ -23,7 +23,7 @@ import KlerosRegistry from './recipient-registry-kleros' import { isHexString } from '@ethersproject/bytes' import { Recipient } from '@/graphql/API' import { Project } from './projects' -import { Metadata } from './metadata' +import { Metadata, MetadataFormData } from './metadata' import { DateTime } from 'luxon' const registryLookup: Record = { @@ -74,7 +74,7 @@ export async function getRegistryInfo( listingPolicyUrl: `${ipfsGatewayUrl}/ipfs/${recipientRegistryPolicy}`, recipientCount: recipientCount.toNumber(), owner, - isRegistrationOpen: registry.isRegistrationOpen, + isSelfRegistration: registry.isSelfRegistration, requireRegistrationDeposit: registry.requireRegistrationDeposit, } } @@ -342,11 +342,19 @@ export async function getRequests( const requestType = Number(recipient.requestType) if (requestType === RequestTypeCode.Registration) { // Registration request - const { name, description, imageHash, thumbnailImageHash } = metadata + const { + name, + description, + imageHash, + bannerImageHash, + thumbnailImageHash, + } = metadata + metadata = { name, description, imageUrl: `${ipfsGatewayUrl}/ipfs/${imageHash}`, + bannerImageUrl: `${ipfsGatewayUrl}/ipfs/${bannerImageHash}`, thumbnailImageUrl: thumbnailImageHash ? `${ipfsGatewayUrl}/ipfs/${thumbnailImageHash}` : `${ipfsGatewayUrl}/ipfs/${imageHash}`, @@ -393,4 +401,19 @@ export async function getRequests( return Object.keys(requests).map((recipientId) => requests[recipientId]) } -export default { getProject, getProjects, projectExists } +export async function addRecipient( + registryAddress: string, + recipientMetadata: MetadataFormData, + deposit: BigNumber, + signer: Signer +) { + const registry = RecipientRegistry.create(recipientRegistryType) + return registry.addRecipient( + registryAddress, + recipientMetadata, + deposit, + signer + ) +} + +export default { addRecipient, getProject, getProjects, projectExists } diff --git a/vue-app/src/api/types.ts b/vue-app/src/api/types.ts index 6649bb368..0f5a57927 100644 --- a/vue-app/src/api/types.ts +++ b/vue-app/src/api/types.ts @@ -8,7 +8,7 @@ export interface RegistryInfo { listingPolicyUrl: string recipientCount: number owner: string - isRegistrationOpen: boolean + isSelfRegistration: boolean requireRegistrationDeposit: boolean } @@ -17,7 +17,7 @@ export interface RecipientRegistryInterface { registerProject: Function rejectProject: Function removeProject: Function - isRegistrationOpen?: boolean + isSelfRegistration?: boolean requireRegistrationDeposit?: boolean } @@ -43,6 +43,7 @@ interface RecipientMetadata { name: string description: string imageUrl: string + thumbnailImageUrl: string } export interface RecipientRegistryRequest { diff --git a/vue-app/src/components/CriteriaModal.vue b/vue-app/src/components/CriteriaModal.vue index f903d132b..9662a47a4 100644 --- a/vue-app/src/components/CriteriaModal.vue +++ b/vue-app/src/components/CriteriaModal.vue @@ -35,7 +35,7 @@ Add project diff --git a/vue-app/src/components/NavBar.vue b/vue-app/src/components/NavBar.vue index d9bee75a9..a9bf16244 100644 --- a/vue-app/src/components/NavBar.vue +++ b/vue-app/src/components/NavBar.vue @@ -71,6 +71,7 @@ export default class NavBar extends Vue { { to: '/about/how-it-works', text: 'How it works', emoji: 'โš™๏ธ' }, { to: '/about/maci', text: 'Bribery protection', emoji: '๐Ÿค‘' }, { to: '/about/sybil-resistance', text: 'Sybil resistance', emoji: '๐Ÿ‘ค' }, + { to: '/about/layer-2', text: 'Layer 2', emoji: '๐Ÿš€' }, { to: 'https://github.com/clrfund/monorepo/', text: 'Code', @@ -79,27 +80,21 @@ export default class NavBar extends Vue { { to: '/recipients', text: 'Recipients', - emoji: '๐Ÿš€', + emoji: '๐Ÿ’Ž', }, { to: '/metadata', text: 'Metadata', emoji: '๐Ÿ“ƒ', }, - ] + ].filter((item) => { + return chain.isLayer2 || item.text !== 'Layer 2' + }) created() { const savedTheme = lsGet(this.themeKey) const theme = isValidTheme(savedTheme) ? savedTheme : getOsColorScheme() this.$store.commit(TOGGLE_THEME, theme) - - if (chain.isLayer2) { - this.dropdownItems.splice(-1, 0, { - to: '/about/layer-2', - text: 'Layer 2', - emoji: '๐Ÿš€', - }) - } } closeHelpDropdown(): void { diff --git a/vue-app/src/components/RecipientSubmissionWidget.vue b/vue-app/src/components/RecipientSubmissionWidget.vue index e9355d087..e4f88f5c0 100644 --- a/vue-app/src/components/RecipientSubmissionWidget.vue +++ b/vue-app/src/components/RecipientSubmissionWidget.vue @@ -1,5 +1,5 @@ @@ -71,6 +73,11 @@ export default class TransactionModal extends Vue { padding: 1.5rem; } +.btn-row { + display: flex; + justify-content: center; +} + .close-btn { margin-top: $modal-space; } diff --git a/vue-app/src/store/getters.ts b/vue-app/src/store/getters.ts index a9bd06562..323072821 100644 --- a/vue-app/src/store/getters.ts +++ b/vue-app/src/store/getters.ts @@ -79,13 +79,6 @@ const getters = { getters.recipientSpacesRemaining < 20 ) }, - isRoundBufferPhase: (state: RootState, getters): boolean => { - return ( - !!state.currentRound && - !getters.isJoinPhase && - !hasDateElapsed(state.currentRound.signUpDeadline) - ) - }, isRoundContributionPhase: (state: RootState): boolean => { return ( !!state.currentRound && @@ -225,15 +218,31 @@ const getters = { return nativeTokenDecimals }, - isRecipientRegistrationOpen: (state: RootState): boolean => { - return !!state.recipientRegistryInfo?.isRegistrationOpen + isSelfRegistration: (state: RootState): boolean => { + return !!state.recipientRegistryInfo?.isSelfRegistration }, requireRegistrationDeposit: (state: RootState): boolean => { return !!state.recipientRegistryInfo?.requireRegistrationDeposit }, - addProjectUrl: (): string => { - return '/join/project' + canAddProject: (_, getters): boolean => { + const { + requireRegistrationDeposit, + isRecipientRegistryOwner, + isRecipientRegistryFull, + isRoundJoinPhase, + } = getters + + return ( + (requireRegistrationDeposit || isRecipientRegistryOwner) && + !isRecipientRegistryFull && + isRoundJoinPhase + ) }, + joinFormUrl: + () => + (metadataId?: string): string => { + return metadataId ? `/join/metadata/${metadataId}` : '/join/project' + }, maxRecipients: (state: RootState): number | undefined => { const { currentRound, maciFactory } = state if (currentRound) { diff --git a/vue-app/src/views/AboutHowItWorks.vue b/vue-app/src/views/AboutHowItWorks.vue index 091e0cce5..b2481f0ed 100644 --- a/vue-app/src/views/AboutHowItWorks.vue +++ b/vue-app/src/views/AboutHowItWorks.vue @@ -83,7 +83,7 @@

- If you dont contribute in the contribution phase, the round is over for + If you don't contribute in the contribution phase, the round is over for you once this phase ends.

diff --git a/vue-app/src/views/AboutRecipients.vue b/vue-app/src/views/AboutRecipients.vue index a47a9b635..f80bd1b13 100644 --- a/vue-app/src/views/AboutRecipients.vue +++ b/vue-app/src/views/AboutRecipients.vue @@ -37,9 +37,14 @@

Register your project

- In order to participate in a funding round as a project, you'll need to - submit an application to join the recipient registry (via an on-chain - transaction). + In order to participate in a funding round as a project, + you'll need to submit an application to join the recipient registry + (via an on-chain transaction)you'll need to contact the round coordinator to submit an + application.

MACI, our anti-bribery tech, currently limits the amount of projects @@ -58,12 +63,19 @@ Click "See round criteria" and familiarize yourself with the criteria for projects. +

  • + Once you're familiar with the criteria and you're sure your project + meets them, + click "Add project." You'll see a series of forms to fill out asking + for more information about your project + contact the round coordinator to add your project to the recipient + registry. +