Skip to content

Instantly share code, notes, and snippets.

@tolicodes
Created March 4, 2022 19:45
Show Gist options
  • Save tolicodes/2981264987c7ce0be5ccc065e2506c81 to your computer and use it in GitHub Desktop.
Save tolicodes/2981264987c7ce0be5ccc065e2506c81 to your computer and use it in GitHub Desktop.
Offchain Whitelist
//SPDX-License-Identifier: Unlicense
pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// for whitelist and yellowlist server signatures
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract YellowDuckCollection is ERC721Enumerable, Ownable {
using Strings for uint256;
// for whitelist and yellowlist server signatures
using ECDSA for bytes32;
// metadata
string public baseURI;
string public placeholderURI;
string private baseExtension = ".json";
// cost of token
// change using setCost
uint256 public cost = 0.12 ether;
// max total ducks
uint256 public maxSupply = 999;
// max yellowlist supply
// uint256 public maxYellowlistSupply = 111;
// running supply
uint256 public supply = 0;
// max mints per whitelisted address
uint256 public maxWhitelistMintAmount = 3;
// max public sale mints per address
uint256 public maxPublicSaleMintAmount = 5;
// number of mints per address for whitelist
mapping(address => uint8) whitelistAddressMintedAmount;
// number of mints per address for public sale
mapping(address => uint8) publicSaleAddressMintedAmount;
// 0: yellowlist
// 1: whitelist
// 2: public
// 3: ended
uint8 public saleState = 1;
// yellowlist token IDs that have already been minted
mapping(uint256 => bool) private mintedTokenIds;
// yellowlist server signatures already used to mint a token (whitelist or yellow list)
mapping(bytes => bool) private usedYellowlistBackendSignatures;
// adddress for server signing for yellowlist or whitelist
address private adminWalletAddress =
0x4b9F8c0c724d231401Fc5709a956a4159f48754e;
// keeps track of next sequential token ID
uint256 private _nextTokenId = 114;
bool private isRevealed = false;
constructor(
string memory _name,
string memory _symbol
) ERC721(_name, _symbol) {
}
// UTILITY FUNCTIONS
// use this function to get the hash of any string
function _getHash(string memory str) internal pure returns (bytes32) {
string memory prefix = "\x19Ethereum Signed Message:\n";
string memory stringLength = Strings.toString(bytes(str).length);
bytes memory abiPackedConcattedBytes = bytes.concat(
bytes(prefix),
bytes(stringLength),
bytes(str)
);
bytes32 ret = keccak256(abiPackedConcattedBytes);
return ret;
}
// used to verify signature from backend for yellowlist and whitelist
function _verifyBackendSignature(
string memory message,
bytes memory signature
) public view returns (bool) {
require(
!usedYellowlistBackendSignatures[signature],
"Signature has already been used to mint"
);
//we hash it on our side to verify the message is the one signed for
bytes32 messageHash = _getHash(message);
//check the signing address of the message did in fact sign that message message
address signingAddress = ECDSA.recover(messageHash, signature);
//verify that the message hash was signed by the admin wallet addreess
bool valid = signingAddress == adminWalletAddress;
require(valid, "Message not signed");
return true;
}
// gets the next token id to mint
function _getNextTokenId() private returns (uint256) {
// we start at the current nextTokenId
for (uint256 i = _nextTokenId; i <= maxSupply; i++) {
// this begins token IDs at 1
_nextTokenId++;
// inject solidity
// if the token has already been minted (via yellowlist), we keep going
if (!mintedTokenIds[_nextTokenId]) {
// otherwise, that's the ID we are gonna mint!
return _nextTokenId;
}
}
// there are no more tokens!
require(false, "All tokens have been minted");
return 0;
}
// ADMIN FUNCTIONS
// requires yellowlist state
modifier requireYellowlistState() {
require(saleState == 0, "Not in yellowlist sale");
_;
}
// requires yellowlist state
modifier requireWhitelistState() {
require(saleState == 1, "Not in whitelist sale");
_;
}
// requires yellowlist state
modifier _requirePublicSaleState() {
require(saleState == 2, "Not in public sale");
_;
}
// transition to yellowlist state
function reveal() public onlyOwner {
isRevealed = true;
}
// end sale
function endSale() public onlyOwner {
saleState = 3;
}
// transition to yellowlist state
function enableYellowlistState() public onlyOwner {
saleState = 0;
cost = 0.12 ether;
}
// transition to whitelist state
function enableWhitelistState() public onlyOwner {
saleState = 1;
cost = 0.11 ether;
}
// transition to public sale state
function enablePublicSaleState() public onlyOwner {
saleState = 2;
cost = 0.12 ether;
}
// change the cost of the token
function setCost(uint256 _newCost) public onlyOwner {
cost = _newCost;
}
// set new base URI for metadata
function setBaseURI(string memory _uri) public onlyOwner {
baseURI = _uri;
}
// set new base URI for metadata
function setPlaceholderURI(string memory _uri) public onlyOwner {
placeholderURI = _uri;
}
// BUYER FUNCTIONS
// used by buyer to mint a specific token if they are on yellowlist
// must provide a signature from the backend
function yellowlistMint(uint256 tokenId, bytes memory signature)
public
payable
requireYellowlistState
{
// fails if signature is invalid
_verifyBackendSignature(Strings.toString(tokenId), signature);
require(supply + 1 <= maxSupply, "Purchase would exceed max supply.");
require(msg.value == cost, "You must pay for your duck");
// mint the specified tokenId
_safeMint(msg.sender, tokenId);
supply++;
mintedTokenIds[tokenId] = true;
// use up the signature so that it can no longer be used to mint
usedYellowlistBackendSignatures[signature] = true;
}
function toAsciiString(address x) internal pure returns (string memory) {
bytes memory s = new bytes(40);
for (uint i = 0; i < 20; i++) {
bytes1 b = bytes1(uint8(uint(uint160(x)) / (2**(8*(19 - i)))));
bytes1 hi = bytes1(uint8(b) / 16);
bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
s[2*i] = char(hi);
s[2*i+1] = char(lo);
}
return string(bytes.concat(bytes("0x"), s));
}
function char(bytes1 b) internal pure returns (bytes1 c) {
if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
else return bytes1(uint8(b) + 0x57);
}
// used by buyer to mint if they're on the whitelist
function whitelistMint(bytes memory signature, uint8 numberOfTokens)
public
payable
requireWhitelistState
{
require(numberOfTokens > 0, "You must mint at least one duck.");
// 111 is max yellowlist supply, so we'll remove these from the supply
require(supply + numberOfTokens <= (maxSupply - 114), "Purchase would exceed max supply.");
require(
whitelistAddressMintedAmount[msg.sender] + numberOfTokens <= maxWhitelistMintAmount,
"You may mint up to 3 ducks."
);
require(msg.value == cost * numberOfTokens, "You must pay for your duck");
string memory sender = toAsciiString(msg.sender);
// fails if signature is invalid (message is the sender wallet address)
_verifyBackendSignature(sender, signature);
for (uint i = 0; i < numberOfTokens; i++) {
// mint the next avaialble token id
_safeMint(msg.sender, _getNextTokenId());
supply++;
// track that the wallet minted another token
whitelistAddressMintedAmount[msg.sender]++;
}
}
// public
function publicMint(uint8 numberOfTokens) public payable _requirePublicSaleState {
require(numberOfTokens > 0, "You must mint at least one duck for public sale.");
// 114 is max yellowlist supply, so we'll remove these from the supply
require(supply + numberOfTokens <= (maxSupply - 114), "Purchase would exceed max supply.");
require(
publicSaleAddressMintedAmount[msg.sender] + numberOfTokens <= maxPublicSaleMintAmount,
"You may mint up to 5 ducks on the public list sale."
);
require(msg.value == cost * numberOfTokens, "You must pay for your duck");
for (uint i = 0; i < numberOfTokens; i++) {
// mint the next avaialble token id
_safeMint(msg.sender, _getNextTokenId());
supply++;
// track that the wallet minted another token!
publicSaleAddressMintedAmount[msg.sender]++;
}
}
// for OpenSea
function tokenURI(uint256 tokenId)
public
view
virtual
override
returns (string memory)
{
require(
_exists(tokenId),
"ERC721Metadata: URI query for nonexistent token"
);
return string(
abi.encodePacked(
isRevealed ? baseURI : placeholderURI,
"/",
tokenId.toString(),
baseExtension
)
);
}
function withdraw() onlyOwner public {
payable(msg.sender).transfer(address(this).balance);
}
function whitelistMintedByAddress() public view returns (uint8) {
return whitelistAddressMintedAmount[msg.sender];
}
function publicMintedByAddress() public view returns (uint8) {
return publicSaleAddressMintedAmount[msg.sender];
}
}
import { ethers } from 'ethers';
import { useEffect } from 'react';
import { useState } from 'react';
export function Connect({ setWalletAddress }) {
const [walletAddress, setLocalWalletAddress] = useState();
if (window.ethereum) {
window.ethereum.on('accountsChanged', function (accounts) {
window.location.reload()
})
window.ethereum.on('networkChanged', function (networkId) {
// console.log(networkId)
// Time to reload your interface with the new networkId
console.log(networkId)
})
}
const requestWalletAddress = async () => {
const [walletAddress] = await window.ethereum.request({ method: 'eth_requestAccounts' });
const provider = new ethers.providers.Web3Provider(window.ethereum);
setLocalWalletAddress(walletAddress);
setWalletAddress(walletAddress);
}
useEffect(() => {
// only if ethereum is already connected, we fetch the address
if (window.ethereum.isConnected()) {
requestWalletAddress();
}
})
// when button is pressed ask to connect
const connectMetamask = async () => {
requestWalletAddress();
};
let view = (<div></div>);
const shortWalletAddress = walletAddress && `${walletAddress.substring(0, 5)}...${walletAddress.substring(walletAddress.length - 4, walletAddress.length)}`
if (walletAddress) {
view = (
<div style={{color: 'white', style: 'inline', textAlign: 'center'}}>
<a onClick={connectMetamask} class="button-icon is-wallet w-inline-block">
<div style={{margin: '0 auto'}}>
<p>
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 4H16V3C16 2.20435 15.6839 1.44129 15.1213 0.87868C14.5587 0.316071 13.7956 0 13 0H3C2.20435 0 1.44129 0.316071 0.87868 0.87868C0.316071 1.44129 0 2.20435 0 3V3V15C0 15.7956 0.316071 16.5587 0.87868 17.1213C1.44129 17.6839 2.20435 18 3 18H17C17.7956 18 18.5587 17.6839 19.1213 17.1213C19.6839 16.5587 20 15.7956 20 15V7C20 6.20435 19.6839 5.44129 19.1213 4.87868C18.5587 4.31607 17.7956 4 17 4ZM3 2H13C13.2652 2 13.5196 2.10536 13.7071 2.29289C13.8946 2.48043 14 2.73478 14 3V4H3C2.73478 4 2.48043 3.89464 2.29289 3.70711C2.10536 3.51957 2 3.26522 2 3C2 2.73478 2.10536 2.48043 2.29289 2.29289C2.48043 2.10536 2.73478 2 3 2V2ZM18 12H17C16.7348 12 16.4804 11.8946 16.2929 11.7071C16.1054 11.5196 16 11.2652 16 11C16 10.7348 16.1054 10.4804 16.2929 10.2929C16.4804 10.1054 16.7348 10 17 10H18V12ZM18 8H17C16.2044 8 15.4413 8.31607 14.8787 8.87868C14.3161 9.44129 14 10.2044 14 11C14 11.7956 14.3161 12.5587 14.8787 13.1213C15.4413 13.6839 16.2044 14 17 14H18V15C18 15.2652 17.8946 15.5196 17.7071 15.7071C17.5196 15.8946 17.2652 16 17 16H3C2.73478 16 2.48043 15.8946 2.29289 15.7071C2.10536 15.5196 2 15.2652 2 15V5.83C2.32127 5.94302 2.65943 6.00051 3 6H17C17.2652 6 17.5196 6.10536 17.7071 6.29289C17.8946 6.48043 18 6.73478 18 7V8Z" fill="currentColor"></path>
</svg>
{" " +shortWalletAddress}
</p>
</div>
</a>
</div>
)
} else {
view = (
<a onClick={() => {connectMetamask()}} class="button-icon is-wallet w-inline-block"><div class="margin-right margin-small"><div class="embed w-embed"><svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 4H16V3C16 2.20435 15.6839 1.44129 15.1213 0.87868C14.5587 0.316071 13.7956 0 13 0H3C2.20435 0 1.44129 0.316071 0.87868 0.87868C0.316071 1.44129 0 2.20435 0 3V3V15C0 15.7956 0.316071 16.5587 0.87868 17.1213C1.44129 17.6839 2.20435 18 3 18H17C17.7956 18 18.5587 17.6839 19.1213 17.1213C19.6839 16.5587 20 15.7956 20 15V7C20 6.20435 19.6839 5.44129 19.1213 4.87868C18.5587 4.31607 17.7956 4 17 4ZM3 2H13C13.2652 2 13.5196 2.10536 13.7071 2.29289C13.8946 2.48043 14 2.73478 14 3V4H3C2.73478 4 2.48043 3.89464 2.29289 3.70711C2.10536 3.51957 2 3.26522 2 3C2 2.73478 2.10536 2.48043 2.29289 2.29289C2.48043 2.10536 2.73478 2 3 2V2ZM18 12H17C16.7348 12 16.4804 11.8946 16.2929 11.7071C16.1054 11.5196 16 11.2652 16 11C16 10.7348 16.1054 10.4804 16.2929 10.2929C16.4804 10.1054 16.7348 10 17 10H18V12ZM18 8H17C16.2044 8 15.4413 8.31607 14.8787 8.87868C14.3161 9.44129 14 10.2044 14 11C14 11.7956 14.3161 12.5587 14.8787 13.1213C15.4413 13.6839 16.2044 14 17 14H18V15C18 15.2652 17.8946 15.5196 17.7071 15.7071C17.5196 15.8946 17.2652 16 17 16H3C2.73478 16 2.48043 15.8946 2.29289 15.7071C2.10536 15.5196 2 15.2652 2 15V5.83C2.32127 5.94302 2.65943 6.00051 3 6H17C17.2652 6 17.5196 6.10536 17.7071 6.29289C17.8946 6.48043 18 6.73478 18 7V8Z" fill="currentColor"></path>
</svg></div></div><div class="padButton">Connect Wallet</div></a>
)
}
return view;
}
import 'animate.css';
import { useEffect, useState } from 'react';
import { Connect } from './Connect';
import { DuckCounter } from './DuckCounter';
import YellowDuckCollection from '../Assets/Contracts/YellowDuckCollection.json';
import axios from 'axios';
import { ethers } from 'ethers';
import usePortal from 'react-useportal';
function Home() {
const contractAddress = '0x088C09a1FcC6B4EeA127341EF292386320D26aD3';
const SIGNING_SERVER_ROOT = "https://us-central1-yellowduck-337518.cloudfunctions.net"
const provider = new ethers.providers.Web3Provider(window.ethereum);
// get the end user
const signer = provider.getSigner();
// get the smart contract
const contract = new ethers.Contract(contractAddress, YellowDuckCollection.abi, signer);
const PHASE_YELLOWLIST = 0;
const PHASE_WHITELIST = 1;
const PHASE_PUBLICSALE = 2;
const PHASE_CLOSED = 3;
// MetaMask Wallet Address
const [walletAddress, setWalletAddress] = useState("");
// Yellowlist/Whitelist/Public State
const [phase, setPhase] = useState(-1);
// Counter
const [counter, setCounter] = useState(1);
// Status, leave blank
const [status, setStatus] = useState('')
// Cost
const [cost, setCost] = useState('')
// Ducks Minted
const [ducksMinted, setDucksMinted] = useState(0);
// total user can mint
const [canMintAmount, setCanMintAmount] = useState(0);
// Minted
const [mintedAmount, setMintedAmount] = useState(0)
useEffect(() => {
web3GetSaleState();
}, [walletAddress]);
const [costOfMint, setCostOfMint] = useState(0);
useEffect(() => {
setCostOfMint(ethers.utils.parseUnits('' + (cost * counter), 'ether'));
}, [cost, counter]);
useEffect(() => {
if (phase === PHASE_YELLOWLIST) {
if (mintedAmount) {
setCanMintAmount(0);
} else {
setCanMintAmount(1);
}
} else if (phase === PHASE_WHITELIST) {
setCanMintAmount(3 - mintedAmount);
} else if (phase === PHASE_PUBLICSALE) {
setCanMintAmount(5 - mintedAmount);
}
}, [mintedAmount, phase]);
useEffect(() => {
if (phase === PHASE_CLOSED) {
setStatus("Public Sale has been closed");
}
else if (canMintAmount) {
setStatus(
phase === PHASE_YELLOWLIST
? "Welcome to the #YellowList."
: "You can mint up to " + canMintAmount + " duck(s)."
);
} else {
if (phase === PHASE_YELLOWLIST) {
setStatus("You have minted your #YellowList!")
} else if (phase === PHASE_WHITELIST) {
setStatus("You have hit the mint limit for our whitelist. Check in later for the public sale!");
} else if (phase === PHASE_PUBLICSALE) {
setStatus("You have minted all of your YellowDucks.");
}
}
}, [canMintAmount, phase]);
const web3GetSaleState = async () => {
try {
const phase = await contract.saleState()
const costOfOneMint = await contract.cost()
const ducksMinted = await contract.supply()
const mintSinglePrice = ethers.utils.formatEther(costOfOneMint)
const ducksMintedFriendly = ethers.utils.formatEther(''+ducksMinted) * 1000000000000000000
setDucksMinted(ducksMintedFriendly);
setCost(mintSinglePrice)
setPhase(phase);
if(!walletAddress) return;
let localStorageMinted;
let contractMinted;
switch (phase) {
case PHASE_YELLOWLIST:
setMintedAmount(parseInt(localStorage.getItem(`${walletAddress}-yellowlistMinted`)) || 0);
break;
case PHASE_WHITELIST:
localStorageMinted = parseInt(localStorage.getItem(`${walletAddress}-whiteMinted`)) || 0;
setMintedAmount(localStorageMinted);
contractMinted = await contract.whitelistMintedByAddress() || 0;
// the contract is sometimes behind. If the number is bigger on contract they probably
// used another device to mint, so we use that
if (contractMinted > localStorageMinted) {
setMintedAmount(contractMinted);
}
break;
case PHASE_PUBLICSALE:
localStorageMinted = parseInt(localStorage.getItem(`${walletAddress}-publicMinted`)) || 0;
setMintedAmount(localStorageMinted);
contractMinted = await contract.publicMintedByAddress() || 0;
// the contract is sometimes behind. If the number is bigger on contract they probably
// used another device to mint, so we use that
if (contractMinted > localStorageMinted) {
setMintedAmount(contractMinted);
}
break;
case PHASE_CLOSED:
setStatus("");
break;
default:
setStatus("The minting is in an unknown state.");
break;
}
} catch (e) {
console.log(e)
}
}
const catchErrors = (e) => {
if (e['message']) {
if (e['message'].includes('Signature has already')) {
setStatus("You have already minted your duck(s)! Please check your OpenSea collection.")
setMintedAmount(1)
}
else if (e['message'].toString().includes('insufficient')) {
setStatus("You must have at least " + (+ethers.utils.formatEther(costOfMint)).toFixed(2) + " ETH to mint " + counter + " YellowDuck(s)!")
}
else {
setStatus(e['message'])
}
} else {
setStatus("An unknown error has occurred.")
}
}
const web3PerformYellowlistMint = async () => {
try {
const {
tokenId,
signature
} = (await axios.get(`${SIGNING_SERVER_ROOT}/getYellowlistSignature`, {
params: {
walletAddress
}
})).data;
try {
console.log(costOfMint);
const mint = await contract.yellowlistMint(tokenId, signature, { value: costOfMint });
setMintedAmount(1);
setDucksMinted(ducksMinted + 1);
localStorage.setItem(`${walletAddress}-yellowlistMinted`, 1)
alert("Congratulations! You have minted your #YellowList! Please wait for up to 60 seconds for your YellowDuck to arrive in your wallet.");
await web3GetSaleState();
} catch (e) {
catchErrors(e);
}
} catch (e) {
alert("You need to be listed for a 1-to-1 mint by completing a duck challenge.")
}
}
const web3PerformWhitelistMint = async () => {
try {
const {
signature
} = (await axios.get(`${SIGNING_SERVER_ROOT}/getWhitelistSignature`, {
params: {
walletAddress
}
})).data;
//this value must match the smart contract!
try {
const mint = await contract.whitelistMint(signature, counter, { value: costOfMint });
setDucksMinted(ducksMinted + counter);
alert("Congratulations! You have successfully minted! Please wait for up to 60 seconds for your YellowDuck(s) to arrive in your wallet.");
const newMintedAmount = counter + mintedAmount;
setMintedAmount(newMintedAmount);
localStorage.setItem(`${walletAddress}-whiteMinted`, newMintedAmount);
} catch (e) {
catchErrors(e);
}
} catch (e) {
alert("You are not currently whitelisted!");
}
}
const web3PerformPublicMint = async () => {
try {
//this value must match the smart contract!
const mint = await contract.publicMint(counter, { value: costOfMint });
setDucksMinted(ducksMinted + counter);
alert("Congratulations! You have minted your YellowDuck(s)! Please wait for up to 60 seconds for your YellowDuck(s) to arrive in your wallet.");
const newMintedAmount = counter + mintedAmount;
setMintedAmount(newMintedAmount);
localStorage.setItem(`${walletAddress}-publicMinted`, newMintedAmount);
} catch (e) {
catchErrors(e);
}
}
// <h3 className="heading-status">Welcome to YellowDuck NFT Minting!</h3>
let statusInfo = (
<h3 className="heading-status"></h3>
)
let mintButton = null;
if (walletAddress) {
switch (phase) {
case PHASE_YELLOWLIST:
statusInfo = (
<h3 className="heading-status">Yellowlist Sale</h3>
)
if (!mintedAmount) {
mintButton = (
<div key={1}>
<button className="minting-button" onClick={web3PerformYellowlistMint}>Mint!</button>
</div>
)
}
break;
case PHASE_WHITELIST:
statusInfo = (
<h3 className="heading-status">Whitelist Sale</h3>
)
mintButton = (
<div key={1}>
<button className="minting-button" onClick={web3PerformWhitelistMint}>Mint!</button>
</div>
)
break;
case PHASE_PUBLICSALE:
statusInfo = (
<h3 className="heading-status">Public Sale</h3>
)
mintButton = (
<div key={1}>
<button className="minting-button" onClick={web3PerformPublicMint}>Mint!</button>
</div>
)
break;
case PHASE_CLOSED:
statusInfo = (
<h3 className="heading-status">Sold Out</h3>
)
break;
default:
break;
}
}
const incrementCounter = () => {
setCounter(Math.min(counter + 1, canMintAmount));
};
const decrementCounter = () => {
setCounter(Math.max(1, counter - 1));
}
const boxCounter = (
<div className="box-items">
<div className="box">
<ButtonDecrement onClickFunc={() => { decrementCounter(); } } />
<Display message={counter} />
<ButtonIncrement onClickFunc={() => { incrementCounter(); }} />
</div>
</div>
);
const { Portal } = usePortal({
bindTo: document.querySelector('.connect-button')
});
const { Portal:DuckCounterPortal } = usePortal({
bindTo: document.querySelector('#duck-counter')
});
return (
<>
<Portal>
<Connect setWalletAddress={setWalletAddress} />
</Portal>
<DuckCounterPortal>
<DuckCounter counter={ducksMinted} />
</DuckCounterPortal>
<div>{statusInfo}</div>
{(canMintAmount > 0) && (
<div className="box">
{[PHASE_PUBLICSALE, PHASE_WHITELIST].includes(phase) && (
boxCounter
)}
{mintButton && (
<div className="box-items">
{mintButton}
</div>
)}
{/* <div className="box-items">
<div className="heading-status">
<a className="heading-status" target="_blank" href="https://testnets.opensea.io/account">Link to OpenSea Collection</a>
</div>
</div> */}
</div>)}
<p className="mint-status" style={{ textAlign: 'center' }}>{status}</p>
</>
);
}
function ButtonIncrement(props) {
return (
<button className="mint-button-inc" onClick={props.onClickFunc}>
+
</button>
)
}
function ButtonDecrement(props) {
return (
<button className="mint-button-dec" onClick={props.onClickFunc}>
-
</button>
)
}
function Display(props) {
return (
<label className="mint-num-val" >{props.message}</label>
)
}
export default Home;
import * as functions from "firebase-functions";
const ethers = require('ethers');
const dotenv = require('dotenv');
dotenv.config();
const Firestore = require('@google-cloud/firestore')
const cors = require('cors');
const db = new Firestore();
// for signatures
const wallet = new ethers.Wallet(process.env.ETH_PRIVATE_KEY);
export const getYellowlistSignature = functions.https.onRequest(async (request, response) => {
cors()(request, response, async () => {
const walletAddress = request.query.walletAddress as string;
if (!walletAddress) {
response.status(400).send('walletAddress Required');
return;
}
const yellowListDoc = await db.collection('yellowlist').doc(walletAddress.toLowerCase()).get();
if (yellowListDoc.exists) {
const tokenId = yellowListDoc.data().tokenId;
const msg = '' + tokenId;
const signature = await wallet.signMessage(msg);
response.send({
signature,
tokenId
});
} else {
response.status(401).send('You are not allowed to mint yellowlist tokens. Shame....')
};
})
});
export const getWhitelistSignature = functions.https.onRequest(async (request, response) => {
cors()(request, response, async () => {
let walletAddress = request.query.walletAddress as string;
if (!walletAddress) {
response.status(400).send('walletAddress Required');
return;
}
const whiteListDoc = await db.collection('whitelist').doc(walletAddress.toLowerCase()).get();
if (whiteListDoc.exists) {
// address of the wallet who is allowed to mint (must be lowercase)
const msg = walletAddress.toLowerCase();
const signature = await wallet.signMessage(msg);
response.json({
signature
});
} else {
response.status(401).send('You are not allowed to mint whitelist tokens. Shame....')
}
});
});
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-etherscan");
require("@nomiclabs/hardhat-ethers");
const dotenv = require('dotenv');
dotenv.config();
require('./scripts/deploy');
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.7",
paths: {
artifacts: './src/artifacts',
},
networks: {
rinkeby: {
url: "https://rinkeby-light.eth.linkpool.io/",
accounts: [process.env.ETH_PRIVATE_KEY]// add the account that will deploy the contract (private key)
},
},
// support for verifying my contract
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
const dotenv = require("dotenv");
dotenv.config();
task("deploy", "Deploys the collection")
.setAction(async (a, hre) => {
const constructorArguments = [
"YellowDuckCollection",
"YDV09B"
];
const YellowDuck = await hre.ethers.getContractFactory("YellowDuckCollection");
const yellowDuck = await YellowDuck.deploy(...constructorArguments);
await yellowDuck.deployed();
console.log("YellowDuck deployed to:", yellowDuck.address);
console.log('Waiting 60s to verify')
await new Promise ((resolve) => {
setTimeout(async () => {
await hre.run('verify:verify', {
address: yellowDuck.address,
constructorArguments,
});
resolve();
}, 60 * 1000);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment