diff --git a/.env.example b/.env.example index bbe81e3f..4a06f640 100644 --- a/.env.example +++ b/.env.example @@ -17,7 +17,7 @@ NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID= # NFT -NFTSCAN_API_KEY= +SIMPLEHASH_API_KEY= # OPENAI diff --git a/cspell.json b/cspell.json index e87de654..d38b5663 100644 --- a/cspell.json +++ b/cspell.json @@ -37,7 +37,7 @@ "MINTABLE", "MORALIS", "nextjs", - "nftscan", + "simplehash", "nocheck", "nprogress", "onclickoutside", diff --git a/deploy/dev/secrets.yaml b/deploy/dev/secrets.yaml index a87b7e13..8101f9c4 100644 --- a/deploy/dev/secrets.yaml +++ b/deploy/dev/secrets.yaml @@ -3,7 +3,7 @@ apiVersion: v1 stringData: REDIS_URL: ${REDIS_URL_DEV} DATABASE_URL: ${PG_CONN_STRING_DEV} - NFTSCAN_API_KEY: ${NFTSCAN_API_KEY} + SIMPLEHASH_API_KEY: ${SIMPLEHASH_API_KEY} OPENAI_API_KEY: ${OPENAI_API_KEY} ANONYMOUS_ACCOUNT_PRIVATEKEY: ${ANONYMOUS_ACCOUNT_PRIVATEKEY} PORTFOLIO_GITHUB_TOKEN: ${PORTFOLIO_GITHUB_TOKEN} diff --git a/deploy/prod/secrets.yaml b/deploy/prod/secrets.yaml index 5011846e..62575c79 100644 --- a/deploy/prod/secrets.yaml +++ b/deploy/prod/secrets.yaml @@ -3,7 +3,7 @@ apiVersion: v1 stringData: REDIS_URL: ${REDIS_URL} DATABASE_URL: ${PG_CONN_STRING} - NFTSCAN_API_KEY: ${NFTSCAN_API_KEY} + SIMPLEHASH_API_KEY: ${SIMPLEHASH_API_KEY} OPENAI_API_KEY: ${OPENAI_API_KEY} ANONYMOUS_ACCOUNT_PRIVATEKEY: ${ANONYMOUS_ACCOUNT_PRIVATEKEY} kind: Secret diff --git a/next.config.js b/next.config.js index ec9ce117..bbb8b01b 100644 --- a/next.config.js +++ b/next.config.js @@ -61,7 +61,7 @@ module.exports = withBundleAnalyzer({ ENV_REDIS_URL: process.env.REDIS_URL, ENV_REDIS_EXPIRE: process.env.REDIS_EXPIRE, ENV_REDIS_REFRESH: process.env.REDIS_REFRESH, - ENV_NFTSCAN_API_KEY: process.env.NFTSCAN_API_KEY, + ENV_SIMPLEHASH_API_KEY: process.env.SIMPLEHASH_API_KEY, ENV_OPENAI_API_KEY: process.env.OPENAI_API_KEY, ENV_ANONYMOUS_ACCOUNT_PRIVATEKEY: process.env.ANONYMOUS_ACCOUNT_PRIVATEKEY, ENV_PORTFOLIO_GITHUB_TOKEN: process.env.PORTFOLIO_GITHUB_TOKEN, diff --git a/src/app/site/[site]/nft/page.tsx b/src/app/site/[site]/nft/page.tsx index b1179ec6..cb6433e7 100644 --- a/src/app/site/[site]/nft/page.tsx +++ b/src/app/site/[site]/nft/page.tsx @@ -1,10 +1,8 @@ import { Metadata } from "next" -import Script from "next/script" +import { Image } from "~/components/ui/Image" import { UniLink } from "~/components/ui/UniLink" -import { UniMedia } from "~/components/ui/UniMedia" import { getTranslation } from "~/lib/i18n" -import { toGateway } from "~/lib/ipfs-parser" import getQueryClient from "~/lib/query-client" import { fetchGetSite, getNFTs } from "~/queries/site.server" @@ -38,98 +36,50 @@ export default async function SiteNFTPage({ const site = await fetchGetSite(params.site, queryClient) const { t } = await getTranslation("common") - let nfts = (await getNFTs(site?.owner)) ?? [] - - const names = new Map() - nfts.forEach((chain: any) => { - if (!names.get(chain.chain)) { - names.set(chain.chain, true) - chain.assets = [] - chain.collection_assets.forEach((collection: any) => { - chain.assets = chain.assets.concat(collection.assets) - }) - chain.assets = chain.assets.filter((asset: any) => asset.content_uri) - } - }) - nfts = nfts - .filter((chain: any) => chain.assets?.length) - .sort((a: any, b: any) => b.assets.length - a.assets.length) - - const displayNames: Record = { - eth: "Ethereum", - bsc: "BNB Chain", - pls: "Polygon", - arbi: "Arbitrum One", - opti: "Optimism", - avax: "Avalanche", - cro: "Cronos", - platon: "PlatON", - glmr: "Moonbeam", - ftm: "Fantom", - gnosis: "Gnosis", + let nfts = (await getNFTs(site?.owner)) ?? { + nfts: [], + chains: [], } return ( <> -

NFT {t("Showcase")}

-
+
Supported chains:{" "} - {Object.values(displayNames).join(", ")} + {nfts.chains.join(", ")}
- {nfts.map((chain: any) => ( -
-
- - - {displayNames[chain.chain] || chain.chain} ( - {chain.assets.length}) - - -
- {chain.assets.map((nft: any) => ( - - -
- {nft.name} -
-
- ))} +
+ {nfts.nfts.map((nft) => ( + +
+
+ {"nft"} +
-
-
- ))} +
+ {nft.name} +
+ + ))} +
) diff --git a/src/components/ui/UniMedia.tsx b/src/components/ui/UniMedia.tsx deleted file mode 100644 index 58db689e..00000000 --- a/src/components/ui/UniMedia.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client" - -import React, { useState } from "react" - -import { Image } from "~/components/ui/Image" -import { IPFS_GATEWAY, IPFS_GATEWAY_FALLBACK } from "~/lib/env" - -export const UniMedia = ({ - src, - mime_type, -}: { - src: string - mime_type?: string -}) => { - const [errorHandled, setErrorHandled] = useState(false) - - const [type, setType] = useState(mime_type) - - const handleError = async (e: any) => { - const src = e.target.getAttribute("original-src") - if (src && !errorHandled) { - try { - const response = await fetch( - src.replace(IPFS_GATEWAY, IPFS_GATEWAY_FALLBACK), - { - method: "HEAD", - }, - ) - setType(response.headers.get("content-type") ?? "") - setErrorHandled(true) - } catch (error) {} - } - } - - return ( -
-
- {(() => { - switch (type?.split("/")[0]) { - case "video": - return ( -
-
- ) -} diff --git a/src/lib/env.server.ts b/src/lib/env.server.ts index c2ea955d..bf23fccd 100644 --- a/src/lib/env.server.ts +++ b/src/lib/env.server.ts @@ -10,8 +10,8 @@ export const REDIS_REFRESH = parseInt(config.ENV_REDIS_REFRESH || process.env.REDIS_REFRESH || "0") || 5 * 1000 // 5 seconds -export const NFTSCAN_API_KEY = - config.ENV_NFTSCAN_API_KEY || process.env.NFTSCAN_API_KEY +export const SIMPLEHASH_API_KEY = + config.ENV_SIMPLEHASH_API_KEY || process.env.SIMPLEHASH_API_KEY export const OPENAI_API_KEY = config.ENV_OPENAI_API_KEY || process.env.OPENAI_API_KEY export const ANONYMOUS_ACCOUNT_PRIVATEKEY = diff --git a/src/queries/site.server.ts b/src/queries/site.server.ts index a260babe..c4526c71 100644 --- a/src/queries/site.server.ts +++ b/src/queries/site.server.ts @@ -8,7 +8,7 @@ import { cacheGet } from "~/lib/redis.server" import { ExpandedCharacter } from "~/lib/types" import * as siteModel from "~/models/site.model" -import { NFTSCAN_API_KEY } from "../lib/env.server" +import { SIMPLEHASH_API_KEY } from "../lib/env.server" export const prefetchGetSite = async ( input: string, @@ -110,7 +110,7 @@ export const fetchGetComments = async ( } export const getNFTs = async (address?: string) => { - if (!NFTSCAN_API_KEY || !address) { + if (!SIMPLEHASH_API_KEY || !address) { return null } @@ -119,19 +119,84 @@ export const getNFTs = async (address?: string) => { noUpdate: true, expireTime: 60 * 60 * 24, getValueFun: async () => { + const chains = ( + (await ( + await fetch("https://api.simplehash.com/api/v0/chains", { + headers: { + "X-API-KEY": SIMPLEHASH_API_KEY, + accept: "application/json", + }, + }) + ).json()) as { + chain: string + is_testnet: boolean + }[] + ) + .filter((item) => !item.is_testnet) + .map((item) => item.chain) + .join(",") const result = await ( await fetch( - `https://restapi.nftscan.com/api/v2/assets/chain/${address}?erc_type=&chain=eth;bnb;polygon;arbitrum;optimism;avax;cronos;platon;moonbeam;fantom;gnosis`, + `https://api.simplehash.com/api/v0/nfts/owners_v2?${new URLSearchParams( + { + chains: chains, + wallet_addresses: address, + filters: "spam_score__lte=90", + order_by: "floor_price__desc", + }, + ).toString()}`, { headers: { - "X-API-KEY": NFTSCAN_API_KEY, + "X-API-KEY": SIMPLEHASH_API_KEY, + accept: "application/json", }, }, ) ).json() - return result.data + while (result.next && result.nfts.length < 1000) { + const next = await ( + await fetch(result.next, { + headers: { + "X-API-KEY": SIMPLEHASH_API_KEY, + accept: "application/json", + }, + }) + ).json() + result.nfts = result.nfts.concat(next.nfts) + result.next = next.next + } + return { + nfts: result.nfts, + chains: chains.split(","), + } }, - }) + }) as Promise<{ + nfts: { + nft_id: string + chain: string + contract_address: string + token_id: string + name: string + description: string + previews: { + image_small_url: string + image_medium_url: string + image_large_url: string + image_opengraph_url: string + blurhash: string + predominant_color: string + } + external_url?: string + collection?: { + external_url?: string + marketplace_pages?: { + nft_url?: string + collection_url?: string + }[] + } + }[] + chains: string[] + }> } export const getCharacterColors = async (character?: ExpandedCharacter) => {