We will use some OpenZeppelin
contracts and that is why we need first to installed them. Brownie comes with package manager we can use for this purpose. Universal sintax is brownie pm install [ORGANIZATION]/[REPOSITORY]@[VERSION]
in our case
$brownie pm install OpenZeppelin/openzeppelin-contracts@4.5.0
#check if package is properly installed
$brownie pm list
Brownie v1.19.3 - Python development framework for Ethereum
The following packages are currently installed:
OpenZeppelin
└─OpenZeppelin/openzeppelin-contracts@4.5.0
Now in our nft_package.sol
test contract try to import newly installed OpenZeppelin contract ERC721. Open nft_package.sol
from ./contracts
// SPDX-License-Identifier: MIT
# Here we are importing newly installed ERC721 contract
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/ERC721.sol";
#defining compiler
pragma solidity 0.8.13;
contract NftPackage {
uint256 public test = 10;
}
From command line:
$brownie compile
Brownie v1.19.3 - Python development framework for Ethereum
Compiling contracts...
Solc version: 0.8.13
Optimizer: Enabled Runs: 200
EVM Version: Istanbul
Generating build data...
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Receiver
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Metadata
- OpenZeppelin/openzeppelin-contracts@4.5.0/Address
- OpenZeppelin/openzeppelin-contracts@4.5.0/Context
- OpenZeppelin/openzeppelin-contracts@4.5.0/Strings
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC165
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC165
- NftPackage
Project has been compiled. Build artifacts saved at /my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts
We successfully installed and imported OpenZepplein ERC721 inside our contract and now we can start to code our Muscial NFT contract.
Idea here is to have musical NFT based on ERC721
contract with few simple functionalities like minting and adding tokenURI. Then we will inherit from few other OpenZepellin
contracts like ownable
(track and manage via modifier who have right to call certain functions), counters
(count number of newly minted tokens) and URIStorage
(linking our newly minted NFT to NFT metadata on IPFS).
I will write whole contract here and explain with inline comments what is going on (very useful tool for this kind of basic contracts and experimentation is OpenZepelinWizard)
We will deploy two main contracts: MockUSDC and MusicNFT. MockUSDC is basically ERC20 that mimic USDC on Mumbai testnet. Reason why we don't use real test USDC is fact that is very hard to obtain them on Mumbai and MockUSDC will play that role just fine. Second contract is ERC721 where we simply mint new musical NFTs and assigne new URI. Here are contracts (with explanations inline).
ERC20 MockUSDC
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC20/ERC20.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/access/Ownable.sol";
contract MockUSDC is ERC20, Ownable {
// Mint to deoployer all MUSDC tokens
constructor() ERC20("MockUSDC", "MUSDC") {
_mint(msg.sender, 1000000000000000 * 10 ** decimals());
}
}
From command line
$brownie compile
Brownie v1.19.3 - Python development framework for Ethereum
Compiling contracts...
Solc version: 0.8.21
Optimizer: Enabled Runs: 200
EVM Version: Istanbul
Generating build data...
- OpenZeppelin/openzeppelin-contracts@4.5.0/Ownable
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC20
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20Metadata
- OpenZeppelin/openzeppelin-contracts@4.5.0/Context
- MockUSDC
Project has been compiled. Build artifacts saved at /home/ilija/code/my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts
We have our mock token ready to be used
And here is MusicNFT contracts
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/ERC721.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/access/Ownable.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/utils/Counters.sol";
import "OpenZeppelin/openzeppelin-contracts@4.5.0/contracts/token/ERC20/IERC20.sol";
contract MusicNFT is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
IERC20 public immutable USDC;
uint256 public immutable maxNFT;
uint256 public NFTPriceInUSDC;
// Event to be emited ones we mint new NFT token
event newNFTMinted(uint256 numberOfNFT, address owner);
// Event to be emited ones new price is setted
event newPrice(string message, uint256 newPrice);
constructor(address _usdc, uint256 _maxNft) ERC721("MuscialNFT", "MFT") {
// instatiating mock ERC20 token (that we previusly deployed)
USDC = IERC20(_usdc);
// Defining maximum number of tokens this contract cna mint
maxNFT = _maxNft;
}
/**
This function is used for minting new NFTs. We need to pass token uri (uniform resource
Identifier) from IPFS (via Pinata). Metadata of some NFT is basically
detail explanation of NFT content: characteristic, link to song in our
case and other relevant information. Second argument that we need to pass is number of
NFTs we want to buy
*/
// For crypto buyers
function buyNFT(string memory uri, uint256 nftCount) public {
// Here we are cheking if desired number of NFTs is bigger then one
require(nftCount > 0, "You have to mint at least one Track Pack NFT.");
// Here we are cheking if number of already minted NFT is bigger then max allowed to be minted
require(nftCount + _tokenIdCounter.current() <= maxNFT, "There aren't enough Track Pack NFTs for this drop for you to mint you amount you chose. Another drop will be available soon!");
// Here we check if user balance in MockUSDC is bigger the number NFTs he want to buy * price of NFTs
require(USDC.balanceOf(msg.sender) >= NFTPriceInUSDC * nftCount, "You don't have enough USDC to pay for the Track Pack NFT(s).");
// Check if total amount of approved MOckUSDC to this contract tokens is bigger then number of NFTs * price
require(USDC.allowance(msg.sender, address(this)) >= NFTPriceInUSDC * nftCount, "You haven't approved this contract to spend enough of your USDC to pay for the Track Pack NFT(s).");
// If everything goes ok then make MockUSDC tokens transfer from user account to this contract
USDC.transferFrom(msg.sender, address(this), NFTPriceInUSDC * nftCount);
// Take new NFT token ID
for (uint256 x = 0; x < nftCount; x++) {
uint256 tokenId = _tokenIdCounter.current();
// Increment counter
_tokenIdCounter.increment();
// Mint new token
_safeMint(msg.sender, tokenId);
// Set new token URI to token ID
_setTokenURI(tokenId, uri);
// Emit event about succesful minting
emit newNFTMinted(tokenId, msg.sender);
}
}
// When credit card buyers buy new NFT we need to mint to custodial wallet. Same as beafore. Diffrence is: onnly owner can mint, 2. there is no need to pay in usdc
// it is already done by credit card.
function createNFT(address custodialWallet, string memory uri) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(custodialWallet, tokenId);
_setTokenURI(tokenId, uri);
emit newNFTMinted(tokenId, custodialWallet);
}
// This function is used to set new NFT price if necessary
function setNFTPrice(uint256 _newPrice) public onlyOwner {
NFTPriceInUSDC = _newPrice;
emit newPrice("New price is seted", _newPrice);
}
// Get token URI function
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
// The following functions are overrides required by Solidity.
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}
}
Now we have also our second contract written. Lets compile it and see if everything works well
$brownie compile
Brownie v1.19.3 - Python development framework for Ethereum
Compiling contracts...
Solc version: 0.8.21
Optimizer: Enabled Runs: 200
EVM Version: Istanbul
Generating build data...
- OpenZeppelin/openzeppelin-contracts@4.5.0/Ownable
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC20
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Receiver
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC721URIStorage
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC721Metadata
- OpenZeppelin/openzeppelin-contracts@4.5.0/Address
- OpenZeppelin/openzeppelin-contracts@4.5.0/Context
- OpenZeppelin/openzeppelin-contracts@4.5.0/Counters
- OpenZeppelin/openzeppelin-contracts@4.5.0/Strings
- OpenZeppelin/openzeppelin-contracts@4.5.0/ERC165
- OpenZeppelin/openzeppelin-contracts@4.5.0/IERC165
- MusicNFT
Project has been compiled. Build artifacts saved at /home/ilija/code/my_tutorials/musical_nft_thumbnails/smart-contracts/brownie_musical_nfts/build/contracts
With this two contract we can move to deployment to Mumbai and start with integration with our Django backend.
Code can be found in github repo
Top comments (0)