diff --git a/README.md b/README.md index 624c2a27..789ff276 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ -# 🚩 Challenge {challengeNum}: {challengeEmoji} {challengeTitle} +# 🚩 Challenge #0: 🎟 Simple NFT Example -{challengeHeroImage} +![readme-0](https://raw.githubusercontent.com/Quantum3-Labs/speedrunstark/4fa48a3fb7eb1319c3424b9b835fc6acdb1a9f00/packages/nextjs/public/hero.png) -A {challengeDescription}. +📚 This tutorial is meant for developers that already understand the 🖍️ basics: [Starklings](https://starklings.app/) or [Node Guardians](https://nodeguardians.io/campaigns?f=3%3D2) -🌟 The final deliverable is an app that {challengeDeliverable}. -Deploy your contracts to a testnet then build and upload your app to a public web server. Submit the url on [SpeedRunStark.com](https://speedrunstark.com/)! +🎫 Create a simple NFT: -💬 Meet other builders working on this challenge and get help in the {challengeTelegramLink} +👷‍♀️ You'll compile and deploy your first smart contract. Then, you'll use a template React app full of important Starknet components and hooks. Finally, you'll deploy an NFT to a public network to share with friends! 🚀 ---- +🌟 The final deliverable is an app that lets users purchase and transfer NFTs. Deploy your contracts to a testnet, then build and upload your app to a public web server. + +💬 Submit this challenge, meet other builders working on this challenge or get help in the [Builders telegram chat](https://t.me/+wO3PtlRAreo4MDI9)! ## Checkpoint 0: 📦 Environment 📚 @@ -31,44 +32,135 @@ Make sure you have the compatible versions otherwise refer to [Scaffold-Stark Re Then download the challenge to your computer and install dependencies by running: ```sh -git clone https://github.com/Quantum3-Labs/speedrunstark.git {challengeName} -cd {challengeName} -git checkout {challengeName} +git clone https://github.com/Quantum3-Labs/speedrunstark.git challenge-0-simple-nft +cd challenge-0-simple-nft +git checkout challenge-0-simple-nft yarn install ``` > in the same terminal, start your local network (a local instance of a blockchain): -```sh +```bash yarn chain ``` > in a second terminal window, 🛰 deploy your contract (locally): ```sh -cd +cd challenge-0-simple-nft yarn deploy ``` > in a third terminal window, start your 📱 frontend: ```sh -cd +cd challenge-0-simple-nft yarn start ``` -📱 Open to see the app. +📱 Open [http://localhost:3000](http://localhost:3000) to see the app. + +--- + +## Checkpoint 1: ⛽️ Gas & Wallets 👛 + +> 🔥 We'll use burner wallets on localhost. + +> 👛 Explore how burner wallets work in 🏗 Scaffold-Stark. You will notice the `Connect Wallet` button on the top right corner. After click it, you can choose the `Burner Wallet` option. You will get a default prefunded account. + +## ![wallet](https://raw.githubusercontent.com/Quantum3-Labs/speedrunstark/simple-nft-example/packages/nextjs/public/ch0-wallet.png) + +## Checkpoint 2: 🖨 Minting + +> ✏️ Mint some NFTs! Click the **MINT NFT** button in the `My NFTs` tab. + +![image](https://raw.githubusercontent.com/Quantum3-Labs/speedrunstark/simple-nft-example/packages/nextjs/public/ch0-mynft.png) + +👀 You should see your NFTs start to show up: -> 👩‍💻 Rerun `yarn deploy --reset` whenever you want to deploy new contracts to the frontend, update your current contracts with changes, or re-deploy it to get a fresh contract address. +![image](https://raw.githubusercontent.com/Quantum3-Labs/speedrunstark/simple-nft-example/packages/nextjs/public/ch0-nfts-images.png) -🔏 Now you are ready to edit your smart contract `{YourCollectible.cairo}` in `packages/snfoundry/contracts` +👛 Open an window Browser and navigate to + +🎟 Transfer an NFT from one address to another using the UI: + +![image](https://github.com/Quantum3-Labs/speedrunstark/blob/simple-nft-example/packages/nextjs/public/ch0-nfts-images-transfer.png?raw=true) + +👛 Try to mint an NFT from a different address. + +🕵🏻‍♂️ Inspect the `Debug Contracts` tab to figure out what address is the owner of YourCollectible? + +🔏 You can also check out your smart contract `YourCollectible.cairo` in `packages/snfoundry/contracts`. + +💼 Take a quick look at your deploy script `deploy.ts` in `packages/snfoundry/script-ts`. + +📝 If you want to edit the frontend, navigate to `packages/nextjs/app` and open the specific page you want to modify. For instance: `/myNFTs/page.tsx`. For guidance on [routing](https://nextjs.org/docs/app/building-your-application/routing/defining-routes) and configuring [pages/layouts](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts) checkout the Next.js documentation. --- -### ⚔️ Side Quests +## Checkpoint 3: 💾 Deploy your contract! 🛰 + +🛰 Ready to deploy to a public testnet?!? + +> Change the defaultNetwork in `packages/nextjs/scaffold.config.ts` to `sepolia`. + +![chall-0-scaffold-config](https://raw.githubusercontent.com/Quantum3-Labs/speedrunstark/simple-nft-example/packages/nextjs/public/ch0-scaffold-config.png) + +Prepare your environment variables. + +> Find the `packages/snfoundry/.env` file and fill the env variables related to Sepolia testnet with your own contract address and private key. + +⛽️ You will need to send ETH or STRK to your deployer Contract Addres with your wallet, or get it from a public faucet of your chosen network. -_To finish your README, can add these links_ +> Some popular faucets are [Starknet Faucet](https://starknet-faucet.vercel.app/) and [Blastapi Starknet Sepolia Eth](https://blastapi.io/faucets/starknet-sepolia-eth) + +🚀 Deploy your NFT smart contract with `yarn deploy`. + +> you input `yarn deploy --network sepolia`. + +--- + +## Checkpoint 4: 🚢 Ship your frontend! 🚁 + +> 🦊 Since we have deployed to a public testnet, you will now need to connect using a wallet you own(Argent X or Braavos). + +![connect-wallet](https://raw.githubusercontent.com/Quantum3-Labs/speedrunstark/simple-nft-example/packages/nextjs/public/ch0-wallet.png) + +> You should see the correct network in the frontend (): + +![image](https://raw.githubusercontent.com/Quantum3-Labs/speedrunstark/simple-nft-example/packages/nextjs/public/ch0-balance.png) + +> 💬 Hint: For faster loading of your transfer page, consider updating the `fromBlock` passed to `useScaffoldEventHistory` in [`packages/nextjs/app/transfers/page.tsx`](https://github.com/Quantum3-Labs/scaffold-stark-2/blob/main/packages/nextjs/hooks/scaffold-stark/useScaffoldEventHistory.ts) to `blocknumber - 10` at which your contract was deployed. Example: `fromBlock: 3750241n` (where `n` represents its a [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)). To find this blocknumber, search your contract's address on Starkscan and find the `Contract Creation` transaction line. + +🚀 Deploy your NextJS App + +```shell +yarn vercel +``` + +> Follow the steps to deploy to Vercel. Once you log in (email, github, etc), the default options should work. It'll give you a public URL. + +> If you want to redeploy to the same production URL you can run `yarn vercel --prod`. If you omit the `--prod` flag it will deploy it to a preview/test URL. + +⚠️ Run the automated testing function to make sure your app passes + +```shell +yarn test +``` + +#### Configuration of Third-Party Services for Production-Grade Apps + +By default, 🏗 Scaffold-Stark provides predefined Open API endpoint for some services such as Blast. This allows you to begin developing and testing your applications more easily, avoiding the need to register for these services. +This is great to complete your **SpeedRunStark**. + +For production-grade applications, it's recommended to obtain your own API keys (to prevent rate limiting issues). You can configure these at: + +🔷 `RPC_URL_SEPOLIA` variable in `packages/snfoundry/.env` and `packages/nextjs/.env.local`. You can create API keys from the [Alchemy dashboard](https://dashboard.alchemy.com/). + +> 💬 Hint: It's recommended to store env's for nextjs in Vercel/system env config for live apps and use .env.local for local testing. + +--- -> 🏃 Head to your next challenge [here](https://speedrunstark.com/). +> 🏃 Head to your next challenge [here](https://github.com/Quantum3-Labs/speedrunstark/tree/challenge-1-decentralized-staking). -> 💬 Problems, questions, comments on the stack? Post them to the [🏗 Scaffold-Stark developers chat](https://t.me/+wO3PtlRAreo4MDI9) +> 💭 Problems, questions, comments on the stack? Post them to the [🏗 Scaffold-Stark developers chat](https://t.me/+wO3PtlRAreo4MDI9) diff --git a/packages/nextjs/.gitignore b/packages/nextjs/.gitignore index fd3dbb57..00bba9bb 100644 --- a/packages/nextjs/.gitignore +++ b/packages/nextjs/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel diff --git a/packages/nextjs/app/api/ipfs/add/route.ts b/packages/nextjs/app/api/ipfs/add/route.ts new file mode 100644 index 00000000..7e0e8a74 --- /dev/null +++ b/packages/nextjs/app/api/ipfs/add/route.ts @@ -0,0 +1,12 @@ +import { ipfsClient } from "~~/utils/simpleNFT/ipfs"; + +export async function POST(request: Request) { + try { + const body = await request.json(); + const res = await ipfsClient.add(JSON.stringify(body)); + return Response.json(res); + } catch (error) { + console.log("Error adding to ipfs", error); + return Response.json({ error: "Error adding to ipfs" }); + } +} diff --git a/packages/nextjs/app/api/ipfs/get-metadata/route.ts b/packages/nextjs/app/api/ipfs/get-metadata/route.ts new file mode 100644 index 00000000..c9f1c321 --- /dev/null +++ b/packages/nextjs/app/api/ipfs/get-metadata/route.ts @@ -0,0 +1,12 @@ +import { getNFTMetadataFromIPFS } from "~~/utils/simpleNFT/ipfs"; + +export async function POST(request: Request) { + try { + const { ipfsHash } = await request.json(); + const res = await getNFTMetadataFromIPFS(ipfsHash); + return Response.json(res); + } catch (error) { + console.log("Error getting metadata from ipfs", error); + return Response.json({ error: "Error getting metadata from ipfs" }); + } +} diff --git a/packages/nextjs/app/exampleView1/page.tsx b/packages/nextjs/app/exampleView1/page.tsx deleted file mode 100644 index 349cd12a..00000000 --- a/packages/nextjs/app/exampleView1/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -"use client"; - -import type { NextPage } from "next"; - -const ExampleView1: NextPage = () => { - return ( - <> -
-
-
- - ); -}; - -export default ExampleView1; diff --git a/packages/nextjs/app/ipfsDownload/page.tsx b/packages/nextjs/app/ipfsDownload/page.tsx new file mode 100644 index 00000000..8ca59f29 --- /dev/null +++ b/packages/nextjs/app/ipfsDownload/page.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { lazy, useEffect, useState } from "react"; +import type { NextPage } from "next"; +import { notification } from "~~/utils/scaffold-stark/notification"; +import { getMetadataFromIPFS } from "~~/utils/simpleNFT/ipfs-fetch"; + +const LazyReactJson = lazy(() => import("react-json-view")); + +const IpfsDownload: NextPage = () => { + const [yourJSON, setYourJSON] = useState({}); + const [ipfsPath, setIpfsPath] = useState(""); + const [loading, setLoading] = useState(false); + const [mounted, setMounted] = useState(false); + useEffect(() => { + setMounted(true); + }, []); + + const handleIpfsDownload = async () => { + setLoading(true); + const notificationId = notification.loading("Getting data from IPFS"); + try { + const metaData = await getMetadataFromIPFS(ipfsPath); + notification.remove(notificationId); + notification.success("Downloaded from IPFS"); + + setYourJSON(metaData); + } catch (error) { + notification.remove(notificationId); + notification.error("Error downloading from IPFS"); + console.log(error); + } finally { + setLoading(false); + } + }; + + return ( + <> +
+

+ Download from IPFS +

+
+ setIpfsPath(e.target.value)} + autoComplete="off" + /> +
+ + + {mounted && ( + { + setYourJSON(edit.updated_src); + }} + onAdd={(add) => { + setYourJSON(add.updated_src); + }} + onDelete={(del) => { + setYourJSON(del.updated_src); + }} + /> + )} +
+ + ); +}; + +export default IpfsDownload; diff --git a/packages/nextjs/app/ipfsUpload/page.tsx b/packages/nextjs/app/ipfsUpload/page.tsx new file mode 100644 index 00000000..27c9df07 --- /dev/null +++ b/packages/nextjs/app/ipfsUpload/page.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { lazy, useEffect, useState } from "react"; +import type { NextPage } from "next"; +import { notification } from "~~/utils/scaffold-stark/notification"; +import { addToIPFS } from "~~/utils/simpleNFT/ipfs-fetch"; +import nftsMetadata from "~~/utils/simpleNFT/nftsMetadata"; + +const LazyReactJson = lazy(() => import("react-json-view")); + +const IpfsUpload: NextPage = () => { + const [yourJSON, setYourJSON] = useState(nftsMetadata[0]); + const [loading, setLoading] = useState(false); + const [uploadedIpfsPath, setUploadedIpfsPath] = useState(""); + const [mounted, setMounted] = useState(false); + useEffect(() => { + setMounted(true); + }, []); + + const handleIpfsUpload = async () => { + setLoading(true); + const notificationId = notification.loading("Uploading to IPFS..."); + try { + const uploadedItem = await addToIPFS(yourJSON); + notification.remove(notificationId); + notification.success("Uploaded to IPFS"); + + setUploadedIpfsPath(uploadedItem.path); + } catch (error) { + notification.remove(notificationId); + notification.error("Error uploading to IPFS"); + console.log(error); + } finally { + setLoading(false); + } + }; + + return ( + <> +
+

+ Upload to IPFS +

+ + {mounted && ( + { + setYourJSON(edit.updated_src); + }} + onAdd={(add) => { + setYourJSON(add.updated_src); + }} + onDelete={(del) => { + setYourJSON(del.updated_src); + }} + /> + )} + + {uploadedIpfsPath && ( + + )} +
+ + ); +}; + +export default IpfsUpload; diff --git a/packages/nextjs/app/myNFTs/page.tsx b/packages/nextjs/app/myNFTs/page.tsx new file mode 100644 index 00000000..04f48097 --- /dev/null +++ b/packages/nextjs/app/myNFTs/page.tsx @@ -0,0 +1,91 @@ +"use client"; + +import type { NextPage } from "next"; +import { useAccount } from "@starknet-react/core"; +import { CustomConnectButton } from "~~/components/scaffold-stark/CustomConnectButton"; +import { MyHoldings } from "~~/components/SimpleNFT/MyHoldings"; +import { useScaffoldReadContract } from "~~/hooks/scaffold-stark/useScaffoldReadContract"; +import { useScaffoldWriteContract } from "~~/hooks/scaffold-stark/useScaffoldWriteContract"; +import { notification } from "~~/utils/scaffold-stark"; +import { addToIPFS } from "~~/utils/simpleNFT/ipfs-fetch"; +import nftsMetadata from "~~/utils/simpleNFT/nftsMetadata"; +import { useState } from "react"; + +const MyNFTs: NextPage = () => { + const { address: connectedAddress, isConnected, isConnecting } = useAccount(); + const [status, setStatus] = useState("Mint NFT"); + + const { writeAsync: mintItem } = useScaffoldWriteContract({ + contractName: "YourCollectible", + functionName: "mint_item", + args: [connectedAddress, ""], + }); + + const { data: tokenIdCounter, refetch } = useScaffoldReadContract({ + contractName: "YourCollectible", + functionName: "current", + watch: false, + }); + + const handleMintItem = async () => { + setStatus("Minting NFT"); + // circle back to the zero item if we've reached the end of the array + if (tokenIdCounter === undefined) { + setStatus("Mint NFT"); + return; + } + + const tokenIdCounterNumber = Number(tokenIdCounter); + const currentTokenMetaData = + nftsMetadata[tokenIdCounterNumber % nftsMetadata.length]; + const notificationId = notification.loading("Uploading to IPFS"); + try { + const uploadedItem = await addToIPFS(currentTokenMetaData); + + // First remove previous loading notification and then show success notification + notification.remove(notificationId); + notification.success("Metadata uploaded to IPFS"); + + await mintItem({ + args: [connectedAddress, uploadedItem.path], + }); + setStatus("Updating NFT List"); + refetch(); + } catch (error) { + notification.remove(notificationId); + console.error(error); + setStatus("Mint NFT"); + } + }; + + return ( + <> +
+
+

+ My NFTs +

+
+
+
+ {!isConnected || isConnecting ? ( + + ) : ( + + )} +
+ + + ); +}; + +export default MyNFTs; diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx index 14a6f002..20da62d6 100644 --- a/packages/nextjs/app/page.tsx +++ b/packages/nextjs/app/page.tsx @@ -1,15 +1,61 @@ "use client"; import type { NextPage } from "next"; -import { useAccount } from "@starknet-react/core"; +import Image from "next/image"; const Home: NextPage = () => { - const connectedAddress = useAccount(); - return ( <>
-
+
+

+ SpeedRunStarknet + + Challenge #0: Simple NFT + +

+
+ challenge banner +
+

+ 🎫 Create a simple NFT to learn basics of 🏗️ Scaffold-Stark 2. + You'll use 👷‍♀️ + + Starknet Foundry + {" "} + to compile and deploy smart contracts. Then, you'll use a + template React app full of important Ethereum components and + hooks. Finally, you'll deploy an NFT to a public network to + share with friends! 🚀 +

+

+ 🌟 The final deliverable is an app that lets users purchase and + transfer NFTs. Deploy your contracts to a testnet then build and + upload your app to a public web server. Submit the url on{" "} + + Scaffoldstark.com + {" "} + ! +

+
+
+
); diff --git a/packages/nextjs/app/transfers/page.tsx b/packages/nextjs/app/transfers/page.tsx new file mode 100644 index 00000000..86d5b347 --- /dev/null +++ b/packages/nextjs/app/transfers/page.tsx @@ -0,0 +1,76 @@ +"use client"; + +import type { NextPage } from "next"; +import { Address } from "~~/components/scaffold-stark"; +import { useScaffoldEventHistory } from "~~/hooks/scaffold-stark/useScaffoldEventHistory"; + +const Transfers: NextPage = () => { + const { data: transferEvents, isLoading } = useScaffoldEventHistory({ + contractName: "YourCollectible", + eventName: "openzeppelin::token::erc721::erc721::ERC721Component::Transfer", + fromBlock: 0n, + watch: true, + }); + + //console.log(transferEvents) + if (isLoading) + return ( +
+ +
+ ); + + return ( + <> +
+
+

+ + All Transfers Events + +

+
+
+ + + + + + + + + + {!transferEvents || transferEvents.length === 0 ? ( + + + + ) : ( + transferEvents?.map((event, index) => { + return ( + + + + + + ); + }) + )} + +
Token IdFromTo
+ No events found +
+ {event.args.token_id?.toString()} + + {" "} +
{" "} +
+ {" "} +
{" "} +
+
+
+ + ); +}; + +export default Transfers; diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx index 4683518f..8a0e22f7 100644 --- a/packages/nextjs/components/Header.tsx +++ b/packages/nextjs/components/Header.tsx @@ -4,7 +4,14 @@ import React, { useCallback, useRef, useState, useEffect } from "react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { Bars3Icon, BugAntIcon, PhotoIcon } from "@heroicons/react/24/outline"; +import { + ArrowDownTrayIcon, + ArrowPathIcon, + ArrowUpTrayIcon, + Bars3Icon, + BugAntIcon, + PhotoIcon, +} from "@heroicons/react/24/outline"; import { useOutsideClick } from "~~/hooks/scaffold-stark"; import { CustomConnectButton } from "~~/components/scaffold-stark/CustomConnectButton"; import { useTheme } from "next-themes"; @@ -21,10 +28,25 @@ type HeaderMenuLink = { export const menuLinks: HeaderMenuLink[] = [ { - label: "Example View 1", - href: "/exampleView1", + label: "My NFTs", + href: "/myNFTs", icon: , }, + { + label: "Transfers", + href: "/transfers", + icon: , + }, + { + label: "IPFS Upload", + href: "/ipfsUpload", + icon: , + }, + { + label: "IPFS Download", + href: "/ipfsDownload", + icon: , + }, { label: "Debug Contracts", href: "/debug", diff --git a/packages/nextjs/components/SimpleNFT/MyHoldings.tsx b/packages/nextjs/components/SimpleNFT/MyHoldings.tsx new file mode 100644 index 00000000..2c8142d8 --- /dev/null +++ b/packages/nextjs/components/SimpleNFT/MyHoldings.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { NFTCard } from "./NFTcard"; +import { useAccount } from "@starknet-react/core"; +import { useScaffoldContract } from "~~/hooks/scaffold-stark/useScaffoldContract"; +import { useScaffoldReadContract } from "~~/hooks/scaffold-stark/useScaffoldReadContract"; +import { notification } from "~~/utils/scaffold-stark"; +import { getMetadataFromIPFS } from "~~/utils/simpleNFT/ipfs-fetch"; +import { NFTMetaData } from "~~/utils/simpleNFT/nftsMetadata"; +import { decodeBigIntArrayToText } from "~~/utils/scaffold-stark/contractsData"; + +export interface Collectible extends Partial { + id: number; + uri: string; + owner: string; +} + +export const MyHoldings = ({ + setStatus, +}: { + setStatus: Dispatch>; +}) => { + const { address: connectedAddress } = useAccount(); + const [myAllCollectibles, setMyAllCollectibles] = useState([]); + const [allCollectiblesLoading, setAllCollectiblesLoading] = useState(false); + + const { data: yourCollectibleContract } = useScaffoldContract({ + contractName: "YourCollectible", + }); + + const { data: myTotalBalance } = useScaffoldReadContract({ + contractName: "YourCollectible", + functionName: "balance_of", + args: [connectedAddress ?? ""], + }); + + useEffect(() => { + const updateMyCollectibles = async (): Promise => { + if ( + myTotalBalance === undefined || + yourCollectibleContract === undefined || + connectedAddress === undefined + ) + return; + + setAllCollectiblesLoading(true); + const collectibleUpdate: Collectible[] = []; + const totalBalance = parseInt(myTotalBalance.toString()); + for (let tokenIndex = 0; tokenIndex < totalBalance; tokenIndex++) { + try { + const tokenId = await yourCollectibleContract.functions[ + "token_of_owner_by_index" + ](connectedAddress, BigInt(tokenIndex)); + + const tokenURI = + await yourCollectibleContract.functions["token_uri"](tokenId); + + const ipfsHash = tokenURI.replace( + /https:\/\/ipfs\.io\/(ipfs\/)?/, + "", + ); + + const nftMetadata: NFTMetaData = await getMetadataFromIPFS(ipfsHash); + + collectibleUpdate.push({ + id: parseInt(tokenId.toString()), + uri: tokenURI, + owner: connectedAddress, + ...nftMetadata, + }); + } catch (e) { + notification.error("Error fetching all collectibles"); + setAllCollectiblesLoading(false); + console.log(e); + } + } + collectibleUpdate.sort((a, b) => a.id - b.id); + setMyAllCollectibles(collectibleUpdate); + setAllCollectiblesLoading(false); + }; + + updateMyCollectibles().finally(() => setStatus("Mint NFT")); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [connectedAddress, myTotalBalance]); + + if (allCollectiblesLoading) + return ( +
+ +
+ ); + + if (!connectedAddress) return null; + + return ( + <> + {myAllCollectibles.length === 0 ? ( +
+
No NFTs found
+
+ ) : ( +
+ {myAllCollectibles.map((item) => ( + + ))} +
+ )} + + ); +}; diff --git a/packages/nextjs/components/SimpleNFT/NFTcard.tsx b/packages/nextjs/components/SimpleNFT/NFTcard.tsx new file mode 100644 index 00000000..d74efb36 --- /dev/null +++ b/packages/nextjs/components/SimpleNFT/NFTcard.tsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import { Collectible } from "./MyHoldings"; +import { AddressInput } from "../scaffold-stark"; +import { Address } from "../scaffold-stark"; +import { Address as AddressType } from "@starknet-react/chains"; +import { useScaffoldWriteContract } from "~~/hooks/scaffold-stark/useScaffoldWriteContract"; +export const NFTCard = ({ nft }: { nft: Collectible }) => { + const [transferToAddress, setTransferToAddress] = useState(""); + + const { writeAsync: transferNFT } = useScaffoldWriteContract({ + contractName: "YourCollectible", + functionName: "transfer_from", + args: [nft.owner, transferToAddress, BigInt(nft.id.toString())], + }); + + const wrapInTryCatch = + (fn: () => Promise, errorMessageFnDescription: string) => async () => { + try { + await fn(); + } catch (error) { + console.error( + `Error calling ${errorMessageFnDescription} function`, + error, + ); + } + }; + + return ( +
+
+ {/* eslint-disable-next-line */} + NFT Image +
+ # {nft.id} +
+
+
+
+

{nft.name}

+
+ {nft.attributes?.map((attr: any, index: any) => ( + + {attr.value} + + ))} +
+
+
+

{nft.description}

+
+
+ Owner : +
+
+
+ Transfer To: + setTransferToAddress(newValue)} + /> +
+
+ +
+
+
+ ); +}; diff --git a/packages/nextjs/components/scaffold-stark/Input/AddressInput.tsx b/packages/nextjs/components/scaffold-stark/Input/AddressInput.tsx index d9b82b2e..0ede7aea 100644 --- a/packages/nextjs/components/scaffold-stark/Input/AddressInput.tsx +++ b/packages/nextjs/components/scaffold-stark/Input/AddressInput.tsx @@ -3,8 +3,10 @@ import { blo } from "blo"; import { useDebounceValue } from "usehooks-ts"; import { CommonInputProps, InputBase } from "~~/components/scaffold-stark"; import { Address } from "@starknet-react/chains"; -import Image from "next/image"; - +import { getChecksumAddress, validateChecksumAddress } from "starknet"; +import { BlockieAvatar } from "~~/components/scaffold-stark/BlockieAvatar"; +import { getStarknetPFPIfExists } from "~~/utils/profile"; +import useConditionalStarkProfile from "~~/hooks/useConditionalStarkProfile"; /** * Address input with ENS name resolution */ @@ -15,17 +17,20 @@ export const AddressInput = ({ onChange, disabled, }: CommonInputProps
) => { - // TODO : Add Starkname functionality here with cached profile, check ENS on scaffold-eth const [_debouncedValue] = useDebounceValue(value, 500); - const handleChange = useCallback( (newValue: Address) => { - //setEnteredEnsName(undefined); onChange(newValue); }, [onChange], ); + const isValidAddress = typeof value === "string" && value.startsWith("0x"); + const checkSumAddress = isValidAddress + ? getChecksumAddress(value as Address) + : undefined; + const { data: profile } = useConditionalStarkProfile(value as Address); + return ( name={name} @@ -35,16 +40,21 @@ export const AddressInput = ({ disabled={disabled} prefix={null} suffix={ - // eslint-disable-next-line @next/next/no-img-element - value && ( - - ) + ) : ( + + )) } /> ); diff --git a/packages/nextjs/components/scaffold-stark/Input/EtherInput.tsx b/packages/nextjs/components/scaffold-stark/Input/EtherInput.tsx index a28bdf56..f3cf1a55 100644 --- a/packages/nextjs/components/scaffold-stark/Input/EtherInput.tsx +++ b/packages/nextjs/components/scaffold-stark/Input/EtherInput.tsx @@ -154,7 +154,7 @@ export const EtherInput = ({ disabled={!internalUsdMode && !nativeCurrencyPrice} >