diff --git a/bun.lockb b/bun.lockb index 0cd88f1..ed31571 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/public/images/full-width-text.svg b/public/images/full-width-text.svg deleted file mode 100644 index 1efeb75..0000000 --- a/public/images/full-width-text.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/public/images/github.png b/public/images/github.png deleted file mode 100644 index d6ca946..0000000 Binary files a/public/images/github.png and /dev/null differ diff --git a/public/images/keren.webp b/public/images/keren.webp deleted file mode 100644 index 08405e6..0000000 Binary files a/public/images/keren.webp and /dev/null differ diff --git a/public/images/onchainsummer.png b/public/images/onchainsummer.png deleted file mode 100644 index b280bce..0000000 Binary files a/public/images/onchainsummer.png and /dev/null differ diff --git a/src/components/Cart.tsx b/src/components/Cart.tsx index 3a392c8..999170a 100644 --- a/src/components/Cart.tsx +++ b/src/components/Cart.tsx @@ -8,10 +8,10 @@ import { useCartContext } from '~/contexts/Cart'; import { api } from '~/utils/api'; import { useSendCalls, useShowCallsStatus } from 'wagmi/experimental' import { client } from '~/providers/Thirdweb'; -import { useActiveAccount, useActiveWallet } from 'thirdweb/react'; +import { useActiveAccount, useActiveWallet, useWalletBalance } from 'thirdweb/react'; import { DEFAULT_CHAIN } from '~/constants/chain'; import { Connect } from '~/components/Connect'; -import { parseAbiItem, encodeFunctionData } from "viem"; +import { parseAbiItem, encodeFunctionData, isAddressEqual } from "viem"; import { flattenObject } from '~/helpers/flattenObject'; import Donation from '~/components/Donation'; import { REFERRAL_CODE_NFT } from '~/constants/addresses'; @@ -20,6 +20,9 @@ import ReferralChip from '~/components/Referral/ReferralChip'; import posthog from "posthog-js"; import { env } from '~/env'; import { config } from '~/providers/Wagmi'; +import { GAS_FREE_TOKEN } from '~/constants/addresses'; + +const GAS_FREE_TOKEN_BALANCE_THRESHOLD = 1_000_000n; // 1M gas free tokens const Cart: FC = () => { const { showCallsStatus } = useShowCallsStatus({ config }); @@ -27,6 +30,15 @@ const Cart: FC = () => { const { cart, referralCode, updateItem, deleteItem } = useCartContext(); const wallet = useActiveWallet(); const account = useActiveAccount(); + const { data: gasFreeTokenBalance } = useWalletBalance({ + address: account?.address, + tokenAddress: GAS_FREE_TOKEN, + chain: DEFAULT_CHAIN, + client, + }); + const meetsGasFreeTokenBalanceThreshold = useMemo(() => { + return gasFreeTokenBalance && gasFreeTokenBalance.value >= GAS_FREE_TOKEN_BALANCE_THRESHOLD; + }, [gasFreeTokenBalance]); const { data: etherPrice } = api.dex.getEtherPrice.useQuery({ chainId: base.id, }); @@ -136,9 +148,14 @@ const Cart: FC = () => { auxiliaryFunds: { supported: true }, - paymasterService: { - url: env.NEXT_PUBLIC_PAYMASTER_URL, - } + ...(cart.some(item => + isAddressEqual(item.address, GAS_FREE_TOKEN) && + item.usdAmountDesired >= 10 + ) || meetsGasFreeTokenBalanceThreshold ? { + paymasterService: { + url: env.NEXT_PUBLIC_PAYMASTER_URL, + } + } : {}) } }, { onSuccess(tx) { diff --git a/src/components/Donation.tsx b/src/components/Donation.tsx index 89caa85..11564c4 100644 --- a/src/components/Donation.tsx +++ b/src/components/Donation.tsx @@ -20,7 +20,7 @@ export const Donation: FC = () => { const [offset, setOffset] = useState(0); const CAUSES_PER_PAGE = 5; const { data: causeData, isLoading: causesIsLoading } = api.endaoment.search.useQuery({ - searchTerm: debounceQuery ?? 'paws', + searchTerm: debounceQuery ?? 'cats', claimedStatus: 'claimed', count: CAUSES_PER_PAGE, offset: offset, @@ -67,19 +67,36 @@ export const Donation: FC = () => { }); }; + const CauseImage = ({ cause }: { cause: EndaomentOrg }) => { + const [error, setError] = useState(false); + if (error) { + return ( +
+ ); + } + return ( + {cause.name} setError(true)} + /> + ); + }; + const Cause: FC<{ cause: EndaomentOrg }> = ({ cause }) => { const [isExpanded, setIsExpanded] = useState(false); return (
- {cause.name} +
+
+ +
+
setIsExpanded(!isExpanded)} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index dedc006..f4c78d6 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -12,14 +12,11 @@ export const Footer: FC = () => { label: "About Smart Wallet", href: "https://www.smartwallet.dev?utm_source=basetokenstore", }, - { - label: "Buy an Ad", - href: "/advertisement/create", - }, - { - label: "Onchain Summer", - href: "https://www.base.org/onchainsummer?utm_source=basetokenstore", - }, + // UNCOMMENT THIS TO SHOW THE AD LINk + // { + // label: "Buy an Ad", + // href: "/advertisement/create", + // }, { label: "Build on Base", href: "https://www.base.org?utm_source=basetokenstore", @@ -66,27 +63,9 @@ export const Footer: FC = () => { ))}
- - Onchain Summer - - - Github -
- Open source license - Clone on GitHub -
- - - Keren: Report an issue -
- Report an issue - by $keren -
-
-
) }; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 54d9a84..f2f761d 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -30,7 +30,6 @@ export const Layout: FC = ({ children }) => {
-
{todaysAdIsNotCensored && (
diff --git a/src/components/Nft/ListingsGrid.tsx b/src/components/Nft/ListingsGrid.tsx index ef2d241..21b7b5d 100644 --- a/src/components/Nft/ListingsGrid.tsx +++ b/src/components/Nft/ListingsGrid.tsx @@ -34,11 +34,14 @@ export const ListingsGrid: FC = ({ collectionSlug }) => { t.order_hash === listing.order_hash )) ); + console.log({uniqueListings: JSON.stringify(uniqueListings)}) return uniqueListings; }); } }, [listingsData]); + console.log({listings, listingsData}) + return ( <>
diff --git a/src/components/Token/Grid.tsx b/src/components/Token/Grid.tsx index f9f46cb..96bae1c 100644 --- a/src/components/Token/Grid.tsx +++ b/src/components/Token/Grid.tsx @@ -23,6 +23,14 @@ export const TokenGrid: FC = ({ category, query, address }) => { refetchOnWindowFocus: false, }); + const { data: pinnedToken } = api.coingecko.getTokenCardDataById.useQuery({ + id: 'mochi-thecatcoin', + }, { + enabled: !!query, + refetchOnMount: false, + refetchOnWindowFocus: false, + }); + const { data: searchedTokens, isLoading: searchIsLoading } = api.coingecko.searchTokens.useQuery({ query: query ?? '', }, { @@ -82,6 +90,14 @@ export const TokenGrid: FC = ({ category, query, address }) => { return { ...token, address: baseAddress }; }).filter((token) => token.address); + if (pinnedToken) { + const pinnedTokenAddress = tokenAddresses?.find((t) => t.id === pinnedToken.id)?.platforms?.base; + scopedTokens?.unshift({ + ...pinnedToken, + address: pinnedTokenAddress, + }); + } + // if there is a search, filter the tokens if (query) { const filteredTokens = !scopedTokens ? [] : scopedTokens.filter((token) => @@ -112,14 +128,14 @@ export const TokenGrid: FC = ({ category, query, address }) => { // return paginated tokens return scopedTokens?.slice(indexOfFirstToken, indexOfLastToken); - }, [address, indexOfFirstToken, indexOfLastToken, query, searchedTokenAddresses, searchedTokens, tokenAddresses, tokens, tokensOwnedByAddress]); + }, [tokens, pinnedToken, query, address, indexOfFirstToken, indexOfLastToken, tokenAddresses, searchedTokens, searchedTokenAddresses, tokensOwnedByAddress]); const { data: fallbackToken, isLoading: fallbackTokenIsLoading } = api.coingecko.getTokenCardDataById.useQuery({ - id: 'pawthereum-2', + id: 'mochi-thecatcoin', }, { refetchOnMount: false, refetchOnWindowFocus: false, diff --git a/src/constants/addresses.ts b/src/constants/addresses.ts index 262039c..a0c8049 100644 --- a/src/constants/addresses.ts +++ b/src/constants/addresses.ts @@ -1,3 +1,4 @@ export const REFERRAL_CODE_NFT = "0xDb827803A886e88e66Ce98704d72AFC515ef018f"; export const REFERRAL_CODE_MINTER = "0xA473533c54D105C6334fE06c8624f7dfbb09ba25"; -export const BANNER_ADVERTISEMENT = '0x4047f984f20f174919bffbf0c5f347270d13a112'; \ No newline at end of file +export const BANNER_ADVERTISEMENT = '0x4047f984f20f174919bffbf0c5f347270d13a112'; +export const GAS_FREE_TOKEN = '0xf6e932ca12afa26665dc4dde7e27be02a7c02e50'; \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 4f287a4..f5a7db5 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,7 +3,7 @@ import Head from "next/head"; import { type FC, useState } from "react"; import Logo from "~/components/Logo"; import NftCollectionsGrid from "~/components/Nft/CollectionsGrid"; -import RefferedBanner from "~/components/Referral/ReferredBanner"; +// import RefferedBanner from "~/components/Referral/ReferredBanner"; import TokenGrid from "~/components/Token/Grid"; import useDebounce from "~/hooks/useDebounce"; @@ -69,7 +69,9 @@ const HomePage: FC = ({ referralNft, category: defaultCateg ))}
- + {/* UNCOMMENT THIS TO SHOW THE REFERRAL BANNER + + */}
diff --git a/src/server/api/routers/opensea.ts b/src/server/api/routers/opensea.ts index 77071ee..aec476d 100644 --- a/src/server/api/routers/opensea.ts +++ b/src/server/api/routers/opensea.ts @@ -69,33 +69,68 @@ export const openSeaRouter = createTRPCRouter({ cursor: z.string().optional(), })) .query(async ({ input }) => { - const { collection, limit = 100, cursor } = input; - const url = new URL(`https://api.opensea.io/api/v2/listings/collection/${collection}/best`); - // url params for limit and next - url.searchParams.append('limit', limit.toString()); - if (cursor) { - url.searchParams.append('next', cursor); + const { collection, limit = 100, cursor: initialCursor } = input; + + // Helper function to fetch and process a page of listings + async function fetchListingsPage(cursor?: string): Promise<{ + listings: OpenSeaListingResponse['listings']; + next?: string; + }> { + const url = new URL(`https://api.opensea.io/api/v2/listings/collection/${collection}/best`); + url.searchParams.append('limit', limit.toString()); + if (cursor) { + url.searchParams.append('next', cursor); + } + const response = await fetch(url.toString(), { + method: 'GET', + headers: { + 'accept': 'application/json', + 'x-api-key': env.OPENSEA_API_KEY, + }, + }); + return response.json() as Promise; } - const response = await fetch(url.toString(), { - method: 'GET', - headers: { - 'accept': 'application/json', - 'x-api-key': env.OPENSEA_API_KEY, - }, - }); - const data = await response.json() as OpenSeaListingResponse; - return { - ...data, - listings: data.listings.filter(listing => - // only return basic listings that can be bought right away - listing.type === 'basic' && - // only return listings that are selling one item at a time - listing.protocol_data.parameters.offer.length === 1 && - // only return listings that are charging in eth - listing.protocol_data.parameters.consideration.every( - consideration => consideration.itemType === 0 + + // Map to store the cheapest listing for each token + const cheapestByToken = new Map(); + let nextCursor = initialCursor; + + // Keep fetching until we have enough listings or no more pages + while (cheapestByToken.size < limit) { + const page = await fetchListingsPage(nextCursor); + + // Filter and process listings + page.listings + .filter(listing => + // only return basic listings that can be bought right away + listing.type === 'basic' && + // only return listings that are selling one item at a time + listing.protocol_data.parameters.offer.length === 1 && + // only return listings that are charging in eth + listing.protocol_data.parameters.consideration.every( + consideration => consideration.itemType === 0 + ) ) - ), + .forEach(listing => { + const identifierOrCriteria = listing.protocol_data.parameters.offer[0]?.identifierOrCriteria; + if (!identifierOrCriteria) return; + + const currentPrice = BigInt(listing.price.current.value); + const existingListing = cheapestByToken.get(identifierOrCriteria); + + if (!existingListing || currentPrice < BigInt(existingListing.price.current.value)) { + cheapestByToken.set(identifierOrCriteria, listing); + } + }); + + // Break if no more pages + if (!page.next) break; + nextCursor = page.next; + } + + return { + listings: Array.from(cheapestByToken.values()).slice(0, limit), + next: nextCursor, }; }), getPurchaseEncodedData: publicProcedure