mirror of
https://github.com/placeholder-soft/web.git
synced 2026-01-12 22:45:00 +08:00
feat: add BNS discount (#806)
* add BNS discount * add sepolia validator address * fix api handler type
This commit is contained in:
@@ -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],
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<g clip-path="url(#clip0_8972_15511)">
|
||||
<rect width="32" height="32" rx="8" fill="#0052FF" />
|
||||
<path
|
||||
d="M15.9808 27C22.0665 27 27 22.0751 27 16C27 9.92487 22.0665 5 15.9808 5C10.207 5 5.47042 9.43292 5 15.0754H19.5648V16.9246H5C5.47042 22.5671 10.207 27 15.9808 27Z"
|
||||
fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_8972_15511">
|
||||
<rect width="32" height="32" rx="8" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 542 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -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}
|
||||
/>
|
||||
<p className={classNames(CB1RowClasses)}>Coinbase One verification</p>
|
||||
<p className={CB1RowClasses}>Coinbase One verification</p>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -110,9 +118,9 @@ export default function RegistrationLearnMoreModal({
|
||||
width={30}
|
||||
height={30}
|
||||
wrapperClassName="rounded-full"
|
||||
imageClassName={CBRowClasses}
|
||||
imageClassName={CBIDRowClasses}
|
||||
/>
|
||||
<p className={classNames(CBIDRowClasses)}>A cb.id username</p>
|
||||
<p className={CBIDRowClasses}>A cb.id username</p>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -131,9 +139,9 @@ export default function RegistrationLearnMoreModal({
|
||||
width={30}
|
||||
height={30}
|
||||
wrapperClassName="rounded-full"
|
||||
imageClassName={CBRowClasses}
|
||||
imageClassName={BuildathonRowClasses}
|
||||
/>
|
||||
<p className={classNames(BuildathonRowClasses)}>Base buildathon participant</p>
|
||||
<p className={BuildathonRowClasses}>Base buildathon participant</p>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -152,9 +160,9 @@ export default function RegistrationLearnMoreModal({
|
||||
width={30}
|
||||
height={30}
|
||||
wrapperClassName="rounded-full"
|
||||
imageClassName={CBRowClasses}
|
||||
imageClassName={SummerPassRowClasses}
|
||||
/>
|
||||
<p className={classNames(SummerPassRowClasses)}>Summer Pass Level 3</p>
|
||||
<p className={SummerPassRowClasses}>Summer Pass Level 3</p>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -164,6 +172,48 @@ export default function RegistrationLearnMoreModal({
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
<li className="flex items-center gap-3">
|
||||
<Tooltip content="BNS (.base) username holders are eligible for a 0.01 ETH discount">
|
||||
<div className="flex flex-row items-center justify-start gap-2">
|
||||
<ImageWithLoading
|
||||
src={BNSOwnership}
|
||||
alt="criteria icon"
|
||||
width={30}
|
||||
height={30}
|
||||
wrapperClassName="rounded-full"
|
||||
imageClassName={BNSRowClasses}
|
||||
/>
|
||||
<p className={BNSRowClasses}>BNS username</p>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{allActiveDiscounts.has(Discount.BNS_NAME) && (
|
||||
<div className={qualifiedClasses}>
|
||||
<p className="text-green-60">Qualified</p>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
<li className="flex items-center gap-3">
|
||||
<Tooltip content="Available for anyone holding a base.eth NFT">
|
||||
<div className="flex flex-row items-center justify-start gap-2">
|
||||
<ImageWithLoading
|
||||
src={BaseNFT as StaticImageData}
|
||||
alt="criteria icon"
|
||||
width={30}
|
||||
height={30}
|
||||
wrapperClassName="rounded-full"
|
||||
imageClassName={BaseDotEthNFTRowClasses}
|
||||
/>
|
||||
<p className={BaseDotEthNFTRowClasses}>Base.eth NFT</p>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{allActiveDiscounts.has(Discount.BASE_ETH_NFT) && (
|
||||
<div className={qualifiedClasses}>
|
||||
<p className="text-green-60">Qualified</p>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
{!hasDiscount && (
|
||||
<>
|
||||
|
||||
@@ -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<MappedDiscountData>(() => {
|
||||
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,
|
||||
|
||||
@@ -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<BNSProofResponse | null>(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 };
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user