1+ import "server-only" ;
12import { moduleToBase64 } from "app/(app)/(dashboard)/published-contract/utils/module-base-64" ;
23import { RocketIcon , ShieldCheckIcon } from "lucide-react" ;
4+ import { unstable_cache } from "next/cache" ;
35import Link from "next/link" ;
6+ import { resolveAvatar } from "thirdweb/extensions/ens" ;
7+ import { Blobbie } from "thirdweb/react" ;
48import { fetchPublishedContractVersion } from "@/api/contract/fetch-contracts-with-versions" ;
5- import { ClientOnly } from "@/components/blocks/client-only " ;
9+ import { Img } from "@/components/blocks/Img " ;
610import { Badge } from "@/components/ui/badge" ;
711import { Button } from "@/components/ui/button" ;
812import { Skeleton } from "@/components/ui/skeleton" ;
913import { getClientThirdwebClient } from "@/constants/thirdweb-client.client" ;
1014import { serverThirdwebClient } from "@/constants/thirdweb-client.server" ;
15+ import { resolveEns } from "@/lib/ens" ;
1116import { replaceDeployerAddress } from "@/lib/publisher-utils" ;
1217import { cn } from "@/lib/utils" ;
1318import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler" ;
14- import { ContractPublisher } from "./contract-publisher " ;
19+ import { shortenIfAddress } from "@/utils/usedapp-external " ;
1520
16- interface ContractCardProps {
21+ type ContractCardProps = {
1722 publisher : string ;
1823 contractId : string ;
1924 titleOverride ?: string ;
@@ -29,7 +34,7 @@ interface ContractCardProps {
2934 moduleId : string ;
3035 version ?: string ;
3136 } [ ] ;
32- }
37+ } ;
3338
3439function getContractUrl (
3540 {
@@ -74,6 +79,72 @@ function getContractUrl(
7479 return replaceDeployerAddress ( pathName ) ;
7580}
7681
82+ const cached_fetchPublishedContractVersion = unstable_cache (
83+ async ( publisher : string , contractId : string , version : string = "latest" ) => {
84+ const result = await fetchPublishedContractVersion (
85+ publisher ,
86+ contractId ,
87+ serverThirdwebClient ,
88+ version ,
89+ ) . catch ( ( ) => null ) ;
90+
91+ if ( ! result ) {
92+ return null ;
93+ }
94+
95+ const publisherEnsAndAvatar = result . publisher
96+ ? await cached_resolvePublisherEnsAndAvatar ( result . publisher )
97+ : undefined ;
98+
99+ // Note: Do not return BigInt - it can't be serialized and cached by unstable_cache and will throw an error
100+ return {
101+ name : result . name ,
102+ displayName : result . displayName ,
103+ description : result . description ,
104+ publisher : {
105+ address : result . publisher ,
106+ ensName : publisherEnsAndAvatar ?. ensName ,
107+ ensAvatar : publisherEnsAndAvatar ?. ensAvatar ,
108+ } ,
109+ version : result . version ,
110+ audit : result . audit ,
111+ } ;
112+ } ,
113+ [ "fetchPublishedContractVersion" ] ,
114+ {
115+ revalidate : 3600 , // 1 hour
116+ } ,
117+ ) ;
118+
119+ const cached_resolvePublisherEnsAndAvatar = unstable_cache (
120+ async ( _addressOrEns : string ) => {
121+ const addressOrEns = replaceDeployerAddress ( _addressOrEns ) ;
122+ const [ ensNameInfo , ensAvatar ] = await Promise . allSettled ( [
123+ resolveEns ( addressOrEns , serverThirdwebClient ) ,
124+ resolveAvatar ( {
125+ client : serverThirdwebClient ,
126+ name : addressOrEns ,
127+ } ) ,
128+ ] ) ;
129+
130+ return {
131+ ensName :
132+ ensNameInfo . status === "fulfilled"
133+ ? ensNameInfo . value ?. ensName
134+ : undefined ,
135+ address :
136+ ensNameInfo . status === "fulfilled"
137+ ? ensNameInfo . value ?. address
138+ : undefined ,
139+ ensAvatar : ensAvatar . status === "fulfilled" ? ensAvatar . value : undefined ,
140+ } ;
141+ } ,
142+ [ "resolveAddressAndEns" ] ,
143+ {
144+ revalidate : 3600 , // 1 hour
145+ } ,
146+ ) ;
147+
77148export async function ContractCard ( {
78149 publisher,
79150 contractId,
@@ -83,12 +154,11 @@ export async function ContractCard({
83154 modules = [ ] ,
84155 isBeta,
85156} : ContractCardProps ) {
86- const publishedContractResult = await fetchPublishedContractVersion (
157+ const publishedContractResult = await cached_fetchPublishedContractVersion (
87158 publisher ,
88159 contractId ,
89- serverThirdwebClient ,
90160 version ,
91- ) . catch ( ( ) => null ) ;
161+ ) ;
92162
93163 if ( ! publishedContractResult ) {
94164 return null ;
@@ -186,13 +256,12 @@ export async function ContractCard({
186256 ! modules ?. length && "mt-auto" ,
187257 ) }
188258 >
189- { publishedContractResult . publisher && (
190- < ClientOnly ssr = { < Skeleton className = "size-5 rounded-full" /> } >
191- < ContractPublisher
192- addressOrEns = { publishedContractResult . publisher }
193- client = { getClientThirdwebClient ( ) }
194- />
195- </ ClientOnly >
259+ { publishedContractResult . publisher . address && (
260+ < ContractPublisher
261+ address = { publishedContractResult . publisher . address }
262+ ensName = { publishedContractResult . publisher . ensName || undefined }
263+ ensAvatar = { publishedContractResult . publisher . ensAvatar || undefined }
264+ />
196265 ) }
197266
198267 < div className = "flex items-center justify-between" >
@@ -226,3 +295,31 @@ export async function ContractCard({
226295export function ContractCardSkeleton ( ) {
227296 return < Skeleton className = "h-[218px] border" /> ;
228297}
298+
299+ function ContractPublisher ( props : {
300+ ensName : string | undefined ;
301+ address : string ;
302+ ensAvatar : string | undefined ;
303+ } ) {
304+ const displayName = props . ensName || props . address ;
305+ return (
306+ < Link
307+ className = "flex shrink-0 items-center gap-1.5 hover:underline"
308+ href = { `/${ displayName } ` }
309+ target = "_blank"
310+ >
311+ < Img
312+ className = "size-5 rounded-full object-cover"
313+ src = {
314+ resolveSchemeWithErrorHandler ( {
315+ client : getClientThirdwebClient ( ) ,
316+ uri : props . ensAvatar ,
317+ } ) || ""
318+ }
319+ fallback = { < Blobbie address = { props . address } /> }
320+ />
321+
322+ < span className = "text-xs" > { shortenIfAddress ( displayName ) } </ span >
323+ </ Link >
324+ ) ;
325+ }
0 commit comments