@@ -4,10 +4,12 @@ import { Suspense, useEffect, useMemo, useState } from "react";
44import { useRouter , useSearchParams } from "next/navigation" ;
55import { encodeAbiParameters , isAddress , parseEther , parseUnits , zeroAddress } from "viem" ;
66import { useReadContract } from "wagmi" ;
7+ import { useMiniapp } from "~~/components/MiniappProvider" ;
78import { IPFSUploader } from "~~/components/marketplace/IPFSUploader" ;
89import { TagsInput } from "~~/components/marketplace/TagsInput" ;
910import { useDeployedContractInfo } from "~~/hooks/scaffold-eth" ;
1011import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth/useScaffoldWriteContract" ;
12+ import { resolveIpfsUrl } from "~~/services/ipfs/fetch" ;
1113import { uploadJSON } from "~~/services/ipfs/upload" ;
1214import TOKENS_JSON from "~~/tokens.json" ;
1315
@@ -22,11 +24,23 @@ const ERC20_DECIMALS_ABI = [
2224 } ,
2325] as const ;
2426
27+ // Minimal ERC20 ABI to read symbol (for custom tokens)
28+ const ERC20_SYMBOL_ABI = [
29+ {
30+ type : "function" ,
31+ name : "symbol" ,
32+ stateMutability : "view" ,
33+ inputs : [ ] ,
34+ outputs : [ { name : "" , type : "string" } ] ,
35+ } ,
36+ ] as const ;
37+
2538const KNOWN_TOKENS = TOKENS_JSON as Record < string , `0x${string } `> ;
2639
2740const NewListingPageInner = ( ) => {
2841 const router = useRouter ( ) ;
2942 const searchParams = useSearchParams ( ) ;
43+ const { composeCast, isMiniApp } = useMiniapp ( ) ;
3044 const [ title , setTitle ] = useState ( "" ) ;
3145 const [ description , setDescription ] = useState ( "" ) ;
3246 const [ category , setCategory ] = useState ( "" ) ;
@@ -43,6 +57,7 @@ const NewListingPageInner = () => {
4357
4458 const { writeContractAsync : writeMarketplace } = useScaffoldWriteContract ( { contractName : "Marketplace" } ) ;
4559 const { data : simpleListings } = useDeployedContractInfo ( { contractName : "SimpleListings" } ) ;
60+ const { data : marketplaceInfo } = useDeployedContractInfo ( { contractName : "Marketplace" } ) ;
4661
4762 const isCustomToken = currency === "TOKEN" ;
4863 const isKnownToken = currency !== "ETH" && currency !== "TOKEN" && Boolean ( KNOWN_TOKENS [ currency ] ) ;
@@ -63,6 +78,17 @@ const NewListingPageInner = () => {
6378 } ,
6479 } ) ;
6580
81+ // Attempt to fetch symbol for custom tokens for a nicer price label
82+ const { data : tokenSymbolData } = useReadContract ( {
83+ address : isCustomToken && isTokenAddressValid ? ( tokenAddress as `0x${string } `) : undefined ,
84+ abi : ERC20_SYMBOL_ABI ,
85+ functionName : "symbol" ,
86+ query : {
87+ enabled : isCustomToken && isTokenAddressValid ,
88+ retry : false ,
89+ } ,
90+ } ) ;
91+
6692 useEffect ( ( ) => {
6793 if ( ! ( isCustomToken || isKnownToken ) ) {
6894 setDecimalsOverride ( null ) ;
@@ -120,6 +146,24 @@ const NewListingPageInner = () => {
120146 loadingDecimals ,
121147 ] ) ;
122148
149+ // Derive a human-friendly price label similar to listing page using in-memory values
150+ const priceLabel = useMemo ( ( ) => {
151+ const trimmed = ( price || "" ) . trim ( ) ;
152+ if ( ! trimmed ) return "" ;
153+ try {
154+ if ( isCustomToken ) {
155+ const sym = ( tokenSymbolData as string | undefined ) || "TOKEN" ;
156+ return `${ trimmed } ${ sym } ` ;
157+ }
158+ if ( isKnownToken ) {
159+ return `${ trimmed } ${ currency } ` ;
160+ }
161+ return `${ trimmed } ETH` ;
162+ } catch {
163+ return trimmed ;
164+ }
165+ } , [ price , isCustomToken , isKnownToken , tokenSymbolData , currency ] ) ;
166+
123167 const onSubmit = async ( e : React . FormEvent ) => {
124168 e . preventDefault ( ) ;
125169 setSubmitting ( true ) ;
@@ -176,12 +220,51 @@ const NewListingPageInner = () => {
176220 ] ,
177221 [ paymentToken , priceWei ] ,
178222 ) ;
223+ const shareImageUrlLocal = ( resolveIpfsUrl ( localImageCid ) as string | null ) || localImageCid || undefined ;
224+
225+ // Write and wait for receipt so we can derive the new listing id from logs
226+ await writeMarketplace (
227+ {
228+ functionName : "createListing" ,
229+ args : [ simpleListings ?. address as `0x${string } `, cid , encoded ] ,
230+ } ,
231+ {
232+ blockConfirmations : 1 ,
233+ onBlockConfirmation : receipt => {
234+ try {
235+ // Parse ListingCreated log from Marketplace
236+ const mpAddress = ( marketplaceInfo ?. address || "" ) . toLowerCase ( ) ;
237+ const createdLog = receipt . logs . find ( l => ( l as any ) . address ?. toLowerCase ( ) === mpAddress ) ;
238+ let newId : string | undefined ;
239+ if ( createdLog && ( createdLog as any ) . topics && ( createdLog as any ) . topics . length >= 2 ) {
240+ try {
241+ const hex = ( createdLog as any ) . topics [ 1 ] as string ;
242+ if ( hex && hex . startsWith ( "0x" ) ) newId = String ( BigInt ( hex ) ) ;
243+ } catch { }
244+ }
179245
180- await writeMarketplace ( {
181- functionName : "createListing" ,
182- args : [ simpleListings ?. address as `0x${string } `, cid , encoded ] ,
183- } ) ;
184- router . push ( `/location/${ encodeURIComponent ( locationId ) } ` ) ;
246+ // Navigate to location first for UX consistency
247+ router . push ( `/location/${ encodeURIComponent ( locationId ) } ` ) ;
248+
249+ // Prompt to share using in-memory details even before indexing
250+ if ( isMiniApp && newId ) {
251+ try {
252+ const base =
253+ process . env . NEXT_PUBLIC_URL || ( typeof window !== "undefined" ? window . location . origin : "" ) ;
254+ const url = `${ base } /listing/${ encodeURIComponent ( newId ) } ` ;
255+ const text = `Check out my new listing: ${ title } \n\n${ description } ${ priceLabel ? `\n\n${ priceLabel } ` : "" } ` ;
256+ const embeds : string [ ] = [ ] ;
257+ if ( url ) embeds . push ( url ) ;
258+ if ( shareImageUrlLocal && embeds . length < 2 ) embeds . push ( shareImageUrlLocal ) ;
259+ setTimeout ( ( ) => {
260+ composeCast ( { text, embeds } ) . catch ( ( ) => { } ) ;
261+ } , 300 ) ;
262+ } catch { }
263+ }
264+ } catch { }
265+ } ,
266+ } ,
267+ ) ;
185268 return ;
186269 } catch { }
187270 }
@@ -223,11 +306,47 @@ const NewListingPageInner = () => {
223306 [ paymentToken , priceWei ] ,
224307 ) ;
225308
226- await writeMarketplace ( {
227- functionName : "createListing" ,
228- args : [ simpleListings ?. address as `0x${string } `, cid , encoded ] ,
229- } ) ;
230- router . push ( `/location/${ encodeURIComponent ( locationId ) } ` ) ;
309+ await writeMarketplace (
310+ {
311+ functionName : "createListing" ,
312+ args : [ simpleListings ?. address as `0x${string } `, cid , encoded ] ,
313+ } ,
314+ {
315+ blockConfirmations : 1 ,
316+ onBlockConfirmation : receipt => {
317+ try {
318+ const mpAddress = ( marketplaceInfo ?. address || "" ) . toLowerCase ( ) ;
319+ const createdLog = receipt . logs . find ( l => ( l as any ) . address ?. toLowerCase ( ) === mpAddress ) ;
320+ let newId : string | undefined ;
321+ if ( createdLog && ( createdLog as any ) . topics && ( createdLog as any ) . topics . length >= 2 ) {
322+ try {
323+ const hex = ( createdLog as any ) . topics [ 1 ] as string ;
324+ if ( hex && hex . startsWith ( "0x" ) ) newId = String ( BigInt ( hex ) ) ;
325+ } catch { }
326+ }
327+
328+ router . push ( `/location/${ encodeURIComponent ( locationId ) } ` ) ;
329+
330+ if ( isMiniApp && newId ) {
331+ try {
332+ const base =
333+ process . env . NEXT_PUBLIC_URL || ( typeof window !== "undefined" ? window . location . origin : "" ) ;
334+ const url = `${ base } /listing/${ encodeURIComponent ( newId ) } ` ;
335+ const text = `Check out my new listing: ${ title } \n\n${ description } ${ priceLabel ? `\n\n${ priceLabel } ` : "" } ` ;
336+ const embeds : string [ ] = [ ] ;
337+ if ( url ) embeds . push ( url ) ;
338+ const shareImageUrl2 = imageCid ? ( resolveIpfsUrl ( imageCid ) as string | null ) || imageCid : undefined ;
339+ if ( shareImageUrl2 && embeds . length < 2 ) embeds . push ( shareImageUrl2 ) ;
340+ setTimeout ( ( ) => {
341+ composeCast ( { text, embeds } ) . catch ( ( ) => { } ) ;
342+ } , 300 ) ;
343+ } catch { }
344+ }
345+ } catch { }
346+ } ,
347+ } ,
348+ ) ;
349+ return ;
231350 } finally {
232351 setSubmitting ( false ) ;
233352 }
0 commit comments