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