@@ -80,6 +80,21 @@ async function fetchIpfsJson(cidOrUrl: string): Promise<any | null> {
8080 return null ;
8181}
8282
83+ const denormalizeMetadata = ( json : any ) : any => {
84+ return {
85+ metadata : json ,
86+ title : typeof json ?. title === "string" ? json . title : null ,
87+ description : typeof json ?. description === "string" ? json . description : null ,
88+ category : typeof json ?. category === "string" ? json . category : null ,
89+ image : typeof json ?. image === "string" ? json . image : null ,
90+ contact : typeof json ?. contact === "object" ? json . contact : ( typeof json ?. contact === "string" ? json . contact : null ) ,
91+ tags : Array . isArray ( json ?. tags ) ? json . tags : ( typeof json ?. tags === "string" ? json . tags : null ) ,
92+ price : typeof json ?. price === "string" || typeof json ?. price === "number" ? String ( json . price ) : null ,
93+ currency : typeof json ?. currency === "string" ? json . currency : null ,
94+ locationId : typeof json ?. locationId === "string" ? json . locationId : null ,
95+ } ;
96+ }
97+
8398// Minimal ABI to read listing pointer + data from Marketplace
8499const marketplaceGetListingAbi = [
85100 {
@@ -97,6 +112,12 @@ const marketplaceGetListingAbi = [
97112 } ,
98113] as const ;
99114
115+ const erc20MetaAbi = [
116+ { name : "name" , type : "function" , stateMutability : "view" , inputs : [ ] , outputs : [ { type : "string" } ] } ,
117+ { name : "symbol" , type : "function" , stateMutability : "view" , inputs : [ ] , outputs : [ { type : "string" } ] } ,
118+ { name : "decimals" , type : "function" , stateMutability : "view" , inputs : [ ] , outputs : [ { type : "uint8" } ] } ,
119+ ] ;
120+
100121function signatureToSelector ( signature : string ) : `0x${string } ` {
101122 const hash = keccak256 ( stringToHex ( signature ) ) ;
102123 return ( `0x${ hash . slice ( 2 , 10 ) } ` ) as `0x${string } `;
@@ -153,11 +174,6 @@ ponder.on("Marketplace:ListingCreated" as any, async ({ event, context }) => {
153174 . set ( { tokenName : "Ether" , tokenSymbol : "ETH" , tokenDecimals : 18 } )
154175 . where ( eq ( listings . id , id ) ) ;
155176 } else {
156- const erc20MetaAbi = [
157- { name : "name" , type : "function" , stateMutability : "view" , inputs : [ ] , outputs : [ { type : "string" } ] } ,
158- { name : "symbol" , type : "function" , stateMutability : "view" , inputs : [ ] , outputs : [ { type : "string" } ] } ,
159- { name : "decimals" , type : "function" , stateMutability : "view" , inputs : [ ] , outputs : [ { type : "uint8" } ] } ,
160- ] as const ;
161177 const [ name , symbol , decimals ] = await Promise . all ( [
162178 publicClient . readContract ( { address : pt as `0x${string } `, abi : erc20MetaAbi , functionName : "name" } ) . catch ( ( ) => null ) ,
163179 publicClient . readContract ( { address : pt as `0x${string } `, abi : erc20MetaAbi , functionName : "symbol" } ) . catch ( ( ) => null ) ,
@@ -181,19 +197,7 @@ ponder.on("Marketplace:ListingCreated" as any, async ({ event, context }) => {
181197 try {
182198 const json : any | null = await fetchIpfsJson ( cid ) ;
183199 if ( json ) {
184- const denorm : any = {
185- metadata : json ,
186- // Top-level denormalized fields for convenient querying & UI
187- title : typeof json ?. title === "string" ? json . title : null ,
188- description : typeof json ?. description === "string" ? json . description : null ,
189- category : typeof json ?. category === "string" ? json . category : null ,
190- image : typeof json ?. image === "string" ? json . image : null ,
191- contact : typeof json ?. contact === "object" ? json . contact : ( typeof json ?. contact === "string" ? json . contact : null ) ,
192- tags : Array . isArray ( json ?. tags ) ? json . tags : ( typeof json ?. tags === "string" ? json . tags : null ) ,
193- price : typeof json ?. price === "string" || typeof json ?. price === "number" ? String ( json . price ) : null ,
194- currency : typeof json ?. currency === "string" ? json . currency : null ,
195- locationId : typeof json ?. locationId === "string" ? json . locationId : null ,
196- } ;
200+ const denorm = denormalizeMetadata ( json ) ;
197201 await db . sql . update ( listings ) . set ( denorm ) . where ( eq ( listings . id , id ) ) ;
198202 }
199203 } catch { }
@@ -244,7 +248,74 @@ ponder.on("Marketplace:ListingDeleted" as any, async ({ event, context }) => {
244248 await db . sql . delete ( listings ) . where ( eq ( listings . id , listingId ) ) ;
245249} ) ;
246250
247- // (No SimpleListings event handlers required)
251+ ponder . on ( "Marketplace:ListingContenthashUpdated" as any , async ( { event, context } ) => {
252+ const { db } = context ;
253+ const listingId = ( event as any ) . args . id . toString ( ) ;
254+ const cid = ( event as any ) . args . contenthash as string ;
255+
256+ try {
257+ await db . sql . update ( listings ) . set ( { cid } ) . where ( eq ( listings . id , listingId ) ) ;
258+ const json : any | null = await fetchIpfsJson ( cid ) ;
259+ if ( json ) {
260+ const denorm = denormalizeMetadata ( json ) ;
261+ await db . sql . update ( listings ) . set ( denorm ) . where ( eq ( listings . id , listingId ) ) ;
262+ }
263+
264+ try {
265+ const mpAddress = ( ( event as any ) . log ?. address as string ) || ( ( event as any ) . transaction ?. to as string ) ;
266+ if ( mpAddress ) {
267+ const [ , , , , listingData ] = ( await publicClient . readContract ( {
268+ address : mpAddress as `0x${string } `,
269+ abi : marketplaceGetListingAbi ,
270+ functionName : "getListing" ,
271+ args : [ BigInt ( listingId ) ] ,
272+ } ) ) as unknown as [ string , string , string , boolean , Hex ] ;
273+
274+ if ( listingData && ( listingData ) . length > 0 ) {
275+ try {
276+ const [ paymentToken , price ] = decodeAbiParameters (
277+ [ { type : "address" } , { type : "uint256" } ] ,
278+ listingData ,
279+ ) ;
280+ await db . sql
281+ . update ( listings )
282+ . set ( {
283+ paymentToken : paymentToken ?. toLowerCase ?.( ) ?? null ,
284+ priceWei : price ?. toString ?.( ) ?? null ,
285+ } )
286+ . where ( eq ( listings . id , listingId ) ) ;
287+
288+ const isEth = ! paymentToken || paymentToken === zeroAddress ;
289+ if ( isEth ) {
290+ await db . sql
291+ . update ( listings )
292+ . set ( { tokenName : "Ether" , tokenSymbol : "ETH" , tokenDecimals : 18 } )
293+ . where ( eq ( listings . id , listingId ) ) ;
294+ } else {
295+ const [ name , symbol , decimals ] = await Promise . all ( [
296+ publicClient . readContract ( { address : paymentToken as `0x${string } `, abi : erc20MetaAbi , functionName : "name" } ) ,
297+ publicClient . readContract ( { address : paymentToken as `0x${string } `, abi : erc20MetaAbi , functionName : "symbol" } ) ,
298+ publicClient . readContract ( { address : paymentToken as `0x${string } `, abi : erc20MetaAbi , functionName : "decimals" } ) ,
299+ ] ) ;
300+ await db . sql
301+ . update ( listings )
302+ . set ( {
303+ tokenName : typeof name === "string" ? name : null ,
304+ tokenSymbol : typeof symbol === "string" ? symbol : null ,
305+ tokenDecimals : typeof decimals === "number" ? decimals : ( typeof decimals === "bigint" ? Number ( decimals ) : null ) ,
306+ } )
307+ . where ( eq ( listings . id , listingId ) ) ;
308+ }
309+ } catch { }
310+ }
311+ }
312+ } catch ( chainErr ) {
313+ console . error ( `[Ponder] Error refreshing on-chain listing data for ${ listingId } :` , chainErr ) ;
314+ }
315+ } catch ( e ) {
316+ console . error ( `[Ponder] Error handling ListingContenthashUpdated for ${ listingId } :` , e ) ;
317+ }
318+ } ) ;
248319
249320// --- EAS Reviews indexing ---
250321// Read new per-chain keyed shape only
0 commit comments