feat: replace nftscan with simplehash

This commit is contained in:
DIYgod 2023-11-21 22:42:31 +08:00
parent d976965844
commit 034723fe8b
No known key found for this signature in database
9 changed files with 114 additions and 181 deletions

View File

@ -17,7 +17,7 @@ NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID=
# NFT
NFTSCAN_API_KEY=
SIMPLEHASH_API_KEY=
# OPENAI

View File

@ -37,7 +37,7 @@
"MINTABLE",
"MORALIS",
"nextjs",
"nftscan",
"simplehash",
"nocheck",
"nprogress",
"onclickoutside",

View File

@ -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}

View File

@ -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

View File

@ -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,

View File

@ -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<string, boolean>()
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<string, string> = {
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 (
<>
<Script
type="module"
src="https://cdn.jsdelivr.net/npm/@google/model-viewer/dist/model-viewer.min.js"
></Script>
<h2 className="page-title">NFT {t("Showcase")}</h2>
<div className="mt-8 text-zinc-500 text-sm">
<div className="mt-8 text-zinc-500 text-sm mb-8">
<span className="font-medium">Supported chains:</span>{" "}
{Object.values(displayNames).join(", ")}
{nfts.chains.join(", ")}
</div>
<div>
{nfts.map((chain: any) => (
<div
key={chain.chain}
className="flex flex-col"
data-network={chain.chain}
>
<details open>
<summary>
<span className="inline-block text-2xl font-bold my-8 pl-2">
{displayNames[chain.chain] || chain.chain} (
{chain.assets.length})
</span>
</summary>
<div className="grid grid-cols-3 md:grid-cols-4 gap-10">
{chain.assets.map((nft: any) => (
<UniLink
key={nft.mint_transaction_hash}
className="xlog-nft flex items-center flex-col"
href={nft.external_link}
data-collection={nft.contract_address}
data-token-id={nft.token_id}
data-name={nft.name}
>
<UniMedia
src={
nft.content_uri?.startsWith?.("http") ||
nft.content_uri?.startsWith?.("data:")
? nft.content_uri
: nft.content_uri?.startsWith?.("<svg")
? `data:image/svg+xml,${encodeURIComponent(
nft.content_uri,
)}` || ""
: toGateway(
nft.content_uri?.startsWith?.("ipfs://")
? nft.content_uri
: `ipfs://${nft.content_uri}`,
) || ""
}
mime_type={nft.content_type}
/>
<div className="text-xs mt-2 text-center font-medium">
{nft.name}
</div>
</UniLink>
))}
<div className="grid grid-cols-4 md:grid-cols-5 gap-8">
{nfts.nfts.map((nft) => (
<UniLink
key={nft.nft_id}
className="xlog-nft flex items-center flex-col"
href={
nft.external_url ||
nft.collection?.external_url ||
nft.collection?.marketplace_pages?.[0]?.nft_url ||
nft.collection?.marketplace_pages?.[0]?.collection_url
}
data-collection={nft.contract_address.toLowerCase()}
data-token-id={nft.token_id}
data-name={nft.name}
>
<div className="w-full aspect-[1] relative rounded overflow-hidden">
<div className="absolute top-0 bottom-0 left-0 right-0">
<Image
className="object-cover no-optimization w-full h-full rounded-2xl"
alt={"nft"}
src={nft.previews.image_medium_url}
fill={true}
/>
</div>
</div>
</details>
</div>
))}
<div className="text-xs mt-2 text-center font-medium">
{nft.name}
</div>
</UniLink>
))}
</div>
</div>
</>
)

View File

@ -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 (
<div className="w-full aspect-[1] relative rounded overflow-hidden">
<div className="absolute top-0 bottom-0 left-0 right-0">
{(() => {
switch (type?.split("/")[0]) {
case "video":
return (
<video
className="w-full h-full"
src={src}
autoPlay
loop
muted
/>
)
case "model":
return (
// @ts-ignore
<model-viewer
class="w-full h-full"
src={src}
ar
ar-modes="webxr scene-viewer quick-look"
seamless-poster
shadow-intensity="1"
camera-controls
enable-pan
/>
)
case "text":
return <iframe className="w-full h-full" src={src}></iframe>
default:
return (
<Image
className="object-cover"
alt={"nft"}
src={src}
original-src={src}
fill={true}
onError={handleError}
/>
)
}
})()}
</div>
</div>
)
}

View File

@ -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 =

View File

@ -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) => {