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,16 @@ 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+ addressOrEns = {
263+ publishedContractResult . publisher . ensName ||
264+ publishedContractResult . publisher . address ||
265+ ""
266+ }
267+ ensAvatar = { publishedContractResult . publisher . ensAvatar || undefined }
268+ />
196269 ) }
197270
198271 < div className = "flex items-center justify-between" >
@@ -226,3 +299,29 @@ export async function ContractCard({
226299export function ContractCardSkeleton ( ) {
227300 return < Skeleton className = "h-[218px] border" /> ;
228301}
302+
303+ function ContractPublisher ( props : {
304+ addressOrEns : string ;
305+ address : string ;
306+ ensAvatar : string | undefined ;
307+ } ) {
308+ return (
309+ < Link
310+ className = "flex shrink-0 items-center gap-1.5 hover:underline"
311+ href = { `/${ props . addressOrEns } ` }
312+ >
313+ < Img
314+ className = "size-5 rounded-full object-cover"
315+ src = {
316+ resolveSchemeWithErrorHandler ( {
317+ client : getClientThirdwebClient ( ) ,
318+ uri : props . ensAvatar ,
319+ } ) || ""
320+ }
321+ fallback = { < Blobbie address = { props . address } /> }
322+ />
323+
324+ < span className = "text-xs" > { shortenIfAddress ( props . addressOrEns ) } </ span >
325+ </ Link >
326+ ) ;
327+ }
0 commit comments