-
Notifications
You must be signed in to change notification settings - Fork 26
ERC-721 extension #33
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
ab3f959
ERC-721 extension
damianmarti 657c245
Improvements from the PR review
damianmarti a52ec67
Fix EIP link
Pabl0cks 4d5e01a
Fix EIP link in template
Pabl0cks a3f2383
Add ERC-721 Enumerable extension section
Pabl0cks 0fd3bf6
Add missing words
Pabl0cks File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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). | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Installation | ||
|
|
||
| You can install any of the extensions in this repository by running the following command: | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```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`. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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. | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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\`\`\`. | ||
| `; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | ||
| `; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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))) | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
92
extension/packages/nextjs/app/erc721/components/AllNfts.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"; | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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[]>([]); | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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> => { | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (totalSupply === undefined || se2NftContract === undefined) return; | ||
|
|
||
| setLoading(true); | ||
| const collectibleUpdate: Collectible[] = []; | ||
| const totalBalance = parseInt(totalSupply.toString()); | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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"); | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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> | ||
Pabl0cks marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| <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> | ||
| )} | ||
| </> | ||
| ); | ||
| }; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.