블록체인 SNS 개발 - 1

개요


기존의 SNS는 사용자가 게시물을 작성하면 그 데이터가 중앙화된 서버에 저장이 되지만,
이번 프로젝트에서는 사용자의 데이터 소유에 대한 web3 본질에 대해 생각하며 작성된 게시물을 NFT로 발행하였다.
사용자가 작성한 게시물을 자신의 지갑에서 NFT로 직접 소유할 수 있도록 개발한 것이다.
처음에는 이더리움과 클레이튼 두 네트워크 모두 개발하려 하였지만, 이더리움 네트워크의 속도 문제 때문에 개발에 어려움이 있어서
클레이튼 네트워크로만 개발하게 되었다.

한편, 신기하게도 프로젝트를 함께한 팀원 모두가 반려동물을 키우고 있었고, 의료비 부담에 대한 공감대가 있었다.
이러한 공감대를 바탕으로 블록체인 SNS에 더하여 사용자들의 반려동물 의료비 부담을 경감해줄 수 있는 토큰 이코노미를 구상하였다.
작성된 게시물들은 일반적인 SNS와 같이 다른 사용자들에게 공유되며 서로 좋아요와 댓글을 남기며 소통할 수 있다.
특히 사용자는 본인의 게시물이 좋아요를 받을 때마다 별도의 토큰을 지급을 받는다.
이렇게 지급 받은 토큰은 동물병원 예약에 사용할 수 있도록 명확한 토큰의 사용처를 마련하여
사용자들의 적극적인 서비스 이용을 기대하며 설계하였다.

이전의 프로젝트에서 다른 팀원분들도 열심히 해주셨지만 특히나 이번 프로젝트는 최종 프로젝트이기도 하고,
모든 팀원분들이 반려동물을 양육하여 프로젝트의 목표에 대해 공감하며 잘 마무리 할 수 있었다.
초기에 구상한 기능들을 모두 구현할 수 있었고 AWS를 이용하여 실제 배포까지 진행하여 아주 좋은 프로젝트 경험이었다.
자세한 회고는 각 기능들에 대한 설명을 마친 후 이어서 작성하겠다.

GitHub 링크
배포 링크

1. 로그인 & 회원가입


로그인은 이전 프로젝트인 '인센티브 기반 블록체인' 커뮤니티 개발할 때처럼 지갑을 통해 로그인할 수 있도록 개발하였다.
별도의 회원가입 절차 없이 Back-end에서 회원가입 프로세스가 진행되기 때문에
사용자는 지갑 연결만을 통해 바로 서비스를 이용할 수 있도록 하였다.

    const accounts = await window.klaytn.enable();
    let username = '';

    await axios.post(`${host}/user/login`, { address: accounts[0] })
      .then((res) => {
        const userInfo = res.data.data;
        if (userInfo !== null) {
          const username = userInfo.user_name === accounts[0] ?
            userInfo.user_name.slice(0, 4) + '···' + userInfo.user_name.slice(-4)
            : userInfo.user_name;

          localStorage.setItem('userId', userInfo._id);
          localStorage.setItem('isConnected', true);
          localStorage.setItem('account', JSON.stringify(accounts[0]));
          localStorage.setItem('networkType', 1);
          alert(`${username} 님!\n다시 돌아오신 것을 환영합니다!`);
          window.location.reload();
        } else {
          signup(accounts[0]);
          const username = accounts[0].slice(0, 4) + '···' + accounts[0].slice(-4)
          localStorage.setItem('isConnected', true);
          localStorage.setItem('account', JSON.stringify(accounts[0]));
          localStorage.setItem('networkType', 1);
          alert(`${username} 님!\nPETOPIA 구성원이 되신 것을 축하합니다~~\n회원정보는 마이페이지에서 수정하실 수 있습니다.`);
          window.location.href = `${domain}/mypage`;
        }
      });

위 코드는 지갑연결할 때 실행되는 코드로 지갑 연결 시 DB에서 유저정보를 읽어와 기존 회원인지 신규 회원인지 판별하도록 하였다.
기존 회원이라면 로그인 후 메인 페이지로 이동이 된다.
신규 회원이라면 유저네임은 지갑주소로 설정하여 DB에 유저 정보가 저장되고,
로그인시 마이페이지로 이동하여 회원정보를 수정할 수 있도록 하였다.

2. 게시물 작성(NFT 발행)


게시물은 일반적인 SNS 처럼 사용자에게 이미지와 텍스트를 입력 받아 작성된다.
여기서 일반 SNS와 다른 점은 이미지와 텍스트를 DB에 저장하는 것이 아니라 Infura IPFS API를 통해 METADATA로 생성해준다.

    const createMetadata = async () => {

        const ipfsUrl = 'https://ipfs.infura.io/ipfs/';
        const imagePath = await uploadIPFS(imageFile);
        const metadata = {
            "description": inputText,
            "image": `${ipfsUrl}${imagePath}`,
            "name": "PETO_NFT",
        };
        const metadataPath = await uploadIPFS(JSON.stringify(metadata));
        createNFT(`${ipfsUrl}${metadataPath}`);

    };

    const uploadIPFS = async (file) => {
        const ipfs = create("https://ipfs.infura.io:5001/api/v0");
        return (await ipfs.add(file)).path;
    };

해당 METADATA를 tokenURI에 담아 사용자의 지갑으로 NFT Mint 요청을 보내고,
사용자가 승인을 하면 해당 지갑으로 NFT(KIP-17)이 발행된다.
데이터를 읽어올 때 컨트랙트에서 받아오기 때문에 DB에는 발행된 NFT의 TOKEN ID를 저장해주고
이미지나 텍스트에 대한 정보는 저장하지 않았다.

    const createNFT = async (tokenURI) => {
        const tokenContract = await new caver.klay.Contract(kip17Abi, contractAddress, {
            from: account
        });
        tokenContract.options.address = contractAddress;

        const newTokenId = await tokenContract.methods.mintNFT(tokenURI)
        .send({ from: account, gas: 0xf4240 });

        const postInfo = {
            "tokenId": newTokenId.events.Transfer.returnValues.tokenId,
            "userId": localStorage.getItem('userId'),
            "postDate": new Date(),
            "networkType": localStorage.getItem('networkType')
        };

        await axios.post(`${host}/post/`, postInfo)
            .then((res) => {
                const postInfo = res.data.data;
                if (postInfo !== null) {
                    alert(`우리 아기 추억 저장 완료!`);
                    window.location.replace(`${domain}`);
                } else {
                    alert(`게시물 작성이 실패하였습니다.\n관리자에게 문의하세요.`);
                }
            });
    };

좋은 웹페이지 즐겨찾기