Skip to content

Commit

Permalink
Graduation NFT (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
carletex committed Aug 2, 2024
1 parent c9de764 commit fd4d5f5
Show file tree
Hide file tree
Showing 4 changed files with 582 additions and 4 deletions.
97 changes: 97 additions & 0 deletions packages/hardhat/contracts/BatchGraduationNFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

interface IBatchRegistry {
function BATCH_NUMBER() external view returns (uint16);
}

interface IGraduateNFTMetadata {
function getName() external view returns (string memory);
function getColor() external view returns (uint8, uint8, uint8);
}

contract BatchGraduationNFT is ERC721 {
IBatchRegistry public batchRegistry;

using Counters for Counters.Counter;
Counters.Counter private _tokenIds;

mapping(address => address) public yourGraduationContractAddress;
mapping(uint256 => address) public tokenToMetadataContract;

event MetadataSet(address builder, address metadataContract);

// Errors
error NoMetadataSet();
error UnauthorizedCaller();

constructor(address _batchRegistry) ERC721("BatchGraduate", "BGRAD") {
batchRegistry = IBatchRegistry(_batchRegistry);
}

modifier callerIsBatchRegistry() {
if (msg.sender != address(batchRegistry)) revert UnauthorizedCaller();
_;
}

function setMetadataContract(address metadataContract) public {
// Verify that the contract has the required methods
IGraduateNFTMetadata(metadataContract).getName();
IGraduateNFTMetadata(metadataContract).getColor();

yourGraduationContractAddress[msg.sender] = metadataContract;
emit MetadataSet(msg.sender, metadataContract);
}

function mint(address builder) public callerIsBatchRegistry returns (uint256) {
if (yourGraduationContractAddress[builder] == address(0)) revert NoMetadataSet();

_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
_safeMint(builder, newTokenId);

tokenToMetadataContract[newTokenId] = yourGraduationContractAddress[builder];
return newTokenId;
}

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

address metadataContract = tokenToMetadataContract[tokenId];
IGraduateNFTMetadata metadata = IGraduateNFTMetadata(metadataContract);

string memory name = metadata.getName();
(uint8 r, uint8 g, uint8 b) = metadata.getColor();

string memory svg = generateSVG(name, r, g, b);
string memory json = Base64.encode(
bytes(string(
abi.encodePacked(
'{"name": "', name, ' Graduate", ',
'"description": "Batch', Strings.toString(batchRegistry.BATCH_NUMBER()), ' Graduation NFT", ',
'"image": "data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '"}'
)
))
);

return string(abi.encodePacked("data:application/json;base64,", json));
}

function generateSVG(string memory name, uint8 r, uint8 g, uint8 b) internal view returns (string memory) {
return string(abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">',
'<circle cx="100" cy="100" r="90" fill="rgb(', Strings.toString(r), ',', Strings.toString(g), ',', Strings.toString(b), ')" />',
'<path d="M112.436 44.642v18.643h-7.841v-5.493a4.374 4.374 0 0 0-4.377-4.365c-2.417 0-4.381 1.954-4.381 4.365v5.493H88.03V44.642h4.57v2.693a2.073 2.073 0 0 0 .6 1.498 2.095 2.095 0 0 0 1.494.625 2.104 2.104 0 0 0 1.495-.625 2.081 2.081 0 0 0 .599-1.498v-2.693h2.362v-9.827c5.87-2.396 6.033 4.381 12.053 2.408-1.988 3.577-5.081 4-7.594 3.377-1.899-.47-2.421.596-2.421.596v3.446h2.456v2.681a2.058 2.058 0 0 0 .598 1.499 2.095 2.095 0 0 0 1.495.624 2.104 2.104 0 0 0 1.495-.624 2.076 2.076 0 0 0 .599-1.499v-2.68h4.605z" fill="white" />',
'<text x="100" y="85" font-family="Times New Roman, Georgia, serif" font-weight="bold" font-size="24" fill="white" text-anchor="middle">Batch ', Strings.toString(batchRegistry.BATCH_NUMBER()), '</text>',
'<text x="100" y="93" font-family="Times New Roman, Georgia, serif" font-size="8" fill="white" text-anchor="middle">graduate</text>',
'<text x="100" y="135" font-family="monospace" font-size="14" fill="white" text-anchor="middle">', name ,'</text>',
'<text x="100" y="170" font-family="Times New Roman, Georgia, serif" font-size="6" fill="white" text-anchor="middle">BuidlGuidl</text>',
'</svg>'
));
}
}
19 changes: 17 additions & 2 deletions packages/hardhat/contracts/BatchRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
pragma solidity >=0.8.0 <0.9.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "./BatchGraduationNFT.sol";

contract BatchRegistry is Ownable {

uint16 public immutable BATCH_NUMBER;
uint256 constant CHECK_IN_REWARD = 0.01 ether;
BatchGraduationNFT public batchGraduationNFT;

mapping(address => bool) public allowList;
mapping(address => address) public yourContractAddress;
mapping(address => uint256) public graduatedTokenId;
bool public isOpen = true;
uint256 public checkedInCounter;

Expand All @@ -18,7 +22,8 @@ contract BatchRegistry is Ownable {
error BatchNotOpen();
error NotAContract();
error NotInAllowList();

error AlreadyGraduated();
error NotCheckedIn();

modifier batchIsOpen() {
if (!isOpen) revert BatchNotOpen();
Expand All @@ -30,8 +35,10 @@ contract BatchRegistry is Ownable {
_;
}

constructor(address initialOwner) {
constructor(address initialOwner, uint16 batchNumber) {
super.transferOwnership(initialOwner);
batchGraduationNFT = new BatchGraduationNFT(address(this));
BATCH_NUMBER = batchNumber;
}

function updateAllowList(address[] calldata builders, bool[] calldata statuses) public onlyOwner {
Expand Down Expand Up @@ -61,6 +68,14 @@ contract BatchRegistry is Ownable {
emit CheckedIn(wasFirstTime, tx.origin, msg.sender);
}

function graduate() public {
if (graduatedTokenId[msg.sender] != 0) revert AlreadyGraduated();
if (yourContractAddress[msg.sender] == address(0)) revert NotCheckedIn();

uint256 newTokenId = batchGraduationNFT.mint(msg.sender);
graduatedTokenId[msg.sender] = newTokenId;
}

// Withdraw function for admins in case some builders don't end up checking in
function withdraw() public onlyOwner {
(bool success, ) = payable(owner()).call{value: address(this).balance}("");
Expand Down
12 changes: 10 additions & 2 deletions packages/hardhat/deploy/00_deploy_your_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { Contract } from "ethers";

// Update with your Batch number
const BATCH_NUMBER = "8";

/**
* Deploys a contract named "deployYourContract" using the deployer account and
* constructor arguments set to the deployer address
Expand All @@ -25,7 +28,7 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
await deploy("BatchRegistry", {
from: deployer,
// Contract constructor arguments
args: [deployer],
args: [deployer, BATCH_NUMBER],
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.
Expand All @@ -34,7 +37,12 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn

// Get the deployed contract to interact with it after deploying.
const batchRegistry = await hre.ethers.getContract<Contract>("BatchRegistry", deployer);
console.log("BatchRegistry deployed to:", await batchRegistry.getAddress());
console.log("\nBatchRegistry deployed to:", await batchRegistry.getAddress());
console.log("Remember to update the allow list!\n");

// The GraduationNFT contract is deployed on the BatchRegistry constructor.
const batchGraduationNFTAddress = await batchRegistry.batchGraduationNFT();
console.log("BatchGraduation NFT deployed to:", batchGraduationNFTAddress, "\n");
};

export default deployYourContract;
Expand Down
Loading

0 comments on commit fd4d5f5

Please sign in to comment.