From da17eb1213c3f614c55bd9c69a8d033878513419 Mon Sep 17 00:00:00 2001
From: pazerny kormoran <38940023+pazernykormoran@users.noreply.github.com>
Date: Thu, 9 May 2024 14:14:14 +0200
Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=91=20Integrate=20with=20Gitcoin=20bac?=
=?UTF-8?q?kend=20(#55)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: p-sad
---
packages/frontend/.env.example | 1 +
.../frontend/src/backend/getPassportScore.ts | 68 +++++++++++++++++
.../src/blockchain/hooks/useAuctionState.ts | 1 -
.../userActious/UserActionSection.tsx | 2 -
.../components/userActious/bid/BidFlow.tsx | 8 +-
.../userActious/bid/InitialBidFlow.tsx | 25 ++++++
.../userActious/bid/PlaceBid/PlaceBidFlow.tsx | 15 +++-
...nPassword.tsx => CheckGitcoinPassport.tsx} | 10 ++-
.../gitcoin/CheckingGitcoinScore.tsx | 44 ++++++++++-
.../userActious/gitcoin/GitcoinFlow.tsx | 76 +++++++++++++++++++
.../userActious/gitcoin/GitcointFlow.tsx | 30 --------
.../gitcoin/InsufficientUserScore.tsx | 4 +-
.../gitcoin/SufficientUserScore.tsx | 4 +-
.../userActious/gitcoin/UserGitcoinScore.tsx | 14 ++--
.../src/pages/api/scorer/[userAddress].ts | 2 +-
packages/frontend/src/types/api/scorer.ts | 4 +-
.../src/types/passport/GticoinCredentials.ts | 6 ++
17 files changed, 254 insertions(+), 60 deletions(-)
create mode 100644 packages/frontend/src/backend/getPassportScore.ts
create mode 100644 packages/frontend/src/components/userActious/bid/InitialBidFlow.tsx
rename packages/frontend/src/components/userActious/gitcoin/{CheckGitcoinPassword.tsx => CheckGitcoinPassport.tsx} (78%)
create mode 100644 packages/frontend/src/components/userActious/gitcoin/GitcoinFlow.tsx
delete mode 100644 packages/frontend/src/components/userActious/gitcoin/GitcointFlow.tsx
create mode 100644 packages/frontend/src/types/passport/GticoinCredentials.ts
diff --git a/packages/frontend/.env.example b/packages/frontend/.env.example
index 0bf114e82..d9a242c29 100644
--- a/packages/frontend/.env.example
+++ b/packages/frontend/.env.example
@@ -1,4 +1,5 @@
NEXT_PUBLIC_INFURA_KEY=
+NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
NEXT_PUBLIC_VOUCHER_REDEEM_DEADLINE="2024-04-25T17:44:44.773Z"
RATE_LIMIT_GLOBAL=100
RATE_LIMIT_NONCES=5
diff --git a/packages/frontend/src/backend/getPassportScore.ts b/packages/frontend/src/backend/getPassportScore.ts
new file mode 100644
index 000000000..0e9c7cedd
--- /dev/null
+++ b/packages/frontend/src/backend/getPassportScore.ts
@@ -0,0 +1,68 @@
+import { isApiErrorResponse } from '@/types/api/error'
+import {
+ GetPassportScorerNonceResponseSchema,
+ GetResponseSchema,
+ SubmitAddressForScoringRequest,
+ SubmitAddressForScoringResponseSchema,
+} from '@/types/api/scorer'
+import { useMutation } from '@tanstack/react-query'
+import { Hex } from 'viem'
+import { useAccount, useChainId, useSignMessage } from 'wagmi'
+
+export const useSendForScoring = () => {
+ const { address } = useAccount()
+ const chainId = useChainId()
+ const { signMessageAsync } = useSignMessage()
+
+ return useMutation({
+ mutationFn: async () => {
+ if (!address) {
+ throw new Error('No address')
+ }
+
+ try {
+ return await getGitcoinScore(address, chainId)
+ } catch (error) {
+ if (!(error instanceof ErrorWithStatus) || error.status != 404) {
+ throw error
+ }
+ }
+
+ const nonceData = await getGitcoinNonce()
+ const signature = await signMessageAsync({ message: nonceData.message })
+ await sendForScoring({ userAddress: address as Hex, signature, nonce: nonceData.nonce })
+ },
+ })
+}
+
+class ErrorWithStatus extends Error {
+ constructor(message: string, public status: number) {
+ super(message)
+ }
+}
+
+export const getGitcoinScore = async (userAddress: Hex, chainId: number) => {
+ const result = await fetch(`/api/scorer/${userAddress}?chainId=${chainId}`)
+ const data = GetResponseSchema.parse(await result.json())
+ if (isApiErrorResponse(data)) throw new ErrorWithStatus(data.error, result.status)
+ return data
+}
+
+const getGitcoinNonce = async () => {
+ const response = await fetch(`/api/scorer/nonce`)
+ const data = GetPassportScorerNonceResponseSchema.parse(await response.json())
+ if (isApiErrorResponse(data)) throw new Error(data.error)
+ return data
+}
+
+const sendForScoring = async (requestData: SubmitAddressForScoringRequest) => {
+ const response = await fetch(`/api/scorer`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(requestData),
+ })
+ const data = SubmitAddressForScoringResponseSchema.parse(await response.json())
+ if (isApiErrorResponse(data)) throw new Error(data.error)
+}
diff --git a/packages/frontend/src/blockchain/hooks/useAuctionState.ts b/packages/frontend/src/blockchain/hooks/useAuctionState.ts
index 265079165..e7ede536c 100644
--- a/packages/frontend/src/blockchain/hooks/useAuctionState.ts
+++ b/packages/frontend/src/blockchain/hooks/useAuctionState.ts
@@ -12,7 +12,6 @@ export type AuctionState =
| 'AwaitingResults'
| 'ClaimingFlow'
| 'ClaimingClosed'
- | 'GitcoinFlow'
export function useAuctionState(): AuctionState | undefined {
const { address, chainId: userChainId } = useAccount()
diff --git a/packages/frontend/src/components/userActious/UserActionSection.tsx b/packages/frontend/src/components/userActious/UserActionSection.tsx
index 3ac0bb361..5bacf7e17 100644
--- a/packages/frontend/src/components/userActious/UserActionSection.tsx
+++ b/packages/frontend/src/components/userActious/UserActionSection.tsx
@@ -3,7 +3,6 @@ import { Colors } from '@/styles/colors'
import { ReactElement } from 'react'
import styled from 'styled-components'
import { ConnectWalletWarning } from './ConnectWalletWarning'
-import { GitcoinFlow } from './gitcoin/GitcointFlow'
import { BidFlow } from './bid/BidFlow'
import { AwaitingResults } from '@/components/userActious/claim/AwaitingResults'
import { ClaimingClosed } from '@/components/userActious/claim/ClaimingClosed'
@@ -19,7 +18,6 @@ const UserActions: Record ReactElement> = {
AwaitingResults: AwaitingResults,
ClaimingFlow: ClaimingFlow,
ClaimingClosed: ClaimingClosed,
- GitcoinFlow: GitcoinFlow,
}
export const UserActionSection = () => {
diff --git a/packages/frontend/src/components/userActious/bid/BidFlow.tsx b/packages/frontend/src/components/userActious/bid/BidFlow.tsx
index b2ae1b6b7..a13841cd3 100644
--- a/packages/frontend/src/components/userActious/bid/BidFlow.tsx
+++ b/packages/frontend/src/components/userActious/bid/BidFlow.tsx
@@ -1,7 +1,7 @@
import { useUserBid } from '@/blockchain/hooks/useUserBid'
import { useEffect, useState } from 'react'
-import { PlaceBidFlow } from './PlaceBid/PlaceBidFlow'
import { BumpBidFlow } from './BumpBid/BumpBidFlow'
+import { InitialBidFlow } from './InitialBidFlow'
export const BidFlow = () => {
const userBid = useUserBid()
@@ -15,9 +15,5 @@ export const BidFlow = () => {
}
}, [isTransactionViewLock, userBidExists])
- return isInitialBid ? :
-}
-
-export interface FlowProps {
- setTransactionViewLock: (value: boolean) => void
+ return isInitialBid ? :
}
diff --git a/packages/frontend/src/components/userActious/bid/InitialBidFlow.tsx b/packages/frontend/src/components/userActious/bid/InitialBidFlow.tsx
new file mode 100644
index 000000000..881713a09
--- /dev/null
+++ b/packages/frontend/src/components/userActious/bid/InitialBidFlow.tsx
@@ -0,0 +1,25 @@
+import { GitcoinCredentials } from '@/types/passport/GticoinCredentials'
+import { useState } from 'react'
+import { GitcoinFlow } from '../gitcoin/GitcoinFlow'
+import { PlaceBidFlow } from './PlaceBid/PlaceBidFlow'
+
+interface Props {
+ setTransactionViewLock: (value: boolean) => void
+}
+
+export const InitialBidFlow = ({ setTransactionViewLock }: Props) => {
+ const [gitcoinCredentials, setGitcoinCredentials] = useState()
+ const [gitcoinSettled, setGitcoinSettled] = useState(false)
+
+ if (!gitcoinCredentials || !gitcoinSettled) {
+ return (
+ setGitcoinSettled(true)}
+ />
+ )
+ }
+
+ return
+}
diff --git a/packages/frontend/src/components/userActious/bid/PlaceBid/PlaceBidFlow.tsx b/packages/frontend/src/components/userActious/bid/PlaceBid/PlaceBidFlow.tsx
index e2789eab0..30ce99ba4 100644
--- a/packages/frontend/src/components/userActious/bid/PlaceBid/PlaceBidFlow.tsx
+++ b/packages/frontend/src/components/userActious/bid/PlaceBid/PlaceBidFlow.tsx
@@ -1,5 +1,4 @@
import { useAccount } from 'wagmi'
-import { FlowProps } from '../BidFlow'
import { TxFlowSteps } from '@/components/auction/TxFlowSteps'
import { useEffect, useMemo, useState } from 'react'
import { useMinimumBid } from '@/blockchain/hooks/useMinimumBid'
@@ -7,14 +6,24 @@ import { PlaceBidForm } from './PlaceBidForm'
import { formatEther, parseEther } from 'viem'
import { AuctionTransaction } from '@/components/auction/AuctionTransaction'
import { usePlaceBid } from './usePlaceBid'
+import { GitcoinCredentials } from '@/types/passport/GticoinCredentials'
-export const PlaceBidFlow = ({ setTransactionViewLock }: FlowProps) => {
+interface FlowProps {
+ setTransactionViewLock: (value: boolean) => void
+ gitcoinCredentials: GitcoinCredentials
+}
+
+export const PlaceBidFlow = ({ setTransactionViewLock, gitcoinCredentials }: FlowProps) => {
const { address } = useAccount()
const [view, setView] = useState(TxFlowSteps.Placing)
const minimumBid = useMinimumBid()
const [bid, setBid] = useState('0')
const parsedBid = useMemo(() => parseEther(bid || '0'), [bid])
- const bidAction = usePlaceBid({ value: parsedBid, score: BigInt(20), proof: '0x' })
+ const bidAction = usePlaceBid({
+ value: parsedBid,
+ score: BigInt(gitcoinCredentials.score),
+ proof: gitcoinCredentials.proof,
+ })
useEffect(() => setTransactionViewLock(bidAction.status !== 'idle'), [bidAction.status, setTransactionViewLock])
useEffect(() => setView(TxFlowSteps.Placing), [address])
diff --git a/packages/frontend/src/components/userActious/gitcoin/CheckGitcoinPassword.tsx b/packages/frontend/src/components/userActious/gitcoin/CheckGitcoinPassport.tsx
similarity index 78%
rename from packages/frontend/src/components/userActious/gitcoin/CheckGitcoinPassword.tsx
rename to packages/frontend/src/components/userActious/gitcoin/CheckGitcoinPassport.tsx
index 2fd240eff..5f90c97c0 100644
--- a/packages/frontend/src/components/userActious/gitcoin/CheckGitcoinPassword.tsx
+++ b/packages/frontend/src/components/userActious/gitcoin/CheckGitcoinPassport.tsx
@@ -3,14 +3,20 @@ import { FormHeading, FormRow, FormWrapper } from '../../form'
import { Button } from '../../buttons'
import { SeparatorWithText } from '@/components/common/Separator'
-export const CheckGitcoinPassword = () => {
+interface Props {
+ onCheckScoreClick: () => void
+}
+
+export const CheckGitcoinPassport = ({ onCheckScoreClick }: Props) => {
return (
Check Gitcoin Passport
To place a bid we need to check your score. By verifying your score we checking if you are a human.
-
+
diff --git a/packages/frontend/src/components/userActious/gitcoin/CheckingGitcoinScore.tsx b/packages/frontend/src/components/userActious/gitcoin/CheckingGitcoinScore.tsx
index 2446ed226..47b25753e 100644
--- a/packages/frontend/src/components/userActious/gitcoin/CheckingGitcoinScore.tsx
+++ b/packages/frontend/src/components/userActious/gitcoin/CheckingGitcoinScore.tsx
@@ -3,6 +3,12 @@ import { FormHeading, FormRow, FormWrapper } from '../../form'
import { Stepper } from '@/components/stepper/Stepper'
import { ClockIcon } from '@/components/icons'
import { Button } from '@/components/buttons'
+import { useEffect } from 'react'
+import { useQuery } from '@tanstack/react-query'
+import { useAccount, useChainId } from 'wagmi'
+import { Hex } from 'viem'
+import { getGitcoinScore } from '@/backend/getPassportScore'
+import { GetScoreResponseSuccess } from '@/types/api/scorer'
const gitcoinScoreSteps = [
{
@@ -31,7 +37,37 @@ const gitcoinScoreSteps = [
},
]
-export const CheckGitcoinScore = () => {
+interface CheckGitcoinScoreProps {
+ setGitcoinCredentials: (credentials: GetScoreResponseSuccess) => void
+ gitcoinRequestSettled: boolean
+ gitcoinRequestError: boolean
+ onSignAgainClick: () => void
+}
+
+export const CheckGitcoinScore = ({
+ setGitcoinCredentials,
+ gitcoinRequestSettled,
+ gitcoinRequestError,
+ onSignAgainClick,
+}: CheckGitcoinScoreProps) => {
+ const { address } = useAccount()
+ const chainId = useChainId()
+ const queryEnabled = !!(gitcoinRequestSettled && address)
+ const { data } = useQuery({
+ queryKey: ['gitcoinScore', address, chainId],
+ queryFn: () => getGitcoinScore(address as Hex, chainId),
+ enabled: queryEnabled,
+ retry: true,
+ retryDelay: 5000,
+ })
+ const step = gitcoinRequestSettled ? 2 : 1
+
+ useEffect(() => {
+ if (data && data.status == 'done') {
+ setGitcoinCredentials(data)
+ }
+ }, [data, setGitcoinCredentials])
+
return (
@@ -41,8 +77,10 @@ export const CheckGitcoinScore = () => {
It will take about 1 minute. Please stay on this page.
-
-
+
+
)
}
diff --git a/packages/frontend/src/components/userActious/gitcoin/GitcoinFlow.tsx b/packages/frontend/src/components/userActious/gitcoin/GitcoinFlow.tsx
new file mode 100644
index 000000000..de4f4b3da
--- /dev/null
+++ b/packages/frontend/src/components/userActious/gitcoin/GitcoinFlow.tsx
@@ -0,0 +1,76 @@
+import { useState } from 'react'
+import { CheckGitcoinPassport } from './CheckGitcoinPassport'
+import { CheckGitcoinScore } from './CheckingGitcoinScore'
+import { UserGitcoinScore } from '@/components/userActious/gitcoin/UserGitcoinScore'
+import { MissingGitcoinPassport } from './MissingGitcoinPassport'
+import { useSendForScoring } from '@/backend/getPassportScore'
+import { GitcoinCredentials } from '@/types/passport/GticoinCredentials'
+import { GetScoreResponseSuccess } from '@/types/api/scorer'
+
+enum GitcoinState {
+ INITIAL_PAGE,
+ CHECKING_SCORE,
+ MISSING_PASSPORT,
+ YOUR_SCORE,
+}
+
+const ScoreDecimals = 8
+const ScoreMultiplier = 10 ** ScoreDecimals
+
+interface Props {
+ setGitcoinCredentials: (credentials: GitcoinCredentials) => void
+ gitcoinCredentials: GitcoinCredentials | undefined
+ gitcoinSettled: () => void
+}
+
+export const GitcoinFlow = ({ gitcoinCredentials, setGitcoinCredentials, gitcoinSettled }: Props) => {
+ const [gitcoinState, setGitcoinState] = useState(GitcoinState.INITIAL_PAGE)
+ const { mutateAsync, isSuccess, isError } = useSendForScoring()
+
+ const setCredentials = (credentials: GetScoreResponseSuccess) => {
+ setGitcoinCredentials({
+ score: BigInt(credentials?.score),
+ proof: credentials.signature,
+ })
+ setGitcoinState(GitcoinState.YOUR_SCORE)
+ }
+
+ const sendForScoring = async () => {
+ const data = await mutateAsync()
+ if (data?.status === 'done') {
+ setCredentials(data)
+ }
+ }
+
+ const onCheckScoreClick = () => {
+ setGitcoinState(GitcoinState.CHECKING_SCORE)
+ sendForScoring()
+ }
+
+ switch (gitcoinState) {
+ case GitcoinState.INITIAL_PAGE:
+ return
+ case GitcoinState.CHECKING_SCORE:
+ return (
+
+ )
+ case GitcoinState.YOUR_SCORE:
+ return (
+
+ )
+ case GitcoinState.MISSING_PASSPORT:
+ return setGitcoinState(GitcoinState.INITIAL_PAGE)} />
+
+ default:
+ return
+ }
+}
diff --git a/packages/frontend/src/components/userActious/gitcoin/GitcointFlow.tsx b/packages/frontend/src/components/userActious/gitcoin/GitcointFlow.tsx
deleted file mode 100644
index ce0a34882..000000000
--- a/packages/frontend/src/components/userActious/gitcoin/GitcointFlow.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { useState } from 'react'
-import { CheckGitcoinPassword } from './CheckGitcoinPassword'
-import { CheckGitcoinScore } from './CheckingGitcoinScore'
-import { UserGitcoinScore } from '@/components/userActious/gitcoin/UserGitcoinScore'
-import { MissingGitcoinPassport } from './MissingGitcoinPassport'
-
-enum GitcoinState {
- INITIAL_PAGE,
- CHECKING_SCORE,
- MISSING_PASSPORT,
- YOUR_SCORE,
-}
-
-export const GitcoinFlow = () => {
- const [gitcoinState, setGitcoinState] = useState(GitcoinState.MISSING_PASSPORT)
-
- switch (gitcoinState) {
- case GitcoinState.INITIAL_PAGE:
- return
- case GitcoinState.CHECKING_SCORE:
- return
- case GitcoinState.YOUR_SCORE:
- return
- case GitcoinState.MISSING_PASSPORT:
- return setGitcoinState(GitcoinState.INITIAL_PAGE)} />
-
- default:
- return
- }
-}
diff --git a/packages/frontend/src/components/userActious/gitcoin/InsufficientUserScore.tsx b/packages/frontend/src/components/userActious/gitcoin/InsufficientUserScore.tsx
index 2527e6421..226e7f46a 100644
--- a/packages/frontend/src/components/userActious/gitcoin/InsufficientUserScore.tsx
+++ b/packages/frontend/src/components/userActious/gitcoin/InsufficientUserScore.tsx
@@ -7,7 +7,7 @@ import { urls } from '@/constants/urls'
import { UserScoreProps } from '@/components/userActious/gitcoin/UserGitcoinScore'
import { environment } from '@/config/environment'
-export const InsufficientUserScore = ({ userScore }: UserScoreProps) => {
+export const InsufficientUserScore = ({ userScore, getBackToScoring }: UserScoreProps) => {
return (
@@ -32,7 +32,7 @@ export const InsufficientUserScore = ({ userScore }: UserScoreProps) => {
-
+
)
}
diff --git a/packages/frontend/src/components/userActious/gitcoin/SufficientUserScore.tsx b/packages/frontend/src/components/userActious/gitcoin/SufficientUserScore.tsx
index dca23e72a..50caa8934 100644
--- a/packages/frontend/src/components/userActious/gitcoin/SufficientUserScore.tsx
+++ b/packages/frontend/src/components/userActious/gitcoin/SufficientUserScore.tsx
@@ -5,7 +5,7 @@ import { environment } from '@/config/environment'
import styled from 'styled-components'
import { Colors } from '@/styles/colors'
-export const SufficientUserScore = ({ userScore }: UserScoreProps) => {
+export const SufficientUserScore = ({ userScore, gitcoinSettled }: UserScoreProps) => {
return (
@@ -20,7 +20,7 @@ export const SufficientUserScore = ({ userScore }: UserScoreProps) => {
You can place your bid now!
-
+
)
}
diff --git a/packages/frontend/src/components/userActious/gitcoin/UserGitcoinScore.tsx b/packages/frontend/src/components/userActious/gitcoin/UserGitcoinScore.tsx
index 580fab0fd..2151c3588 100644
--- a/packages/frontend/src/components/userActious/gitcoin/UserGitcoinScore.tsx
+++ b/packages/frontend/src/components/userActious/gitcoin/UserGitcoinScore.tsx
@@ -2,17 +2,17 @@ import { SufficientUserScore } from '@/components/userActious/gitcoin/Sufficient
import { InsufficientUserScore } from '@/components/userActious/gitcoin/InsufficientUserScore'
import { environment } from '@/config/environment'
-const userScore = 17
-
export interface UserScoreProps {
- userScore: number
+ userScore?: number
+ gitcoinSettled?: () => void
+ getBackToScoring?: () => void
}
-export const UserGitcoinScore = () => {
- const isSufficientScore = userScore >= environment.gitcoinRequiredScore
+export const UserGitcoinScore = ({ userScore, gitcoinSettled, getBackToScoring }: UserScoreProps) => {
+ const isSufficientScore = userScore && userScore >= environment.gitcoinRequiredScore
return isSufficientScore ? (
-
+
) : (
-
+
)
}
diff --git a/packages/frontend/src/pages/api/scorer/[userAddress].ts b/packages/frontend/src/pages/api/scorer/[userAddress].ts
index ae6af2965..25693f9fe 100644
--- a/packages/frontend/src/pages/api/scorer/[userAddress].ts
+++ b/packages/frontend/src/pages/api/scorer/[userAddress].ts
@@ -61,7 +61,7 @@ async function getScore(req: NextApiRequest, res: NextApiResponse
+
+export const GetResponseSchema = z.union([
ErrorResponseSchema,
GetScoreResponseProcessingSchema,
GetScoreResponseSuccessSchema,
diff --git a/packages/frontend/src/types/passport/GticoinCredentials.ts b/packages/frontend/src/types/passport/GticoinCredentials.ts
new file mode 100644
index 000000000..35e152867
--- /dev/null
+++ b/packages/frontend/src/types/passport/GticoinCredentials.ts
@@ -0,0 +1,6 @@
+import { Hex } from 'viem'
+
+export type GitcoinCredentials = {
+ score: bigint
+ proof: Hex
+}