diff --git a/apps/web/pages/api/proofs/bns/index.ts b/apps/web/pages/api/proofs/bns/index.ts index c276058..8ba2fb5 100644 --- a/apps/web/pages/api/proofs/bns/index.ts +++ b/apps/web/pages/api/proofs/bns/index.ts @@ -61,7 +61,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return res.status(404).json({ error: 'address is not eligible for early access' }); } - const responseData: EarlyAccessProofResponse = { + const responseData: BNSProofResponse = { ...content, proofs, discountValidatorAddress: USERNAME_BNS_DISCOUNT_VALIDATORS[parsedChain], diff --git a/apps/web/src/addresses/usernames.ts b/apps/web/src/addresses/usernames.ts index dcb2409..d786cb7 100644 --- a/apps/web/src/addresses/usernames.ts +++ b/apps/web/src/addresses/usernames.ts @@ -39,7 +39,7 @@ export const USERNAME_EA_DISCOUNT_VALIDATORS: AddressMap = { }; export const USERNAME_BNS_DISCOUNT_VALIDATORS: AddressMap = { - [baseSepolia.id]: '0x', + [baseSepolia.id]: '0x1DE649d8b004A44491a7D3ebbb23F4B0DA89DE78', [base.id]: '0x', }; diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-nft.svg b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-nft.svg new file mode 100644 index 0000000..55f870d --- /dev/null +++ b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-nft.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/bns.jpg b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/bns.jpg new file mode 100644 index 0000000..9cc799e Binary files /dev/null and b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/bns.jpg differ diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx index 930acf4..8b19dbe 100644 --- a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx @@ -8,6 +8,8 @@ import Link from 'next/link'; import baseBuildathonParticipant from './images/base-buildathon-participant.svg'; import summerPassLvl3 from './images/summer-pass-lvl-3.svg'; import cbidVerification from './images/cbid-verification.svg'; +import BNSOwnership from './images/bns.jpg'; +import BaseNFT from './images/base-nft.svg'; import coinbaseOneVerification from './images/coinbase-one-verification.svg'; import coinbaseVerification from './images/coinbase-verification.svg'; import { StaticImageData } from 'next/dist/shared/lib/get-img-props'; @@ -43,6 +45,12 @@ export default function RegistrationLearnMoreModal({ const SummerPassRowClasses = classNames(rowClasses, { 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.SUMMER_PASS_LVL_3), }); + const BNSRowClasses = classNames(rowClasses, { + 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.BNS_NAME), + }); + const BaseDotEthNFTRowClasses = classNames(rowClasses, { + 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.BASE_ETH_NFT), + }); const qualifiedClasses = classNames( 'flex flex-row items-center justify-center py-3 px-1 h-5 text-xs bg-green-0 rounded ml-3', @@ -89,9 +97,9 @@ export default function RegistrationLearnMoreModal({ width={30} height={30} wrapperClassName="rounded-full" - imageClassName={CBRowClasses} + imageClassName={CB1RowClasses} /> -

Coinbase One verification

+

Coinbase One verification

@@ -110,9 +118,9 @@ export default function RegistrationLearnMoreModal({ width={30} height={30} wrapperClassName="rounded-full" - imageClassName={CBRowClasses} + imageClassName={CBIDRowClasses} /> -

A cb.id username

+

A cb.id username

@@ -131,9 +139,9 @@ export default function RegistrationLearnMoreModal({ width={30} height={30} wrapperClassName="rounded-full" - imageClassName={CBRowClasses} + imageClassName={BuildathonRowClasses} /> -

Base buildathon participant

+

Base buildathon participant

@@ -152,9 +160,9 @@ export default function RegistrationLearnMoreModal({ width={30} height={30} wrapperClassName="rounded-full" - imageClassName={CBRowClasses} + imageClassName={SummerPassRowClasses} /> -

Summer Pass Level 3

+

Summer Pass Level 3

@@ -164,6 +172,48 @@ export default function RegistrationLearnMoreModal({ )} +
  • + +
    + +

    BNS username

    + +
    +
    + {allActiveDiscounts.has(Discount.BNS_NAME) && ( +
    +

    Qualified

    +
    + )} +
  • +
  • + +
    + +

    Base.eth NFT

    + +
    +
    + {allActiveDiscounts.has(Discount.BASE_ETH_NFT) && ( +
    +

    Qualified

    +
    + )} +
  • {!hasDiscount && ( <> diff --git a/apps/web/src/hooks/useAggregatedDiscountValidators.ts b/apps/web/src/hooks/useAggregatedDiscountValidators.ts index db78cda..f2a2862 100644 --- a/apps/web/src/hooks/useAggregatedDiscountValidators.ts +++ b/apps/web/src/hooks/useAggregatedDiscountValidators.ts @@ -4,6 +4,10 @@ import { useCheckCBIDAttestations, useCheckCoinbaseAttestations, useCheckEAAttestations, + // useBuildathonAttestations, + // useSummerPassAttestations, + // useBaseDotEthAttestations, + useBNSAttestations, } from 'apps/web/src/hooks/useAttestations'; import { useActiveDiscountValidators } from 'apps/web/src/hooks/useReadActiveDiscountValidators'; import { Discount } from 'apps/web/src/utils/usernames'; @@ -32,13 +36,21 @@ export function useAggregatedDiscountValidators() { const { data: EAData, loading: loadingEAAttestations } = useCheckEAAttestations(); const { data: coinbaseData, loading: loadingCoinbaseAttestations } = useCheckCoinbaseAttestations(); + // const { data: BuildathonData, loading: loadingBuildathon } = useBuildathonAttestations(); + // const { data: SummerPassData, loading: loadingSummerPass } = useSummerPassAttestations(); + // const { data: BaseDotEthData, loading: loadingBaseDotEth } = useBaseDotEthAttestations(); + const { data: BNSData, loading: loadingBNS } = useBNSAttestations(); const loadingDiscounts = loadingCoinbaseAttestations || loadingCBIDAttestations || loadingCB1Attestations || loadingActiveDiscounts || - loadingEAAttestations; + loadingEAAttestations || + // loadingBuildathon || + // loadingSummerPass || + // loadingBaseDotEth || + loadingBNS; const discountsToAttestationData = useMemo(() => { const discountMapping: MappedDiscountData = {}; @@ -59,14 +71,51 @@ export function useAggregatedDiscountValidators() { discountKey: validator.key, }; } - if (EAData && validator.discountValidator === EAData.discountValidatorAddress) { discountMapping[Discount.EARLY_ACCESS] = { ...EAData, discountKey: validator.key }; } + + // if ( + // BuildathonData && + // validator.discountValidator === BuildathonData.discountValidatorAddress + // ) { + // discountMapping[Discount.BASE_BUILDATHON_PARTICIPANT] = { + // ...BuildathonData, + // discountKey: validator.key, + // }; + // } + // if ( + // SummerPassData && + // validator.discountValidator === SummerPassData.discountValidatorAddress + // ) { + // discountMapping[Discount.SUMMER_PASS_LVL_3] = { + // ...SummerPassData, + // discountKey: validator.key, + // }; + // } + // if ( + // BaseDotEthData && + // validator.discountValidator === BaseDotEthData.discountValidatorAddress + // ) { + // discountMapping[Discount.BASE_ETH_NFT] = { ...BaseDotEthData, discountKey: validator.key }; + // } + if (BNSData && validator.discountValidator === BNSData.discountValidatorAddress) { + discountMapping[Discount.BNS_NAME] = { ...BNSData, discountKey: validator.key }; + } }); return discountMapping; - }, [activeDiscountValidators, CBIDData, CB1Data, coinbaseData, EAData]); + }, [ + activeDiscountValidators, + CBIDData, + CB1Data, + coinbaseData, + EAData, + // BuildathonData, + // SummerPassData, + // BaseDotEthData, + BNSData, + ]); return { data: discountsToAttestationData, diff --git a/apps/web/src/hooks/useAttestations.ts b/apps/web/src/hooks/useAttestations.ts index 5d094f9..6aeacad 100644 --- a/apps/web/src/hooks/useAttestations.ts +++ b/apps/web/src/hooks/useAttestations.ts @@ -10,6 +10,7 @@ import { Address, ReadContractErrorType, encodeAbiParameters } from 'viem'; import { useAccount, useReadContract } from 'wagmi'; import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; import { useErrors } from 'apps/web/contexts/Errors'; +import { BNSProofResponse } from 'apps/web/pages/api/proofs/bns'; export type AttestationData = { discountValidatorAddress: Address; @@ -225,17 +226,13 @@ export function useCheckEAAttestations(): AttestationHookReturns { useEffect(() => { async function checkEarlyAccess(a: string) { - try { - const params = new URLSearchParams(); - params.append('address', a); - params.append('chain', basenameChain.id.toString()); - const response = await fetch(`/api/proofs/earlyAccess?${params}`); - if (response.ok) { - const result = (await response.json()) as EarlyAccessProofResponse; - setEAProofResponse(result); - } - } catch (error) { - logError(error, 'Error checking early access'); + const params = new URLSearchParams(); + params.append('address', a); + params.append('chain', basenameChain.id.toString()); + const response = await fetch(`/api/proofs/earlyAccess?${params}`); + if (response.ok) { + const result = (await response.json()) as EarlyAccessProofResponse; + setEAProofResponse(result); } } @@ -281,3 +278,75 @@ export function useCheckEAAttestations(): AttestationHookReturns { } return { data: null, loading: isLoading, error }; } + +// export function useBuildathonAttestations() { +// return { data: null, loading: isLoading, error }; +// } +// export function useSummerPassAttestations() { +// return { data: null, loading: isLoading, error }; +// } +// export function useBaseDotEthAttestations() { +// return { data: null, loading: isLoading, error }; +// } + +// merkle tree discount calls api endpoint +export function useBNSAttestations() { + const { address } = useAccount(); + const [proofResponse, setProofResponse] = useState(null); + const { basenameChain } = useBasenameChain(); + const { logError } = useErrors(); + + useEffect(() => { + async function checkBNS(a: string) { + const params = new URLSearchParams(); + params.append('address', a); + params.append('chain', basenameChain.id.toString()); + const response = await fetch(`/api/proofs/bns?${params}`); + if (response.ok) { + const result = (await response.json()) as BNSProofResponse; + setProofResponse(result); + } + } + + if (address) { + checkBNS(address).catch((error) => { + logError(error, 'Error checking BNS discount availability'); + }); + } + }, [address, basenameChain.id, logError]); + + const encodedProof = useMemo( + () => + proofResponse?.proofs + ? encodeAbiParameters([{ type: 'bytes32[]' }], [proofResponse?.proofs]) + : '0x0', + [proofResponse?.proofs], + ); + + const readContractArgs = useMemo(() => { + if (!proofResponse?.proofs || !address) { + return {}; + } + return { + address: proofResponse?.discountValidatorAddress, + abi: EarlyAccessValidatorABI, + functionName: 'isValidDiscountRegistration', + args: [address, encodedProof], + }; + }, [address, proofResponse?.discountValidatorAddress, proofResponse?.proofs, encodedProof]); + + const { data: isValid, isLoading, error } = useReadContract(readContractArgs); + + if (isValid && proofResponse && address) { + return { + data: { + discountValidatorAddress: proofResponse.discountValidatorAddress, + discount: Discount.BNS_NAME, + validationData: encodedProof, + }, + loading: false, + error: null, + }; + } + return { data: null, loading: isLoading, error }; +} diff --git a/apps/web/src/utils/usernames.ts b/apps/web/src/utils/usernames.ts index 3f3689a..a594f19 100644 --- a/apps/web/src/utils/usernames.ts +++ b/apps/web/src/utils/usernames.ts @@ -335,6 +335,8 @@ export enum Discount { COINBASE_VERIFIED_ACCOUNT = 'COINBASE_VERIFIED_ACCOUNT', BASE_BUILDATHON_PARTICIPANT = 'BASE_BUILDATHON_PARTICIPANT', SUMMER_PASS_LVL_3 = 'SUMMER_PASS_LVL_3', + BNS_NAME = 'BNS_NAME', + BASE_ETH_NFT = 'BASE_ETH_NFT', } export function isValidDiscount(key: string): key is keyof typeof Discount {