Skip to content

Commit 25a477b

Browse files
Implement listingTypeAddress handling in PayButton and ListingDetailsPage for improved payment processing. Refactor PayButton to utilize new prop and ensure proper address validation. Enhance user experience by preventing actions with invalid addresses.
1 parent e9efffb commit 25a477b

File tree

2 files changed

+39
-16
lines changed

2 files changed

+39
-16
lines changed

packages/nextjs/app/listing/[id]/page.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ const ListingDetailsPage = () => {
207207
[indexed?.paymentToken, data?.decoded?.paymentToken],
208208
);
209209

210+
const payListingTypeAddress = useMemo(() => {
211+
try {
212+
const addr = (indexed?.listingType as string | undefined) ?? (pointer?.listingType as string | undefined);
213+
return addr?.toLowerCase();
214+
} catch {
215+
return undefined;
216+
}
217+
}, [indexed?.listingType, pointer]);
218+
210219
// (back navigation handled globally in Header BackButton)
211220

212221
// Close lightbox on Escape
@@ -284,6 +293,7 @@ const ListingDetailsPage = () => {
284293
// For ETH payments, pass decoded values when available (non-ETH handled via approval in button)
285294
priceWei={payPriceWei}
286295
paymentToken={payToken}
296+
listingTypeAddress={payListingTypeAddress}
287297
disabled={!active}
288298
/>
289299
) : null}

packages/nextjs/components/marketplace/PayButton.tsx

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import { useCallback, useMemo, useState } from "react";
44
import { useRouter } from "next/navigation";
55
import { keccak256, stringToHex, zeroAddress } from "viem";
66
import { useAccount, usePublicClient, useReadContract, useWriteContract } from "wagmi";
7-
import { useScaffoldReadContract } from "~~/hooks/scaffold-eth/useScaffoldReadContract";
87
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth/useScaffoldWriteContract";
98

109
export interface PayButtonProps {
1110
listingId: string | number;
1211
priceWei?: string; // pass as string to avoid bigint serialization issues
1312
paymentToken?: string; // zero address for ETH; otherwise ERC20
1413
disabled?: boolean;
14+
listingTypeAddress?: string; // spender/listing type contract address passed from parent
1515
}
1616

17-
export const PayButton = ({ listingId, priceWei, paymentToken, disabled }: PayButtonProps) => {
17+
export const PayButton = ({ listingId, priceWei, paymentToken, disabled, listingTypeAddress }: PayButtonProps) => {
1818
const { writeContractAsync, isMining } = useScaffoldWriteContract({ contractName: "Marketplace" });
1919
const { address } = useAccount();
2020
const router = useRouter();
@@ -34,17 +34,11 @@ export const PayButton = ({ listingId, priceWei, paymentToken, disabled }: PayBu
3434
return "Buy";
3535
}, [isEth, priceWei]);
3636

37-
// Read listing pointer to get listingType (spender) address
3837
const idBig = useMemo(() => BigInt(typeof listingId === "number" ? listingId : listingId.toString()), [listingId]);
39-
const { data: listingRes } = useScaffoldReadContract({
40-
contractName: "Marketplace",
41-
functionName: "getListing",
42-
args: [idBig],
43-
// don't need to watch aggressively here
44-
watch: false,
45-
} as any);
46-
const pointer = useMemo(() => (listingRes ? (listingRes as any)[0] : undefined), [listingRes]);
47-
const listingTypeAddress = (pointer?.listingType as string | undefined)?.toLowerCase?.();
38+
const listingTypeSpenderLower = useMemo(
39+
() => (listingTypeAddress ? listingTypeAddress.toLowerCase() : undefined),
40+
[listingTypeAddress],
41+
);
4842

4943
// Minimal ERC20 ABI
5044
const erc20Abi = useMemo(
@@ -81,15 +75,15 @@ export const PayButton = ({ listingId, priceWei, paymentToken, disabled }: PayBu
8175

8276
const ownerAddress = (address || "0x0000000000000000000000000000000000000000") as `0x${string}`;
8377
const tokenAddress = (paymentToken || "0x0000000000000000000000000000000000000000") as `0x${string}`;
84-
const spenderAddress = (listingTypeAddress || "0x0000000000000000000000000000000000000000") as `0x${string}`;
78+
const spenderAddress = (listingTypeSpenderLower || "0x0000000000000000000000000000000000000000") as `0x${string}`;
8579

8680
const { data: allowanceData } = useReadContract({
8781
address: tokenAddress,
8882
abi: erc20Abi,
8983
functionName: "allowance",
9084
args: [ownerAddress, spenderAddress],
9185
query: {
92-
enabled: isErc20 && !!address && !!listingTypeAddress && !!paymentToken,
86+
enabled: isErc20 && !!address && !!listingTypeSpenderLower && !!paymentToken,
9387
},
9488
} as any);
9589

@@ -108,6 +102,10 @@ export const PayButton = ({ listingId, priceWei, paymentToken, disabled }: PayBu
108102

109103
const onBuy = useCallback(async () => {
110104
try {
105+
// Prevent approving zero address if listing type (spender) isn't available yet
106+
if (isErc20 && !listingTypeSpenderLower) {
107+
return;
108+
}
111109
if (isErc20) {
112110
const needed = priceWei ? BigInt(priceWei) : 0n;
113111
const current = (allowanceData as bigint | undefined) ?? 0n;
@@ -129,11 +127,26 @@ export const PayButton = ({ listingId, priceWei, paymentToken, disabled }: PayBu
129127
} catch {
130128
// swallow; user may have rejected or tx failed
131129
}
132-
}, [isErc20, allowanceData, priceWei, doBuy, writeTokenAsync, tokenAddress, erc20Abi, spenderAddress, publicClient]);
130+
}, [
131+
isErc20,
132+
listingTypeSpenderLower,
133+
allowanceData,
134+
priceWei,
135+
doBuy,
136+
writeTokenAsync,
137+
tokenAddress,
138+
erc20Abi,
139+
spenderAddress,
140+
publicClient,
141+
]);
133142

134143
return (
135144
<>
136-
<button className="btn btn-primary" onClick={onBuy} disabled={disabled || isMining}>
145+
<button
146+
className="btn btn-primary"
147+
onClick={onBuy}
148+
disabled={disabled || isMining || (isErc20 && !listingTypeSpenderLower)}
149+
>
137150
{label}
138151
</button>
139152
{/* Approving is now automatic; no modal shown. */}

0 commit comments

Comments
 (0)