From 26ef77ed4cdeca1a44a98924fd2fcc697d4fb3fe Mon Sep 17 00:00:00 2001 From: Jordan Frankfurt Date: Thu, 8 Aug 2024 14:04:02 -0500 Subject: [PATCH] feat: add BNS discount (#806) * add BNS discount * add sepolia validator address * fix api handler type --- apps/web/pages/api/proofs/bns/index.ts | 2 +- apps/web/src/addresses/usernames.ts | 2 +- .../images/base-nft.svg | 13 +++ .../RegistrationLearnMoreModal/images/bns.jpg | Bin 0 -> 1829 bytes .../RegistrationLearnMoreModal/index.tsx | 66 +++++++++++-- .../hooks/useAggregatedDiscountValidators.ts | 55 ++++++++++- apps/web/src/hooks/useAttestations.ts | 91 +++++++++++++++--- apps/web/src/utils/usernames.ts | 2 + 8 files changed, 207 insertions(+), 24 deletions(-) create mode 100644 apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-nft.svg create mode 100644 apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/bns.jpg 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 0000000000000000000000000000000000000000..9cc799e99e3da321fa528ce988f869c94c8701a6 GIT binary patch literal 1829 zcmbu9c~DbV7{zZ$ScTHCx}Ygi7pj1o02O22P-X2XVr7lMNVPyxih{^8k(5o-ptVq| zRjg40hMHmqnvhB$G!Tr|1u+N$Eg?ZIg0}BT2@kWphAukOzdG%A=l*rSId^{N%pB|i zHU?gg4vz{4I4c~8v>bqqfGq&`KWAR#e@ftR*!O^F2i^d0;BiD?Ma1EWI7|sh0N||u z)CPYI&I(Vkwz0LdcUWLqkh~78aCp2G0dH+hAXv5!TiyYIXzk*?KGeoFc8~4y18y5i zt1sI5ywj|3-`NMhNlwo#vv=_DT)1fQij}KY`@Tg93<`dGW5|}RVc`*xQPJ=1ii=N3 zOiF%#@4gQ*GP4+Y2lEdV6c!biS5zK3%K3y_^ZCgyPM!Yp%-K4AeZwU|*`kaTvJ`Y@xwiJ|NVi%2SdYJ-T1>tkAI$cGKoB&{%uA-i_X26=fVN}OUwQQ z`v;fB1e_ItfG60_bK$HCEEi8CSbMLxaS4sJ-E+Wo`G!(Ew|A;9HY@CX$U9;8^xQrN zk2fjW705i=A7uXytn5FLy##y3H4Gf_I7@hVB7lJT5H!{T!HJ?_ve_ZY5Y5_|hitkQ zLVC*6VqhU!-i-l#iGAkq!xsr<>hFu=FHlULJ*j?u;w+uom@ppWNzZCEuev|8xj3ar zOz+s4y`WoqcOodLItbx%rzh|D-oD*&aH5TF?CnYBue;zpEJA%mMvn;nNeq-_nfrK- z(X3eH1P9t{%DWb_>^LGF%i&N%8yRvu+j>&1;7_Ic!B?spouWs&XcYJoZ*8)^R*0r+ z-jxm1hBm{$$O84{l-8;ZTxKtyQt@N*;C5ldi8}#93-nBc{jACmW3)4!rrICQsz>?b zJZ0|!BUL>kGZcv^7pJf7r8O)}7!8g~}mROx<{Qxa4t)Jc0buhIESuT&jM zYTvdmM;owkBGn1L)%?@O^Q!PZ>UtY)by#+`Fqs+l(IuJce1d6h{^cE#CN{0J$jF`~ z!CQG1X)PEqdO?Ow7*K4akyV#j4AU1SZ|}Z=kQB{~7o$=wv@ z9orVsUEIXuP<_WWN82<-qUcr8Vk!pOF{Gbgecn2Ncs8fu6OoAN9` zgXegkVE|5y8JgAxJQZNT+TV1gGlN%3^ZZ=FwljMeBgHTVO4|ecWjc~;6hCW2;g;?Y z{s>K`>qwv@9`(tF9Wq|;{e1dJ>EpTdShoXpyHa$uW(m2m=3Gr*!1IV(P)qPL-*M={ zullc0cS9_T)+*_zCc$J~+7X&}R@-LdJ^B-~clnlnpVJAjydlpaYP0-|?zp>z67_I9cE_a$=*8x8&g9f$-f;Q3s*^1g*fhG#CSfTtuq4>HLY@&vcI#0w3aNm)Wi3b-sX zoYy_-hzfEpGR!OPwmS<7t9G>^&fNAq#5M_8*-(dmq)IIB7AULu5$td1nQG?j;1B(4 zrR>M=KXbiY{~NdM*FNDyNth+*Jy0f8(J9F=y2=&O zo*Wi=WplmC61?M++}-j@UB{ZOh{xG@LyWBa`hALUP5GlDA@NA$!(uMO7lLlx;cw4n z?!v$gQYc%I-Z^$$BNiB4WAw++b0lrfF;=Ky)zta*gMqxVG_Q6@Yl?`2m!JW0=MQIu z!zZ|sChBDTC=n4 -

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 {