[Ethereum] NFT 마켓플레이스 만들기

0xDave·2022년 9월 22일
0

Ethereum

목록 보기
13/112
post-thumbnail

본 글은 Road to Web3 예제를 공부하며 몰랐던 내용들을 정리한 글입니다. 잘못된 내용이 있다면 언제든지 지적해주세요 😇


🦄 pinata.js


1. Axios

Axios는 백엔드와 프론트엔드가 서로 통신하기 위한 HTTP 비동기 라이브러리다.

자바스크립트 자체에서 fetch를 쓰기도 하지만 React나 Vue에서 ajax를 구현할 때 Axios를 사용한다. 따로 설치를 해야하는 단점이 있지만 fetch보다 다양한 기능을 제공하며 비교적 간단한 문법을 통해 사용 가능하다. 더 자세한 차이점은 여기에 잘 나와있다!

2. FormData

FormData는 HTML 단이 아닌 자바스크립트 단에서 폼 데이터를 다루는 객체라고 보면 된다. 보통은 HTML5의 <form> 태그를 이용해 input 값을 서버에 전송하지만, 자바스크립트에서 FormData() 클래스를 이용해 똑같이 스크립트로도 전송을 할수 있다. 그리고 HTML에서의 Submit 제출 동작은 ajax를 통해 서버에 제출한다고 보면 된다.

보통의 Ajax 통신에는 FormData를 잘 사용하지 않는다. 주로 JSON을 이용한 Key-Values 데이터 전송을 하기 때문이다. 하지만 이미지 같은 멀티미디어 파일을 페이지 전환 없이 폼 데이터를 비동기로 제출 할 경우 FormData객체를 사용한다. 또한 자바스크립트로 좀 더 타이트하게 폼 데이터를 관리하고 싶을 때 formData 객체를 이용한다.

추가적으로 formData 는 일반적인 객체와는 다르게 문자열 화할 수 없고, console.log 로 출력해볼 수 없다. 따로 확인 하기 위해서는 for문을 이용해야 한다고 한다.


👛 SellNFT.js


    //IPFS에 이미지 업로드
    async function OnChangeFile(e) {
        var file = e.target.files[0];
        try {
            const response = await uploadFileToIPFS(file);
            if(response.success === true) {
                console.log("Uploaded image to Pinata: ", response.pinataURL)
                setFileURL(response.pinataURL);
            }
        }
        catch(e) {
            console.log("Error during file upload", e);
        }
    }

JS에서는 input 태그의 속성 중 type="file"을 이용하면 파일을 업로드 할 수 있다. 사용자가 업로드 한 파일은 e.target.files에 담겨있으며, multipl 속성이 있는 태그는 한번에 여러 개 파일을 첨부할 수 있기 때문에 [0] 번 이외에 인덱스 값을 추가로 가질 수 있다.

사실 pinata.js에서 FormData를 사용한 이유는 사용자가 업로드한 파일을 서버에 그대로 전달할 수 없기 때문이다.


👉 Navbar.js


1. useLocation

function Navbar() {

const [connected, toggleConnect] = useState(false);
const location = useLocation();
const [currAddress, updateAddress] = useState('0x');
//...

Navbar 파일 상단에 useLocation()을 사용한 것을 볼 수 있다. 찾아보니 현재 페이지 주소를 알 수 있도록 해주는 react hook이었다. 예전 API reference를 보면 위에서 사용한 pathname 외에도 다양한 기능을 제공한다.


2. window.ethereum.request

async function connectWebsite() {

    const chainId = await window.ethereum.request({ method: 'eth_chainId' });
    if(chainId !== '0x5')
    {
      //alert('Incorrect network! Switch your metamask network to Rinkeby');
      await window.ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{ chainId: '0x5' }],
     })
    }  
    await window.ethereum.request({ method: 'eth_requestAccounts' })
      .then(() => {
        updateButton();
        console.log("here");
        getAddress();
        window.location.replace(location.pathname)
      });
}

컨트랙트가 잘 작동하려면 사용자 지갑의 네트워크가 제대로 설정돼있는지 확인이 필요하다. 이를 위해 메타마스크에서는 네트워크가 올바르게 되어있는지 확인하는 메소드를 제공한다. window.ethereum.request({ method: '사용하고자 하는 메소드' }) 형식으로 사용되며 이 외에도 다양한 메소드를 제공한다.

ethereum.request을 기본 형식으로 하며 파라미터만 바꿔주면 된다.

예제에서 두번째 부분에 사용된 eth_requestAccounts 는 연결된 지갑 주소를 string array 형태로 리턴한다.

각 네트워크의 Hex 값은 위와 같으며, 더 자세한 사항은 메타마스크 docs에 나와있다.


3. Event

  useEffect(() => {
    let val = window.ethereum.isConnected();
    if(val)
    {
      console.log("here");
      getAddress();
      toggleConnect(val);
      updateButton();
    }

    window.ethereum.on('accountsChanged', function(accounts){
      window.location.replace(location.pathname)
    })
  });

이더리움에서는 useEffect 부분에 다음과 같이 eventListener를 추가할 수 있다.

window.ethereum.on('이벤트 이름', eventListener를)

그런데 Navbar.js 에는 따로 설정한 이벤트가 없어서 찾아보니 메타마스크에서 제공하는 이벤트였다. accountsChanged 이벤트는 말 그대로 연결된 지갑의 주소가 바뀔 때마다 작동한다.

이 외에도 connect, disconnect, chainChanged, message 등의 이벤트를 제공한다.


🛒 Marketplace.js


async function getAllNFTs() {
    const ethers = require("ethers");
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();
    let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer)
    //create an NFT Token
    let transaction = await contract.getAllNFTs()

    //Fetch all the details of every NFT from the contract and display
    const items = await Promise.all(transaction.map(async i => {
        const tokenURI = await contract.tokenURI(i.tokenId);
        let meta = await axios.get(tokenURI);
        meta = meta.data;

        let price = ethers.utils.formatUnits(i.price.toString(), 'ether');
        let item = {
            price,
            tokenId: i.tokenId.toNumber(),
            seller: i.seller,
            owner: i.owner,
            image: meta.image,
            name: meta.name,
            description: meta.description,
        }
        return item;
    }))

    updateFetched(true);
    updateData(items);
}

NFT의 정보를 가져와서 화면에 보여주는 코드다.

1. contract.tokenURI(i.tokenId)

ERC721에서 tokenURI(tokenId) 메소드를 사용할 수 있는데, tokenId를 인자로 넣어주면 해당 NFT의 tokenURI를 string 형태로 리턴해준다. OpenZeppelin docs에서 다양한 메소드를 찾아볼 수 있다.


2. Promise.all

Promise.all은 여러 프로미스를 병렬적으로 처리할 때 사용한다. 모든 프로미스가 성공적으로 실행되고 나서 반환 값을 배열에 넣어 반환한다. 하지만 도중에 하나라도 프로미스가 실패하면 성공했던 프로미스의 결과는 무시되며 reject된다. Dev scroll 블로그에서 좋은 그림이 있어서 가져왔다.

위 코드에선 axios.get으로 메타데이터를 가져올 때 Promise.all 을 사용했다. 그런데 여러 메타데이터를 가져오는 과정에서 잘못된 주소가 하나 껴있다면 전부 취소되기 때문에 비효율적일 수 있다. 따라서 Promise.allSettled를 사용하는 것이 좋다는 의견이 많다. Promise.allSettled는 마찬가지로 여러 프로미스를 병렬적으로 처리하지만 하나의 프로미스가 실패해도 계속 진행된다.

따라서 어떤 프로미스가 실패했는지 알 수 있으며, 성공한 프로미스의 결과는 그대로 가져와서 사용할 수 있으니 훨씬 괜찮다. 각자 장단점이 있기 때문에 필요한 곳에 Promise.allPromise.allSettled를 적절하게 사용하는 것이 좋을 것 같다.


3. axios.get

axios를 이용해 get 요청을 보내면 response를 받을 수 있는데 이 때 response.data 에서 내가 원하는 정보들을 가져올 수 있다.

코드 작성 예시는 다음과 같다.


🔥 배운 점


완벽한 NFT 마켓플레이스를 만든 것이 아니라 조금 아쉬웠지만 이번 예제에서는 JS에서 부족한 부분을 채울 수 있어서 좋았다. 명확하지 않았던 Axios의 개념과 사용방법, 그 동안 사용하지 않았던 FormData 등 이번에는 프론트 쪽 개념을 배울 수 있었다. 자바스크립트 공부도 소홀히 하면 안 되겠다는 느낌을 매우 강하게 받았다.


출처 및 참고자료


  1. [JS] 📚 FormData 사용법 & 응용 총정리 (+ fetch 전송하기)

  2. [AXIOS] 📚 axios 설치 & 특징 & 문법 💯 정리

  3. How to Build an NFT Marketplace from Scratch

  4. [Javascript] FormData 란?(Ajax 이미지 첨부)

  5. RPC API

  6. [React axios] React의 axios 기본 :: axios로 GET, POST, PUT, DELETE 요청 보내기

  7. [JS] 📚 더이상 Promise.all 쓰지말고 Promise.allSettled 사용하자

  8. Axios를 사용하여 HTTP요청하기

profile
Just BUIDL :)

0개의 댓글