Loopstudio Collectibles
Created and maintained with
Description
Loopstudio Collectibles is an NFT collection crafted by Loopstudio developers and designers
Art was made by one of our incredible designers, Facundo Astete, and his drawings represents a different role at Loopstudio:
- Project Manager
- Developer
- UX/UI Designer
- QA
- Office Manager
Each role, in conjuntion with different traits, define an unique NFT that represents a colaborator of Loopstudio:
- Seniority
- Role
- Hobby
- Does it drinks coffe or Mate?
Technical Details
Stack
- Solidity 0.8.15
- Hardhat
- hardhat-deploy
- hardhat-ethers
- hardhat-etherscan
- Openzeppelin Contracts
- ERC721URIStorage
- Ownable
- Chainlink Contracts
- VRFConsumerBaseV2
- Typescript & Typechain
- Pinnata SDK (IPFS)
Project Structure
.
├── contracts/
├── deploy/
├── deployments
├── tasks
├── typechain-types
├── utils
├── test
├── hardhat.config.ts
├── helper-hardhat-config.ts
What does each folder represents?
- contracts: where the
LoopNFT.sol
is placed - deploy:
hardhat-deploy
scripts to deploy to localhost, using mock contracts, testnet and mainnet. The scripts also verifies contract source code toetherscan
and publish the NFTmetadata
toIPFS
usingpinata-sdk
- deployment: information about old deploys and ABI, generated by
hardhat-deploy
- tasks: hardhat tasks to interact with the
LoopNFT.sol
contract after its deployed to a network - typechain-types: types definitions for each contract generated by
typechain
- utils: images/assets, metadata about the NFT items and functions to upload information to
IPFS
and verify sourcecode againstetherscan
- test: unit and staging tests
- hardhat.config.ts: hardhat configuration file. Currenlty supports
ethereum
andpolygon
networks configuration. - helper-hardhat-config.ts: helper file containing information per network, like # of confirmations, Chainlink smart contract addresses and subscriptions.
Use Case
We wanted to represent the following usecase:
- The contract is deployed and a limited amount of unique items are available to be minted.
- Initially, the collection is empty, this means, the owner of the contract has no control of any NFT.
- Any EOA can retrieve one or more of the limited unique items by interacting with the
mint
function and paying the gas fees. - The contract obtains a random item based on a random number and assign it to the minter address
- When there are no items left, the contract reverts the following
mint
transactions
How did we solve it?
We follow the following approach to bootstrap 70 unique items and ensure that the mint
was randomic:
- The contract constructor receives
characterUris
, an array of NFT metadata following the ERC721 Metadata Standards. Metadata was previously stored onIPFS
usingpinata sdk
to ensure its completely descentralized. - We used Chainlink VRF for random numbers generation, thus
LoopNFT
inherits fromVRFConsumerBaseV2
- Once the contract is deployed, it address is added as a VRF subscription consumer, this means that we needed to fund the contract address with
$LINK
- Since we didnt want to charge the minter with extra fees of random requesting, an
onlyOnwer initializeRandoms
function is invoked by the deployer to fulfill an array of random numbers. The size of this array is exactly the same as the different unique items that the collection provides. - Each time
mint
is called we take the latest random number and we divide it by the modulus of thecharacterUris
aray size to get the current charactertokenUri
to mint. We associate thattokenUri
with thetokenId
that is being minted usingERC721URIStorage
_setTokenUri(tokenId, tokenUri)
method. Since both the random number and the tokenUri were used, we removed them from the contract storage.
Getting started
Config
- Copy
.env.example
to.env
- Fullfil the following properties:
GOERLI_URL=[INFURA OR ALCHEMY RPC URL]
MUAMBAI_URL=[INFURA OR ALCHEMY RPC URL] // Optional: for polygon deployment
PRIVATE_KEY=[DEPLOYER_PRIVATE_KEY]
PRIVATE_KEY_2=[ANOTHER_ACCOUNT_PRIVATE_KEY] // Optional
REPORT_GAS=true
ETHERSCAN_API_KEY=[YOUR_ETHERSCAN_API_KEY]
VERIFY_CONTRACT=false
COINMARKETCAP_API_KEY=[YOUR_COINMARKETCAP_API_KEY] // Optional: only needed by hardhat-gas-reporter.
PINATA_API_KEY=[YOUR_PINATA_API_KEY]
PINATA_API_SECRET=[YOUR_PINATA_API_SECRET]
UPLOAD_TO_PINATA=true
Deploy
npm install
oryarn install
- Deploy to localhost
yarn hardhat deploy
- Deploy to ethereum testnet
yarn hardhat deploy --network goerli
- Deploy to polygon testnet
yarn hardhat deploy --network muambai
Interact
Since the contracts are verified by default using etherscan
you can just interact with your contract on the etherscan
UI, i.e https://etherscan.io/token/0x271682DEB8C4E0901D1a1550aD2e64D568E69909#readContract
Otherwise, we provide hardhat tasks
to interact with LoopNFT
:
- After deployed, random numbers must be fulfilled:
yarn hardhat initializeRandoms --ca {contract_address} --network {goerli|localhost}
- Check random values after 6 confirmations:
yarn hardhat getRandomValues --ca {contract_address} --id 0 --network {goerli|localhost}
- Then, new items can be minted:
yarn hardhat mintLoopNFT --ca {contract_address} --network {goerli|localhost}
- Check
tokenCounter
by running;yarn hardhat getTokenCounter --ca {contract_address} --network {goerli|localhost}
Test
Tests are separated between unit
and staging
tests.
unit
tests are for testing LoopNFT public API meanwhile staging
tests are for running on testnet with:
- an already deployed LoopNFT contract.
- an already configured Chainlink VRF subscription
Commands:
- Unit:
npx hardhat test
- Staging:
npx hardhat test --network goerli
- Coverage:
npx hardhat coverage
OpenSea
After deployed and minted you should find your items on OpeanSea: