diff --git a/.env.example b/.env.example index 4c7e308a..717a26ce 100644 --- a/.env.example +++ b/.env.example @@ -16,12 +16,7 @@ NEXT_PUBLIC_MIRA_LINK="https://mira.crossbell.io" # NFT -MORALIS_WEB3_API_KEY= -ALCHEMY_ETHEREUM_API_KEY= -ALCHEMY_POLYGON_API_KEY= NFTSCAN_API_KEY= -OPENSEA_API_KEY= -POAP_API_KEY= # OPENAI diff --git a/cspell.json b/cspell.json index a2d7390c..e87de654 100644 --- a/cspell.json +++ b/cspell.json @@ -63,7 +63,6 @@ "tokenomics", "Topbar", "Unfollow", - "unidata", "unist", "urlset", "wagmi", diff --git a/deploy/dev/secrets.yaml b/deploy/dev/secrets.yaml index cc981cd1..a3e8b332 100644 --- a/deploy/dev/secrets.yaml +++ b/deploy/dev/secrets.yaml @@ -3,12 +3,7 @@ apiVersion: v1 stringData: REDIS_URL: ${REDIS_URL_DEV} DATABASE_URL: ${PG_CONN_STRING_DEV} - MORALIS_WEB3_API_KEY: ${MORALIS_WEB3_API_KEY} - ALCHEMY_ETHEREUM_API_KEY: ${ALCHEMY_ETHEREUM_API_KEY} - ALCHEMY_POLYGON_API_KEY: ${ALCHEMY_POLYGON_API_KEY} NFTSCAN_API_KEY: ${NFTSCAN_API_KEY} - OPENSEA_API_KEY: ${OPENSEA_API_KEY} - POAP_API_KEY: ${POAP_API_KEY} OPENAI_API_KEY: ${OPENAI_API_KEY} ANONYMOUS_ACCOUNT_PRIVATEKEY: ${ANONYMOUS_ACCOUNT_PRIVATEKEY} kind: Secret diff --git a/deploy/prod/secrets.yaml b/deploy/prod/secrets.yaml index 5620f564..5011846e 100644 --- a/deploy/prod/secrets.yaml +++ b/deploy/prod/secrets.yaml @@ -3,12 +3,7 @@ apiVersion: v1 stringData: REDIS_URL: ${REDIS_URL} DATABASE_URL: ${PG_CONN_STRING} - MORALIS_WEB3_API_KEY: ${MORALIS_WEB3_API_KEY} - ALCHEMY_ETHEREUM_API_KEY: ${ALCHEMY_ETHEREUM_API_KEY} - ALCHEMY_POLYGON_API_KEY: ${ALCHEMY_POLYGON_API_KEY} NFTSCAN_API_KEY: ${NFTSCAN_API_KEY} - OPENSEA_API_KEY: ${OPENSEA_API_KEY} - POAP_API_KEY: ${POAP_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 c61752e5..3b69e624 100644 --- a/next.config.js +++ b/next.config.js @@ -112,12 +112,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_MORALIS_WEB3_API_KEY: process.env.MORALIS_WEB3_API_KEY, - ENV_ALCHEMY_ETHEREUM_API_KEY: process.env.ALCHEMY_ETHEREUM_API_KEY, - ENV_ALCHEMY_POLYGON_API_KEY: process.env.ALCHEMY_POLYGON_API_KEY, ENV_NFTSCAN_API_KEY: process.env.NFTSCAN_API_KEY, - ENV_OPENSEA_API_KEY: process.env.OPENSEA_API_KEY, - ENV_POAP_API_KEY: process.env.POAP_API_KEY, ENV_OPENAI_API_KEY: process.env.OPENAI_API_KEY, ENV_ANONYMOUS_ACCOUNT_PRIVATEKEY: process.env.ANONYMOUS_ACCOUNT_PRIVATEKEY, diff --git a/package.json b/package.json index c5d25519..804f899e 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,6 @@ "tailwind-merge": "1.13.1", "tailwind-scrollbar-hide": "1.1.7", "tailwindcss-animate": "1.0.6", - "unidata.js": "0.8.0", "unified": "10.1.2", "unist-builder": "3.0.1", "unist-util-visit": "4.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a91db8bf..df581edb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,7 +112,7 @@ dependencies: version: 4.21.3(@codemirror/view@6.13.2) '@urql/core': specifier: 4.0.10 - version: 4.0.10(graphql@16.6.0) + version: 4.0.10 ahooks: specifier: 3.7.7 version: 3.7.7(react@18.2.0) @@ -359,9 +359,6 @@ dependencies: tailwindcss-animate: specifier: 1.0.6 version: 1.0.6(tailwindcss@3.3.2) - unidata.js: - specifier: 0.8.0 - version: 0.8.0(typescript@5.1.3)(zod@3.21.4) unified: specifier: 10.1.2 version: 10.1.2 @@ -523,15 +520,13 @@ devDependencies: packages: - /@0no-co/graphql.web@1.0.4(graphql@16.6.0): + /@0no-co/graphql.web@1.0.4: resolution: {integrity: sha512-W3ezhHGfO0MS1PtGloaTpg0PbaT8aZSmmaerL7idtU5F7oCI+uu25k+MsMS31BVFlp4aMkHSrNRxiD72IlK8TA==} peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 peerDependenciesMeta: graphql: optional: true - dependencies: - graphql: 16.6.0 dev: false /@adraffy/ens-normalize@1.9.0: @@ -4681,10 +4676,6 @@ packages: /@types/katex@0.16.0: resolution: {integrity: sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==} - /@types/lodash@4.14.195: - resolution: {integrity: sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==} - dev: false - /@types/mdast@3.0.11: resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==} dependencies: @@ -4869,26 +4860,15 @@ packages: '@codemirror/view': 6.13.2 dev: false - /@urql/core@4.0.10(graphql@16.6.0): + /@urql/core@4.0.10: resolution: {integrity: sha512-Vs3nOSAnYftqOCg034Ostp/uSqWlQg5ryLIzcOrm8+O43s4M+Ew4GQAuemIH7ZDB8dek6h61zzWI3ujd8FH3NA==} dependencies: - '@0no-co/graphql.web': 1.0.4(graphql@16.6.0) + '@0no-co/graphql.web': 1.0.4 wonka: 6.3.2 transitivePeerDependencies: - graphql dev: false - /@wagmi/chains@1.0.0(typescript@5.1.3): - resolution: {integrity: sha512-eNbqRWyHbivcMNq5tbXJks4NaOzVLHnNQauHPeE/EDT9AlpqzcrMc+v2T1/2Iw8zN4zgqB86NCsxeJHJs7+xng==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - typescript: 5.1.3 - dev: false - /@wagmi/chains@1.1.0(typescript@5.1.3): resolution: {integrity: sha512-pWZlxBk0Ql8E7DV8DwqlbBpOyUdaG9UDlQPBxJNALuEK1I0tbQ3AVvSDnlsEIt06UPmPo5o27gzs3hwPQ/A+UA==} peerDependencies: @@ -5862,13 +5842,6 @@ packages: engines: {node: '>=4'} dev: true - /axios-retry@3.5.0: - resolution: {integrity: sha512-g48qNrLX30VU6ECWltpFCPegKK6dWzMDYv2o83W2zUL/Zh/SLXbT6ksGoKqYZHtghzqeeXhZBcSXJkO1fPbCcw==} - dependencies: - '@babel/runtime': 7.22.5 - is-retry-allowed: 2.2.0 - dev: false - /axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: @@ -5895,16 +5868,6 @@ packages: - debug dev: false - /axios@1.4.0: - resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} - dependencies: - follow-redirects: 1.15.2 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - dev: false - /axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} dependencies: @@ -8434,11 +8397,6 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /graphql@16.6.0: - resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - dev: false - /gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -9108,11 +9066,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-retry-allowed@2.2.0: - resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==} - engines: {node: '>=10'} - dev: false - /is-set@2.0.2: resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} dev: true @@ -10390,12 +10343,6 @@ packages: dependencies: mime-db: 1.52.0 - /mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - dev: false - /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -13101,27 +13048,6 @@ packages: engines: {node: '>=4'} dev: true - /unidata.js@0.8.0(typescript@5.1.3)(zod@3.21.4): - resolution: {integrity: sha512-oHd1JZCOoNbwWYWfrCWbTmAZUoy6WWTAfDFy5rLYB98x2JiH9QV6PBEuYRqEqH1lN4V6Y/rlxPspbEsGVfFGLg==} - dependencies: - '@types/lodash': 4.14.195 - '@urql/core': 4.0.10(graphql@16.6.0) - axios: 1.4.0 - axios-retry: 3.5.0 - crossbell: 1.4.0(typescript@5.1.3)(zod@3.21.4) - graphql: 16.6.0 - lodash: 4.17.21 - lodash-es: 4.17.21 - mime: 3.0.0 - viem: 0.3.50(typescript@5.1.3)(zod@3.21.4) - transitivePeerDependencies: - - bufferutil - - debug - - typescript - - utf-8-validate - - zod - dev: false - /unified@10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} dependencies: @@ -13436,25 +13362,6 @@ packages: vfile-message: 3.1.4 dev: false - /viem@0.3.50(typescript@5.1.3)(zod@3.21.4): - resolution: {integrity: sha512-s+LxCYZTR9F/qPk1/n1YDVAX9vSeVz7GraqBZWGrDuenCJxo9ArCoIceJ6ksI0WwSeNzcZ0VVbD/kWRzTxkipw==} - dependencies: - '@adraffy/ens-normalize': 1.9.0 - '@noble/curves': 1.0.0 - '@noble/hashes': 1.3.0 - '@scure/bip32': 1.3.0 - '@scure/bip39': 1.2.0 - '@wagmi/chains': 1.0.0(typescript@5.1.3) - abitype: 0.8.7(typescript@5.1.3)(zod@3.21.4) - isomorphic-ws: 5.0.0(ws@8.12.0) - ws: 8.12.0 - transitivePeerDependencies: - - bufferutil - - typescript - - utf-8-validate - - zod - dev: false - /viem@1.0.7(typescript@5.1.3)(zod@3.21.4): resolution: {integrity: sha512-P/T2Zn8+V74kxO2e2NdhNR/MBtldovssigvc36+7g3DgTeETKnSS1fMQdBl0jE+Atlal+M6cuJ2HcFY7U45o0Q==} peerDependencies: diff --git a/src/app/site/[site]/nft/page.tsx b/src/app/site/[site]/nft/page.tsx index 8613cedb..a0be6ba0 100644 --- a/src/app/site/[site]/nft/page.tsx +++ b/src/app/site/[site]/nft/page.tsx @@ -1,10 +1,10 @@ import { Metadata } from "next" import Script from "next/script" -import type { Asset } from "unidata.js" import { UniLink } from "~/components/ui/UniLink" import { UniMedia } from "~/components/ui/UniMedia" import { useTranslation } from "~/lib/i18n" +import { toGateway } from "~/lib/ipfs-parser" import getQueryClient from "~/lib/query-client" import { fetchGetSite, getNFTs } from "~/queries/site.server" @@ -38,7 +38,32 @@ export default async function SiteNFTPage({ const site = await fetchGetSite(params.site, queryClient) const { t } = await useTranslation("common") - const nfts = await getNFTs(site?.owner) + let nfts = await getNFTs(site?.owner) + + nfts.forEach((chain: any) => { + 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", + } return ( <> @@ -46,31 +71,53 @@ export default async function SiteNFTPage({ type="module" src="https://cdn.jsdelivr.net/npm/@google/model-viewer/dist/model-viewer.min.js" > -

NFT {t("Showcase")}

-
-
- {nfts.list - ?.filter((nft: Asset) => nft.items?.[0]?.address) - .map((nft: Asset) => ( - - -
- {nft.name} -
-
- ))} -
+

NFT {t("Showcase")}

+
+ Supported chains:{" "} + {Object.values(displayNames).join(", ")} +
+
+ {nfts.map((chain: any) => ( +
+
+ + + {displayNames[chain.chain] || chain.chain} ( + {chain.assets.length}) + + +
+ {chain.assets.map((nft: any) => ( + + +
+ {nft.name} +
+
+ ))} +
+
+
+ ))}
) diff --git a/src/components/ui/UniLink.tsx b/src/components/ui/UniLink.tsx index bce473fd..986b179a 100644 --- a/src/components/ui/UniLink.tsx +++ b/src/components/ui/UniLink.tsx @@ -25,7 +25,11 @@ export const UniLink = ({ } if (!href) { - return {children} + return ( + + {children} + + ) } const isExternal = diff --git a/src/lib/env.server.ts b/src/lib/env.server.ts index 7ba74704..bb09d19b 100644 --- a/src/lib/env.server.ts +++ b/src/lib/env.server.ts @@ -10,17 +10,8 @@ export const REDIS_REFRESH = parseInt(config.ENV_REDIS_REFRESH || process.env.REDIS_REFRESH || "0") || 5 * 1000 // 5 seconds -export const MORALIS_WEB3_API_KEY = - config.ENV_MORALIS_WEB3_API_KEY || process.env.MORALIS_WEB3_API_KEY -export const ALCHEMY_ETHEREUM_API_KEY = - config.ENV_ALCHEMY_ETHEREUM_API_KEY || process.env.ALCHEMY_ETHEREUM_API_KEY -export const ALCHEMY_POLYGON_API_KEY = - config.ENV_ALCHEMY_POLYGON_API_KEY || process.env.ALCHEMY_POLYGON_API_KEY export const NFTSCAN_API_KEY = config.ENV_NFTSCAN_API_KEY || process.env.NFTSCAN_API_KEY -export const OPENSEA_API_KEY = - config.ENV_OPENSEA_API_KEY || process.env.OPENSEA_API_KEY -export const POAP_API_KEY = config.ENV_POAP_API_KEY || process.env.POAP_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 565426e3..30ef240f 100644 --- a/src/queries/site.server.ts +++ b/src/queries/site.server.ts @@ -1,15 +1,14 @@ import chroma from "chroma-js" import { getAverageColor } from "fast-average-color-node" -import { Asset } from "unidata.js" import { QueryClient } from "@tanstack/react-query" -import { IPFS_GATEWAY } from "~/lib/env" import { toGateway } from "~/lib/ipfs-parser" import { cacheGet, getRedis } from "~/lib/redis.server" import { ExpandedCharacter } from "~/lib/types" import * as siteModel from "~/models/site.model" -import unidata from "~/queries/unidata.server" + +import { NFTSCAN_API_KEY } from "../lib/env.server" export const prefetchGetSite = async ( input: string, @@ -111,58 +110,34 @@ export const fetchGetComments = async ( } export const getNFTs = async (address?: string) => { - const redis = await getRedis() - const redisKey = `nfts/${address}` - - if (!address) { + if (!NFTSCAN_API_KEY || !address) { return null } + const redis = await getRedis() + const redisKey = `nfts/${address}` + let cache try { - cache = await redis?.get(redisKey) + // cache = await redis?.get(redisKey) } catch (error) {} if (cache) { return JSON.parse(cache) } else { - const result = await unidata.assets.get({ - source: "Ethereum NFT", - identity: address, - }) - await Promise.all( - result.list.map(async (nft: Asset) => { - if (!nft.items?.[0].mime_type && nft.items?.[0]?.address) { - try { - new URL(nft.items[0].address) - } catch (error) { - return nft - } - try { - const mime_type = await cacheGet({ - key: `nft-mimetype/${nft.items[0].address}`, - getValueFun: async () => { - const head = await fetch( - `${nft.items![0].address!.replace( - IPFS_GATEWAY, - "https://gateway.ipfs.io/ipfs/", - )}`, - { - method: "HEAD", - }, - ) - return head.headers.get("content-type") - }, - }) - nft.items[0].mime_type = mime_type - } catch (error) { - console.warn(error) - } - } - return nft - }), - ) - redis?.set(redisKey, JSON.stringify(result), "EX", 60 * 60 * 24) - return result + 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`, + { + headers: { + "X-API-KEY": NFTSCAN_API_KEY, + }, + }, + ) + ).json() + if (result.data) { + redis?.set(redisKey, JSON.stringify(result.data), "EX", 60 * 60 * 24) + } + return result.data } } diff --git a/src/queries/unidata.server.ts b/src/queries/unidata.server.ts deleted file mode 100644 index b673aed6..00000000 --- a/src/queries/unidata.server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Unidata from "unidata.js" - -import { IPFS_GATEWAY } from "../lib/env" -import { - ALCHEMY_ETHEREUM_API_KEY, - ALCHEMY_POLYGON_API_KEY, - MORALIS_WEB3_API_KEY, - NFTSCAN_API_KEY, - OPENSEA_API_KEY, - POAP_API_KEY, -} from "../lib/env.server" - -export default new Unidata({ - ipfsGateway: IPFS_GATEWAY, - moralisWeb3APIKey: MORALIS_WEB3_API_KEY, - alchemyEthereumAPIKey: ALCHEMY_ETHEREUM_API_KEY, - alchemyPolygonAPIKey: ALCHEMY_POLYGON_API_KEY, - nftscanAPIKey: NFTSCAN_API_KEY, - openseaAPIKey: OPENSEA_API_KEY, - poapAPIKey: POAP_API_KEY, -})