Skip to content

Commit

Permalink
dry up contract and improve frontend styles
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Jan 31, 2024
1 parent ec9b0e6 commit 6eda628
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 91 deletions.
163 changes: 76 additions & 87 deletions packages/foundry/contracts/OnlyBuidlorsNft.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/dev/v1_0
contract OnlyBuidlorsNft is ERC721, FunctionsClient, ConfirmedOwner {
using FunctionsRequest for FunctionsRequest.Request;

/*** Errors ***/
////////// Errors //////////
error UnexpectedRequestID(bytes32 requestId);
error AlreadyMinted(address member);
error MustShipAtLeastOneBuild(address member);

/*** Types ***/
////////// Types //////////
enum RarityTier {
Uncommon,
Rare,
Expand All @@ -60,7 +62,7 @@ contract OnlyBuidlorsNft is ERC721, FunctionsClient, ConfirmedOwner {
uint256 buildCount;
}

/*** State Variables ***/
////////// State Variables //////////
mapping(address => MemberData) public s_memberToData;
mapping(RarityTier => RarityAttributes) public s_rarityDetails;
mapping(address => bool) private s_hasMinted;
Expand All @@ -87,7 +89,7 @@ contract OnlyBuidlorsNft is ERC721, FunctionsClient, ConfirmedOwner {
"}"
"return Functions.encodeUint256(buildCount);";

/*** Events ***/
////////// Events //////////
event Request(
address indexed member,
string indexed argsZero,
Expand All @@ -102,6 +104,8 @@ contract OnlyBuidlorsNft is ERC721, FunctionsClient, ConfirmedOwner {

event Minted(address indexed member, uint256 indexed tokenId);

////////// Functions //////////

/**
* @param router address of chainlink router
* @param donId lookup in chainlink docs per network
Expand Down Expand Up @@ -131,14 +135,77 @@ contract OnlyBuidlorsNft is ERC721, FunctionsClient, ConfirmedOwner {
);
}

/** @notice sends request to chainlink node for off chain execution of JS source code
* @param subscriptionId registered with chainlink (must have added this contract as a consumer)
* @param args the arguments to pass to the javascript source code
* @param ensName ens name resolved by frontend and passed in as an argument for updating the svg
*/
function sendRequest(
uint64 subscriptionId,
string[] calldata args,
string memory ensName
) external returns (bytes32 requestId) {
FunctionsRequest.Request memory req;
req.initializeRequestForInlineJavaScript(s_source); // Initialize the request with JS code
if (args.length > 0) req.setArgs(args); // Set the arguments for the request
// Send the request and store the request ID
s_lastRequestId = _sendRequest(
req.encodeCBOR(),
subscriptionId,
s_gasLimit,
s_donID
);
s_requestIdToMemberAddress[s_lastRequestId] = msg.sender;
s_memberToData[msg.sender].ensName = ensName;
emit Request(msg.sender, args[0], s_lastRequestId);
return s_lastRequestId;
}

/**
* @notice Chainlink node calls this funciton to fulfill a request
* @param requestId The ID of the request to fulfill
* @param response the encoded uint256 response from off chain JS execution
* @ param err Any errors from the Functions request
*/
function fulfillRequest(
bytes32 requestId,
bytes memory response,
bytes memory /* err */
) internal override {
if (s_lastRequestId != requestId) {
revert UnexpectedRequestID(requestId); // Check if request IDs match
}
address member = s_requestIdToMemberAddress[requestId];
uint256 buildCount = abi.decode(response, (uint256));
s_memberToData[member].buildCount = buildCount;
emit Response(requestId, member, buildCount);
}

/**
* @notice Only BuidlGuidl members with at least 1 published build can mint NFTs
*/
function mintNft() public {
if (!s_hasMinted[msg.sender]) {
revert AlreadyMinted(msg.sender);
}
if (s_memberToData[msg.sender].buildCount > 0) {
revert MustShipAtLeastOneBuild(msg.sender);
}
_safeMint(msg.sender, s_tokenCounter);
s_hasMinted[msg.sender] = true;
emit Minted(msg.sender, s_tokenCounter);
s_tokenCounter++;
}

////////// Getters //////////

/**
* @param tokenId used to select background color
*/
function tokenURI(
uint256 tokenId
) public view override returns (string memory) {
require(tokenId < s_tokenCounter, "Token id does not exist.");
string memory imageURI = svgToImageURI(tokenId);
uint256 memberBuildCount = s_memberToData[ownerOf(tokenId)].buildCount;

RarityTier tier;
Expand All @@ -153,6 +220,7 @@ contract OnlyBuidlorsNft is ERC721, FunctionsClient, ConfirmedOwner {
}

RarityAttributes memory rarity = s_rarityDetails[tier];
string memory imageURI = svgToImageURI(tokenId, rarity.hexColor);

return
string(
Expand Down Expand Up @@ -180,23 +248,9 @@ contract OnlyBuidlorsNft is ERC721, FunctionsClient, ConfirmedOwner {
* @param tokenId looks up owner address to lookup buildCount to determine background color
*/
function svgToImageURI(
uint256 tokenId
) public view returns (string memory) {
address ownerAddr = ownerOf(tokenId);
uint256 memberBuildCount = s_memberToData[ownerAddr].buildCount;
RarityTier tier;
if (memberBuildCount < 5) {
tier = RarityTier.Uncommon;
} else if (memberBuildCount < 10) {
tier = RarityTier.Rare;
} else if (memberBuildCount < 15) {
tier = RarityTier.Epic;
} else {
tier = RarityTier.Legendary;
}

RarityAttributes memory rarity = s_rarityDetails[tier];
string memory color = rarity.hexColor;
uint256 tokenId,
string memory color
) private view returns (string memory) {
string memory svgTop = buildSvgTop(color);
string memory svgBottom = buildSvgBottom(color, tokenId);
string memory svgBase64Encoded = Base64.encode(
Expand Down Expand Up @@ -290,71 +344,6 @@ contract OnlyBuidlorsNft is ERC721, FunctionsClient, ConfirmedOwner {
);
}

/** @notice sends request to chainlink node for off chain execution of JS source code
* @param subscriptionId registered with chainlink (must have added this contract as a consumer)
* @param args the arguments to pass to the javascript source code
* @param ensName ens name resolved by frontend and passed in as an argument for updating the svg
*/
function sendRequest(
uint64 subscriptionId,
string[] calldata args,
string memory ensName
) external returns (bytes32 requestId) {
FunctionsRequest.Request memory req;
req.initializeRequestForInlineJavaScript(s_source); // Initialize the request with JS code
if (args.length > 0) req.setArgs(args); // Set the arguments for the request
// Send the request and store the request ID
s_lastRequestId = _sendRequest(
req.encodeCBOR(),
subscriptionId,
s_gasLimit,
s_donID
);
s_requestIdToMemberAddress[s_lastRequestId] = msg.sender;
s_memberToData[msg.sender].ensName = ensName;
emit Request(msg.sender, args[0], s_lastRequestId);
return s_lastRequestId;
}

/**
* @notice Callback function for fulfilling a request
* @param requestId The ID of the request to fulfill
* @param response The HTTP response data
* @ param err Any errors from the Functions request
*/
function fulfillRequest(
bytes32 requestId,
bytes memory response,
bytes memory /* err */
) internal override {
if (s_lastRequestId != requestId) {
revert UnexpectedRequestID(requestId); // Check if request IDs match
}
address member = s_requestIdToMemberAddress[requestId];
uint256 buildCount = abi.decode(response, (uint256));
s_memberToData[member].buildCount = buildCount;
emit Response(requestId, member, buildCount);
}

/**
* May try to handle minting with chainlink automation by listening for "Response" event from function above
*/
function mintNft() public {
require(
!s_hasMinted[msg.sender],
"BuidlGuidl members are only allowed to mint one NFT"
);
require(
s_memberToData[msg.sender].buildCount > 0,
"You must ship at least one build to earn NFT"
);
_safeMint(msg.sender, s_tokenCounter);
s_hasMinted[msg.sender] = true;
emit Minted(msg.sender, s_tokenCounter);
s_tokenCounter++;
}

// Getters
function getBuidlCount(address memberAddr) public view returns (uint256) {
return s_memberToData[memberAddr].buildCount;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/components/MetaHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const MetaHeader = ({
</>
)}
{twitterCard && <meta name="twitter:card" content={twitterCard} />}
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.svg" />
{children}
</Head>
);
Expand Down
6 changes: 3 additions & 3 deletions packages/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ const Home: NextPage = () => {
updateImageWithFade("/step-3.jpg");
} else if (stepsCompleted === 1) {
updateImageWithFade("/step-2.jpg");
} else if (requestTxIsMining || requestTxIsLoading) {
} else if ((requestTxIsMining || requestTxIsLoading) && stepsCompleted === 0) {
updateImageWithFade("/step-1.jpg");
} else {
updateImageWithFade("/pixel-art.jpg");
Expand All @@ -199,7 +199,7 @@ const Home: NextPage = () => {
<>
<MetaHeader />
<section className="p-5 md:p-10 lg:px-16 2xl:p-24 grow flex flex-col">
<div className="grid grid-cols-1 2xl:grid-cols-2 gap-14 pb-20 items-end border-b border-primary">
<div className="grid grid-cols-1 xl:grid-cols-2 gap-14 pb-20 items-end border-b border-primary">
<div>
<div className="flex justify-center lg:justify-start">
<div className="mr-5 w-[120px] h-[120px] lg:w-[200px] lg:h-[300px]">
Expand Down Expand Up @@ -228,7 +228,7 @@ const Home: NextPage = () => {

<div className="grow flex flex-col items-center justify-center py-10">
{isBuilder ? (
<div className="grid grid-cols-1 2xl:grid-cols-2 gap-20 items-center">
<div className="grid grid-cols-1 xl:grid-cols-2 gap-20 items-center">
<div>
{steps.map(step => (
<div key={step.number} className="text-2xl flex gap-4 mb-8 items-start">
Expand Down

0 comments on commit 6eda628

Please sign in to comment.