Ganache스캔, 숙박예약 관리 Dapp 만들어보기

Knave·2021년 8월 5일

BC

목록 보기
10/10

AWS 환경 구성해보기

AWS Geth

이더리움 런던 하드포크

이더리움 런던 하드포크 12시간 전, 변화하는 점들

현재로서는 이더리움 Trx별로 가스비를 얼마를 지불해야할지, 어느정도가 최소 요구되는지 확인하기가 어려움, 따라서 자신이 원하는 Trx를 서명할때 불필요한 이더가 지불되는 경우가 많음.

런던 하드포크를 통해서는 요구되는 예상 가스비를 알 수 있게되고, 이를 통해 사용자는 가스비를 합리적으로 계산하여 지불이 가능하다. 기존에는 초과하여 지불되는 가스 팁의 존재는 변경사항 적용 후에는 소각되는 시스템으로 된다. 블록의 가스리밋 또한 Trx처리량에 따라? 조절이 되면서 Trx처리량이 많아서 혼잡할 경우 basic fee 또한 올라가고, 처리량이 적다면 fee를 적게 내도 되는 자동적으로 조절되는(?) 생태계가 형성된다. 채굴자의 입장에서는 가스비가 이전보다 줄어들기 때문에 반대할 수 있으나, 부테린이 보기에도 현재는 이더 발행량이 너무 과한 상황이고. 가스비가 줄고 발행량이 줄게되면 이더의 가치가 정상화되면서 올라갈 것이므로(deflation) 채굴자에 나쁜 소식만은 아니라고 생각한다.

채굴 난이도의 조정도 논의 되었었는데, 채굴 난이도를 높여서 어느정도 이더의 발행량도 조절하고, POW에서 POS로 변화하는 방식으로의 전환은 11월로 연기되었다. 현재도 EHT beacon chain에서 POS방식을 테스트하고 있는데 참여자의 수만 봐도 성공적으로 보이고 결과도 좋아보여서 아마 빠른 시일내에 체인을 merge 시키지 않을까 싶다.

나만의 이더스캔(Ganache Scan) 만들기

<!DOCTYPE html>
<html lang="en">

<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Ganache Scan-Pure</title>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <script type="text/javascript" src="./bignumber.min.js"></script>
    <script type="text/javascript" src="./web3.js"></script>

    <script type="text/javascript">
        if (typeof web3 !== "undefined") {
            web3 = new Web3(web3.currentProvider);
        } else {
            // set the provider you want from Web3.providers
            web3 = new Web3(
                new Web3.providers.HttpProvider("http://localhost:8545")
            );
        }

        console.log(web3);

        if (web3.isConnected()) {
            console.log("connected");
        } else {
            console.log("not connected");
        }

        let accounts = web3.eth.accounts;
        console.log(accounts);

        let balance = web3.eth.getBalance(accounts[0]);
        console.log(web3.fromWei(balance).toNumber() + "ETH");

        for (var i = 0; i < accounts.length; i++) {
            document.getElementById().innerHTML = accounts[i];
            document.getElementById().innerHTML =
                web3.fromWei(web3.eth.getBalance(accounts[i])).toNumber() + "ETH";
        }

        function identifyInput() {

            let input = document.getElementById("search_input").value;
            console.log(input);

            if (input.length === 66) {
                let T = []
                web3.eth.getTransaction(input, function (error, tx) {
                    console.log(tx);
                    T.push("<table>")
                    T.push("<th colspan=\"2\">" + "Transaction Detail" + "</th>")
                    for (j in tx) {
                        console.log(j)
                        T.push("<tr>");
                        T.push("<td>" + j + "</td>");

                        if (j == "blockNumber") {
                            T.push('<td id="second_search">' + tx[j] + "</td>");
                        } else if (j == "from") {
                            T.push('<td id="from">' + tx[j] + '</td>');
                        } else if (j == "to") {
                            T.push('<td id="to">' + tx[j] + '</td>');
                        } else {
                            T.push('<td>' + tx[j] + "</td>");
                        }
                        T.push("</tr>");
                    }
                    document.getElementById("scan_result").innerHTML = T.join("");
                });
            } else if (input.length === 42) {
                document.getElementById("scan_result").innerHTML = "  Balance : " +
                    web3.fromWei(web3.eth.getBalance(input)).toNumber() +
                    "  ETH"
            } else if (typeof input == 'string') {
                let D = [];
                web3.eth.getBlock(input, function (error, block) {
                    console.log(block);
                    D.push("<table>")
                    D.push('<th colspan="2">' + "Block Detail" + "</th>")
                    for (i in block) {
                        D.push("<tr>");
                        D.push("<td>" + i + "</td>");
                        if (i == "transactions") {
                            D.push('<td id="second_search">' + block[i] + '</td>');

                        } else {
                            D.push('<td>' + block[i] + '</td>');
                        }
                        D.push("</tr>");
                    }
                    D.push("</table>")
                    document.getElementById("scan_result").innerHTML = D.join("");
                });
            }
        }

        function testAccFrom() {
            let addressFrom = document.getElementById("from").innerText
            document.getElementById("scan_result").innerHTML = "  Balance : " +
                web3.fromWei(web3.eth.getBalance(addressFrom)).toNumber() +
                "  ETH"
        }

        function testAccTo() {
            let addressTo = document.getElementById("to").innerText
            document.getElementById("scan_result").innerHTML = "  Balance : " +
                web3.fromWei(web3.eth.getBalance(addressTo)).toNumber() +
                "  ETH"
        }

        function test() {
            let second_input = document.getElementById("second_search").innerText

            if (second_input.length === 66) {
                let T = []
                web3.eth.getTransaction(second_input, function (error, tx) {
                    console.log(tx);
                    T.push("<table>")
                    T.push("<th colspan=\"2\">" + "Transaction Detail" + "</th>")
                    for (j in tx) {
                        console.log(j)
                        T.push("<tr>");
                        T.push("<td>" + j + "</td>");

                        if (j == "blockNumber") {
                            T.push('<td id="second_search">' + tx[j] + "</td>");
                        } else if (j == "from") {
                            T.push('<td id="from">' + tx[j] + '</td>');
                        } else if (j == "to") {
                            T.push('<td id="to">' + tx[j] + '</td>');
                        } else {
                            T.push('<td>' + tx[j] + "</td>");
                        }
                        T.push("</tr>");
                    }
                    document.getElementById("scan_result").innerHTML = T.join("");
                });

            } else if (typeof second_input == 'string') {
                let D = [];
                web3.eth.getBlock(second_input, function (error, block) {
                    console.log(block);
                    D.push("<table>")
                    D.push('<th colspan="2">' + "Block Detail" + "</th>")
                    for (i in block) {
                        D.push("<tr>");
                        D.push("<td>" + i + "</td>");
                        if (i == "transactions") {
                            D.push('<td id="second_search">' + block[i] + '</td>');

                        } else {
                            D.push('<td>' + block[i] + '</td>');
                        }
                        D.push("</tr>");
                    }
                    D.push("</table>")
                    document.getElementById("scan_result").innerHTML = D.join("");
                });
            }
        }
    </script>

    <title>Document</title>
</head>

<body>
    <div>
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">Ganache Scan</a>
        </nav>
        <header class=" bg-gradient text-white" id="bg_need">
            <div class="container px-4 text-center">
                <h1 class="fw-bolder mb-5">Welcome to Ganache Scan</h1>

                <div class="mb-5">
                    <input type="text" id="search_input" value="" placeholder="Blcok-Num,TRX,Account">
                    <button onclick="identifyInput()">Search</button>
                </div>
            </div>
        </header>
    </div>
    <section>
        <div id="scan_result" class="row gx-4 justify-content-center pb-5">
        </div>
        <footer class="text-white fixed-bottom" id="bg_need">
            <div class="container px-3 text-center">
                <h6>Scan</h6>
            </div>
        </footer>
    </section>
</body>
<style>
    table,
    th,
    tb {
        border-collapse: collapse;
        max-width: 800px;
    }

    table {
        width: 100%;
        max-width: 800px;
    }

    th,
    td {
        border: 1px solid #ddd;
        padding: 10px;
        max-width: 800px;
        word-break: break-all;
    }

    #scan_result {
        width: 800px;
        word-break: break-all;
        margin: auto
    }

    #bg_need {
        background-color: #293961;

    }
</style>

</html>

기본적인 기능이 구현된 가나슈 스캔이다.

호텔 예약, 관리 Dapp 만들어보기

이제 값을 읽어오는것에서 나아가, 체인에 새 데이터를 쓰고, 변화시키고, 다시 읽어오는 것 까지 해보는 것을 구현해보자

  • 환경 설정

web3.js - Ethereum JavaScript API - web3.js 1.0.0 documentation

web3를 업데이트 하기 위해 위 링크 속 개발자들의 git hub으로 이동하여 최신 버전의 web3 문서를 열고, html형식으로 열린 문서를 우클릭으로 다운로드 하여 web3.js형식으로 생성한다.

그 다음 생성한 web3.js를 새로 Dapp html을 만들 폴더에 같이 넣어 놓는다.

Dapp 개발을 위한 코드들은 아래의 코드를 참조하여 작성하면 된다.

<!Doctype html>
<html>
<title>Room Share</title>

<head>
    <meta charset="UTF-8">

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">


    <script src="./web3.min.js"></script>

<body
    style="background-image: url(https://images.unsplash.com/photo-1428366890462-dd4baecf492b?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1868&q=80); background-repeat: no-repeat; background-size : cover">
    <div>
        <nav class="navbar navbar-expand-lg navbar-light">
            <div class="container-fluid">
                <h1 class="badge bg-warning">Hotel</h1>
        </nav>
        <header class=" bg-gradient text-white" id="bg_need">
            <div class="container px-4 text-center">
                <h1 class="fw-bolder mb-5">
                    Welcome</h1>


            </div>
        </header>
    </div>
    <div id="left-box" class="ms-lg-5" style="width:600px; ">
        <div>
            <button type="button" class="btn-dark mb-3" onclick="init()">Connect Wallet</button>
            <input class="mb-3" type="text" id="account" value=""></input>


        </div>
        <div>
            <button type="button" class="btn-dark" onclick="getContract()">Get Contract Instance</button>
            <input type="text" id="contract" value=""></input>

        </div>
        <div class="mt-5">
            <input type="text" id="room_search" value="">
            <button type="button" class="btn-btn-dark" onclick="chkStatus()">Search</button>
            <br>
            <button type="button" class="btn-dark" disabled="disabled">Room Status</button>
            <input type="text" id="room_Status" value=""></input>
            <br>
            <button type="button" class="btn-dark" disabled="disabled">Room Price</button>
            <input type="text" id="room_Price" value=""></input>
            <br>
            <button type="button" class="btn-dark" disabled="disabled">Room owner</button>
            <input type=" text" id="room_Occupant" value=""></input>
            <br>

        </div>

        <div class="mt-5">
            <input type="text" id="ownerAddress" value="" placeholder="Type your address">
            <input type="password" id="pwNum" value="" placeholder="PW">


            <br>
            <input type="number" id="room_Number" value="" placeholder="Room Number">
            <input type="number" id="room_lumpSum" value="" placeholder="lump sum">
            <button type="button" class="btn-dark" onclick="rent()">Rent</button>

        </div>

        <div class="mt-5">
            <button type="button" class="btn-warning" onclick="roomSet()">Status Setting (Only by Owner)</button>

            <br>
            <input type="text" id="room_Setting1" value="" placeholder="roomNum"></input>
            <input type="text" id="room_Setting2" value="" placeholder="update status"></input>

            <br>
            <button type="button" class="btn-warning" onclick="priceSet()">Price Setting (Only by Owner)</button>
            <br>
            <input type="text" id="room_Price1" value="" placeholder="roomNum">
            <input type="number" id="room_Price2" value="" placeholder="set Price">
        </div>
    </div>

    <div id="right-box">
        <label>Event</label>
        <input type="text" id="eventResult"></input>
        <div id="QR_area" class="float-end me-5 mb-5 mt-5">

        </div>

    </div>

</body>

<script type="text/javascript">
    let provider;
    let web3;
    let account;
    let contract;
    let lumpSum

    async function init() {

        if (window.ethereum) {
            web3 = new Web3(window.ethereum);
            try {
                // Request account access if needed
                await window.ethereum.enable();
                // Acccounts now exposed
                web3.eth.getAccounts().then(function (accounts) {
                    account = accounts[0]
                    document.getElementById("account").value = account
                    document.getElementById("ownerAddress").value = account
                })
            } catch (error) {}
        }
        // Legacy dapp browsers...
        else if (window.web3) {
            // Use Mist/MetaMask's provider.
            web3 = window.web3;
            console.log("Injected web3 detected.");
        }
        document.getElementById("eventResult").value = "Account Connected"
    }

    function getContract() {
        let abi = [{
                "inputs": [],
                "payable": false,
                "stateMutability": "nonpayable",
                "type": "constructor"
            },
            {
                "anonymous": false,
                "inputs": [{
                    "indexed": false,
                    "internalType": "uint256",
                    "name": "_periods",
                    "type": "uint256"
                }],
                "name": "period",
                "type": "event"
            },
            {
                "constant": true,
                "inputs": [{
                    "internalType": "uint256",
                    "name": "_flat",
                    "type": "uint256"
                }],
                "name": "getCurrentOccupant",
                "outputs": [{
                    "internalType": "address",
                    "name": "",
                    "type": "address"
                }],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "constant": true,
                "inputs": [{
                    "internalType": "uint256",
                    "name": "_flat",
                    "type": "uint256"
                }],
                "name": "getFlatAvailability",
                "outputs": [{
                    "internalType": "bool",
                    "name": "",
                    "type": "bool"
                }],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "constant": true,
                "inputs": [{
                    "internalType": "uint256",
                    "name": "_flat",
                    "type": "uint256"
                }],
                "name": "getPriceOfFlat",
                "outputs": [{
                    "internalType": "uint256",
                    "name": "",
                    "type": "uint256"
                }],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "constant": false,
                "inputs": [{
                    "internalType": "uint8",
                    "name": "_flat",
                    "type": "uint8"
                }],
                "name": "rentAFlat",
                "outputs": [{
                    "internalType": "uint256",
                    "name": "",
                    "type": "uint256"
                }],
                "payable": true,
                "stateMutability": "payable",
                "type": "function"
            },
            {
                "constant": false,
                "inputs": [{
                        "internalType": "uint8",
                        "name": "_flat",
                        "type": "uint8"
                    },
                    {
                        "internalType": "bool",
                        "name": "_newAvailability",
                        "type": "bool"
                    }
                ],
                "name": "setFlatAvailability",
                "outputs": [],
                "payable": false,
                "stateMutability": "nonpayable",
                "type": "function"
            },
            {
                "constant": false,
                "inputs": [{
                        "internalType": "uint8",
                        "name": "_flat",
                        "type": "uint8"
                    },
                    {
                        "internalType": "uint256",
                        "name": "_price",
                        "type": "uint256"
                    }
                ],
                "name": "setPriceOfFlat",
                "outputs": [],
                "payable": false,
                "stateMutability": "nonpayable",
                "type": "function"
            }
        ]
        contract = new web3.eth.Contract(abi, "0x465F416407a9b173D9e2129AAD87608912522058")
        document.getElementById("contract").value = contract._address


        // listen event
        // contract.events.cleanRoom({}, function (error, event) {
        //     console.log(event)
        //     document.getElementById("eventResult").value = event.returnValues.name + ", " + event
        //         .returnValues.sender
        // })

    }

    function chkStatus() {
        let room_Number = document.getElementById("room_search").value

        contract.methods.getPriceOfFlat(room_Number).call()
            .then(function (result) {
                let c = result / 1000000000000000000;
                document.getElementById("room_Price").value = c
            })

        contract.methods.getCurrentOccupant(room_Number).call()
            .then(function (result) {
                document.getElementById("room_Occupant").value = result
            })


        contract.methods.getFlatAvailability(room_Number).call()
            .then(function (result) {

                if (result == 0) {
                    document.getElementById("room_Status").value = "Not available"
                } else if (result == 1) {
                    document.getElementById("room_Status").value = "available"
                }
            });
    }

    function getQR() {
        let pwNum = document.getElementById("pwNum").value
        let ownerAddress = document.getElementById("ownerAddress").value
        let QRinput = ownerAddress + pwNum

        console.log('QRinput', QRinput)
        document.getElementById("QR_area").innerHTML =
            '<img src="https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=' + QRinput +
            '" alt="" class="ms-5"> <br><p>This is your room key, keep it safe<p>'


    }
    async function rent() {
        let roomNum = document.getElementById("room_Number").value
        let lumpSum = document.getElementById("room_lumpSum").value
        lumpSum = web3.utils.toWei(lumpSum)
        contract.methods.rentAFlat(roomNum).send({
            from: account,
            value: (lumpSum)
        }).on('receipt', function (receipt) {
            console.log(receipt);
            console.log(receipt.events.period.returnValues._periods)
            document.getElementById("eventResult").value = receipt.events.period.returnValues._periods +
                " days rented!!";
            getQR()
        })
    }




    function roomSet() {
        let e = document.getElementById("room_Setting1").value
        let f = document.getElementById("room_Setting2").value
        if (f == "true") {
            f = 1
        } else if (f == "false") {
            f = 0
        }
        f = Boolean(f)
        contract.methods.setFlatAvailability(e, f).send({
            from: account
        })
    }

    function priceSet() {
        let g = document.getElementById("room_Price1").value
        let h = document.getElementById("room_Price2").value
        h = web3.utils.toWei(h)

        contract.methods.setPriceOfFlat(g, h).send({
            from: account
        })
    }
</script>
<style>
    button,
    input {
        margin: 1px;

    }

    #left-box {
        width: 300px;


        float: left
    }

    #right-box {
        width: 300px;


        float: left
    }
</style>

</html>

작성한 위의 컨트랙트 코드와 abi, 컨트랙트 주소 등은 자신이 만든 솔리디티 컨트랙트의 것들로 업데이트 해야한다. 이렇게 작성한 Dapp은 가나슈로 사용해도 되지만, 메타마스크를 연동하여 Ropsten Network 상에서 자신이 보유한 가상이더로 사용해 볼 수 있다.

호텔 예약,관리 컨트랙트

pragma solidity ^0.5.0;

contract EthBnb {
    

    address payable landlordAddress;
    address payable tenantAddress;
    
    struct Flat {
        uint256 price;
        address currentOccupant;
        bool flatIsAvailable;
        uint256 periods;
    }

    Flat[8] flatDB;
    
    event period(uint _periods);

    modifier landlordOnly() {
        require(msg.sender == landlordAddress);
        _;
    }
    
    constructor() public {
        
        landlordAddress = msg.sender;
        
        for (uint i=0; i<8; i++) {
            flatDB[i].flatIsAvailable = true;
            if (i % 2 == 0) {
                flatDB[i].price = 0.2 ether;
            } else {
                flatDB[i].price = 0.5 ether;
            }
        }
    }
    
    
    function getFlatAvailability(uint _flat) view public returns(bool) {
        return flatDB[_flat].flatIsAvailable;
    }

    function getPriceOfFlat(uint _flat) view public returns(uint256) {
        return flatDB[_flat].price;
    }

    function getCurrentOccupant(uint _flat) view public returns(address) {
        return flatDB[_flat].currentOccupant;
    }



    function setFlatAvailability(uint8 _flat, bool _newAvailability) landlordOnly public {
        flatDB[_flat].flatIsAvailable = _newAvailability;
        if (_newAvailability) {
            flatDB[_flat].currentOccupant = address(0);
            flatDB[_flat].periods = 0;
        }
    }
    
    function setPriceOfFlat(uint8 _flat, uint256 _price) landlordOnly public {
        flatDB[_flat].price = _price; 
    }
    


    function rentAFlat(uint8 _flat) public payable returns(uint256) {

        tenantAddress = msg.sender;

        if (msg.value % flatDB[_flat].price == 0 && msg.value > 0 && flatDB[_flat].flatIsAvailable == true) {

            uint256 numberOfNightsPaid = msg.value / flatDB[_flat].price;

            flatDB[_flat].flatIsAvailable = false;

            flatDB[_flat].currentOccupant = tenantAddress;
            
            flatDB[_flat].periods = numberOfNightsPaid;

            landlordAddress.transfer(msg.value);
            
            emit period(numberOfNightsPaid); 
            
            return numberOfNightsPaid;
            
        
        } else {
            
            tenantAddress.transfer(msg.value);
            
            return 0;
        }
        
    }   

}

위 컨트랙트에서는 사용자는 search 통해 방의 번호를 입력하면 예약여부, 방의 가격, 현재 사용자를 확인할 수 있고, 이더 송금을 통해 호텔을 예약하고, 가격 설정을 통해 일정금액 이상의 이더를 받아야만 예약처리를 해준다. 예약이 완료되면 방의 가격과 송금액을 계산하여 숙박일수가 표시되고, 예약자가 자신의 방의 키로 사용할 수 있게끔 예약자의 공개키 주소와 예약자가 설정한 비밀번호를 섞은 QR코드를 발급해준다. 해당 QR은 숙소에 도착했을 시, 같은 방식으로 예약자의 주소와 예약 당시 사용한 비밀번호를 넣는다면 동일한 QR이 생성될 것이기 때문에 어느정도 예약자 확인, 예약 보안의 역할을 수행하도록 설계하였다. 방의 재설정은 주인만 status setting, price setting을 통해 방의 상태를 초기화할 수 있다.

위 코드를 자신이 사용하고자하는 환경(ganache, ropsten) 등에 배포하고 CA주소와 ABI를 HTML파일에서 읽을 수 있도록 입력, 나머지는 web3함수와 HTML의 영역에서 알맞게 구현하면 웹에서도 체인 상의 변화를 관찰할 수 있는 간단한 Dapp이 완성된다.

profile
Hello

0개의 댓글