Skip to content

Commit

Permalink
Update challenge
Browse files Browse the repository at this point in the history
  • Loading branch information
gianalarcon committed Aug 15, 2024
1 parent c97f571 commit d9ab9e2
Show file tree
Hide file tree
Showing 37 changed files with 3,111 additions and 330 deletions.
132 changes: 112 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 📚

Expand All @@ -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 <challenge_folder_name>
cd challenge-0-simple-nft
yarn deploy
```

> in a third terminal window, start your 📱 frontend:
```sh
cd <challenge_folder_name>
cd challenge-0-simple-nft
yarn start
```

📱 Open <http://localhost:3000> 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 <http://localhost:3000>

🎟 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 (<http://localhost:3000>):
![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)
1 change: 1 addition & 0 deletions packages/nextjs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel
Expand Down
12 changes: 12 additions & 0 deletions packages/nextjs/app/api/ipfs/add/route.ts
Original file line number Diff line number Diff line change
@@ -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" });
}
}
12 changes: 12 additions & 0 deletions packages/nextjs/app/api/ipfs/get-metadata/route.ts
Original file line number Diff line number Diff line change
@@ -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" });
}
}
15 changes: 0 additions & 15 deletions packages/nextjs/app/exampleView1/page.tsx

This file was deleted.

84 changes: 84 additions & 0 deletions packages/nextjs/app/ipfsDownload/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className="flex items-center flex-col flex-grow pt-10">
<h1 className="text-center mb-4">
<span className="block text-4xl font-bold">Download from IPFS</span>
</h1>
<div
className={`flex border-2 border-accent/95 bg-base-200 rounded-full text-accent w-96`}
>
<input
className="input input-ghost focus:outline-none focus:bg-transparent focus:text-secondary-content h-[2.2rem] min-h-[2.2rem] px-4 border w-full font-medium placeholder:text-accent/50 text-secondary-content/75"
placeholder="IPFS CID"
value={ipfsPath}
onChange={(e) => setIpfsPath(e.target.value)}
autoComplete="off"
/>
</div>
<button
className={`btn btn-secondary text-white my-6 ${loading ? "loading" : ""}`}
disabled={loading}
onClick={handleIpfsDownload}
>
Download from IPFS
</button>

{mounted && (
<LazyReactJson
style={{ padding: "1rem", borderRadius: "0.75rem" }}
src={yourJSON}
theme="solarized"
enableClipboard={false}
onEdit={(edit) => {
setYourJSON(edit.updated_src);
}}
onAdd={(add) => {
setYourJSON(add.updated_src);
}}
onDelete={(del) => {
setYourJSON(del.updated_src);
}}
/>
)}
</div>
</>
);
};

export default IpfsDownload;
85 changes: 85 additions & 0 deletions packages/nextjs/app/ipfsUpload/page.tsx
Original file line number Diff line number Diff line change
@@ -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<object>(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 (
<>
<div className="flex items-center flex-col flex-grow pt-10">
<h1 className="text-center mb-4">
<span className="block text-4xl font-bold">Upload to IPFS</span>
</h1>

{mounted && (
<LazyReactJson
style={{ padding: "1rem", borderRadius: "0.75rem" }}
src={yourJSON}
theme="solarized"
enableClipboard={false}
onEdit={(edit) => {
setYourJSON(edit.updated_src);
}}
onAdd={(add) => {
setYourJSON(add.updated_src);
}}
onDelete={(del) => {
setYourJSON(del.updated_src);
}}
/>
)}
<button
className={`btn btn-secondary text-white my-4 ${loading ? "loading" : ""}`}
disabled={loading}
onClick={handleIpfsUpload}
>
Upload to IPFS
</button>
{uploadedIpfsPath && (
<div className="mt-4">
<a
href={`https://ipfs.io/ipfs/${uploadedIpfsPath}`}
target="_blank"
rel="noreferrer"
>
{`https://ipfs.io/ipfs/${uploadedIpfsPath}`}
</a>
</div>
)}
</div>
</>
);
};

export default IpfsUpload;
Loading

0 comments on commit d9ab9e2

Please sign in to comment.