diff --git a/.github/workflows/finalize-round.yml b/.github/workflows/finalize-round.yml index 27d9c3c39..4183c5a45 100644 --- a/.github/workflows/finalize-round.yml +++ b/.github/workflows/finalize-round.yml @@ -1,10 +1,19 @@ name: Finalize a test round -on: workflow_dispatch +on: + workflow_dispatch: + inputs: + branch_name: + description: 'Clrfund branch name' + required: true + default: 'develop' + subgraph_name: + description: 'Clrfund subgraph name' + required: true + default: 'yuetloo/clrfund-dev' env: NODE_VERSION: 16.x - SUBGRPAH_URL: "https://api.thegraph.com/subgraphs/name/clrfund/clrfund-arbitrum-goerli" NETWORK: "arbitrum-goerli" COORDINATOR_ETH_PK: ${{ secrets.ARBITRUM_GOERLI_COORDINATOR_WALLET_PRIVATE_KEY }} COORDINATOR_PK: ${{ secrets.ARBITRUM_GOERLI_COORDINATOR_MACI_PRIVATE_KEY }} @@ -33,6 +42,8 @@ jobs: cargo install zkutil --version 0.3.2 - name: Checkout source code uses: actions/checkout@v3 + with: + ref: ${{ github.event.inputs.branch_name }} - name: Download batch 64 params run: | ls -la $GITHUB_WORKSPACE @@ -42,6 +53,8 @@ jobs: yarn && yarn build - name: Run finalize scripts run: | + export SUBGRPAH_URL="https://api.thegraph.com/subgraphs/name/${{ github.event.inputs.subgraph_name }}" + echo $SUBGRAPH_URL export NODE_CONFIG=$(node -e "const snarkParamsPath=process.env.GITHUB_WORKSPACE + '/params'; console.log(JSON.stringify({ snarkParamsPath }));") export ROUND=$(curl -X POST -d '{"query":"{fundingRoundFactories {id currentRound {id maci}}}"}' $SUBGRPAH_URL) export FACTORY_ADDRESS=$(node -e 'console.log(JSON.parse(process.env.ROUND).data.fundingRoundFactories[0].id)') diff --git a/README.md b/README.md index 6ee604039..0ee3e2c32 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ In a future version, we plan to address this by routing ETH and token contributi - [Deployment](docs/deployment.md) - [Providing matching funds](docs/funding-source.md) - [How to tally votes and verify results](docs/tally-verify.md) +- [How to donate to projects](docs/howto-use-app.md) - [Running the subgraph](docs/subgraph.md) - [Sitemap](docs/sitemap.md) - [Website theme and customization](docs/theme.md) diff --git a/contracts/package.json b/contracts/package.json index c98a7d240..44c9083a3 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@clrfund/contracts", - "version": "4.2.3", + "version": "4.2.4", "license": "GPL-3.0", "scripts": { "hardhat": "hardhat", diff --git a/docs/howto-use-app.md b/docs/howto-use-app.md new file mode 100644 index 000000000..a48935190 --- /dev/null +++ b/docs/howto-use-app.md @@ -0,0 +1,62 @@ +# How to use the CLR.fund app + +## What you'll need +1. ETH + + > For testing, + - Get Goerli ETH from [Unitap](https://unitap.app/gas-tap) + - Use the [bridge](https://bridge.arbitrum.io/?l2ChainId=421613) to convert to Arbitrum Goerli ETH + +2. Contribution tokens + + > For testing, mint the DAI tokens using the `mint()` function from the [etherscan contract page](https://goerli.arbiscan.io//address/0x65bc8dd04808d99cf8aa6749f128d55c2051edde#writeContract) with the follow inputs: + + - usr (address): Your wallet address + - wad (uint256): How many DAI tokens to mint, e.g. 20000000000000000000 is 20 DAI + +Note, all the links provided below are from our test site for testing and illustration purposes only. For production, please replace https://clrfund-testnet.netlify.app with https://clr.fund + +## How to add your project as a fund recipient + + - Goto https://clrfund-testnet.netlify.app/#/join to submit your project + - Once your project is approved, it will show up on the project page and start receiving donations + - To check your project status, goto https://clrfund-testnet.netlify.app/#/recipients + +## How to contribute to projects +1. Register as a contributor + - Verify with [BrightID](https://www.brightid.org) + - Goto https://clrfund-testnet.netlify.app/#/verify to get registered + + + + +https://github.com/clrfund/monorepo/assets/18424940/c0a995f8-1835-4de3-b6ee-ccfbe32e52b4 + + + + +2. Contribute to projects + - Goto https://clrfund-testnet.netlify.app/#/projects to contribute to projects + + + +https://github.com/clrfund/monorepo/assets/18424940/9b50acbf-ca43-454f-be88-4c52058669ad + + + + +3. Reallocate contributions + - Goto https://clrfund-testnet.netlify.app/#/projects to change your contribution allocations + + + +https://github.com/clrfund/monorepo/assets/18424940/43188b69-9ece-4c2e-9ecc-babc1cdc09a3 + + + +## Bug report +You can report any issues with one of the following ways: +1. Report any issues in the `support` channel of the [CLR.fund discord](https://discord.gg/ZnsYPV6dCv) +2. Open a github issue on https://github.com/clrfund/monorepo/issues + + diff --git a/docs/tally-verify.md b/docs/tally-verify.md index 7c9bc9810..e62ecce4d 100644 --- a/docs/tally-verify.md +++ b/docs/tally-verify.md @@ -243,9 +243,10 @@ After finalizing the round, enable the leaderboard view in the vue-app by export ```sh cd contracts -yarn hardhat fetch-round --output-dir ../vue-app/src/rounds --network xdai --round-address +yarn hardhat fetch-round --output-dir ../vue-app/src/rounds --network xdai --round-address --operator ``` +3) Build and deploy the app diff --git a/docs/videos/contribute.mp4 b/docs/videos/contribute.mp4 new file mode 100644 index 000000000..af218a8c8 Binary files /dev/null and b/docs/videos/contribute.mp4 differ diff --git a/docs/videos/reallocate.mp4 b/docs/videos/reallocate.mp4 new file mode 100644 index 000000000..4d8aaecc8 Binary files /dev/null and b/docs/videos/reallocate.mp4 differ diff --git a/docs/videos/register.mp4 b/docs/videos/register.mp4 new file mode 100644 index 000000000..113b4ba3e Binary files /dev/null and b/docs/videos/register.mp4 differ diff --git a/subgraph/package.json b/subgraph/package.json index e56d96d15..57b851705 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -1,6 +1,6 @@ { "name": "@clrfund/subgraph", - "version": "4.2.3", + "version": "4.2.4", "repository": "https://github.com/clrfund/monorepo/subgraph", "keywords": [ "clr.fund", diff --git a/subgraph/src/OptimisticRecipientRegistryMapping.ts b/subgraph/src/OptimisticRecipientRegistryMapping.ts index feeb4e29f..24394f231 100644 --- a/subgraph/src/OptimisticRecipientRegistryMapping.ts +++ b/subgraph/src/OptimisticRecipientRegistryMapping.ts @@ -5,7 +5,7 @@ import { RequestSubmitted, } from '../generated/OptimisticRecipientRegistry/OptimisticRecipientRegistry' -import { Recipient } from '../generated/schema' +import { Recipient, RecipientRegistry } from '../generated/schema' // It is also possible to access smart contracts from mappings. For // example, the contract that has emitted the event can be connected to @@ -31,20 +31,48 @@ export function handleOwnershipTransferred(event: OwnershipTransferred): void { } export function handleRequestResolved(event: RequestResolved): void { + log.info('handleRequestResolved', []) + let recipientRegistryId = event.address.toHexString() + let recipientRegistry = RecipientRegistry.load(recipientRegistryId) + if (!recipientRegistry) { + log.warning( + 'handleRequestResolved - ignore unknown recipient registry {} hash {}', + [event.address.toHexString(), event.transaction.hash.toHex()] + ) + return + } - log.info('handleRequestResolved', []) let recipientId = event.params._recipientId.toHexString() let recipient = new Recipient(recipientId) - recipient.requestType = BigInt.fromI32(event.params._type).toString() - recipient.requester = event.transaction.from.toHexString() - recipient.submissionTime = event.params._timestamp.toString() - recipient.rejected = event.params._rejected - recipient.verified = !event.params._rejected recipient.recipientRegistry = recipientRegistryId - recipient.recipientIndex = event.params._recipientIndex recipient.requestResolvedHash = event.transaction.hash + // verified means the request is resolved + recipient.verified = true + + if (event.params._rejected) { + // this is a challengeRequest + if (event.params._type == 0) { + // reject add request + recipient.rejected = event.params._rejected + recipient.recipientIndex = event.params._recipientIndex + recipient.requester = event.transaction.from.toHexString() + recipient.submissionTime = event.params._timestamp.toString() + } else { + // reject delete request - revert request type back to 'Add' + // to clear the 'Pending removal' status + recipient.requestType = '0' + } + } else { + // this is a executeRequest + recipient.requestType = BigInt.fromI32(event.params._type).toString() + recipient.recipientIndex = event.params._recipientIndex + // reject the recipient if it was a 'delete recipient request' + recipient.rejected = event.params._rejected + recipient.requester = event.transaction.from.toHexString() + recipient.submissionTime = event.params._timestamp.toString() + } recipient.save() } @@ -53,20 +81,53 @@ export function handleRequestSubmitted(event: RequestSubmitted): void { log.info('handleRequestSubmitted', []) let recipientRegistryId = event.address.toHexString() - //TODO: create RecipientRegistry entity here if it does not exist. + let recipientRegistery = RecipientRegistry.load(recipientRegistryId) + if (!recipientRegistery) { + log.warning( + 'handleRequestSubmitted - ignore unknown recipient registry {} hash {}', + [event.address.toHexString(), event.transaction.hash.toHex()] + ) + return + } let recipientId = event.params._recipientId.toHexString() let recipient = new Recipient(recipientId) - recipient.recipientRegistry = recipientRegistryId - recipient.recipientAddress = event.params._recipient - recipient.requestType = BigInt.fromI32(event.params._type).toString() - recipient.requester = event.transaction.from.toHexString() recipient.submissionTime = event.params._timestamp.toString() - recipient.deposit = event.transaction.value - recipient.recipientMetadata = event.params._metadata - recipient.verified = false - recipient.requestSubmittedHash = event.transaction.hash + recipient.lastUpdatedAt = event.block.timestamp.toString() + + if (event.params._type == 0) { + // add recipient request + recipient.requestType = BigInt.fromI32(event.params._type).toString() + recipient.recipientIndex = null + recipient.recipientAddress = event.params._recipient + recipient.requester = event.transaction.from.toHexString() + recipient.deposit = event.transaction.value + recipient.recipientMetadata = event.params._metadata + recipient.requestSubmittedHash = event.transaction.hash + + // reset these fields in case the same recipient was added and removed + // in which case these fields could hold values for previous record + recipient.requestResolvedHash = null + recipient.verified = false + recipient.rejected = false + recipient.createdAt = event.block.timestamp.toString() + } else if (event.params._type == 1) { + // mark the record as pending delete + recipient.requestType = BigInt.fromI32(event.params._type).toString() + recipient.verified = false + log.info('handleRequestSubmitted - delete id {}', [recipientId]) + } else { + // don't know how to process this request + recipient.requestType = BigInt.fromI32(event.params._type).toString() + log.warning( + 'handleRequestSubmitted - ignore unknown type {} from txHash {}', + [ + BigInt.fromI32(event.params._type).toString(), + event.transaction.hash.toString(), + ] + ) + } recipient.save() } diff --git a/vue-app/.env.example b/vue-app/.env.example index 0b6569a2b..23cceb272 100644 --- a/vue-app/.env.example +++ b/vue-app/.env.example @@ -55,14 +55,12 @@ VITE_RECIPIENT_REGISTRY_TYPE=optimistic VITE_RECIPIENT_REGISTRY_POLICY=QmeygKjvrpidJeFHv6ywjUrj718nwtFQgCCPPR4r5nL87R -# Comma-separated list of IPFS hashes -VITE_EXTRA_ROUNDS= - # Operator of clr.fund instance VITE_OPERATOR= -# Index of first round to consider -VITE_FIRST_ROUND= +# Comma-separated list of cancelled round address that should not display on the app +# VITE_VOIDED_ROUNDS=0x123,0x456 +VITE_VOIDED_ROUNDS= # Default Language VITE_I18N_LOCALE= diff --git a/vue-app/package.json b/vue-app/package.json index bf830f0bd..aa4fea3a1 100644 --- a/vue-app/package.json +++ b/vue-app/package.json @@ -1,6 +1,6 @@ { "name": "@clrfund/vue-app", - "version": "4.2.3", + "version": "4.2.4", "private": true, "license": "GPL-3.0", "scripts": { diff --git a/vue-app/src/api/core.ts b/vue-app/src/api/core.ts index ca482cb51..703c758ae 100644 --- a/vue-app/src/api/core.ts +++ b/vue-app/src/api/core.ts @@ -108,3 +108,8 @@ export const recipientJoinDeadlineConfig = deadline?.isValid ? deadline : null // make sure walletconnect qrcode modal is not blocked by the Wallet Modal export const walletConnectZIndex = import.meta.env.VITE_WALLET_CONNECT_Z_INDEX || '1500' + +// use this to filter out rounds that should not be displayed on the app +export const voidedRounds = new Set( + (import.meta.env.VITE_VOIDED_ROUNDS || '').split(',').map(round => round.toLowerCase()), +) diff --git a/vue-app/src/api/projects.ts b/vue-app/src/api/projects.ts index c6a85de33..7bcc19164 100644 --- a/vue-app/src/api/projects.ts +++ b/vue-app/src/api/projects.ts @@ -1,6 +1,6 @@ import { BigNumber, Contract, Signer } from 'ethers' import type { TransactionResponse } from '@ethersproject/abstract-provider' -import { FundingRound } from './abi' +import { FundingRound, OptimisticRecipientRegistry } from './abi' import { factory, provider, recipientRegistryType, ipfsGatewayUrl } from './core' import SimpleRegistry from './recipient-registry-simple' @@ -9,6 +9,7 @@ import KlerosRegistry from './recipient-registry-kleros' import sdk from '@/graphql/sdk' import { getLeaderboardData } from '@/api/leaderboard' import type { RecipientApplicationData } from '@/api/types' +import { getEventArg } from '@/utils/contracts' export interface LeaderboardProject { id: string // Address or another ID depending on registry implementation @@ -164,28 +165,28 @@ export async function getProjectByIndex( } /** - * Check if the recipient with the submission hash exists in the subgraph + * Return the recipientId for the given transaction hash * @param transactionHash recipient submission hash - * @returns true if recipients with the submission hash was found + * @returns recipientId or null for not found */ -export async function recipientExists(transactionHash: string): Promise { - const data = await sdk.GetRecipientBySubmitHash({ transactionHash }) - return data.recipients.length > 0 -} - -/** - * Return the recipient for the given submission hash - * @param transactionHash recipient submission hash - * @returns project or null for not found - */ -export async function getRecipientBySubmitHash(transactionHash: string): Promise { +export async function getRecipientIdByHash(transactionHash: string): Promise { try { - const data = await sdk.GetRecipientBySubmitHash({ transactionHash }) - const exists = data.recipients.length > 0 - return exists ? OptimisticRegistry.decodeProject(data.recipients[0]) : null + const receipt = await provider.getTransactionReceipt(transactionHash) + + // should only have 1 event, just in case, return the first matching event + for (const log of receipt.logs) { + const registry = new Contract(log.address, OptimisticRecipientRegistry, provider) + try { + const recipientId = getEventArg(receipt, registry, 'RequestSubmitted', '_recipientId') + return recipientId + } catch { + // try next log + } + } } catch { return null } + return null } export function toLeaderboardProject(project: any): LeaderboardProject { diff --git a/vue-app/src/api/recipient-registry-optimistic.ts b/vue-app/src/api/recipient-registry-optimistic.ts index fcfd04a2c..6550092cf 100644 --- a/vue-app/src/api/recipient-registry-optimistic.ts +++ b/vue-app/src/api/recipient-registry-optimistic.ts @@ -52,6 +52,7 @@ export enum RequestStatus { Accepted = 'Accepted', Rejected = 'Rejected', Executed = 'Live', + PendingRemoval = 'Pending removal', Removed = 'Removed', } @@ -131,6 +132,10 @@ export async function getRequests(registryInfo: RegistryInfo, registryAddress: s if (recipient.verified) { request.status = requestType === RequestTypeCode.Removal ? RequestStatus.Removed : RequestStatus.Executed + } else { + if (requestType === RequestTypeCode.Removal) { + request.status = RequestStatus.PendingRemoval + } } // In case there are two requests submissions events, we always prioritize @@ -245,14 +250,19 @@ export async function getProjects(registryAddress: string, startTime?: number, e } if (requestType === RequestTypeCode.Removal) { - const removedAt = submissionTime - if (!startTime || removedAt <= startTime) { - // Start time not specified - // or recipient had been removed before start time - project.isHidden = true + if (recipient.verified) { + const removedAt = submissionTime + if (!startTime || removedAt <= startTime) { + // Start time not specified + // or recipient had been removed before start time + project.isHidden = true + } else { + // Disallow contributions to removed recipient, but don't hide it + project.isLocked = true + } } else { - // Disallow contributions to removed recipient, but don't hide it - project.isLocked = true + // project is not removed yet, keep the index so that it can still receive contributions + project.index = recipient.recipientIndex } } @@ -307,9 +317,14 @@ export async function getProject(recipientId: string, filter = true): Promise { if (fundingRoundAddress === '0x0000000000000000000000000000000000000000') { return null } - const rounds = await getRounds() - const roundIndex = rounds.findIndex(round => isSameAddress(round.address, fundingRoundAddress)) - if (roundIndex >= 0) { - return fundingRoundAddress - } - return null + return isVoidedRound(fundingRoundAddress) ? null : fundingRoundAddress } function toRoundInfo(data: any, network: string): RoundInfo { diff --git a/vue-app/src/api/rounds.ts b/vue-app/src/api/rounds.ts index 45e48998e..2d036752f 100644 --- a/vue-app/src/api/rounds.ts +++ b/vue-app/src/api/rounds.ts @@ -1,6 +1,6 @@ import sdk from '@/graphql/sdk' import extraRounds from '@/rounds/rounds.json' -import { chain } from './core' +import { chain, voidedRounds } from './core' export interface Round { index: number @@ -10,6 +10,10 @@ export interface Round { startTime: number } +export function isVoidedRound(address: string): boolean { + return voidedRounds.has(address.toLowerCase()) +} + function toRoundId({ network, address }: { network: string; address: string }): string { return `${network}-${address}`.toLowerCase() } @@ -17,8 +21,13 @@ function toRoundId({ network, address }: { network: string; address: string }): //TODO: update to take factory address as a parameter export async function getRounds(): Promise { //TODO: updateto pass factory address as a parameter, default to env. variable - //NOTE: why not instantiate the sdk here? - const data = await sdk.GetRounds() + + let data + try { + data = await sdk.GetRounds() + } catch { + return [] + } const rounds: Round[] = extraRounds.map(({ address, network, startTime }, index): Round => { return { index, address, network, hasLeaderboard: true, startTime } @@ -26,16 +35,14 @@ export async function getRounds(): Promise { const leaderboardRounds = new Set(rounds.map(r => toRoundId({ network: r.network || '', address: r.address }))) - const firstRound = Number(import.meta.env.VITE_FIRST_ROUND || 0) + for (const fundingRound of data.fundingRounds) { + const address = fundingRound.id - for (let roundIndex = 0; roundIndex < data.fundingRounds.length; roundIndex++) { - if (roundIndex < firstRound) { + if (isVoidedRound(address)) { // filter out cancelled or test rounds continue } - const fundingRound = data.fundingRounds[roundIndex] - const address = fundingRound.id const isLeaderboardRound = leaderboardRounds.has(toRoundId({ network: chain.label, address })) if (!isLeaderboardRound) { rounds.push({ diff --git a/vue-app/src/api/subgraph.ts b/vue-app/src/api/subgraph.ts new file mode 100644 index 000000000..db01532a2 --- /dev/null +++ b/vue-app/src/api/subgraph.ts @@ -0,0 +1,17 @@ +import type { TransactionReceipt } from '@ethersproject/abstract-provider' +import sdk from '@/graphql/sdk' + +/** + * Check if the transaction in the receipt exists in the subgraph + * @param receipt transaction receipt + * @returns true if the latest block number in subgraph is greater than the transaction block number + */ +export async function isTransactionInSubgraph(receipt: TransactionReceipt): Promise { + const data = await sdk.GetLatestBlockNumber() + + if (!data._meta?.block.number) { + return false + } + + return data._meta.block.number > receipt.blockNumber +} diff --git a/vue-app/src/utils/contracts.ts b/vue-app/src/utils/contracts.ts index e7cfeb67b..92a71c594 100644 --- a/vue-app/src/utils/contracts.ts +++ b/vue-app/src/utils/contracts.ts @@ -43,14 +43,14 @@ export async function waitForTransaction( */ export async function waitForTransactionAndCheck( pendingTransaction: Promise, - checkFn: (hash: string) => Promise, + checkFn: (receipt: TransactionReceipt) => Promise, onTransactionHash?: (hash: string) => void, ): Promise { const receipt = await waitForTransaction(pendingTransaction, onTransactionHash) return new Promise(resolve => { async function checkAndWait(depth = 0) { - if (await checkFn(receipt.transactionHash)) { + if (await checkFn(receipt)) { resolve(receipt) } else { if (depth > MAX_WAIT_DEPTH) { diff --git a/vue-app/src/views/JoinView.vue b/vue-app/src/views/JoinView.vue index 868187de2..57169cd16 100644 --- a/vue-app/src/views/JoinView.vue +++ b/vue-app/src/views/JoinView.vue @@ -717,7 +717,8 @@ import { useVuelidate } from '@vuelidate/core' import { required, requiredIf, email, maxLength, url, helpers } from '@vuelidate/validators' import type { RecipientApplicationData } from '@/api/types' import type { Project } from '@/api/projects' -import { recipientExists, formToProjectInterface } from '@/api/projects' +import { isTransactionInSubgraph } from '@/api/subgraph' +import { formToProjectInterface } from '@/api/projects' import { chain, showComplianceRequirement, isOptimisticRecipientRegistry } from '@/api/core' import { DateTime } from 'luxon' import { useRecipientStore, useAppStore, useUserStore } from '@/stores' @@ -948,8 +949,8 @@ async function addRecipient() { recipientRegistryInfo.value.deposit, currentUser.value.walletProvider.getSigner(), ), - hash => { - return isOptimisticRecipientRegistry ? recipientExists(hash) : Promise.resolve(true) + receipt => { + return isOptimisticRecipientRegistry ? isTransactionInSubgraph(receipt) : Promise.resolve(true) }, hash => (txHash.value = hash), ) diff --git a/vue-app/src/views/Landing.vue b/vue-app/src/views/Landing.vue index 2db1150f1..e34759a03 100644 --- a/vue-app/src/views/Landing.vue +++ b/vue-app/src/views/Landing.vue @@ -177,6 +177,7 @@ import ImageResponsive from '@/components/ImageResponsive.vue' import { useAppStore } from '@/stores' import { storeToRefs } from 'pinia' import { getAssetsUrl } from '@/utils/url' +import { isSameAddress } from '@/utils/accounts' const appStore = useAppStore() const { @@ -200,7 +201,7 @@ const leaderboardRoute = computed(() => { } const roundAddress = currentRoundAddress.value || '' - const leaderboard = leaderboardRounds.find(round => round.address === roundAddress) + const leaderboard = leaderboardRounds.find(round => isSameAddress(round.address, roundAddress)) return leaderboard ? { name: 'leaderboard', params: { network: leaderboard.network, address: roundAddress } } : null }) diff --git a/vue-app/src/views/ProjectAdded.vue b/vue-app/src/views/ProjectAdded.vue index abfbbf3ce..59fe6290c 100644 --- a/vue-app/src/views/ProjectAdded.vue +++ b/vue-app/src/views/ProjectAdded.vue @@ -49,7 +49,7 @@ import { useAppStore } from '@/stores' import { useRoute } from 'vue-router' import { storeToRefs } from 'pinia' import { isOptimisticRecipientRegistry } from '@/api/core' -import { getRecipientBySubmitHash } from '@/api/projects' +import { getRecipientIdByHash } from '@/api/projects' const route = useRoute() const appStore = useAppStore() @@ -59,9 +59,9 @@ const hash = computed(() => route.params.hash as string) const recipientId = ref('') onMounted(async () => { - const recipient = await getRecipientBySubmitHash(hash.value) - if (recipient) { - recipientId.value = recipient.id + const id = await getRecipientIdByHash(hash.value) + if (id) { + recipientId.value = id } }) diff --git a/vue-app/src/views/RecipientRegistry.vue b/vue-app/src/views/RecipientRegistry.vue index dfe789f77..3f7d94345 100644 --- a/vue-app/src/views/RecipientRegistry.vue +++ b/vue-app/src/views/RecipientRegistry.vue @@ -113,11 +113,7 @@ > -
+
@@ -196,8 +192,12 @@ function isExecuted(request: Request): boolean { return request.status === RequestStatus.Executed } +function isPendingRemoval(request: Request): boolean { + return request.status === RequestStatus.PendingRemoval +} + function hasProjectLink(request: Request): boolean { - return request.type === RequestType.Registration && request.status === RequestStatus.Executed + return isExecuted(request) || isPendingRemoval(request) } async function approve(request: Request): Promise {