Skip to content

Commit

Permalink
Added approve for NFT and USDC
Browse files Browse the repository at this point in the history
  • Loading branch information
luloxi committed Sep 20, 2024
1 parent 39dd13d commit b38b749
Show file tree
Hide file tree
Showing 12 changed files with 684 additions and 440 deletions.
521 changes: 289 additions & 232 deletions packages/foundry/contracts/Marketplace.sol

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/foundry/contracts/MockUSDC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "@openzeppelin/contracts/access/Ownable.sol";
contract MockUSDC is ERC20, ERC20Burnable, Ownable {
constructor() ERC20("USDC", "USDC") Ownable(msg.sender) { }

function mint(address to, uint256 amount) public onlyOwner {
function mint(address to, uint256 amount) public {
_mint(to, amount);
}

Expand Down
59 changes: 42 additions & 17 deletions packages/nextjs/app/marketplace/_components/Marketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export interface Collectible extends Partial<NFTMetaData> {

export const Marketplace = () => {
const { address: isConnected, isConnecting } = useAccount();

const [listedCollectibles, setListedCollectibles] = useState<Collectible[]>([]);

// Fetch the collectible contract
Expand All @@ -44,6 +43,18 @@ export const Marketplace = () => {
watch: true,
});

// Fetch Marketplace Purchase events
const {
data: purchaseEvents,
isLoading: purchaseIsLoadingEvents,
error: purchaseErrorReadingEvents,
} = useScaffoldEventHistory({
contractName: "Marketplace",
eventName: "Purchase",
fromBlock: 0n,
watch: true,
});

// Fetch SimpleMint CollectionStarted events
const {
data: simpleMintEvents,
Expand Down Expand Up @@ -102,30 +113,25 @@ export const Marketplace = () => {
for (const event of simpleMintEvents || []) {
try {
const { args } = event;
// This could be used to interact with the SimpleMinted contract
// const nftAddress = args?.nft;
const artist = args?.artist;
const tokenURI = args?.tokenURI;
const usdPrice = args?.usdPrice;
const maxTokenId = args?.maxTokenId;

// Ensure tokenURI is defined
if (!tokenURI) {
console.warn("Skipping event because tokenURI is undefined");
continue;
}
if (!tokenURI) continue;

const ipfsHash = tokenURI.replace("https://ipfs.io/ipfs/", "");
const nftMetadata: NFTMetaData = await getMetadataFromIPFS(ipfsHash);

// Add the NFT collection to the collectibles array
collectiblesUpdate.push({
listingId: undefined, // Not applicable for SimpleMint NFTs
listingId: undefined,
uri: tokenURI,
owner: artist || "",
price: usdPrice ? usdPrice.toString() : undefined, // Set price as USD price from the event
payableCurrency: usdPrice ? "USDC" : undefined, // Set payableCurrency to USDC if usdPrice is present
maxTokenId: maxTokenId ? Number(maxTokenId) : undefined, // Add maxTokenId
price: usdPrice ? usdPrice.toString() : undefined,
payableCurrency: usdPrice ? "USDC" : undefined,
maxTokenId: maxTokenId ? Number(maxTokenId) : undefined,
...nftMetadata,
});
} catch (e) {
Expand All @@ -134,23 +140,42 @@ export const Marketplace = () => {
}
}

// Update state with merged collectibles
setListedCollectibles(collectiblesUpdate);
// Debugging: Log the Purchase Events
console.log("Purchase Events:", purchaseEvents);

// Debugging: Log the listed collectibles before filtering
console.log("Listed Collectibles before filtering:", collectiblesUpdate);

// Filter out NFTs that have been purchased
const updatedCollectibles = collectiblesUpdate.filter(collectible => {
const hasBeenPurchased = purchaseEvents?.some(purchase => {
const purchaseItemId = Number(purchase.args.itemId); // Convert itemId from Purchase to number
return purchaseItemId === collectible.listingId; // Ensure both are numbers for comparison
});
console.log(`Collectible ${collectible.listingId} has been purchased:`, hasBeenPurchased);
return !hasBeenPurchased;
});

// Debugging: Log the filtered collectibles
console.log("Filtered Collectibles:", updatedCollectibles);

// Update state with filtered collectibles
setListedCollectibles(updatedCollectibles);
};

fetchListedNFTs();
}, [events, simpleMintEvents, yourCollectibleContract]);
}, [events, simpleMintEvents, purchaseEvents, yourCollectibleContract]);

if (isLoadingEvents || simpleMintIsLoadingEvents) {
if (isLoadingEvents || simpleMintIsLoadingEvents || purchaseIsLoadingEvents) {
return (
<div className="flex justify-center items-center mt-10">
<span className="loading loading-spinner loading-lg"></span>
</div>
);
}

if (errorReadingEvents || simpleMintErrorReadingEvents) {
return <div>Error fetching events: {errorReadingEvents?.message || simpleMintErrorReadingEvents?.message}</div>;
if (errorReadingEvents || simpleMintErrorReadingEvents || purchaseErrorReadingEvents) {
return <div>Error fetching events: {errorReadingEvents?.message || purchaseErrorReadingEvents?.message}</div>;
}

return (
Expand Down
106 changes: 86 additions & 20 deletions packages/nextjs/app/marketplace/_components/NFTCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState } from "react";
import { formatEther } from "viem";
import { useAccount } from "wagmi";
import { Address } from "~~/components/scaffold-eth";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";
import { useDeployedContractInfo, useScaffoldReadContract, useScaffoldWriteContract } from "~~/hooks/scaffold-eth";
import { NFTMetaData } from "~~/utils/simpleNFT/nftsMetadata";

export interface Collectible extends Partial<NFTMetaData> {
Expand All @@ -25,7 +26,21 @@ export const NFTCard = ({ nft }: { nft: Collectible }) => {
}
const [activeTab, setActiveTab] = useState(initialActiveTab);

const { address: connectedAddress } = useAccount();

const { writeContractAsync: MarketplaceWriteContractAsync } = useScaffoldWriteContract("Marketplace");
const { writeContractAsync: USDCWriteContractAsync } = useScaffoldWriteContract("MockUSDC");

const { data: marketplaceData } = useDeployedContractInfo("Marketplace");

const { data: usdcAllowance } = useScaffoldReadContract({
contractName: "MockUSDC",
functionName: "allowance",
args: [connectedAddress, marketplaceData?.address],
watch: true,
});

console.log("usdcAllowance", usdcAllowance);

const handleBuyNFT = async () => {
if (!nft.listingId || !nft.price || !nft.payableCurrency) return; // Skip if required data is missing
Expand All @@ -49,14 +64,37 @@ export const NFTCard = ({ nft }: { nft: Collectible }) => {
}
};

// Convert and format price for display (handle if price is undefined)
const handleApproveUSDC = async () => {
if (!nft.listingId || !nft.price || !nft.payableCurrency) return; // Skip if required data is missing

try {
// let value;

await USDCWriteContractAsync({
functionName: "approve",
args: [marketplaceData?.address, BigInt(nft.price)],
});
} catch (err) {
console.error("Error calling buy function", err);
}
};

// Convert and format price for display
const formattedPrice =
nft.price && nft.payableCurrency === "ETH"
? formatEther(BigInt(nft.price)) // Format from wei to ETH
: nft.price
? (parseInt(nft.price) / 1e6).toFixed(2) // Format USDC (assuming 6 decimal places)
: "N/A"; // If price is undefined

const usdcPriceInUnits = nft.price ? BigInt(nft.price) : BigInt(0); // Ensure USDC price is handled as BigInt

// Check if approval is required (USDC)
const requiresApproval =
nft.payableCurrency === "USDC" &&
usdcAllowance !== undefined && // Ensure that allowance is defined
usdcPriceInUnits > BigInt(usdcAllowance.toString()); // Ensure comparison is accurate

return (
<div className="card card-compact bg-base-100 w-[300px]">
{/* Tabs navigation */}
Expand Down Expand Up @@ -112,15 +150,26 @@ export const NFTCard = ({ nft }: { nft: Collectible }) => {
</div>
</div>

<div className="card-actions justify-end">
<button
// className="btn btn-secondary bg-green-600 btn-md px-8 tracking-wide function-button"
className={`btn btn-primary hover:bg-green-500 py-3 px-6 bg-green-600 `}
onClick={handleBuyNFT}
>
Mint
</button>
</div>
{/* Conditionally render the Allow button */}
{requiresApproval ? (
<div className="card-actions justify-end">
<button
className="btn btn-warning btn-md px-8 tracking-wide function-button"
onClick={handleApproveUSDC}
>
Allow USDC
</button>
</div>
) : (
<div className="card-actions justify-end">
<button
className="btn btn-secondary btn-md px-8 tracking-wide function-button"
onClick={handleBuyNFT}
>
Mint NFT
</button>
</div>
)}
</div>
)}
</div>
Expand All @@ -141,22 +190,39 @@ export const NFTCard = ({ nft }: { nft: Collectible }) => {

{/* Display price and buy button only if price is available */}
{nft.price && nft.payableCurrency && (
<div className="flex flex-row justify-around items-center my-2 space-y-1">
<div className="flex flex-row justify-around my-2 space-y-1">
<div>
<div className="flex space-x-3 mt-1 items-center">
<span className="font-semibold">Max Supply: </span>
<span>{nft.maxTokenId}</span>
</div>
<div className="flex flex-row items-center gap-2">
<span className="text-lg">
<b>{formattedPrice}</b> {nft.payableCurrency}
</span>
</div>
</div>
<div className="card-actions justify-end">
<button
className="btn btn-secondary btn-md px-8 tracking-wide function-button"
onClick={handleBuyNFT}
>
Buy
</button>
</div>

{/* Conditionally render the Allow button */}
{requiresApproval ? (
<div className="card-actions justify-end">
<button
className="btn btn-warning btn-md px-8 tracking-wide bg-yellow-600 border-0"
onClick={handleApproveUSDC}
>
Allow USDC
</button>
</div>
) : (
<div className="card-actions justify-end">
<button
className="btn btn-secondary btn-md px-8 tracking-wide function-button"
onClick={handleBuyNFT}
>
Buy NFT
</button>
</div>
)}
</div>
)}
</div>
Expand Down
7 changes: 3 additions & 4 deletions packages/nextjs/app/myProfile/_components/MyHoldings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export const MyHoldings = () => {
const tokenURI = await yourCollectibleContract.read.tokenURI([tokenId]);
const owner = await yourCollectibleContract.read.ownerOf([tokenId]);

// if (showOnlyMyNFTs && owner.toLowerCase() !== connectedAddress.toLowerCase()) {
// continue;
// }
if (owner.toLowerCase() !== connectedAddress.toLowerCase()) {
continue;
}

const ipfsHash = tokenURI.replace("https://ipfs.io/ipfs/", "");

Expand All @@ -73,7 +73,6 @@ export const MyHoldings = () => {
};

updateMyCollectibles();
// }, [connectedAddress, showOnlyMyNFTs, myTotalBalance]); // Watching balance to update NFTs
}, [connectedAddress, myTotalBalance]); // Watching balance to update NFTs

if (allCollectiblesLoading)
Expand Down
Loading

0 comments on commit b38b749

Please sign in to comment.