原文地址 :https://docs.alchemy.com/docs/how-to-build-an-nft-marketplace-from-scratch
请大家关注我的推特(twitter.com/SoullessL)和Link3(link3.to/caishen),获取最新的Alchemy小白教程。教程汇总链接(jayjiang.gitbook.io/web3book/alchemy-road-to-web3)。
大部分人应该已经添加过了,如果已经添加可以忽略
测试网络信息如下
Network Name: Goerli Test Network
RPC base URL: https://eth-goerli.alchemyapi.io/v2/{INSERT YOUR API KEY}
Chain ID: 5
Block Explorer URL: https://goerli.etherscan.io/
Symbol (Optional): ETH
登录(www.alchemy.com)其中{INSERT YOUR API KEY}需要修改为你的Api Key,如截图所示。
进入Alchemy官方的Github地址(https://github.com/OMGWINNING/NFT-Marketplace-Tutorial),点击Fork按钮,把官方的代码复制到你的Github里。
然后系统就会自动转跳到你的Github页面,像截图里的地方会是你的github名字,我们记录下自己项目的GitHub浏览器地址。
然后通过Gitpod打开项目,在浏览器输入 https://gitpod.io/#https://github.com/你的github名字/NFT-Marketplace-Tutorial 开打项目。
然后等待出现下面的窗口,你可以点击这个Open Browser,来预览我们项目。
然后会出来这样一个窗口,就说明我们的项目加载好了,我图里的图片还没完全加载完,可以忽略。
然后我们回到我们的Terminal窗口,同时按住Ctrl+C,取消程序的运行。
然后我们输入命令 npm install dotenv --save 安装一下dotenv环境
然后在Explorer窗体的空白处点击鼠标右键,弹出一个窗口,选择New File。
文件名字为.env,里面的内容为
REACT_APP_ALCHEMY_API_URL="<YOUR_API_URL>"
REACT_APP_PRIVATE_KEY="<YOUR_PRIVATE_KEY>"
请记得把”<YOUR_API_URL>”替换成Achemy的Goerli网络的Https部分,而”<YOUR_PRIVATE_KEY>”需要替换成你的Metamask账号的的私钥(这边最好使用一个新的账号,防止私钥泄密,这边使用的账号可以和你领取NFT的账号不一样,所以完全可以使用新的好账号,记得转点测试的ETH到新账号当手续费就行。用完最后记得把你的私钥删除,更安全一点)。
然后我们进入 https://pinata.cloud 注册一个新的账号,然后进入https//app.pinata.cloud/keys,点击New Key,勾选Admin,输入Key name,点击Create Key,创建一个新的Key。在弹出的窗口里,记录对应的 API Key 和 API Secret。
REACT_APP_PINATA_KEY="<YOUR_PINATA_KEY>"
REACT_APP_PINATA_SECRET="<YOUR_PINATA_SECRET>"
然后把pinata对应的KEY和SECRET替换"<YOUR_PINATA_KEY>"和"<YOUR_PINATA_SECRET>",并添加到.env文件里。
最终.env文件如上图所示,但是对应的Key都是你自己的。
贴上整体的.env文件内容,方便大家替换。
REACT_APP_ALCHEMY_API_URL="<YOUR_API_URL>"
REACT_APP_PRIVATE_KEY="<YOUR_PRIVATE_KEY>"
REACT_APP_PINATA_KEY="<YOUR_PINATA_KEY>"
REACT_APP_PINATA_SECRET="<YOUR_PINATA_SECRET>"
然后我们找到hardhat.config.js文件,把里的内容替换为
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-ethers");
const fs = require('fs');
// const infuraId = fs.readFileSync(".infuraid").toString().trim() || "";
require('dotenv').config();
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
module.exports = {
networks: {
goerli: {
url: process.env.REACT_APP_ALCHEMY_API_URL,
accounts: [process.env.REACT_APP_PRIVATE_KEY]
}
},
solidity: {
version: "0.8.4",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
};
打开Contracts文件夹下面的NFTMarketplace.sol 文件,替换全部内容为
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract NFTMarketplace is ERC721URIStorage {
using Counters for Counters.Counter;
//_tokenIds variable has the most recent minted tokenId
Counters.Counter private _tokenIds;
//Keeps track of the number of items sold on the marketplace
Counters.Counter private _itemsSold;
//owner is the contract address that created the smart contract
address payable owner;
//The fee charged by the marketplace to be allowed to list an NFT
uint256 listPrice = 0.01 ether;
//The structure to store info about a listed token
struct ListedToken {
uint256 tokenId;
address payable owner;
address payable seller;
uint256 price;
bool currentlyListed;
}
//the event emitted when a token is successfully listed
event TokenListedSuccess (
uint256 indexed tokenId,
address owner,
address seller,
uint256 price,
bool currentlyListed
);
//This mapping maps tokenId to token info and is helpful when retrieving details about a tokenId
mapping(uint256 => ListedToken) private idToListedToken;
constructor() ERC721("NFTMarketplace", "NFTM") {
owner = payable(msg.sender);
}
function updateListPrice(uint256 _listPrice) public payable {
require(owner == msg.sender, "Only owner can update listing price");
listPrice = _listPrice;
}
function getListPrice() public view returns (uint256) {
return listPrice;
}
function getLatestIdToListedToken() public view returns (ListedToken memory) {
uint256 currentTokenId = _tokenIds.current();
return idToListedToken[currentTokenId];
}
function getListedTokenForId(uint256 tokenId) public view returns (ListedToken memory) {
return idToListedToken[tokenId];
}
function getCurrentToken() public view returns (uint256) {
return _tokenIds.current();
}
//The first time a token is created, it is listed here
function createToken(string memory tokenURI, uint256 price) public payable returns (uint) {
//Increment the tokenId counter, which is keeping track of the number of minted NFTs
_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
//Mint the NFT with tokenId newTokenId to the address who called createToken
_safeMint(msg.sender, newTokenId);
//Map the tokenId to the tokenURI (which is an IPFS URL with the NFT metadata)
_setTokenURI(newTokenId, tokenURI);
//Helper function to update Global variables and emit an event
createListedToken(newTokenId, price);
return newTokenId;
}
function createListedToken(uint256 tokenId, uint256 price) private {
//Make sure the sender sent enough ETH to pay for listing
require(msg.value == listPrice, "Hopefully sending the correct price");
//Just sanity check
require(price > 0, "Make sure the price isn't negative");
//Update the mapping of tokenId's to Token details, useful for retrieval functions
idToListedToken[tokenId] = ListedToken(
tokenId,
payable(address(this)),
payable(msg.sender),
price,
true
);
_transfer(msg.sender, address(this), tokenId);
//Emit the event for successful transfer. The frontend parses this message and updates the end user
emit TokenListedSuccess(
tokenId,
address(this),
msg.sender,
price,
true
);
}
//This will return all the NFTs currently listed to be sold on the marketplace
function getAllNFTs() public view returns (ListedToken[] memory) {
uint nftCount = _tokenIds.current();
ListedToken[] memory tokens = new ListedToken[](nftCount);
uint currentIndex = 0;
//at the moment currentlyListed is true for all, if it becomes false in the future we will
//filter out currentlyListed == false over here
for(uint i=0;i<nftCount;i++)
{
uint currentId = i + 1;
ListedToken storage currentItem = idToListedToken[currentId];
tokens[currentIndex] = currentItem;
currentIndex += 1;
}
//the array 'tokens' has the list of all NFTs in the marketplace
return tokens;
}
//Returns all the NFTs that the current user is owner or seller in
function getMyNFTs() public view returns (ListedToken[] memory) {
uint totalItemCount = _tokenIds.current();
uint itemCount = 0;
uint currentIndex = 0;
//Important to get a count of all the NFTs that belong to the user before we can make an array for them
for(uint i=0; i < totalItemCount; i++)
{
if(idToListedToken[i+1].owner == msg.sender || idToListedToken[i+1].seller == msg.sender){
itemCount += 1;
}
}
//Once you have the count of relevant NFTs, create an array then store all the NFTs in it
ListedToken[] memory items = new ListedToken[](itemCount);
for(uint i=0; i < totalItemCount; i++) {
if(idToListedToken[i+1].owner == msg.sender || idToListedToken[i+1].seller == msg.sender) {
uint currentId = i+1;
ListedToken storage currentItem = idToListedToken[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
function executeSale(uint256 tokenId) public payable {
uint price = idToListedToken[tokenId].price;
address seller = idToListedToken[tokenId].seller;
require(msg.value == price, "Please submit the asking price in order to complete the purchase");
//update the details of the token
idToListedToken[tokenId].currentlyListed = true;
idToListedToken[tokenId].seller = payable(msg.sender);
_itemsSold.increment();
//Actually transfer the token to the new owner
_transfer(address(this), msg.sender, tokenId);
//approve the marketplace to sell NFTs on your behalf
approve(address(this), tokenId);
//Transfer the listing fee to the marketplace creator
payable(owner).transfer(listPrice);
//Transfer the proceeds from the sale to the seller of the NFT
payable(seller).transfer(msg.value);
}
//We might add a resell token function in the future
//In that case, tokens won't be listed by default but users can send a request to actually list a token
//Currently NFTs are listed by default
}
然后我们打开Terminal,在里面输入 npx hardhat run scripts/deploy.js --network goerli 来部署我们的智能合约到goerli 测试网。
然后我们可以到src文件夹下找到Marketplace.json文件,里面的address就是我们部署的合约地址。然后我们可以通过 https://goerli.etherscan.io/address/合约地址 来查看到我们创建的合约。
然后我们在Termainl里面输入 npm start 把系统跑起来,并且点击Open Browser预览系统。
点击Conenct 连接我们的钱包。
然后你可以点击List My NFT,填写信息,选择一个图片,点击 List NFT,等弹出Metamask窗口,点击确定来上传一个NFT。
上传成功以后,你可以在Profile里面看到创建的NFT。
你也可以进你创建的合约地址(https://goerli.etherscan.io/address/你的合约地址),看到对应的创建NFT信息。
最后我们进入Source Control菜单,填写备注,然后点击Commit右边的小三角,然后点击Commit&Push按钮,把代码提交到我们的GitHub。
由于国内网络变化,如果你碰到如图所示问题,那么你需要重新创建一个 pinata Key。如果你保存了之前Key的JWT的值也可以。
然后在.env文件里添加一行
REACT_APP_PINATA_JWT=你的JWT的token
总体的.env文件就变成如果所示。
然后找到pinata.js文件,把全部内容替换为如下内容
//require('dotenv').config();
const key = process.env.REACT_APP_PINATA_KEY;
const secret = process.env.REACT_APP_PINATA_SECRET;
const axios = require('axios');
const FormData = require('form-data');
export const uploadJSONToIPFS = async(JSONBody) => {
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;
//making axios POST request to Pinata ⬇️
return axios
.post(url, JSONBody, {
headers: {
// pinata_api_key: key,
// pinata_secret_api_key: secret,
authorization: "Bearer "+process.env.REACT_APP_PINATA_JWT
}
})
.then(function (response) {
return {
success: true,
pinataURL: "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash
};
})
.catch(function (error) {
console.log(error)
return {
success: false,
message: error.message,
}
});
};
export const uploadFileToIPFS = async(file) => {
const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
//making axios POST request to Pinata ⬇️
let data = new FormData();
data.append('file', file);
const metadata = JSON.stringify({
name: 'testname',
keyvalues: {
exampleKey: 'exampleValue'
}
});
data.append('pinataMetadata', metadata);
//pinataOptions are optional
const pinataOptions = JSON.stringify({
cidVersion: 0,
customPinPolicy: {
regions: [
{
id: 'FRA1',
desiredReplicationCount: 1
},
{
id: 'NYC1',
desiredReplicationCount: 2
}
]
}
});
data.append('pinataOptions', pinataOptions);
return axios
.post(url, data, {
maxBodyLength: 'Infinity',
headers: {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
// pinata_api_key: key,
// pinata_secret_api_key: secret,
authorization: "Bearer "+process.env.REACT_APP_PINATA_JWT
}
})
.then(function (response) {
console.log("image uploaded", response.data.IpfsHash)
return {
success: true,
pinataURL: "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash
};
})
.catch(function (error) {
console.log(error)
return {
success: false,
message: error.message,
}
});
};
链接:https://alchemyapi.typeform.com/roadtoweekseven
表单最后填写,项目的Github地址(https://github.com/你的Github名字/NFT-Marketplace-Tutorial)~~和~~你的部署的合约地址(https://goerli.etherscan.io/address/你的合约地址)。