본문 바로가기
💠BlockChain💠

블록체인 - 지갑 프로그램 만들기

by 백씨네 2023. 5. 9.

목차

1. 브라우저와 노드의 통신

2. 화면 구성

3. 코드 작성하기

 

 

 

 

이전 코드

https://github.com/100space/2304/tree/main/0504

 

GitHub - 100space/2304

Contribute to 100space/2304 development by creating an account on GitHub.

github.com

 

 

1. 브라우저와 노드의 통신


브라우저와 노드 1개인 상황에서의 통신을 구현하는 코드를 작성할 것이다.
간단한 흐름을 보기 위한 코드이기 때문에 nunjucks를 이용해서 화면을 구성할 예정이고, CSS는 제외했다.
디렉토리 구조도 잘 나누어야 하지만, wallet_front 디렉토리를 이용하여 front 서버를 이용해서 화면을 그릴 것이다.

브라우저는 3000번 포트를 이용하고, 노드는 8545번 포트를 이용한다.

 

 

 

2. 화면 구성

 

2-1. Wallet

wallet 영역은 버튼을 이용해서 privateKey, publicKey, account, balance(잔액)을 생성하고, 이를 보여주는 영역이다.
privateKey, publicKey, account의 경우에는 브라우저 서버에서 단순히 메서드 호출을 이용해서 화면을 그릴 수 있지만, balance의 경우에는 UTXO(미사용 트랜잭션 출력값)을 이용해서 구해야 하기 때문에 노드에서 해당 account에 대한 UTXO만 얻어서 잔액을 구할 것이다.

 

2-2. Wallet List

Wallet List는 생성버튼을 이용해서 만드는 keypair 및 account가 여러 개 있을 경우 그 목록을 보여주는 역할을 하며, account 값만 나열한다. 이 account 값을 누르면 wallet의 항목에 내가 선택한 계정의 정보가 보인다.

 

2-3. Transaction

Transaction 영역은 송금을 보내는 역할로 "받는 사람"과 "보내는 금액"을 작성할 수 있다.

wallet 영역에 선택된 계정에서 보내며, 해당 계정의 UTXO를 사용한다.

 

 

 

 

3. 코드 작성하기

3-1. Node 만들기

// index.ts

const chain = new Chain()
const crypto = new CryptoModule()
const proof = new ProofOfWork(crypto)
const workProof = new WorkProof(proof)
const block = new Block(crypto, workProof)
const transaction = new Transaction(crypto)
const unspent = new Unspent()
const digitalSignature = new DigitalSignature(crypto)
const accounts = new Wallet(digitalSignature)
const baekspace = new Ingchain(chain, block, transaction, unspent, accounts)

const app = App(baekspace) // server/app.ts

app.listen(8545, () => {
    console.log(`server start`)
})

 

//server/app.ts

import Ingchain from "@core/ingchain"
import express from "express"

export default (blockchain: Ingchain) => {
    const app = express()
    app.use(express.json())

    app.get("/", (req, res) => {
        res.send("Hello, Ingchain")
    })

    //잔액 구하기
    app.post("/getBalance", (req, res) => {
        const { account } = req.body
        const balance = blockchain.getBlance(account)
        res.json({ balance })
    })

    //계정 생성
    app.put("/accounts", (req, res) => {
        const account = blockchain.accounts.create() // account는 public으로 주입했다.
        res.json({ ...account })
    })

    //계정 목록 불러오기
    app.get("/accounts", (req, res) => {
        const account = blockchain.accounts.getAccounts()
        res.json(account)
    })

    //블록 마이닝
    app.post("/mineblock", (req, res) => {
        const { account } = req.body
        const newBlock = blockchain.mineBlock(account)
        res.json(newBlock)
    })

    // 트랜잭션
    app.post("/transaction", (req, res) => {
        const { receipt } = req.body
        receipt.amount = parseInt(receipt.amount)
        const transaction = blockchain.sendTransaction(receipt)
        res.json({
            transaction,
        })
    })
    return app
}

 

 

3-2. 브라우저 Wallet 만들기 

브라우저에서 wallet을 화면에 그릴 때 기존에 코드를 잘 작성해 놓게 되면 코드의 재활용이 좋다.
단순히 Node를 구성하는 데에만 쓰는 메서드가 아닌 브라우저에서도 사용할 수 있기 때문에 코드를 확장성 있게 잘 구현하면 좋다.

src/wallet_front 디렉토리 안에서 구현할 예정이다

 

//  wallet_front/index.ts
import CryptoModule from "@core/crypto/crypto.module"
import DigitalSignature from "@core/wallet/digitalSignature"
import Wallet from "@core/wallet/wallet"
import WalletClient from "@wallet_front/app"
const crypto = new CryptoModule()
const digitalSignature = new DigitalSignature(crypto)
const accounts = new Wallet(digitalSignature)
const app = WalletClient(accounts)

app.listen(3000, () => {
    console.log(`wallet Start`)
})

 

//  wallet_front/app.ts
import express from "express"
import nunjucks from "nunjucks"
import axios from "axios"
import path from "path"
import Wallet from "@core/wallet/wallet"

export default (accounts: Wallet) => {
    const app = express()
    const viewDir = path.join(__dirname, "views")
    app.use(express.json())
    app.set("view engine", "html")
    nunjucks.configure(viewDir, {
        express: app,
    })

    app.get("/", (req, res) => {
        res.render("index")
    })

    //계정 생성하기
    app.post("/wallet", async (req, res) => {
        const account = accounts.create()
        const {
            data: { balance },
        } = await axios.post("http://127.0.0.1:8545/getBalance", {
            account: account.account,
        })
        res.json({ ...account, balance })
    })

    //계정 목록 불러오기
    app.get("/wallet", (req, res) => {
        const accountList = accounts.getAccounts()
        res.json(accountList)
    })

    //계정을 이용해서 잔액 구하기
    app.get("/wallet/:account", async (req, res) => {
        const account = accounts.get(req.params.account)
        const {
            data: { balance },
        } = await axios.post("http://127.0.0.1:8545/getBalance", {
            account: account.account,
        })
        res.json({ ...account, balance })
    })

    // 트랜잭션 만들기
    app.post("/transaction", async (req, res) => {
        const { sender, received, amount } = req.body
        const { publicKey, privateKey } = accounts.get(sender)
        const receipt = accounts.sign(
            {
                sender: {
                    account: sender,
                    publicKey,
                },
                received,
                amount,
            },
            privateKey
        )
        const tx = await axios.post("http://127.0.0.1:8545/transaction", { receipt })
        res.json(tx.data)
    })
    return app
}

 

 

<!--  화면 그리기 -->
<!--  views/index.html -->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    </head>
    <body>
        <h2>wallet</h2>
        <button id="wallet_btn">지갑생성</button>
        <ul id="wallet_list">
            <li>privateKey : <span class="privateKey"></span></li>
            <li>publicKey : <span class="publicKey"></span></li>
            <li>account : <span class="account"></span></li>
            <li>balance : <span claa="balance"></span></li>
        </ul>

        <h2>Wallet List</h2>
        <ul id="wallet_list2"></ul>

        <h2>Transaction</h2>
        <form action="" id="transactionForm">
            <ul>
                <li>received : <input type="text" id="received" placeholder="보낼계정" /></li>
                <li>amount : <input type="text" id="amount" placeholder="보낼금액" /></li>
            </ul>
            <button type="submit">전송</button>
        </form>
    </body>
    <script type="text/javascript">
        const walletBtn = document.querySelector("#wallet_btn")
        const walletUl = document.querySelector("#wallet_list2")
        const transactionForm = document.querySelector("#transactionForm")

        const createWallet = async () => {
            const response = await axios.post("http://127.0.0.1:3000/wallet")
            console.log(response.data)
            view(response.data)
            walletList()
        }

        const view = (accounts) => {
            const walletList = document.querySelectorAll("#wallet_list > li > span")
            walletList[0].innerHTML = accounts.privateKey
            walletList[1].innerHTML = accounts.publicKey
            walletList[2].innerHTML = accounts.account
            walletList[3].innerHTML = accounts.balance
        }

        const walletList = async () => {
            const { data } = await axios.get("http://127.0.0.1:3000/wallet")
            const accountList = data.map((account) => `<li>${account}</li>`).join("")
            walletUl.innerHTML = accountList
        }
        const clickHandler = async (e) => {
            try {
                const account = e.target.innerHTML
                if (account.length !== 40) return
                const { data } = await axios.get(`http://127.0.0.1:3000/wallet/${account}`)
                view(data)
            } catch (e) {
                console.error(e.message)
            }
        }
        const submitHansdler = async (e) => {
            e.preventDefault()
            const request = {
                sender: document.querySelector(".account").innerHTML,
                received: e.target.received.value,
                amount: e.target.amount.value,
            }
            await axios.post("http://127.0.0.1:3000/transaction", {
                ...request,
            })
        }
        walletBtn.addEventListener("click", createWallet)
        walletUl.addEventListener("click", clickHandler)
        transactionForm.addEventListener("submit", submitHansdler)
        walletList()
    </script>
</html>

 

 

nunjucks를 이용해서 간단하게 구현하는 코드가 완성되었다.

트랜잭션을 전송하게 되면 화면에 보이는 것이 없어서 제대로 트랜잭션이 생성되었는지 확인할 수 없지만 진행된 것이기 때문에 여러 번 누르지 않도록 해야 하고, 보통은 다음블록이 생성되면서 트랜잭션이 데이터에 포함되기 전까지 로딩페이지를 보여줄 수 있고, 아니면 락 기능을 이용해서 처리가 된 것을 시각화할 수 있다.

그리고, 현재 작성한 코드는 트랜잭션이 발생해도 다음 블록이 생성돼야 잔액이 보이기 때문에 트랜잭션을 생성한 후에 블록을 생성하여서 제대로 트랜잭션이 처리되었는지 확인할 수 있다.

 

 

 


댓글