nft minting, staking 페이지를 만들며 NFT dApp 만드는 일련의 과정들을 정리해보자.
nft에서 말하는 민팅은 두가지 뜻이 있다. 하나는 판매자 입장에서 가상화폐로 NFT를 만들어 낸다는 뜻이고, 다른 하나는 구매자 입장에서 NFT를 구매한다는 뜻도 된다.
지금 다루고 있는 dapp상 민팅 페이지는 NFT를 구매할 수 있는 페이지를 말한다.
web3 지갑은 dpp과 상호작용하고 이더리움 블록체인에서 거래를 수행하는데 사용된다. 지갑 자체가 암호자체를 보관하는 것은 아니고, 디지털 자산에 액세스할 수 있는 정보를 저장한다.
web에서는 직접적으로 스마트 컨트랙트와 상호작용할 수는 없다. 그래서 JSON-RPC 프로토콜을 통해 이더리움 노드와 통신하는 기능을 포함한 JavaScript 라이브러리 web3
혹은 ethers
사용한다. 라이브러리는 다양한 기능을 포함하고 이더리움 지갑에 연결할 수 있게 해준다.
(ethers를 사용하여 프로젝트를 구성하였기 때문에 ethers에 대한 기본적인 개념이나 사용법에 대해 작성해보았다.)
web3.js 이후에 나온 라이브러리로 provider와 signer를 주입하는 형태로 간편하고 유연한 코드 작성이 가능하다.
const contract = new ethers.Contract(contractAddress, ABI, provider or signer);
signer대신 provider를 넘기면 읽기 전용
native token(Matic coin), ERC20 token 어떤 구매 수단을 선택하느냐에 따라 프로세스가 달라진다. native token선택 시 별도의 과정없이 바로 mint contract를 부를 수 있다.
function approve(address _spender, uint256 _value) public returns (bool success)
유저지갑에서(from) spender주소
(to)에 value
만큼의 토큰을 이동할 수 있는 권한을 승인
function allowance(address _owner, address _spender) public view returns (uint256) {
return _allowances[owner][spender];
}
//return example
{
'A' : {
'B': 3000,
'C': 5000
}
}
// -> A가 가진 토큰 중 3000을 B에게 5000을 C에게 맡김
구매 시마다 approve과정을 거친다면 사용자 편의상 좋지 않을 수 있다. 그래서 approve 금액을 max로 설정해놓고, allowance 함수를 통해 금액을 확인하고, 구매하려는 금액보다 allowance금액이 크다면 approve는 다시 진행하지 않는 방법이 있다.
(다만, 신뢰할 수 없는 스마트 컨트랙트에 max권한을 맡긴다는 것은 위험성이 있을 수 있다.)
useEffect(() => {
(async () => {
// 구매수단이 token일 경우
if (selectedOpt === CRYPTOCURRENCY.TOKEN) {
// 'allowance'로 금액 확인
const allowanceAmount = await erc20Contract.allowance(account, MINTER_CONTRACT_ADDRESS);
// 총 가격 = 개당 가격 * 선택한 수량
const currentPrice = ethers.utils.parseEther(String(getSelectedInfo().price * count));
// allowance 금액이 'currentPrice'보다 크면 true
const pass = ethers.BigNumber.from(allowanceAmount).sub(currentPrice) >= ethers.BigNumber.from(0);
}
})();
}, [selectedOpt, count]);
transfer token + mint NFT 두 기능 합쳐져서 진행
waitForTransaction
메소드를 사용한다. transaction.status가 '0'이면 실패, '1'이면 성공 const mintNft = async () => {
const minterContract = new ethers.Contract(MINTER_CONTRACT_ADDRESS, MINTER_CONTRACT_ABI, library?.getSigner());
const response = await minterContract.setMint(count, 1, {
gasLimit: 500000,
});
await checkTransactionStatus(response.hash);
};
const checkTransactionStatus = async (hash: string) => {
const provider = new ethers.providers.JsonRpcProvider(process.env.REACT_APP_RPC_URL);
setLoading(true);
await provider.waitForTransaction(hash).then(function (transaction) {
setLoading(false);
if (transaction.status === 1) {
setPhase(PHASE.FREE_OF_APPROVAL);
setCount(1);
alert("민팅에 성공하였습니다.");
} else {
alert("민팅에 실패하였습니다.");
}
});
};
*임의의 smart contract를 이용한 샘플 코드 예시
보유한 NFT를 블록체인 네트워크에 고정시키는 것을 말한다. 일종의 은행예치시스템과 비슷하다고 생각하면 이해하기 쉽다.
반대로 unstaking을 하면 스테이킹한 자산을 해제하여 보유한 암호화폐를 출금 또는 거래 가능한 상태로 만들 수 있다.
(*지갑 연결과 approve 프로세스 이전과 동일하거나 비슷하기 때문에 생략)
이전에 민팅해서 구매한 nft들은 각각의 tokenId를 가지고 있다. 보유자의 주소를 넣으면 tokenId를 담은 목록을 보여준다.
nftContract.tokensURI(account);
stake할 아이템의 tokenId를 넘겨준다.
const stakeToken = async () => {
const stakingContract = new ethers.Contract(
NFT_STAKING_CONTRACT_ADDRESS,
NFT_STAKING_CONTRACT_ABI,
library?.getSigner(),
);
const staking = await stakingContract.nftStaking(toNumber(toStakeItems), { gasLimit: 40000000 });
setIsLoading(true);
await provider.waitForTransaction(staking.hash).then((transaction) => {
if (transaction.status === 1) {
setAfterStaking((prev) => !prev);
} else {
alert('스테이킹 실패. Staking failed.');
}
setIsLoading(false);
});
};
*임의의 smart contract를 이용한 샘플 코드 예시