TIL101. Web3 NFT dApp 만들기

조연정·2022년 12월 15일
0

nft minting, staking 페이지를 만들며 NFT dApp 만드는 일련의 과정들을 정리해보자.

minting page

nft에서 말하는 민팅은 두가지 뜻이 있다. 하나는 판매자 입장에서 가상화폐로 NFT를 만들어 낸다는 뜻이고, 다른 하나는 구매자 입장에서 NFT를 구매한다는 뜻도 된다.
지금 다루고 있는 dapp상 민팅 페이지는 NFT를 구매할 수 있는 페이지를 말한다.

minting 과정

1. web3 지갑 연결

web3 지갑은 dpp과 상호작용하고 이더리움 블록체인에서 거래를 수행하는데 사용된다. 지갑 자체가 암호자체를 보관하는 것은 아니고, 디지털 자산에 액세스할 수 있는 정보를 저장한다.

web에서는 직접적으로 스마트 컨트랙트와 상호작용할 수는 없다. 그래서 JSON-RPC 프로토콜을 통해 이더리움 노드와 통신하는 기능을 포함한 JavaScript 라이브러리 web3 혹은 ethers 사용한다. 라이브러리는 다양한 기능을 포함하고 이더리움 지갑에 연결할 수 있게 해준다.

  • Smart Contract
    스마트 컨트랙트란 블록체인에 등록되어 작동하는 프로그램으로, 서면으로 이루어지던 금융거래, 부동산 계약, 공증 등 다양한 형태의 계약을 코드로 구현하고 특정 조건이 충족되었을 때 해당 계약이 이행되게 하는 script를 통해 계약을 체결하고 이행하는 것을 말한다.
  • JSON-RPC
    대부분이 이더리움 환경 상에서 스마트 컨트랙트를 만드는데 이때 이더리움 노드들은 JSON-RPC라고 불리는 언어로만 소통할 수 있다.

ethers.js

(ethers를 사용하여 프로젝트를 구성하였기 때문에 ethers에 대한 기본적인 개념이나 사용법에 대해 작성해보았다.)
web3.js 이후에 나온 라이브러리로 provider와 signer를 주입하는 형태로 간편하고 유연한 코드 작성이 가능하다.

  • Provider
    이더리움 네트워크 연결을 위한 추상화(abstraction)를 제공하는 클래스로 블록 ,트랜잭션 등의 읽기 전용 액세스를 제공한다.
    (개인이 노드가 될수없기 때문에 네트워크의 정보를 대신 제공해주는 역할을 한다.)
  • Signer
    : 직간접적으로 private key에 대한 접근권을 가지고 있는 클래스로 계정의 ether를 사용하여 트랜잭션을 수행할 수 있도록 한다.
    signer는 이더리움 계정을 통해서 트랜잭션에 사인을 해서 이더리움 네트워크 상의 정보를 변경하는 트랜잭션을 실행할 수 있게 해준다.
  • Contract
    : 이더리움 네트워크 상의 특정한 컨트랙트와 연결하여 일반적인 js의 오브젝트처럼 사용할 수 있도록 한다.
const contract = new ethers.Contract(contractAddress, ABI, provider or signer);

signer대신 provider를 넘기면 읽기 전용

2.구매 수단 선택

native token(Matic coin), ERC20 token 어떤 구매 수단을 선택하느냐에 따라 프로세스가 달라진다. native token선택 시 별도의 과정없이 바로 mint contract를 부를 수 있다.

3. approve 트랜잭션

  • 유저의 지갑 내 토큰을 다른 주소가 가져갈 수 있는 권한을 승인하는 트랜젝션이다.
  • 현재 토큰의 개수보다 더 많이 승인하는 것도 가능하다.
    'ethers.constants.MaxUint256' 를 사용하면 무한대로 approve가 가능하다.
function approve(address _spender, uint256 _value) public returns (bool success)

유저지갑에서(from) spender주소(to)에 value만큼의 토큰을 이동할 수 있는 권한을 승인

  • allowance
    spender(대리자)가 owner(실제 토큰 보유자) 대신 거래할 수 있는 금액
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]);

4. mint NFT

transfer token + mint NFT 두 기능 합쳐져서 진행

  • setMint 메소드 (갯수, 0 or 1-> 코인=0 토큰=1, signer )
    -> 임의의 smart contract를 이용한 메소드
  • 트랜젝션의 실패,성공 여부를 확인하기 위해서 폴링을 돌려주는 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를 이용한 샘플 코드 예시

staking page

보유한 NFT를 블록체인 네트워크에 고정시키는 것을 말한다. 일종의 은행예치시스템과 비슷하다고 생각하면 이해하기 쉽다.
반대로 unstaking을 하면 스테이킹한 자산을 해제하여 보유한 암호화폐를 출금 또는 거래 가능한 상태로 만들 수 있다.

nft 목록 불러오기

(*지갑 연결과 approve 프로세스 이전과 동일하거나 비슷하기 때문에 생략)

이전에 민팅해서 구매한 nft들은 각각의 tokenId를 가지고 있다. 보유자의 주소를 넣으면 tokenId를 담은 목록을 보여준다.

 nftContract.tokensURI(account);

stake

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를 이용한 샘플 코드 예시

profile
Lv.1🌷

0개의 댓글