Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
33 changes: 15 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
# 🔌 create-eth Extensions
# ERC-721 NFT Extension for Scaffold-ETH 2

This repository holds all the BG curated extensions for [create-eth](https://github.com/scaffold-eth/create-eth), so you can extend the functionality of your Scaffold-ETH project.
This extension introduces an ERC-721 token contract and demonstrates how to use it, including getting the total supply and holder balance, listing all NFTs and NFTs from an address, and how to transfer.

## Usage
The ERC-721 Token Standard introduces a standard for Non-Fungible Tokens ([EIP-721](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol)), in other words, each Token is unique.

The ERC-721 token contract is implemented using the [ERC-721 token implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol) from OpenZeppelin.

The ERC-721 token implementation uses the [ERC-721 Enumerable extension](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/ERC721Enumerable.sol) from OpenZeppelin to list all tokens and all the tokens owned by an address. You can remove this if you plan to use an indexer, like a Subgraph or Ponder (extensions available).

## Installation

You can install any of the extensions in this repository by running the following command:

```bash
npx create-eth@latest -e <extension-name>
npx create-eth@latest -e erc-721
```

## Available Extensions
## 🚀 Setup extension

- [subgraph](https://github.com/scaffold-eth/create-eth-extensions/tree/subgraph): This Scaffold-ETH 2 extension helps you build and test subgraphs locally for your contracts. It also enables interaction with the front-end and facilitates easy deployment to Subgraph Studio.
- [eip-712](https://github.com/scaffold-eth/create-eth-extensions/tree/eip-712): An implementation of EIP-712, allowing you to send, sign, and verify typed messages in a user-friendly manner.
- [ponder](https://github.com/scaffold-eth/create-eth-extensions/tree/ponder): This Scaffold-ETH 2 extension comes pre-configured with [ponder.sh](https://ponder.sh), providing an example to help you get started quickly.
- [onchainkit](https://github.com/scaffold-eth/create-eth-extensions/tree/onchainkit): This Scaffold-ETH 2 extension comes pre-configured with [onchainkit](https://onchainkit.xyz/), providing an example to help you get started quickly.
- [erc-20](https://github.com/scaffold-eth/create-eth-extensions/tree/erc-20): This extension introduces an ERC-20 token contract and demonstrates how to interact with it, including getting a holder balance and transferring tokens.
- [eip-5792](https://github.com/scaffold-eth/create-eth-extensions/tree/eip-5792): This extension shows how to use [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) wallet capabilities. It comes with an example frontend interaction with the `EIP5792_Example.sol` contract. The code demonstrates how to make a batched transaction that sets new greetings and increments the counter in a single transaction using Wagmi's experimental hooks and Coinbase smart wallet.
Deploy your contract running `yarn deploy`

## Create your own extension
## Interact with the NFT

You can extend Scaffold-ETH by creating your own extension. To do so, you need to create a new repository with the following structure:
Start the front-end with `yarn start` and go to the _/erc721_ page to interact with your deployed ERC-721 token.

`ToDo`

```bash
npx create-eth@latest -e your-github-username/your-extension-repository:branch-name # branch-name is optional
```
You can check the code at `packages/nextjs/app/erc721`.
19 changes: 19 additions & 0 deletions extension/README.md.args.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const extraContents = `## 🚀 Setup ERC-721 NFT Extension
This extension introduces an ERC-721 token contract and demonstrates how to use it, including getting the total supply and holder balance, listing all NFTs and NFTs from an address, and how to transfer.
The ERC-721 Token Standard introduces a standard for Non-Fungible Tokens ([EIP-721](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol)), in other words, each Token is unique.
The ERC-721 token contract is implemented using the [ERC-721 token implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol) from OpenZeppelin.
### Setup
Deploy your contract running \`\`\`yarn deploy\`\`\`
### Interact with the NFT
Start the front-end with \`\`\`yarn start\`\`\` and go to the _/erc721_ page to interact with your deployed ERC-721 token.
You can check the code at \`\`\`packages/nextjs/app/erc721\`\`\`.
`;
48 changes: 48 additions & 0 deletions extension/packages/foundry/contracts/SE2NFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

contract SE2NFT is ERC721Enumerable {
uint256 public tokenIdCounter;
mapping(uint256 tokenId => string) public tokenURIs;
string[] public uris = [
"QmVHi3c4qkZcH3cJynzDXRm5n7dzc9R9TUtUcfnWQvhdcw", // Zebra
"QmfVMAmNM1kDEBYrC2TPzQDoCRFH6F5tE1e9Mr4FkkR5Xr", // Buffalo
"QmcvcUaKf6JyCXhLD1by6hJXNruPQGs3kkLg2W1xr7nF1j" // Rhino
];

constructor() ERC721("SE2-ERC721-NFT", "SE2NFT") { }

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

function mintItem(
address to
) public returns (uint256) {
tokenIdCounter++;
_safeMint(to, tokenIdCounter);

bytes32 predictableRandom = keccak256(
abi.encodePacked(
tokenIdCounter, blockhash(block.number - 1), msg.sender, address(this)
)
);

tokenURIs[tokenIdCounter] = uris[uint256(predictableRandom) % uris.length];
return tokenIdCounter;
}

function tokenURI(
uint256 tokenId
) public view override returns (string memory) {
_requireOwned(tokenId);

string memory _tokenURI = tokenURIs[tokenId];
string memory base = _baseURI();

return string.concat(base, _tokenURI);
}
}
5 changes: 5 additions & 0 deletions extension/packages/foundry/script/Deploy.s.sol.args.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const deploymentsScriptsImports = `import { DeploySE2Nft } from "./DeploySE2Nft.s.sol";`;
export const deploymentsLogic = `
DeploySE2Nft deploySE2Nft = new DeploySE2Nft();
deploySE2Nft.run();
`;
14 changes: 14 additions & 0 deletions extension/packages/foundry/script/DeploySE2Nft.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "../contracts/SE2NFT.sol";
import "./DeployHelpers.s.sol";

contract DeploySE2Nft is ScaffoldETHDeploy {
function run() external ScaffoldEthDeployerRunner {
SE2NFT se2Nft = new SE2NFT();
console.logString(
string.concat("SE2NFT deployed at: ", vm.toString(address(se2Nft)))
);
}
}
51 changes: 51 additions & 0 deletions extension/packages/hardhat/contracts/SE2NFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

contract SE2NFT is ERC721Enumerable {
uint256 public tokenIdCounter;
mapping(uint256 tokenId => string) public tokenURIs;
string[] public uris = [
"QmVHi3c4qkZcH3cJynzDXRm5n7dzc9R9TUtUcfnWQvhdcw", // Zebra
"QmfVMAmNM1kDEBYrC2TPzQDoCRFH6F5tE1e9Mr4FkkR5Xr", // Buffalo
"QmcvcUaKf6JyCXhLD1by6hJXNruPQGs3kkLg2W1xr7nF1j" // Rhino
];

constructor() ERC721("SE2-ERC721-NFT", "SE2NFT") {}

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

function mintItem(address to) public returns (uint256) {
tokenIdCounter++;
_safeMint(to, tokenIdCounter);

bytes32 predictableRandom = keccak256(
abi.encodePacked(
tokenIdCounter,
blockhash(block.number - 1),
msg.sender,
address(this)
)
);

tokenURIs[tokenIdCounter] = uris[
uint256(predictableRandom) % uris.length
];
return tokenIdCounter;
}

function tokenURI(
uint256 tokenId
) public view override returns (string memory) {
_requireOwned(tokenId);

string memory _tokenURI = tokenURIs[tokenId];
string memory base = _baseURI();

return string.concat(base, _tokenURI);
}
}
38 changes: 38 additions & 0 deletions extension/packages/hardhat/deploy/01_deploy_se2_nft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { Contract, parseEther } from "ethers";

/**
* Deploys a contract named "YourContract" using the deployer account and
* constructor arguments set to the deployer address
*
* @param hre HardhatRuntimeEnvironment object.
*/
const deploySe2Nft: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
/*
On localhost, the deployer account is the one that comes with Hardhat, which is already funded.

When deploying to live networks (e.g `yarn deploy --network sepolia`), the deployer account
should have sufficient balance to pay for the gas fees for contract creation.

You can generate a random account with `yarn generate` which will fill DEPLOYER_PRIVATE_KEY
with a random private key in the .env file (then used on hardhat.config.ts)
You can run the `yarn account` command to check your balance in every network.
*/
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;

await deploy("SE2NFT", {
from: deployer,
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,
});
};

export default deploySe2Nft;

// Tags are useful if you have multiple deploy files and only want to run one of them.
// e.g. yarn deploy --tags SE2NFT
deploySe2Nft.tags = ["SE2NFT"];
92 changes: 92 additions & 0 deletions extension/packages/nextjs/app/erc721/components/AllNfts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"use client";

import { useEffect, useState } from "react";
import { NFTCard } from "./NFTCard";
import { useAccount } from "wagmi";
import { useScaffoldContract, useScaffoldReadContract } from "~~/hooks/scaffold-eth";
import { notification } from "~~/utils/scaffold-eth";

export interface Collectible {
id: number;
uri: string;
owner: string;
image: string;
name: string;
}

export const AllNfts = () => {
const [myNfts, setMyNfts] = useState<Collectible[]>([]);
const [loading, setLoading] = useState(false);

const { data: se2NftContract } = useScaffoldContract({
contractName: "SE2NFT",
});

const { data: totalSupply } = useScaffoldReadContract({
contractName: "SE2NFT",
functionName: "totalSupply",
watch: true,
});

useEffect(() => {
const updateMyNfts = async (): Promise<void> => {
if (totalSupply === undefined || se2NftContract === undefined) return;

setLoading(true);
const collectibleUpdate: Collectible[] = [];
const totalBalance = parseInt(totalSupply.toString());
for (let tokenIndex = 0; tokenIndex < totalBalance; tokenIndex++) {
try {
const tokenId = await se2NftContract.read.tokenByIndex([BigInt(tokenIndex)]);

const tokenURI = await se2NftContract.read.tokenURI([tokenId]);
const owner = await se2NftContract.read.ownerOf([tokenId]);

const tokenMetadata = await fetch(tokenURI);
const metadata = await tokenMetadata.json();

collectibleUpdate.push({
id: parseInt(tokenId.toString()),
uri: tokenURI,
owner,
image: metadata.image,
name: metadata.name,
});
} catch (e) {
notification.error("Error fetching your NTFs");
setLoading(false);
console.log(e);
}
}
collectibleUpdate.sort((a, b) => a.id - b.id);
setMyNfts(collectibleUpdate);
setLoading(false);
};

updateMyNfts();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [totalSupply]);

if (loading)
return (
<div className="flex justify-center items-center mt-10">
<span className="loading loading-spinner loading-lg"></span>
</div>
);

return (
<>
<div className="flex justify-center items-center space-x-2 flex-col sm:flex-row">
<p className="y-2 mr-2 font-bold text-2xl">Total Supply:</p>
<p className="text-xl">{totalSupply ? totalSupply.toString() : 0} tokens</p>
</div>
{myNfts.length > 0 && (
<div className="flex flex-wrap gap-4 my-8 px-5 justify-center">
{myNfts.map(item => (
<NFTCard nft={item} key={item.id} />
))}
</div>
)}
</>
);
};
Loading