前提
该项目是我这段时间的一个学习心得,一个简单的以太坊的NFT demo项目,该代码仅供学习,在学习前,希望你已经熟悉了solidity、eth、ipfs等技术。
该项目简单的总结是:利用ipfs存储图片和图片的json信息,然后将图片/json的ipfs 地址传入solidity合约中进行售卖,其中利用到了ERC721非同质化协议。
值得一提的是你买到的作品不是属于你的,因为作品在ipfs上,你没有ipfs的账号权限那你并没有NFT作品的实质拥有权。所以请要购买nft的同学小心。
安装
-
Metamask 安裝 -
https://metamask.io/ -
Rinkeby faucet 測試幣領取 -
https://faucets.chain.link/rinkeby
https://fauceth.komputing.org/
https://faucet.rinkeby.io/ -
ETH 線上編輯器 -
https://remix.ethereum.org/ -
Nic Meta 智能合約範例 -
https://github.com/niclin/nic_meta/blob/master/contracts/nic_meta_nft.sol -
ETH 轉 Gwei -
https://eth-converter.com/ -
OpenSea 測試網路 -
https://testnets.opensea.io/ -
HashLips 組圖專案 -
https://github.com/HashLips/hashlips_art_engine -
IPFS 上傳空間 -
https://www.pinata.cloud/
OpenSea
OpenSea是一個總部位於美國紐約市的非同質化代幣在线交易市场[1]。截止2022年1月,該公司的估值為 133億美元,被認為是最大的非同質化代幣交易平台。
代码
// Contract based on https://docs.openzeppelin.com/contracts/3.x/erc721
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract NicMeta is ERC721Enumerable, Ownable {
using Strings for uint256;
// 是否开始售卖
bool public _isSaleActive = false;
// 是否展示
bool public _revealed = false;
// Constants
// 最大支持NFT数
uint256 public constant MAX_SUPPLY = 10;
// nft发布交易的最小值
uint256 public mintPrice = 0.01 ether;
// 最大金额
uint256 public maxBalance = 1;
// 最大发型数
uint256 public maxMint = 1;
// 基础路径,后面要放nft图片
string baseURI;
// 不显示图片路径
string public notRevealedUri;
// 展示的数据结尾:.json
string public baseExtension = ".json";
mapping(uint256 => string) private _tokenURIs;
// ERC721("Nic Meta", "NM") 创建nft 的名字和简称
constructor(string memory initBaseURI, string memory initNotRevealedUri)
ERC721("Nic Meta", "NM")
{
// 初始化图片的路径
setBaseURI(initBaseURI);
setNotRevealedURI(initNotRevealedUri);
}
function mintNicMeta(uint256 tokenQuantity) public payable {
// 进行判断你所挖的NFT是否大于最大数
require(
totalSupply() + tokenQuantity <= MAX_SUPPLY,
"Sale would exceed max supply"
);
// 必须先开启售卖才能进行购买
require(_isSaleActive, "Sale must be active to mint NicMetas");
// 判断金额大小
require(
balanceOf(msg.sender) + tokenQuantity <= maxBalance,
"Sale would exceed max balance"
);
// 发送的购买金额ETH是低于售卖价格
require(
tokenQuantity * mintPrice <= msg.value,
"Not enough ether sent"
);
require(tokenQuantity <= maxMint, "Can only mint 1 tokens at a time");
// 开始mint
_mintNicMeta(tokenQuantity);
}
function _mintNicMeta(uint256 tokenQuantity) internal {
// 进行售卖,查找有没有合适的NFT
for (uint256 i = 0; i < tokenQuantity; i++) {
uint256 mintIndex = totalSupply();
if (totalSupply() < MAX_SUPPLY) {
// ERC721 下的方法,进行安全铸造NFT,并且转给合约调用者
_safeMint(msg.sender, mintIndex);
}
}
}
function tokenURI(uint256 tokenId)
public
view
virtual
override
returns (string memory)
{
require(
_exists(tokenId),
"ERC721Metadata: URI query for nonexistent token"
);
if (_revealed == false) {
return notRevealedUri;
}
string memory _tokenURI = _tokenURIs[tokenId];
string memory base = _baseURI();
// If there is no base URI, return the token URI.
if (bytes(base).length == 0) {
return _tokenURI;
}
// If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
if (bytes(_tokenURI).length > 0) {
return string(abi.encodePacked(base, _tokenURI));
}
// If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
return
string(abi.encodePacked(base, tokenId.toString(), baseExtension));
}
// internal
function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}
//only owner
function flipSaleActive() public onlyOwner {
_isSaleActive = !_isSaleActive;
}
function flipReveal() public onlyOwner {
_revealed = !_revealed;
}
function setMintPrice(uint256 _mintPrice) public onlyOwner {
mintPrice = _mintPrice;
}
function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
notRevealedUri = _notRevealedURI;
}
function setBaseURI(string memory _newBaseURI) public onlyOwner {
baseURI = _newBaseURI;
}
function setBaseExtension(string memory _newBaseExtension)
public
onlyOwner
{
baseExtension = _newBaseExtension;
}
function setMaxBalance(uint256 _maxBalance) public onlyOwner {
maxBalance = _maxBalance;
}
function setMaxMint(uint256 _maxMint) public onlyOwner {
maxMint = _maxMint;
}
function withdraw(address to) public onlyOwner {
uint256 balance = address(this).balance;
payable(to).transfer(balance);
}
}
Remix操作合约
remix 地址:https://remix.ethereum.org/
-
将合约写入
-
编译合约
-
连接小狐狸并部署合约
我部署的是rinkeby网络
最后进行deploy 部署合约
-
运行合约进行 mint
先设置金额,就是你要花多少钱购买,在这个位置可以传入金额。
调用合约里的互动方法
-
打开测试网的opensea 查看你的nft
这里的opensea要先连接小狐狸才能进行查看
到这,如果看到了这个图片就是成了,只是图片没有,因为我们还没开始进行IPFS部署图片
IPFS部署图片
生成图片
打开https://github.com/HashLips/hashlips_art_engine,克隆项目,这需要你有前端编程技术,如果没有,那可以结束了。
下载后,src/main.js 这里修改为0
进行build
ta会帮你建立很多图片
到时候我们传上ipfs上的就是build文件里的images和json文件夹
上传到IPFS
打开https://www.pinata.cloud/,上传build文件里的images文件夹
得到地址
将config.js文件的路径更换成我们的IPFS地址,如图第十行
然后更新
打开https://www.pinata.cloud/,上传build文件里的json文件夹里的文件
和上面的图片文件夹一样得到一个ipfs地址
创建默认图片路径
先复制上图图片到新目录下,我创建的目录名为unpack,图片重命名为unpack.png,如图:
和上面一样,上传unpack到ipfs空间。
新增unpack.json 文件,里面的内容如图:
image填写的是刚上传的unpack.png的ipfs地址。
上传unpack.json到ipfs
返回remix, 在互动setNotRevealedURI里填写unpack.json的ipfs
在setBaseURI填写unpack.png的ipfs
结尾要加上"/"
再打开opensea,如果图片变了就是成功了。