From ba34f1e207db2d818f550f4100d71eff1d31a16d Mon Sep 17 00:00:00 2001 From: Garey Simpson <8252379+gareys@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:34:41 -0400 Subject: [PATCH 001/171] feat: allow aux funds for name registration (#1125) --- .../Basenames/RegistrationForm/index.tsx | 20 ++++++++----- apps/web/src/hooks/useCapabilitiesSafe.ts | 29 ++++++++++++------- apps/web/src/hooks/useRegisterNameCallback.ts | 2 +- .../src/hooks/useWriteContractsWithLogs.ts | 2 +- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/apps/web/src/components/Basenames/RegistrationForm/index.tsx b/apps/web/src/components/Basenames/RegistrationForm/index.tsx index 3d3634d336..79681e9bb0 100644 --- a/apps/web/src/components/Basenames/RegistrationForm/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationForm/index.tsx @@ -19,6 +19,7 @@ import Tooltip from 'apps/web/src/components/Tooltip'; import TransactionError from 'apps/web/src/components/TransactionError'; import { usePremiumEndDurationRemaining } from 'apps/web/src/hooks/useActiveEthPremiumAmount'; import useBasenameChain, { supportedChainIds } from 'apps/web/src/hooks/useBasenameChain'; +import useCapabilitiesSafe from 'apps/web/src/hooks/useCapabilitiesSafe'; import { useEthPriceFromUniswap } from 'apps/web/src/hooks/useEthPriceFromUniswap'; import { useDiscountedNameRegistrationPrice, @@ -144,12 +145,18 @@ export default function RegistrationForm() { [setReverseRecord], ); + const { auxiliaryFunds: auxiliaryFundsEnabled } = useCapabilitiesSafe({ + chainId: connectedChain?.id, + }); const { data: balance } = useBalance({ address, chainId: connectedChain?.id }); const insufficientBalanceToRegister = balance?.value !== undefined && price !== undefined && balance?.value < price; const correctChain = connectedChain?.id === basenameChain.id; + const insufficientFundsAndNoAuxFunds = insufficientBalanceToRegister && !auxiliaryFundsEnabled; const insufficientBalanceToRegisterAndCorrectChain = insufficientBalanceToRegister && correctChain; + const insufficientFundsNoAuxFundsAndCorrectChain = + !auxiliaryFundsEnabled && insufficientBalanceToRegisterAndCorrectChain; const hasResolvedUSDPrice = price !== undefined && ethUsdPrice !== undefined; const usdPrice = hasResolvedUSDPrice ? formatUsdPrice(price, ethUsdPrice) : '--.--'; @@ -247,14 +254,14 @@ export default function RegistrationForm() {

{formatEtherPrice(initialPrice)}

{formatEtherPrice(discountedPrice)} ETH @@ -263,7 +270,7 @@ export default function RegistrationForm() { ) : (

{formatEtherPrice(price)} ETH @@ -273,7 +280,7 @@ export default function RegistrationForm() { ${usdPrice} )}

- {insufficientBalanceToRegister ? ( + {insufficientFundsAndNoAuxFunds ? (

your ETH balance is insufficient

) : Boolean(nameIsFree && IS_EARLY_ACCESS) ? (

Discounted during Early Access.

@@ -316,9 +323,7 @@ export default function RegistrationForm() { type="button" variant={ButtonVariants.Black} size={ButtonSizes.Medium} - disabled={ - insufficientBalanceToRegisterAndCorrectChain || registerNameIsPending - } + disabled={insufficientFundsNoAuxFundsAndCorrectChain || registerNameIsPending} isLoading={registerNameIsPending} rounded fullWidth @@ -371,7 +376,6 @@ export default function RegistrationForm() { baseSingleYearEthCost={singleYearBasePrice} isOpen={premiumExplainerModalOpen} toggleModal={togglePremiumExplainerModal} - nameLength={selectedName?.length} /> )} diff --git a/apps/web/src/hooks/useCapabilitiesSafe.ts b/apps/web/src/hooks/useCapabilitiesSafe.ts index 09f9c419ba..c3142a5ebf 100644 --- a/apps/web/src/hooks/useCapabilitiesSafe.ts +++ b/apps/web/src/hooks/useCapabilitiesSafe.ts @@ -1,7 +1,7 @@ /* A hook to safely check wallet_getCapabilities support - Responsabilities: + Responsibilities: - Check for unsupported wallets (e.g: metamask) - Use experimental useCapabilities - Check atomicBatch (batchcall) and paymasterService for a given chain @@ -13,6 +13,11 @@ import { base } from 'viem/chains'; import { useAccount } from 'wagmi'; import { useCapabilities } from 'wagmi/experimental'; +// To add a new capability, add it to this list +const CAPABILITIES_LIST = ['atomicBatch', 'paymasterService', 'auxiliaryFunds'] as const; + +type ListedCapabilities = (typeof CAPABILITIES_LIST)[number]; + export type UseCapabilitiesSafeProps = { chainId?: Chain['id']; }; @@ -24,17 +29,21 @@ export default function useCapabilitiesSafe({ chainId }: UseCapabilitiesSafeProp const isMetamaskWallet = connector?.id === 'io.metamask'; const enabled = isConnected && !isMetamaskWallet; - const { data: capabilities } = useCapabilities({ query: { enabled } }); + const { data: rawCapabilities } = useCapabilities({ query: { enabled } }); const featureChainId = chainId ?? currentChainId ?? base.id; - const atomicBatchEnabled = - (capabilities && capabilities[featureChainId]?.atomicBatch?.supported === true) ?? false; - const paymasterServiceEnabled = - (capabilities && capabilities[featureChainId]?.paymasterService?.supported === true) ?? false; + function isCapabilitySupported(capability: ListedCapabilities) { + return ( + (rawCapabilities && rawCapabilities[featureChainId]?.[capability]?.supported === true) ?? + false + ); + } + + const capabilities = CAPABILITIES_LIST.reduce((acc, capability) => { + acc[capability] = isCapabilitySupported(capability); + return acc; + }, {} as Record); - return { - atomicBatchEnabled, - paymasterServiceEnabled, - }; + return capabilities; } diff --git a/apps/web/src/hooks/useRegisterNameCallback.ts b/apps/web/src/hooks/useRegisterNameCallback.ts index f812f1b08f..211a16cbc7 100644 --- a/apps/web/src/hooks/useRegisterNameCallback.ts +++ b/apps/web/src/hooks/useRegisterNameCallback.ts @@ -32,7 +32,7 @@ export function useRegisterNameCallback( const { address } = useAccount(); const { basenameChain } = useBasenameChain(); const { logError } = useErrors(); - const { paymasterServiceEnabled } = useCapabilitiesSafe({ + const { paymasterService: paymasterServiceEnabled } = useCapabilitiesSafe({ chainId: basenameChain.id, }); diff --git a/apps/web/src/hooks/useWriteContractsWithLogs.ts b/apps/web/src/hooks/useWriteContractsWithLogs.ts index 5628e2ed60..fcca09297d 100644 --- a/apps/web/src/hooks/useWriteContractsWithLogs.ts +++ b/apps/web/src/hooks/useWriteContractsWithLogs.ts @@ -55,7 +55,7 @@ export default function useWriteContractsWithLogs({ // Errors & Analytics const { logEventWithContext } = useAnalytics(); const { logError } = useErrors(); - const { atomicBatchEnabled } = useCapabilitiesSafe({ chainId: chain.id }); + const { atomicBatch: atomicBatchEnabled } = useCapabilitiesSafe({ chainId: chain.id }); const { chain: connectedChain } = useAccount(); const [batchCallsStatus, setBatchCallsStatus] = useState(BatchCallsStatus.Idle); From 04f658eabfc1994ea004bc8ae95a5fee7f5246c3 Mon Sep 17 00:00:00 2001 From: Brendan from DeFi Date: Tue, 22 Oct 2024 15:34:44 -0700 Subject: [PATCH 002/171] feat: convert basenames claim frame to open frame (#1141) * convert basenames claim frame to open frame * revert changes to FrameTheme * removing unused imports --- apps/web/app/frames/proxy-ip-check.ts | 5 ++++ .../api/basenames/frame/04_txSubmitted.ts | 24 ++++++++------- .../pages/api/basenames/frame/constants.ts | 2 ++ .../api/basenames/frame/frameResponses.ts | 17 ++++++++++- apps/web/pages/api/basenames/frame/tx.ts | 29 ++++++++++--------- .../UsernameProfileSectionFrames/Context.tsx | 4 ++- .../UsernameProfileSectionFrames/Frame.tsx | 3 ++ 7 files changed, 58 insertions(+), 26 deletions(-) diff --git a/apps/web/app/frames/proxy-ip-check.ts b/apps/web/app/frames/proxy-ip-check.ts index dcfaaec3be..a51ae6ec6d 100644 --- a/apps/web/app/frames/proxy-ip-check.ts +++ b/apps/web/app/frames/proxy-ip-check.ts @@ -3,6 +3,7 @@ import dns from 'dns/promises'; import ipaddr from 'ipaddr.js'; import { NextRequest, NextResponse } from 'next/server'; import { URL } from 'url'; +import { isDevelopment } from 'apps/web/src/constants'; export function withIPCheck(handler: (req: NextRequest) => Promise) { return async function (req: NextRequest) { @@ -10,6 +11,10 @@ export function withIPCheck(handler: (req: NextRequest) => Promise) { const url = searchParams.get('url'); if (url) { + if (isDevelopment) { + const res = await handler(req); + return res; + } try { const parsedUrl = new URL(url); const hostname = parsedUrl.hostname; diff --git a/apps/web/pages/api/basenames/frame/04_txSubmitted.ts b/apps/web/pages/api/basenames/frame/04_txSubmitted.ts index 5fd40b8897..9b094a29ab 100644 --- a/apps/web/pages/api/basenames/frame/04_txSubmitted.ts +++ b/apps/web/pages/api/basenames/frame/04_txSubmitted.ts @@ -42,20 +42,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) let name; try { - const result = await getFrameMessage(body, { - neynarApiKey: NEYNAR_API_KEY, - }); - isValid = result.isValid; - message = result.message; - if (!isValid) { - throw new Error('Message is not valid'); - } - if (!message) { - throw new Error('No message received'); + if (body.trustedData) { + const result = await getFrameMessage(body, { + neynarApiKey: NEYNAR_API_KEY, + }); + isValid = result.isValid; + message = result.message; + if (!isValid) { + throw new Error('Message is not valid'); + } + if (!message) { + throw new Error('No message received'); + } } const messageState = JSON.parse( - decodeURIComponent(message.state?.serialized), + decodeURIComponent(message?.state?.serialized ?? body.untrustedData.state), ) as TxFrameStateType; if (!messageState) { throw new Error('No message state received'); diff --git a/apps/web/pages/api/basenames/frame/constants.ts b/apps/web/pages/api/basenames/frame/constants.ts index f15862cf89..464e5cad8c 100644 --- a/apps/web/pages/api/basenames/frame/constants.ts +++ b/apps/web/pages/api/basenames/frame/constants.ts @@ -5,3 +5,5 @@ export const DOMAIN = isDevelopment ? `http://localhost:3000` : 'https://www.bas export const NEYNAR_API_KEY = process.env.NEYNAR_API_KEY; export const CHAIN = base; + +export const acceptedProtocols = { anonymous: 'vNext' }; diff --git a/apps/web/pages/api/basenames/frame/frameResponses.ts b/apps/web/pages/api/basenames/frame/frameResponses.ts index cc62728fb5..05299b2297 100644 --- a/apps/web/pages/api/basenames/frame/frameResponses.ts +++ b/apps/web/pages/api/basenames/frame/frameResponses.ts @@ -4,9 +4,11 @@ import initialImage from 'apps/web/pages/api/basenames/frame/assets/initial-imag import searchImage from 'apps/web/pages/api/basenames/frame/assets/search-image.png'; import txSucceededImage from 'apps/web/pages/api/basenames/frame/assets/tx-succeeded.png'; import txFailedImage from 'apps/web/pages/api/basenames/frame/assets/tx-failed.png'; -import { DOMAIN } from 'apps/web/pages/api/basenames/frame/constants'; +import { DOMAIN, acceptedProtocols } from 'apps/web/pages/api/basenames/frame/constants'; export const initialFrame: FrameMetadataResponse = getFrameMetadata({ + isOpenFrame: true, + accepts: acceptedProtocols, buttons: [ { label: 'Claim', @@ -19,6 +21,8 @@ export const initialFrame: FrameMetadataResponse = getFrameMetadata({ }); export const inputSearchValueFrame = getFrameHtmlResponse({ + isOpenFrame: true, + accepts: acceptedProtocols, buttons: [ { label: 'Continue', @@ -35,6 +39,8 @@ export const inputSearchValueFrame = getFrameHtmlResponse({ export const retryInputSearchValueFrame = (error?: string) => getFrameHtmlResponse({ + isOpenFrame: true, + accepts: acceptedProtocols, buttons: [ { label: 'Search again', @@ -58,6 +64,8 @@ export const buttonIndexToYears = { export const setYearsFrame = (targetName: string, formattedTargetName: string) => getFrameHtmlResponse({ + isOpenFrame: true, + accepts: acceptedProtocols, buttons: [ { label: '1 year', @@ -90,11 +98,14 @@ export const confirmationFrame = ( registrationPriceInEth: string, ) => getFrameHtmlResponse({ + isOpenFrame: true, + accepts: acceptedProtocols, buttons: [ { action: 'tx', label: `Claim name`, target: `${DOMAIN}/api/basenames/frame/tx`, + postUrl: `${DOMAIN}/api/basenames/frame/04_txSubmitted`, }, ], image: { @@ -112,6 +123,8 @@ export const confirmationFrame = ( export const txSucceededFrame = (name: string, transactionId: string) => getFrameHtmlResponse({ + isOpenFrame: true, + accepts: acceptedProtocols, buttons: [ { action: 'link', @@ -131,6 +144,8 @@ export const txSucceededFrame = (name: string, transactionId: string) => export const txRevertedFrame = (name: string, transactionId: string) => getFrameHtmlResponse({ + isOpenFrame: true, + accepts: acceptedProtocols, buttons: [ { action: 'link', diff --git a/apps/web/pages/api/basenames/frame/tx.ts b/apps/web/pages/api/basenames/frame/tx.ts index 6d92322efa..8d539c7cce 100644 --- a/apps/web/pages/api/basenames/frame/tx.ts +++ b/apps/web/pages/api/basenames/frame/tx.ts @@ -37,31 +37,34 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const body = req.body as FrameRequest; let message; let isValid; + let messageState; let name; let years; let priceInWei; let claimingAddress; try { - const result = await getFrameMessage(body, { - neynarApiKey: NEYNAR_API_KEY, - }); - isValid = result.isValid; - message = result.message; - if (!isValid) { - throw new Error('Message is not valid'); - } - if (!message) { - throw new Error('No message received'); + if (body.trustedData) { + const result = await getFrameMessage(body, { + neynarApiKey: NEYNAR_API_KEY, + }); + isValid = result.isValid; + message = result.message; + if (!isValid) { + throw new Error('Message is not valid'); + } + if (!message) { + throw new Error('No message received'); + } } - claimingAddress = message.address as `0x${string}`; + claimingAddress = (message?.address ?? body.untrustedData.address) as `0x${string}`; if (!claimingAddress) { throw new Error('No address received'); } - const messageState = JSON.parse( - decodeURIComponent(message.state?.serialized), + messageState = JSON.parse( + decodeURIComponent(message?.state?.serialized ?? body.untrustedData.state), ) as TxFrameStateType; if (!messageState) { throw new Error('No message state received'); diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Context.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Context.tsx index 6230c25669..9dfb63af6a 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Context.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Context.tsx @@ -24,7 +24,7 @@ import useReadBaseEnsTextRecords from 'apps/web/src/hooks/useReadBaseEnsTextReco import { UsernameTextRecordKeys } from 'apps/web/src/utils/usernames'; import { ActionType } from 'libs/base-ui/utils/logEvent'; import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; -import { namehash } from 'viem'; +import { EstimateGasExecutionError, namehash } from 'viem'; import { useAccount, useChainId, useConfig, useWriteContract } from 'wagmi'; import { sendTransaction, signTypedData, switchChain } from 'wagmi/actions'; @@ -148,6 +148,8 @@ export function FramesProvider({ children }: FramesProviderProps) { logEventWithContext('basename_profile_frame_invalid_chain_id', ActionType.error); } else if (error instanceof CouldNotChangeChainError) { logEventWithContext('basename_profile_frame_could_not_change_chain', ActionType.error); + } else if (error instanceof EstimateGasExecutionError) { + logEventWithContext('basename_profile_frame_insufficient_funds', ActionType.error); } else { logError(error, `${frame.postUrl ?? frame.title} failed to complete a frame transaction`); } diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Frame.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Frame.tsx index 537de7529d..ab1b158ba3 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Frame.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Frame.tsx @@ -16,6 +16,7 @@ import { } from 'apps/web/src/components/Basenames/UsernameProfileSectionFrames/FrameTheme'; import cn from 'classnames'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import { EstimateGasExecutionError } from 'viem'; type FrameProps = { url: string; @@ -82,6 +83,8 @@ export default function Frame({ url, className }: FrameProps) { : interactionData.transactionData.chainId; const requestedChainId = parseChainId(chainId); setError(`Must switch chain to ${requestedChainId}`); + } else if (err instanceof EstimateGasExecutionError) { + setError(`Insufficient funds for this transaction`); } else { if (signatureData) { setError('Error signing data'); From 2fee0a494ef7a414f33130cd7326363b7ee3790c Mon Sep 17 00:00:00 2001 From: omkar Date: Wed, 23 Oct 2024 10:53:44 -0400 Subject: [PATCH 003/171] Fix coupon code discount priority + talent protocol discount (#1146) * Fix coupon code discount priority + talent protocol discount * Add talent protocol css classes --- .../RegistrationLearnMoreModal/index.tsx | 9 ++++++--- .../hooks/useAggregatedDiscountValidators.ts | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx index 7cb38ac28b..4b2011840d 100644 --- a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx @@ -52,6 +52,9 @@ export default function RegistrationLearnMoreModal({ const BaseDotEthNFTRowClasses = classNames(rowClasses, { 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.BASE_DOT_ETH_NFT), }); + const TalentProtocolRowClasses = classNames(rowClasses, { + 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.TALENT_PROTOCOL), + }); const qualifiedClasses = classNames( 'flex flex-row items-center justify-center py-3 px-1 h-5 text-xs bg-green-0 rounded ml-3', @@ -161,13 +164,13 @@ export default function RegistrationLearnMoreModal({ width={30} height={30} wrapperClassName="rounded-full" - imageClassName={BaseDotEthNFTRowClasses} + imageClassName={TalentProtocolRowClasses} /> -

Builder score 50+

+

Builder score 50+

- {allActiveDiscounts.has(Discount.BASE_DOT_ETH_NFT) && ( + {allActiveDiscounts.has(Discount.TALENT_PROTOCOL) && (

Qualified

diff --git a/apps/web/src/hooks/useAggregatedDiscountValidators.ts b/apps/web/src/hooks/useAggregatedDiscountValidators.ts index b4c1c39c70..b4f9e6b8dc 100644 --- a/apps/web/src/hooks/useAggregatedDiscountValidators.ts +++ b/apps/web/src/hooks/useAggregatedDiscountValidators.ts @@ -24,10 +24,11 @@ export type MappedDiscountData = { export function findFirstValidDiscount( aggregatedData: MappedDiscountData, ): DiscountData | undefined { - const priorityOrder: Partial<{ [key in Discount]: number }> & { default: 2 } = { - [Discount.BNS_NAME]: 0, - [Discount.CB1]: 1, - default: 2, + const priorityOrder: Partial<{ [key in Discount]: number }> & { default: 3 } = { + [Discount.DISCOUNT_CODE]: 0, + [Discount.BNS_NAME]: 1, + [Discount.CB1]: 2, + default: 3, }; const sortedDiscounts = Object.values(aggregatedData).sort((a, b) => { @@ -53,7 +54,7 @@ export function useAggregatedDiscountValidators(code?: string) { const { data: BNSData, loading: loadingBNS } = useBNSAttestations(); const { data: DiscountCodeData, loading: loadingDiscountCode } = useDiscountCodeAttestations(code); - const { data: talentProtocolData, loading: loadingTalentProtocolAttestations } = + const { data: TalentProtocolData, loading: loadingTalentProtocolAttestations } = useTalentProtocolAttestations(); const loadingDiscounts = @@ -134,11 +135,11 @@ export function useAggregatedDiscountValidators(code?: string) { } if ( - talentProtocolData && - validator.discountValidator === talentProtocolData.discountValidatorAddress + TalentProtocolData && + validator.discountValidator === TalentProtocolData.discountValidatorAddress ) { discountMapping[Discount.TALENT_PROTOCOL] = { - ...talentProtocolData, + ...TalentProtocolData, discountKey: validator.key, }; } @@ -156,6 +157,7 @@ export function useAggregatedDiscountValidators(code?: string) { BaseDotEthData, BNSData, DiscountCodeData, + TalentProtocolData, ]); return { From 1263e0bab4b3bb0dd4a9bb6f99068dce00b4395f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Thu, 24 Oct 2024 16:54:16 -0400 Subject: [PATCH 004/171] replace copy (#1151) --- .../Basenames/UsernameProfileSectionBadges/Badges/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionBadges/Badges/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionBadges/Badges/index.tsx index e51bfb4268..a6d8a3cca3 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionBadges/Badges/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionBadges/Badges/index.tsx @@ -49,8 +49,7 @@ export const BADGE_INFO: Record< VERIFIED_IDENTITY: { name: 'Coinbase Verified ID', title: 'Coinbase Verified ID', - description: - "You've got a Coinbase account and you verified your ID. Thanks for being our customer.", + description: "You've got a Coinbase account and you verified your ID. Thanks for being legit!", cta: 'Get verified', ctaLink: 'https://coinbase.com/onchain-verify', image: verifiedIdentity, From c053f0ae1fe7f9b65345fc5bd506b7787590f374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Fri, 25 Oct 2024 10:30:11 -0400 Subject: [PATCH 005/171] hide basename promotional modal (#1152) --- apps/web/app/(basenames)/name/[username]/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/web/app/(basenames)/name/[username]/page.tsx b/apps/web/app/(basenames)/name/[username]/page.tsx index 99a517c41a..503b8f2057 100644 --- a/apps/web/app/(basenames)/name/[username]/page.tsx +++ b/apps/web/app/(basenames)/name/[username]/page.tsx @@ -1,7 +1,6 @@ import { BaseName } from '@coinbase/onchainkit/identity'; import ProfileProviders from 'apps/web/app/(basenames)/name/[username]/ProfileProviders'; import ErrorsProvider from 'apps/web/contexts/Errors'; -import DynamicProfilePromo from 'apps/web/src/components/Basenames/ProfilePromo/dynamic'; import UsernameProfile from 'apps/web/src/components/Basenames/UsernameProfile'; import { redirectIfNotNameOwner } from 'apps/web/src/utils/redirectIfNotNameOwner'; import { @@ -48,7 +47,6 @@ export default async function Username({ params }: UsernameProfileProps) {
-
From a2ddd5f166db49bd2839876df8f787a3c283d2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Fri, 25 Oct 2024 11:53:39 -0400 Subject: [PATCH 006/171] Add location to basename profile / TextRecords (#1153) --- .../Basenames/UsernameLocationField/index.tsx | 52 ++++++++++++++ .../Basenames/UsernameProfileCard/index.tsx | 70 ++++++++++++------- .../index.tsx | 6 ++ apps/web/src/components/Icon/Icon.tsx | 17 +++++ .../src/hooks/useReadBaseEnsTextRecords.ts | 1 + apps/web/src/utils/usernames.ts | 5 ++ 6 files changed, 124 insertions(+), 27 deletions(-) create mode 100644 apps/web/src/components/Basenames/UsernameLocationField/index.tsx diff --git a/apps/web/src/components/Basenames/UsernameLocationField/index.tsx b/apps/web/src/components/Basenames/UsernameLocationField/index.tsx new file mode 100644 index 0000000000..a0c363476a --- /dev/null +++ b/apps/web/src/components/Basenames/UsernameLocationField/index.tsx @@ -0,0 +1,52 @@ +import Fieldset from 'apps/web/src/components/Fieldset'; +import Input from 'apps/web/src/components/Input'; +import Label from 'apps/web/src/components/Label'; +import { + textRecordsKeysPlaceholderForDisplay, + USERNAME_LOCATION_MAX_LENGTH, + UsernameTextRecordKeys, +} from 'apps/web/src/utils/usernames'; +import { ChangeEvent, ReactNode, useCallback, useId } from 'react'; + +export type UsernameLocationFieldProps = { + labelChildren?: ReactNode; + onChange: (key: UsernameTextRecordKeys, value: string) => void; + value: string; + disabled?: boolean; +}; + +export default function UsernameLocationField({ + labelChildren = 'Location', + onChange, + value, + disabled = false, +}: UsernameLocationFieldProps) { + const onChangeLocation = useCallback( + (event: ChangeEvent) => { + const location = event.target.value; + if (location.length > USERNAME_LOCATION_MAX_LENGTH) { + event.preventDefault(); + } else { + if (onChange) onChange(UsernameTextRecordKeys.Location, location); + } + }, + [onChange], + ); + + const usernameLocationFieldId = useId(); + + return ( +
+ {labelChildren && } + +
+ ); +} diff --git a/apps/web/src/components/Basenames/UsernameProfileCard/index.tsx b/apps/web/src/components/Basenames/UsernameProfileCard/index.tsx index 6952861a8d..c6a5cdf9b3 100644 --- a/apps/web/src/components/Basenames/UsernameProfileCard/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileCard/index.tsx @@ -20,6 +20,7 @@ export default function UsernameProfileCard() { }); const textRecordDescription = existingTextRecords[UsernameTextRecordKeys.Description]; + const textRecordLocation = existingTextRecords[UsernameTextRecordKeys.Location]; const textRecordsSocial = textRecordsSocialFieldsEnabled.reduce( (previousValue, textRecordKey) => { @@ -29,9 +30,11 @@ export default function UsernameProfileCard() { {} as UsernameTextRecords, ); + const hasTextRecordsSocials = Object.values(textRecordsSocial).filter((v) => !!v).length > 0; + // TODO: Empty state / CTA to edit if owner const hasTextRecordsToDisplay = - !!textRecordDescription || Object.values(textRecordsSocial).filter((v) => !!v).length > 0; + !!textRecordDescription || !!textRecordLocation || hasTextRecordsSocials; if (!hasTextRecordsToDisplay) { return; @@ -47,32 +50,45 @@ export default function UsernameProfileCard() {

{textRecordDescription}

)} -
    - {textRecordsSocialFieldsEnabled.map( - (textRecordKey) => - !!existingTextRecords[textRecordKey] && ( -
  • - - - - - - {formatSocialFieldForDisplay(textRecordKey, existingTextRecords[textRecordKey])} - - -
  • - ), - )} -
+ {!!textRecordLocation && ( +

+ + + + {textRecordLocation} +

+ )} + {hasTextRecordsSocials && ( +
    + {textRecordsSocialFieldsEnabled.map( + (textRecordKey) => + !!existingTextRecords[textRecordKey] && ( +
  • + + + + + + {formatSocialFieldForDisplay( + textRecordKey, + existingTextRecords[textRecordKey], + )} + + +
  • + ), + )} +
+ )} ); } diff --git a/apps/web/src/components/Basenames/UsernameProfileSettingsManageProfile/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSettingsManageProfile/index.tsx index b6ee790046..9940f8a267 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSettingsManageProfile/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSettingsManageProfile/index.tsx @@ -17,6 +17,7 @@ import { UsernameTextRecordKeys, } from 'apps/web/src/utils/usernames'; import UsernameCastsField from 'apps/web/src/components/Basenames/UsernameCastsField'; +import UsernameLocationField from 'apps/web/src/components/Basenames/UsernameLocationField'; const settingTabClass = classNames( 'flex flex-col justify-between gap-8 text-gray/60 md:items-center p-4 md:p-8', @@ -72,6 +73,11 @@ export default function UsernameProfileSettingsManageProfile() { value={updatedTextRecords[UsernameTextRecordKeys.Description]} disabled={writeTextRecordsIsPending} /> +
{textRecordsSocialFieldsEnabled.map((textRecordKey) => ( diff --git a/apps/web/src/components/Icon/Icon.tsx b/apps/web/src/components/Icon/Icon.tsx index 89eb541a78..f68b34edfb 100644 --- a/apps/web/src/components/Icon/Icon.tsx +++ b/apps/web/src/components/Icon/Icon.tsx @@ -521,6 +521,23 @@ const ICONS: Record JSX.Element> = { /> ), + + map: ({ color, width, height }: SvgProps) => ( + + + + ), }; export function Icon({ name, color = 'white', width = '24', height = '24' }: IconProps) { diff --git a/apps/web/src/hooks/useReadBaseEnsTextRecords.ts b/apps/web/src/hooks/useReadBaseEnsTextRecords.ts index 42287f796f..979737d7a3 100644 --- a/apps/web/src/hooks/useReadBaseEnsTextRecords.ts +++ b/apps/web/src/hooks/useReadBaseEnsTextRecords.ts @@ -38,6 +38,7 @@ export default function useReadBaseEnsTextRecords({ [UsernameTextRecordKeys.Github]: '', [UsernameTextRecordKeys.Email]: '', [UsernameTextRecordKeys.Phone]: '', + [UsernameTextRecordKeys.Location]: '', [UsernameTextRecordKeys.Avatar]: '', [UsernameTextRecordKeys.Frames]: '', [UsernameTextRecordKeys.Casts]: '', diff --git a/apps/web/src/utils/usernames.ts b/apps/web/src/utils/usernames.ts index 8f5a0047b8..8e5c240611 100644 --- a/apps/web/src/utils/usernames.ts +++ b/apps/web/src/utils/usernames.ts @@ -63,6 +63,7 @@ export const USERNAME_MIN_CHARACTER_LENGTH = 3; export const USERNAME_MAX_CHARACTER_LENGTH = 20; export const USERNAME_DESCRIPTION_MAX_LENGTH = 200; +export const USERNAME_LOCATION_MAX_LENGTH = 100; // DANGER: Changing this post-mainnet launch means the stored data won't be accessible via the updated key export enum UsernameTextRecordKeys { @@ -75,6 +76,7 @@ export enum UsernameTextRecordKeys { Email = 'email', Phone = 'phone', Avatar = 'avatar', + Location = 'location', // Socials Github = 'com.github', @@ -177,6 +179,7 @@ export const textRecordsKeysEnabled = [ UsernameTextRecordKeys.Github, UsernameTextRecordKeys.Email, UsernameTextRecordKeys.Phone, + UsernameTextRecordKeys.Location, UsernameTextRecordKeys.Twitter, UsernameTextRecordKeys.Farcaster, UsernameTextRecordKeys.Lens, @@ -196,6 +199,7 @@ export const textRecordsKeysForDisplay = { [UsernameTextRecordKeys.Github]: 'Github', [UsernameTextRecordKeys.Email]: 'Email', [UsernameTextRecordKeys.Phone]: 'Phone', + [UsernameTextRecordKeys.Location]: 'Location', [UsernameTextRecordKeys.Twitter]: 'Twitter / X', [UsernameTextRecordKeys.Farcaster]: 'Farcaster', [UsernameTextRecordKeys.Lens]: 'Lens', @@ -215,6 +219,7 @@ export const textRecordsKeysPlaceholderForDisplay = { [UsernameTextRecordKeys.Github]: 'Username', [UsernameTextRecordKeys.Email]: 'Personal email', [UsernameTextRecordKeys.Phone]: '+1 415 ..', + [UsernameTextRecordKeys.Location]: 'New York, NY, USA', [UsernameTextRecordKeys.Twitter]: 'Username', [UsernameTextRecordKeys.Farcaster]: 'Username', [UsernameTextRecordKeys.Lens]: 'name.lens', From 07662778b70348a5a8916ea24230ce495ec73817 Mon Sep 17 00:00:00 2001 From: neodaoist <3170590+neodaoist@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:31:32 -0400 Subject: [PATCH 007/171] Update terms-of-service.md (#1154) * Update terms-of-service.md * Update terms-of-service.md --- apps/base-docs/docs/terms-of-service.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/base-docs/docs/terms-of-service.md b/apps/base-docs/docs/terms-of-service.md index 4a8c2d13d2..7bc6621333 100644 --- a/apps/base-docs/docs/terms-of-service.md +++ b/apps/base-docs/docs/terms-of-service.md @@ -7,11 +7,11 @@ hide_table_of_contents: true # Sequencer, Testnet, Basenames Interface Terms -Last Updated: August 5, 2024 +Last Updated: October 25, 2024 --- -We’re excited you’re interested in Base, a layer-two optimistic rollup on the Ethereum public blockchain. While we do not control Base, these Terms of Service (“Terms”) constitute a legally binding contract made between you and Coinbase Technologies, Inc. (“Coinbase,” “we,” or “us”) that governs your access to and use of the Coinbase Sequencer, Base Testnet, and Basenames Interface, each of which is defined below (collectively, the “Services”). By using the Services in any way, you agree to be bound by these Terms. If you do not accept the terms and conditions of these Terms, you are not permitted to access or otherwise use the Services. +We’re excited you’re interested in Base, a layer-two optimistic rollup on the Ethereum public blockchain. While we do not control Base, these Terms of Service (“Terms”) constitute a legally binding contract made between you and Coinbase Technologies, Inc. (“Coinbase,” “we,” or “us”) that governs your access to and use of the Coinbase Sequencer, Base Testnet, Basenames Interface, and Basenames Profile Pages each of which is defined below (collectively, the “Services”). By using the Services in any way, you agree to be bound by these Terms. If you do not accept the terms and conditions of these Terms, you are not permitted to access or otherwise use the Services. **BEFORE WE INCLUDE ANY OTHER DETAILS, WE WANT TO GIVE YOU NOTICE OF SOMETHING UP FRONT: BY AGREEING TO THESE TERMS, YOU AND WE AGREE TO RESOLVE ANY DISPUTES WE MAY HAVE WITH EACH OTHER VIA BINDING ARBITRATION OR IN SMALL CLAIMS COURT (INSTEAD OF A COURT OF GENERAL JURISDICTION), AND YOU AGREE TO DO SO AS AN INDIVIDUAL (INSTEAD OF, FOR EXAMPLE, AS A REPRESENTATIVE OR MEMBER OF A CLASS IN A CLASS ACTION). TO THE EXTENT THAT THE LAW ALLOWS, YOU ALSO WAIVE YOUR RIGHT TO A TRIAL BY JURY. FOR MORE INFORMATION, SEE OUR [ARBITRATION AGREEMENT](https://docs.base.org/docs/arbitration) “DISPUTE RESOLUTION, ARBITRATION AGREEMENT, CLASS ACTION WAIVER, AND JURY TRIAL WAIVER.”** @@ -21,7 +21,7 @@ The Base protocol (“Base”) is an open source, optimistic rollup protocol tha ### 2. Basenames -Basenames is an open source blockchain-based naming protocol that maintains a registry of all domains and subdomains on Base through a series of smart contracts deployed on Base. Basenames is not part of the Services. Users may, through interacting with the Basenames, search such registry, register domains and subdomains and manage their registered names. The Basenames interface located at https://base.org/names (the “Basenames Interface”) is one, but not the exclusive, means of accessing Basenames. You are responsible for conducting your own diligence on other interfaces enabling you to access Basenames to understand the fees and risks that they present. +Basenames is an open source blockchain-based naming protocol that maintains a registry of all domains and subdomains on Base through a series of smart contracts deployed on Base. Basenames is not part of the Services. Users may, through interacting with the Basenames, search such registry, register domains and subdomains and manage their registered names, including by adding metadata and other information (e.g., URLs) to the corresponding text records on the Basenames smart contract (such metadata and other information, the “Basename Profile Information”). The Basenames interface located at https://base.org/names (the “Basenames Interface”) is one, but not the exclusive, means of accessing Basenames. You are responsible for conducting your own diligence on other interfaces enabling you to access Basenames to understand the fees and risks that they present. You understand that anyone can register and own a domain name (and its subdomains) that is not already registered on the registry maintained by Basenames. You further understand that names registered on the registry maintained by Basenames may expire and you are responsible for monitoring and renewing the registration of such names. You acknowledge that Coinbase is not able to forcibly remove, prevent or otherwise interfere with the ability of any person to register a domain name on the registry operated by Basenames and you hereby acknowledge that Coinbase will not be liable for any claims or damages whatsoever associated with your use, inability to use any domain names subject of registration, or to be registered, on the registry maintained by Basenames. You agree that Basenames is purely non-custodial, meaning you are solely responsible for the custody of the cryptographic private keys to the digital asset wallets you hold and use to access Basenames. @@ -46,6 +46,7 @@ Coinbase offers the following Services that enable you to access and interact wi - **The Sequencer:** The Coinbase Sequencer is a node operated by Coinbase that receives, records, and reports transactions on Base. While The Coinbase Sequencer is, initially, the only sequencer node supporting transactions on Base, additional nodes may be provided by third parties in the future and there are other mechanisms for submitting transactions through Ethereum. The Coinbase Sequencer does not store, take custody of, control, send, or receive your virtual currency, except for receiving applicable gas fees. It also does not have the ability to modify, reverse, or otherwise alter any submitted transactions, and will not have access to your private key or the ability to control value on your behalf. We reserve the right to charge and modify the fees in connection with your use of the Coinbase Sequencer. These fees may also be subject to taxes under applicable law. - **Base Testnet:** The Base Testnet is a test environment that allows you to build applications integrated with Base. You are permitted to access and use the Base Testnet only to test and improve the experience, security, and design of Base or applications built on Base, subject to these Terms. Base Testnet Tokens will not be converted into any future rewards offered by Coinbase. Coinbase may change, discontinue, or terminate, temporarily or permanently, all or any part of the Base Testnet, at any time and without notice. - **Basenames Interface:** The Basenames Interface is a web application and graphical user display operated by Coinbase and located at base.org/names. It enables you to interact with Basenames by creating blockchain messages that you can sign and broadcast to Base using your Wallet. The Basenames Interface will not have access to your private key at any point. +- **Basenames Profile Pages:** Coinbase also operates a web application and graphical user display (the “Basenames Profile Pages”) that renders information about all registered Basenames domains and subdomains on Base, including any Basename Profile Information associated therewith. You understand that the information displayed on the Basenames Profile Pages, including all Basename Profile Information, is stored and publicly available on the Basenames decentralized protocol. Coinbase provides the Basenames Profile Pages only as a convenience, does not have control over any of third party content appearing therein, and does not warrant or endorse, nor bear responsibility for the availability or legitimacy of, the content on or accessible from any Basenames Profile Page (including any resources, interactive features, or links to Third-Party Services (as defined below) displayed therein). When viewing any Basenames Profile Page, you should assume that Coinbase has not verified the safety or legitimacy of, any content, resources, interactive features, or links appearing on such Basenames Profile Page (including any Farcaster Frames (or other open source products that provide substantially similar functionality) rendered thereon). It is your responsibility to ensure that you fully understand the nature of any links or other interactive features that you may be able to access on a Basenames Profile Page, including any financial risks that you may be exposed to when interacting with a Third-Party Service. ### 7. Acceptable Use @@ -53,7 +54,7 @@ You agree that you will not use the Services in any manner or for any purpose ot - Infringe or violate the intellectual property rights or any other rights of anyone else (including Coinbase) or attempt to decompile, disassemble, or reverse engineer the Services; - Violate any applicable law or regulation, including without limitation, any applicable anti-money laundering laws, anti-terrorism laws, export control laws, end user restrictions, privacy laws or economic sanctions laws/regulations, including those administered by the U.S. Department of Treasury’s Office of Foreign Assets Control; -- Use the Services in a way that is dangerous, harmful, fraudulent, misleading, deceptive, threatening, harassing, defamatory, obscene, or otherwise objectionable; +- Use the Services in a way that is illegal, dangerous, harmful, fraudulent, misleading, deceptive, threatening, harassing, defamatory, obscene, or otherwise objectionable; - Violate, compromise, or interfere with the security, integrity, or availability of any computer, network, or technology associated with the Services, including using the Services in a manner that constitutes excessive or abusive usage, attempts to disrupt, attack, or interfere with other users, or otherwise impacts the stability of the Services. - Use any Coinbase brands, logos, or trademarks (or any brands, logos, or trademarks that are confusingly similar) without our express prior written approval, which we may withhold at our discretion for any reason. @@ -97,7 +98,7 @@ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, BASE, THE BRIDGING SMART CONT ### 16. Limitation of Liability -TO THE MAXIMUM EXTENT PERMITTED BY LAW, NEITHER THE COINBASE ENTITIES NOR ITS SERVICE PROVIDERS INVOLVED IN CREATING, PRODUCING, OR DELIVERING THE SERVICES WILL BE LIABLE FOR ANY INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES, OR DAMAGES FOR LOST PROFITS, LOST REVENUES, LOST SAVINGS, LOST BUSINESS OPPORTUNITY, LOSS OF DATA OR GOODWILL, SERVICE INTERRUPTION, COMPUTER DAMAGE OR SYSTEM FAILURE, INTELLECTUAL PROPERTY INFRINGEMENT, OR THE COST OF SUBSTITUTE SERVICES OF ANY KIND ARISING OUT OF OR IN CONNECTION WITH THESE TERMS OR FROM THE USE OF OR INABILITY TO USE THE SERVICES, BASE, THE BRIDGING SMART CONTRACTS, OR BASENAMES, WHETHER BASED ON WARRANTY, CONTRACT, TORT (INCLUDING NEGLIGENCE), PRODUCT LIABILITY OR ANY OTHER LEGAL THEORY, AND WHETHER OR NOT THE COINBASE ENTITIES OR ITS SERVICE PROVIDERS HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGE, EVEN IF A LIMITED REMEDY SET FORTH HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. +TO THE MAXIMUM EXTENT PERMITTED BY LAW, NEITHER THE COINBASE ENTITIES NOR THEIR RESPECTIVE SERVICE PROVIDERS INVOLVED IN CREATING, PRODUCING, OR DELIVERING THE SERVICES WILL BE LIABLE FOR ANY INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES, OR DAMAGES FOR LOST PROFITS, LOST REVENUES, LOST SAVINGS, LOST BUSINESS OPPORTUNITY, LOSS OF DATA OR GOODWILL, SERVICE INTERRUPTION, COMPUTER DAMAGE OR SYSTEM FAILURE, INTELLECTUAL PROPERTY INFRINGEMENT, OR THE COST OF SUBSTITUTE SERVICES OF ANY KIND ARISING OUT OF OR IN CONNECTION WITH THESE TERMS OR FROM THE USE OF OR INABILITY TO USE THE SERVICES, BASE, THE BRIDGING SMART CONTRACTS, OR BASENAMES, WHETHER BASED ON WARRANTY, CONTRACT, TORT (INCLUDING NEGLIGENCE), PRODUCT LIABILITY OR ANY OTHER LEGAL THEORY, AND WHETHER OR NOT THE COINBASE ENTITIES OR THEIR RESPECTIVE SERVICE PROVIDERS HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGE, EVEN IF A LIMITED REMEDY SET FORTH HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE. TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT WILL THE COINBASE ENTITIES’ TOTAL LIABILITY ARISING OUT OF OR IN CONNECTION WITH THESE TERMS OR FROM THE USE OF OR INABILITY TO USE THE SERVICES, BASE, THE BRIDGING SMART CONTRACTS, OR BASENAMES EXCEED THE AMOUNTS YOU HAVE PAID OR ARE PAYABLE BY YOU TO THE COINBASE ENTITIES FOR USE OF THE SERVICES OR ONE HUNDRED DOLLARS ($100), WHICHEVER IS HIGHER. THE EXCLUSIONS AND LIMITATIONS OF DAMAGES SET FORTH ABOVE ARE FUNDAMENTAL ELEMENTS OF THE BASIS OF THE BARGAIN BETWEEN COINBASE AND YOU. From cf6699961aed63371cdd8c2fdc7dd28885f2cc95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Mon, 28 Oct 2024 11:23:30 -0400 Subject: [PATCH 008/171] max age 30m for jobs (#1163) --- apps/web/app/(base-org)/jobs/page.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/web/app/(base-org)/jobs/page.tsx b/apps/web/app/(base-org)/jobs/page.tsx index 39593fdb20..d992386006 100644 --- a/apps/web/app/(base-org)/jobs/page.tsx +++ b/apps/web/app/(base-org)/jobs/page.tsx @@ -15,8 +15,13 @@ export const metadata: Metadata = { }, }; +// Revalidate every 30 minutes +export const revalidate = 60 * 30; + async function getJobs() { - const res = await fetch(`${greenhouseApiUrl}/boards/basejobs/jobs?content=true`); + const res = await fetch(`${greenhouseApiUrl}/boards/basejobs/jobs?content=true`, { + next: { revalidate }, + }); try { const { jobs } = (await res.json()) as { jobs: JobType[] }; return jobs; From d2c46c444bbda79f59a785a39b01baee7ff8297f Mon Sep 17 00:00:00 2001 From: Brendan from DeFi Date: Mon, 28 Oct 2024 10:23:34 -0700 Subject: [PATCH 009/171] restore cookie banner (#1169) --- apps/web/src/components/CookieBannerWrapper/index.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/CookieBannerWrapper/index.tsx b/apps/web/src/components/CookieBannerWrapper/index.tsx index 85a2c3635e..87e6bb63f7 100644 --- a/apps/web/src/components/CookieBannerWrapper/index.tsx +++ b/apps/web/src/components/CookieBannerWrapper/index.tsx @@ -1,6 +1,6 @@ 'use client'; -// import { CookieBanner } from '@coinbase/cookie-banner'; +import { CookieBanner } from '@coinbase/cookie-banner'; export const cookieBannerTheme = { colors: { @@ -39,12 +39,17 @@ export const cookieBannerTheme = { tablet: 768, }, zIndex: { + hidden: 0, + normal: 1, + elevated: 2, high: 2, + extraHigh: 3, + backdrop: 999, overlay: 1000, + top: 1001, }, }; export default function CookieBannerWrapper() { - // return ; - return null; + return ; } From 872d2b396b4e54c6a8edbe682932543a0f421546 Mon Sep 17 00:00:00 2001 From: "Ian L." <143178815+cbfyi@users.noreply.github.com> Date: Tue, 29 Oct 2024 04:22:25 +0000 Subject: [PATCH 010/171] Add addresses for Fault Proof contracts on Mainnet L1 (#1168) * Add addresses for Fault Proof contracts on Mainnet L1 Similar to https://github.com/base-org/web/pull/656, but for mainnet. * Update apps/base-docs/docs/building-with-base/base-contracts.md Co-authored-by: Alexis Williams <148368153+awilliams1-cb@users.noreply.github.com> --------- Co-authored-by: Alexis Williams <148368153+awilliams1-cb@users.noreply.github.com> --- apps/base-docs/docs/building-with-base/base-contracts.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/base-docs/docs/building-with-base/base-contracts.md b/apps/base-docs/docs/building-with-base/base-contracts.md index 5773756324..b33a8cf881 100644 --- a/apps/base-docs/docs/building-with-base/base-contracts.md +++ b/apps/base-docs/docs/building-with-base/base-contracts.md @@ -73,12 +73,20 @@ hide_table_of_contents: true | Name | Address | | :--------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | AddressManager | [0x8EfB6B5c4767B09Dc9AA6Af4eAA89F749522BaE2](https://etherscan.io/address/0x8EfB6B5c4767B09Dc9AA6Af4eAA89F749522BaE2) | +| AnchorStateRegistryProxy | [0xdB9091e48B1C42992A1213e6916184f9eBDbfEDf](https://etherscan.io/address/0xdB9091e48B1C42992A1213e6916184f9eBDbfEDf) | +| DelayedWETHProxy (FDG) | [0xa2f2aC6F5aF72e494A227d79Db20473Cf7A1FFE8](https://etherscan.io/address/0xa2f2aC6F5aF72e494A227d79Db20473Cf7A1FFE8) | +| DelayedWETHProxy (PDG) | [0x3E8a0B63f57e975c268d610ece93da5f78c01321](https://etherscan.io/address/0x3E8a0B63f57e975c268d610ece93da5f78c01321) | +| DisputeGameFactoryProxy | [0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e](https://etherscan.io/address/0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e) | +| FaultDisputeGame | [0xCd3c0194db74C23807D4B90A5181e1B28cF7007C](https://etherscan.io/address/0xCd3c0194db74C23807D4B90A5181e1B28cF7007C) | | L1CrossDomainMessenger | [0x866E82a600A1414e583f7F13623F1aC5d58b0Afa](https://etherscan.io/address/0x866E82a600A1414e583f7F13623F1aC5d58b0Afa) | | L1ERC721Bridge | [0x608d94945A64503E642E6370Ec598e519a2C1E53](https://etherscan.io/address/0x608d94945A64503E642E6370Ec598e519a2C1E53) | | L1StandardBridge | [0x3154Cf16ccdb4C6d922629664174b904d80F2C35](https://etherscan.io/address/0x3154Cf16ccdb4C6d922629664174b904d80F2C35) | | L2OutputOracle | [0x56315b90c40730925ec5485cf004d835058518A0](https://etherscan.io/address/0x56315b90c40730925ec5485cf004d835058518A0) | +| MIPS | [0x16e83cE5Ce29BF90AD9Da06D2fE6a15d5f344ce4](https://etherscan.io/address/0x16e83cE5Ce29BF90AD9Da06D2fE6a15d5f344ce4) | | OptimismMintableERC20Factory | [0x05cc379EBD9B30BbA19C6fA282AB29218EC61D84](https://etherscan.io/address/0x05cc379EBD9B30BbA19C6fA282AB29218EC61D84) | | OptimismPortal | [0x49048044D57e1C92A77f79988d21Fa8fAF74E97e](https://etherscan.io/address/0x49048044D57e1C92A77f79988d21Fa8fAF74E97e) | +| PermissionedDisputeGame | [0x19009dEBF8954B610f207D5925EEDe827805986e](https://etherscan.io/address/0x19009dEBF8954B610f207D5925EEDe827805986e) | +| PreimageOracle | [0x9c065e11870B891D214Bc2Da7EF1f9DDFA1BE277](https://etherscan.io/address/0x9c065e11870B891D214Bc2Da7EF1f9DDFA1BE277) | | ProxyAdmin | [0x0475cBCAebd9CE8AfA5025828d5b98DFb67E059E](https://etherscan.io/address/0x0475cBCAebd9CE8AfA5025828d5b98DFb67E059E) | | SystemConfig | [0x73a79Fab69143498Ed3712e519A88a918e1f4072](https://etherscan.io/address/0x73a79Fab69143498Ed3712e519A88a918e1f4072) | | SystemDictator | [0x1fE3fdd1F0193Dd657C0a9AAC37314D6B479E557](https://etherscan.io/address/0x1fE3fdd1F0193Dd657C0a9AAC37314D6B479E557) | From 8d72e3c78dbe0456a7087ff39e39a22fc97faf1c Mon Sep 17 00:00:00 2001 From: "Ian L." <143178815+cbfyi@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:45:41 +0000 Subject: [PATCH 011/171] Update Fault Proof contract addresses for Sepolia (#1167) * Update Fault Proof contract addresses for Sepolia Post-Granite, new implementation contracts were deployed, but their new addresses were not reflected in the docs. It is quite simple to independently verify. The `DisputeGameFactoryProxy` should not have changed. Staring with the game implementations, simply call the read method, `gameImpls(uint32)`, to get the implementation for game type 0 (`FaultDisputeGame`), and 1 (`PermissionedDisputeGame`). From those addresses, you can then call the game implementations to get the `MIPS` / VM (`vm()`), and `DelayedWETHProxy` (`weth()`) addresses. Sample command: cast call --rpc-url 0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1 "gameImpls(uint32)" 0 The `AnchorStateRegistryProxy` should remain unchanged as well. * Update apps/base-docs/docs/building-with-base/base-contracts.md Co-authored-by: Alexis Williams <148368153+awilliams1-cb@users.noreply.github.com> --------- Co-authored-by: Alexis Williams <148368153+awilliams1-cb@users.noreply.github.com> --- .../docs/building-with-base/base-contracts.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/base-docs/docs/building-with-base/base-contracts.md b/apps/base-docs/docs/building-with-base/base-contracts.md index b33a8cf881..0267e59379 100644 --- a/apps/base-docs/docs/building-with-base/base-contracts.md +++ b/apps/base-docs/docs/building-with-base/base-contracts.md @@ -105,18 +105,19 @@ Certain contracts are mandatory according to the [OP Stack SDK](https://stack.op | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | AddressManager | [0x709c2B8ef4A9feFc629A8a2C1AF424Dc5BD6ad1B](https://sepolia.etherscan.io/address/0x709c2B8ef4A9feFc629A8a2C1AF424Dc5BD6ad1B) | | AnchorStateRegistryProxy | [0x4C8BA32A5DAC2A720bb35CeDB51D6B067D104205](https://sepolia.etherscan.io/address/0x4C8BA32A5DAC2A720bb35CeDB51D6B067D104205) | -| DelayedWETHProxy | [0x7698b262B7a534912c8366dD8a531672deEC634e](https://sepolia.etherscan.io/address/0x7698b262B7a534912c8366dD8a531672deEC634e) | +| DelayedWETHProxy (FDG) | [0x489c2E5ebe0037bDb2DC039C5770757b8E54eA1F](https://sepolia.etherscan.io/address/0x489c2E5ebe0037bDb2DC039C5770757b8E54eA1F) | +| DelayedWETHProxy (PDG) | [0x27A6128F707de3d99F89Bf09c35a4e0753E1B808](https://sepolia.etherscan.io/address/0x27A6128F707de3d99F89Bf09c35a4e0753E1B808) | | DisputeGameFactoryProxy | [0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1](https://sepolia.etherscan.io/address/0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1) | -| FaultDisputeGame | [0x48F9F3190b7B5231cBf2aD1A1315AF7f6A554020](https://sepolia.etherscan.io/address/0x48F9F3190b7B5231cBf2aD1A1315AF7f6A554020) | +| FaultDisputeGame | [0x5062792ED6A85cF72a1424a1b7f39eD0f7972a4B](https://sepolia.etherscan.io/address/0x5062792ED6A85cF72a1424a1b7f39eD0f7972a4B) | | L1CrossDomainMessenger | [0xC34855F4De64F1840e5686e64278da901e261f20](https://sepolia.etherscan.io/address/0xC34855F4De64F1840e5686e64278da901e261f20) | | L1ERC721Bridge | [0x21eFD066e581FA55Ef105170Cc04d74386a09190](https://sepolia.etherscan.io/address/0x21eFD066e581FA55Ef105170Cc04d74386a09190) | | L1StandardBridge | [0xfd0Bf71F60660E2f608ed56e1659C450eB113120](https://sepolia.etherscan.io/address/0xfd0Bf71F60660E2f608ed56e1659C450eB113120) | | L2OutputOracle | [0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254](https://sepolia.etherscan.io/address/0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254) | -| MIPS | [0xFF760A87E41144b336E29b6D4582427dEBdB6dee](https://sepolia.etherscan.io/address/0xFF760A87E41144b336E29b6D4582427dEBdB6dee) | +| MIPS | [0x47B0E34C1054009e696BaBAAd56165e1e994144d](https://sepolia.etherscan.io/address/0x47B0E34C1054009e696BaBAAd56165e1e994144d) | | OptimismMintableERC20Factory | [0xb1efB9650aD6d0CC1ed3Ac4a0B7f1D5732696D37](https://sepolia.etherscan.io/address/0xb1efB9650aD6d0CC1ed3Ac4a0B7f1D5732696D37) | | OptimismPortal | [0x49f53e41452C74589E85cA1677426Ba426459e85](https://sepolia.etherscan.io/address/0x49f53e41452C74589E85cA1677426Ba426459e85) | -| PermissionedDisputeGame | [0x54966d5A42a812D0dAaDe1FA2321FF8b102d1ee1](https://sepolia.etherscan.io/address/0x54966d5A42a812D0dAaDe1FA2321FF8b102d1ee1) | -| PreimageOracle | [0x627F825CBd48c4102d36f287be71f4234426b9e4](https://sepolia.etherscan.io/address/0x627F825CBd48c4102d36f287be71f4234426b9e4) | +| PermissionedDisputeGame | [0x593D20C4c69485B95D11507239BE2C725ea2A6fD](https://sepolia.etherscan.io/address/0x593D20C4c69485B95D11507239BE2C725ea2A6fD) | +| PreimageOracle | [0x92240135b46fc1142dA181f550aE8f595B858854](https://sepolia.etherscan.io/address/0x92240135b46fc1142dA181f550aE8f595B858854) | | ProxyAdmin | [0x0389E59Aa0a41E4A413Ae70f0008e76CAA34b1F3](https://sepolia.etherscan.io/address/0x0389E59Aa0a41E4A413Ae70f0008e76CAA34b1F3) | | SystemConfig | [0xf272670eb55e895584501d564AfEB048bEd26194](https://sepolia.etherscan.io/address/0xf272670eb55e895584501d564AfEB048bEd26194) | From a162ccade79ad7d414b4ddae7a43668d944aa65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Tue, 29 Oct 2024 10:43:31 -0400 Subject: [PATCH 012/171] Fix: Dynamically import three.js libraries (#1166) * dynamically import three js libraries * move all models to models file * smarter import * specific import * dynamic import of rigidbody * better loading / color / comments --- apps/web/app/(base-org)/(root)/page.tsx | 10 +- .../components/ThreeHero/DynamicRigidBody.tsx | 34 +++ .../src/components/ThreeHero/PhysicsMesh.tsx | 83 +++++ apps/web/src/components/ThreeHero/index.tsx | 284 ++++-------------- apps/web/src/components/ThreeHero/models.tsx | 193 ++++++++++-- 5 files changed, 354 insertions(+), 250 deletions(-) create mode 100644 apps/web/src/components/ThreeHero/DynamicRigidBody.tsx create mode 100644 apps/web/src/components/ThreeHero/PhysicsMesh.tsx diff --git a/apps/web/app/(base-org)/(root)/page.tsx b/apps/web/app/(base-org)/(root)/page.tsx index 679adf60ee..6eec19f27b 100644 --- a/apps/web/app/(base-org)/(root)/page.tsx +++ b/apps/web/app/(base-org)/(root)/page.tsx @@ -17,16 +17,16 @@ import Link from 'apps/web/src/components/Link'; import MissionSection from 'apps/web/src/components/base-org/root/MissionSection'; import OpLogo from 'apps/web/public/images/op_logo.svg'; -const DynamicThreeHero = dynamic(async () => import('apps/web/src/components/ThreeHero'), { - ssr: false, -}); - +const Scene = dynamic( + async () => import('apps/web/src/components/ThreeHero').then((mod) => mod.Scene), + { ssr: false }, +); export default async function Home() { return (
- +
diff --git a/apps/web/src/components/ThreeHero/DynamicRigidBody.tsx b/apps/web/src/components/ThreeHero/DynamicRigidBody.tsx new file mode 100644 index 0000000000..30b2af3239 --- /dev/null +++ b/apps/web/src/components/ThreeHero/DynamicRigidBody.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { useEffect, useState, forwardRef } from 'react'; +import type { RigidBodyProps, RapierRigidBody, RigidBody } from '@react-three/rapier'; +import { useErrors } from 'apps/web/contexts/Errors'; + +// RigidBody cannot be imported using dynamic() due to some import issues +export const DynamicRigidBody = forwardRef( + ({ children, ...props }, ref) => { + const [RigidBodyDynamic, setRigidBody] = useState(); + const { logError } = useErrors(); + + // Import needs to happens on render + useEffect(() => { + import('@react-three/rapier') + .then((mod) => { + setRigidBody(() => mod.RigidBody); + }) + .catch((error) => logError(error, 'Failed to load RigidBody')); + }, [logError]); + + if (!RigidBodyDynamic) return null; + + return ( + + {children} + + ); + }, +); + +DynamicRigidBody.displayName = 'DynamicRigidBody'; + +export default DynamicRigidBody; diff --git a/apps/web/src/components/ThreeHero/PhysicsMesh.tsx b/apps/web/src/components/ThreeHero/PhysicsMesh.tsx new file mode 100644 index 0000000000..9edfea371b --- /dev/null +++ b/apps/web/src/components/ThreeHero/PhysicsMesh.tsx @@ -0,0 +1,83 @@ +'use client'; + +// Libraries +import { useMemo, useRef } from 'react'; +import dynamic from 'next/dynamic'; + +// Components +import DynamicRigidBody from 'apps/web/src/components/ThreeHero/DynamicRigidBody'; + +// 3D libraries - types +import type { BallArgs, RapierRigidBody } from '@react-three/rapier'; + +// 3D Libraries - static - These cannot be dynamically imported +import { Vector3 } from 'three'; +import { randFloatSpread } from 'three/src/math/MathUtils.js'; +import { useFrame, useThree } from '@react-three/fiber'; + +// Dynamic - react-three/rapier +const BallCollider = dynamic( + async () => import('@react-three/rapier').then((mod) => mod.BallCollider), + { ssr: false }, +); + +const ballArguments: BallArgs = [1]; + +export function PhysicsMesh({ + r = randFloatSpread, + scale = 1, + gravityEffect = 0.2, + children, +}: { + r?: (a: number) => number; + scale?: number; + gravityEffect?: number; + children: React.ReactNode; +}) { + const rigidBodyApiRef = useRef(null); + const { viewport } = useThree(); + const vec = new Vector3(); + + const randomNumberBetween = (min: number, max: number) => { + const posOrNeg = Math.random() > 0.5 ? 1 : -1; + const num = Math.min(Math.random() * (max - min) + min, 14); + return posOrNeg * num; + }; + + const pos = useMemo( + () => + new Vector3( + randomNumberBetween(viewport.width * 0.5, viewport.width * 2), + randomNumberBetween(viewport.height * 0.5, viewport.height * 2), + randomNumberBetween(viewport.width * 0.5, viewport.width * 2), + ), + [viewport.height, viewport.width], + ); + const rot = useMemo(() => new Vector3(r(Math.PI), r(Math.PI), r(Math.PI)), [r]); + + useFrame(() => { + if (!rigidBodyApiRef.current) return; + const vector = rigidBodyApiRef.current.translation(); + const vector3 = new Vector3(vector.x, vector.y, vector.z); + rigidBodyApiRef.current.applyImpulse( + vec.copy(vector3).negate().multiplyScalar(gravityEffect), + true, + ); + }); + + return ( + + + {children} + + ); +} diff --git a/apps/web/src/components/ThreeHero/index.tsx b/apps/web/src/components/ThreeHero/index.tsx index 05ab824587..8bbefb86f6 100644 --- a/apps/web/src/components/ThreeHero/index.tsx +++ b/apps/web/src/components/ThreeHero/index.tsx @@ -1,26 +1,34 @@ /* eslint-disable react/no-unknown-property */ 'use client'; -import * as THREE from 'three'; -import { useRef, useMemo, useState, useEffect, Suspense, useCallback } from 'react'; -import { Canvas, Euler, useFrame, useThree, Vector3 } from '@react-three/fiber'; -import { Lightformer, Environment, Html, Center } from '@react-three/drei'; -import { - Physics, - BallCollider, - Vector3Tuple, - CylinderCollider, - CylinderArgs, - RapierRigidBody, - RigidBody, - BallArgs, -} from '@react-three/rapier'; -import { EffectComposer, Bloom, SMAA } from '@react-three/postprocessing'; +// Libraries +import classNames from 'classnames'; +import { useRef, useState, useEffect, Suspense, useCallback } from 'react'; +import dynamic from 'next/dynamic'; + +// Components +import Image, { StaticImageData } from 'next/image'; +import Link from 'apps/web/src/components/Link'; + +// 3D libraries - types +import type { Vector3 } from '@react-three/fiber'; +import type { Vector3Tuple } from '@react-three/rapier'; +import type { + ColorRepresentation, + Mesh, + BufferGeometry, + NormalBufferAttributes, + Material, +} from 'three'; + +import { Bloom, SMAA, EffectComposer } from '@react-three/postprocessing'; + +// Assets import { - BlackMaterial, - BaseLogoModel, + BaseLogo, + Boxes, Lightning, - blue, + Balls, Controller, Eth, Globe, @@ -30,16 +38,42 @@ import { Play, Blobby, Cursor, + Pointer, } from './models'; import baseLogo from './assets/base-logo.svg'; - -// Environnment import environmentLight from './assets/environmentLight.jpg'; -import Image, { StaticImageData } from 'next/image'; -import { useMediaQuery } from 'usehooks-ts'; -import classNames from 'classnames'; -import Link from 'apps/web/src/components/Link'; + +// 3D libraries - dynamic imports + +// Dynamic - react-three/fiber +const Canvas = dynamic(async () => import('@react-three/fiber').then((mod) => mod.Canvas), { + ssr: false, +}); + +// Dynamic - react-three/drei +const Html = dynamic(async () => import('@react-three/drei').then((mod) => mod.Html), { + ssr: false, +}); + +const Lightformer = dynamic( + async () => import('@react-three/drei').then((mod) => mod.Lightformer), + { + ssr: false, + }, +); + +const Environment = dynamic( + async () => import('@react-three/drei').then((mod) => mod.Environment), + { + ssr: false, + }, +); + +// Dynamic - react-three/rapier +const Physics = dynamic(async () => import('@react-three/rapier').then((mod) => mod.Physics), { + ssr: false, +}); /* The Main Scene @@ -53,11 +87,7 @@ const mintLink = const gravity: Vector3Tuple = [0, 0, 0]; -const sceneFogArguments: [color: THREE.ColorRepresentation, near: number, far: number] = [ - '#111', - 2.5, - 7, -]; +const sceneFogArguments: [color: ColorRepresentation, near: number, far: number] = ['#111', 2.5, 7]; const sceneCamera = { position: [0, 0, 5] as Vector3 }; const sceneSphereArguments: [radius: number, widthSegments: number, heightSegments: number] = [ @@ -69,7 +99,7 @@ type MouseXY = { y: number; }; -export default function Scene(): JSX.Element { +export function Scene(): JSX.Element { const [isVisible, setIsVisible] = useState(false); const [position, setPosition] = useState({ x: 0, y: 0 }); const [isActive, setIsActive] = useState(true); @@ -143,7 +173,7 @@ export default function Scene(): JSX.Element { - + @@ -204,12 +234,8 @@ const light3: Vector3 = [10, 1, 0]; const light4: Vector3 = [10, 10, 0]; function EnvironmentSetup() { const onLightUpdated = useCallback( - ( - self: THREE.Mesh< - THREE.BufferGeometry, - THREE.Material | THREE.Material[] - >, - ) => self.lookAt(0, 0, 0), + (self: Mesh, Material | Material[]>) => + self.lookAt(0, 0, 0), [], ); return ( @@ -271,187 +297,3 @@ export function Everything() { ); } - -const boxGeometry: [width: number, height: number, depth: number] = [0.5, 0.5, 0.5]; -const boxesCount = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; -function Boxes() { - const boxes = useMemo( - () => - boxesCount.map((id) => { - return ( - - - - - - - ); - }), - [], - ); - - return {boxes}; -} - -const sphereGeometry: [width: number, height: number, depth: number] = [0.25, 64, 64]; -const sphereCount = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; -function Balls() { - const boxes = useMemo( - () => - sphereCount.map((id) => { - return ( - - - - - - - ); - }), - [], - ); - - return {boxes}; -} - -const baseLogoRotation: Euler = [Math.PI / 2, 0, 0]; -const baseLogoPosition: [x: number, y: number, z: number] = [0, 0, -10]; - -function BaseLogo() { - const logoRef = useRef(null); - const doneRef = useRef(false); - const isMobile = useMediaQuery('(max-width: 769px)'); - - useFrame(({ pointer }) => { - if (!logoRef.current) return; - - if (doneRef.current) { - logoRef.current.rotation.y = THREE.MathUtils.lerp( - logoRef.current.rotation.y, - pointer.x, - 0.05, - ); - logoRef.current.rotation.x = THREE.MathUtils.lerp( - logoRef.current.rotation.x, - -pointer.y, - 0.05, - ); - } else { - logoRef.current.rotation.y = THREE.MathUtils.lerp(logoRef.current.rotation.y, 0, 0.05); - } - logoRef.current.position.z = THREE.MathUtils.lerp(logoRef.current.position.z, 0, 0.05); - - // lerp never gets to 0 - if (logoRef.current.position.z > -0.01) { - doneRef.current = true; - } - }); - - const cylinderArguments: CylinderArgs = useMemo(() => [10, isMobile ? 1.1 : 2], [isMobile]); - - return ( - - - -
- -
-
-
- ); -} - -const ballArguments: BallArgs = [1]; -export function PhysicsMesh({ - vec = new THREE.Vector3(), - r = THREE.MathUtils.randFloatSpread, - scale = 1, - gravityEffect = 0.2, - children, -}: { - vec?: THREE.Vector3; - r?: (a: number) => number; - scale?: number; - gravityEffect?: number; - children: React.ReactNode; -}) { - const rigidBodyApiRef = useRef(null); - const { viewport } = useThree(); - - const randomNumberBetween = (min: number, max: number) => { - const posOrNeg = Math.random() > 0.5 ? 1 : -1; - const num = Math.min(Math.random() * (max - min) + min, 14); - return posOrNeg * num; - }; - - const pos = useMemo( - () => - new THREE.Vector3( - randomNumberBetween(viewport.width * 0.5, viewport.width * 2), - randomNumberBetween(viewport.height * 0.5, viewport.height * 2), - randomNumberBetween(viewport.width * 0.5, viewport.width * 2), - ), - [viewport.height, viewport.width], - ); - const rot = useMemo(() => new THREE.Vector3(r(Math.PI), r(Math.PI), r(Math.PI)), [r]); - - useFrame(() => { - if (!rigidBodyApiRef.current) return; - const vector = rigidBodyApiRef.current.translation(); - const vector3 = new THREE.Vector3(vector.x, vector.y, vector.z); - rigidBodyApiRef.current.applyImpulse( - vec.copy(vector3).negate().multiplyScalar(gravityEffect), - true, - ); - }); - - return ( - - - {children} - - ); -} - -const pointerPosition: Vector3 = [0, 0, 0]; -const pointerLightPosition: Vector3 = [0, 0, 10]; -function Pointer() { - const vec = new THREE.Vector3(); - const rigidBodyApiRef = useRef(null); - const light = useRef(null); - const isMobile = useMediaQuery('(max-width: 769px)'); - - useFrame(({ pointer, viewport }) => { - rigidBodyApiRef.current?.setNextKinematicTranslation( - vec.set((pointer.x * viewport.width) / 2, (pointer.y * viewport.height) / 2, 0), - ); - light.current?.position.set(0, 0, 10); - light.current?.lookAt((pointer.x * viewport.width) / 2, (pointer.y * viewport.height) / 2, 0); - }); - - const ballColliderArgs: BallArgs = useMemo(() => [isMobile ? 1 : 2], [isMobile]); - - return ( - <> - - - - - - - ); -} diff --git a/apps/web/src/components/ThreeHero/models.tsx b/apps/web/src/components/ThreeHero/models.tsx index 433d9091f4..dd41115fe5 100644 --- a/apps/web/src/components/ThreeHero/models.tsx +++ b/apps/web/src/components/ThreeHero/models.tsx @@ -1,14 +1,12 @@ /* eslint-disable react/no-unknown-property */ 'use client'; -import { useGLTF, Center } from '@react-three/drei'; -import { MeshProps, Vector3, Euler, useLoader } from '@react-three/fiber'; +// Libraries +import { useMemo, useRef } from 'react'; +import { useMediaQuery } from 'usehooks-ts'; +import dynamic from 'next/dynamic'; -import * as THREE from 'three'; -import { SVGLoader, SVGResult } from 'three-stdlib'; -import { PhysicsMesh } from './index'; - -/* glbs */ +// Assets import controlerModel from './assets/controller.glb'; import ethModel from './assets/eth.glb'; import globeModel from './assets/globe.glb'; @@ -19,18 +17,49 @@ import playModel from './assets/play.glb'; import objectModel from './assets/object.glb'; import logoModel from './assets/logo.glb'; import cursorModel from './assets/cursor.glb'; - -/* svgs */ import lightningSVG from './assets/lightning.svg'; -import { useMemo } from 'react'; -import { ExtrudeGeometryOptions } from 'three'; + +// 3D libraries - types +import type { Mesh, Shape, DirectionalLight, ExtrudeGeometryOptions, Group } from 'three'; +import type { SVGResult } from 'three-stdlib'; +import type { MeshProps, Vector3, Euler } from '@react-three/fiber'; +import type { CylinderArgs, BallArgs, RapierRigidBody } from '@react-three/rapier'; + +// 3D Libraries - static - These cannot be dynamically imported +import { Vector3 as ThreeVector3 } from 'three'; +import { lerp } from 'three/src/math/MathUtils.js'; +import { useGLTF } from '@react-three/drei'; +import { useFrame, useLoader } from '@react-three/fiber'; +import { SVGLoader } from 'three-stdlib'; +import DynamicRigidBody from 'apps/web/src/components/ThreeHero/DynamicRigidBody'; + +// 3D libraries - dynamic imports +const PhysicsMesh = dynamic(async () => import('./PhysicsMesh').then((mod) => mod.PhysicsMesh), { + ssr: false, +}); + +// Dynamic - react-three/rapier +const BallCollider = dynamic( + async () => import('@react-three/rapier').then((mod) => mod.BallCollider), + { ssr: false }, +); + +const CylinderCollider = dynamic( + async () => import('@react-three/rapier').then((mod) => mod.CylinderCollider), + { ssr: false }, +); + +// Dynamic - react-three/drei +const Center = dynamic(async () => import('@react-three/drei').then((mod) => mod.Center), { + ssr: false, +}); /* load draco locally (v1.5.7) */ useGLTF.setDecoderPath('draco/'); /* Constants */ export const blue = '#105eff'; -const blackColor = new THREE.Color(0.08, 0.08, 0.08); +export const blackColor = '#444'; // equivalent to rgb(0.08, 0.08, 0.08) /* Models */ export function BlackMaterial() { @@ -41,9 +70,125 @@ export function MetalMaterial() { return ; } +const boxGeometry: [width: number, height: number, depth: number] = [0.5, 0.5, 0.5]; +const boxesCount = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +export function Boxes() { + const boxes = useMemo( + () => + boxesCount.map((id) => { + return ( + + + + + + + ); + }), + [], + ); + + return {boxes}; +} + +const sphereGeometry: [width: number, height: number, depth: number] = [0.25, 64, 64]; +const sphereCount = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +export function Balls() { + const boxes = useMemo( + () => + sphereCount.map((id) => { + return ( + + + + + + + ); + }), + [], + ); + + return {boxes}; +} + +const baseLogoRotation: Euler = [Math.PI / 2, 0, 0]; +const baseLogoPosition: [x: number, y: number, z: number] = [0, 0, -10]; + +export function BaseLogo() { + const logoRef = useRef(null); + const doneRef = useRef(false); + const isMobile = useMediaQuery('(max-width: 769px)'); + + useFrame(({ pointer }) => { + if (!logoRef.current) return; + + if (doneRef.current) { + logoRef.current.rotation.y = lerp(logoRef.current.rotation.y, pointer.x, 0.05); + logoRef.current.rotation.x = lerp(logoRef.current.rotation.x, -pointer.y, 0.05); + } else { + logoRef.current.rotation.y = lerp(logoRef.current.rotation.y, 0, 0.05); + } + logoRef.current.position.z = lerp(logoRef.current.position.z, 0, 0.05); + + // lerp never gets to 0 + if (logoRef.current.position.z > -0.01) { + doneRef.current = true; + } + }); + + const cylinderArguments: CylinderArgs = useMemo(() => [10, isMobile ? 1.1 : 2], [isMobile]); + + return ( + + + +
+ +
+
+
+ ); +} + +const pointerPosition: Vector3 = [0, 0, 0]; +const pointerLightPosition: Vector3 = [0, 0, 10]; + +export function Pointer() { + const vec = new ThreeVector3(); + const rigidBodyApiRef = useRef(null); + const light = useRef(null); + const isMobile = useMediaQuery('(max-width: 769px)'); + + useFrame(({ pointer, viewport }) => { + rigidBodyApiRef.current?.setNextKinematicTranslation( + vec.set((pointer.x * viewport.width) / 2, (pointer.y * viewport.height) / 2, 0), + ); + light.current?.position.set(0, 0, 10); + light.current?.lookAt((pointer.x * viewport.width) / 2, (pointer.y * viewport.height) / 2, 0); + }); + + const ballColliderArgs: BallArgs = useMemo(() => [isMobile ? 1 : 2], [isMobile]); + + return ( + <> + + + + + + + ); +} + export function BaseLogoModel() { const { nodes } = useGLTF(logoModel); - const model = nodes.Base_Logo as THREE.Mesh; + const model = nodes.Base_Logo as Mesh; return (
@@ -57,7 +202,7 @@ export function BaseLogoModel() { export function Lightning() { const svg = useLoader(SVGLoader, lightningSVG.src); const shapes = (svg as SVGResult).paths[0].toShapes(true); - const extrudeArguments: [shapes: THREE.Shape[], options: ExtrudeGeometryOptions] = useMemo( + const extrudeArguments: [shapes: Shape[], options: ExtrudeGeometryOptions] = useMemo( () => [ shapes, { @@ -85,7 +230,7 @@ export function Lightning() { export function Controller(props: MeshProps) { const { nodes } = useGLTF(controlerModel); - const model = nodes.Controller as THREE.Mesh; + const model = nodes.Controller as Mesh; return ( @@ -97,7 +242,7 @@ export function Controller(props: MeshProps) { export function Eth() { const { nodes } = useGLTF(ethModel); - const model = nodes.ETH as THREE.Mesh; + const model = nodes.ETH as Mesh; return ( @@ -109,7 +254,7 @@ export function Eth() { export function Globe() { const { nodes } = useGLTF(globeModel); - const model = nodes.Globe as THREE.Mesh; + const model = nodes.Globe as Mesh; return ( @@ -127,7 +272,7 @@ const phoneHeight = 0.86; const phoneDimension: [width?: number | undefined, height?: number] = [phoneWidth, phoneHeight]; export function Phone() { const { nodes } = useGLTF(phoneModel); - const model = nodes.Cylinder as THREE.Mesh; + const model = nodes.Cylinder as Mesh; return ( @@ -143,7 +288,7 @@ export function Phone() { export function Headphones() { const { nodes } = useGLTF(headphonesModel); - const model = nodes.Headphones as THREE.Mesh; + const model = nodes.Headphones as Mesh; return ( @@ -155,7 +300,7 @@ export function Headphones() { export function Spikey() { const { nodes } = useGLTF(spikeyModel); - const model = nodes.Spikey as THREE.Mesh; + const model = nodes.Spikey as Mesh; return ( @@ -167,7 +312,7 @@ export function Spikey() { export function Play() { const { nodes } = useGLTF(playModel); - const model = nodes.Play as THREE.Mesh; + const model = nodes.Play as Mesh; return ( @@ -179,7 +324,7 @@ export function Play() { export function Blobby() { const { nodes } = useGLTF(objectModel); - const model = nodes.Object_02 as THREE.Mesh; + const model = nodes.Object_02 as Mesh; return ( @@ -191,8 +336,8 @@ export function Blobby() { export function Cursor() { const { nodes } = useGLTF(cursorModel); - const cursor = nodes.Cursor as THREE.Mesh; - const cursor1 = nodes.Cursor1 as THREE.Mesh; + const cursor = nodes.Cursor as Mesh; + const cursor1 = nodes.Cursor1 as Mesh; return (
From 935e3cd8b853983f31455417c0854e1e3620025c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Tue, 29 Oct 2024 14:47:12 -0400 Subject: [PATCH 013/171] dynamic file import (#1173) --- apps/web/app/(base-org)/(root)/page.tsx | 8 ++------ apps/web/src/components/ThreeHero/dynamic.tsx | 10 ++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 apps/web/src/components/ThreeHero/dynamic.tsx diff --git a/apps/web/app/(base-org)/(root)/page.tsx b/apps/web/app/(base-org)/(root)/page.tsx index 6eec19f27b..2d0f6fb81e 100644 --- a/apps/web/app/(base-org)/(root)/page.tsx +++ b/apps/web/app/(base-org)/(root)/page.tsx @@ -12,21 +12,17 @@ import TransactionsFeesSection from 'apps/web/src/components/base-org/root/Trans import BuildAndRewardSection from 'apps/web/src/components/base-org/root/BuildAndRewardSection'; import ErrorsProvider from 'apps/web/contexts/Errors'; import BlogSection from 'apps/web/src/components/base-org/root/BlogSection'; -import dynamic from 'next/dynamic'; import Link from 'apps/web/src/components/Link'; import MissionSection from 'apps/web/src/components/base-org/root/MissionSection'; import OpLogo from 'apps/web/public/images/op_logo.svg'; +import SceneDynamic from 'apps/web/src/components/ThreeHero/dynamic'; -const Scene = dynamic( - async () => import('apps/web/src/components/ThreeHero').then((mod) => mod.Scene), - { ssr: false }, -); export default async function Home() { return (
- +
diff --git a/apps/web/src/components/ThreeHero/dynamic.tsx b/apps/web/src/components/ThreeHero/dynamic.tsx new file mode 100644 index 0000000000..284a44215f --- /dev/null +++ b/apps/web/src/components/ThreeHero/dynamic.tsx @@ -0,0 +1,10 @@ +'use client'; + +import dynamic from 'next/dynamic'; + +const SceneDynamic = dynamic( + async () => import('apps/web/src/components/ThreeHero').then((mod) => mod.Scene), + { ssr: false }, +); + +export default SceneDynamic; From c713d25caaeaba780d1ac41020a86add6eaf9cd6 Mon Sep 17 00:00:00 2001 From: Garey Simpson <8252379+gareys@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:20:24 -0400 Subject: [PATCH 014/171] feat: basename search view profile for name if registered (#1124) * feat: basename search view profile for name if registered * better responsive stlying --- .../RegistrationSearchInput/index.tsx | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/web/src/components/Basenames/RegistrationSearchInput/index.tsx b/apps/web/src/components/Basenames/RegistrationSearchInput/index.tsx index ab12bef9eb..70bf4844c1 100644 --- a/apps/web/src/components/Basenames/RegistrationSearchInput/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationSearchInput/index.tsx @@ -9,7 +9,7 @@ import { useIsNameAvailable } from 'apps/web/src/hooks/useIsNameAvailable'; import { formatBaseEthDomain, validateEnsDomainName } from 'apps/web/src/utils/usernames'; import classNames from 'classnames'; import { ActionType } from 'libs/base-ui/utils/logEvent'; -import { useCallback, useEffect, useId, useRef, useState } from 'react'; +import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'; import { useDebounceValue } from 'usehooks-ts'; import Tooltip from 'apps/web/src/components/Tooltip'; import { InformationCircleIcon } from '@heroicons/react/16/solid'; @@ -19,6 +19,7 @@ import { RegistrationSearchInputProps, RegistrationSearchInputVariant, } from './types'; +import Link from 'apps/web/src/components/Link'; function SuggestionEntry({ suggestion, @@ -166,6 +167,10 @@ export default function RegistrationSearchInput({ }, ); + const registeredContentClasses = classNames( + 'flex grow-0 w-full flex-row items-center justify-between', + ); + const inputIconClasses = classNames( 'absolute top-1/2 z-9 flex -translate-y-1/2 items-center scale-75 md:scale-100', { @@ -184,6 +189,11 @@ export default function RegistrationSearchInput({ 'px-3 py-2 text-sm': variant === RegistrationSearchInputVariant.Small, }); + const mutedStatus = classNames('text-gray-60', { + 'text-sm': variant === RegistrationSearchInputVariant.Large, + 'text-xs': variant === RegistrationSearchInputVariant.Small, + }); + const spinnerWrapperClasses = classNames('flex w-full items-center justify-center', { // Equivalent to the dropdown when one name is available 'h-[6.75rem]': variant === RegistrationSearchInputVariant.Large, @@ -257,6 +267,11 @@ export default function RegistrationSearchInput({ handleSelectName(debouncedSearch); }, [debouncedSearch, handleSelectName]); + const formattedBaseEthDomain = useMemo( + () => formatBaseEthDomain(debouncedSearch, basenameChain.id), + [basenameChain.id, debouncedSearch], + ); + return (
@@ -304,9 +317,12 @@ export default function RegistrationSearchInput({

) : ( <> -

- {formatBaseEthDomain(debouncedSearch, basenameChain.id)} is already registered -

+ +
+ {formattedBaseEthDomain} + Registered +
+ {suggestions.length > 0 ? ( <> From d91cf721baae43171a98abe17ba70ca32827f00e Mon Sep 17 00:00:00 2001 From: Matthew Bunday Date: Wed, 30 Oct 2024 12:48:30 -0400 Subject: [PATCH 015/171] Upgrade next (#1179) --- apps/web/package.json | 2 +- yarn.lock | 132 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 7f567ff333..d94f6f6d68 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -55,7 +55,7 @@ "jsdom": "^25.0.0", "jsonwebtoken": "^9.0.2", "kysely": "^0.27.3", - "next": "^14.2.5", + "next": "^14.2.16", "node-fetch": "^3.3.0", "permissionless": "^0.1.41", "pg": "^8.12.0", diff --git a/yarn.lock b/yarn.lock index 8b4d622b26..1ed09680cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -414,7 +414,7 @@ __metadata: jsdom: ^25.0.0 jsonwebtoken: ^9.0.2 kysely: ^0.27.3 - next: ^14.2.5 + next: ^14.2.16 node-fetch: ^3.3.0 permissionless: ^0.1.41 pg: ^8.12.0 @@ -5311,6 +5311,13 @@ __metadata: languageName: node linkType: hard +"@next/env@npm:14.2.16": + version: 14.2.16 + resolution: "@next/env@npm:14.2.16" + checksum: a81d98802fc0268ab1a042c9dba1db114db6bfff8d0054d3fa3d45322c19ff77713c3e46226e0c9231c706e760ba4ede483a3261e8a0e30f3806817349324f01 + languageName: node + linkType: hard + "@next/env@npm:14.2.5": version: 14.2.5 resolution: "@next/env@npm:14.2.5" @@ -5364,6 +5371,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-arm64@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-darwin-arm64@npm:14.2.16" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-darwin-arm64@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-darwin-arm64@npm:14.2.5" @@ -5385,6 +5399,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-x64@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-darwin-x64@npm:14.2.16" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@next/swc-darwin-x64@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-darwin-x64@npm:14.2.5" @@ -5420,6 +5441,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-gnu@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-linux-arm64-gnu@npm:14.2.16" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-arm64-gnu@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-linux-arm64-gnu@npm:14.2.5" @@ -5441,6 +5469,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-musl@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-linux-arm64-musl@npm:14.2.16" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-arm64-musl@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-linux-arm64-musl@npm:14.2.5" @@ -5462,6 +5497,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-gnu@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-linux-x64-gnu@npm:14.2.16" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-x64-gnu@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-linux-x64-gnu@npm:14.2.5" @@ -5483,6 +5525,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-musl@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-linux-x64-musl@npm:14.2.16" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-x64-musl@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-linux-x64-musl@npm:14.2.5" @@ -5504,6 +5553,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-arm64-msvc@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-win32-arm64-msvc@npm:14.2.16" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-win32-arm64-msvc@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-win32-arm64-msvc@npm:14.2.5" @@ -5525,6 +5581,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-ia32-msvc@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-win32-ia32-msvc@npm:14.2.16" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@next/swc-win32-ia32-msvc@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-win32-ia32-msvc@npm:14.2.5" @@ -5546,6 +5609,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-x64-msvc@npm:14.2.16": + version: 14.2.16 + resolution: "@next/swc-win32-x64-msvc@npm:14.2.16" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@next/swc-win32-x64-msvc@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-win32-x64-msvc@npm:14.2.5" @@ -20252,7 +20322,7 @@ __metadata: languageName: node linkType: hard -"next@npm:*, next@npm:^14.2.5": +"next@npm:*": version: 14.2.5 resolution: "next@npm:14.2.5" dependencies: @@ -20434,6 +20504,64 @@ __metadata: languageName: node linkType: hard +"next@npm:^14.2.16": + version: 14.2.16 + resolution: "next@npm:14.2.16" + dependencies: + "@next/env": 14.2.16 + "@next/swc-darwin-arm64": 14.2.16 + "@next/swc-darwin-x64": 14.2.16 + "@next/swc-linux-arm64-gnu": 14.2.16 + "@next/swc-linux-arm64-musl": 14.2.16 + "@next/swc-linux-x64-gnu": 14.2.16 + "@next/swc-linux-x64-musl": 14.2.16 + "@next/swc-win32-arm64-msvc": 14.2.16 + "@next/swc-win32-ia32-msvc": 14.2.16 + "@next/swc-win32-x64-msvc": 14.2.16 + "@swc/helpers": 0.5.5 + busboy: 1.6.0 + caniuse-lite: ^1.0.30001579 + graceful-fs: ^4.2.11 + postcss: 8.4.31 + styled-jsx: 5.1.1 + peerDependencies: + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-ia32-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + "@playwright/test": + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: 907cf5e34884ae28f922586c072738af26a7fcbf39a9b64ecddf0ed29cce1e07b0f4caea9dd77c716d5435a22f8a0942836d4888843cc8ca33650f0bab358bf1 + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" From 59785fb74a9ac5d928c679da299b7cdabe675038 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:05:34 -0400 Subject: [PATCH 016/171] chore(deps): bump next from 12.2.3 to 14.2.10 (#1180) Bumps [next](https://github.com/vercel/next.js) from 12.2.3 to 14.2.10. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v12.2.3...v14.2.10) --- updated-dependencies: - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 255 ++++++++++++++++++--------------------------------- 2 files changed, 92 insertions(+), 165 deletions(-) diff --git a/package.json b/package.json index cd40e72866..35276b310f 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "classnames": "^2.3.2", "clsx": "^1.2.1", "moment": "^2.29.4", - "next": "12.2.3", + "next": "14.2.10", "tslib": "^2.3.0" }, "lint-staged": { diff --git a/yarn.lock b/yarn.lock index 1ed09680cf..e9bb29d77a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2090,7 +2090,7 @@ __metadata: jest-environment-jsdom: ^29.4.1 lint-staged: ">=10" moment: ^2.29.4 - next: 12.2.3 + next: 14.2.10 pinst: ">=2" prettier: ^2.7.1 react: ^18.2.0 @@ -5297,13 +5297,6 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:12.2.3": - version: 12.2.3 - resolution: "@next/env@npm:12.2.3" - checksum: e96dcbea3fbb3d6b6a0799fe2e41c4929ded383659709f5bd00b6479b0711b89891b72fb28753b428933871d5fd91792fa54bc64a74a038c704b5862d40821ec - languageName: node - linkType: hard - "@next/env@npm:13.5.6": version: 13.5.6 resolution: "@next/env@npm:13.5.6" @@ -5311,6 +5304,13 @@ __metadata: languageName: node linkType: hard +"@next/env@npm:14.2.10": + version: 14.2.10 + resolution: "@next/env@npm:14.2.10" + checksum: edff4b124b11f9fcea4df239b6feb11c16ef49c97d018e098a2fcfc16f4aba27092569fb7bdec148d8b1113ac9b2d2a6b460940287ef5636ad801ac779ffc08c + languageName: node + linkType: hard + "@next/env@npm:14.2.16": version: 14.2.16 resolution: "@next/env@npm:14.2.16" @@ -5343,30 +5343,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-android-arm-eabi@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-android-arm-eabi@npm:12.2.3" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@next/swc-android-arm64@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-android-arm64@npm:12.2.3" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@next/swc-darwin-arm64@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-darwin-arm64@npm:12.2.3" +"@next/swc-darwin-arm64@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-darwin-arm64@npm:13.5.6" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-darwin-arm64@npm:13.5.6" +"@next/swc-darwin-arm64@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-darwin-arm64@npm:14.2.10" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -5385,16 +5371,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-darwin-x64@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-darwin-x64@npm:12.2.3" +"@next/swc-darwin-x64@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-darwin-x64@npm:13.5.6" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-darwin-x64@npm:13.5.6" +"@next/swc-darwin-x64@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-darwin-x64@npm:14.2.10" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -5413,30 +5399,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-freebsd-x64@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-freebsd-x64@npm:12.2.3" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@next/swc-linux-arm-gnueabihf@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-linux-arm-gnueabihf@npm:12.2.3" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@next/swc-linux-arm64-gnu@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-linux-arm64-gnu@npm:12.2.3" +"@next/swc-linux-arm64-gnu@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-linux-arm64-gnu@npm:13.5.6" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-linux-arm64-gnu@npm:13.5.6" +"@next/swc-linux-arm64-gnu@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-linux-arm64-gnu@npm:14.2.10" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard @@ -5455,16 +5427,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-linux-arm64-musl@npm:12.2.3" +"@next/swc-linux-arm64-musl@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-linux-arm64-musl@npm:13.5.6" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-linux-arm64-musl@npm:13.5.6" +"@next/swc-linux-arm64-musl@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-linux-arm64-musl@npm:14.2.10" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard @@ -5483,16 +5455,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-linux-x64-gnu@npm:12.2.3" +"@next/swc-linux-x64-gnu@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-linux-x64-gnu@npm:13.5.6" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-linux-x64-gnu@npm:13.5.6" +"@next/swc-linux-x64-gnu@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-linux-x64-gnu@npm:14.2.10" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard @@ -5511,16 +5483,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-linux-x64-musl@npm:12.2.3" +"@next/swc-linux-x64-musl@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-linux-x64-musl@npm:13.5.6" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-linux-x64-musl@npm:13.5.6" +"@next/swc-linux-x64-musl@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-linux-x64-musl@npm:14.2.10" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard @@ -5539,16 +5511,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-win32-arm64-msvc@npm:12.2.3" +"@next/swc-win32-arm64-msvc@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-win32-arm64-msvc@npm:13.5.6" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-win32-arm64-msvc@npm:13.5.6" +"@next/swc-win32-arm64-msvc@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-win32-arm64-msvc@npm:14.2.10" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -5567,16 +5539,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-win32-ia32-msvc@npm:12.2.3" +"@next/swc-win32-ia32-msvc@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-win32-ia32-msvc@npm:13.5.6" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-win32-ia32-msvc@npm:13.5.6" +"@next/swc-win32-ia32-msvc@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-win32-ia32-msvc@npm:14.2.10" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -5595,16 +5567,16 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:12.2.3": - version: 12.2.3 - resolution: "@next/swc-win32-x64-msvc@npm:12.2.3" +"@next/swc-win32-x64-msvc@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-win32-x64-msvc@npm:13.5.6" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-win32-x64-msvc@npm:13.5.6" +"@next/swc-win32-x64-msvc@npm:14.2.10": + version: 14.2.10 + resolution: "@next/swc-win32-x64-msvc@npm:14.2.10" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -7868,15 +7840,6 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:0.4.3": - version: 0.4.3 - resolution: "@swc/helpers@npm:0.4.3" - dependencies: - tslib: ^2.4.0 - checksum: 5c2f173e950dd3929d84ae48b3586a274d5a874e7cf2013b3d8081e4f8c723fa3a4d4e63b263e84bb7f06431f87b640e91a12655410463c81a3dc2bbc15eceda - languageName: node - linkType: hard - "@swc/helpers@npm:0.5.2": version: 0.5.2 resolution: "@swc/helpers@npm:0.5.2" @@ -11855,7 +11818,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001332, caniuse-lite@npm:^1.0.30001406, caniuse-lite@npm:^1.0.30001524, caniuse-lite@npm:^1.0.30001579, caniuse-lite@npm:^1.0.30001599, caniuse-lite@npm:^1.0.30001640": +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001406, caniuse-lite@npm:^1.0.30001524, caniuse-lite@npm:^1.0.30001579, caniuse-lite@npm:^1.0.30001599, caniuse-lite@npm:^1.0.30001640": version: 1.0.30001643 resolution: "caniuse-lite@npm:1.0.30001643" checksum: e39991c13a0fd8f5c2aa99c9128188e4c4e9d6a203c3da6270c36285460ef152c5e9410ee4db560aa723904668946afe50541dce9636ab5e61434ba71dc22955 @@ -20278,7 +20241,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.4, nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": +"nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" bin: @@ -20380,48 +20343,37 @@ __metadata: languageName: node linkType: hard -"next@npm:12.2.3": - version: 12.2.3 - resolution: "next@npm:12.2.3" - dependencies: - "@next/env": 12.2.3 - "@next/swc-android-arm-eabi": 12.2.3 - "@next/swc-android-arm64": 12.2.3 - "@next/swc-darwin-arm64": 12.2.3 - "@next/swc-darwin-x64": 12.2.3 - "@next/swc-freebsd-x64": 12.2.3 - "@next/swc-linux-arm-gnueabihf": 12.2.3 - "@next/swc-linux-arm64-gnu": 12.2.3 - "@next/swc-linux-arm64-musl": 12.2.3 - "@next/swc-linux-x64-gnu": 12.2.3 - "@next/swc-linux-x64-musl": 12.2.3 - "@next/swc-win32-arm64-msvc": 12.2.3 - "@next/swc-win32-ia32-msvc": 12.2.3 - "@next/swc-win32-x64-msvc": 12.2.3 - "@swc/helpers": 0.4.3 - caniuse-lite: ^1.0.30001332 - postcss: 8.4.14 - styled-jsx: 5.0.2 - use-sync-external-store: 1.2.0 +"next@npm:14.2.10": + version: 14.2.10 + resolution: "next@npm:14.2.10" + dependencies: + "@next/env": 14.2.10 + "@next/swc-darwin-arm64": 14.2.10 + "@next/swc-darwin-x64": 14.2.10 + "@next/swc-linux-arm64-gnu": 14.2.10 + "@next/swc-linux-arm64-musl": 14.2.10 + "@next/swc-linux-x64-gnu": 14.2.10 + "@next/swc-linux-x64-musl": 14.2.10 + "@next/swc-win32-arm64-msvc": 14.2.10 + "@next/swc-win32-ia32-msvc": 14.2.10 + "@next/swc-win32-x64-msvc": 14.2.10 + "@swc/helpers": 0.5.5 + busboy: 1.6.0 + caniuse-lite: ^1.0.30001579 + graceful-fs: ^4.2.11 + postcss: 8.4.31 + styled-jsx: 5.1.1 peerDependencies: - fibers: ">= 3.1.0" - node-sass: ^6.0.0 || ^7.0.0 - react: ^17.0.2 || ^18.0.0-0 - react-dom: ^17.0.2 || ^18.0.0-0 + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 sass: ^1.3.0 dependenciesMeta: - "@next/swc-android-arm-eabi": - optional: true - "@next/swc-android-arm64": - optional: true "@next/swc-darwin-arm64": optional: true "@next/swc-darwin-x64": optional: true - "@next/swc-freebsd-x64": - optional: true - "@next/swc-linux-arm-gnueabihf": - optional: true "@next/swc-linux-arm64-gnu": optional: true "@next/swc-linux-arm64-musl": @@ -20437,15 +20389,15 @@ __metadata: "@next/swc-win32-x64-msvc": optional: true peerDependenciesMeta: - fibers: + "@opentelemetry/api": optional: true - node-sass: + "@playwright/test": optional: true sass: optional: true bin: next: dist/bin/next - checksum: b13b42fbb327adca51abeef68aca4b31c82297f07eb3a12d31a3bf2c1aa9ca34cf1ab41bc2b9f4d3162623e70a1ddf230da39fd3ce241b1eea113a4a010a11fd + checksum: 2d69507cccddc0297ff2e6727d7c0e96653a049c4a0236f6c0cae3e1eb622dabcb576b4f3976046526f31b8252f665f5b98cbb0f66d6e756f4d22b0ccfbd290d languageName: node linkType: hard @@ -22298,17 +22250,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.14": - version: 8.4.14 - resolution: "postcss@npm:8.4.14" - dependencies: - nanoid: ^3.3.4 - picocolors: ^1.0.0 - source-map-js: ^1.0.2 - checksum: fe58766ff32e4becf65a7d57678995cfd239df6deed2fe0557f038b47c94e4132e7e5f68b5aa820c13adfec32e523b693efaeb65798efb995ce49ccd83953816 - languageName: node - linkType: hard - "postcss@npm:8.4.31": version: 8.4.31 resolution: "postcss@npm:8.4.31" @@ -25397,20 +25338,6 @@ __metadata: languageName: node linkType: hard -"styled-jsx@npm:5.0.2": - version: 5.0.2 - resolution: "styled-jsx@npm:5.0.2" - peerDependencies: - react: ">= 16.8.0 || 17.x.x || ^18.0.0-0" - peerDependenciesMeta: - "@babel/core": - optional: true - babel-plugin-macros: - optional: true - checksum: 86d55819ebeabd283a574d2f44f7d3f8fa6b8c28fa41687ece161bf1e910e04965611618921d8f5cd33dc6dae1033b926a70421ae5ea045440a9861edc3e0d87 - languageName: node - linkType: hard - "styled-jsx@npm:5.1.1": version: 5.1.1 resolution: "styled-jsx@npm:5.1.1" From 8364e534e6bdc4e22501ac14ec24b11c0d6a5977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Thu, 31 Oct 2024 14:07:18 -0400 Subject: [PATCH 017/171] BAPP-670: Bump OCK version w dropdown fix (#1182) * bump ock version w dropdown fix * reset * BaseName -> Basename * rename imports * reset * fix --- .../name/[username]/ProfileProviders.tsx | 4 +- .../name/[username]/configure-frames/page.tsx | 6 +-- .../app/(basenames)/name/[username]/page.tsx | 6 +-- apps/web/package.json | 2 +- .../pages/api/basenames/metadata/[tokenId].ts | 4 +- .../src/components/BasenameIdentity/index.tsx | 4 +- .../Basenames/BasenameAvatar/index.tsx | 5 +-- .../Basenames/RegistrationContext.tsx | 4 +- .../Basenames/UsernamePill/types.ts | 4 +- .../UsernameProfileContext/index.tsx | 8 ++-- .../ConnectWalletButton.tsx | 4 +- apps/web/src/hooks/useBaseEnsAvatar.ts | 4 +- apps/web/src/hooks/useBaseEnsName.ts | 6 +-- apps/web/src/hooks/useBasenameChain.ts | 4 +- apps/web/src/hooks/useSetPrimaryBasename.ts | 4 +- apps/web/src/utils/redirectIfNotNameOwner.ts | 4 +- apps/web/src/utils/usernames.ts | 42 +++++++++---------- yarn.lock | 10 ++--- 18 files changed, 62 insertions(+), 63 deletions(-) diff --git a/apps/web/app/(basenames)/name/[username]/ProfileProviders.tsx b/apps/web/app/(basenames)/name/[username]/ProfileProviders.tsx index d63dfe2dbc..2339e9d17f 100644 --- a/apps/web/app/(basenames)/name/[username]/ProfileProviders.tsx +++ b/apps/web/app/(basenames)/name/[username]/ProfileProviders.tsx @@ -1,6 +1,6 @@ 'use client'; -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import AnalyticsProvider from 'apps/web/contexts/Analytics'; import UsernameProfileProvider from 'apps/web/src/components/Basenames/UsernameProfileContext'; @@ -9,7 +9,7 @@ const usernameProfileAnalyticContext = 'username_profile'; type ProfileProvidersProps = { children: React.ReactNode; - username: BaseName; + username: Basename; }; export default function ProfileProviders({ children, username }: ProfileProvidersProps) { diff --git a/apps/web/app/(basenames)/name/[username]/configure-frames/page.tsx b/apps/web/app/(basenames)/name/[username]/configure-frames/page.tsx index 9c3affb6a3..3e2131ea33 100644 --- a/apps/web/app/(basenames)/name/[username]/configure-frames/page.tsx +++ b/apps/web/app/(basenames)/name/[username]/configure-frames/page.tsx @@ -1,4 +1,4 @@ -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import ProfileProviders from 'apps/web/app/(basenames)/name/[username]/ProfileProviders'; import ErrorsProvider from 'apps/web/contexts/Errors'; import FrameBuilder from 'apps/web/src/components/Basenames/ConfigureFramesPageContent/FrameBuilder'; @@ -7,11 +7,11 @@ import { redirectIfNotNameOwner } from 'apps/web/src/utils/redirectIfNotNameOwne import { formatDefaultUsername } from 'apps/web/src/utils/usernames'; export type ConfigureFramesProps = { - params: { username: BaseName }; + params: { username: Basename }; }; export default async function ConfigureFrames({ params }: ConfigureFramesProps) { - let username = await formatDefaultUsername(decodeURIComponent(params.username) as BaseName); + let username = await formatDefaultUsername(decodeURIComponent(params.username) as Basename); await redirectIfNotNameOwner(username); return ( diff --git a/apps/web/app/(basenames)/name/[username]/page.tsx b/apps/web/app/(basenames)/name/[username]/page.tsx index 503b8f2057..51c34d33e5 100644 --- a/apps/web/app/(basenames)/name/[username]/page.tsx +++ b/apps/web/app/(basenames)/name/[username]/page.tsx @@ -1,4 +1,4 @@ -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import ProfileProviders from 'apps/web/app/(basenames)/name/[username]/ProfileProviders'; import ErrorsProvider from 'apps/web/contexts/Errors'; import UsernameProfile from 'apps/web/src/components/Basenames/UsernameProfile'; @@ -12,7 +12,7 @@ import classNames from 'classnames'; import { Metadata } from 'next'; export type UsernameProfileProps = { - params: { username: BaseName }; + params: { username: Basename }; }; export async function generateMetadata({ params }: UsernameProfileProps): Promise { @@ -35,7 +35,7 @@ export async function generateMetadata({ params }: UsernameProfileProps): Promis } export default async function Username({ params }: UsernameProfileProps) { - let username = await formatDefaultUsername(decodeURIComponent(params.username) as BaseName); + let username = await formatDefaultUsername(decodeURIComponent(params.username) as Basename); await redirectIfNotNameOwner(username); const usernameProfilePageClasses = classNames( diff --git a/apps/web/package.json b/apps/web/package.json index d94f6f6d68..3c42a50734 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,7 +15,7 @@ "dependencies": { "@coinbase/cookie-banner": "^1.0.3", "@coinbase/cookie-manager": "^1.1.1", - "@coinbase/onchainkit": "^0.28.5", + "@coinbase/onchainkit": "^0.35.2", "@datadog/browser-logs": "^5.23.3", "@datadog/browser-rum": "^5.23.3", "@frames.js/render": "^0.3.14", diff --git a/apps/web/pages/api/basenames/metadata/[tokenId].ts b/apps/web/pages/api/basenames/metadata/[tokenId].ts index c2fb0cef25..90de95d2f5 100644 --- a/apps/web/pages/api/basenames/metadata/[tokenId].ts +++ b/apps/web/pages/api/basenames/metadata/[tokenId].ts @@ -1,4 +1,4 @@ -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import { premintMapping } from 'apps/web/pages/api/basenames/metadata/premintsMapping'; import L2Resolver from 'apps/web/src/abis/L2Resolver'; import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; @@ -51,7 +51,7 @@ export default async function GET(request: Request) { args: [namehashNode], functionName: 'name', }); - nameExpires = await getBasenameNameExpires(basenameFormatted as BaseName); + nameExpires = await getBasenameNameExpires(basenameFormatted as Basename); } catch (error) { logger.error('Error getting token metadata', error); } diff --git a/apps/web/src/components/BasenameIdentity/index.tsx b/apps/web/src/components/BasenameIdentity/index.tsx index 66926e6ca2..a1525a4fe4 100644 --- a/apps/web/src/components/BasenameIdentity/index.tsx +++ b/apps/web/src/components/BasenameIdentity/index.tsx @@ -1,12 +1,12 @@ 'use client'; -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; import BasenameAvatar from 'apps/web/src/components/Basenames/BasenameAvatar'; import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; import { truncateMiddle } from 'libs/base-ui/utils/string'; import { useEnsAddress } from 'wagmi'; -export default function BasenameIdentity({ username }: { username: BaseName }) { +export default function BasenameIdentity({ username }: { username: Basename }) { const { basenameChain } = useBasenameChain(); const { data: basenameAddress } = useEnsAddress({ diff --git a/apps/web/src/components/Basenames/BasenameAvatar/index.tsx b/apps/web/src/components/Basenames/BasenameAvatar/index.tsx index e2bd58da7e..908a45f260 100644 --- a/apps/web/src/components/Basenames/BasenameAvatar/index.tsx +++ b/apps/web/src/components/Basenames/BasenameAvatar/index.tsx @@ -1,7 +1,6 @@ 'use client'; -import { BaseName } from '@coinbase/onchainkit/identity'; - +import { Basename } from '@coinbase/onchainkit/identity'; import LottieAnimation from 'apps/web/src/components/LottieAnimation'; import useBaseEnsAvatar from 'apps/web/src/hooks/useBaseEnsAvatar'; import ImageWithLoading from 'apps/web/src/components/ImageWithLoading'; @@ -14,7 +13,7 @@ export default function BasenameAvatar({ width, height, }: { - basename: BaseName; + basename: Basename; wrapperClassName?: string; animate?: boolean; width?: number | `${number}` | undefined; diff --git a/apps/web/src/components/Basenames/RegistrationContext.tsx b/apps/web/src/components/Basenames/RegistrationContext.tsx index c3153ce1b9..7b00fe7e2a 100644 --- a/apps/web/src/components/Basenames/RegistrationContext.tsx +++ b/apps/web/src/components/Basenames/RegistrationContext.tsx @@ -35,7 +35,7 @@ import { import { BatchCallsStatus } from 'apps/web/src/hooks/useWriteContractsWithLogs'; import { WriteTransactionWithReceiptStatus } from 'apps/web/src/hooks/useWriteContractWithReceipt'; import useBaseEnsName from 'apps/web/src/hooks/useBaseEnsName'; -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; export enum RegistrationSteps { Search = 'search', @@ -54,7 +54,7 @@ export type RegistrationContextProps = { setRegistrationStep: Dispatch>; selectedName: string; setSelectedName: Dispatch>; - selectedNameFormatted: BaseName; + selectedNameFormatted: Basename; years: number; setYears: Dispatch>; redirectToProfile: () => void; diff --git a/apps/web/src/components/Basenames/UsernamePill/types.ts b/apps/web/src/components/Basenames/UsernamePill/types.ts index 69bd9a29c3..22d68e1f60 100644 --- a/apps/web/src/components/Basenames/UsernamePill/types.ts +++ b/apps/web/src/components/Basenames/UsernamePill/types.ts @@ -1,4 +1,4 @@ -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import { Address } from 'viem'; export enum UsernamePillVariants { @@ -7,7 +7,7 @@ export enum UsernamePillVariants { } export type UsernamePillProps = { variant: UsernamePillVariants; - username: BaseName; + username: Basename; address?: Address; isRegistering?: boolean; }; diff --git a/apps/web/src/components/Basenames/UsernameProfileContext/index.tsx b/apps/web/src/components/Basenames/UsernameProfileContext/index.tsx index cd989fb65f..4b6a9cb5d8 100644 --- a/apps/web/src/components/Basenames/UsernameProfileContext/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileContext/index.tsx @@ -1,5 +1,5 @@ 'use client'; -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; import useBaseEnsName from 'apps/web/src/hooks/useBaseEnsName'; import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; @@ -24,12 +24,12 @@ export enum UsernameProfileSteps {} export type UsernameProfileContextProps = { // Profile details - profileUsername: BaseName; + profileUsername: Basename; profileAddress?: Address; // Profile owner profileEditorAddress?: Address; - profileOwnerUsername?: BaseName; + profileOwnerUsername?: Basename; profileRefetch: () => Promise; // State @@ -67,7 +67,7 @@ export const UsernameProfileContext = createContext type UsernameProfileProviderProps = { children: ReactNode; - username: BaseName; + username: Basename; }; export default function UsernameProfileProvider({ diff --git a/apps/web/src/components/ConnectWalletButton/ConnectWalletButton.tsx b/apps/web/src/components/ConnectWalletButton/ConnectWalletButton.tsx index ab76e4e33c..6e936441ab 100644 --- a/apps/web/src/components/ConnectWalletButton/ConnectWalletButton.tsx +++ b/apps/web/src/components/ConnectWalletButton/ConnectWalletButton.tsx @@ -3,7 +3,7 @@ import { ConnectWallet, Wallet, WalletDropdown, - WalletDropdownBaseName, + WalletDropdownBasename, WalletDropdownDisconnect, WalletDropdownLink, } from '@coinbase/onchainkit/wallet'; @@ -156,7 +156,7 @@ export function ConnectWalletButton({ /> - + ; - const ensNameTyped = data ? (data as BaseName) : undefined; + const ensNameTyped = data ? (data as Basename) : undefined; return { data: ensNameTyped, diff --git a/apps/web/src/hooks/useBasenameChain.ts b/apps/web/src/hooks/useBasenameChain.ts index 5ef7f047d5..382956aa32 100644 --- a/apps/web/src/hooks/useBasenameChain.ts +++ b/apps/web/src/hooks/useBasenameChain.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { base, baseSepolia, Chain } from 'viem/chains'; import { createPublicClient, http } from 'viem'; import { cdpBaseRpcEndpoint, cdpBaseSepoliaRpcEndpoint } from 'apps/web/src/cdp/constants'; -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import { getChainForBasename } from 'apps/web/src/utils/usernames'; import { isDevelopment } from 'apps/web/src/constants'; @@ -22,7 +22,7 @@ export function isBasenameSupportedChain(chainId: number) { return supportedChainIds.includes(chainId); } -export default function useBasenameChain(username?: BaseName) { +export default function useBasenameChain(username?: Basename) { const { chain: connectedChain } = useAccount(); const basenameChain: Chain = useMemo(() => { diff --git a/apps/web/src/hooks/useSetPrimaryBasename.ts b/apps/web/src/hooks/useSetPrimaryBasename.ts index b4d3a62a99..a5350de240 100644 --- a/apps/web/src/hooks/useSetPrimaryBasename.ts +++ b/apps/web/src/hooks/useSetPrimaryBasename.ts @@ -5,7 +5,7 @@ import { } from 'apps/web/src/addresses/usernames'; import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; import { useCallback, useEffect } from 'react'; -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import { useAccount } from 'wagmi'; import useBaseEnsName from 'apps/web/src/hooks/useBaseEnsName'; import { useErrors } from 'apps/web/contexts/Errors'; @@ -22,7 +22,7 @@ import { useUsernameProfile } from 'apps/web/src/components/Basenames/UsernamePr */ type UseSetPrimaryBasenameProps = { - secondaryUsername: BaseName; + secondaryUsername: Basename; }; export default function useSetPrimaryBasename({ secondaryUsername }: UseSetPrimaryBasenameProps) { diff --git a/apps/web/src/utils/redirectIfNotNameOwner.ts b/apps/web/src/utils/redirectIfNotNameOwner.ts index 704dcbac44..e3b6ce906b 100644 --- a/apps/web/src/utils/redirectIfNotNameOwner.ts +++ b/apps/web/src/utils/redirectIfNotNameOwner.ts @@ -1,4 +1,4 @@ -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import { getBasenameAddress, getBasenameEditor, @@ -6,7 +6,7 @@ import { } from 'apps/web/src/utils/usernames'; import { redirect } from 'next/navigation'; -export async function redirectIfNotNameOwner(username: BaseName) { +export async function redirectIfNotNameOwner(username: Basename) { const [address, editor, owner] = await Promise.all([ getBasenameAddress(username), getBasenameEditor(username), diff --git a/apps/web/src/utils/usernames.ts b/apps/web/src/utils/usernames.ts index 8e5c240611..7ecb1ae17f 100644 --- a/apps/web/src/utils/usernames.ts +++ b/apps/web/src/utils/usernames.ts @@ -17,7 +17,7 @@ import L2ResolverAbi from 'apps/web/src/abis/L2Resolver'; import RegistryAbi from 'apps/web/src/abis/RegistryAbi'; import BaseRegistrarAbi from 'apps/web/src/abis/BaseRegistrarAbi'; import { base, baseSepolia, mainnet } from 'viem/chains'; -import { BaseName } from '@coinbase/onchainkit/identity'; +import { Basename } from '@coinbase/onchainkit/identity'; import { USERNAME_BASE_REGISTRAR_ADDRESSES, USERNAME_BASE_REGISTRY_ADDRESSES, @@ -350,8 +350,8 @@ export const USERNAME_DOMAINS: Record = { [base.id]: 'base.eth', }; -export const formatBaseEthDomain = (name: string, chainId: number): BaseName => { - return `${name}.${USERNAME_DOMAINS[chainId] ?? '.base.eth'}`.toLocaleLowerCase() as BaseName; +export const formatBaseEthDomain = (name: string, chainId: number): Basename => { + return `${name}.${USERNAME_DOMAINS[chainId] ?? '.base.eth'}`.toLocaleLowerCase() as Basename; }; export const convertChainIdToCoinType = (chainId: number): string => { @@ -404,7 +404,7 @@ export function isValidDiscount(key: string): key is keyof typeof Discount { return Object.values(Discount).includes(key as Discount); } -export function getChainForBasename(username: BaseName): Chain { +export function getChainForBasename(username: Basename): Chain { return username.endsWith(`.${USERNAME_DOMAINS[base.id]}`) ? base : baseSepolia; } @@ -419,7 +419,7 @@ export function normalizeName(name: string) { } // Assume domainless name to .base.eth -export async function formatDefaultUsername(username: string | BaseName) { +export async function formatDefaultUsername(username: string) { if ( username && !username.endsWith(`.${USERNAME_DOMAINS[baseSepolia.id]}`) && @@ -428,11 +428,11 @@ export async function formatDefaultUsername(username: string | BaseName) { return formatBaseEthDomain(username, base.id); } - return username as BaseName; + return username as Basename; } -export const getTokenIdFromBasename = (username: BaseName) => { - const usernameWithoutDomain = username +export const getTokenIdFromBasename = (username: Basename) => { + const usernameWithoutDomain = (username as string) .replace(`.${USERNAME_DOMAINS[base.id]}`, '') .replace(`.${USERNAME_DOMAINS[baseSepolia.id]}`, ''); @@ -551,13 +551,13 @@ export function validateBasenameAvatarUrl(source: string): ValidationResult { // Get username `addr` // Get username token `owner` -export async function getBasenameAddress(username: BaseName) { +export async function getBasenameAddress(username: Basename) { const chain = getChainForBasename(username); try { const client = getBasenamePublicClient(chain.id); const ensAddress = await client.getEnsAddress({ - name: normalize(username), + name: normalize(username as string), universalResolverAddress: USERNAME_L2_RESOLVER_ADDRESSES[chain.id], }); return ensAddress; @@ -567,17 +567,17 @@ export async function getBasenameAddress(username: BaseName) { /* Get username Basename `editor` in the Base Registrar (different from NFT owner) */ -export function buildBasenameEditorContract(username: BaseName): ContractFunctionParameters { +export function buildBasenameEditorContract(username: Basename): ContractFunctionParameters { const chain = getChainForBasename(username); return { abi: RegistryAbi, address: USERNAME_BASE_REGISTRY_ADDRESSES[chain.id], - args: [namehash(username)], + args: [namehash(username as string)], functionName: 'owner', }; } -export async function getBasenameEditor(username: BaseName) { +export async function getBasenameEditor(username: Basename) { const chain = getChainForBasename(username); try { @@ -592,7 +592,7 @@ export async function getBasenameEditor(username: BaseName) { Get username NFT `owner` in the Base Registry (different from Basename editor) */ -export function buildBasenameOwnerContract(username: BaseName): ContractFunctionParameters { +export function buildBasenameOwnerContract(username: Basename): ContractFunctionParameters { const chain = getChainForBasename(username); const tokenId = getTokenIdFromBasename(username); return { @@ -603,7 +603,7 @@ export function buildBasenameOwnerContract(username: BaseName): ContractFunction }; } -export async function getBasenameOwner(username: BaseName) { +export async function getBasenameOwner(username: Basename) { const chain = getChainForBasename(username); try { @@ -614,7 +614,7 @@ export async function getBasenameOwner(username: BaseName) { } catch (error) {} } -export async function getBasenameNameExpires(username: BaseName) { +export async function getBasenameNameExpires(username: Basename) { const chain = getChainForBasename(username); const tokenId = getTokenIdFromBasename(username); try { @@ -656,20 +656,20 @@ export async function getBasenameAvailable(name: string, chain: Chain): Promise< // Build a TextRecord contract request export function buildBasenameTextRecordContract( - username: BaseName, + username: Basename, key: UsernameTextRecordKeys, ): ContractFunctionParameters { const chain = getChainForBasename(username); return { abi: L2ResolverAbi, address: USERNAME_L2_RESOLVER_ADDRESSES[chain.id], - args: [namehash(username), key], + args: [namehash(username as string), key], functionName: 'text', }; } // Get a single TextRecord -export async function getBasenameTextRecord(username: BaseName, key: UsernameTextRecordKeys) { +export async function getBasenameTextRecord(username: Basename, key: UsernameTextRecordKeys) { const chain = getChainForBasename(username); try { const client = getBasenamePublicClient(chain.id); @@ -680,7 +680,7 @@ export async function getBasenameTextRecord(username: BaseName, key: UsernameTex } // Get a all TextRecords -export async function getBasenameTextRecords(username: BaseName) { +export async function getBasenameTextRecords(username: Basename) { const chain = getChainForBasename(username); try { const readContracts: ContractFunctionParameters[] = textRecordsKeysEnabled.map((key) => { @@ -698,7 +698,7 @@ export async function getBasenameTextRecords(username: BaseName) { Reclaim a Basename contrat write method */ export function buildBasenameReclaimContract( - username: BaseName, + username: Basename, address: Address, ): ContractFunctionParameters { const chain = getChainForBasename(username); diff --git a/yarn.lock b/yarn.lock index e9bb29d77a..aee8802291 100644 --- a/yarn.lock +++ b/yarn.lock @@ -361,7 +361,7 @@ __metadata: dependencies: "@coinbase/cookie-banner": ^1.0.3 "@coinbase/cookie-manager": ^1.1.1 - "@coinbase/onchainkit": ^0.28.5 + "@coinbase/onchainkit": ^0.35.2 "@datadog/browser-logs": ^5.23.3 "@datadog/browser-rum": ^5.23.3 "@frames.js/render": ^0.3.14 @@ -2288,9 +2288,9 @@ __metadata: languageName: node linkType: hard -"@coinbase/onchainkit@npm:^0.28.5": - version: 0.28.7 - resolution: "@coinbase/onchainkit@npm:0.28.7" +"@coinbase/onchainkit@npm:^0.35.2": + version: 0.35.2 + resolution: "@coinbase/onchainkit@npm:0.35.2" dependencies: "@rainbow-me/rainbowkit": ^2.1.3 "@tanstack/react-query": ^5 @@ -2305,7 +2305,7 @@ __metadata: "@xmtp/frames-validator": ^0.6.0 react: ^18 react-dom: ^18 - checksum: 49c7041ec516f41d2580115a79e0b7af562dfeb9e2a6b7567e8ed96269334d8aa39d1800e7306afcaeda5b33e47938d693532c5108814c514e2e2cd5235be7e8 + checksum: 34496e95c77654a0aa44a846f554ccaf103e1fe56cccf5cc6097b0ddb4821056b33ef4862a2d83e67cd98b2270a8e88627aa0e15252dc3465d46e83233d19fb6 languageName: node linkType: hard From 2c24508c9744740db017201550e69960bfc33689 Mon Sep 17 00:00:00 2001 From: mche-cb <64994051+mche-cb@users.noreply.github.com> Date: Thu, 31 Oct 2024 19:56:23 -0400 Subject: [PATCH 018/171] docs: Add app blocklist info (#1155) --- apps/base-docs/docs/security.md | 45 ---------------- apps/base-docs/docs/security/app-blocklist.md | 51 +++++++++++++++++++ apps/base-docs/docs/security/bounty.md | 18 +++++++ apps/base-docs/docs/security/report.md | 29 +++++++++++ apps/base-docs/sidebars.js | 8 ++- 5 files changed, 105 insertions(+), 46 deletions(-) delete mode 100644 apps/base-docs/docs/security.md create mode 100644 apps/base-docs/docs/security/app-blocklist.md create mode 100644 apps/base-docs/docs/security/bounty.md create mode 100644 apps/base-docs/docs/security/report.md diff --git a/apps/base-docs/docs/security.md b/apps/base-docs/docs/security.md deleted file mode 100644 index 3e57f89666..0000000000 --- a/apps/base-docs/docs/security.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Security -slug: /security -description: The Base bug bounty program and procedures for reporting vulnerabilities. -keywords: - [ - Base, - bug bounty program, - report vulnerability, - bug report, - cybersecurity, - HackerOne, - Base network, - Bedrock, - Optimism, - vulnerability reporting, - crypto security, - open source, - ] -hide_table_of_contents: true ---- - -# Security - ---- - -## Bug bounty program - -In line with our strategy of being the safest way for users to access crypto: - -- Coinbase will be extending our [best-in-industry](https://www.coinbase.com/blog/celebrating-10-years-of-our-bug-bounty-program) million-dollar [HackerOne bug bounty program](https://hackerone.com/coinbase?type=team) to cover the Base network, the Base bridge contracts, and Base infrastructure. -- Coinbase will be working in tandem with OP Labs to harden the security guarantees of Bedrock and accelerate the timeline for decentralized fault-proofs on the [OP Stack](https://stack.optimism.io/). -- Coinbase's bug bounty program will run alongside Optimism's existing [Immunefi Bedrock bounty program](https://immunefi.com/bounty/optimism/) to support the open source [Bedrock](https://stack.optimism.io/docs/releases/bedrock/) OP Stack framework. - ---- - -## Reporting vulnerabilities - -All potential vulnerability reports can be submitted via the [HackerOne](https://hackerone.com/coinbase) platform. - -The HackerOne platform allows us to have a centralized and single reporting source for us to deliver optimized SLA's and results. All reports submitted to the platform are triaged around the clock by our team of Coinbase engineers with domain knowledge, assuring the best quality of review. - -For more information on reporting vulnerabilities and our HackerOne bug bounty program, view our [security program policies](https://hackerone.com/coinbase?view_policy=true). - ---- diff --git a/apps/base-docs/docs/security/app-blocklist.md b/apps/base-docs/docs/security/app-blocklist.md new file mode 100644 index 0000000000..3f8f29ad37 --- /dev/null +++ b/apps/base-docs/docs/security/app-blocklist.md @@ -0,0 +1,51 @@ +--- +title: How to avoid getting your app flagged as malicious +slug: /security/app-blocklist +description: The Base bug bounty program and procedures for reporting vulnerabilities. +keywords: + [ + Base, + Coinbase Wallet, + dapp, + app, + malicious warning, + browser, + dapp developer, + app developer, + best practice, + unblock, + remove warning, + ] +hide_table_of_contents: true +--- + +# How to avoid getting your app flagged as malicious + +--- + +Ensuring that your app is perceived as trustworthy and not flagged as malicious requires attention to best practices. Here’s a quick guide on how to build a secure and compliant app from day one + +## Smart Contracts + +- **Verify Source Code:** Ensure that the source code of your contracts is verified and publicly available on [block explorers](https://docs.base.org/docs/tools/block-explorers/). +- **Audit Your Contracts**: Having your contracts audited by a reputable firm is crucial. Publish the audit report and provide a reference link to it, so users can easily find it. Audits show that you’ve taken extra steps to secure your smart contracts. +- **Limit User Funds Exposure**: Design your contracts to minimize the exposure of user funds. Use efficient design to reduce any unnecessary risk. For example, request the minimum amount needed to fulfill the transaction. + +--- + +## App Best Practices + +- **Accessibility Across Regions**: Avoid geo-blocking or access restrictions that prevent certain regions or countries from accessing your app. +- **Consistent Web2 Behavior**: Avoid rapid or unexplained changes in UI that can make users feel uncertain about the app’s reliability. +- **Transparent Web3 Interactions**: Make sure your app’s web3 interactions are clear and match the UI actions. For example, a “Mint” button should clearly emit a mint transaction. +- **Standard Sign-in Methods**: Provide all standard connection methods for users to sign in, such as WalletConnect / WalletLink or popular browser extension wallets. + +--- + +## Verification Request + +Once you’ve implemented these best practices, consider submitting a verification request through the following [form](https://report.blockaid.io/). This step helps ensure that your app is recognized as safe and verified by trusted sources in the ecosystem. + +By following these recommendations, you’ll significantly reduce the chances of your app being flagged as malicious and foster a secure and trustworthy environment for your users. + +--- diff --git a/apps/base-docs/docs/security/bounty.md b/apps/base-docs/docs/security/bounty.md new file mode 100644 index 0000000000..6b7c2cdfbe --- /dev/null +++ b/apps/base-docs/docs/security/bounty.md @@ -0,0 +1,18 @@ +--- +title: Bug bounty +slug: /security/bounty +description: The Base bug bounty program +keywords: + [Base, HackerOne, bug bounty program, bug report, Base network, Bedrock, Optimism, open source] +hide_table_of_contents: true +--- + +# Bug bounty program + +In line with our strategy of being the safest way for users to access crypto: + +- Coinbase will be extending our [best-in-industry](https://www.coinbase.com/blog/celebrating-10-years-of-our-bug-bounty-program) million-dollar [HackerOne bug bounty program](https://hackerone.com/coinbase?type=team) to cover the Base network, the Base bridge contracts, and Base infrastructure. +- Coinbase will be working in tandem with OP Labs to harden the security guarantees of Bedrock and accelerate the timeline for decentralized fault-proofs on the [OP Stack](https://stack.optimism.io/). +- Coinbase's bug bounty program will run alongside Optimism's existing [Immunefi Bedrock bounty program](https://immunefi.com/bounty/optimism/) to support the open source [Bedrock](https://stack.optimism.io/docs/releases/bedrock/) OP Stack framework. + +--- diff --git a/apps/base-docs/docs/security/report.md b/apps/base-docs/docs/security/report.md new file mode 100644 index 0000000000..f67678a92d --- /dev/null +++ b/apps/base-docs/docs/security/report.md @@ -0,0 +1,29 @@ +--- +title: Report vulnerability +slug: /security/report +description: The Base procedures for reporting vulnerabilities. +keywords: + [ + Base, + report vulnerability, + cybersecurity, + HackerOne, + Base network, + Bedrock, + Optimism, + vulnerability reporting, + crypto security, + open source, + ] +hide_table_of_contents: true +--- + +# Reporting vulnerabilities + +All potential vulnerability reports can be submitted via the [HackerOne](https://hackerone.com/coinbase) platform. + +The HackerOne platform allows us to have a centralized and single reporting source for us to deliver optimized SLA's and results. All reports submitted to the platform are triaged around the clock by our team of Coinbase engineers with domain knowledge, assuring the best quality of review. + +For more information on reporting vulnerabilities and our HackerOne bug bounty program, view our [security program policies](https://hackerone.com/coinbase?view_policy=true). + +--- diff --git a/apps/base-docs/sidebars.js b/apps/base-docs/sidebars.js index ec5243aad7..4bf6b60c60 100644 --- a/apps/base-docs/sidebars.js +++ b/apps/base-docs/sidebars.js @@ -86,7 +86,13 @@ module.exports = { items: ['tokens/token-list', 'tokens/wallet'], }, ['contracts'], - ['security'], + { + type: 'category', + label: 'Security', + collapsible: false, + collapsed: false, + items: ['security/bounty', 'security/report', 'security/app-blocklist'], + }, { type: 'link', label: 'Status', From e7e63f2aea7a936e9f1c84aa03dc4d638d40b68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Mon, 4 Nov 2024 11:28:17 -0500 Subject: [PATCH 019/171] Github action: check for large files (#1199) * github action: check for large files * fix action * comment setup * fix file checker --- .github/workflows/file-size-checker.yml | 128 ++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 .github/workflows/file-size-checker.yml diff --git a/.github/workflows/file-size-checker.yml b/.github/workflows/file-size-checker.yml new file mode 100644 index 0000000000..18fbc30fc9 --- /dev/null +++ b/.github/workflows/file-size-checker.yml @@ -0,0 +1,128 @@ +name: File Size Checker + +# Add required permissions +permissions: + contents: read + pull-requests: write + statuses: write + +on: + pull_request: + types: [opened, synchronize] + +jobs: + check-file-sizes: + name: File Size Check + runs-on: ubuntu-latest + + steps: + # - name: Setup environment + # run: | + # apt-get update + # apt-get install -y git bc + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check file sizes + id: check-sizes + run: | + # Initialize variables for tracking findings + large_files="" + huge_files="" + + # Get all files in the PR + echo "Files changed in PR:" + git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} + + for file in $(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}); do + if [ -f "$file" ]; then + size=$(stat -c%s "$file") + size_mb=$(echo "scale=2; $size/1048576" | bc) + + echo "Checking $file: ${size_mb}MB" + + # Check for files over 40MB + if (( $(echo "$size_mb > 40" | bc -l) )); then + huge_files="${huge_files}* ${file} (${size_mb}MB)\n" + # Check for files over 10MB + elif (( $(echo "$size_mb > 10" | bc -l) )); then + large_files="${large_files}* ${file} (${size_mb}MB)\n" + fi + fi + done + + # Print findings for debugging + echo "Large files found:" + echo -e "$large_files" + echo "Huge files found:" + echo -e "$huge_files" + + # Set outputs for use in next steps + echo "large_files<> $GITHUB_OUTPUT + echo -e "$large_files" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "huge_files<> $GITHUB_OUTPUT + echo -e "$huge_files" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Fail if huge files are found + if [ ! -z "$huge_files" ]; then + echo "❌ Files over 40MB found!" + exit 1 + fi + + - name: Update Status and Comment + if: always() + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const hugeFiles = `${{ steps.check-sizes.outputs.huge_files }}`; + const largeFiles = `${{ steps.check-sizes.outputs.large_files }}`; + + try { + console.log('Repository:', context.payload.repository.name); + console.log('Owner:', context.payload.repository.owner.login); + console.log('SHA:', context.payload.pull_request.head.sha); + + // Set status check that will be used by branch protection + await github.rest.repos.createCommitStatus({ + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + sha: context.payload.pull_request.head.sha, + state: hugeFiles ? 'failure' : 'success', + context: 'File Size Check', + description: hugeFiles ? 'Files over 40MB found' : 'All files within size limits', + target_url: `https://github.com/${context.payload.repository.owner.login}/${context.payload.repository.name}/actions/runs/${context.runId}` + }); + + // Only comment if issues were found + if (hugeFiles || largeFiles) { + let comment = '## ⚠️ File Size Check Results\n\n'; + + if (hugeFiles) { + comment += '### 🚫 Files over 40MB (Not Allowed):\n' + hugeFiles + '\n'; + comment += '**These files must be removed from git history before the PR can be merged.**\n\n'; + } + + if (largeFiles) { + comment += '### ⚠️ Large Files (Over 10MB):\n' + largeFiles + '\n'; + comment += 'Consider reducing the size of these files if possible.\n'; + } + + await github.rest.issues.createComment({ + issue_number: context.payload.pull_request.number, + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + body: comment + }); + } + } catch (error) { + console.error('Error:', error); + console.error('Context:', JSON.stringify(context.payload, null, 2)); + core.setFailed(error.message); + } From dc16fec1cd016be8cb25862cc9e2dcf9ff05755a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Mon, 4 Nov 2024 15:33:14 -0500 Subject: [PATCH 020/171] Feat: IPFS + Cloudinary integration (#1201) * IPFS + Cloudinary integration * faster proxy * lint * mb feedback --- .../name/[username]/opengraph-image.tsx | 36 ++++++++++----- .../basenames/[name]/assets/cardImage.svg.tsx | 45 ++++++++++++------- .../RegistrationProfileForm/index.tsx | 3 -- .../Basenames/UsernameProfileCard/index.tsx | 3 +- .../Basenames/UsernameProfileCasts/index.tsx | 3 +- .../UsernameProfileSectionFrames/Context.tsx | 3 +- .../UsernameProfileSettingsAvatar/index.tsx | 3 +- .../index.tsx | 3 +- .../UsernameProfileSidebar/index.tsx | 1 - .../ConnectWalletButton/UserAvatar.tsx | 4 -- .../src/components/WalletIdentity/index.tsx | 4 -- apps/web/src/hooks/useBaseEnsAvatar.ts | 39 ++++++++-------- .../src/hooks/useReadBaseEnsTextRecords.ts | 13 +----- .../src/hooks/useWriteBaseEnsTextRecords.ts | 5 +-- apps/web/src/utils/images.ts | 9 +++- apps/web/src/utils/pinata.ts | 2 +- apps/web/src/utils/urls.ts | 34 ++++++++------ apps/web/src/utils/usernames.ts | 2 +- 18 files changed, 114 insertions(+), 98 deletions(-) diff --git a/apps/web/app/(basenames)/name/[username]/opengraph-image.tsx b/apps/web/app/(basenames)/name/[username]/opengraph-image.tsx index bcd0ef107a..073788206c 100644 --- a/apps/web/app/(basenames)/name/[username]/opengraph-image.tsx +++ b/apps/web/app/(basenames)/name/[username]/opengraph-image.tsx @@ -7,11 +7,16 @@ import { isDevelopment } from 'apps/web/src/constants'; import { formatBaseEthDomain, getBasenameImage, + getChainForBasename, USERNAME_DOMAINS, + UsernameTextRecordKeys, } from 'apps/web/src/utils/usernames'; import { base, baseSepolia } from 'viem/chains'; import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; -import { CLOUDFARE_IPFS_PROXY } from 'apps/web/src/utils/urls'; +import { getIpfsGatewayUrl, IpfsUrl, IsValidIpfsUrl } from 'apps/web/src/utils/urls'; +import { Basename } from '@coinbase/onchainkit/identity'; +import { getCloudinaryMediaUrl } from 'apps/web/src/utils/images'; +import { logger } from 'apps/web/src/utils/logger'; export const runtime = 'edge'; const size = { @@ -63,24 +68,35 @@ export default async function OpenGraphImage(props: ImageRouteProps) { const domainName = isDevelopment ? `http://localhost:3000` : 'https://www.base.org'; const profilePicture = getBasenameImage(username); + const chain = getChainForBasename(username as Basename); let imageSource = domainName + profilePicture.src; // NOTE: Do we want to fail if the name doesn't exists? try { - const client = getBasenamePublicClient(base.id); - const avatar = await client.getEnsAvatar({ + const client = getBasenamePublicClient(chain.id); + const avatar = await client.getEnsText({ name: username, - universalResolverAddress: USERNAME_L2_RESOLVER_ADDRESSES[base.id], - assetGatewayUrls: { - ipfs: CLOUDFARE_IPFS_PROXY, - }, + key: UsernameTextRecordKeys.Avatar, + universalResolverAddress: USERNAME_L2_RESOLVER_ADDRESSES[chain.id], }); - // Satori Doesn't support webp - if (avatar && !avatar.endsWith('.webp')) { + if (!avatar) return; + + // IPFS Resolution + if (IsValidIpfsUrl(avatar)) { + const ipfsUrl = getIpfsGatewayUrl(avatar as IpfsUrl); + if (ipfsUrl) { + imageSource = ipfsUrl; + } + } else { imageSource = avatar; } - } catch (error) {} + + // Cloudinary resize / fetch + imageSource = getCloudinaryMediaUrl({ media: imageSource, format: 'png', width: 80 }); + } catch (error) { + logger.error('Error fetching basename Avatar:', error); + } return new ImageResponse( ( diff --git a/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx b/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx index 8119f167c0..deabe8d199 100644 --- a/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx +++ b/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx @@ -1,13 +1,20 @@ import satori from 'satori'; import { NextRequest } from 'next/server'; -import { getBasenameImage } from 'apps/web/src/utils/usernames'; +import { + getBasenameImage, + getChainForBasename, + UsernameTextRecordKeys, +} from 'apps/web/src/utils/usernames'; import twemoji from 'twemoji'; -import { base } from 'viem/chains'; import { getBasenamePublicClient } from 'apps/web/src/hooks/useBasenameChain'; import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; import { isDevelopment } from 'apps/web/src/constants'; import ImageRaw from 'apps/web/src/components/ImageRaw'; -import { CLOUDFARE_IPFS_PROXY } from 'apps/web/src/utils/urls'; +import { getIpfsGatewayUrl, IpfsUrl, IsValidIpfsUrl } from 'apps/web/src/utils/urls'; +import { logger } from 'apps/web/src/utils/logger'; +import { Basename } from '@coinbase/onchainkit/identity'; +import { getCloudinaryMediaUrl } from 'apps/web/src/utils/images'; + const emojiCache: Record> = {}; export async function loadEmoji(emojiString: string) { @@ -37,26 +44,34 @@ export default async function handler(request: NextRequest) { const username = url.searchParams.get('name') ?? 'yourname'; const domainName = isDevelopment ? `${url.protocol}//${url.host}` : 'https://www.base.org'; const profilePicture = getBasenameImage(username); - const chainIdFromParams = url.searchParams.get('chainId'); - const chainId = chainIdFromParams ? Number(chainIdFromParams) : base.id; + const chain = getChainForBasename(username as Basename); let imageSource = domainName + profilePicture.src; - // NOTE: Do we want to fail if the name doesn't exists? try { - const client = getBasenamePublicClient(chainId); - const avatar = await client.getEnsAvatar({ + const client = getBasenamePublicClient(chain.id); + const avatar = await client.getEnsText({ name: username, - universalResolverAddress: USERNAME_L2_RESOLVER_ADDRESSES[chainId], - assetGatewayUrls: { - ipfs: CLOUDFARE_IPFS_PROXY, - }, + key: UsernameTextRecordKeys.Avatar, + universalResolverAddress: USERNAME_L2_RESOLVER_ADDRESSES[chain.id], }); - // Satori Doesn't support webp - if (avatar && !avatar.endsWith('.webp')) { + if (!avatar) return; + + // IPFS Resolution + if (IsValidIpfsUrl(avatar)) { + const ipfsUrl = getIpfsGatewayUrl(avatar as IpfsUrl); + if (ipfsUrl) { + imageSource = ipfsUrl; + } + } else { imageSource = avatar; } - } catch (error) {} + + // Cloudinary resize / fetch + imageSource = getCloudinaryMediaUrl({ media: imageSource, format: 'png', width: 120 }); + } catch (error) { + logger.error('Error fetching basename Avatar:', error); + } // Using Satori for a SVG response const svg = await satori( diff --git a/apps/web/src/components/Basenames/RegistrationProfileForm/index.tsx b/apps/web/src/components/Basenames/RegistrationProfileForm/index.tsx index 5af3e7d881..3856b5bfe1 100644 --- a/apps/web/src/components/Basenames/RegistrationProfileForm/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationProfileForm/index.tsx @@ -20,7 +20,6 @@ import { import classNames from 'classnames'; import { ActionType } from 'libs/base-ui/utils/logEvent'; import { useCallback, useEffect, useState } from 'react'; -import { useAccount } from 'wagmi'; export enum FormSteps { Description = 'description', @@ -33,7 +32,6 @@ export default function RegistrationProfileForm() { const [transitionStep, setTransitionStep] = useState(false); const { logError } = useErrors(); const { redirectToProfile, selectedNameFormatted } = useRegistration(); - const { address } = useAccount(); const { logEventWithContext } = useAnalytics(); const { @@ -43,7 +41,6 @@ export default function RegistrationProfileForm() { writeTextRecordsIsPending, writeTextRecordsError, } = useWriteBaseEnsTextRecords({ - address: address, username: selectedNameFormatted, onSuccess: () => { redirectToProfile(); diff --git a/apps/web/src/components/Basenames/UsernameProfileCard/index.tsx b/apps/web/src/components/Basenames/UsernameProfileCard/index.tsx index c6a5cdf9b3..5f12f8bb87 100644 --- a/apps/web/src/components/Basenames/UsernameProfileCard/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileCard/index.tsx @@ -12,10 +12,9 @@ import { import Link from 'next/link'; export default function UsernameProfileCard() { - const { profileUsername, profileAddress } = useUsernameProfile(); + const { profileUsername } = useUsernameProfile(); const { existingTextRecords } = useReadBaseEnsTextRecords({ - address: profileAddress, username: profileUsername, }); diff --git a/apps/web/src/components/Basenames/UsernameProfileCasts/index.tsx b/apps/web/src/components/Basenames/UsernameProfileCasts/index.tsx index c64edd6113..cb96899230 100644 --- a/apps/web/src/components/Basenames/UsernameProfileCasts/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileCasts/index.tsx @@ -6,10 +6,9 @@ import NeymarCast from 'apps/web/src/components/NeymarCast'; import useReadBaseEnsTextRecords from 'apps/web/src/hooks/useReadBaseEnsTextRecords'; export default function UsernameProfileCasts() { - const { profileUsername, profileAddress } = useUsernameProfile(); + const { profileUsername } = useUsernameProfile(); const { existingTextRecords } = useReadBaseEnsTextRecords({ - address: profileAddress, username: profileUsername, }); const casts = existingTextRecords.casts.split(',').filter((cast) => !!cast); diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Context.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Context.tsx index 9dfb63af6a..d92cad22a7 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Context.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/Context.tsx @@ -89,10 +89,9 @@ export function FramesProvider({ children }: FramesProviderProps) { const { logEventWithContext } = useAnalytics(); const { address } = useAccount(); const { logError } = useErrors(); - const { profileUsername, profileAddress, currentWalletIsProfileOwner } = useUsernameProfile(); + const { profileUsername, currentWalletIsProfileOwner } = useUsernameProfile(); const { existingTextRecords, existingTextRecordsIsLoading, refetchExistingTextRecords } = useReadBaseEnsTextRecords({ - address: profileAddress, username: profileUsername, refetchInterval: currentWalletIsProfileOwner ? 1000 * 5 : Infinity, }); diff --git a/apps/web/src/components/Basenames/UsernameProfileSettingsAvatar/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSettingsAvatar/index.tsx index b119fc38dc..5caa3e98c0 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSettingsAvatar/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSettingsAvatar/index.tsx @@ -13,7 +13,7 @@ import { Icon } from 'apps/web/src/components/Icon/Icon'; import { PinResponse } from 'pinata'; export default function UsernameProfileSettingsAvatar() { - const { profileUsername, profileAddress, currentWalletIsProfileEditor } = useUsernameProfile(); + const { profileUsername, currentWalletIsProfileEditor } = useUsernameProfile(); const [avatarFile, setAvatarFile] = useState(); const [avatarIsLoading, setAvatarIsLoading] = useState(false); @@ -30,7 +30,6 @@ export default function UsernameProfileSettingsAvatar() { writeTextRecordsIsPending, hasChanged, } = useWriteBaseEnsTextRecords({ - address: profileAddress, username: profileUsername, onSuccess: () => { setAvatarFile(undefined); diff --git a/apps/web/src/components/Basenames/UsernameProfileSettingsManageProfile/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSettingsManageProfile/index.tsx index 9940f8a267..ca9d6941c2 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSettingsManageProfile/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSettingsManageProfile/index.tsx @@ -24,7 +24,7 @@ const settingTabClass = classNames( ); export default function UsernameProfileSettingsManageProfile() { - const { profileUsername, profileAddress, currentWalletIsProfileEditor, setShowProfileSettings } = + const { profileUsername, currentWalletIsProfileEditor, setShowProfileSettings } = useUsernameProfile(); const { logError } = useErrors(); @@ -42,7 +42,6 @@ export default function UsernameProfileSettingsManageProfile() { writeTextRecordsError, hasChanged, } = useWriteBaseEnsTextRecords({ - address: profileAddress, username: profileUsername, onSuccess: closeSettings, }); diff --git a/apps/web/src/components/Basenames/UsernameProfileSidebar/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSidebar/index.tsx index c202f00b93..a4e83c909f 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSidebar/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSidebar/index.tsx @@ -44,7 +44,6 @@ export default function UsernameProfileSidebar() { ]); const { existingTextRecords } = useReadBaseEnsTextRecords({ - address: profileAddress, username: profileUsername, }); diff --git a/apps/web/src/components/ConnectWalletButton/UserAvatar.tsx b/apps/web/src/components/ConnectWalletButton/UserAvatar.tsx index 726d74b5cc..97158ff3d6 100644 --- a/apps/web/src/components/ConnectWalletButton/UserAvatar.tsx +++ b/apps/web/src/components/ConnectWalletButton/UserAvatar.tsx @@ -3,7 +3,6 @@ import { useAccount, useEnsAvatar, useEnsName } from 'wagmi'; import { mainnet } from 'wagmi/chains'; import useBaseEnsName from 'apps/web/src/hooks/useBaseEnsName'; import ImageWithLoading from 'apps/web/src/components/ImageWithLoading'; -import { CLOUDFARE_IPFS_PROXY } from 'apps/web/src/utils/urls'; import BasenameAvatar from 'apps/web/src/components/Basenames/BasenameAvatar'; export function UserAvatar() { @@ -21,9 +20,6 @@ export function UserAvatar() { const { data: ensAvatar, isLoading: ensAvatarIsLoading } = useEnsAvatar({ name: ensName ?? undefined, chainId: mainnet.id, - assetGatewayUrls: { - ipfs: CLOUDFARE_IPFS_PROXY, - }, query: { retry: false, }, diff --git a/apps/web/src/components/WalletIdentity/index.tsx b/apps/web/src/components/WalletIdentity/index.tsx index 23dd79afa0..9e525e2874 100644 --- a/apps/web/src/components/WalletIdentity/index.tsx +++ b/apps/web/src/components/WalletIdentity/index.tsx @@ -4,7 +4,6 @@ import BasenameAvatar from 'apps/web/src/components/Basenames/BasenameAvatar'; import useBaseEnsAvatar from 'apps/web/src/hooks/useBaseEnsAvatar'; import useBaseEnsName from 'apps/web/src/hooks/useBaseEnsName'; import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; -import { CLOUDFARE_IPFS_PROXY } from 'apps/web/src/utils/urls'; import { getBasenameImage } from 'apps/web/src/utils/usernames'; import { truncateMiddle } from 'libs/base-ui/utils/string'; import Image from 'next/image'; @@ -33,9 +32,6 @@ export default function WalletIdentity({ address }: { address: Address }) { const { data: ensAvatar } = useEnsAvatar({ name: basename ?? undefined, chainId: mainnet.id, - assetGatewayUrls: { - ipfs: CLOUDFARE_IPFS_PROXY, - }, query: { retry: false, }, diff --git a/apps/web/src/hooks/useBaseEnsAvatar.ts b/apps/web/src/hooks/useBaseEnsAvatar.ts index feb0cf4d43..231e3a6c69 100644 --- a/apps/web/src/hooks/useBaseEnsAvatar.ts +++ b/apps/web/src/hooks/useBaseEnsAvatar.ts @@ -1,8 +1,7 @@ -import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; import { Basename } from '@coinbase/onchainkit/identity'; -import { useEnsAvatar } from 'wagmi'; -import { USERNAME_L2_RESOLVER_ADDRESSES } from 'apps/web/src/addresses/usernames'; -import { CLOUDFARE_IPFS_PROXY } from 'apps/web/src/utils/urls'; +import { getIpfsGatewayUrl, IpfsUrl, IsValidIpfsUrl } from 'apps/web/src/utils/urls'; +import useReadBaseEnsTextRecords from 'apps/web/src/hooks/useReadBaseEnsTextRecords'; +import { UsernameTextRecordKeys } from 'apps/web/src/utils/usernames'; export type UseBaseEnsNameProps = { name?: BaseEnsNameData; @@ -10,20 +9,24 @@ export type UseBaseEnsNameProps = { export type BaseEnsNameData = Basename | undefined; -// Wrapper around onchainkit's useName export default function useBaseEnsAvatar({ name }: UseBaseEnsNameProps) { - const { basenameChain } = useBasenameChain(name); + const { existingTextRecords, refetchExistingTextRecords, existingTextRecordsIsLoading } = + useReadBaseEnsTextRecords({ + username: name, + }); - return useEnsAvatar({ - name: name, - chainId: basenameChain.id, - universalResolverAddress: USERNAME_L2_RESOLVER_ADDRESSES[basenameChain.id], - assetGatewayUrls: { - ipfs: CLOUDFARE_IPFS_PROXY, - }, - query: { - retry: false, - enabled: !!name, - }, - }); + let avatar = existingTextRecords[UsernameTextRecordKeys.Avatar]; + + if (IsValidIpfsUrl(avatar)) { + const ipfsUrl = getIpfsGatewayUrl(avatar as IpfsUrl); + if (ipfsUrl) { + avatar = ipfsUrl; + } + } + + return { + data: avatar, + refetch: refetchExistingTextRecords, + isLoading: existingTextRecordsIsLoading, + }; } diff --git a/apps/web/src/hooks/useReadBaseEnsTextRecords.ts b/apps/web/src/hooks/useReadBaseEnsTextRecords.ts index 979737d7a3..27a78aca4d 100644 --- a/apps/web/src/hooks/useReadBaseEnsTextRecords.ts +++ b/apps/web/src/hooks/useReadBaseEnsTextRecords.ts @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Address } from 'viem'; import { UsernameTextRecords, UsernameTextRecordKeys, @@ -11,13 +10,11 @@ import { BaseEnsNameData } from 'apps/web/src/hooks/useBaseEnsName'; import useBasenameChain from 'apps/web/src/hooks/useBasenameChain'; export type UseReadBaseEnsTextRecordsProps = { - address?: Address; username: BaseEnsNameData; refetchInterval?: number; }; export default function useReadBaseEnsTextRecords({ - address, username, refetchInterval = Infinity, }: UseReadBaseEnsTextRecordsProps) { @@ -69,15 +66,9 @@ export default function useReadBaseEnsTextRecords({ refetch: refetchExistingTextRecords, error: existingTextRecordsError, } = useQuery({ - queryKey: [ - 'useReadBaseEnsTextRecords', - address, - textRecordsKeysEnabled, - basenameChain.id, - username, - ], + queryKey: ['useReadBaseEnsTextRecords', textRecordsKeysEnabled, basenameChain.id, username], queryFn: getExistingTextRecords, - enabled: !!address && !!username, + enabled: !!username, retry: false, refetchInterval, refetchOnWindowFocus: false, diff --git a/apps/web/src/hooks/useWriteBaseEnsTextRecords.ts b/apps/web/src/hooks/useWriteBaseEnsTextRecords.ts index d45c7f6fe9..0ea0a9d582 100644 --- a/apps/web/src/hooks/useWriteBaseEnsTextRecords.ts +++ b/apps/web/src/hooks/useWriteBaseEnsTextRecords.ts @@ -8,10 +8,9 @@ import useReadBaseEnsTextRecords from 'apps/web/src/hooks/useReadBaseEnsTextReco import useWriteContractWithReceipt from 'apps/web/src/hooks/useWriteContractWithReceipt'; import { UsernameTextRecords, UsernameTextRecordKeys } from 'apps/web/src/utils/usernames'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { namehash, encodeFunctionData, Address } from 'viem'; +import { namehash, encodeFunctionData } from 'viem'; export type UseWriteBaseEnsTextRecordsProps = { - address?: Address; username: BaseEnsNameData; onSuccess?: () => void; }; @@ -29,7 +28,6 @@ export type UseWriteBaseEnsTextRecordsProps = { */ export default function useWriteBaseEnsTextRecords({ - address, username, onSuccess, }: UseWriteBaseEnsTextRecordsProps) { @@ -39,7 +37,6 @@ export default function useWriteBaseEnsTextRecords({ // Fetch existing TextRecords const { existingTextRecords, existingTextRecordsIsLoading, refetchExistingTextRecords } = useReadBaseEnsTextRecords({ - address, username, }); diff --git a/apps/web/src/utils/images.ts b/apps/web/src/utils/images.ts index 4f8f5252c1..809799640f 100644 --- a/apps/web/src/utils/images.ts +++ b/apps/web/src/utils/images.ts @@ -30,13 +30,18 @@ function isDataUrl(url: string) { type GetCloudinaryMediaUrlParams = { media: string; width: number; + format?: 'webp' | 'png' | 'jpg'; }; -export function getCloudinaryMediaUrl({ media, width }: GetCloudinaryMediaUrlParams) { +export function getCloudinaryMediaUrl({ + media, + width, + format = 'webp', +}: GetCloudinaryMediaUrlParams) { if (isDataUrl(media)) return media; const imageWidth = `w_${width * 2}`; - const imageFormat = 'f_webp'; + const imageFormat = `f_${format}`; const imageUrl = encodeURIComponent(media); const fetchOptions = [imageWidth, imageFormat, imageUrl].join('/'); diff --git a/apps/web/src/utils/pinata.ts b/apps/web/src/utils/pinata.ts index 62555ec85c..403616bc63 100644 --- a/apps/web/src/utils/pinata.ts +++ b/apps/web/src/utils/pinata.ts @@ -2,5 +2,5 @@ import { PinataSDK } from 'pinata'; export const pinata = new PinataSDK({ pinataJwt: `${process.env.PINATA_API_KEY}`, - pinataGateway: `${process.env.NEXT_PUBLIC_PINATA_GATEWAY_URL}`, + pinataGateway: `${process.env.PINATA_GATEWAY_URL}`, }); diff --git a/apps/web/src/utils/urls.ts b/apps/web/src/utils/urls.ts index 2bcd097590..f3e3a4b83f 100644 --- a/apps/web/src/utils/urls.ts +++ b/apps/web/src/utils/urls.ts @@ -3,9 +3,9 @@ import { cid } from 'is-ipfs'; export type IpfsUrl = `ipfs://${string}`; export const VERCEL_BLOB_HOSTNAME = 'zku9gdedgba48lmr.public.blob.vercel-storage.com'; export const IPFS_URI_PROTOCOL = 'ipfs://'; -export const CLOUDFARE_IPFS_PROXY = process.env.NEXT_PUBLIC_PINATA_GATEWAY_URL - ? `https://${process.env.NEXT_PUBLIC_PINATA_GATEWAY_URL}` - : 'https://cloudflare-ipfs.com'; + +export const PINATA_GATEWAY_URL = process.env.NEXT_PUBLIC_PINATA_GATEWAY_URL ?? undefined; +export const PINATA_GATEWAY_KEY = process.env.NEXT_PUBLIC_PINATA_GATEWAY_KEY ?? undefined; export type QueryParams = Record; @@ -28,13 +28,17 @@ export function isValidUrl(string?: string) { } } -export const IsValidIpfsUrl = (ipfsUrl: IpfsUrl): boolean => { +export const IsValidIpfsUrl = (ipfsUrl: string): boolean => { try { const url = new URL(ipfsUrl); - const ipfsCid = url.pathname.replace('//', ''); + if (url.protocol !== 'ipfs:') return false; + + // Get first path segment after hostname as CID + const ipfsCid = url.host; + + // Validate the CID directly const isValidCid = cid(ipfsCid); - const isValidIpfsUrl = url.protocol === 'ipfs:' && isValidCid; - return isValidIpfsUrl; + return isValidCid; } catch (error) { return false; } @@ -50,18 +54,20 @@ export const IsValidVercelBlobUrl = (source: string): boolean => { } }; -export const getIpfsGatewayUrl = (ipfsUrl?: IpfsUrl): string | undefined => { +export const getIpfsGatewayUrl = (ipfsUrl: IpfsUrl): string | undefined => { if (!ipfsUrl) return; + if (!IsValidIpfsUrl(ipfsUrl)) return; try { const url = new URL(ipfsUrl); - const ipfsCid = url.pathname.replace('//', ''); - - const isValidCid = cid(ipfsCid); - const isValidIpfsUrl = url.protocol === 'ipfs:' && isValidCid; - if (!isValidIpfsUrl) return; + const path = url.host; + const pathname = url.pathname; - return `${CLOUDFARE_IPFS_PROXY}/ipfs/${ipfsCid}`; + if (PINATA_GATEWAY_URL && PINATA_GATEWAY_KEY) { + return `https://${PINATA_GATEWAY_URL}/ipfs/${path}${pathname}?pinataGatewayToken=${PINATA_GATEWAY_KEY}`; + } else { + return `https://ipfs.io/ipfs/${path}${pathname}`; + } } catch (error) { return; } diff --git a/apps/web/src/utils/usernames.ts b/apps/web/src/utils/usernames.ts index 7ecb1ae17f..7403afeec7 100644 --- a/apps/web/src/utils/usernames.ts +++ b/apps/web/src/utils/usernames.ts @@ -508,7 +508,7 @@ export function validateBasenameAvatarUrl(source: string): ValidationResult { const url = new URL(source); if (url.protocol === 'ipfs:') { - const isValid = IsValidIpfsUrl(source as IpfsUrl); + const isValid = IsValidIpfsUrl(source); return { valid: isValid, From 76c7bc3cabaf3f868c8817a3e747c03ff9c40896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Tue, 5 Nov 2024 16:18:53 -0500 Subject: [PATCH 021/171] BAPP-765: ERC1155-v2 support for Basename (#1207) * add address, attestations and hooks for testnet * around the world --- .../src/abis/ERC1155DiscountValidatorV2.ts | 58 ++++ apps/web/src/addresses/usernames.ts | 5 + .../images/base-around-the-world-nft.svg | 20 ++ .../RegistrationLearnMoreModal/index.tsx | 300 +++++++----------- .../hooks/useAggregatedDiscountValidators.ts | 13 +- apps/web/src/hooks/useAttestations.ts | 46 +++ apps/web/src/utils/usernames.ts | 1 + 7 files changed, 250 insertions(+), 193 deletions(-) create mode 100644 apps/web/src/abis/ERC1155DiscountValidatorV2.ts create mode 100644 apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-around-the-world-nft.svg diff --git a/apps/web/src/abis/ERC1155DiscountValidatorV2.ts b/apps/web/src/abis/ERC1155DiscountValidatorV2.ts new file mode 100644 index 0000000000..d7caaae4c1 --- /dev/null +++ b/apps/web/src/abis/ERC1155DiscountValidatorV2.ts @@ -0,0 +1,58 @@ +export default [ + { + type: 'constructor', + inputs: [ + { + name: 'token_', + type: 'address', + internalType: 'address', + }, + { + name: 'tokenIds', + type: 'uint256[]', + internalType: 'uint256[]', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'isValidDiscountRegistration', + inputs: [ + { + name: 'claimer', + type: 'address', + internalType: 'address', + }, + { + name: 'validationData', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'error', + name: 'AddressEmptyCode', + inputs: [ + { + name: 'target', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'FailedInnerCall', + inputs: [], + }, +] as const; diff --git a/apps/web/src/addresses/usernames.ts b/apps/web/src/addresses/usernames.ts index ec2077e4d0..43e6e2f9ad 100644 --- a/apps/web/src/addresses/usernames.ts +++ b/apps/web/src/addresses/usernames.ts @@ -96,3 +96,8 @@ export const TALENT_PROTOCOL_DISCOUNT_VALIDATORS: AddressMap = { [baseSepolia.id]: '0x8b769A3fbC29AC02344218840602615B6c9200e7', [base.id]: '0xb16A4f14A9dED9e27F0Fe59Dc907D245769de19E', }; + +export const BASE_WORLD_DISCOUNT_VALIDATORS: AddressMap = { + [baseSepolia.id]: '0xFa69f6167F40247fe3EFF2d8375B25C5d7834c48', + [base.id]: '0xfEb00a4EfF372a307fDc556Cf4359f7D679E4d11', +}; diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-around-the-world-nft.svg b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-around-the-world-nft.svg new file mode 100644 index 0000000000..b75c9e6e70 --- /dev/null +++ b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/images/base-around-the-world-nft.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx index 4b2011840d..5d8169c427 100644 --- a/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx +++ b/apps/web/src/components/Basenames/RegistrationLearnMoreModal/index.tsx @@ -13,6 +13,7 @@ import BaseNFT from './images/base-nft.svg'; import TalentProtocolIcon from './images/TalentProtocol.svg'; import coinbaseOneVerification from './images/coinbase-one-verification.svg'; import coinbaseVerification from './images/coinbase-verification.svg'; +import BaseWorldNFT from './images/base-around-the-world-nft.svg'; import { StaticImageData } from 'next/dist/shared/lib/get-img-props'; import ImageWithLoading from 'apps/web/src/components/ImageWithLoading'; @@ -20,6 +21,82 @@ function InfoIcon() { return ; } +type DiscountItem = { + discount: Discount; + icon: StaticImageData; + alt: string; + label: string; + tooltipContent: string; +}; + +const DISCOUNT_ITEMS: DiscountItem[] = [ + { + discount: Discount.COINBASE_VERIFIED_ACCOUNT, + icon: coinbaseVerification as StaticImageData, + alt: 'icon of coinbase', + label: 'Coinbase verification', + tooltipContent: 'Verifies you have a valid trading account on Coinbase', + }, + { + discount: Discount.CB1, + icon: coinbaseOneVerification as StaticImageData, + alt: 'icon of coinbase one', + label: 'Coinbase One verification', + tooltipContent: 'Verifies you have an active Coinbase One subscription', + }, + { + discount: Discount.CBID, + icon: cbidVerification as StaticImageData, + alt: 'icon of CBID', + label: 'A cb.id username', + tooltipContent: 'cb.id must have been claimed prior to August 9, 2024.', + }, + { + discount: Discount.BASE_BUILDATHON_PARTICIPANT, + icon: baseBuildathonParticipant as StaticImageData, + alt: 'icon of base buildathon', + label: 'Base buildathon participant', + tooltipContent: 'Available for anyone holding a Base Buildathon participant NFT.', + }, + { + discount: Discount.TALENT_PROTOCOL, + icon: TalentProtocolIcon as StaticImageData, + alt: 'icon of talent protocol', + label: 'Builder score 50+', + tooltipContent: + 'Available for anyone with an onchain builder score 50+. Go to passport.talentprotocol.com to mint yours.', + }, + { + discount: Discount.SUMMER_PASS_LVL_3, + icon: summerPassLvl3 as StaticImageData, + alt: 'icon of summer pass', + label: 'Summer Pass Level 3', + tooltipContent: + 'Available for anyone holding a Summer Pass Level 3 NFT. Go to wallet.coinbase.com/ocs to get your Summer Pass', + }, + { + discount: Discount.BNS_NAME, + icon: BNSOwnership, + alt: 'icon of BNS', + label: 'BNS username', + tooltipContent: 'BNS (.base) username holders are eligible for a 0.01 ETH discount', + }, + { + discount: Discount.BASE_DOT_ETH_NFT, + icon: BaseNFT as StaticImageData, + alt: 'icon of Base', + label: 'Base.eth NFT', + tooltipContent: 'Available for anyone holding a base.eth NFT', + }, + { + discount: Discount.BASE_WORLD, + icon: BaseWorldNFT as StaticImageData, + alt: 'icon of Base World', + label: 'Base around the world NFT', + tooltipContent: 'Available for anyone holding one of the Base around the world NFTs', + }, +]; + export default function RegistrationLearnMoreModal({ isOpen, toggleModal, @@ -31,30 +108,6 @@ export default function RegistrationLearnMoreModal({ const hasDiscount = allActiveDiscounts.size > 0; const rowClasses = 'flex flex-row items-center justify-start'; - const CBRowClasses = classNames(rowClasses, { - 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.COINBASE_VERIFIED_ACCOUNT), - }); - const CB1RowClasses = classNames(rowClasses, { - 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.CB1), - }); - const CBIDRowClasses = classNames(rowClasses, { - 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.CBID), - }); - const BuildathonRowClasses = classNames(rowClasses, { - 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.BASE_BUILDATHON_PARTICIPANT), - }); - 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_DOT_ETH_NFT), - }); - const TalentProtocolRowClasses = classNames(rowClasses, { - 'opacity-40': hasDiscount && !allActiveDiscounts.has(Discount.TALENT_PROTOCOL), - }); const qualifiedClasses = classNames( 'flex flex-row items-center justify-center py-3 px-1 h-5 text-xs bg-green-0 rounded ml-3', @@ -71,174 +124,37 @@ export default function RegistrationLearnMoreModal({ : "You'll receive a name for free (5+ characters for 1 year) if your wallet has any of the following:"}

    -
  • - -
    - -

    Coinbase verification

    - -
    -
    - {allActiveDiscounts.has(Discount.COINBASE_VERIFIED_ACCOUNT) && ( -
    -

    Qualified

    -
    - )} -
  • -
  • - -
    - -

    Coinbase One verification

    - -
    -
    - {allActiveDiscounts.has(Discount.CB1) && ( -
    -

    Qualified

    -
    - )} -
  • -
  • - -
    - -

    A cb.id username

    - -
    -
    - {allActiveDiscounts.has(Discount.CBID) && ( -
    -

    Qualified

    -
    - )} -
  • -
  • - -
    - -

    Base buildathon participant

    - -
    -
    - {allActiveDiscounts.has(Discount.BASE_BUILDATHON_PARTICIPANT) && ( -
    -

    Qualified

    -
    - )} -
  • -
  • - -
    - -

    Builder score 50+

    - -
    -
    - {allActiveDiscounts.has(Discount.TALENT_PROTOCOL) && ( -
    -

    Qualified

    -
    - )} -
  • -
  • - -
    - -

    Summer Pass Level 3

    - -
    -
    - {allActiveDiscounts.has(Discount.SUMMER_PASS_LVL_3) && ( -
    -

    Qualified

    -
    - )} -
  • -
  • - -
    - -

    BNS username

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

    Qualified

    -
    - )} -
  • -
  • - -
    - -

    Base.eth NFT

    - -
    -
    - {allActiveDiscounts.has(Discount.BASE_DOT_ETH_NFT) && ( -
    -

    Qualified

    -
    - )} -
  • + {DISCOUNT_ITEMS.map(({ discount, icon, alt, label, tooltipContent }) => ( +
  • + +
    + +

    + {label} +

    + +
    +
    + {allActiveDiscounts.has(discount) && ( +
    +

    Qualified

    +
    + )} +
  • + ))}
{!hasDiscount && ( <> diff --git a/apps/web/src/hooks/useAggregatedDiscountValidators.ts b/apps/web/src/hooks/useAggregatedDiscountValidators.ts index b4f9e6b8dc..0e80c6023e 100644 --- a/apps/web/src/hooks/useAggregatedDiscountValidators.ts +++ b/apps/web/src/hooks/useAggregatedDiscountValidators.ts @@ -2,6 +2,7 @@ import { AttestationData, useBNSAttestations, useBaseDotEthAttestations, + useBaseWorldAttestations, useBuildathonAttestations, useCheckCB1Attestations, useCheckCBIDAttestations, @@ -56,6 +57,7 @@ export function useAggregatedDiscountValidators(code?: string) { useDiscountCodeAttestations(code); const { data: TalentProtocolData, loading: loadingTalentProtocolAttestations } = useTalentProtocolAttestations(); + const { data: BaseWorldData, loading: loadingBaseWorld } = useBaseWorldAttestations(); const loadingDiscounts = loadingCoinbaseAttestations || @@ -68,7 +70,8 @@ export function useAggregatedDiscountValidators(code?: string) { loadingBaseDotEth || loadingBNS || loadingDiscountCode || - loadingTalentProtocolAttestations; + loadingTalentProtocolAttestations || + loadingBaseWorld; const discountsToAttestationData = useMemo(() => { const discountMapping: MappedDiscountData = {}; @@ -143,6 +146,13 @@ export function useAggregatedDiscountValidators(code?: string) { discountKey: validator.key, }; } + + if (BaseWorldData && validator.discountValidator === BaseWorldData.discountValidatorAddress) { + discountMapping[Discount.BASE_WORLD] = { + ...BaseWorldData, + discountKey: validator.key, + }; + } }); return discountMapping; @@ -158,6 +168,7 @@ export function useAggregatedDiscountValidators(code?: string) { BNSData, DiscountCodeData, TalentProtocolData, + BaseWorldData, ]); return { diff --git a/apps/web/src/hooks/useAttestations.ts b/apps/web/src/hooks/useAttestations.ts index 19941dff21..194c5a88dd 100644 --- a/apps/web/src/hooks/useAttestations.ts +++ b/apps/web/src/hooks/useAttestations.ts @@ -5,10 +5,12 @@ import AttestationValidatorABI from 'apps/web/src/abis/AttestationValidator'; import CBIDValidatorABI from 'apps/web/src/abis/CBIdDiscountValidator'; import EarlyAccessValidatorABI from 'apps/web/src/abis/EarlyAccessValidator'; import ERC1155DiscountValidator from 'apps/web/src/abis/ERC1155DiscountValidator'; +import ERC1155DiscountValidatorV2 from 'apps/web/src/abis/ERC1155DiscountValidatorV2'; import ERC721ValidatorABI from 'apps/web/src/abis/ERC721DiscountValidator'; import TalentProtocolDiscountValidatorABI from 'apps/web/src/abis/TalentProtocolDiscountValidator'; import { BASE_DOT_ETH_ERC721_DISCOUNT_VALIDATOR, + BASE_WORLD_DISCOUNT_VALIDATORS, BUILDATHON_ERC721_DISCOUNT_VALIDATOR, TALENT_PROTOCOL_DISCOUNT_VALIDATORS, USERNAME_1155_DISCOUNT_VALIDATORS, @@ -592,3 +594,47 @@ export function useTalentProtocolAttestations() { } return { data: null, loading: isLoading, error }; } + +const baseWorldTokenIds = [ + BigInt(0), + BigInt(1), + BigInt(2), + BigInt(3), + BigInt(4), + BigInt(5), + BigInt(6), +]; + +export function useBaseWorldAttestations() { + const { address } = useAccount(); + const { basenameChain } = useBasenameChain(); + + const discountValidatorAddress = BASE_WORLD_DISCOUNT_VALIDATORS[basenameChain.id]; + + const readContractArgs = useMemo(() => { + if (!address) { + return {}; + } + return { + address: discountValidatorAddress, + abi: ERC1155DiscountValidatorV2, + functionName: 'isValidDiscountRegistration', + args: [address, encodeAbiParameters([{ type: 'uint256[]' }], [baseWorldTokenIds])], + }; + }, [address, discountValidatorAddress]); + + const { data: isValid, isLoading, error } = useReadContract({ ...readContractArgs, query: {} }); + if (isValid && address) { + return { + data: { + discountValidatorAddress, + discount: Discount.BASE_WORLD, + validationData: '0x0' as `0x${string}`, + }, + 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 7403afeec7..dc8a783835 100644 --- a/apps/web/src/utils/usernames.ts +++ b/apps/web/src/utils/usernames.ts @@ -398,6 +398,7 @@ export enum Discount { BASE_DOT_ETH_NFT = 'BASE_DOT_ETH_NFT', DISCOUNT_CODE = 'DISCOUNT_CODE', TALENT_PROTOCOL = 'TALENT_PROTOCOL', + BASE_WORLD = 'BASE_WORLD', } export function isValidDiscount(key: string): key is keyof typeof Discount { From ca413fe6a09d9a0d3069d2634a87fcb65979a63b Mon Sep 17 00:00:00 2001 From: wbnns Date: Wed, 6 Nov 2024 23:34:34 +0900 Subject: [PATCH 022/171] chore(ecosystem): Remove inactive project (#1215) Removes BotFi; inactive since mid-summer (report via community) --- apps/web/public/images/partners/botfi.webp | Bin 7330 -> 0 bytes apps/web/src/data/ecosystem.json | 9 --------- 2 files changed, 9 deletions(-) delete mode 100644 apps/web/public/images/partners/botfi.webp diff --git a/apps/web/public/images/partners/botfi.webp b/apps/web/public/images/partners/botfi.webp deleted file mode 100644 index 4a7267cb2ff21bc9560351d3910963280f3ea459..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7330 zcmV;T99`p5Nk&GR8~^}UMM6+kP&il$0000G0002L006%L06|PpNP!Ii00FSa|DPer z+4on~J+rsRo+_m?o*9pE2t|dmC^+;aqED^CmnV1qpy6)9!=hR?Uw%@zRu!( zS}Uk2QVK-SwK~_3Ezi91L(kgUv#kBg>o@JQ?2rK!01yeB2mrJWnXt#zpR%iW-m5Bm zSXI?~rzF|&_Jv!{KTrUO1WOD6w2j>Pf8UT~=Db%nr+Sxpl7ByS*o2`LfRO-+5dgzh zIN}+SOf#o!a`i6rBwsyw#Wny|0%I(IArlXKfn=6AWlq(nDal6;T&fCK35QqK6 zL6TYGl==2)O3wN38vTJN6xIOqZg$bPWR^H(5oW29H*8#iu_3S)7`fk5Bs-k4Ff~cZ zOD6)c6rGKLv8TUJW~pbfYLaoznezd*&}^B^ieQHA?SM%!>fEA zaX|>?zJ)GCzix-95Cn_APR9UyK7ncx2u4h%W0<~J8a97|5tErQ$jTokAnRZK4^0o1>$a9QSs4Udfk+1z{+Yx9yT6U)^ z-X*iF8=mAfOQC{1SFk6G#iit<`z{6`s>YFxtc`3GR{>zuq0f@q81-v!-T$Ax>w?|J8vs!yk|wI)@Qlhr{d>xc_IF=?_^MO(Ua<{bE3p*-Hpaj+J|(Ga zSe05dhv-|y<$*HkU81>X2q+{=@cS@2!PQ7g{z}hCL z;B*#HT~+TrySksge(31qW|CgngsP4te|Y=22-fsggjK$wW3j7xCCMbADw|e4$?WT+ zBG}#$v^-455UP??Hn%FNU+)5u-l*UhQiG6JCHcZ)Fg<56ZYmwi?wq|L(nBJ+h>j)r zT!{2cg!O09mmek1fk;mn^t+cSORy@>0Hg;ZY@4Z;A$tD3Gi>(^2Hee*hoi>a0< zcJiNqG$5#+$Ao3t@%6=E8Za22W?HH=<0`NX07tTu<+_vmLh2bT^a*{*N_k-mq>f-c zCM?+*?;c!FFyLBd;|AsC)GEd!oYZo2-QefMd2-#t#d{)SsWnnIG!X+s$w7*Yq=jTuXz z>g+M5Z;b9nt*w3AhAcSoxJOAkZRoDK>$TobUj|a{jNUY@Ll1b9)OrcewzULv|EyX9 zQl1|}dPqdjwaW(>dMXG_4KR|Nn@JxT4N+ObkSdp3ReG~*U9Xhzv( z4wX!KPaC9G%gfa8rM#*Y(i8?uyi3nc9t=V4LEba?j?>4%qB$ch`5qHap8#8DeU^ZP zX9LnCh%x31@?1b_$vGt;M;iltK2KxhbMt4Tc2_*2$cPDdUTS zVR8in2>M^mlwo$f5;1aZ02qwx@ERuN-}|7FubTEGCJeLZSF6G1POxA*eV`oQo*$+F zHe$j+XS@#;WPR0?1M69&s2UjT?sWfrdA_CTCB!Jud8 zL*+SbeMI@2=XbB+nz3;aEWw1KR{9fB$s4c_vw-O-1rVQ47c4s7h?d;7K1d&~j1Qoa zyFpW_;c|RyKA2ny(Dt(i%=6=Mu(^VujVZ&e{B>u8dzd$sHgV(wO7aWidH4O!>bSNRvfa zV-}sZ0ciWvYd!Uyn_|=)YG5?uA%HZM#fWx#zE%Y=u$>L0JOJ3_x8Tr|djir#26Mei zbv)hx==&Ibos?I!0H>x5uPXOI6ASv>Pt}Yg0HAsWT`kA2$Djo_GsNn-J0Q&=f|2jh z)AN;nU2p)i8k%tr*b1Hw64mq27?36qV8wPiGUKtP#zdwyQa(5Y)>NlbLqul0Y6u|G zO9J44Kj=uN90dUc^V2CwRc?h?Ov{&{qT`qARsbWtU;r$3BfTf-cwhfoZST{QOnFCt zh{4F|!J;QGT{HrWNRNmCF!b=>n6hia?NGbr5;{`V?SL2-YyA)|s$<7lOU3}Kk%mN! z1t1GfdYjI%tLL=&AT=);8;e@3Rtnh6gV z)HGO(Dd~6;V!&dJuu)S_K6l*8OU;dj`!BT8{`X~M>RDU;AIm{%2!_2v=lJmouw{VM zG$Z-J?MLsj!Ah%4oH%ji^(Gy6{VN@$Pdw`^ke`AAkJm z2PH}E%#;nP|8)!uY75q9qC5swlm(LNvZTYYrteHrr))go*%73U!NT8oe!VtqnW(A0 zrn38ka`c_22@dxB-slH&OFctmJI!E5EjlSiP6=G?rTkaryk_X$O2>I+Q#PRW$M5^d*D50TG&c&{Wk@p?qloOjBV~re#7oXEZFD zJHDT;MEJi}gGIBC`76`XpxhY-&3`nLvXF6i2%28d_B>rVNcnH%bmAXg%RtKWTJj;l zK1@qM#`9W`Q$h7|roqd2UJG(-FyFJx0_S*s6_OiROqfO=GNu1#9|&@6vF@+*;Zkx> zK#*&LJu~`1De3kE1Ua|ZE2R&TC#SCgi~L8}C!r6JBVQba2t^oU-%ffBj5_j;p@>n8 zF?OFr)u4Ft=aVADD9S3urYA(msD4LJ|(YO1{H(CH5Mz~P9|Dxnv#?E2P!CHiBUc5Q!>@!(k$cemn;Ko zQP2@E{(mXctf;b-e0W135)`&XFdN@VCRqV#mXeQbHw0o)=oT2b^`m5xX|ZUUk&kXQ z1c(F$FA*?glZVMH)#A`JB_G**2oM@vr^Ork{M)gw_fG5tPEH zWyk-$Lv0e}j4QSq77G9fat{DjP&gp+4gdhqNdTPzD!>5106uLrl}IEbA|WMm3ow8U ziDU?H0fyAJ{yVZnCwdRcUWE>V`rqlERAd|6kMg}~dOH0H|5Mx(^>6i$uHW^~`aYmP zmj8zRy!VOpHT~cHKkNs<2lj9C9b?{rAFv*pKe(R+zqjAG9|2#^|Hgm+{o(ik^Z@_U z>H+D`*gn$#+vLADFK06P2SX-_O25YNc8sq`-1&WJ><9e2`){TXufN={Vo%bq{p_+~ zo{1{`0H{0P$Ni)U2u8S#j=^F-lxr#Z_sW;1Z8|I^d@+W1P8tWv{Sm((E>%}D0GoZj z150(etMYFL-2H=Zz>!G`QSjeX)!R0P=j#SBUXo(UEA9u>svG=6=3ftm6uO6V*=ncg z?QY*m$nkCu#NKrPNr;F3mkj-?HI@%h>@V4KU(IrCtlK!>WUnc)ZQ`SG$w%ns-ALJc5vf3auK^|@tm2a(qM?w1pt z?%)N?U1{|=yklD3i!BT4Gg!0iH?Hf4fuJK*wzSmOfxcnzCRD*>ZS}tdKhaFtENz^Q zJE(Bj<^tbqnjog^RV**;M3DGxB-6=}-9{eZ2ZqsJr}qLIO0-{=>6PrdBdR`33-%LA(pDX7aZc2?2aoA(g@b#k*){xGS~ zP?vhz=$^m@ErmpnHpSBMtct^(vX;Z`%|pEmhA|iKAtPzAJ~$+0dZ$FHP<_ZM3eW-a z0Ue;;_a#ZY&+4wL8nd}W=fQxT<+nWjdg8r?rn*Cr%DI?Q`Ndeasg6iPR$*wd6b?%^ zLft`UO(A~RWTFd|H~!!}T+GpFkqPulFqboL zn`kX%4gHDB_Wj(bO=}D~{RcsHmCDh_;yO@|nA7X$9aT6I{ zn7oM+Im0uB{LanN8q~t+*ARS0NBtdmpGPUYbPu_aPWaFLbrDbI48>{e6_TZnUXkb) zyK1?;yXX+Zd^%s~l3$)4FKATshE059qUFoes^i zY6Zi1+G)`XhzFXYZ}6&TaeLBczePJkbL`p}kJ-)jzM7+%!>uZonSLd(`fjrR3^1V( zu-%vH@?&7nJv@ak@XJ9>j1o^Dt@p0ZdMtT2zeg8@jLCdGo^tu9)kULjrtujG{k32X6|ulRFcGa^Qn7 z3;bhlKJ13tdJC5BG?Bqjh2|Mw~0cb$Q)_s^S9+s*s56#GvIiDtsu zBUa&eO>TphhSrmS=TW6+#y5Vg3a$ES);MbyMJosw%Q%x@GzEIz)LiAb^)T|dpwx0R zu>b(1G3@5aXZx#l?t4JQUnw>Asm@*u+dbpj!@wKQQd19*yvu6gp$MWVxTtEqQG> z6$Y%CAR9``H@~$}%gEm3OT!LXseZN^RAi$`F{zb?wZz!cIuz?lc@d$0RN?bwPi4pc z4UT9<3=nXM8k(W`i4I&*C4r#JTKB00vNe`ZWU)6!*H{0liQK+7!p$!5)us8{Fptxh zb&HmQNI+(c~&b5O5N<53pM(|5Mk?|l|MoB?L z+xv}fXCrq9RJr|t8BlUE}vPA}e9rH;m5h2^CkBelM zEV%vOU$iyryQLPA(!)@GJ>@9rxcS7Mk$}8o@>n(l}pOE2y zNx`C`9c1guP$3yLftl<4z46GauEBp)hDQlg#6+ZyJdumZMeCsAUHXxXGzUO2X+6&9 zl{vX@IpoBaVXpUi5V$gcSIb?Kd~(knn=5BLJtcE8BwYqOf0XbmO?dRa`{xnbE}n1I z|Jb-DZF&OD>sQan-rr)$;?W{}&Hj!HYfkQ`3r3MdVWu@+E?wnwtSQ#cS^B*C?5LD% zXqESt#pf?}=tqdcsswXMyo&*>ea6?OwVD*e9UE`bWMz9@Kc?m`-wIDi2#P^yK1&&S zKQj1ANNI>7R-|IyvdRLD>OXVq8=S}>aN8mcMr~SnwEg}tgRU^6{v(Oj0Am6{bUx+) z@CzD<6ZJ`@oefH#2|U~BG+#PF$6X@yeuT9=SqyuzU8bN^yE8?iO@D-wSqQ4G;^1EL zO&rj)qGMx*eSI{|s)me}or0CSML-~5Yf%c1K02>GM6?|on4Uj18-MZFKP zn>5nBvZ?D=_j018QWnsx10sXj34&gE(B@HUzp`-Ply#_c;YL*rm4e7)dRR|u$7huo(v+JeZtiWzhPwG=#}HzWB9J$k&(?XjcYwu$O?cRB4CoNQf&+Bo4p_$)gmyU$=W4bejxQ!G0&Zwk^{QbZWR&yVyz{_K^n?b|B5|a2h1uZSb*5qozXPVEUM394G!Pe1p70E{F!i&g z*8dXNq`U`G@yU!m(xu-)rx5~+hq%XqM@*}G{Y%N;xL;7zCxjn9T!p~XO{y{#u9o6B z`4Ql;KZ&RimUI^?wT711$h?kxH_o`@TBl(ymN&%PjMgY`@5y3IQ=Tb(e#~GFuju+4 z;Ew42w9ax>sAuje!l7#{VzXkC@xO(-a0hyWMv7mGDK56V(?UBug#aVfJ-Y5rq!R^8 zXg)B9kSr~J#feuYl}<;yLszE%Dss;FIfIohN(KH~-NjC|l;2CLz7WV!^rgHUbQH;< z2&_5A5;+_lQ(h=YB)~tGrbBvbY^6MLQKz{OOD3L#1qssRWD+~yG6WZgIoz->HjeBBarPkSqBinJ{gcDS2xD?n;uyfC=|9Ha2yNwHNj2H{PxsHF+|>(}L&ZxP z@(PL)yU3w&6OW5Tp(nhjbXj;wK0pzsH75{mg$w)X-IWU%(F==KQSHkC=nFWF+!7TI z#U&+*g~em2jBGn}^SI0K{8YYtT2Dp`phP1(Hlp5ERucC*IjEyGrB@JN>Rx&-`Td>j z(IT*gI?UrCY=qZgwF3*i(Iqha?;n*^xmUr33EaQpO=#gPFeoD)M{{wYL&~3pXg;4G zy1MxN%7@`RMWA8y;H|JKcuMKN&W&}tbg)3yak@1Yz7KnVevA0|gatdA z^8PxFt!n7teD$=0d;f{&1bX}{{=$_aRS)?SemvAYdh}HnLv~yexn4B&+>Vs#V@vEr zF|H$kRbpjOHpEN1)}w1XOo8s30T`w7npco(hTrWjlZnhCNN;34{r2sy?ji!>4Pg2_ z-ozL%$v1FL(^A=gIP&H7Uo{HAZ{1Q&-9q=mEk&q3NS+gag6Y-;iY6a5jSpAXT#KLC z@o1!tkHYU!!ieE9SYFdw!$Yvf=a5xtYMaHR-M^LO?|`p#7#3A)qCe4{zH^;bFKiJ`!y+59?`V0T{yF Date: Wed, 6 Nov 2024 13:20:16 -0500 Subject: [PATCH 023/171] Fix 1155 Discount validator token encoding (#1217) --- apps/web/src/hooks/useAttestations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/hooks/useAttestations.ts b/apps/web/src/hooks/useAttestations.ts index 194c5a88dd..aab973f6e1 100644 --- a/apps/web/src/hooks/useAttestations.ts +++ b/apps/web/src/hooks/useAttestations.ts @@ -629,7 +629,7 @@ export function useBaseWorldAttestations() { data: { discountValidatorAddress, discount: Discount.BASE_WORLD, - validationData: '0x0' as `0x${string}`, + validationData: encodeAbiParameters([{ type: 'uint256[]' }], [baseWorldTokenIds]), }, loading: false, error: null, From d189f3a93d1b7bedb9069a65fb991f54c55678ba Mon Sep 17 00:00:00 2001 From: witty <131909329+0xwitty@users.noreply.github.com> Date: Thu, 7 Nov 2024 18:30:51 +0300 Subject: [PATCH 024/171] Typo corrections (#1213) * typo Update privacy-policy.md The spelling error in the text. * typo Update terms-of-service.md There is one orthographic mistake. --- apps/base-docs/docs/privacy-policy.md | 2 +- apps/base-docs/docs/terms-of-service.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base-docs/docs/privacy-policy.md b/apps/base-docs/docs/privacy-policy.md index 765a0e4cde..22115fd66b 100644 --- a/apps/base-docs/docs/privacy-policy.md +++ b/apps/base-docs/docs/privacy-policy.md @@ -13,7 +13,7 @@ Last updated: July 12, 2023 At Base (referred to here as “**we**”, “**us**” or “**our**”), we respect and protect the privacy of those users and developers (“**you**” and “**your**” or “**Users**” and “**Developers**”, as relevant) who explore and use Base (“**Base**”) through the Base protocol or any other applications, tools, and features we operate  (collectively, the “**Services**”). -This Privacy Policy describes how we collect, use, and disclose personal information when you use our Services, which include the services offered on our website [https://base.org](https://base.org/) ( “**Site**”). This Privacy Policy does not apply to any processing which Base carries out as a processor on behalf of those Users and Developers who explore and use Base. Please note that we do not control websites, applications, or services operated by third parties, and we are not responsible for their actions. We encourage you to review the privacy policies of the other websites, decentralised applications, and services you use to access or interact with our Services. +This Privacy Policy describes how we collect, use, and disclose personal information when you use our Services, which include the services offered on our website [https://base.org](https://base.org/) ( “**Site**”). This Privacy Policy does not apply to any processing which Base carries out as a processor on behalf of those Users and Developers who explore and use Base. Please note that we do not control websites, applications, or services operated by third parties, and we are not responsible for their actions. We encourage you to review the privacy policies of the other websites, decentralized applications, and services you use to access or interact with our Services. # 1. WHAT INFORMATION WE COLLECT  diff --git a/apps/base-docs/docs/terms-of-service.md b/apps/base-docs/docs/terms-of-service.md index 7bc6621333..595026449e 100644 --- a/apps/base-docs/docs/terms-of-service.md +++ b/apps/base-docs/docs/terms-of-service.md @@ -63,7 +63,7 @@ You agree that you will not use the Services in any manner or for any purpose ot ‍By using the Services, Base, or the Bridging Smart Contracts, you represent that you understand there are risks inherent in using cryptographic and public blockchain-based systems, including, but not limited, to the Services and digital assets such as bitcoin (BTC) and ether (ETH). You expressly agree that you assume all risks in connection with your access and use of Base, the Bridging Smart Contracts, Basenames, and the separate Services offered by Coinbase. That means, among other things, you understand and acknowledge that: - The Base, the Bridging Smart Contracts, Basenames, and the separate Services may be subject to cyberattacks and exploits, which could result in the irrevocable loss or reduction in value of your digital assets or in additional copies of your digital assets being created or bridged without your consent. -- Base is subject to periodic upgrades by the Optimism Collective. The Optimism Collective may approve a protocol upgrade that, if implemented, may significantly impacts Base, and may introduce other risks, bugs, malfunctions, cyberattack vectors, or other changes to Base that could disrupt the operation of Base, the Bridging Smart Contracts, Basenames, or the Services or otherwise cause you damage or loss. +- Base is subject to periodic upgrades by the Optimism Collective. The Optimism Collective may approve a protocol upgrade that, if implemented, may significantly impact Base, and may introduce other risks, bugs, malfunctions, cyberattack vectors, or other changes to Base that could disrupt the operation of Base, the Bridging Smart Contracts, Basenames, or the Services or otherwise cause you damage or loss. - If you lose your Wallet seed phrase, private keys, or password, you might permanently be unable to access your digital assets. You bear sole responsibility for safeguarding and ensuring the security of your Wallet. You further expressly waive and release Coinbase, its parents, affiliates, related companies, their officers, directors, members, employees, consultants, representatives. agents, partners, licensors, and each of their respective successors and assigns (collectively, the “Coinbase Entities”) from any and all liability, claims, causes of action, or damages arising from or in any way related to your use of the Services, and your interaction with Base, the Bridging Smart Contracts, or Basenames. Also, to the extent applicable, you shall and hereby do waive the benefits and protections of California Civil Code § 1542, which provides: “[a] general release does not extend to claims that the creditor or releasing party does not know or suspect to exist in his or her favor at the time of executing the release and that, if known by him or her, would have materially affected his or her settlement with the debtor or released party.” From 6336a6a3e7be071ed7880e5b7692b86440e29e91 Mon Sep 17 00:00:00 2001 From: Davion Selever Date: Thu, 7 Nov 2024 18:31:40 +0300 Subject: [PATCH 025/171] Corrected errors in docs (#1211) --- apps/base-docs/base-learn/docs/arrays/arrays-in-solidity.md | 2 +- .../base-learn/docs/arrays/filtering-an-array-sbs.md | 2 +- .../basic-functions-exercise.md | 4 ++-- .../hello-world-step-by-step.md | 4 ++-- .../docs/deployment-to-testnet/contract-verification-sbs.md | 2 +- .../base-learn/docs/erc-20-token/erc-20-token-sbs.md | 4 ++-- apps/base-docs/base-learn/docs/error-triage/error-triage.md | 2 +- .../base-learn/docs/hardhat-deploy/hardhat-deploy-sbs.md | 6 +++--- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/base-docs/base-learn/docs/arrays/arrays-in-solidity.md b/apps/base-docs/base-learn/docs/arrays/arrays-in-solidity.md index b01053975b..6e0b661200 100644 --- a/apps/base-docs/base-learn/docs/arrays/arrays-in-solidity.md +++ b/apps/base-docs/base-learn/docs/arrays/arrays-in-solidity.md @@ -69,7 +69,7 @@ contract StorageArray { You cannot use a `storage` array as a function parameter, and you cannot write a function that `return`s a `storage` array. -Storage arrays are dynamic, unless they are declared with an explicit size. However, their functionality is limited compared to other languages. The `.push(value)` function works as expected. the `.pop()` function removes the last value of an array, but it **does not** return that value. You also **may not** use `.pop()` with an index to remove an element from the middle of an array, or to remove more than one element. +Storage arrays are dynamic, unless they are declared with an explicit size. However, their functionality is limited compared to other languages. The `.push(value)` function works as expected. The `.pop()` function removes the last value of an array, but it **does not** return that value. You also **may not** use `.pop()` with an index to remove an element from the middle of an array, or to remove more than one element. You can use the `delete` keyword with an array. Doing so on an entire array will reset the array to zero length. Calling it on an element within the array will reset that value to its default. It **will not** resize the array! diff --git a/apps/base-docs/base-learn/docs/arrays/filtering-an-array-sbs.md b/apps/base-docs/base-learn/docs/arrays/filtering-an-array-sbs.md index 84f611fafb..7b5a53639a 100644 --- a/apps/base-docs/base-learn/docs/arrays/filtering-an-array-sbs.md +++ b/apps/base-docs/base-learn/docs/arrays/filtering-an-array-sbs.md @@ -170,7 +170,7 @@ uint[] public numbers; uint numEven; ``` -Add a new function called `debugLoadArray` that takes a `uint` called `_number` as an argument, and fills the array by looping through `_numbers` times, pushing each number into the array. **For now, _don't_ update `numEven`**. +Add a new function called `debugLoadArray` that takes a `uint` called `_number` as an argument, and fills the array by looping through `_number` times, pushing each number into the array. **For now, _don't_ update `numEven`**.
diff --git a/apps/base-docs/base-learn/docs/contracts-and-basic-functions/basic-functions-exercise.md b/apps/base-docs/base-learn/docs/contracts-and-basic-functions/basic-functions-exercise.md index 57b7b80958..be026d9e4c 100644 --- a/apps/base-docs/base-learn/docs/contracts-and-basic-functions/basic-functions-exercise.md +++ b/apps/base-docs/base-learn/docs/contracts-and-basic-functions/basic-functions-exercise.md @@ -26,8 +26,8 @@ A function called `adder`. It must: - Accept two `uint` arguments, called `_a` and `_b` - Return a `uint` `sum` and a `bool` `error` -- If `_a` + `_b` do not overflow, it should return the `sum` and an `error` of `false` -- If `_a` + `_b` overflow, it should return `0` as the `sum`, and an `error` of `true` +- If `_a` + `_b` does not overflow, it should return the `sum` and an `error` of `false` +- If `_a` + `_b` overflows, it should return `0` as the `sum`, and an `error` of `true` ### Subtractor diff --git a/apps/base-docs/base-learn/docs/contracts-and-basic-functions/hello-world-step-by-step.md b/apps/base-docs/base-learn/docs/contracts-and-basic-functions/hello-world-step-by-step.md index 04929a660e..3c2c535d6b 100644 --- a/apps/base-docs/base-learn/docs/contracts-and-basic-functions/hello-world-step-by-step.md +++ b/apps/base-docs/base-learn/docs/contracts-and-basic-functions/hello-world-step-by-step.md @@ -77,7 +77,7 @@ Is `public` the most appropriate [visibility specifier]? It would work, but you won't be calling this function from within the contract, so `external` is more appropriate. -You also need to specify a return type, and we've decided this function should return a string. You'll learn more about this later, but in Solidity, many of the more complex types require you to specify if they are `storage` or `memory`. You can then have your function return a string of `"Hello World!`. +You also need to specify a return type, and we've decided this function should return a string. You'll learn more about this later, but in Solidity, many of the more complex types require you to specify if they are `storage` or `memory`. You can then have your function return a string of `"Hello World!"`. Don't forget your semicolon. They're mandatory in Solidity! @@ -136,7 +136,7 @@ function Greeter(string memory _name) external pure returns (string memory) { Unfortunately, this does not work in Solidity. The error message you receive is a little confusing: -> TypeError: Operator + not compatible with types literal_string "Hello " and string memory. +> TypeError: Operator + not compatible with types literal_string "Hello" and string memory. You might think that there is some sort of type casting or conversion error that could be solved by explicitly casting the string literal to string memory, or vice versa. This is a great instinct. Solidity is a very explicit language. diff --git a/apps/base-docs/base-learn/docs/deployment-to-testnet/contract-verification-sbs.md b/apps/base-docs/base-learn/docs/deployment-to-testnet/contract-verification-sbs.md index 41e84cc731..3272ae93d1 100644 --- a/apps/base-docs/base-learn/docs/deployment-to-testnet/contract-verification-sbs.md +++ b/apps/base-docs/base-learn/docs/deployment-to-testnet/contract-verification-sbs.md @@ -4,7 +4,7 @@ description: Verify your contract and interact with it. hide_table_of_contents: false --- -Once your contract is deployed, you can verify it using a number of popular services. Doing so will let you users have confidence that your contract does what you claim, and will allow you to interact with it using a similar interface to what you used in Remix. +Once your contract is deployed, you can verify it using a number of popular services. Doing so will let your users have confidence that your contract does what you claim, and will allow you to interact with it using a similar interface to what you used in Remix. --- diff --git a/apps/base-docs/base-learn/docs/erc-20-token/erc-20-token-sbs.md b/apps/base-docs/base-learn/docs/erc-20-token/erc-20-token-sbs.md index cca565b3c8..73f0364dcf 100644 --- a/apps/base-docs/base-learn/docs/erc-20-token/erc-20-token-sbs.md +++ b/apps/base-docs/base-learn/docs/erc-20-token/erc-20-token-sbs.md @@ -12,8 +12,8 @@ The ERC-20 is a standard that allows for the development of fungible tokens and By the end of this lesson you should be able to: -- Describe OpenZepplin -- Import the OpenZepplin ERC-20 implementation +- Describe OpenZeppelin +- Import the OpenZeppelin ERC-20 implementation - Describe the difference between the ERC-20 standard and OpenZeppelin's ERC20.sol - Build and deploy an ERC-20 compliant token diff --git a/apps/base-docs/base-learn/docs/error-triage/error-triage.md b/apps/base-docs/base-learn/docs/error-triage/error-triage.md index 1234fb638b..d50e34bcfe 100644 --- a/apps/base-docs/base-learn/docs/error-triage/error-triage.md +++ b/apps/base-docs/base-learn/docs/error-triage/error-triage.md @@ -406,7 +406,7 @@ function badRandomLoopFixed() public view returns (uint) { The `uint` type will _panic_ in the event of an overflow or underflow. ```solidity -function badSubstraction() public pure returns (uint) { +function badSubtraction() public pure returns (uint) { uint first = 1; uint second = 2; return first - second; diff --git a/apps/base-docs/base-learn/docs/hardhat-deploy/hardhat-deploy-sbs.md b/apps/base-docs/base-learn/docs/hardhat-deploy/hardhat-deploy-sbs.md index bae9771ff1..063e6a8eec 100644 --- a/apps/base-docs/base-learn/docs/hardhat-deploy/hardhat-deploy-sbs.md +++ b/apps/base-docs/base-learn/docs/hardhat-deploy/hardhat-deploy-sbs.md @@ -159,8 +159,8 @@ Reuse `Lock__factory` but use the connect function and pass the address of the n ✔ should get the unlockTime value ✔ should have the right ether balance ✔ should have the right owner - ✔ shouldn"t allow to withdraw before unlock time (51ms) - ✔ shouldn"t allow to withdraw a non owner + ✔ shouldn't allow to withdraw before unlock time (51ms) + ✔ shouldn't allow to withdraw a non owner ✔ should allow to withdraw a owner 6 passing (2s) @@ -170,7 +170,7 @@ Reuse `Lock__factory` but use the connect function and pass the address of the n Deploying to a real test network involves configuring the network parameters in the hardhat config file. You need to include parameters such as: -- The JSON RPC url +- The JSON RPC URL - The account you want to use - Real test ether or the native Blockchain token for gas costs From e8d2b458abfeee0188fe67f3195cc4c1d9666452 Mon Sep 17 00:00:00 2001 From: Pat Date: Thu, 7 Nov 2024 09:32:16 -0600 Subject: [PATCH 026/171] docstutorial): update 'Pay' component to 'Checkout' for consistency (#1209) --- ...tutorial.md => 2_ock-checkout-tutorial.md} | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename apps/base-docs/tutorials/docs/{2_ock-pay-tutorial.md => 2_ock-checkout-tutorial.md} (84%) diff --git a/apps/base-docs/tutorials/docs/2_ock-pay-tutorial.md b/apps/base-docs/tutorials/docs/2_ock-checkout-tutorial.md similarity index 84% rename from apps/base-docs/tutorials/docs/2_ock-pay-tutorial.md rename to apps/base-docs/tutorials/docs/2_ock-checkout-tutorial.md index af3b81c93e..5dc83f2dc1 100644 --- a/apps/base-docs/tutorials/docs/2_ock-pay-tutorial.md +++ b/apps/base-docs/tutorials/docs/2_ock-checkout-tutorial.md @@ -1,6 +1,6 @@ --- title: 'Build a eCommerce App using Coinbase Commerce and OnchainKit' -slug: /coinbase-commerce-payment-integration +slug: /coinbase-commerce-onchainkit-checkout description: Learn how to integrate Coinbase Commerce payments into your application using OnchainKit. author: hughescoin keywords: [ @@ -56,7 +56,7 @@ Here, you'll need to add a detailed description of the product or service you're ![pay-commerce-uuid](../../assets/images/onchainkit-tutorials/pay-create-product-details.png) -Once your product is created you will be presented with a small popup that contains a link to your products hosted page. Click on the `View product` button. This will take you to a page with more details about your newly created product. Pay close attention to the URL of this page, as it contains a crucial piece of information: the product's UUID. You'll need to copy this UUID from the URL. +After creating your product, click `View product` in the popup to access the product page and copy the UUID from its URL. ![pay-commerce-uuid](../../assets/images/onchainkit-tutorials/pay-copy-product-link.png) @@ -142,12 +142,12 @@ export const NEXT_PUBLIC_WC_PROJECT_ID = process.env.NEXT_PUBLIC_WC_PROJECT_ID; ## Implementing the Payment Component -To implement the payment component, start by opening the `src/app/page.tsx` file. You'll need to add some new imports at the top of the file: import the `Pay`, `PayButton`, and `PayStatus` components from '@coinbase/onchainkit', as well as the `Image` component from 'next/image'. +To implement the payment component, start by opening the `src/app/page.tsx` file. You'll need to add some new imports at the top of the file: import the `Checkout`, `CheckoutButton`, `CheckoutStatus` components from '@coinbase/onchainkit', as well as the `Image` component from 'next/image'. Next, create a constant for your product ID using the environment variable you set up earlier. This will allow you to easily reference your product in the payment component. ```typescript -import { Pay, PayButton, PayStatus } from '@coinbase/onchainkit'; +import { Checkout, CheckoutButton, CheckoutStatus } from '@coinbase/onchainkit/checkout'; import Image from 'next/image'; const productId = process.env.NEXT_PUBLIC_PRODUCT_ID; @@ -168,7 +168,7 @@ For visual appeal, add an image of your product to the `/public` folder. This im When setting up the payment component, it's important to implement conditional rendering. This ensures that the payment button only appears once the user's wallet is connected. This approach provides a smoother user experience and prevents potential errors from attempting to initiate a payment before a wallet is available. ::: -Finally, configure the Pay component within your JSX. Wrap the `PayButton` and `PayStatus` components inside the `Pay` component, passing your `productId` as a prop to the `Pay` component. Set the `coinbaseBranded` prop on the `PayButton` to true for consistent branding. This setup creates a complete payment flow, allowing users to initiate a payment and view its status all within your application. +Finally, configure the Checkout component within your JSX. Wrap the `CheckoutButton` and `CheckoutStatus` components inside the `Checkout` component, passing your `productId` as a prop to the `Checkout` component. Set the `coinbaseBranded` prop on the `CheckoutButton` to true for consistent branding. This setup creates a complete payment flow, allowing users to initiate a payment and view its status all within your application. ```jsx
@@ -181,10 +181,10 @@ Finally, configure the Pay component within your JSX. Wrap the `PayButton` and ` {' '} {/* Added spacing */} {address ? ( - - - - + + + + ) : ( )} @@ -200,7 +200,7 @@ You may now test your implementation locally by running `bun run dev` Congratulations! You've successfully integrated Coinbase Commerce payments into your application using OnchainKit. This is a significant achievement that opens up new possibilities for your business. -As next steps, consider expanding your product catalog by adding more items to your site. Each new product can be seamlessly integrated using the same Pay component, allowing you to create a diverse and engaging e-commerce experience. Once you're satisfied with your application, you can easily deploy it using a service like Vercel, making your creation accessible to users worldwide. Keep exploring and building – the potential for your onchain commerce application is limitless! +As next steps, consider expanding your product catalog by adding more items to your site. Each new product can be seamlessly integrated using the same Checkout component, allowing you to create a diverse and engaging e-commerce experience. Once you're satisfied with your application, you can easily deploy it using a service like Vercel, making your creation accessible to users worldwide. Keep exploring and building – the potential for your onchain commerce application is limitless! --- From ac97665c4ac1049ce420ba5fd1de541911fdac24 Mon Sep 17 00:00:00 2001 From: Pavel Zaborskii Date: Thu, 7 Nov 2024 17:15:17 +0100 Subject: [PATCH 027/171] correct quorum spelling in issue descriptions (#1212) --- .../docs/reading-and-displaying-data/useReadContract.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/base-docs/base-learn/docs/reading-and-displaying-data/useReadContract.md b/apps/base-docs/base-learn/docs/reading-and-displaying-data/useReadContract.md index 9630ba46e9..1431d4beaf 100644 --- a/apps/base-docs/base-learn/docs/reading-and-displaying-data/useReadContract.md +++ b/apps/base-docs/base-learn/docs/reading-and-displaying-data/useReadContract.md @@ -58,19 +58,19 @@ Add the following two issues: ```text _issueDesc: We should enable light mode by default. -_quorom: 2 +_quorum: 2 ``` ```text _issueDesc: We should make inverted mouse controls the default selection. -_quorom: 2 +_quorum: 2 ``` Switch to a **different wallet address**. Claim your tokens with the new address, and add one more issue: ```text _issueDesc: Two spaces, not four, not tabs! -_quorom: 2 +_quorum: 2 ``` Call the `getAllIssues` function under the `Read Contract` tab to make sure all three are there. From ce51c6e33d8e30036e4f338cad4148d6265ff979 Mon Sep 17 00:00:00 2001 From: Matthew Bunday Date: Thu, 7 Nov 2024 15:26:45 -0500 Subject: [PATCH 028/171] Multiname management (#1216) * Scaffold page * Add NamesList component * API endppint for getUsernames * Add NamesList component * Move route * Ugly list demo working * Style it up a bit * Manage names list styling and expiry display * Style the header * Add triple dot icon * Triple dot dropdown menu * Set as primary working * Work on transfers, checkpoint * Work on transfers * Lint unused dep * UI polish * Add empty state * Resolve type errors? * Reolve types * Slightly better empty state * Remove console.log * Only show My Basenames if the user has a wallet connected * Improve dropdown mechanics * Spacing feedback * Error handling ala Leo * Handle success / failure correctly * Lint * Add some mobile margin * Fix mobile padding --- .../api/basenames/getUsernames/route.ts | 29 +++++ .../web/app/(basenames)/manage-names/page.tsx | 32 +++++ apps/web/next-env.d.ts | 2 +- apps/web/package.json | 1 + .../Basenames/ManageNames/NameDisplay.tsx | 111 ++++++++++++++++++ .../Basenames/ManageNames/NamesList.tsx | 81 +++++++++++++ .../Basenames/ManageNames/hooks.tsx | 105 +++++++++++++++++ .../context.tsx | 8 +- .../index.tsx | 5 +- apps/web/src/components/Dropdown/index.tsx | 15 ++- .../web/src/components/DropdownMenu/index.tsx | 8 +- apps/web/src/components/Icon/Icon.tsx | 53 +++++++++ .../components/Layout/UsernameNav/index.tsx | 12 +- apps/web/src/hooks/useSetPrimaryBasename.ts | 9 +- apps/web/src/types/ManagedAddresses.ts | 17 +++ yarn.lock | 8 ++ 16 files changed, 479 insertions(+), 17 deletions(-) create mode 100644 apps/web/app/(basenames)/api/basenames/getUsernames/route.ts create mode 100644 apps/web/app/(basenames)/manage-names/page.tsx create mode 100644 apps/web/src/components/Basenames/ManageNames/NameDisplay.tsx create mode 100644 apps/web/src/components/Basenames/ManageNames/NamesList.tsx create mode 100644 apps/web/src/components/Basenames/ManageNames/hooks.tsx create mode 100644 apps/web/src/types/ManagedAddresses.ts diff --git a/apps/web/app/(basenames)/api/basenames/getUsernames/route.ts b/apps/web/app/(basenames)/api/basenames/getUsernames/route.ts new file mode 100644 index 0000000000..b1baae10cf --- /dev/null +++ b/apps/web/app/(basenames)/api/basenames/getUsernames/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from 'next/server'; + +import type { ManagedAddressesResponse } from 'apps/web/src/types/ManagedAddresses'; + +export async function GET(request: NextRequest) { + const address = request.nextUrl.searchParams.get('address'); + if (!address) { + return NextResponse.json({ error: 'No address provided' }, { status: 400 }); + } + + const network = request.nextUrl.searchParams.get('network') ?? 'base-mainnet'; + if (network !== 'base-mainnet' && network !== 'base-sepolia') { + return NextResponse.json({ error: 'Invalid network provided' }, { status: 400 }); + } + + const response = await fetch( + `https://api.cdp.coinbase.com/platform/v1/networks/${network}/addresses/${address}/identity?limit=50`, + { + headers: { + Authorization: `Bearer ${process.env.CDP_BEARER_TOKEN}`, + 'Content-Type': 'application/json', + }, + }, + ); + + const data = (await response.json()) as ManagedAddressesResponse; + + return NextResponse.json(data, { status: 200 }); +} diff --git a/apps/web/app/(basenames)/manage-names/page.tsx b/apps/web/app/(basenames)/manage-names/page.tsx new file mode 100644 index 0000000000..1aec6958aa --- /dev/null +++ b/apps/web/app/(basenames)/manage-names/page.tsx @@ -0,0 +1,32 @@ +import ErrorsProvider from 'apps/web/contexts/Errors'; +import type { Metadata } from 'next'; +import { initialFrame } from 'apps/web/pages/api/basenames/frame/frameResponses'; +import NamesList from 'apps/web/src/components/Basenames/ManageNames/NamesList'; + +export const metadata: Metadata = { + metadataBase: new URL('https://base.org'), + title: `Basenames`, + description: + 'Basenames are a core onchain building block that enables anyone to establish their identity on Base by registering human-readable names for their address(es). They are a fully onchain solution which leverages ENS infrastructure deployed on Base.', + openGraph: { + title: `Basenames`, + url: `/manage-names`, + }, + twitter: { + site: '@base', + card: 'summary_large_image', + }, + other: { + ...(initialFrame as Record), + }, +}; + +export default async function Page() { + return ( + +
+ +
+
+ ); +} diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index fd36f9494e..725dd6f245 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -3,4 +3,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/web/package.json b/apps/web/package.json index 3c42a50734..718ac4131e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -45,6 +45,7 @@ "base-ui": "0.1.1", "classnames": "^2.5.1", "cloudinary": "^2.5.1", + "date-fns": "^4.1.0", "dd-trace": "^5.21.0", "ethers": "5.7.2", "framer-motion": "^11.9.0", diff --git a/apps/web/src/components/Basenames/ManageNames/NameDisplay.tsx b/apps/web/src/components/Basenames/ManageNames/NameDisplay.tsx new file mode 100644 index 0000000000..0a1b44fd63 --- /dev/null +++ b/apps/web/src/components/Basenames/ManageNames/NameDisplay.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import UsernameProfileProvider from 'apps/web/src/components/Basenames/UsernameProfileContext'; +import ProfileTransferOwnershipProvider from 'apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/context'; +import UsernameProfileTransferOwnershipModal from 'apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal'; +import BasenameAvatar from 'apps/web/src/components/Basenames/BasenameAvatar'; +import { Basename } from '@coinbase/onchainkit/identity'; +import { formatDistanceToNow, parseISO } from 'date-fns'; +import { Icon } from 'apps/web/src/components/Icon/Icon'; +import Dropdown from 'apps/web/src/components/Dropdown'; +import DropdownItem from 'apps/web/src/components/DropdownItem'; +import DropdownMenu from 'apps/web/src/components/DropdownMenu'; +import DropdownToggle from 'apps/web/src/components/DropdownToggle'; +import classNames from 'classnames'; +import { + useUpdatePrimaryName, + useRemoveNameFromUI, +} from 'apps/web/src/components/Basenames/ManageNames/hooks'; +import Link from 'apps/web/src/components/Link'; + +const transitionClasses = 'transition-all duration-700 ease-in-out'; + +const pillNameClasses = classNames( + 'bg-blue-500 mx-auto text-white relative leading-[2em] overflow-hidden text-ellipsis max-w-full', + 'shadow-[0px_8px_16px_0px_rgba(0,82,255,0.32),inset_0px_8px_16px_0px_rgba(255,255,255,0.25)]', + transitionClasses, + 'rounded-[2rem] py-6 px-6 w-full', +); + +const avatarClasses = classNames( + 'flex items-center justify-center overflow-hidden rounded-full', + transitionClasses, + 'h-[2.5rem] w-[2.5rem] md:h-[4rem] md:w-[4rem] top-3 md:top-4 left-4', +); + +type NameDisplayProps = { + domain: string; + isPrimary: boolean; + tokenId: string; + expiresAt: string; +}; + +export default function NameDisplay({ domain, isPrimary, tokenId, expiresAt }: NameDisplayProps) { + const expirationText = formatDistanceToNow(parseISO(expiresAt), { addSuffix: true }); + + const { setPrimaryUsername } = useUpdatePrimaryName(domain as Basename); + + const [isOpen, setIsOpen] = useState(false); + const openModal = useCallback(() => setIsOpen(true), []); + const closeModal = useCallback(() => setIsOpen(false), []); + + const { removeNameFromUI } = useRemoveNameFromUI(domain as Basename); + + return ( +
  • +
    + +
    + +
    +

    {domain}

    +

    Expires {expirationText}

    +
    +
    + +
    + {isPrimary && ( + Primary + )} + + + + + + + + Transfer + name + + + {!isPrimary ? ( + // eslint-disable-next-line @typescript-eslint/no-misused-promises + + + Set as + primary + + + ) : null} + + +
    +
    + + + + + +
  • + ); +} diff --git a/apps/web/src/components/Basenames/ManageNames/NamesList.tsx b/apps/web/src/components/Basenames/ManageNames/NamesList.tsx new file mode 100644 index 0000000000..c4838f1da2 --- /dev/null +++ b/apps/web/src/components/Basenames/ManageNames/NamesList.tsx @@ -0,0 +1,81 @@ +'use client'; + +import NameDisplay from './NameDisplay'; +import { useNameList } from 'apps/web/src/components/Basenames/ManageNames/hooks'; +import Link from 'apps/web/src/components/Link'; +import { Icon } from 'apps/web/src/components/Icon/Icon'; +import AnalyticsProvider from 'apps/web/contexts/Analytics'; + +const usernameManagementListAnalyticContext = 'username_management_list'; + +function NamesLayout({ children }: { children: React.ReactNode }) { + return ( + +
    +
    +

    My Basenames

    + + + +
    + {children} +
    +
    + ); +} + +export default function NamesList() { + const { namesData, isLoading, error } = useNameList(); + + if (error) { + return ( + +
    + Failed to load names. Please try again later. +
    +
    + ); + } + + if (isLoading) { + return ( + +
    Loading names...
    +
    + ); + } + + if (!namesData?.data?.length) { + return ( + +
    + No names found. +
    +
    + + Get a Basename! + +
    +
    + ); + } + + return ( + +
      + {namesData.data.map((name) => ( + + ))} +
    +
    + ); +} diff --git a/apps/web/src/components/Basenames/ManageNames/hooks.tsx b/apps/web/src/components/Basenames/ManageNames/hooks.tsx new file mode 100644 index 0000000000..63d1a1b355 --- /dev/null +++ b/apps/web/src/components/Basenames/ManageNames/hooks.tsx @@ -0,0 +1,105 @@ +import { useCallback, useEffect } from 'react'; +import { useErrors } from 'apps/web/contexts/Errors'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useAccount, useChainId } from 'wagmi'; +import { ManagedAddressesResponse } from 'apps/web/src/types/ManagedAddresses'; +import useSetPrimaryBasename from 'apps/web/src/hooks/useSetPrimaryBasename'; +import { Basename } from '@coinbase/onchainkit/identity'; + +export function useNameList() { + const { address } = useAccount(); + const chainId = useChainId(); + const { logError } = useErrors(); + + const network = chainId === 8453 ? 'base-mainnet' : 'base-sepolia'; + + const { + data: namesData, + isLoading, + error, + } = useQuery({ + queryKey: ['usernames', address, network], + queryFn: async (): Promise => { + try { + const response = await fetch( + `/api/basenames/getUsernames?address=${address}&network=${network}`, + ); + if (!response.ok) { + throw new Error(`Failed to fetch usernames: ${response.statusText}`); + } + return (await response.json()) as ManagedAddressesResponse; + } catch (err) { + logError(err, 'Failed to fetch usernames'); + throw err; + } + }, + enabled: !!address, + }); + + return { namesData, isLoading, error }; +} + +export function useRemoveNameFromUI(domain: Basename) { + const { address } = useAccount(); + const chainId = useChainId(); + + const network = chainId === 8453 ? 'base-mainnet' : 'base-sepolia'; + const queryClient = useQueryClient(); + + const removeNameFromUI = useCallback(() => { + queryClient.setQueryData( + ['usernames', address, network], + (prevData: ManagedAddressesResponse) => { + return { ...prevData, data: prevData.data.filter((name) => name.domain !== domain) }; + }, + ); + }, [address, domain, network, queryClient]); + + return { removeNameFromUI }; +} + +export function useUpdatePrimaryName(domain: Basename) { + const { address } = useAccount(); + const chainId = useChainId(); + const { logError } = useErrors(); + + const queryClient = useQueryClient(); + + const network = chainId === 8453 ? 'base-mainnet' : 'base-sepolia'; + + // Hook to update primary name + const { setPrimaryName, transactionIsSuccess } = useSetPrimaryBasename({ + secondaryUsername: domain, + }); + + const setPrimaryUsername = useCallback(async () => { + try { + await setPrimaryName(); + } catch (error) { + logError(error, 'Failed to update primary name'); + throw error; + } + }, [logError, setPrimaryName]); + + useEffect(() => { + if (transactionIsSuccess) { + queryClient.setQueryData( + ['usernames', address, network], + (prevData: ManagedAddressesResponse) => { + return { + ...prevData, + data: prevData.data.map((name) => + name.domain === domain + ? { ...name, is_primary: true } + : name.is_primary + ? { ...name, is_primary: false } + : name, + ), + }; + }, + ); + } + }, [transactionIsSuccess, address, domain, network, queryClient]); + + return { setPrimaryUsername }; +} diff --git a/apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/context.tsx b/apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/context.tsx index 8a5dd1254d..04b785421a 100644 --- a/apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/context.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/context.tsx @@ -337,9 +337,11 @@ export default function ProfileTransferOwnershipProvider({ // Smart wallet: One transaction batchCallsStatus === BatchCallsStatus.Success || // Other wallet: 4 Transactions are successfull - ownershipSettings.every( - (ownershipSetting) => ownershipSetting.status === WriteTransactionWithReceiptStatus.Success, - ), + (ownershipSettings.length > 0 && + ownershipSettings.every( + (ownershipSetting) => + ownershipSetting.status === WriteTransactionWithReceiptStatus.Success, + )), [batchCallsStatus, ownershipSettings], ); diff --git a/apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/index.tsx b/apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/index.tsx index 0a6c90fc42..d03eb2b539 100644 --- a/apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileTransferOwnershipModal/index.tsx @@ -28,11 +28,13 @@ const ownershipStepsTitleForDisplay = { type UsernameProfileTransferOwnershipModalProps = { isOpen: boolean; onClose: () => void; + onSuccess?: () => void; }; export default function UsernameProfileTransferOwnershipModal({ isOpen, onClose, + onSuccess, }: UsernameProfileTransferOwnershipModalProps) { // Hooks const { address } = useAccount(); @@ -103,8 +105,9 @@ export default function UsernameProfileTransferOwnershipModal({ useEffect(() => { if (isSuccess) { setCurrentOwnershipStep(OwnershipSteps.Success); + onSuccess?.(); } - }, [isSuccess, setCurrentOwnershipStep]); + }, [isSuccess, setCurrentOwnershipStep, onSuccess]); return ( { - setOpen(false); + const timeoutId = setTimeout(() => { + setOpen(false); + }, 300); + return () => clearTimeout(timeoutId); }, []); const openDropdown = useCallback(() => { @@ -56,7 +59,15 @@ export default function Dropdown({ children }: DropdownProps) { return ( -
    +
    {children}
    diff --git a/apps/web/src/components/DropdownMenu/index.tsx b/apps/web/src/components/DropdownMenu/index.tsx index a2bdadcae3..0d17afd834 100644 --- a/apps/web/src/components/DropdownMenu/index.tsx +++ b/apps/web/src/components/DropdownMenu/index.tsx @@ -44,8 +44,8 @@ export default function DropdownMenu({ let dropdownStyle: CSSProperties = {}; if (dropdownToggleRef?.current) { const { top, height, right } = dropdownToggleRef.current.getBoundingClientRect(); - dropdownStyle.top = top + height + 'px'; - dropdownStyle.left = `${right}px`; + dropdownStyle.top = top + height + window.scrollY + 'px'; + dropdownStyle.left = `${right + window.scrollX}px`; dropdownStyle.transform = `translateX(-100%)`; } @@ -61,8 +61,8 @@ export default function DropdownMenu({ let arrowStyle: CSSProperties = {}; if (dropdownToggleRef?.current) { const { top, height, left, width } = dropdownToggleRef.current.getBoundingClientRect(); - arrowStyle.top = top + height + 'px'; - arrowStyle.left = `${left + width / 2}px`; + arrowStyle.top = top + height + window.scrollY + 'px'; + arrowStyle.left = `${left + width / 2 + window.scrollX}px`; } return ( diff --git a/apps/web/src/components/Icon/Icon.tsx b/apps/web/src/components/Icon/Icon.tsx index f68b34edfb..49e0a5fc45 100644 --- a/apps/web/src/components/Icon/Icon.tsx +++ b/apps/web/src/components/Icon/Icon.tsx @@ -538,6 +538,59 @@ const ICONS: Record JSX.Element> = { /> ), + list: ({ color, width, height }: SvgProps) => ( + + + + + + + + + ), + verticalDots: ({ color, width, height }: SvgProps) => ( + + + + ), + transfer: ({ color, width, height }: SvgProps) => ( + + + + ), }; export function Icon({ name, color = 'white', width = '24', height = '24' }: IconProps) { diff --git a/apps/web/src/components/Layout/UsernameNav/index.tsx b/apps/web/src/components/Layout/UsernameNav/index.tsx index 73d5e1a9bc..e042d9db71 100644 --- a/apps/web/src/components/Layout/UsernameNav/index.tsx +++ b/apps/web/src/components/Layout/UsernameNav/index.tsx @@ -1,6 +1,6 @@ 'use client'; -import Link from 'next/link'; import usernameBaseLogo from './usernameBaseLogo.svg'; +import Link from 'apps/web/src/components/Link'; import { ConnectWalletButton, @@ -42,7 +42,7 @@ export default function UsernameNav() { [switchChain], ); - const walletStateClasses = classNames('p2 rounded', { + const walletStateClasses = classNames('p2 rounded flex items-center gap-6', { 'bg-white': isConnected, }); @@ -111,6 +111,14 @@ export default function UsernameNav() { + {isConnected && ( + + + + My Basenames + + + )} { + const setPrimaryName = useCallback(async (): Promise => { // Already primary - if (secondaryUsername === primaryUsername) return; + if (secondaryUsername === primaryUsername) return undefined; // No user is connected - if (!address) return; + if (!address) return undefined; try { await initiateTransaction({ @@ -81,6 +81,7 @@ export default function useSetPrimaryBasename({ secondaryUsername }: UseSetPrima }); } catch (error) { logError(error, 'Set primary name transaction canceled'); + return undefined; } return true; @@ -95,5 +96,5 @@ export default function useSetPrimaryBasename({ secondaryUsername }: UseSetPrima const isLoading = transactionIsLoading || primaryUsernameIsLoading || primaryUsernameIsFetching; - return { setPrimaryName, canSetUsernameAsPrimary, isLoading }; + return { setPrimaryName, canSetUsernameAsPrimary, isLoading, transactionIsSuccess }; } diff --git a/apps/web/src/types/ManagedAddresses.ts b/apps/web/src/types/ManagedAddresses.ts new file mode 100644 index 0000000000..5ffc4aa53a --- /dev/null +++ b/apps/web/src/types/ManagedAddresses.ts @@ -0,0 +1,17 @@ +export type ManagedAddressesData = { + domain: string; + expires_at: string; + is_primary: boolean; + manager_address: string; + network_id: string; + owner_address: string; + primary_address: string; + token_id: string; +}; + +export type ManagedAddressesResponse = { + data: ManagedAddressesData[]; + has_more: boolean; + next_page: string; + total_count: number; +}; diff --git a/yarn.lock b/yarn.lock index aee8802291..5cd976b724 100644 --- a/yarn.lock +++ b/yarn.lock @@ -401,6 +401,7 @@ __metadata: classnames: ^2.5.1 cloudinary: ^2.5.1 csv-parser: ^3.0.0 + date-fns: ^4.1.0 dd-trace: ^5.21.0 dotenv: ^16.0.3 eslint-config-next: ^13.1.6 @@ -13298,6 +13299,13 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^4.1.0": + version: 4.1.0 + resolution: "date-fns@npm:4.1.0" + checksum: fb681b242cccabed45494468f64282a7d375ea970e0adbcc5dcc92dcb7aba49b2081c2c9739d41bf71ce89ed68dd73bebfe06ca35129490704775d091895710b + languageName: node + linkType: hard + "dc-polyfill@npm:^0.1.4": version: 0.1.6 resolution: "dc-polyfill@npm:0.1.6" From 24dbe96e0d38e243bd014cd41dfbeaa30d1b8a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Galley?= Date: Thu, 7 Nov 2024 15:40:09 -0500 Subject: [PATCH 029/171] fix script (#1223) --- .github/workflows/file-size-checker.yml | 37 +++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/workflows/file-size-checker.yml b/.github/workflows/file-size-checker.yml index 18fbc30fc9..a8c438eb64 100644 --- a/.github/workflows/file-size-checker.yml +++ b/.github/workflows/file-size-checker.yml @@ -85,22 +85,20 @@ jobs: const largeFiles = `${{ steps.check-sizes.outputs.large_files }}`; try { - console.log('Repository:', context.payload.repository.name); - console.log('Owner:', context.payload.repository.owner.login); - console.log('SHA:', context.payload.pull_request.head.sha); - - // Set status check that will be used by branch protection - await github.rest.repos.createCommitStatus({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - sha: context.payload.pull_request.head.sha, - state: hugeFiles ? 'failure' : 'success', - context: 'File Size Check', - description: hugeFiles ? 'Files over 40MB found' : 'All files within size limits', - target_url: `https://github.com/${context.payload.repository.owner.login}/${context.payload.repository.name}/actions/runs/${context.runId}` - }); - - // Only comment if issues were found + // Only create status check if we have permission (not a fork PR) + if (context.payload.pull_request.head.repo.full_name === context.payload.repository.full_name) { + await github.rest.repos.createCommitStatus({ + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + sha: context.payload.pull_request.head.sha, + state: hugeFiles ? 'failure' : 'success', + context: 'File Size Check', + description: hugeFiles ? 'Files over 40MB found' : 'All files within size limits', + target_url: `https://github.com/${context.payload.repository.owner.login}/${context.payload.repository.name}/actions/runs/${context.runId}` + }); + } + + // Comments should work for both fork and non-fork PRs if (hugeFiles || largeFiles) { let comment = '## ⚠️ File Size Check Results\n\n'; @@ -124,5 +122,10 @@ jobs: } catch (error) { console.error('Error:', error); console.error('Context:', JSON.stringify(context.payload, null, 2)); - core.setFailed(error.message); + // Only ignore status check permission errors for fork PRs + if (error.status === 403 && context.payload.pull_request.head.repo.full_name !== context.payload.repository.full_name) { + console.log('Ignoring status check permission error for fork PR'); + } else { + core.setFailed(error.message); + } } From 2ee1723ba2fe2be17395ccc072f39aca10350c3b Mon Sep 17 00:00:00 2001 From: NikolaiKryshnev <63440682+NikolaiKryshnev@users.noreply.github.com> Date: Thu, 7 Nov 2024 23:50:38 +0300 Subject: [PATCH 030/171] Enhancements: Add comments in tos.ts and switch Twitter icon to X (#1140) * Update tos.ts: Add comments Included comments explaining the purpose of the array and the meaning of each code. * Update tos.ts Removed unnecessary parenthetical comment * Update index.tsx: switch Twitter icon to X switch Twitter icon to X in SVG assets * Update socialPlatforms.ts: rename Twitter to X - Renaming [SocialPlatform.Twitter]: 'twitter' to [SocialPlatform.Twitter]: 'x' - Modifying share link URL to `https://x.com/intent/tweet` --- apps/bridge/pages/api/tos.ts | 58 ++++++++++++++------------- apps/web/src/utils/socialPlatforms.ts | 4 +- libs/base-ui/Icon/index.tsx | 16 ++++---- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/apps/bridge/pages/api/tos.ts b/apps/bridge/pages/api/tos.ts index be54946c8d..2d7316c983 100644 --- a/apps/bridge/pages/api/tos.ts +++ b/apps/bridge/pages/api/tos.ts @@ -1,36 +1,38 @@ import type { NextApiRequest, NextApiResponse } from 'next'; +// Array of two-letter country codes of European Union members (according to ISO 3166-1 alpha-2) const EU_COUNTRIES = [ - 'AT', - 'BE', - 'BG', - 'CY', - 'CZ', - 'DE', - 'DK', - 'EE', - 'ES', - 'FI', - 'FR', - 'GB', - 'GR', - 'HU', - 'HR', - 'IE', - 'IT', - 'LT', - 'LU', - 'LV', - 'MT', - 'NL', - 'PL', - 'PT', - 'RO', - 'SE', - 'SI', - 'SK', + 'AT', // Austria + 'BE', // Belgium + 'BG', // Bulgaria + 'CY', // Cyprus + 'CZ', // Czech Republic + 'DE', // Germany + 'DK', // Denmark + 'EE', // Estonia + 'ES', // Spain + 'FI', // Finland + 'FR', // France + 'GB', // United Kingdom + 'GR', // Greece + 'HU', // Hungary + 'HR', // Croatia + 'IE', // Ireland + 'IT', // Italy + 'LT', // Lithuania + 'LU', // Luxembourg + 'LV', // Latvia + 'MT', // Malta + 'NL', // Netherlands + 'PL', // Poland + 'PT', // Portugal + 'RO', // Romania + 'SE', // Sweden + 'SI', // Slovenia + 'SK', // Slovakia ]; + export default function handler(req: NextApiRequest, res: NextApiResponse) { const country = res.getHeader('x-cf-country') as string; const tosRegion = EU_COUNTRIES.includes(country) ? 'EU' : 'US'; diff --git a/apps/web/src/utils/socialPlatforms.ts b/apps/web/src/utils/socialPlatforms.ts index 0604bcd40f..c5795e6e11 100644 --- a/apps/web/src/utils/socialPlatforms.ts +++ b/apps/web/src/utils/socialPlatforms.ts @@ -25,7 +25,7 @@ export const socialPlatformCtaForDisplay = { }; export const socialPlatformIconName: Record = { - [SocialPlatform.Twitter]: 'twitter', + [SocialPlatform.Twitter]: 'x', [SocialPlatform.Farcaster]: 'farcaster', }; @@ -44,7 +44,7 @@ export const socialPlatformShareLinkFunction: SocialPlatformShareLinkFunction = url: url, }; - return urlWithQueryParams('https://twitter.com/intent/tweet', shareParams); + return urlWithQueryParams('https://x.com/intent/tweet', shareParams); }, [SocialPlatform.Farcaster]: ({ text, url }: SocialMediaShareParams) => { const shareParams: QueryParams = { diff --git a/libs/base-ui/Icon/index.tsx b/libs/base-ui/Icon/index.tsx index d080bc2ec6..4bd7770b21 100644 --- a/libs/base-ui/Icon/index.tsx +++ b/libs/base-ui/Icon/index.tsx @@ -29,18 +29,16 @@ const ICONS: Record JSX.Element> = { ), twitter: ({ color, width, height }: SvgProps) => ( - - - + + ), github: ({ color, width, height }: SvgProps) => ( Date: Fri, 8 Nov 2024 09:54:49 -0500 Subject: [PATCH 031/171] remove api call in in favor of exit 0 (#1227) --- .github/workflows/file-size-checker.yml | 28 ++----------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/.github/workflows/file-size-checker.yml b/.github/workflows/file-size-checker.yml index a8c438eb64..71a0968c1c 100644 --- a/.github/workflows/file-size-checker.yml +++ b/.github/workflows/file-size-checker.yml @@ -16,11 +16,6 @@ jobs: runs-on: ubuntu-latest steps: - # - name: Setup environment - # run: | - # apt-get update - # apt-get install -y git bc - - name: Checkout code uses: actions/checkout@v4 with: @@ -85,20 +80,7 @@ jobs: const largeFiles = `${{ steps.check-sizes.outputs.large_files }}`; try { - // Only create status check if we have permission (not a fork PR) - if (context.payload.pull_request.head.repo.full_name === context.payload.repository.full_name) { - await github.rest.repos.createCommitStatus({ - owner: context.payload.repository.owner.login, - repo: context.payload.repository.name, - sha: context.payload.pull_request.head.sha, - state: hugeFiles ? 'failure' : 'success', - context: 'File Size Check', - description: hugeFiles ? 'Files over 40MB found' : 'All files within size limits', - target_url: `https://github.com/${context.payload.repository.owner.login}/${context.payload.repository.name}/actions/runs/${context.runId}` - }); - } - - // Comments should work for both fork and non-fork PRs + // Only comment if issues were found if (hugeFiles || largeFiles) { let comment = '## ⚠️ File Size Check Results\n\n'; @@ -121,11 +103,5 @@ jobs: } } catch (error) { console.error('Error:', error); - console.error('Context:', JSON.stringify(context.payload, null, 2)); - // Only ignore status check permission errors for fork PRs - if (error.status === 403 && context.payload.pull_request.head.repo.full_name !== context.payload.repository.full_name) { - console.log('Ignoring status check permission error for fork PR'); - } else { - core.setFailed(error.message); - } + core.setFailed(error.message); } From 6bb9f92f792afefe72c2e3a42086781c2c23a679 Mon Sep 17 00:00:00 2001 From: Matthew Bunday Date: Fri, 8 Nov 2024 13:53:54 -0500 Subject: [PATCH 032/171] Fix nav padding (#1230) --- apps/web/src/components/Layout/UsernameNav/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/Layout/UsernameNav/index.tsx b/apps/web/src/components/Layout/UsernameNav/index.tsx index e042d9db71..22cca65afe 100644 --- a/apps/web/src/components/Layout/UsernameNav/index.tsx +++ b/apps/web/src/components/Layout/UsernameNav/index.tsx @@ -42,7 +42,7 @@ export default function UsernameNav() { [switchChain], ); - const walletStateClasses = classNames('p2 rounded flex items-center gap-6', { + const walletStateClasses = classNames('p-2 rounded flex items-center gap-6', { 'bg-white': isConnected, }); From 819ac6dd5698f753ad2c4ea6d2dc0a5fe7590900 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Fri, 8 Nov 2024 12:54:33 -0600 Subject: [PATCH 033/171] chore: add stats page w/ datadog iframe (#1224) * chore: add stats page w/ datadog iframe * fix linter --- apps/web/app/(stats)/layout.tsx | 32 +++++++++++++++++++++++++++++ apps/web/app/(stats)/stats/page.tsx | 18 ++++++++++++++++ apps/web/next.config.js | 1 + 3 files changed, 51 insertions(+) create mode 100644 apps/web/app/(stats)/layout.tsx create mode 100644 apps/web/app/(stats)/stats/page.tsx diff --git a/apps/web/app/(stats)/layout.tsx b/apps/web/app/(stats)/layout.tsx new file mode 100644 index 0000000000..7baa778472 --- /dev/null +++ b/apps/web/app/(stats)/layout.tsx @@ -0,0 +1,32 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + metadataBase: new URL('https://base.org'), + title: `Base`, + description: + 'Base is a secure, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain.', + openGraph: { + type: 'website', + title: `Base`, + description: + 'Base is a secure, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain.', + url: `/`, + images: ['https://base.org/images/base-open-graph.png'], + }, + twitter: { + site: '@base', + card: 'summary_large_image', + }, +}; + +export default async function StatsLayout({ + children, // will be a page or nested layout +}: { + children: React.ReactNode; +}) { + return ( +
    + {children} +
    + ); +} diff --git a/apps/web/app/(stats)/stats/page.tsx b/apps/web/app/(stats)/stats/page.tsx new file mode 100644 index 0000000000..bfa6a24a23 --- /dev/null +++ b/apps/web/app/(stats)/stats/page.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + metadataBase: new URL('https://base.org'), + title: `Base | Stats`, + description: 'Live stats for the Base network', +}; + +export default async function Page() { + return ( +