'NFT'에 해당되는 글 2건

환경 :  버추얼박스 , Ubuntu 20.04.1 LTS,  vscode, 클레이튼

인프런 강좌에 NFT 개발 환경 구축을 위해서 클레이튼을 사용 하던데

클레이튼 구축이라고 보면  옳다.

 

 출처 ㅣ https://github.com/kkagill/crypto-ytt-starter.git

 

nodejs 설치 되어 있지 않는 default한 우분투20에서 

npm만 설치 해준다.

 

#apt update
#apt install npm 
# nodejs -v
 v10.19.0

 

인프런 강의 따라 하는디 디펜던시 하도 심해서 내가 환경 구축에 대한 가이드를 작성중이다.

무조건  nodejs 버전은 10대로 가야 된다.

바로 노드 설치하면 14 , 16이 설치 되어 버리니   npm만 설치하면 nodejs 버전이 10.19가 깔린다.

 

#git clone https://github.com/kkagill/crypto-ytt-starter.git
#cd crypto-ytt-starter/
#npm cache clean --force
#npm install

#npm install -g npm@6.4.1
#npm install -g truffle
#truffle version
Truffle v5.3.2 (core: 5.3.2)
Solidity v0.5.16 (solc-js)
Node v10.19.0
Web3.js v1.3.5

#npm install fs

 

vscode로 버추얼박스에 설치된 이미지에 remote develoment를 실행시킨다.

remote developer 접속

 

 

 

src 소스 수정

webpack.config.js를 아래와 같이 수정해주고

const webpack = require('webpack')

const path = require('path')

const fs = require('fs')

const CopyWebpackPlugin = require("copy-webpack-plugin");

const host = process.env.HOST || '0.0.0.0';

 

module.exports = {

  entry: "./src/index.js",

  mode: 'development',

  node: {

    fs: 'empty',

    net: 'empty',

  },

  output: {

    filename: "index.js",

    path: path.resolve(__dirname'dist')   

  },

  plugins: [   

    new webpack.DefinePlugin({

      DEPLOYED_ADDRESS: JSON.stringify(fs.readFileSync('deployedAddress''utf8').replace(/\n|\r/g"")),

      DEPLOYED_ABI: fs.existsSync('deployedABI') && fs.readFileSync('deployedABI''utf8'),

 

      DEPLOYED_ADDRESS_TOKENSALES: JSON.stringify(fs.readFileSync('deployedAddress_TokenSales''utf8').replace(/\n|\r/g"")),

      DEPLOYED_ABI_TOKENSALES: fs.existsSync('deployedABI_TokenSales') && fs.readFileSync('deployedABI_TokenSales''utf8')

    }),

    new CopyWebpackPlugin([{ from: "./src/index.html"to: "index.html"}])

  ],

  devServer: { contentBase: path.join(__dirname"dist"), compress: true,disableHostCheck: true }

}

 

index.js

import Caver from "caver-js";

import { Spinner } from 'spin.js';

 

const config = {

  rpcURL: 'https://api.baobab.klaytn.net:8651'

}

const cav = new Caver(config.rpcURL);

//const yttContract = new cav.klay.Contract(DEPLOYED_ABI, DEPLOYED_ADDRESS);

 

const App = {

  auth: {

    accessType: 'keystore',

    keystore: '',

    password: ''

  },

 

  //#region 계정 인증

  

  start: async function () {

    const walletFromSession = sessionStorage.getItem('walletInstance');

    if (walletFromSession) {

      try {

        cav.klay.accounts.wallet.add(JSON.parse(walletFromSession));

        this.changeUI(JSON.parse(walletFromSession));

      } catch (e) {

        sessionStorage.removeItem('walletInstance');

      }

    }

  },

 

  handleImport: async function () {

    const fileReader = new FileReader();

    fileReader.readAsText(event.target.files[0]);

    fileReader.onload = (event=> {

      try {

        console.log("event.target.result"event.target.result);

        /*

        if (!this.checkValidKeystore(event.target.result)) {

          $('#message').text('유효하지 않은 keystore 파일입니다.');

          return;

        }

        */

        this.auth.keystore = event.target.result;

        $('#message').text('keystore 통과. 비밀번호를 입력하세요.');

        document.querySelector('#input-password').focus();

      } catch (event) {

        $('#message').text('유효하지 않은 keystore 파일입니다.');

        return;

      }

    }

  },

 

  handlePassword: async function () {

    this.auth.password = event.target.value;

  },

 

  handleLogin: async function () {

    if (this.auth.accessType === 'keystore') {

      try {

        const privateKey = cav.klay.accounts.decrypt(this.auth.keystorethis.auth.password).privateKey;

        this.integrateWallet(privateKey);

      } catch (e) {

        $('#message').text('비밀번호가 일치하지 않습니다.');

      }

    }

  },

 

  handleLogout: async function () {

    this.removeWallet();

    location.reload();

  }, 

 

  getWallet: function () {

    if (cav.klay.accounts.wallet.length) {

      return cav.klay.accounts.wallet[0];

    }

  },

 

  checkValidKeystore: function (keystore) {

    const parsedKeystore = JSON.parse(keystore);

    const isValidKeystore = parsedKeystore.version &&

      parsedKeystore.id &&

      parsedKeystore.address &&

      parsedKeystore.crypto;

 

    return isValidKeystore;

  },

 

  integrateWallet: function (privateKey) {

    const walletInstance = cav.klay.accounts.privateKeyToAccount(privateKey);

    cav.klay.accounts.wallet.add(walletInstance)

    sessionStorage.setItem('walletInstance'JSON.stringify(walletInstance));

    this.changeUI(walletInstance);

  },

 

  reset: function () {

    this.auth = {

      keystore: '',

      password: ''

    };

  },

 

  changeUI: async function (walletInstance) {

    $('#loginModal').modal('hide');

    $("#login").hide();

    $('#logout').show();

    // ...

    $('#address').append('<br>' + '<p>' + '내 계정 주소: ' + walletInstance.address + '</p>');  

    // ...   

    // ...

    // ...

  },

 

  removeWallet: function () {

    cav.klay.accounts.wallet.clear();

    sessionStorage.removeItem('walletInstance');

    this.reset();

  }, 

 

  showSpinner: function () {

    var target = document.getElementById('spin');

    return new Spinner(opts).spin(target);

  },

  //#endregion

 

  checkTokenExists: async function () {   

   

  },

 

  createToken: async function () {   

    

  },  

 

  mintYTT: async function (videoIdauthordateCreatedhash) {    

    

  },    

  

  displayMyTokensAndSale: async function (walletInstance) {       

   

  },   

 

  displayAllTokens: async function (walletInstance) {   

    

  },

   

  renderMyTokens: function (tokenIdyttmetadata) {    

    

  },

 

  renderMyTokensSale: function (tokenIdyttmetadataprice) { 

   

  },

 

  renderAllTokens: function (tokenIdyttmetadata) {   

     

  },    

 

  approve: function () {

      

  },

 

  cancelApproval: async function () {

          

  },

 

  checkApproval: async function(walletInstance) {

       

  },

 

  sellToken: async function (button) {    

       

  },

 

  buyToken: async function (button) {

      

  },

 

  onCancelApprovalSuccess: async function (walletInstance) {

  

  },     

 

  isTokenAlreadyCreated: async function (videoId) {

   

  },

 

  getERC721MetadataSchema: function (videoIdtitleimgUrl) {

    

  },

 

  getBalanceOf: async function (address) {

   

  },

 

  getTokenOfOwnerByIndex: async function (addressindex) {

  

  },

 

  getTokenUri: async function (tokenId) {

    

  },

 

  getYTT: async function (tokenId) {

   

  },

 

  getMetadata: function (tokenUri) {

   

  },

 

  getTotalSupply: async function () {

   

  },

 

  getTokenByIndex: async function (index) {

    

  },  

 

  isApprovedForAll: async function (owneroperator) {

 

  },  

 

  getTokenPrice: async function (tokenId) {

   

  },  

 

  getOwnerOf: async function (tokenId) {

   

  },

 

  getBasicTemplate: function(templatetokenIdyttmetadata) {  

  

  }

};

 

window.App = App;

 

window.addEventListener("load"function () {

  App.start(); 

  $("#tabs").tabs().css({'overflow': 'auto'});

});

 

var opts = {

  lines: 10// The number of lines to draw

  length: 30// The length of each line

  width: 17// The line thickness

  radius: 45// The radius of the inner circle

  scale: 1// Scales overall size of the spinner

  corners: 1// Corner roundness (0..1)

  color: '#5bc0de'// CSS color or array of colors

  fadeColor: 'transparent'// CSS color or array of colors

  speed: 1// Rounds per second

  rotate: 0// The rotation offset

  animation: 'spinner-line-fade-quick'// The CSS animation name for the lines

  direction: 1// 1: clockwise, -1: counterclockwise

  zIndex: 2e9// The z-index (defaults to 2000000000)

  className: 'spinner'// The CSS class to assign to the spinner

  top: '50%'// Top position relative to parent

  left: '50%'// Left position relative to parent

  shadow: '0 0 1px transparent'// Box-shadow for the lines

  position: 'absolute' // Element positioning

};

 

index.html

<!DOCTYPE html>

<html lang="en">

 

<head>

  <meta charset="utf-8">

  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <meta name="theme-color" content="#000000">

  <title>크립토 유튜브 썸네일</title>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

  <script src="index.js"></script>

  <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet'>

  <link href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet">

</head>

 

<body>

  <div class="container">

    <div class="row">

      <div class="col-md-8 col-md-offset-2">    

        <h3 class="text-center">크립토 <span style="color: red;">유튜브</span> 썸네일</h1>

        <h3 class="text-center">

          <code>Collect, Buy and Sell YouTube Thumbnails!</code>           

          <button type="button" 

                  class="btn btn-info pull-right" 

                  id="login" 

                  data-toggle="modal"

                  data-target="#loginModal">

                  로그인

          </button>

          <button type="button" 

                  class="btn btn-info pull-right" 

                  id="logout" 

                  style="display: none;"

                  onclick="App.handleLogout()">

                  로그아웃

          </button>

        </h3>         

        <div class="text-center" id="address"></div>         

      </div>

    </div>  

 

    <div id="spin"></div>   

  </div>  

 

  <!-- Modals -->

 

  <div class="modal fade" tabindex="-1" role="dialog" id="loginModal">

    <div class="modal-dialog modal-sm">

      <div class="modal-content">         

        <div class="modal-body">

          <div class="form-group">

            <label for="keystore">Keystore</label>

            <input type="file" id="keystore" onchange="App.handleImport()">

          </div>

          <div class="form-group">

            <label for="input-password">비밀번호</label>

            <input type="password" class="form-control" id="input-password" onchange="App.handlePassword()">

            <p class="help-block" id="message"></p>

          </div>

        </div>

        <div class="modal-footer">

          <button type="button" class="btn btn-default" data-dismiss="modal">닫기</button>

          <button type="button" class="btn btn-primary" onclick="App.handleLogin()">제출</button>

        </div>

      </div><!-- /.modal-content -->

    </div><!-- /.modal-dialog -->

  </div><!-- /.modal -->

</body>

 

<!-- Templates -->

 

</html>

 

<style>

  @keyframes spinner-line-fade-more {

    0%, 100% {

      opacity0;

    }

    1% {

      opacity1;

    }

  }

 

  @keyframes spinner-line-fade-quick {

    0%, 39%, 100% {

      opacity0.25;

    }

    40% {

      opacity1;

    }

  }

 

  @keyframes spinner-line-fade-default {

    0%, 100% {

      opacity0.22;

    }

    1% {

      opacity1;

    }

  }

 

  .panel-footer {

    height56px;   

    overflowhidden;

  }

</style>

 

 

 

소스를 위 소스로 변경해 준다.

npm run dev로 프로젝트를 실행하면 locahost에서는 접속이 가능하다.

root@server:~/crypto-ytt-starter# npm run dev

> crypto-youtube-thumbnail-starter@0.0.1 dev /root/crypto-ytt-starter
> webpack-dev-server

ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /root/crypto-ytt-starter/dist
ℹ 「wdm」: Hash: e0e5fdf71534985470fa

외부 접속 가능하게 ngrok로 포워딩 해준다.

#snap install ngrok
#ngrok http 8080

 

 

포워딩 해준 주소 접속하면 접속이 가능하다.

리엑트 개발자들은 더 좋은 방법이 있을거 같다.

 

ERC721등 여러 함수들은 직접 구현하길 바란다.

 

'NFT' 카테고리의 다른 글

NFT EIP-721 Non-Fungible Token Standard  (0) 2021.03.11
블로그 이미지

iesay

,

'NFT' 카테고리의 다른 글

NFT 클레이튼 개발 환경 구축  (0) 2021.04.22
블로그 이미지

iesay

,