11import { moduleToBase64 } from "app/(app)/(dashboard)/published-contract/utils/module-base-64" ;
22import { RocketIcon , ShieldCheckIcon } from "lucide-react" ;
3+ import { unstable_cache } from "next/cache" ;
34import Link from "next/link" ;
5+ import { resolveAvatar } from "thirdweb/extensions/ens" ;
6+ import { Blobbie } from "thirdweb/react" ;
47import { fetchPublishedContractVersion } from "@/api/contract/fetch-contracts-with-versions" ;
5- import { ClientOnly } from "@/components/blocks/client-only " ;
8+ import { Img } from "@/components/blocks/Img " ;
69import { Badge } from "@/components/ui/badge" ;
710import { Button } from "@/components/ui/button" ;
811import { Skeleton } from "@/components/ui/skeleton" ;
912import { getClientThirdwebClient } from "@/constants/thirdweb-client.client" ;
1013import { serverThirdwebClient } from "@/constants/thirdweb-client.server" ;
14+ import { resolveEns } from "@/lib/ens" ;
1115import { replaceDeployerAddress } from "@/lib/publisher-utils" ;
1216import { cn } from "@/lib/utils" ;
1317import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler" ;
14- import { ContractPublisher } from "./contract-publisher " ;
18+ import { shortenIfAddress } from "@/utils/usedapp-external " ;
1519
16- interface ContractCardProps {
20+ type ContractCardProps = {
1721 publisher : string ;
1822 contractId : string ;
1923 titleOverride ?: string ;
@@ -29,7 +33,7 @@ interface ContractCardProps {
2933 moduleId : string ;
3034 version ?: string ;
3135 } [ ] ;
32- }
36+ } ;
3337
3438function getContractUrl (
3539 {
@@ -74,6 +78,72 @@ function getContractUrl(
7478 return replaceDeployerAddress ( pathName ) ;
7579}
7680
81+ const cached_fetchPublishedContractVersion = unstable_cache (
82+ async ( publisher : string , contractId : string , version : string = "latest" ) => {
83+ const result = await fetchPublishedContractVersion (
84+ publisher ,
85+ contractId ,
86+ serverThirdwebClient ,
87+ version ,
88+ ) . catch ( ( ) => null ) ;
89+
90+ if ( ! result ) {
91+ return null ;
92+ }
93+
94+ const publisherEnsAndAvatar = result . publisher
95+ ? await cached_resolvePublisherEnsAndAvatar ( result . publisher )
96+ : undefined ;
97+
98+ // Note: Do not return BigInt - it can't be serialized and cached by unstable_cache and will throw an error
99+ return {
100+ name : result . name ,
101+ displayName : result . displayName ,
102+ description : result . description ,
103+ publisher : {
104+ address : result . publisher ,
105+ ensName : publisherEnsAndAvatar ?. ensName ,
106+ ensAvatar : publisherEnsAndAvatar ?. ensAvatar ,
107+ } ,
108+ version : result . version ,
109+ audit : result . audit ,
110+ } ;
111+ } ,
112+ [ "fetchPublishedContractVersion" ] ,
113+ {
114+ revalidate : 3600 , // 1 hour
115+ } ,
116+ ) ;
117+
118+ const cached_resolvePublisherEnsAndAvatar = unstable_cache (
119+ async ( _addressOrEns : string ) => {
120+ const addressOrEns = replaceDeployerAddress ( _addressOrEns ) ;
121+ const [ ensNameInfo , ensAvatar ] = await Promise . allSettled ( [
122+ resolveEns ( addressOrEns , serverThirdwebClient ) ,
123+ resolveAvatar ( {
124+ client : serverThirdwebClient ,
125+ name : addressOrEns ,
126+ } ) ,
127+ ] ) ;
128+
129+ return {
130+ ensName :
131+ ensNameInfo . status === "fulfilled"
132+ ? ensNameInfo . value ?. ensName
133+ : undefined ,
134+ address :
135+ ensNameInfo . status === "fulfilled"
136+ ? ensNameInfo . value ?. address
137+ : undefined ,
138+ ensAvatar : ensAvatar . status === "fulfilled" ? ensAvatar . value : undefined ,
139+ } ;
140+ } ,
141+ [ "resolvePublisherEnsAndAvatar" ] ,
142+ {
143+ revalidate : 3600 , // 1 hour
144+ } ,
145+ ) ;
146+
77147export async function ContractCard ( {
78148 publisher,
79149 contractId,
@@ -83,12 +153,11 @@ export async function ContractCard({
83153 modules = [ ] ,
84154 isBeta,
85155} : ContractCardProps ) {
86- const publishedContractResult = await fetchPublishedContractVersion (
156+ const publishedContractResult = await cached_fetchPublishedContractVersion (
87157 publisher ,
88158 contractId ,
89- serverThirdwebClient ,
90159 version ,
91- ) . catch ( ( ) => null ) ;
160+ ) ;
92161
93162 if ( ! publishedContractResult ) {
94163 return null ;
@@ -186,13 +255,12 @@ export async function ContractCard({
186255 ! modules ?. length && "mt-auto" ,
187256 ) }
188257 >
189- { publishedContractResult . publisher && (
190- < ClientOnly ssr = { < Skeleton className = "size-5 rounded-full" /> } >
191- < ContractPublisher
192- addressOrEns = { publishedContractResult . publisher }
193- client = { getClientThirdwebClient ( ) }
194- />
195- </ ClientOnly >
258+ { publishedContractResult . publisher . address && (
259+ < ContractPublisher
260+ address = { publishedContractResult . publisher . address }
261+ ensName = { publishedContractResult . publisher . ensName || undefined }
262+ ensAvatar = { publishedContractResult . publisher . ensAvatar || undefined }
263+ />
196264 ) }
197265
198266 < div className = "flex items-center justify-between" >
@@ -226,3 +294,31 @@ export async function ContractCard({
226294export function ContractCardSkeleton ( ) {
227295 return < Skeleton className = "h-[218px] border" /> ;
228296}
297+
298+ function ContractPublisher ( props : {
299+ ensName : string | undefined ;
300+ address : string ;
301+ ensAvatar : string | undefined ;
302+ } ) {
303+ const displayName = props . ensName || props . address ;
304+ return (
305+ < Link
306+ className = "flex shrink-0 items-center gap-1.5 hover:underline"
307+ href = { `/${ displayName } ` }
308+ target = "_blank"
309+ >
310+ < Img
311+ className = "size-5 rounded-full object-cover"
312+ src = {
313+ resolveSchemeWithErrorHandler ( {
314+ client : getClientThirdwebClient ( ) ,
315+ uri : props . ensAvatar ,
316+ } ) || ""
317+ }
318+ fallback = { < Blobbie address = { props . address } /> }
319+ />
320+
321+ < span className = "text-xs" > { shortenIfAddress ( displayName ) } </ span >
322+ </ Link >
323+ ) ;
324+ }
0 commit comments