Dapp开发实战(去中心化NFT交易平台)

在以太坊上建立数字市场 在本次实战所使用的技术是React, Next.js, Tailwind CSS, RemixIDE, Solidity, Ethers.
编写智能合约
市场将由两个主要的智能合约组成:
用于铸造 NFT的NFT合约 和 促进 NFT销售的市场合约
为了编写 NFT,我们可以使用OpenZeppelin获得的ERC721标准。
参考:https://docs.openzeppelin.com/contracts/4.x/erc721

contract NFT is ERC721URIStorage { using Counters for Counters.Counter; Counters.Counter private _tokenIds; address contractAddress; constructor(address marketplaceAddress) ERC721("Xia Blocks NFTs", "XBNFT") { contractAddress = marketplaceAddress; }//合约可以允许市场合约批准将代币从所有者转移到卖家 function createToken(string memory tokenURI) public returns (uint) { _tokenIds.increment(); uint256 newItemId = _tokenIds.current(); _mint(msg.sender, newItemId); _setTokenURI(newItemId, tokenURI); setApprovalForAll(contractAddress, true); return newItemId; } }

编写市场合约
继承ReentrancyGuard:有助于防止对函数的可重入调用的合同模块
// SPDX-License-Identifier: XXL pragma solidity ^0.8.3; import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract NFTMarket is ReentrancyGuard { using Counters for Counters.Counter; Counters.Counter private _itemIds; Counters.Counter private _itemsSold; uint[] marketItems; //存储记录 struct MarketItem { uint itemId; address nftContract; uint256 tokenId; address payable seller; address payable owner; uint256 price; }mapping(uint256 => MarketItem) private idToMarketItem; event MarketItemCreated ( uint indexed itemId, address indexed nftContract, uint256 indexed tokenId, address seller, address owner, uint256 price ); function getMarketItem(uint256 marketItemId) public view returns (MarketItem memory) { return idToMarketItem[marketItemId]; }function createMarketItem( address nftContract, uint256 tokenId, uint256 price ) public payable nonReentrant { require(price > 0, "Price must be at least 1 wei"); _itemIds.increment(); uint256 itemId = _itemIds.current(); marketItems.push(itemId); idToMarketItem[itemId] =MarketItem( itemId, nftContract, tokenId, payable(msg.sender), payable(address(0)), price ); IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId); emit MarketItemCreated( itemId, nftContract, tokenId, msg.sender, address(0), price ); }function createMarketSale( address nftContract, uint256 itemId ) payable public { uint price = idToMarketItem[itemId].price; uint tokenId = idToMarketItem[itemId].tokenId; require(msg.value =https://www.it610.com/article/= price,"Please submit the asking price in order to complete the purchase"); idToMarketItem[itemId].seller.transfer(msg.value); IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId); idToMarketItem[itemId].owner = payable(msg.sender); _itemsSold.increment(); }function fetchMarketItem(uint itemId) public view returns (MarketItem memory) { MarketItem memory item = idToMarketItem[itemId]; return item; } //返回所有仍在销售的市场商品 function fetchMarketItems() public view returns (MarketItem[] memory) { uint itemCount = _itemIds.current(); uint unsoldItemCount = _itemIds.current() - _itemsSold.current(); uint currentIndex = 0; MarketItem[] memory items = new MarketItem[](unsoldItemCount); for (uint i = 0; i < itemCount; i++) { if (idToMarketItem[i + 1].owner == address(0)) { uint currentId = idToMarketItem[i + 1].itemId; MarketItem storage currentItem = idToMarketItem[currentId]; items[currentIndex] = currentItem; currentIndex += 1; } }return items; } //返回用户已购买的NFT function fetchMyNFTs() public view returns (MarketItem[] memory) { uint totalItemCount = _itemIds.current(); uint itemCount = 0; uint currentIndex = 0; for (uint i = 0; i < totalItemCount; i++) { if (idToMarketItem[i + 1].owner == msg.sender) { itemCount += 1; } } MarketItem[] memory items = new MarketItem[](itemCount); for (uint i = 0; i < totalItemCount; i++) { if (idToMarketItem[i + 1].owner == msg.sender) { uint currentId = idToMarketItem[i + 1].itemId; MarketItem storage currentItem = idToMarketItem[currentId]; items[currentIndex] = currentItem; currentIndex += 1; } } return items; } }

编写前端
Dapp开发实战(去中心化NFT交易平台)
文章图片

Dapp开发实战(去中心化NFT交易平台)
文章图片

Home页面:使用web3Modal加载钱包连接以太坊网络
调用合约函数fetchMarketItems加载页面
const [nfts, setNfts] = useState([]) const [loaded, setLoaded] = useState('not-loaded') useEffect(() => { loadNFTs() }, []) async function loadNFTs() { const providerOptions = { fortmatic: { package: Fortmatic, options: { // Mikko's TESTNET api key key: "pk_test_391E26A3B43A3350" } }, walletconnect: { package: WalletConnectProvider, // required options: { infuraId: "INFURA_ID" // required } } }; const web3Modal = new Web3Modal({ network: "mainnet", cacheProvider: true, disableInjectedProvider: false, providerOptions: providerOptions, // required }); const connection = await web3Modal.connect() const provider = new ethers.providers.Web3Provider(connection) const tokenContract = new ethers.Contract(nftaddress, nftabi, provider) const marketContract = new ethers.Contract(nftmarketaddress, nftmarketabi, provider) const data = https://www.it610.com/article/await marketContract.fetchMarketItems()const items = await Promise.all(data.map(async i => { const tokenUri = await tokenContract.tokenURI(i.tokenId) const meta = await axios.get(tokenUri) let price = web3.utils.fromWei(i.price.toString(), 'ether'); let item = { price, tokenId: i.tokenId.toNumber(), seller: i.seller, owner: i.owner, image: meta.data.image, name: meta.data.name, description: meta.data.description, } return item })) console.log('items: ', items) setNfts(items) setLoaded('loaded') }

Create NFT 铸币页面(信息上传ipfs返回链接作为tokenURI)
Dapp开发实战(去中心化NFT交易平台)
文章图片

const client = ipfsHttpClient('https://ipfs.infura.io:5001/api/v0') const [fileUrl, setFileUrl] = useState(null) const [formInput, updateFormInput] = useState({ price: '', name: '', description: '' }) const router = useRouter()async function createSale(url) { const web3Modal = new Web3Modal({ network: "mainnet", cacheProvider: true, }); const connection = await web3Modal.connect() const provider = new ethers.providers.Web3Provider(connection) const signer = provider.getSigner()let contract = new ethers.Contract(nftaddress,nftabi, signer) let transaction = await contract.createToken(url) let tx = await transaction.wait() let event = tx.events[0] let value = https://www.it610.com/article/event.args[2] let tokenId = value.toNumber() const price = web3.utils.toWei(formInput.price,'ether')const listingPrice = web3.utils.toWei('0.1', 'ether')contract = new ethers.Contract(nftmarketaddress, nftmarketabi, signer) transaction = await contract.createMarketItem(nftaddress, tokenId, price, { value: listingPrice })await transaction.wait() router.push('/') } async function onChange(e) { const file = e.target.files[0]; try { const added = await client.add( file, { progress: (prog) => console.log(`received: ${prog}`) } ) const url = `https://ipfs.infura.io/ipfs/${added.path}` setFileUrl(url) } catch (error) { console.log('Error uploading file: ', error); } }async function createMarket() { const { name, description, price } = formInput if (!name || !description || !price || !fileUrl) return // first, upload to IPFS const data = https://www.it610.com/article/JSON.stringify({ name, description, image: fileUrl }) try { const added = await client.add(data) const url = `https://ipfs.infura.io/ipfs/${added.path}` createSale(url) } catch (error) { console.log('Error uploading file: ', error); } }

【Dapp开发实战(去中心化NFT交易平台)】MyNFT 同上loadNFTS一样,不过调用合约fetchMyNFTs
Dapp开发实战(去中心化NFT交易平台)
文章图片

完整代码已放置Gitee https://gitee.com/xiaxuliang/nftmakret

    推荐阅读