Opensea 클론 코딩 회고

mango7loco·2022년 4월 18일
2

블록체인

목록 보기
3/4
post-thumbnail

실제 프로젝트를 진행하면서 있었던 과정과 생각들을 시간의 흐름에 따라 회고합니다.

[깃헙 레포]
https://github.com/codestates/beb-03-whale
[WhaleNFT 이더스캔]
https://ropsten.etherscan.io/token/0xe23e30b939a085a2d92f50c803f574c58912162b
[TransferWhaleNFT 이더스캔]
https://ropsten.etherscan.io/address/0xd5e92129477fd369c14411919a6833c05c214275

역할 분담

프로젝트1에서는 3~4명이 팀을 이뤄서 오픈씨 클론 코딩을 진행했다. 우리조는 3명이었는데, 각자 맡고 싶은 역할을 이야기하고 분배했다. 그렇게 나는 서버와 DB를 맡게 되었고, 다른 분들은 각각 프론트엔드와 스마트컨트랙트를 맡으셨다.

이전부터 세계 최대의 NFT 마켓플레이스인 오픈씨를 종종 사용해봤지만, 사용자가 아닌 개발자의 관점으로 오픈씨를 다시 살펴보았다. 오픈씨의 기능들을 탐색해보면서, 팀원들 모두 프로젝트를 진행하는 것에 익숙하지 않고 일주일이라는 한정된 시간이 있기 때문에 개발의 최소 목표치를 먼저 정하기로 했다.

구현 목표

오픈씨는 기본적으로 NFT를 거래하는 플랫폼이기 때문에, 다음과 같은 기능들은 최소한으로 갖춰져야 한다고 생각했다.

  • 플랫폼에 등록된 NFT들을 보여준다.
  • NFT 민팅을 쉽게 할 수 있게 해준다.
  • 자신의 NFT를 판매할 수 있다.
  • 다른 사람들이 판매하는 NFT를 구매할 수 있다.

위의 기능들을 고려해서 다음과 같은 페이지들을 구상했다.

  • 처음 웹사이트에 접속했을 때 보이는 Main
  • 플랫폼에 등록된 NFT들을 보여주는 Explore
  • 플랫폼에 연결된 나의 지갑이 소유하고 있는 NFT들을 보여주는 MyPage
  • NFT를 민팅할 수 있는 기능을 지원하는 Create
  • 소유하고 있는 NFT를 판매할 수 있는 Sell
  • 원하는 NFT를 구매할 수 있는 Buy

목업

팀원들과 회의를 통해서 앞과 같은 기능들을 구현하기로 결정했고, 시작하기에 앞서 전체적인 모습을 시각화하기 위해서, 프론트엔드를 맡아주신 분이 Keynote를 활용해서 다음과 같은 목업을 만들어주셨다.

이처럼 사용자들이 우리 웹사이트를 사용할 때 보여질 화면들을 결정하고 나서, 이렇게 보여지는 페이지에서 각각의 버튼을 눌렀을 때 올바르게 잘 작동하기 위해서 어떠한 로직이 필요할지 이야기했다.

회의 - 민팅

먼저 시중에서는 각각의 NFT 프로젝트마다 고유의 기능을 집어넣기 위해서 표준 규격에 해당하는 스마트 컨트랙트를 상속받은 걸 기본으로 해서 추가적인 로직에 맞게 함수나 변수를 추가해서 변화를 준다. 그렇지만 사용자의 개별적인 요구에 맞춰서 스마트 컨트랙트 자체에 변화를 줄 수 있는 인터페이스를 제공하는 기능을 구현하기에는, 현재 우리가 가진 리소스가 부족하다고 판단했다. 그러므로 우리가 미리 민팅을 쉽게 할 수 있는 스마트 컨트랙트를 만들어서, 사용자가 원하는 이미지, 이름, 설명 정보 등을 업로드하여 그 정보를 토대로 민팅할 수 있게 만들었다. 이미지와 메타데이터는 infura를 활용해서 ipfs에 저장되게 했고 이 정보가 스마트 컨트랙트에 입력될 수 있게 했다. 블록체인 자체가 분산이라는 키워드도 집중하므로 ipfs를 활용하는 게 나아 보였는데, ipfs에 대해서는 대략적인 개념만 알고 넘어가서 추가적인 공부가 필요할 것 같다.

이때 스마트 컨트랙트를 통해서 민팅을 하고, 민팅에 사용한 정보를 클라이언트가 서버로 보내서, 서버가 해당 정보를 저장하면 어떤가 생각했다. 이에 대해서 팀원들과 이야기를 했는데, 내 생각은 민팅에 사용하는 메타데이터는 발행된 이후로 바뀌지 않고, NFT의 소유자와 거래 여부만 바뀌기 때문에 발행 직후의 시점에 서버에 저장하는 게 좋을 것 같다였다. 하지만 클라이언트가 보내는 정보를 신뢰해야 하는 문제가 발생하므로 이더리움 네트워크를 통해서 온체인 데이터를 직접 서버가 받아서 저장해야 한다는 의견이 있었다.

나는 클라이언트가 서버에 민팅되는 직후 바로 서버에 해당 정보를 보내면 서버에서 해당 정보가 업데이트되는 시점이 일치하게 되는데, 클라이언트가 민팅되었다는 정보를 보내주지 않으면 온체인 데이터를 통해서 다시 데이터베이스를 스스로 업데이트하기 전까지는 민팅이 되어있으나 해당 정보를 가지고 있지 않은 문제가 발생한다고 생각했다. 그리고 최종적으로는 서버와 분리해서 계속해서 데이터베이스를 주기적으로 업데이트하는 프로그램을 만들었지만, 이 이야기를 하던 당시에는 클라이언트가 메타데이터를 주지 않더라도, 클라이언트가 어떤 액션을 했다는 정보를 서버가 전달받아서 그러한 액션이 취해질 때마다 서버에서 클라이언트로부터 받은 정보를 토대로 데이터베이스를 업데이트하거나, 네트워크를 이용해 온체인 데이터로부터 데이터베이스를 업데이트를 할 생각을 하고 있었다. 그러한 상황에서 나는 서버가 주기적으로 데이터베이스를 업데이트하는 동작을 하면서 클라이언트의 요청에 응답하는 것이 동시에 돌아가는 것이 사용성을 떨어뜨린다는 생각이 들었다.

그래서 클라이언트에게 정보를 받아서 데이터베이스를 업데이트하면서, 그 정보가 맞는지 블록체인 네트워크를 통해 검증하는 과정도 생각했으나 애초에 클라이언트가 주는 정보로 데이터베이스를 업데이트하는 것 자체가 블록체인에서 지향하는 가치와 멀어진다는 생각이 들어, 팀원분의 의견에 동의해서 아예 클라이언트로부터는 정보를 받지 않고 주기적으로 온체인 데이터만을 이용해 업데이트하는 방향으로 개발하기로 했다.

회의 - 거래

NFT를 거래하기 위해서는 판매자가 올려놓은 가격에 맞춰서 구매자가 판매자에게 돈을 지불하면, NFT의 소유권이 바껴야 한다. 그런데 판매자가 NFT를 판매하려고 플랫폼에 등록하는 시점과 구매자가 나타나서 NFT를 플랫폼에서 구매하는 시점이 다르기 때문에 NFT transfer를 수행하는 트랜잭션을 어떻게 해낼 것인지 고민했다. 스마트 컨트랙트를 잘 짜면 소유권을 넘기지 않고도 해낼 수 있다고 하였지만, 당시에 우리는 그렇게까지 하진 못했다. 그래서 NFT transfer 기능을 구현하는 스마트컨트랙트에게도 NFT를 처분할 수 있는 권한을 줘서 거래 조건이 만족하면 스마트컨트랙트가 대신 NFT의 소유권을 넘겨주는 방식으로 구현하기로 했다.

플랫폼에 접속했을 때 사용자에게 NFT들을 보여주기 위해서는, 마켓플레이스에 등록된 NFT 정보들을 보관하는 데이터베이스가 필요하다고 생각했다. 특히 NFT를 발행하는 스마트컨트랙트와 NFT를 거래할 수 있게 만들어주는 스마트컨트랙트가 다르기 때문에, 프론트엔드에서 매번 새로운 페이지를 열 때마다 네트워크에서 각각의 정보를 받아오는 것은 사용성이 다소 떨어진다는 생각을 했다. 즉, 현재 보여지는 NFT가 거래가 가능한지 여부와 가격을 알려면 단순히 NFT의 메타데이터만 필요한 것이 아니라, 거래에 관련된 데이터도 필요하기 때문에 이를 서버 쪽에서 한 번에 보관하는 것이 더 낫다는 판단을 했다. 그리하여 온체인 데이터를 서버에서 받아와 클라리언트에 전해주는 식으로 구현하기로 결정했다.

내가 구현한 기능들

이러한 과정들을 통해서 내가 맡은 백엔드에서 해야될 일들을 가늠해볼 수 있었다.

  • 온체인 데이터를 관계형 데이터베이스에 저장할 때, 효율적이면서 추후에 다른 데이터 정보들도 추가될 때 문제가 없게 확장성 있는 구조로 스키마를 작성하고 구현한다.
  • NFT민팅과 거래에 사용될 스마트 컨트랙트를 이용해서, 주기적으로 데이터베이스를 온체인데이터(NFT 메타데이터, 소유 정보, 거래 정보)에 맞게 동기화한다.
  • 클라이언트가 렌더링할 때 활용할 수 있는 데이터들을 전달해줄 수 있는 API를 제공하며 이를 쉽게 활용할 수 있게 API 문서를 작성하고, 서버와 DB를 연동해서 원하는 정보를 가져올 수 있게 적절한 쿼리를 보낸다.

관계형 데이터베이스 구현

이 관계형 데이터베이스 스키마를 작성할 당시에는 최소한으로 구현할 목표치를 정해두기는 했지만, 다른 기능들도 추가적으로 구현할 수 있음을 염두에 두고 작성했다. 그래서 해당 NFT의 거래 기록 테이블(누가 민팅을 했고, 이제까지 거래된 가격과 거래된 시점), 우리가 제공한 스마트 컨트랙트가 아닌 다른 스마트 컨트랙트를 통해 만들어진 NFT 프로젝트 정보를 담기 위한 테이블, 해당 NFT의 rarity를 계산하고, explore에서 filtering 검색 기능을 사용하기 위해서 활용될 trait 정보를 저장하는 테이블이 현재까지 구현된 기능에서는 사용되진 않지만 미리 만들어둔 테이블들이다. 현재는 NFTs 테이블을 통해서 발행된 NFT 데이터들을 관리한다.


위의 데이터베이스 스키마는 dbdiagram.io/d에서 아래의 코드를 넣어서 만든 것이다.

Table NFTs {
  token_id int [pk]
  contract_address varchar(100)
  owner varchar(100)
  name varchar(100)
  description varchar(500)
  image_link varchar(200)
  price float
  room_number int
  trait1_id int
  trait2_id int
}

Ref: NFTs.contract_address > Contracts.contract_address

Table Contracts {
  contract_address varchar(100) [pk]
  contract_description varchar(500)
  title varchar(100)
  category varchar(100)
}

Ref: Trade_Histories.token_id > NFTs.token_id

Table Trade_Histories {
  history_id int [pk]
  token_id int
  price float
  from_address varchar(100)
  to_address varchar(100)
  transaction_hash varchar(100)
  block_height int
}

Ref: NFTs.trait1_id > Trait1.trait1_id

Table Trait1 {
  trait1_id int [pk]
  name varchar(100)
  count int
}

Ref: NFTs.trait2_id > Trait2.trait2_id

Table Trait2 {
  trait2_id int [pk]
  name varchar(100)
  count int
}

아래는 실제 mySQL에 사용되어 datbase를 만드는데 사용하는 코드이다.

CREATE TABLE NFTs (
    token_id INT,
    contract_address varchar(100),
    owner varchar(100),
    name varchar(100),
    description varchar(500),
    image_link varchar(200),
    price FLOAT,
    room_number INT,
    trait1_id INT,
    trait2_id INT,
    PRIMARY KEY(token_id)
);

CREATE TABLE Contracts (
    contract_address varchar(100),
    contract_description varchar(500),
    title varchar(100),
    category varchar(100),
    PRIMARY KEY(contract_address)
);

CREATE TABLE Trait1 (
    trait1_id INT AUTO_INCREMENT,
    name varchar(100),
    count INT,
    PRIMARY KEY(trait1_id)
);

CREATE TABLE Trait2 (
    trait2_id INT AUTO_INCREMENT,
    name varchar(100),
    count INT,
    PRIMARY KEY(trait2_id)
);

CREATE TABLE Trade_Histories (
    history_id INT AUTO_INCREMENT,
    token_id INT,
    price FLOAT,
    from_address varchar(100),
    to_address varchar(100),
    transaction_hash varchar(100),
    block_height INT,
    PRIMARY KEY(history_id)
);


ALTER TABLE NFTs ADD FOREIGN KEY (trait1_id) REFERENCES Trait1 (trait1_id);

ALTER TABLE NFTs ADD FOREIGN KEY (trait2_id) REFERENCES Trait2 (trait2_id);

ALTER TABLE Trade_Histories ADD FOREIGN KEY (token_id) REFERENCES NFTs (token_id);

ALTER TABLE NFTs ADD FOREIGN KEY (contract_address) REFERENCES Contracts (contract_address);

이를 표로 정리하면, 아래와 같다.

온체인데이터 동기화 구현

위에서 준비한 관계형 데이터베이스에 실제적으로 온체인 데이터를 활용해 채우기 위해서는, web3.js, axios, mysql을 이용해서 테이블들을 업데이트를 해야 한다. whaleContract는 NFT를 민팅하는 스마트 컨트랙트의 기능을 js에서 객체처럼 사용할 수 있게 하고, transferContract는 거래를 지원하는 스마트 컨트랙트의 기능을 js에서 객체처럼 사용할 수 있게 한다. infura를 이용해서 원격으로 노드에 접속할 수 있게 provider를 설정한다. provider는 간단히 설명하면, 이더리움 네트워크에 접속할 수 있게 하는 노드를 무엇으로 할지 설정해서, 그 노드를 이용해서 스마트 컨트랙트를 활용할 수 있게 해준다.

// ddatabase/index.js
const rpcURL = "https://ropsten.infura.io/v3/0e4ca7c98aff4188997b4dfed819da2d"

const Contract = require("web3-eth-contract");
Contract.setProvider(rpcURL);

const whale = require("./abi/whaleNFT.json");
const whaleAddr = "0xe23E30b939a085a2d92f50C803F574c58912162B";
const whaleContract = new Contract(whale, whaleAddr);

const transfer = require("./abi/transferNFT.json");
const transferAddr = "0xD5e92129477fD369C14411919A6833c05C214275"
const transferContract = new Contract(transfer, transferAddr);

const db = require("./db");
const axios = require("axios");

const Web3 = require('web3');
const web3 = new Web3(rpcURL);

실제로는 데이터베이스를 동작하는 프로그램은 꺼지지 않고 계속해서 작동할 것이므로, 처음 프로그램이 시작될 때만 테이블들을 초기화하고 그후에는 똑같은 사이클이 반복되게 만들었다. 동기화 과정은 section으로 구분했다.

section1은 발행된 모든 NFT들을, tokenId를 기준으로 해당 NFT를 소유하고 있는 지갑 주소와 매칭해서 저장한다.

    let nftOwners = {};
    for (let i=1; i<=LastIdx; i++){
        let info = await whaleContract.methods.ownerOf(i).call();
        nftOwners[i] = {};
        nftOwners[i]['owner'] = info;
    }
    prevLastIdx = LastIdx;
    let checkIdx = LastIdx + 1;
    try {
        while (true){
            let checkInfo = await whaleContract.methods.ownerOf(checkIdx).call();
            if (checkInfo[1] == 'x'){
                nftOwners[checkIdx] = {};
                nftOwners[checkIdx]['owner'] = checkInfo;
                LastIdx = checkIdx;
                checkIdx++;
                continue;
            }
        }
    }
    catch (err){
    }

section2는 tokenId를 이용해서 tokenURI에 저장되어 있는 메타데이터들을 저장한다.

    for (let i = 1; i <= LastIdx; i++){
        let tokenURI = await whaleContract.methods.tokenURI(i).call();
        let res = await axios.get(tokenURI);
        nftOwners[i]['name'] = res.data.name;
        nftOwners[i]['description'] = res.data.description;
        nftOwners[i]['image'] = res.data.image;
    }

section3은 앞에서 저장한 정보들을 이용해서 기존의 데이터베이스를 업데이트한다.

    for (let i = 1; i <= prevLastIdx; i++){
        const updateQuery = `UPDATE NFTs
                                SET owner='${nftOwners[i]['owner']}', name='${nftOwners[i]['name']}',
                                description='${nftOwners[i]['description']}', image_link='${nftOwners[i]['image']}',
                                price='0', room_number='-1'
                                WHERE token_id='${i}';`;
        db.query(updateQuery, (error, result) => {
            if (error) {
                console.log(error);
            }
        });
    }
    for (let i = prevLastIdx+1; i <= LastIdx; i++){
        const addQuery = `INSERT INTO NFTs (token_id, owner, name, description, image_link, price, room_number)
                            VALUES ('${i}', '${nftOwners[i]['owner']}', '${nftOwners[i]['name']}',
                            '${nftOwners[i]['description']}', '${nftOwners[i]['image']}', '0', '-1');`;
        db.query(addQuery, (error, result) => {
            if (error) {
                console.log(error);
            }
        });
    }

section4는 이제까지는 NFT의 소유 지갑과 메타데이터를 업데이트 한 것인데, 우리는 거래 기능도 제공해야 하므로 해당 NFT의 거래 가능 여부와 가격 정보를 저장한다.

    let ahead = [];
    let rooms = {}; // key is token_id
    let roomNum = await transferContract.methods.roomCount().call();
    for (let i = 0; i < roomNum; i++){
        let roomInfo = await transferContract.methods.roomInfo(i).call();
        if (roomInfo['0'] == 1){
            let tokenId = roomInfo['2'];
            if (ahead.includes(tokenId)){
                rooms[tokenId]['price'] = web3.utils.fromWei(roomInfo['3'], 'ether');
                rooms[tokenId]['roomNum'] = i;
            }
            else {
                ahead.push(tokenId);
                rooms[tokenId] = {};
                rooms[tokenId]['price'] = web3.utils.fromWei(roomInfo['3'], 'ether');
                rooms[tokenId]['roomNum'] = i;
            }
        }
        if (roomInfo['0'] == 2 && ahead.includes(roomInfo['2'])){
            rooms[roomInfo['2']]['price'] = 0;
            rooms[roomInfo['2']]['roomNum'] = -1;
        }
    }

section5는 저장한 거래 정보를 이용해서 기존의 데이터베이스를 업데이트한다.

    for (let i = 0; i < ahead.length; i++){
        let tokenId = ahead[i];
        const updateQuery = `UPDATE NFTs
                                SET price='${rooms[tokenId]['price']}', room_number='${rooms[tokenId]['roomNum']}'
                                WHERE token_id='${tokenId}';`;
        db.query(updateQuery, (error, result) => {
            if (error) {
                console.log(error);
            }
        });
    }

section6는 이 전체 사이클이 잘 마무리 되었음을 의미한다.

실제로 작동을 시키면 아래와 같이 콘솔로 데이터들이 어떻게 들어오는지 확인할 수 있다.

웹서버 API 구현

API 문서를 작성할 때는 어떤 기능을 어느 정도 수준으로 구현할지 확실하지 않아서 일단 필요할만한 정보들을 전부 보내주는 식으로 작성했는데, 페이지에 따라서 딱 사용하는 정보만을 담아서 불필요한 정보들은 빼고 보낼 수 있게 수정할 수 있을 것이다.

지금은 응답해야 할 상황이 단순해서 간단하게 분기점을 나누었지만, 기능이 복잡해질 때를 대비해서 함수명을 더 직관적으로 바꿀 필요가 있다고 생각한다. 아래는 서버에 요청이 들어올 때, 데이터베이스와 연동해서 필요한 정보들을 응답하는 코드이다.

// webserver/controller/nftController.js
const models = require("../models");

module.exports = {
    findAll: (req, res) => {
        if (req.query.account_address != undefined){
            models.get1(req.query.account_address, (error, result) => {
                if (error) {
                    return res.status(500).send('Internal Server Error');
                }
                else {
                    return res.status(200).json(result);
                }
            });
        }
        else if (req.query.token_id !== undefined){
            models.get2(req.query.token_id, (error, result) => {
                if (error) {
                    return res.status(500).send('Internal Server Error');
                }
                else {
                    return res.status(200).json(result);
                }
            })
        }
        else {
            models.get0((error, result) => {
                if (error) {
                    return res.status(500).send('Internal Server Error');
                }
                else {
                    return res.status(200).json(result);
                }
            })
        }
    },
};
// webserver/models/index.js
const db = require("../db");

module.exports = {
    get1 : (addr, callback) => {
        const queryString = `SELECT token_id, owner, name, description, image_link, price, room_number
                                FROM NFTs
                                WHERE owner='${addr}';`;
        db.query(queryString, (error, result) => {
            console.log(result);
            callback(error, result);
        });
    },
    get2 : (tokenId, callback) => {
        const queryString = `SELECT token_id, owner, name, description, image_link, price, room_number
                                FROM NFTs
                                WHERE token_id='${tokenId}';`;
        db.query(queryString, (error, result) => {
            console.log(result);
            callback(error, result);
        });
    },
    get0 : (callback) => {
        const queryString = `SELECT token_id, owner, name, description, image_link, price, room_number FROM NFTs`;
        db.query(queryString, (error, result) => {
            console.log(result);
            callback(error, result);
        });
    }

}

실제로 작동을 하면, 서버에 요청이 들어올 때 다음과 같이 데이터를 잘 전달해주는 것을 확인할 수 있다.

API 문서를 작성하면서 느꼈던 것 중 하나는, 생각보다 API 문서를 잘 만들어야 한다는 압박감이 생긴다는 것이다. 왜냐면 아직 데이터베이스나 서버를 완성 상태로 만들어 놓기 전에, 클라이언트에서는 API 문서를 보고 어떤 것을 응답 받는지 확인해서 그 데이터들을 토대로 렌더링 할 수 있게 구현해 놓기 때문에 나중에 API 문서를 크게 수정하게 되면 프론트엔드를 개발하는 분이 두 번 일을 하는 상황이 발생하기 때문이다. 그래도 이렇게 API 문서를 작성해두고 서버를 완성하면 더미 데이터를 활용해서 실제 데이터를 받아올 수 있는 기능을 구현하기 전에, 클라이언트에서는 자체적으로 테스트를 하면서 코딩을 할 수 있었다.

데모

Main - 발행된 NFT들이 순서대로 표시된다.
지갑연결 - 오른쪽 상단의 지갑을 클릭하면 메타마스크 지갑이 연동되는 것을 확인할 수 있다.
Explore - 발행된 전체 NFT들이 표시된다.
MyPage - 소유한 NFT들이 표시된다.
Create - 이미지 업로드와 메타데이터 입력이 가능하고, 이를 통해 사이트에 연동된 지갑으로 민팅을 할 수 있다.
Buy - 판매 중인 NFT를 구매할 수 있다.
Sell - 소유 중인 NFT를 판매할 수 있다.
whaleNFT - 실제로 Ropsten Testnet Network에 올라가 있는 NFT 스마트 컨트랙트가 잘 작동됨을 확인할 수 있다.
transferNFT - 마찬가지로 Ropsten Testnet Network에 올라가 있는 NFT 스마트 컨트랙트의 Transactions을 통해서 잘 작동됨을 확인할 수 있다.


Keep

  • 처음부터 규칙을 정해서 팀원들과 소통하는 것이 좋았다. 같이 프로젝트를 진행하면서 발생할 수 있을 법한 애매모호한 상황들을 미리 정리하고 시작하는 것이 도움되는 것 같다. 그리고 구글미트, 디스코드, 노션과 같은 툴을 활용하여 팀의 소통을 활발하게 하여 생산성을 높이는 것도 좋았다.
  • 정해진 시간마다 회의를 함으로써, 현재 하고 있는 일을 공유해서 문제가 있으면 같이 이야기해보면서 서로 도울 수 있어서 좋았다. 그렇지 않고 혼자서 해결해보려고 붙잡고 있으면서 끙끙 앓다보면 고생은 하는데 성과는 나오지 않는 악순환에 빠질 수 있다. 그리고 다음 회의 때까지의 목표치가 계속해서 공유되므로, 단기적인 데드라인이 생겨서 각자 맡은 부분을 개발하면서 느슨해지는 상황을 예방할 수 있었다.
  • 회의, 목표와 개발 상황, 개발 문서, 문제 해결 등을 문서화해서 공유하는 것이 꽤나 좋았다.
  • 미완성인 부분이 있어도, 다른 부분에서 그 문제를 해결하여 겉으로 동작하기에는 미완성인 부분을 확인할 수 없게 만들 수 있음을 알았다. 항상 완벽한 개발 조건이 갖춰지는 것은 아니므로, 상황에 따라 이를 적절히 활용하는 것이 필요하다는 것을 느낄 수 있었다.

Problem

  • 다양한 모듈을 가져와서 사용하는데 단순한 기능을 구현하고 싶어도, 그 모듈이 일단 동작하기 위해서 세팅을 하는 과정 자체가 어려웠다. 이를 더 능숙하게 하는 연습이 필요하다. 또한, 튜토리얼 문서와 정리된 docs를 읽고, 관련된 설명이 잘되어 있는 사이트들을 구글링하는 것도 필요하다.
  • API문서를 먼저 작성해야 프론트엔드가 이걸 이용해서 진행할 수 있었다. 각각 역할이 나누어져 있지만 어떤 부분은 먼저 끝내 놓아야, 다른 분들이 진행할 수 있는 식의 상황이 발생해서 이러한 지점들을 잘 체크하는 것이 중요함을 느꼈다.
  • 데이터를 동기화하려는 것을 구현하다보니, 스마트컨트랙트에서 함수를 추가하면 속도가 개선될 것을 알게 되었다. 그런데 시간이 한정되어 있기 때문에 일단 구현 완성을 목적을 두고 그대로 진행하였다. 이렇듯 처음에 계획을 세울 때와 다르게, 실제 진행을 하면서 다른 기능들을 다시 수정해야 할 상황들이 돌발적으로 발생하는 것이 어려웠다.
  • 똑같은 기능을 하더라도 그것을 어떻게 구현할 것인가를 선택해야 하는 상황에서 팀원들과의 충분한 대화와 경험치가 필요함을 깨달았다. 예를 들어서, 온체인데이터를 저장할 것인지, 사용자가 보내온 데이터를 저장할 것인지. 프론트엔드에서는 해당 이미지들을 블록체인 네트워크에서 바로 가져올 것인지, 서버에서 데이터를 가져올 것인지 등의 상황이 있었다.
  • 어떤 것을 구현하고 싶다는 생각이 들어도, 그것을 바로 구현할 수 있는 능력이 부족해서 당장 개발에 들어가는 것이 아니라, 해당 개념을 공부하고 실험해보면서 구현해야 하는 어려움이 있었다.

Try

  • 현재는 온체인데이터를 주기적으로 계속 받아와 데이터베이스를 동기화하는 프로그램, 클라이언트의 요청에 맞게 데이터베이스에서 정보를 가져와 응답하는 서버, 브라우저를 통해 접속하는 클라이언트를 각각 따로 실행시켜줘야 하고 로컬에서만 사용할 수 있다. 이를 직접 배포해서 해당 기능을 로컬 컴퓨터가 아닌 누구나 접속 가능한 형태로 만들어야 한다.
  • 추가적인 기능들을 구현해보고 싶다. 스마트컨트랙트 거래취소, 트랜잭션 히스토리를 통해 해당 NFT의 역대 거래 정보 제공, NFT의 traits들의 정보를 합산하여 rarity 정보 제공, 판매자가 판매를 올리지 않아도 구매를 원하는 사람이 가격을 제안하는 기능 제공, NFT를 경매 방식으로 판매하는 기능 등도 구현해보고 싶다.
  • 현재는 approve와 sell을 두번 나누어 판매 등록이 이루어지는데, 이를 서버가 대행해주는 방식으로 한 번만 눌러도 되게끔 서버에서 계정을 만들어서 스마트컨트랙트에 트랜잭션을 보내서 처리하는 기능도 구현해보고 싶다.
  • 정보를 동기화할 때, 기존에 있는 모든 정보들과 온체인 데이터들을 일일이 대조해야 하고, 새로운 데이터들도 있는지 확인하는 과정을 거치는데 이를 좀 더 효율적으로 해내는 방법을 알고 싶다.
profile
I can do this all day

1개의 댓글

comment-user-thumbnail
2022년 7월 27일

안녕하세요. 글을 관심 있게 보고 있고 상당히 도움을 많이 받고 있는 블록체인 업계 재직자입니다. 혹시 관련 업계에서 프로젝트 참여 및 관련 회사에서 일하는 것에 관심 있으시면 편하신 시간에 편하신 방식으로 이야기 잠깐 나눌 수 있을 지 궁금합니다. 연락처를 찾지 못해 댓글로 남기는 점 양해부탁드립니다.

답글 달기