[TIL] 2023-04-20 DAY25
2023-04-24 DAY26
GIT 주소 : https://github.com/dowoo303/web3_metamask
https://github.com/dowoo303/layers_nft_hashLips
오늘 배운것들
- NFT 발행해보기
- 메타마스크 프론트엔드 학습(잔액조회, NFT민팅)
- 각 파츠별 그림을 이용하여 조합한 NFT를 제작 후 민팅해보기
각종 팁
- 값이 배열이면 뒤에 [0] 이런식으로 값을 담으면 됨.
- 각종 주소는 ?앞까지만
- 읽을 때는 call
보낼 때는 send
회고
사이트모음
NFT 생성 사이트(피나타): https://www.pinata.cloud/
메타데이터 설명서: https://docs.opensea.io/docs/metadata-standards
Remix: https://remix.ethereum.org/#lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.18+commit.87f61d96.js
-> 어제처럼 리믹스에서 새 파일에 코드 붙여넣고 컴파일 후 발행해보기

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
contract MintNft is ERC721Enumerable {
mapping(uint => string) public metadataUri;
uint public totalNft;
constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}
function mintNft(string memory _metadataUri) public {
uint tokenId = totalSupply() + 1;
_mint(msg.sender, tokenId);
metadataUri[tokenId] = _metadataUri;
}
function tokenURI(uint _tokenId) public override view returns(string memory) {
return metadataUri[_tokenId];
}
}

-> ENVIRONMENT를 인젝트 프로바이더로 변경하고 밑에 DEPLOY에서 이름과 심볼 설정 후 디플로이

-> 위 pinata에서 올린 json링크(메타데이터주소 ?전까지)(사진링크아님)를 mintNft에 넣어주고 민팅

-> 오픈씨에서 확인 가능 !
사이트 모음
메마 가이드: https://docs.metamask.io/wallet/get-started/detect-metamask/
Web3 js: https://web3js.readthedocs.io/en/v1.8.2/
import { useEffect, useState } from "react";
function App() {
const [account, setAccount] = useState("");
// 메타마스크 연결
const onClickAccount = async () => {
try {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
setAccount(accounts[0]);
} catch (error) {
console.error(error);
}
};
const onClickLogOut = () => {
setAccount("");
};
return (
<div className="bg-red-100 min-h-screen flex justify-center items-center">
{account ? (
<div className="text-main font-semibold">
{account.substring(0, 4)}...{account.substring(account.length - 4)}
<button className="ml-4 btn-style" onClick={onClickLogOut}>
로그아웃
</button>
</div>
) : (
<button className="btn-style" onClick={onClickAccount}>
<img
className="w-12"
src={`${process.env.PUBLIC_URL}/images/metamask.png`}
/>
</button>
)}
</div>
);
}
export default App;
-> 메타마스크 연결 및 로그아웃 구현
개념
이더리움과 상호작용할 수 있는 라이브러리
설치명령어npm i web3

-> ABI 복사 후 web.config.js에 붙여넣기
import { useEffect, useState } from "react";
import Web3 from "web3";
import {
NFT_ABI,
NFT_CONTRACT_ADDRESS,
TOKEN_ABI,
TOKEN_CONTRACT_ADDRESS,
} from "./web3.config";
// 프로바이더 추가 -> 마치 메마에서 다른 생태계 네트워크 추가하는 것과 같음
const web3 = new Web3("https://rpc-mumbai.maticvigil.com");
const contract = new web3.eth.Contract(TOKEN_ABI, TOKEN_CONTRACT_ADDRESS); // ABI와 컨트랙트 주소 연결
const NFTcontract = new web3.eth.Contract(NFT_ABI, NFT_CONTRACT_ADDRESS); // ABI와 컨트랙트 주소 연결
function App() {
const [account, setAccount] = useState("");
const [mybalance, setMybalance] = useState();
// 메타마스크 연결
const onClickAccount = async () => {
try {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
setAccount(accounts[0]);
} catch (error) {
console.error(error);
}
};
const onClickLogOut = () => {
setAccount("");
};
// 잔액조회
const onClickBalance = async () => {
try {
if (!account || !contract) return;
// 콜 함수
const balance = await contract.methods.balanceOf(account).call();
const totalSupply = await contract.methods.totalSupply().call();
console.log(totalSupply, web3.utils.fromWei(totalSupply));
setMybalance(web3.utils.fromWei(balance)); // 10^18 나누기 (/ 10**18)
} catch (error) {
console.error(error);
}
};
// 민팅
const onClickMint = async () => {
try {
const result = await NFTcontract.methods
.mintNft(
"https://gateway.pinata.cloud/ipfs/QmZ5ynCXHF5LyFwehgMxQQuxrq3x1hs7zcgo1bQ2QsRCmH"
)
.send({
from: account,
});
console.log(result);
} catch (error) {
console.error(error);
}
};
return (
<div className="bg-red-100 min-h-screen flex justify-center items-center">
{account ? (
<div>
<div className="text-main font-semibold">
{account.substring(0, 4)}...{account.substring(account.length - 4)}
<button className="ml-4 btn-style" onClick={onClickLogOut}>
로그아웃
</button>
</div>
<div>
<button className="btn-style m-8" onClick={onClickBalance}>
잔액 조회
</button>
{mybalance && (
<div className="flex items-center">{mybalance} tMatic</div>
)}
</div>
<div className="flex items-center mt-4">
<button className="m-8 btn-style" onClick={onClickMint}>
민팅
</button>
</div>
</div>
) : (
<button className="btn-style" onClick={onClickAccount}>
<img
className="w-12"
src={`${process.env.PUBLIC_URL}/images/metamask.png`}
/>
</button>
)}
</div>
);
}
export default App;
-> 잔액조회까지 완료 ! 위 리믹스에서 진행했던 민팅을 구현하려다 오류나서 다음날로
시작
DAY 25에 이어서 시작
const web3 = new Web3(window.ethereum);코드 변경
-> 위 기능들(balanceOf, tokenOfOwnerByIndex 등)을 코드로 구현할 예정
import axios from "axios";
import { useEffect, useState } from "react";
import Web3 from "web3";
import {
NFT_ABI,
NFT_CONTRACT_ADDRESS,
TOKEN_ABI,
TOKEN_CONTRACT_ADDRESS,
} from "./web3.config";
// 프로바이더 추가 -> 마치 메마에서 다른 생태계 네트워크 추가하는 것과 같음
const web3 = new Web3(window.ethereum);
const contract = new web3.eth.Contract(TOKEN_ABI, TOKEN_CONTRACT_ADDRESS); // ABI와 컨트랙트 주소 연결
const NFTcontract = new web3.eth.Contract(NFT_ABI, NFT_CONTRACT_ADDRESS); // ABI와 컨트랙트 주소 연결
function App() {
const [account, setAccount] = useState("");
const [mybalance, setMybalance] = useState();
const [nftMetadata, setNftMetadata] = useState();
// 메타마스크 연결
const onClickAccount = async () => {
try {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
setAccount(accounts[0]);
} catch (error) {
console.error(error);
}
};
const onClickLogOut = () => {
setAccount("");
};
// 잔액조회
const onClickBalance = async () => {
try {
if (!account || !contract) return;
// 콜 함수
const balance = await contract.methods.balanceOf(account).call();
const totalSupply = await contract.methods.totalSupply().call();
console.log(totalSupply, web3.utils.fromWei(totalSupply));
setMybalance(web3.utils.fromWei(balance)); // 10^18 나누기 (/ 10**18)
} catch (error) {
console.error(error);
}
};
// 민팅
const onClickMint = async () => {
try {
const uri =
"https://gateway.pinata.cloud/ipfs/QmXV6PgzzfBJxvhfp6PeXB7H7XktPCBcKCuXJr1uUvndwe";
// NFT mint
const result = await NFTcontract.methods.mintNft(uri).send({
from: account,
});
if (!result.status) return;
// // 이 지갑이 갖고있는 nft갯수 조회
// const balanceOf = await NFTcontract.methods.balanceOf(account).call();
// // 문자열로 들어옴
// // 현재까지 발행된 NFT 갯수 조회
// const tokenOfOwnerByIndex = await NFTcontract.methods
// .tokenOfOwnerByIndex(account, parseInt(balanceOf) - 1)
// .call();
// // 방금 생성한 NFT의 메타데이터(json)을 가져옴 (사실 mintNft 와 같음)
// const tokenURI = await NFTcontract.methods
// .tokenURI(tokenOfOwnerByIndex)
// .call();
// // axios를 통해 메타데이터를 response에 저장
// const response = await axios.get(tokenURI);
const response = await axios.get(uri);
setNftMetadata(response.data);
} catch (error) {
console.error(error);
}
};
return (
<div className="bg-red-100 min-h-screen flex justify-center items-center">
{account ? (
<div>
<div className="text-main font-semibold">
{account.substring(0, 4)}...{account.substring(account.length - 4)}
<button className="ml-4 btn-style" onClick={onClickLogOut}>
로그아웃
</button>
</div>
<div>
<button className="btn-style m-8" onClick={onClickBalance}>
잔액 조회
</button>
{mybalance && (
<div className="flex items-center">{mybalance} tMatic</div>
)}
</div>
<div className="flex items-center mt-4">
{nftMetadata && (
<div className="m-4">
<img src={nftMetadata.image} alt="NFT" />
<div>Name: {nftMetadata.name}</div>
<div>description: {nftMetadata.description}</div>
<div>
{nftMetadata.attributes.map((v, i) => {
return (
<div key={i}>
<span>{v.trait_type} : </span>
<span>{v.value}</span>
</div>
);
})}
</div>
</div>
)}
<button className="m-8 btn-style" onClick={onClickMint}>
민팅
</button>
</div>
</div>
) : (
<button className="btn-style" onClick={onClickAccount}>
<img
className="w-12"
src={`${process.env.PUBLIC_URL}/images/metamask.png`}
/>
</button>
)}
</div>
);
}
export default App;
-> uri가 어짜피 존재하기 때문에 중간 주석처리한 부분들은 사실 코딩할 필요가 없었음.(연습삼아 진행)
nftMetadata안에 nft에 대한 정보들이 다 들어있으므로 이걸 이용하면 여러가지 정보 표현 가능.

-> 민팅한 NFT 화면에 띄워보기 및 NFT 데이터 불러오기까지 완료
시작
HashLips를 이용하여 여러가지 layer들도 조합된NFT를 제작해본다.
-> 각 layer들은 pixilart나 포토샵등 다양한 곳에서 제작 후 이름을 orange#50 와 같은 형식으로 폴더별로 저장해준다.(#50의 뜻은 이 특성이 50% 확률로 나타난다는 뜻임)
-> 그렇게 만든 폴더들을 layers 폴더 하나에 넣은 후 HashLips에 사용
사이트
layer 그림 그리는 사이트: https://www.pixilart.com/draw
아트 제너레이팅: https://github.com/HashLips
제작 순서
- HashLips 가져 온 후 npm 설치
npm install
!윈도우 설치 에러시 ->npm update먼저!
- layers 폴더 내가 만든 layers 폴더로 변경
- config.js 변경 -> layers마다 순서 생각해서 넣기
// If you have selected Solana then the collection starts from 0 automatically const layerConfigurations = [ { growEditionSizeTo: 50, layersOrder: [ { name: "Background" }, { name: "Face" }, { name: "Hat" }, { name: "Eyes" }, ], }, ];
npm run build로 생성 -> growEditionSizeTo: 50 갯수만큼 build폴더에 생성됨
- pinata에 이미지 폴더만 폴더째 업로드
- 메타데이터 재정의(config.js)
const namePrefix = "BCS3rd"; const description = "First generating art"; const baseUri = "https://gateway.pinata.cloud/ipfs/QmUP8nRQRc7XQgGSzcwf4KXxCzw3Qtg8b6vbFpZftdKS4v";-> baseUri는 4번에서 pinata에 등록한 이미지 경로
6. 5번에서 재정의한 메타데이터 업데이트
npm run update_info
7. 업데이트가 끝난 json폴더 또한 pinata에 업로드
8. 저번과 마찬가지로 리믹스에서 nft mint진행
단, 저번과 코드는 다르게 진행
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract MintNft is ERC721Enumerable {
string public metadataUri;
uint public totalNft;
constructor(
string memory _name,
string memory _symbol,
string memory _metadataUri,
uint _totalNft
) ERC721(_name, _symbol) {
metadataUri = _metadataUri;
totalNft = _totalNft;
}
function mintNft() public {
require(totalNft > totalSupply(), "No more mint.");
uint tokenId = totalSupply() + 1;
_mint(msg.sender, tokenId);
}
function batchMint(uint _amount) public {
for(uint i = 0; i < _amount; i++) {
mintNft();
}
}
function tokenURI(uint _tokenId) public override view returns(string memory) {
return string(abi.encodePacked(metadataUri, '/', Strings.toString(_tokenId), '.json'));
}
function burnNFT(uint _tokenId) public {
require(msg.sender == ownerOf(_tokenId), "Caller is not token owner.");
_burn(_tokenId);
}
}
-> 폴더째로 올렸기 때문에 코드가 저번과는 약간 다름 + burn기능 추가

-> deploy (메타데이터URI는 위 7번에서 올린 json 경로)

-> batchMint로 한번에 민팅

layer를 각 조합해 자동으로 만든 NFT 6개 민팅 완료 !