Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/main screen #1

Merged
merged 4 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/hardhat/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
FORKING_URL=
DEPLOYER_PRIVATE_KEY=
ETHERSCAN_API_KEY=
ETHERSCAN_OPTIMISTIC_API_KEY=
67 changes: 67 additions & 0 deletions packages/hardhat/contracts/YourCollectible.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2; //Do not change the solidity version as it negatively impacts submission grading

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract YourCollectible is
ERC721,
ERC721Enumerable,
ERC721URIStorage,
Ownable
{
using Counters for Counters.Counter;

Counters.Counter public tokenIdCounter;

constructor() ERC721("YourCollectible", "YCB") {}

function _baseURI() internal pure override returns (string memory) {
return "https://ipfs.io/ipfs/";
}

function mintItem(address to, string memory uri) public returns (uint256) {
tokenIdCounter.increment();
uint256 tokenId = tokenIdCounter.current();
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
return tokenId;
}

// The following functions are overrides required by Solidity.

function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 quantity
) internal override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId, quantity);
}

function _burn(
uint256 tokenId
) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}

function tokenURI(
uint256 tokenId
) public view override(ERC721, ERC721URIStorage) returns (string memory) {
return super.tokenURI(tokenId);
}

function supportsInterface(
bytes4 interfaceId
)
public
view
override(ERC721, ERC721Enumerable, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
9 changes: 4 additions & 5 deletions packages/hardhat/deploy/00_deploy_your_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,22 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;

await deploy("YourContract", {
await deploy("YourCollectible", {
from: deployer,
// Contract constructor arguments
args: [deployer],
args: [],
log: true,
// autoMine: can be passed to the deploy function to make the deployment process faster on local networks by
// automatically mining the contract deployment transaction. There is no effect on live networks.
autoMine: true,
});

// Get the deployed contract to interact with it after deploying.
const yourContract = await hre.ethers.getContract<Contract>("YourContract", deployer);
console.log("👋 Initial greeting:", await yourContract.greeting());
const yourCollectible = await hre.ethers.getContract<Contract>("YourCollectible", deployer);
};

export default deployYourContract;

// Tags are useful if you have multiple deploy files and only want to run one of them.
// e.g. yarn deploy --tags YourContract
deployYourContract.tags = ["YourContract"];
deployYourContract.tags = ["YourCollectible"];
9 changes: 8 additions & 1 deletion packages/hardhat/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ const deployerPrivateKey =
process.env.DEPLOYER_PRIVATE_KEY ?? "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
// If not set, it uses ours Etherscan default API key.
const etherscanApiKey = process.env.ETHERSCAN_API_KEY || "DNXJA8RX2Q3VZ4URQIWP7Z68CJXQZSC6AW";
const etherscanOptimisticApiKey = process.env.ETHERSCAN_OPTIMISTIC_API_KEY || "5WAJWBWKK5ZCWJ1HQKJ61CMY8SZRMQEK94";
// forking rpc url
const forkingURL = process.env.FORKING_URL || "";

const config: HardhatUserConfig = {
solidity: {
compilers: [
{
version: "0.8.20",
version: "0.8.17",
settings: {
optimizer: {
enabled: true,
Expand Down Expand Up @@ -72,6 +73,12 @@ const config: HardhatUserConfig = {
optimismSepolia: {
url: `https://sepolia.optimism.io`,
accounts: [deployerPrivateKey],
verify: {
etherscan: {
apiUrl: "https://api-sepolia-optimistic.etherscan.io",
apiKey: etherscanOptimisticApiKey,
},
},
},
polygon: {
url: `https://polygon-rpc.com`,
Expand Down
40 changes: 20 additions & 20 deletions packages/hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,39 @@
},
"devDependencies": {
"@ethersproject/abi": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.7",
"@nomicfoundation/hardhat-ethers": "^3.0.8",
"@nomicfoundation/hardhat-network-helpers": "^1.0.11",
"@nomicfoundation/hardhat-verify": "^2.0.10",
"@typechain/ethers-v5": "^11.1.2",
"@ethersproject/providers": "^5.7.1",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.3",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@nomicfoundation/hardhat-network-helpers": "^1.0.6",
"@nomicfoundation/hardhat-verify": "^2.0.3",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^9.1.0",
"@types/eslint": "^8",
"@types/mocha": "^10.0.8",
"@types/mocha": "^9.1.1",
"@types/prettier": "^2",
"@types/qrcode": "^1.5.5",
"@types/qrcode": "^1",
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
"chai": "^4.5.0",
"chai": "^4.3.6",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"ethers": "^6.13.2",
"hardhat": "^2.22.10",
"hardhat-deploy": "^0.12.4",
"hardhat-deploy-ethers": "^0.4.2",
"hardhat-gas-reporter": "^2.2.1",
"ethers": "^6.10.0",
"hardhat": "^2.19.4",
"hardhat-deploy": "^0.11.45",
"hardhat-deploy-ethers": "^0.4.1",
"hardhat-gas-reporter": "^1.0.9",
"prettier": "^2.8.4",
"solidity-coverage": "^0.8.13",
"solidity-coverage": "^0.8.5",
"ts-node": "^10.9.1",
"typechain": "^8.3.2",
"typechain": "^8.1.0",
"typescript": "^5.1.6"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.2",
"@openzeppelin/contracts": "^4.8.1",
"@typechain/ethers-v6": "^0.5.1",
"dotenv": "^16.4.5",
"envfile": "^7.1.0",
"qrcode": "^1.5.4"
"dotenv": "^16.0.3",
"envfile": "^6.18.0",
"qrcode": "^1.5.1"
}
}
58 changes: 58 additions & 0 deletions packages/hardhat/test/Challenge0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// This script executes when you run 'yarn test'
//

import { ethers } from "hardhat";
import { expect } from "chai";
import { YourCollectible } from "../typechain-types";

describe("🚩 Challenge 0: 🎟 Simple NFT Example 🤓", function () {
let myContract: YourCollectible;

describe("YourCollectible", function () {
const contractAddress = process.env.CONTRACT_ADDRESS;

let contractArtifact: string;
if (contractAddress) {
// For the autograder.
contractArtifact = `contracts/download-${contractAddress}.sol:YourCollectible`;
} else {
contractArtifact = "contracts/YourCollectible.sol:YourCollectible";
}

it("Should deploy the contract", async function () {
const YourCollectible = await ethers.getContractFactory(contractArtifact);
myContract = await YourCollectible.deploy();
console.log("\t"," 🛰 Contract deployed on", await myContract.getAddress());
});

describe("mintItem()", function () {
it("Should be able to mint an NFT", async function () {
const [owner] = await ethers.getSigners();

console.log("\t", " 🧑‍🏫 Tester Address: ", owner.address);

const startingBalance = await myContract.balanceOf(owner.address);
console.log("\t", " ⚖️ Starting balance: ", Number(startingBalance));

console.log("\t", " 🔨 Minting...");
const mintResult = await myContract.mintItem(owner.address, "QmfVMAmNM1kDEBYrC2TPzQDoCRFH6F5tE1e9Mr4FkkR5Xr");
console.log("\t", " 🏷 mint tx: ", mintResult.hash);

console.log("\t", " ⏳ Waiting for confirmation...");
const txResult = await mintResult.wait();
expect(txResult?.status).to.equal(1);

console.log("\t", " 🔎 Checking new balance: ", Number(startingBalance));
expect(await myContract.balanceOf(owner.address)).to.equal(startingBalance + 1n);
});

it("Should track tokens of owner by index", async function () {
const [owner] = await ethers.getSigners();
const startingBalance = await myContract.balanceOf(owner.address);
const token = await myContract.tokenOfOwnerByIndex(owner.address, startingBalance - 1n);
expect(token).to.greaterThan(0);
});
});
});
});
4 changes: 3 additions & 1 deletion packages/nextjs/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"parser": "@typescript-eslint/parser",
"extends": ["next/core-web-vitals", "plugin:prettier/recommended", "plugin:@typescript-eslint/recommended"],
"extends": ["next/core-web-vitals", "plugin:prettier/recommended", "plugin:@typescript-eslint/recommended", "next"],
"rules": {
"@typescript-eslint/no-unused-vars": ["error"],
"@typescript-eslint/no-explicit-any": ["off"],
"@typescript-eslint/ban-ts-comment": ["off"],
"react/no-unescaped-entities": "off",
"@next/next/no-page-custom-font": "off",
"prettier/prettier": [
"warn",
{
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" });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const AddressComponent = ({
<div className="bg-base-100 border-base-300 border shadow-md shadow-secondary rounded-3xl px-6 lg:px-8 mb-6 space-y-1 py-4 overflow-x-auto">
<div className="flex">
<div className="flex flex-col gap-1">
<Address address={address} format="long" onlyEnsOrAddress />
<Address address={address} format="long" />
<div className="flex gap-1 items-center">
<span className="font-bold text-sm">Balance:</span>
<Balance address={address} className="text" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const SearchBar = () => {
return (
<form onSubmit={handleSearch} className="flex items-center justify-end mb-5 space-x-3 mx-5">
<input
className="border-primary bg-base-100 text-base-content p-2 mr-2 w-full md:w-1/2 lg:w-1/3 rounded-md shadow-md focus:outline-none focus:ring-2 focus:ring-accent"
className="border-primary bg-base-100 text-base-content placeholder-base-content/80 p-2 mr-2 w-full md:w-1/2 lg:w-1/3 rounded-md shadow-md focus:outline-none focus:ring-2 focus:ring-accent"
type="text"
value={searchInput}
placeholder="Search by hash or address"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useState } from "react";
import Link from "next/link";
import CopyToClipboard from "react-copy-to-clipboard";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";

export const TransactionHash = ({ hash }: { hash: string }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ export const TransactionsTable = ({ blocks, transactionReceipts }: TransactionsT
<td className="w-1/12 md:py-4">{block.number?.toString()}</td>
<td className="w-2/1 md:py-4">{timeMined}</td>
<td className="w-2/12 md:py-4">
<Address address={tx.from} size="sm" onlyEnsOrAddress />
<Address address={tx.from} size="sm" />
</td>
<td className="w-2/12 md:py-4">
{!receipt?.contractAddress ? (
tx.to && <Address address={tx.to} size="sm" onlyEnsOrAddress />
tx.to && <Address address={tx.to} size="sm" />
) : (
<div className="relative">
<Address address={receipt.contractAddress} size="sm" onlyEnsOrAddress />
<Address address={receipt.contractAddress} size="sm" />
<small className="absolute top-4 left-4">(Contract Creation)</small>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const TransactionComp = ({ txHash }: { txHash: Hash }) => {
<strong>From:</strong>
</td>
<td>
<Address address={transaction.from} format="long" onlyEnsOrAddress />
<Address address={transaction.from} format="long" />
</td>
</tr>
<tr>
Expand All @@ -73,11 +73,11 @@ const TransactionComp = ({ txHash }: { txHash: Hash }) => {
</td>
<td>
{!receipt?.contractAddress ? (
transaction.to && <Address address={transaction.to} format="long" onlyEnsOrAddress />
transaction.to && <Address address={transaction.to} format="long" />
) : (
<span>
Contract Creation:
<Address address={receipt.contractAddress} format="long" onlyEnsOrAddress />
<Address address={receipt.contractAddress} format="long" />
</span>
)}
</td>
Expand Down
15 changes: 7 additions & 8 deletions packages/nextjs/app/debug/_components/DebugContracts.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
"use client";

import { useEffect, useMemo } from "react";
import { useEffect } from "react";
import { useLocalStorage } from "usehooks-ts";
import { BarsArrowUpIcon } from "@heroicons/react/20/solid";
import { ContractUI } from "~~/app/debug/_components/contract";
import { ContractName, GenericContract } from "~~/utils/scaffold-eth/contract";
import { useAllContracts } from "~~/utils/scaffold-eth/contractsData";
import { ContractName } from "~~/utils/scaffold-eth/contract";
import { getAllContracts } from "~~/utils/scaffold-eth/contractsData";

const selectedContractStorageKey = "scaffoldEth2.selectedContract";
const contractsData = getAllContracts();
const contractNames = Object.keys(contractsData) as ContractName[];

export function DebugContracts() {
const contractsData = useAllContracts();
const contractNames = useMemo(() => Object.keys(contractsData) as ContractName[], [contractsData]);

const [selectedContract, setSelectedContract] = useLocalStorage<ContractName>(
selectedContractStorageKey,
contractNames[0],
Expand All @@ -23,7 +22,7 @@ export function DebugContracts() {
if (!contractNames.includes(selectedContract)) {
setSelectedContract(contractNames[0]);
}
}, [contractNames, selectedContract, setSelectedContract]);
}, [selectedContract, setSelectedContract]);

return (
<div className="flex flex-col gap-y-6 lg:gap-y-8 py-8 lg:py-12 justify-center items-center">
Expand All @@ -44,7 +43,7 @@ export function DebugContracts() {
onClick={() => setSelectedContract(contractName)}
>
{contractName}
{(contractsData[contractName] as GenericContract)?.external && (
{contractsData[contractName].external && (
<span className="tooltip tooltip-top tooltip-accent" data-tip="External contract">
<BarsArrowUpIcon className="h-4 w-4 cursor-pointer" />
</span>
Expand Down
Loading
Loading