From 3597817c07bccde1102bf7739506760c92e6728a Mon Sep 17 00:00:00 2001 From: Mahdi Date: Tue, 29 Oct 2024 17:02:50 +0330 Subject: [PATCH 1/2] add attestation functionality to budget vote --- .../[category]/attestation/index.ts | 272 +++++++++++++++++ .../[category]/attestation/utils.ts | 170 +++++++++++ app/allocation/[category]/page.tsx | 277 +----------------- app/allocation/page.tsx | 70 ++++- app/allocation/utils.ts | 1 + app/comparison/utils/data-fetching/ranking.ts | 3 +- 6 files changed, 516 insertions(+), 277 deletions(-) create mode 100644 app/allocation/[category]/attestation/index.ts create mode 100644 app/allocation/[category]/attestation/utils.ts diff --git a/app/allocation/[category]/attestation/index.ts b/app/allocation/[category]/attestation/index.ts new file mode 100644 index 0000000..ececca1 --- /dev/null +++ b/app/allocation/[category]/attestation/index.ts @@ -0,0 +1,272 @@ +import { EAS, SchemaRegistry, SchemaEncoder } from '@ethereum-attestation-service/eas-sdk'; +import { Signer } from 'ethers'; +import { Wallet } from 'thirdweb/wallets'; +import { activeChain } from '@/app/lib/constants'; +import { axiosInstance } from '@/app/utils/axiosInstance'; +import { EASNetworks, SCHEMA_UID, convertRankingToAttestationFormat, generateRandomString, getPrevAttestationIds } from './utils'; + +export enum AttestationState { + Initial, + Loading, + Success, + Error, +} + +type AttestFunc = { + ranking: { + id: number + name: string + ranking: { RF6Id: string, share: number }[] + } + signer: Signer + wallet: Wallet + setAttestationState: (state: AttestationState) => void + setAttestationLink: (link: string) => void +} + +export const attest = async ({ ranking, signer, wallet, setAttestationState, setAttestationLink }: AttestFunc) => { + // const localStorageTag = process.env.NEXT_PUBLIC_LOCAL_STORAGE_TAG!; + // const identityString = localStorage.getItem(localStorageTag); + + // if (!identityString) { + // console.error('Identity string is missing!'); + // router.push('/'); + // return; + // } + + // const identity = new Identity(identityString); + + if (!ranking) return; + + setAttestationState(AttestationState.Loading); + + const chainId = activeChain.id; + const easConfig = EASNetworks[chainId]; + const address = wallet?.getAccount()?.address; + + if (!easConfig) { + console.error('no eas config'); + return; + } + if (!signer || !address) { + console.error('signer', signer, 'address', address); + return; + } + + const eas = new EAS(easConfig.EASDeployment); + const schemaRegistry = new SchemaRegistry(easConfig.SchemaRegistry); + + eas.connect(signer as any); + schemaRegistry.connect(signer as any); + const schema = await schemaRegistry.getSchema({ uid: SCHEMA_UID }); + const schemaEncoder = new SchemaEncoder(schema.schema); + // let proof = ['']; + // setProgress(ProgressState.Creating); + try { + const item = await convertRankingToAttestationFormat( + ranking.ranking.map(({ RF6Id, share }) => ({ RF6Id, share })), + ranking.name, + // comment, + ); + + const schemaData = [ + { name: 'listName', type: 'string', value: item.listName }, + { + name: 'listMetadataPtr', + type: 'string', + value: item.listMetadataPtr, + }, + ]; + + // const signalData = { + // category: item.listName, + // value: item.listMetadataPtr, + // }; + + // // generate proof of vote + // const groupId = process.env.NEXT_PUBLIC_BANDADA_GROUP_ID!; + // const users = await getMembersGroup(groupId); + // if (users && identityString !== '{}') { + // const bandadaGroup = await getGroup(groupId); + // let treeDepth = 16; + // if (bandadaGroup === null) { + // console.log('The Bandada group does not exist:', groupId); + // } + // else { + // treeDepth = bandadaGroup.treeDepth; + // } + // const group = new Group(groupId, treeDepth, users); + // console.log('going to encode signalData: '); + // console.log(signalData); + // const signal = toBigInt( + // encodeBytes32String(signalData.toString()), + // ).toString(); + // const { + // proof: tempProof, + // merkleTreeRoot, + // nullifierHash, + // } = await generateProof(identity, group, groupId, signal); + // proof = tempProof; + // console.log('generated proof of vote: ', proof); + + // const { data: currentMerkleRoot, error: errorRootHistory } + // = await supabase + // .from('root_history') + // .select() + // .order('created_at', { ascending: false }) + // .limit(1); + + // if (errorRootHistory) { + // console.log(errorRootHistory); + // } + + // if (!currentMerkleRoot) { + // console.error('Wrong currentMerkleRoot'); + // } + + // if ( + // currentMerkleRoot == null + // || merkleTreeRoot !== currentMerkleRoot[0].root + // ) { + // // compare merkle tree roots + // const { + // data: dataMerkleTreeRoot, + // error: errorMerkleTreeRoot, + // } = await supabase + // .from('root_history') + // .select() + // .eq('root', merkleTreeRoot); + + // if (errorMerkleTreeRoot) { + // console.log(errorMerkleTreeRoot); + // } + + // console.log('merkleTreeRoot: ', merkleTreeRoot); + // console.log('dataMerkleTreeRoot: ', dataMerkleTreeRoot); + + // if (!dataMerkleTreeRoot) { + // console.error('Wrong dataMerkleTreeRoot'); + // } + // else if (dataMerkleTreeRoot.length === 0) { + // console.log('Merkle Root is not part of the group'); + // } + + // console.log('dataMerkleTreeRoot', dataMerkleTreeRoot); + // const merkleTreeRootDuration + // = bandadaGroup?.fingerprintDuration ?? 0; + + // if ( + // dataMerkleTreeRoot + // && Date.now() + // > Date.parse(dataMerkleTreeRoot[0].created_at) + // + merkleTreeRootDuration + // ) { + // console.log('Merkle Tree Root is expired'); + // } + // } + + // const { data: nullifier, error: errorNullifierHash } + // = await supabase + // .from('nullifier_hash') + // .select('nullifier') + // .eq('nullifier', nullifierHash); + + // if (errorNullifierHash) { + // console.log(errorNullifierHash); + // } + + // if (!nullifier) { + // console.log('Wrong nullifier'); + // } + // else if (nullifier.length > 0) { + // console.log('You are using the same nullifier twice'); + // } + + // const { error: errorNullifier } = await supabase + // .from('nullifier_hash') + // .insert([{ nullifier: nullifierHash }]); + + // if (errorNullifier) { + // console.error(errorNullifier); + // } + + // const { data: dataFeedback, error: errorFeedback } + // = await supabase + // .from('feedback') + // .insert([{ signal: schemaData }]) + // .select() + // .order('created_at', { ascending: false }); + + // if (errorFeedback) { + // console.error(errorFeedback); + // } + + // if (!dataFeedback) { + // console.error('Wrong dataFeedback'); + // } + + // // TODO everything is good so add the proof in attestation : Mahdi + // } + + const schemaDataWithProof = [ + ...schemaData, + { + name: 'proof', + type: 'string[]', + value: [generateRandomString(20)], + }, + ]; + + console.log('sdwp', schemaDataWithProof); + const encodedData = schemaEncoder.encodeData(schemaDataWithProof); + + const prevAttestations = await getPrevAttestationIds( + address, + SCHEMA_UID, + easConfig.gqlUrl, + ranking.name, + ); + + if (prevAttestations.length > 0) { + for (const id of prevAttestations) { + const revokedTransactions = await eas.revoke({ + schema: SCHEMA_UID, + data: { uid: id }, + }); + await revokedTransactions.wait(); + } + } + + const tx = await eas.attest({ + schema: SCHEMA_UID, + data: { + data: encodedData, + recipient: address, + revocable: true, + }, + }); + + const newAttestationUID = await tx.wait(); + + // posthog.capture('Attested', { + // attestedCategory: category?.data.collection?.name, + // }); + + console.log('attestaion id', newAttestationUID); + // await finishCollections(collectionId); + + const attestationLink = `${easConfig.explorer}/attestation/view/${newAttestationUID}`; + + await axiosInstance.post('/flow/report-attest', { + collectionId: ranking.id, + attestationId: attestationLink, + }); + + setAttestationState(AttestationState.Success); + setAttestationLink(attestationLink); + } + catch (e) { + console.error('error on sending tx:', e); + setAttestationState(AttestationState.Error); + } +}; diff --git a/app/allocation/[category]/attestation/utils.ts b/app/allocation/[category]/attestation/utils.ts new file mode 100644 index 0000000..87d43cc --- /dev/null +++ b/app/allocation/[category]/attestation/utils.ts @@ -0,0 +1,170 @@ +import { useState, useEffect } from 'react'; +import { optimismSepolia } from 'thirdweb/chains'; +import { type Address } from 'viem'; +import { useActiveWallet } from 'thirdweb/react'; +import { ethers6Adapter } from 'thirdweb/adapters/ethers6'; +import { axiosInstance } from '@/app/utils/axiosInstance'; +import { client } from '@/app/utils/wallet/provider'; +import { activeChain } from '@/app/lib/constants'; + +export type EASConfig = { + EASDeployment: Address + SchemaRegistry: Address +}; + +export function generateRandomString(length: number): string { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * characters.length); + result += characters.charAt(randomIndex); + } + + return result; +} + +type Signer = Awaited>; + +export function useSigner() { + const wallet = useActiveWallet(); + + const [signer, setSigner] = useState(); + + useEffect(() => { + async function getSigner() { + console.log('In useSigner'); + if (!wallet) return; + + console.log('wallet', wallet); + const account = wallet.getAccount(); + + console.log('account', account); + if (!account) return; + + const ethersSigner = await ethers6Adapter.signer.toEthers({ + client, + chain: activeChain, + account, + }); + + setSigner(ethersSigner); + } + + getSigner(); + }, [wallet]); + return signer; +} + +interface Config extends EASConfig { + explorer: string + gqlUrl: string +} + +export const EASNetworks: Record = { + // Optimism + 10: { + EASDeployment: '0x4200000000000000000000000000000000000021', + SchemaRegistry: '0x4200000000000000000000000000000000000020', + explorer: 'https://optimism.easscan.org', + gqlUrl: 'https://optimism.easscan.org/graphql', + }, + // Optimism Sepolia + [optimismSepolia.id]: { + EASDeployment: '0x4200000000000000000000000000000000000021', + SchemaRegistry: '0x4200000000000000000000000000000000000020', + explorer: 'https://optimism-sepolia.easscan.org', + gqlUrl: 'https://optimism-sepolia.easscan.org/graphql', + }, +}; + +export const SCHEMA_UID = process.env.NEXT_PUBLIC_EAS_SCHEMA_UID || '0x8c12749f56c911dbc13a6a6685b6964c3ea03023f246137e9c53ba97974e4b75'; + +export const pinFileToIPFS = async (list: object) => { + try { + const res = await axiosInstance.post('/flow/pinJSONToIPFS', { + json: list, + }); + return res.data; + } + catch (error) { + console.log(error); + } +}; + +type Ranking = { + RF6Id: string + share: number +} + +export const convertRankingToAttestationFormat = async ( + ranking: Ranking[], + collectionName: string, + // collectionDescription: string, +) => { + const obj = { + // listDescription: `${collectionDescription}`, + impactEvaluationLink: 'https://pairwise.vote', + impactCategory: ['PAIRWISE'], + impactEvaluationDescription: `This list has been carefully curated and ranked by Pairwise among projects related to ${collectionName}.`, + listContent: ranking.map(item => ({ + RPGF3_Application_UID: item.RF6Id, + allocation: item.share, + })), + }; + + const listName = collectionName; + const listMetadataPtrType = 1; + + const url = await pinFileToIPFS(obj); + + return { + listName, + listMetadataPtrType, + listMetadataPtr: `https://giveth.mypinata.cloud/ipfs/${url}`, + }; +}; + +export const getPrevAttestationIds = async ( + address: string, + schemaId: string, + gqlUrl: string, + collectionName: string, +): Promise => { + const query = ` + query PrevAttestationsQuery($where: AttestationWhereInput) { + groupByAttestation( + where: $where, + by: [id, decodedDataJson] + ) { + id + decodedDataJson + } + } +`; + + const res = await axiosInstance.post(gqlUrl, { + query: query, + operationName: 'PrevAttestationsQuery', + variables: { + where: { + revocable: { equals: true }, + revoked: { equals: false }, + schemaId: { + equals: schemaId, + }, + attester: { equals: address }, + }, + by: null, + }, + }); + + const temp = res.data.data.groupByAttestation.map((item: any) => ({ + ...item, + data: JSON.parse(item.decodedDataJson), + })); + + return temp + .filter((item: any) => item.data[0].value.value === collectionName) + .map((item: any) => item.id); +}; diff --git a/app/allocation/[category]/page.tsx b/app/allocation/[category]/page.tsx index 7e67e6d..e508f57 100644 --- a/app/allocation/[category]/page.tsx +++ b/app/allocation/[category]/page.tsx @@ -4,11 +4,6 @@ import { useCallback, useEffect, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import debounce from 'lodash.debounce'; import { useActiveWallet } from 'thirdweb/react'; -import { - EAS, - SchemaEncoder, - SchemaRegistry, -} from '@ethereum-attestation-service/eas-sdk'; import RankingRow from './components/RankingRow'; import HeaderRF6 from '../../comparison/card/Header-RF6'; import Spinner from '@/app/components/Spinner'; @@ -33,13 +28,12 @@ import { IProjectRanking } from '@/app/comparison/utils/types'; import { ArrowLeft2Icon } from '@/public/assets/icon-components/ArrowLeft2'; import { ArrowRightIcon } from '@/public/assets/icon-components/ArrowRight'; import { modifyPercentage, RankItem } from '../utils'; -import { activeChain } from '@/app/lib/constants'; -import { convertRankingToAttestationFormat, EASNetworks, generateRandomString, getPrevAttestationIds, SCHEMA_UID, useSigner } from './utils'; -import { axiosInstance } from '@/app/utils/axiosInstance'; import Modal from '@/app/utils/Modal'; import AttestationSuccessModal from './attestation/AttestationSuccessModal'; import AttestationLoading from './attestation/AttestationLoading'; import AttestationError from './attestation/AttestationError'; +import { attest, AttestationState } from './attestation'; +import { useSigner } from './attestation/utils'; enum VotingStatus { VOTED, @@ -55,13 +49,6 @@ const votingStatusMap = { }, }; -enum AttestationState { - Initial, - Loading, - Success, - Error, -} - const RankingPage = () => { const params = useParams(); const router = useRouter(); @@ -213,257 +200,6 @@ const RankingPage = () => { } }; - const attest = async () => { - // const localStorageTag = process.env.NEXT_PUBLIC_LOCAL_STORAGE_TAG!; - // const identityString = localStorage.getItem(localStorageTag); - - // if (!identityString) { - // console.error('Identity string is missing!'); - // router.push('/'); - // return; - // } - - // const identity = new Identity(identityString); - - if (!ranking) return; - - setAttestationState(AttestationState.Loading); - - const chainId = activeChain.id; - const easConfig = EASNetworks[chainId]; - const address = wallet?.getAccount()?.address; - - if (!easConfig) { - console.error('no eas config'); - return; - } - if (!wallet) { - console.error('no wallet'); - return; - } - if (!signer || !address) { - console.error('signer', signer, 'address', address); - return; - } - - const eas = new EAS(easConfig.EASDeployment); - const schemaRegistry = new SchemaRegistry(easConfig.SchemaRegistry); - - eas.connect(signer as any); - schemaRegistry.connect(signer as any); - const schema = await schemaRegistry.getSchema({ uid: SCHEMA_UID }); - const schemaEncoder = new SchemaEncoder(schema.schema); - // let proof = ['']; - // setProgress(ProgressState.Creating); - try { - const item = await convertRankingToAttestationFormat( - ranking.ranking.map(el => ({ RF6Id: el.project.RF6Id, share: el.share })), - ranking.name, - // comment, - ); - - const schemaData = [ - { name: 'listName', type: 'string', value: item.listName }, - { - name: 'listMetadataPtr', - type: 'string', - value: item.listMetadataPtr, - }, - ]; - - // const signalData = { - // category: item.listName, - // value: item.listMetadataPtr, - // }; - - // // generate proof of vote - // const groupId = process.env.NEXT_PUBLIC_BANDADA_GROUP_ID!; - // const users = await getMembersGroup(groupId); - // if (users && identityString !== '{}') { - // const bandadaGroup = await getGroup(groupId); - // let treeDepth = 16; - // if (bandadaGroup === null) { - // console.log('The Bandada group does not exist:', groupId); - // } - // else { - // treeDepth = bandadaGroup.treeDepth; - // } - // const group = new Group(groupId, treeDepth, users); - // console.log('going to encode signalData: '); - // console.log(signalData); - // const signal = toBigInt( - // encodeBytes32String(signalData.toString()), - // ).toString(); - // const { - // proof: tempProof, - // merkleTreeRoot, - // nullifierHash, - // } = await generateProof(identity, group, groupId, signal); - // proof = tempProof; - // console.log('generated proof of vote: ', proof); - - // const { data: currentMerkleRoot, error: errorRootHistory } - // = await supabase - // .from('root_history') - // .select() - // .order('created_at', { ascending: false }) - // .limit(1); - - // if (errorRootHistory) { - // console.log(errorRootHistory); - // } - - // if (!currentMerkleRoot) { - // console.error('Wrong currentMerkleRoot'); - // } - - // if ( - // currentMerkleRoot == null - // || merkleTreeRoot !== currentMerkleRoot[0].root - // ) { - // // compare merkle tree roots - // const { - // data: dataMerkleTreeRoot, - // error: errorMerkleTreeRoot, - // } = await supabase - // .from('root_history') - // .select() - // .eq('root', merkleTreeRoot); - - // if (errorMerkleTreeRoot) { - // console.log(errorMerkleTreeRoot); - // } - - // console.log('merkleTreeRoot: ', merkleTreeRoot); - // console.log('dataMerkleTreeRoot: ', dataMerkleTreeRoot); - - // if (!dataMerkleTreeRoot) { - // console.error('Wrong dataMerkleTreeRoot'); - // } - // else if (dataMerkleTreeRoot.length === 0) { - // console.log('Merkle Root is not part of the group'); - // } - - // console.log('dataMerkleTreeRoot', dataMerkleTreeRoot); - // const merkleTreeRootDuration - // = bandadaGroup?.fingerprintDuration ?? 0; - - // if ( - // dataMerkleTreeRoot - // && Date.now() - // > Date.parse(dataMerkleTreeRoot[0].created_at) - // + merkleTreeRootDuration - // ) { - // console.log('Merkle Tree Root is expired'); - // } - // } - - // const { data: nullifier, error: errorNullifierHash } - // = await supabase - // .from('nullifier_hash') - // .select('nullifier') - // .eq('nullifier', nullifierHash); - - // if (errorNullifierHash) { - // console.log(errorNullifierHash); - // } - - // if (!nullifier) { - // console.log('Wrong nullifier'); - // } - // else if (nullifier.length > 0) { - // console.log('You are using the same nullifier twice'); - // } - - // const { error: errorNullifier } = await supabase - // .from('nullifier_hash') - // .insert([{ nullifier: nullifierHash }]); - - // if (errorNullifier) { - // console.error(errorNullifier); - // } - - // const { data: dataFeedback, error: errorFeedback } - // = await supabase - // .from('feedback') - // .insert([{ signal: schemaData }]) - // .select() - // .order('created_at', { ascending: false }); - - // if (errorFeedback) { - // console.error(errorFeedback); - // } - - // if (!dataFeedback) { - // console.error('Wrong dataFeedback'); - // } - - // // TODO everything is good so add the proof in attestation : Mahdi - // } - - const schemaDataWithProof = [ - ...schemaData, - { - name: 'proof', - type: 'string[]', - value: [generateRandomString(20)], - }, - ]; - - console.log('sdwp', schemaDataWithProof); - const encodedData = schemaEncoder.encodeData(schemaDataWithProof); - - const prevAttestations = await getPrevAttestationIds( - address, - SCHEMA_UID, - easConfig.gqlUrl, - ranking.name, - ); - - if (prevAttestations.length > 0) { - for (const id of prevAttestations) { - const revokedTransactions = await eas.revoke({ - schema: SCHEMA_UID, - data: { uid: id }, - }); - await revokedTransactions.wait(); - } - } - - const tx = await eas.attest({ - schema: SCHEMA_UID, - data: { - data: encodedData, - recipient: address, - revocable: true, - }, - }); - - const newAttestationUID = await tx.wait(); - - // posthog.capture('Attested', { - // attestedCategory: category?.data.collection?.name, - // }); - - console.log('attestaion id', newAttestationUID); - // await finishCollections(collectionId); - - const attestationLink = `${easConfig.explorer}/attestation/view/${newAttestationUID}`; - - await axiosInstance.post('/flow/report-attest', { - collectionId: ranking.id, - attestationId: attestationLink, - }); - - setAttestationState(AttestationState.Success); - setAttestationLink(attestationLink); - } - catch (e) { - console.error('error on sending tx:', e); - setAttestationState(AttestationState.Error); - } - }; - const submitVotes = async () => { console.log('Projects,', projects); if (!projects) return; @@ -477,7 +213,14 @@ const RankingPage = () => { await updateProjectRanking(); - await attest(); + if (!wallet || !ranking || !signer) { + console.error('Requirements not met for attestations'); + return; + } + + await attest({ ranking: { id: ranking.id, name: ranking.name, + ranking: projects.map(el => ({ RF6Id: el.project.RF6Id, share: el.share })) }, + setAttestationLink, setAttestationState, signer, wallet }); }; const handleAttestationModalClose = () => { diff --git a/app/allocation/page.tsx b/app/allocation/page.tsx index bdcb320..b2fe693 100644 --- a/app/allocation/page.tsx +++ b/app/allocation/page.tsx @@ -14,7 +14,7 @@ import BudgetAllocation, { BudgetCategory, } from './components/BudgetAllocation'; import ConnectBox from './components/ConnectBox'; -import { modifyPercentage, RankItem } from './utils'; +import { modifyPercentage, RankItem, roundFractions } from './utils'; import { ArrowRightIcon } from '@/public/assets/icon-components/ArrowRight'; import { ArrowLeft2Icon } from '@/public/assets/icon-components/ArrowLeft2'; import { CustomizedSlider } from './components/Slider'; @@ -53,6 +53,11 @@ import BallotLoading from '../comparison/ballot/modals/BallotLoading'; import BallotSuccessModal from '../comparison/ballot/modals/BallotSuccessModal'; import BallotNotReady from '../comparison/ballot/modals/BallotNotReady'; import BallotErrorDelegated from '../comparison/ballot/modals/BallotErrorDelegated'; +import { attest, AttestationState } from './[category]/attestation'; +import { useSigner } from './[category]/attestation/utils'; +import AttestationError from './[category]/attestation/AttestationError'; +import AttestationLoading from './[category]/attestation/AttestationLoading'; +import AttestationSuccessModal from './[category]/attestation/AttestationSuccessModal'; const budgetCategory: BudgetCategory = { id: -1, @@ -81,6 +86,7 @@ enum BallotState { const AllocationPage = () => { const wallet = useActiveWallet(); const router = useRouter(); + const signer = useSigner(); const { address } = useAccount(); const { loggedToAgora } = useAuth(); const { isBadgeholder, category } = getJWTData(); @@ -97,6 +103,9 @@ const AllocationPage = () => { const budgetDelegateToYou = delegations?.toYou?.budget; const budgetDelegateFromYou = delegations?.fromYou?.budget; + const [attestationState, setAttestationState] = useState(AttestationState.Initial); + const [attestationLink, setAttestationLink] = useState(); + const [ballotState, setBallotState] = useState( BallotState.Initial ); @@ -124,12 +133,24 @@ const AllocationPage = () => { = useState>(); const [targetDelegate, setTargetDelegate] = useState(); - const { mutate: updateCategoriesRanking } = useUpdateCategoriesRanking({ + const { mutateAsync: updateCategoriesRanking } = useUpdateCategoriesRanking({ budget: totalValue, allocationPercentages: categoriesRanking?.map(el => el.percentage / 100) || [], }); + const handleSubmitVote = async () => { + await updateCategoriesRanking(); + + if (!wallet || !signer || !categoriesRanking) { + console.error('Requirements not met for attestations', wallet, signer, categoriesRanking); + return; + } + + await attest({ ranking: { id: -1, name: 'Budget', ranking: categoriesRanking.map(el => ({ RF6Id: el.RF6Id, share: el.percentage / 100 })) }, + setAttestationLink, setAttestationState, signer, wallet }); + }; + const handleDelegate = async (username: string, target: TargetDelegate) => { if (!categoryToDelegate) return; @@ -151,6 +172,24 @@ const AllocationPage = () => { setDelegationState(DelegationState.Success); }; + const handleAttestationModalClose = () => { + if (attestationState === AttestationState.Success) { + router.push('/allocation'); + } + else if (attestationState === AttestationState.Error) { + setAttestationState(AttestationState.Initial); + } + }; + + const handleVoteBudget = () => { + console.log('wallet?', wallet); + if (!wallet) { + setShowLoginModal(true); + return; + } + setAllocatingBudget(true); + }; + const handleLock = (id: RankItem['id']) => () => { try { if (!categoriesRanking) return; @@ -268,8 +307,9 @@ const AllocationPage = () => { if (categoryRankings) { setCategoriesRanking( categoryRankings.ranking.map(el => ({ + RF6Id: el.project.RF6Id, id: el.projectId, - percentage: Math.round(el.share * 100 * 100) / 100, + percentage: roundFractions(el.share * 100, 6), locked: false, budget: categoryRankings.budget * el.share, })) @@ -280,6 +320,22 @@ const AllocationPage = () => { return (
+ + {attestationState === AttestationState.Success && attestationLink && ( + setAttestationState(AttestationState.Initial)} + /> + )} + {attestationState === AttestationState.Loading && } + {attestationState === AttestationState.Error && } + {}} @@ -470,9 +526,7 @@ const AllocationPage = () => { setCategoryToDelegate(budgetCategory); setDelegationState(DelegationState.DelegationMethod); }} - onScore={() => { - setAllocatingBudget(true); - }} + onScore={handleVoteBudget} username={budgetDelegateFromYou?.metadata?.username} /> )} @@ -528,9 +582,7 @@ const AllocationPage = () => {