<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>nazzzo00.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 13 Jun 2023 13:50:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. nazzzo00.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kj_code00" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Factory Contract (23/06/13)]]></title>
            <link>https://velog.io/@kj_code00/Factory-230613</link>
            <guid>https://velog.io/@kj_code00/Factory-230613</guid>
            <pubDate>Tue, 13 Jun 2023 13:50:24 GMT</pubDate>
            <description><![CDATA[<p>mongodb+srv://root:<a href="mailto:root@cluster0.virefkw.mongodb.net">root@cluster0.virefkw.mongodb.net</a>/?retryWrites=true&amp;w=majority</p>
<p>ㄴ DB URI</p>
<p>mongosh &quot;mongodb+srv://cluster0.virefkw.mongodb.net&quot;  --username root --password root</p>
<p>ㄴ 터미널 접속 주소</p>
<p>0xFA2e0fBBE47B0dbe0B3c4caD0cC81a57F8A663d3
0x28C9fC2f5C973EEEb00e0692074f2569501078F3</p>
<p>0x01B428d2EcAA1b270e274642a908Ed18f5b2207a
0x029e785C643710C29E395999ABa610A11789C6F6</p>
<p>mongosh &quot;mongodb+srv://cluster0.virefkw.mongodb.net&quot;  --username root --password root</p>
<p><a href="https://nest-deploy-c764d61cc1b8.herokuapp.com/">https://nest-deploy-c764d61cc1b8.herokuapp.com/</a></p>
<pre><code>{
    &quot;_id&quot;: &quot;649a60a71d3d844feb19c782&quot;,
    &quot;id&quot;: 82,
    &quot;from&quot;: &quot;0x84a4EE69F600B6Df5C8866A373deb3D78E91FE20&quot;,
    &quot;to&quot;: &quot;0x01C10472f1486CBEe15F10204fA1243c12085a28&quot;,
    &quot;NFTaddress&quot;: &quot;0x9FC63e744239ab41AC097341Ba4147CA93cb6d3A&quot;,
    &quot;tokenId&quot;: 1,
    &quot;price&quot;: 3000000000000000,
    &quot;event&quot;: &quot;minted&quot;,
    &quot;createdAt&quot;: &quot;2023-06-27T04:08:07.433Z&quot;,
    &quot;updatedAt&quot;: &quot;2023-06-27T04:08:07.433Z&quot;,
    &quot;__v&quot;: 0
},</code></pre><br>

<h3 id="1-팩토리-컨트랙트">1. 팩토리 컨트랙트</h3>
<br>


<p>팩토리 컨트랙트는 다른 스마트 컨트랙트를 배포하기 위한 컨트랙트입니다 </p>
<br>

<p>신발 공장이 신발을 일정한 기준에 맞추어 생산하는 것처럼
팩토리 컨트랙트가 생성하는 모든 인스턴스는 특정한 규칙(인터페이스)을 준수하는 것을 보장합니다
대규모 dApp에서도 많이 사용하는 패턴이라고 합니다</p>
<br>


<p>스마트 컨트랙트에 있어서 이 패턴의 핵심은 두 가지입니다</p>
<ul>
<li>동일한 컨트랙트로 어드레스가 다른 여러 인스턴스를 찍어낼 수 있다는 점</li>
<li>한 번 정의하고 나면 원하는 위치(클라이언트 측)에서 새로운 클래스 인스턴스를 만들 수 있다는 점</li>
</ul>
<br>

<p><strong>컨트랙트</strong></p>
<pre><code class="language-js">[Factory.sol]

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

import &quot;./myNFT.sol&quot;;

contract myNFTFactory {
    event NFTCreated(address indexed nftAddress, address indexed creator);

    function createNFT(string memory name, string memory symbol) external {
        myNFT newNFT = new myNFT(name, symbol);
        emit NFTCreated(address(newNFT), msg.sender);
    }
}</code></pre>
<br>


<p>이 팩토리 컨트랙트는 미리 정의한 NFT 컨트랙트의 배포 작업을 위한 별도의 컨트랙트입니다</p>
<p>해당 토큰 생성에 필요한 필수 인자(name, symbol, description 등등)를 똑같이 전달받도록 작성되었으며,
이후 클라이언트 측에서 <code>createNFT</code>함수를 호출하면, 새로운 토큰이 배포되는 과정에서 
새로 만들어진 NFT의 컨트랙트 어드레스(<code>CA</code>)와 배포자의 어드레스(<code>EOA</code>) 정보를
이벤트를 통해서 전달받을 수 있는 구조가 되겠습니다</p>
<br>

<p><strong>클라이언트</strong></p>
<pre><code class="language-js">// 팩토리 컨트랙트 인스턴스 생성
const factoryContract = new ethers.Contract(factoryAddress, factoryABI, wallet);

// NFT 인스턴스 생성
async const createNFT = (name, symbol) =&gt;? {
  try {
    // 팩토리 컨트랙트의 createNFT 함수 호출
    const transaction = await factoryContract.createNFT(name, symbol);

    // 이벤트 필터 설정 (NFTCreated 이벤트 필터)
    const filter = factoryContract.filters.NFTCreated();

    // 영수증 객체 반환받기
    const receipt = await transaction.wait();

    // 이벤트 가져오기
    const events = await factoryContract.queryFilter(filter, receipt.blockHash);

    // 생성된 NFT의 주소와 배포자 주소 출력
    const nftAddress = events[0].args.nftAddress;
    const creatorAddress = events[0].args.creator;
    console.log(&#39;NFT Address:&#39;, nftAddress);
    console.log(&#39;Creator Address:&#39;, creatorAddress);
  } catch (error) {
    console.error(&#39;Error:&#39;, error);
  }
}

// createNFT 함수 호출 (토큰 이름과 심볼)
createNFT(&#39;My NFT&#39;, &#39;NFT&#39;);</code></pre>
<p><br><br></p>
<h3 id="2-contractfactory">2. ContractFactory</h3>
<br>

<p>사실 더 간단하게 해결할 수 있는 방법이 있습니다
자주 쓰이는 패턴이라 그런지 <code>ethers.js</code>에서는 이 팩토리 기능을 담은 클래스를 내장하고 있어서요</p>
<br>

<pre><code class="language-js"> const factory = new ethers.ContractFactory(abi, binary, signer);
 const newContract = await factory.deploy();
 await newContract.deployTransaction.wait(1);
 console.log(`Contract Address: ${contract.address}`);</code></pre>
<br>

<p><code>ContractFactory</code>는 <code>ethers.js</code> 라이브러리에서 제공하는, 컨트랙트 배포를 위한 기능을 담은 클래스입니다
<br></p>
<p>위에서 사용한 방식과 가장 큰 차이점이 있다면, 
<code>ContractFactory</code> 클래스를 사용할 때는 별도의 팩토리 컨트랙트가 필요하지 않기 때문에 
이 클래스가 인스턴스 생성을 위해 요구하는 ABI와 바이너리는 토큰 컨트랙트에서부터 가져와야 한다는 것입니다
<br></p>
<p>돌이켜보면 토큰 컨트랙트를 직접적으로 배포할 때는 해당 컨트랙트의 ABI와 CA값을 필요로 했었죠
(<code>Contract(ABI, CA)</code>)</p>
<p><code>ContractFactory</code> 클래스를 통한 토큰 배포도 마찬가지로 보입니다
내부적으로는 <code>Contract</code> 클래스에 동일한 ABI와 새로운 CA를 계속해서 심어주는 역할을 하도록 구현돼 있는 듯...</p>
<br>


<p><strong>클라이언트</strong></p>
<pre><code class="language-js">const handleSubmit = async (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    try {
        e.preventDefault();

        const network = &#39;http://localhost:8545&#39;;
        const provider = new ethers.JsonRpcProvider(network); // 가나쉬 네트워크 주입
        const signer = provider.getSigner(); // 배포자 어드레스를 찾아주는 메서드

          // 팩토리 컨트랙트가 아닌 NFT 컨트랙트의 abi와 바이트코드가 필요
        const factory = await new ethers.ContractFactory(TokenABI.abi, TokenABI.bytecode, signer);
        const deployment = await factory.getDeployTransaction(
            // NFT 생성에 필요한 name과 symbol 인자
            name.value,
            symbol.value
        );
        const deployedTransaction = await signer.sendTransaction(deploymentTransaction);
        const receipt = await deployedTransaction.wait(); // 영수증 객체 반환

        const nftAddress = receipt.contractAddress;
        const creatorAddress = receipt.from;
        console.log(&quot;토큰 컨트랙트 주소:&quot;, nftAddress);
        console.log(&quot;토큰 배포자:&quot;, creatorAddress);
          ...
    }        </code></pre>
<br>

<ul>
<li><p>간단하다는 것 외에 가스비가 절약된다는 장점도 있습니다</p>
</li>
<li><p><code>web3.js</code> 쪽에서는 비슷한 메서드를 지원하지 않는 것 같습니다</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React & TS (23/06/13)]]></title>
            <link>https://velog.io/@kj_code00/React-TS</link>
            <guid>https://velog.io/@kj_code00/React-TS</guid>
            <pubDate>Tue, 13 Jun 2023 13:23:33 GMT</pubDate>
            <description><![CDATA[<p>리액트에서의 타입스크립트 작성을 위한 간단 매뉴얼</p>
<p><br><br></p>
<ol>
<li>컴포넌트에서 프로퍼티 전달하기
타입 정의는 항상 전달 받는 쪽에서<br>

</li>
</ol>
<pre><code class="language-js">// 상위 컴포넌트
const MainComponent = () =&gt; {
  return (
    &lt;SubComponent name=&quot;kim&quot; /&gt;
  )
}

// 하위 컴포넌트
const SubComponent = ({name}: {name : string}) =&gt; {
}</code></pre>
<p><br><br></p>
<ol start="2">
<li>전달할 인자가 둘 이상이라면 인터페이스를 사용합시다</li>
</ol>
<pre><code class="language-js">interface SubProps {
  name: string;
  age: number;
}


const SubComponent = ({name, age}: SubProps) =&gt; {
}</code></pre>
<p><br><br></p>
<ol start="3">
<li>재사용될 인터페이스라면 확장 문법 고려하기</li>
</ol>
<pre><code class="language-js">interface Human {
  name: string;
  age: number;
}

interface Block8 exntends Human {
  license: boolean
}

const SubComponent = ({name, age, license}: Block8) =&gt; {
}</code></pre>
<p><br><br></p>
<ol start="4">
<li>함수를 넘길 때는 리턴 타입에 대한 정의도 해줘야 합니다
따로 리턴값이 없는 함수라면 () =&gt; void</li>
</ol>
<pre><code class="language-js">const handleClick = () =&gt; {
  // 함수 로직
  return true
}


const MainComponent = () =&gt; {
  return (
    &lt;SubComponent onClick={handleClick} /&gt;
  )
}


interface SubProps {
  onClick: () =&gt; boolean
}

const SubComponent = ({onClick}: SubProps) =&gt; {
}</code></pre>
<p><br><br></p>
<ol start="5">
<li>핸들러 함수에 대한 타입지정 방법</li>
</ol>
<ul>
<li>이벤트 객체는 이벤트의 종류 + 발동시킬 엘리먼트까지 타입을 체크합니다</li>
<li>catch문에서 error 객체의 타입은 기본적으로 <code>Error</code></li>
</ul>
<pre><code class="language-js">const handleClick = (e: MouseEvent&lt;HTMLButtonElement&gt;) =&gt; {
  // 이벤트 처리 로직
}

const handleKeyPress = (e: KeyboardEvent&lt;HTMLInputElement&gt;) =&gt; {
  // 이벤트 처리 로직
}


try {} catch (error: Error){}</code></pre>
<p><br><br></p>
<ol start="6">
<li>객체화된 상태에 대한 타입 정의는 이런 식으로</li>
</ol>
<pre><code class="language-js">interface Human {
  name: string;
  age: number;
}
...

const [state, setState] = useState&lt;Human | null&gt;(null);

...

setState({ name: &#39;jh&#39;, age: 31 });</code></pre>
<p><br><br></p>
<ol start="7">
<li>상태변경 함수를 프로퍼티로 넘길 때는 문법이 다소 생소하긴 합니다</li>
</ol>
<pre><code class="language-js">interface SubProps {
  setState: React.Dispatch&lt;SetStateAction&lt;boolean&gt;;
}

const SubComponent = ({setState}: SubProps) =&gt; {
}</code></pre>
<p><br><br></p>
<ol start="8">
<li>interface 대신 type으로도 배열에 대한 타입 정의 가능</li>
</ol>
<pre><code class="language-js">type Work = {
id: number;
text: string;
done: boolean;
}

const [state, setState] = useState&lt;Work[]&gt;([])</code></pre>
<p><br><br></p>
<blockquote>
<p>타입 vs 인터페이스</p>
</blockquote>
<ul>
<li>인터페이스: 확장(extends) 가능, 다중 상속 가능</li>
<li>타입 : 객체 이외에도 타입 지정 가능, 확장이나 상속 불가능</li>
</ul>
<br>

<p>개인적으로는 좀 더 유연한 interface를 선호합니다</p>
<br>


]]></description>
        </item>
        <item>
            <title><![CDATA[NFT - (23/05/31)]]></title>
            <link>https://velog.io/@kj_code00/NFT-230531</link>
            <guid>https://velog.io/@kj_code00/NFT-230531</guid>
            <pubDate>Wed, 31 May 2023 08:47:29 GMT</pubDate>
            <description><![CDATA[<h2 id="nft-개요">NFT 개요</h2>
<br>

<img src="https://img1.daumcdn.net/thumb/R1280x0.fjpg/?fname=http://t1.daumcdn.net/brunch/service/user/9CMK/image/TCRVSsiDzeoFQ2YEUfOJ5Nmb9Aw">

<blockquote>
<p>NFT (Non-Fungible Token)</p>
</blockquote>
<p>대체 불가능 토큰</p>
<br>

<p><strong>1. NFT가 유일한 이유?</strong></p>
<p>NFT가 유일한 이유는 블록체인 기술을 사용하여 각각의 토큰에 
고유한 <strong>식별자(CA)</strong>를 부여하기 때문입니다</p>
<p>간단히 비유를 들자면 객체와 마찬가지입니다</p>
<pre><code class="language-js">{} !== {} // true</code></pre>
<br>

<p>즉 유일하다는 말의 의미는 같은 모양의 객체일지다라도 참조값이 다르다는 것
말장난이라는 생각도 들지만...</p>
<p><br><br></p>
<p><strong>2. 어떻게 이미지가 보이는걸까?</strong></p>
<p>사실 NFT의 컨트랙트에는 &#39;URL&#39;만이 저장됩니다
실제 파일 데이터를 저장하는 서버는 따로 있습니다
그러니 이미지든 음악이든 영상이든 어떠한 유형의 데이터도 NFT가 될 수 있습니다</p>
<p>그런데 파일을 저장하는 서버는 블록체인 네트워크가 아닙니다
그렇다면 특정 서버에 파일을 저장한다면 그것을 탈중앙화라고 부를 수 있을까요?
또 만약 그 서버에 문제가 생긴다면요?</p>
<br>

<p>네, 실제로 이런 지적들이 있습니다. 파일을 저장한 서버가 중앙화 서버인 경우, 
해당 서버에 문제가 생긴다면 NFT 소유자는 해당 파일에 접근할 수 없게 되겠죠
이는 중앙화된 시스템의 한계를 그대로 따른다는 것을 의미합니다</p>
<p>이러한 이유로 여러 NFT 플랫폼에서는 분산 파일 시스템을 활용해서 데이터를 보관하는 방법을 제시하고 있습니다</p>
<br>



<blockquote>
<p><strong>IPFS (InterPlanetary File System)</strong>
= 분산 파일 시스템</p>
</blockquote>
<p>IPFS는 데이터를 중앙화된 서버가 아닌 여러 컴퓨터에 분산 저장하는 기술입니다</p>
<p>(<a href="https://www.pinata.cloud/">https://www.pinata.cloud/</a>)
↑ ipfs 서비스를 제공하는 대표적인 플랫폼</p>
<br>



<p>일단 NFT의 개요에 대한 설명은 여기까지만...</p>
<p><br><br></p>
<h2 id="erc20-토큰-발행하기">ERC20 토큰 발행하기</h2>
<br>

<p>지난 번에 이어서 이번엔 라이브러리를 활용해서 ERC20 토큰을 발행해보겠습니다</p>
<br>

<p><strong>OpenZeppelin</strong></p>
<blockquote>
<p><code>OpenZeppelin</code>은 ERC 표준을 준수하는 스마트 컨트랙트를 쉽게 작성할 수 있도록 돕는 
높은 신뢰성을 지닌 라이브러리입니다</p>
</blockquote>
<br>

<p><strong>설치</strong></p>
<pre><code class="language-sh">npm install @openzeppelin/contracts</code></pre>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/285ded80-c95f-492c-ab04-b6da4a9ff48b/image.png" alt=""></p>
<p>설치시 <code>node_modules</code> 안에 낯익은 디렉토리 구조가 형성됩니다</p>
<br>


<p>이제 솔리디티로 해야할 것을 정리하자면</p>
<ol>
<li>import (ERC20) <br> <pre><code class="language-sol">import &quot;../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;</code></pre>
</li>
<li>is (extend와 동일. 단 , 컨트랙트는 타입스크립트와 달리 다중상속이 가능합니다)<pre><code class="language-sol">contract ERC20 is Context, IERC20</code></pre>
</li>
<li>interface (IERC20)</li>
</ol>
<p><br><br></p>
<p><strong><code>ERC20.sol</code> 파헤치기</strong></p>
<ul>
<li>상속의 핵심은 토큰 발행 역할을 할 <code>mint</code> 함수입니다</li>
</ul>
<pre><code class="language-js">    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), &quot;ERC20: mint to the zero address&quot;);

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }</code></pre>
<p>↑ <code>mint</code> 함수의 구조</p>
<br>

<ul>
<li>지난 번에 컨트랙트에 상태변수를 선언하면 getter 메서드가 자동으로 생성된다고 했는데,
private로 선언된 상태변수에는 해당되지 않습니다
그래서 직접 메서드를 구현해야 하고, OpenZeppelin도 실제로 그렇게 구현되어 있습니다
(<code>ERC20.sol</code> 파일)<br>
```js
string private _name
...
function name() public view virtual override returns (string memory) {
  return _name;
}
```


</li>
</ul>
<br>

<ul>
<li><code>transfer()</code>와 <code>_transfer()</code>?</li>
</ul>
<pre><code class="language-js">    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), &quot;ERC20: transfer from the zero address&quot;);
        require(to != address(0), &quot;ERC20: transfer to the zero address&quot;);

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance &gt;= amount, &quot;ERC20: transfer amount exceeds balance&quot;);
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }</code></pre>
<p><code>transfer</code> 함수를 실행할 때는 트랜잭션 메세지의 <code>sender</code>(<code>from</code>절)를 owner로 가져와서
<code>_transfer</code> 함수를 실행합니다
(아마도 송금자의 어드레스를 숨기기 위한 보안적이 방편으로 보이는데,
송금 기능과 관련한 대부분의 함수가 이러한 2중 구조로 만들어져 있습니다)</p>
<p>그리고 결과적으로는 누가 누구에게 얼마만큼을 보냈는지가 네트워크에 전달되는 구조입니다
(<code>emit Transfer(from, to, amount)</code>)</p>
<br>


<ul>
<li><code>approve()</code>, <code>allowance()</code>, <code>transferFrom()</code></li>
</ul>
<p>이 셋은 마켓 플레이스 구현에 필수적인 함수들입니다</p>
<ol>
<li><code>approve(spender, amount)</code> 
특정 주소(<code>spender</code>)가 사용자 대신 <code>transfer</code>할 수 있는 권한을 부여하는 메서드 (대리 판매)
일종의 위임장 역할을 합니다. 위험도가 높기 때문에 한도 금액(<code>amount</code>)을 필수 인자로 받습니다</li>
</ol>
<ol start="2">
<li><code>allowance(owner, spender)</code> 
위임받은 <code>amount</code> 데이터를 확인하는 데 사용하는 메서드 (<code>call</code> 함수)
특정 주소가 토큰 소유자로부터 얼마나 많은 토큰을 전송할 수 있는지 확인하는데 사용됩니다</li>
</ol>
<ol start="3">
<li><code>transferFrom(from, to, amount)</code> 
위임받은 3자가 <code>transfer</code> 기능을 수행할 수 있게끔 하는 메서드
실행 주체는 위임받은 3자(<code>msgSender</code> === <code>spender</code>)이지만, 
from의 매개변수로는 사용자(<code>owner</code>)의 어카운트를 전달받습니다</li>
</ol>
<p>이 함수가 발동하면 최종적으로 제3자의 대리 결제가 체결됩니다
그리고 이 대리 결제는 <code>allowance()</code>로 확인되는 가용 금액(<code>amount</code>)을 한도로 합니다</p>
<p><br><br></p>
<p>[myToken.sol]</p>
<pre><code class="language-js">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;


import &quot;../node_modules/openzeppelin-solidity/contracts/token/ERC20/ERC20.sol&quot;;

contract MyToken is ERC20 {
    // ERC20() ~ 자바스크립트의 super()와 같습니다
    constructor() ERC20(&quot;myToken&quot;, &quot;MTK&quot;) {
     _mint(msg.sender, 1000 * (10 ** 18)); // 배포자 계정을 대상으로 토큰 1000개 발행
    }
}</code></pre>
<br>

<p>추가로 코인 스왑을 위한 컨트랙트도 만들어보기로 합니다
이렇게 기능이 명확히 분리될 때는 코드를 따로 작성하는 것이 좋습니다 (객체지향 / 책임분리)
이 컨트랙트는 기준이 될 토큰의 CA값을 주입받아야 합니다</p>
<p>[EthSwap.sol]</p>
<pre><code class="language-js">// SPDX-License-Identifier: MIT
pragma solidity^0.8.0;

import &quot;../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;

contract EthSwap {
    ERC20 public token;
      // 인자인 _token은 CA값입니다
    constructor(ERC20 _token) {
        token = _token;
    }
    function get() public view returns(uint256) {
        return token.totalSupply(); // 상속받은 함수입니다. 주입받은 CA에 대한 발행량을 반환
    }
}</code></pre>
<br>

<p>↓ 컨트랙트 파일(<code>.sol</code>)이 여럿일 때의 migration 파일 작성 예재</p>
<pre><code class="language-js">const MyToken = artifacts.require(&quot;myToken&quot;);
const EthSwap = artifacts.require(&quot;EthSwap&quot;);


module.exports = async (deployed) =&gt; {
    await deployed.deploy(MyToken) // 컨트랙트 배포
    const token = await MyToken.deployed() // 인스턴스를 얻기 위해, === new wb3.eth.Contract(abi, CA)
    await deployed.deploy(EthSwap, token.address) // 두번째 인자는 생성자 함수에 대한 의존성 주입 (CA)
    const swap = await EthSwap.deployed()
    console.log((await swap.get()).toString()) // 1000000000000000000000 (1000개)
}


//  리팩토링
module.exports = async (deployed) =&gt; {
    await deployed.deploy(MyToken)
    // const token = await MyToken.deployed() // === new wb3.eth.Contract(abi, CA)
    // console.log(`toEqual:::`, MyToken.address, token.address)

    await deployed.deploy(EthSwap, MyToken.address)
}</code></pre>
<br>


<br>


<p><strong>↓ EthSwap 컨트랙트 완성본</strong></p>
<pre><code class="language-js">// SPDX-License-Identifier: MIT
pragma solidity^0.8.0;

import &quot;../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;

contract EthSwap {
    ERC20 public token;
    uint256 public rate = 100; // 교환비

    constructor(ERC20 _token) {
        token = _token;
    }
    function getToken() public view returns(address) {
        return address(token); // 주입된 인자를 address 타입(CA)으로 형변환
    }

    // Tx 발동 {from: &quot;EOA(sender)&quot;, to: &quot;EthSwap(CA)&quot;, data: getSwapBalance} ERC20 밸런스값을 반환합니다
    function getSwapBalance() public view returns(uint256) {
        return token.balanceOf(msg.sender);
    }

    function getThisAddress() public view returns(address) {
        return address(this); // 자바스크립트와 마찬가지로 this는 어카운트(인스턴스). 반환받을 값은 address이기 때문에 형변환이 필요합니다
    }

    function getTokenOwner() public view returns(address) {
        return token.owner(); // 이 컨트랙트 배포자(EOA)의 어드레스를 반환받을 함수
    }

    function buyToken() public payable {
         uint256 tokenAmount = msg.value * rate; // value는 wei 단위입니다
         // 모든 발행량은 Token Owner(EOA)가 지니고 있기 때문에 EthSwap CA에 대한 권한 위임이 필요합니다(transferFrom)
          // {from: &quot;EOA(sender)&quot;, to: &quot;EthSwap(CA)&quot;, value}


         require(token.balanceOf(token.owner()) &gt;= tokenAmount, &quot;Error&quot;); 
         // 현재 token.balanceOf()는 1000. 즉 10ETH를 넘어가면 에러를 발생시키는 조건문입니다

         token.transferFrom(token.owner(), msg.sender, tokenAmount); // buyToken 실행시 최종적으로 트랜잭션을 발동시킬 메서드
    }

    function sellToken(uint256 _amount) public payable {
        require(token.balanceOf(msg.sender) &gt;= _amount);
        uint256 etherAmount = _amount/rate;
        require(address(this).balance &gt;= etherAmount);
        token.approve2(msg.sender ,address(this), _amount); // Token CA(msg.sender) &gt; Swap CA(address(this))에 권한 위임
        token.transferFrom(msg.sender, token.owner2(), _amount);

        payable(msg.sender).transfer(etherAmount);
    }
}</code></pre>
<br>

<p><code>EOA -&gt; EthSwap(CA) -&gt; MyToken(CA)</code></p>
<p>EOA가 사용자라면 스왑 컨트랜트는 프론트, 토큰 컨트랙트는 백이라고 볼 수도 있겠습니다</p>
<p><br><br></p>
<p>↓ 테스트 코드</p>
<pre><code class="language-js">const MyToken = artifacts.require(&quot;myToken&quot;);
const EthSwap = artifacts.require(&quot;EthSwap&quot;);


contract(&#39;EthSwap&#39;, ([deployer1, deployer2, account3, account4])=&gt; {
    describe(&quot;Account 확인&quot;, ()=&gt; {
        it(&quot;deployer&quot;, ()=&gt; {
            assert.equal(deployer1, &quot;0x61dC3D704d307Ed8dC7ac9918657BD37EEED95D3&quot;)
            assert.equal(deployer2, &#39;0x1183Bf9e54b6Acf0DAb67ee0dc6E5F6b140092D8&#39;)    
        })
    })
    describe(&quot;token deployed&quot;, ()=&gt;{
        it(&quot;token&quot;, async () =&gt; {
            console.log(MyToken.address) // CA ~ 0xe1EA6B87316b2ABE3cAaf370c07AbFbb459097Eb
        })
    })
    describe(&quot;deployed&quot;, ()=&gt; {
        let token
        let swap

        it(&quot;배포 초기화&quot;, async () =&gt; {
            token = await MyToken.deployed()
            swap = await EthSwap.deployed()
        })

        it(&quot;토큰 배포자의 밸런스 확인&quot;, async ()=&gt; {
            const balance = await token.balanceOf(deployer1)
            assert.equal(balance.toString(), 1000 * 10 ** 18)
        })

        it(&quot;buyToken&quot;, async ()=&gt; {
            const amount = await token.balanceOf(swap.address)
            console.log(amount.toString()) // 0

            const approve = await token.approve(swap.address, web3.utils.toWei(&quot;1000&quot;, &quot;ether&quot;))
            console.log(approve)

            const a = await swap.buyToken({from: account3 , to: swap.address, value: web3.utils.toWei(&quot;1&quot;, &quot;ether&quot;)})
            // console.log(a)

            assert.equal(await swap.getTokenOwner(), await token.owner2())
            console.log(await web3.eth.getBalance(account3))
            console.log((await token.balanceOf(account3)).toString())
            console.log(await web3.eth.getBalance(deployer1))
            console.log((await token.balanceOf(deployer1)).toString())
            console.log(await web3.eth.getBalance(swap.address))

            // pass
            // 90986257720000000000
            // 100000000000000000000
            // 98407029600000000000
            // 900000000000000000000
            // 1000000000000000000
        })
        it(&quot;sellToken&quot;, async () =&gt; {
            const balance = await token.balanceOf(account3)
            console.log(`token:`, balance.toString())
            console.log(`eth:`, await web3.eth.getBalance(account3))
            console.log(&#39;owner:&#39;, (await token.balanceOf(deployer1)).toString())

            await swap.sellToken(balance, {
                from: account3, 
            })
            console.log(`token2:`, balance.toString())
            console.log(`eth2:`, await web3.eth.getBalance(account3))
            console.log(&#39;owner2:&#39;, (await token.balanceOf(deployer1)).toString())

            // token: 100000000000000000000
            // eth: 80960585160000000000
            // owner: 900000000000000000000
            // token2: 0
            // eth2: 81959514880000000000
            // owner2: 1000000000000000000000
        })
    })
})</code></pre>
<br>

<p>코드 이해의 요점은 msg.sender가 누구냐를 파악하는 것</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[payable (23/05/30)]]></title>
            <link>https://velog.io/@kj_code00/payable-230530</link>
            <guid>https://velog.io/@kj_code00/payable-230530</guid>
            <pubDate>Tue, 30 May 2023 22:46:03 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/frenchkebab/post/cff1314c-bf6f-4340-ac91-42afd8317583/image.png">




<h2 id="payable">payable</h2>
<br>

<p>솔리디티의 <code>payable</code>은 두 종류로 나뉩니다</p>
<pre><code class="language-js">// address payable
payable(address);
address payable public owner;


// function payable
function sellItem() external payable {}</code></pre>
<br>

<ul>
<li><code>address payable</code>은 주소의 타입을 지정하는 것을 뜻합니다 
해당 주소에는 이더를 송금할 수 있는 기능(<code>transfer()</code>와 <code>send()</code> 함수)이 내장됩니다</li>
</ul>
<ul>
<li><code>function payable</code>은 함수의 속성을 지정합니다 
그리고 이렇게 <code>payable</code>을 선언한 함수에서만 이더를 보낼 수 있습니다
즉, 컨트랙트에서 <code>payable</code>로 선언된 함수는 외부에서 호출될 때 이더를 전송하는 기능을 수행할 수 있게 됩니다</li>
</ul>
<br>



<p>간단한 마켓 기능을 구현해보면서 두 <code>payable</code> 속성을 이해해봅시다</p>
<p>[appleShop.sol]</p>
<pre><code class="language-js">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract AppleShop {
    // myApple[key] = value, 기본값 0이 할당됩니다
    mapping(address=&gt; uint256) myApple;

    function buy() public payable {
        myApple[msg.sender] += 1;
    }
    // 내가 가진 사과갯수 표시 (call)
    function get() public view returns(uint256) {
        return myApple[msg.sender];
    }
    // 전체 환불. 갯수를 지정하고 싶다면 매개변수를 받아야 합니다
    function sell() public payable {
        uint256 refund = myApple[msg.sender] * 10 ** 18;
        myApple[msg.sender] = 0;

        // payable() ~ 인자는 어드레스(string), 리턴값은 어카운트(object)로 이해하기
        // transfer ~ CA가 가진 이더를 사용자 어카운트에 전달하는 payable 내장 메서드
        payable(msg.sender).transfer(refund);
    }
}</code></pre>
<p>[AppleShop.jsx]</p>
<pre><code class="language-js">import AppleShopContract from &quot;../contracts/AppleShop.json&quot;;
import { useState, useEffect } from &quot;react&quot;;

const AppleShop = ({ web3, account }) =&gt; {
    const [deployed, setDeployed] = useState(null);
    const [apple, setApple] = useState(0);

    const buy = async () =&gt; {
        await deployed.methods.buy().send({
            from : account,
            value : web3.utils.toWei(&#39;1&#39;, &#39;ether&#39;) // 이더는 CA에 전달됩니다
        })
    }

    const sell = async () =&gt; {
        await deployed.methods.sell().send({
            from : account,
        })
    }

    useEffect(() =&gt; {
        if (!deployed) return;
        deployed.methods.get().call().then(setApple);
    }, [deployed]);

    useEffect(() =&gt; {
        if (!web3) return;
        const instance = new web3.eth.Contract(
            AppleShopContract.abi,
            AppleShopContract.networks[1685407833552].address // window.ethereum에서 chainId로 가져올 수도 있습니다
        );
        setDeployed(instance);
    }, []);

    return (
        &lt;&gt;
            &lt;h2&gt;사과 가격: 1 ETH&lt;/h2&gt;
            &lt;h2&gt;현재 계정 : {account}&lt;/h2&gt;
            &lt;div&gt;
                내가 가진 사과 갯수 : {apple}
                &lt;br /&gt;
                &lt;button onClick={buy}&gt;Buy&lt;/button&gt;
            &lt;/div&gt;
            &lt;div&gt;
                총 사과 판매 가격 1 ETH
                &lt;br /&gt;
                &lt;button onClick={sell}&gt;Sell&lt;/button&gt;
            &lt;/div&gt;
        &lt;/&gt;
    );
};</code></pre>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/bf51eb18-f175-4f9b-8a4c-ad303c87abe6/image.png" alt=""></p>
<p><strong><code>buy()</code> 실행 후 CA의 밸런스</strong> </p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/00f55984-de49-4bd0-8724-64d9979e8fa6/image.png" alt=""></p>
<p><strong><code>sell()</code> 실행 후 CA의 밸런스</strong></p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/c40c3e02-6561-4713-b8f9-c039cb54ae8e/image.png" alt=""></p>
<br>



]]></description>
        </item>
        <item>
            <title><![CDATA[ERC (23/05/26)]]></title>
            <link>https://velog.io/@kj_code00/ERC-230526</link>
            <guid>https://velog.io/@kj_code00/ERC-230526</guid>
            <pubDate>Fri, 26 May 2023 03:48:05 GMT</pubDate>
            <description><![CDATA[<h2 id="erc">ERC</h2>
<img src="https://velog.velcdn.com/images/frenchkebab/post/cff1314c-bf6f-4340-ac91-42afd8317583/image.png">


<br>

<p><strong>ERC (Ehereum Request for Comments)</strong></p>
<p>ERC는 이더리움 기반 블록체인 네트워크에서 사용되는 표준 프로토콜을 말합니다</p>
<blockquote>
<p>ERC20 - 토큰
ERC721 - NFT
ERC1155 - NFT (ERC20 + ERC721)</p>
</blockquote>
<br>

<h2 id="erc20">ERC20</h2>
<blockquote>
<p>Token : 이더리움 메인넷을 따라서 스마트 컨트랙트로 발행된 화폐</p>
</blockquote>
<p>토큰을 생성하는 것도 정해진 규칙을 따라야 하는데, 그 표준을 <strong>ERC</strong>라고 부릅니다</p>
<p>자산에 대한 변수명을 value로 할 것인지, amount로 할 것인지, balance로 할 것인지,
메서드명은 어떻게 할 것인지...
이러한 혼란을 막기 위해 통일성이 있는 표준이 필요하기 때문입니다
또한 실제로 가상화폐 거래소에서 거래되는 대부분의 화폐가 ERC20 토큰이기도 합니다</p>
<br>



<pre><code class="language-js">interface balance {
    address: string
    amount: number
}
interface Token {
    name: string
    balance: Balance[]
      transfer(to, value) {}
}

const ZZToken = {
  name: &quot;zzToken&quot;,
  balance : {
      address: &quot;0xabc...&quot;,
    amount: 50
  },
  {
      address: &quot;0xbcd...&quot;,
      amount: 10
  }
}

zzToken.transfer(&quot;0xbcd...&quot;, 25)</code></pre>
<p>↑ 간소화된 예시이긴 하지만, 이러한 인터페이스(<code>ABI</code>)를 제공하는것이 ERC의 역할</p>
<p><br><br></p>
<h2 id="erc20-토큰-생성하기">ERC20 토큰 생성하기</h2>
<br>

<p>이번에는 가나쉬 &gt; 테스트넷 순으로 배포작업을 진행해보려 합니다</p>
<pre><code class="language-sh">npx ganache-cli
npx truffle init</code></pre>
<br>


<ol>
<li><strong>컨트랙트 파일 작성</strong></li>
</ol>
<p>[SimpleStore.sol]</p>
<pre><code class="language-js">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;



contract SimpleStore {
    uint256 public value;
    address public owner; // 솔리디티에는 address라는 타입이 존재합니다

    constructor(uint256 _value){
        value = _value;
        owner = msg.sender;
    }
}</code></pre>
<br>

<p><code>msg.sender</code>는 트랜잭션을 발동시킨 사람의 주소를 가리킵니다
그래서 이 경우 <code>owner</code> 변수에는 해당 컨트랙트를 배포한 어카운트값이 담기게 됩니다
(가나쉬를 사용중이라면 기본값인 첫번째 어카운트가 할당됩니다)</p>
<p>그리고 상태변수만 만들어도 getter 메서드가 자동으로 만들어집니다
한 번 콘솔로 확인해보겠습니다
<br></p>
<p>일단 배포부터 진행합니다</p>
<pre><code>npx truffle migrate</code></pre><p>*네트워크 옵션을 따로 지정하지 않으면 development(로컬) 네트워크로 배포됩니다</p>
<br>


<p><strong>트러플 콘솔</strong></p>
<pre><code class="language-js">// truffle console
let store;
SimpleStore.deployed().then(instance =&gt; store = instance)
store.methods

store.value()
// BN { negative: 0, words: [ 15, &lt;1 empty item&gt; ], length: 1, red: null }
store.owner()
// &#39;0xbdB2e2090573cE871E64d96e7987255e65881E74&#39;</code></pre>
<p><br><br></p>
<ol start="3">
<li><strong>ERC20에 따라 토큰 컨트랙트 작성하기</strong></li>
</ol>
<br>

<p>[MyToken.sol]</p>
<pre><code class="language-js">contract MyToken {
    mapping(address =&gt; uint256) public balances; // balances[address] = uint256 ...매핑에 대한 추가 설명은 아래에
    string public name = &#39;MyToken&#39;;
    string public symbol = &#39;MTK&#39;; // 통화기호
    uint8 public decimals = 18; // 1MTK = 10**18 ~ 이더리움이 사용중인 화폐 단위
    uint256 public totalSupply = 1000 * 10 ** decimals; // 총발행량 (1000개)

      constructor () {
        balances[msg.sender] = totalSupply; // owner 계좌에 총발행량 주입
    }

      function balanceOf(address _owner) public view returns(uint256) {
        return balances[_owner];
    } // 오너의 잔액 체크. 이 함수도 ERC20을 따릅니다
    function transter(address _to, uint256 _value) public returns(bool) {
        require(balances[msg.sender] &gt;= _value); // throw처럼 조건검사(boolean) 후 함수를 종료시키는 함수
        return true;
    } // send 메서드 (송금)
}</code></pre>
<p>이걸로 기본적인 토큰 컨트랙트 작성은 끝났습니다 (진짜...?)</p>
<br>

<p>이제 이 토큰 컨트랙트를 테스트넷에 배포할 차례입니다</p>
<br>


<p>우선 마이그레이션 파일 작성부터</p>
<p>[./migrations/02_delploy_myToken]</p>
<pre><code class="language-js">const MyToken = artifacts.require(&quot;Counter&quot;)

module.exports = (deployer) =&gt; {
    deployer.deploy(MyToken)
}</code></pre>
<br>


<p>테스트넷으로 배포 실행</p>
<pre><code class="language-sh">npx truffle migrate --network goerli</code></pre>
<br>

<p>배포 결과</p>
<pre><code class="language-js">02_delpoy_myToken.js
====================

   Deploying &#39;MyToken&#39;
   -------------------
   &gt; transaction hash:    0xa33abdde2cb3ef5254032...
   &gt; Blocks: 2            Seconds: 17
   &gt; contract address:    0xb7b9Cd85a776979F76...
   &gt; block number:        9065029
   &gt; block timestamp:     1685071284
   &gt; account:             0xFA2e0fBBE47B0dbe0B3c...
   &gt; balance:             0.190967310734754099
   &gt; gas used:            608680 (0x949a8)
   &gt; gas price:           2.500000402 gwei
   &gt; value sent:          0 ETH
   &gt; total cost:          0.00152170024468936 ETH

   Pausing for 2 confirmations...

   -------------------------------
   &gt; confirmation number: 1 (block: 9065030)
   &gt; confirmation number: 2 (block: 9065031)
   &gt; Saving artifacts
   -------------------------------------
   &gt; Total cost:     0.00152170024468936 ETH

Summary
=======
&gt; Total deployments:   2
&gt; Final cost:          0.001969085313407696 ETH
</code></pre>
<br>


<p>지금까지 ERC20 표준을 잘 따랐다면, 생성된 CA값을 복사해서 
메타마스크에서 바로 잔액을 확인하고 송금도 할 수 있습니다</p>
<p><br><br></p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/2d7e2dae-5f51-4d0e-b400-83c3bc88052d/image.png" alt="">
<img src="https://velog.velcdn.com/images/kj_code00/post/2dbd83c5-521d-4535-a26a-3c041cb03b2e/image.png" alt=""></p>
<p>거래소에서 토큰을 사지 말아야 할 이유...</p>
<p><br><br></p>
<h3 id="번외-mapping">번외) mapping()</h3>
<br>

<p>솔리디티 <strong>mapping</strong> 함수에 대한 보충 설명입니다</p>
<br>

<p><code>mapping()</code></p>
<pre><code class="language-js">mapping(number =&gt; string) public a

a[0] = abc
a[1] = def</code></pre>
<p>솔리디티는 위와 같은 방식으로 하나의 데이터를 <code>key</code>, <code>value</code> 구조의 쌍으로 그룹화할 수 있습니다
이는 하나의 상태변수에 여러 데이터를 담기 위해서(군집형) 사용합니다</p>
<p>자바스크립트 객체로 비유하면 한결 이해하기 쉽습니다
<br></p>
<pre><code class="language-js">// mapping(number =&gt; string) public a
const a = [&#39;abc&#39;, &#39;def&#39;]

0: abc
1: def

// mapping(string =&gt; string) public b
const b = {name: kim, id:&quot;zzz&quot;}

b.id : zzz</code></pre>
<br>

<p>이 <code>mapping</code>함수는 솔리디티에만 존재하는 address 타입으로 매핑시키기도 하고,
또는 2차 배열 형태로 응용하는 등 여러 형태로 사용됩니다</p>
<pre><code class="language-js">mapping(address=&gt; uint256) public c
mapping (address =&gt; mapping(uint256=&gt; uint256)) public d

// d
{
      &quot;0xbdB2e2090573cE871E64d96e7987255e65881E74&quot;: {
        0: 1234
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Event - (23/05/25)]]></title>
            <link>https://velog.io/@kj_code00/Event-230525</link>
            <guid>https://velog.io/@kj_code00/Event-230525</guid>
            <pubDate>Fri, 26 May 2023 00:25:28 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-js">    // solidity
    function increment() public returns(uint256){
        value += 1;
        return value;
    }

    // javascript
    // getTransaction
    const increment = async () =&gt; {
        const newTx = await deployed.methods.increment().send({ from: account })
        console.log(`getTransaction:`, newTx)

    // getTransactionReceipt  
    const decrement = async () =&gt; {
        await deployed.methods.decrement().send({ from: account }).then(console.log)</code></pre>
<p>지난번에 만든 카운터 기능을 약간 고치고 싶어서 컨트랙트의 카운터 증감 함수에 리턴값을 추가해봤는데,
콘솔로는 이 리턴값을 확인할 수 없었습니다</p>
<br>


<p><img src="https://velog.velcdn.com/images/kj_code00/post/375944ad-9ebe-4f04-a393-549e83f46419/image.png" alt=""></p>
<p>트랜잭션 객체의 데이터 속성에 리턴값이 들어있을 줄 알았지만..
알고보니 <code>send()</code>로 트랜잭션을 전송했을 경우에
<code>getTransaction</code>으로는 함수의 실행 결과를 바로 반환받을 수 없는게 당연했습니다
트랜잭션은 비동기적으로 처리되고, 블록체인에서 트랜잭션이 포함된 블록을 기다려야 하기 때문입니다</p>
<p>반면<code>then</code>을 사용하면 블록체인에 포함된 객체를 리턴하는데, 
이 경우에도 제가 지정한 리턴값은 담겨있지 않았습니다</p>
<p>그렇다고 해서 <code>call</code> 메서드만으로 상태 업데이트를 처리하려면
앱을 새로고침(<code>call()</code>)하지 않는 이상 모두가 최신 상태를 실시간으로 공유할 수 없다는 문제가 생기겠죠
어떠한 다른 해결법을 구해야 했습니다
<br></p>
<h2 id="event">Event</h2>
 <br>

<p>이더리움에서 말하는 이벤트는 블록체인 네트워크의 블록에 특정값을 <strong>기록</strong>하는 기법을 말합니다
(트랜잭션 실행과 관련된 데이터, 상태변화 등)</p>
<p>이더리움에서 트랜잭션이 발생하면, 해당 트랜잭션이 성공과 실패 여부에 상관없이 트랜잭션 영수증(receipt)을 발행합니다
그리고 코드 실행 중에 이벤트가 발생했을 경우에 그 내용이 컨트랙트 내에서 정의한 방식대로 
트랜잭션 영수증 객체에 포함되어 저장됩니다
이벤트는 로그들을 객체화한 형태로 영수증 객체의 <code>logs</code> 속성에 담기게 됩니다</p>
<br>

<p>이제 이벤트가 발생할 수 있도록 컨트랙트 코드를 수정해보겠습니다</p>
<pre><code class="language-js">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Counter {
    uint256 value;
    event Count(uint256 count);

    constructor(){}

    function get() public view returns(uint256) {
        return value;
    }
    function increment() public {
        value += 1;
          emit Count(value);
    }
    function decrement() public {
        value -= 1;
        emit Count(value);
    }
}</code></pre>
<p>Count라는 이벤트를 등록하고, <code>increment</code>나 <code>decrement</code> 함수가 호출될 때 이벤트가 발생하도록 했습니다
이벤트가 발생하면 업데이트된 value를 전송(<code>emit</code>)합니다</p>
<p>이제 이 value가 영수증 객체에 담겨있는지를 확인해볼 차례입니다</p>
<br>

<p><strong>트러플 콘솔</strong>을 통해서 영수증에 담긴 이벤트 데이터 확인하기</p>
<pre><code class="language-js">// 콘솔 열기
npx truffle console --network goerli

// 영수증 출력
new web3.eth.getTransactionReceipt(&#39;0xc782e00bf256c69e5e08ee97ae8e51c70b1630546e97778044560704d9db9c46&#39;)


//
{
  blockHash: &#39;0xe8141dccc1d0a7f49143c695d356b3f979b0c324d94a904c3a1ff2e017e10099&#39;,
  blockNumber: 9060079,
  contractAddress: null,
  cumulativeGasUsed: 3472955,
  effectiveGasPrice: 2726300271,
  from: &#39;0xfa2e0fbbe4...&#39;,
  gasUsed: &#39;0x6c4f&#39;,
  // 이벤트 데이터(최신 상태의 value)는 logs &gt; data 속성에 16진수 형태로 담겨있습니다  
  logs: [
    {
      address: &#39;0xD518c24...&#39;,
      blockHash: &#39;0xe8141dccc1d0a7f49143c695d356b3f979b0c324d94a904c3a1ff2e017e10099&#39;,
      blockNumber: 9060079,
      data: &#39;0x0000000000000000000000000000000000000000000000000000000000000003&#39;,
      logIndex: 12,
      removed: false,
      topics: [Array],
      transactionHash: &#39;0xc782e00bf256c69e5e08ee97ae8e51c70b1630546e97778044560704d9db9c46&#39;,
      transactionIndex: 9,
      id: &#39;log_1a886bfe&#39;
    }
  ],
  logsBloom: &#39;0x00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000010000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&#39;,
  status: true,
  to: &#39;0xd518c24c0170f4138e38f6b50568542e12a02a1b&#39;,
  transactionHash: &#39;0xc782e00bf256c69e5e08ee97ae8e51c70b1630546e97778044560704d9db9c46&#39;,
  transactionIndex: 9,
  type: &#39;0x2&#39;
}</code></pre>
<p><br><br></p>
<p>그리고 클라이언트 측에서도 이벤트를 구독(<code>subscribe()</code>)해서 카운터 값의 변화를 실시간으로 수신할 수 있습니다
<br></p>
<blockquote>
<p><code>web3.eth.subscribe(type [, options] [, callback]);</code></p>
</blockquote>
<p><code>subscribe()</code> 함수는 이더리움 네트워크에서 발생하는 이벤트를 구독하는 데 사용됩니다
다음과 같은 인자를 받을 수 있습니다</p>
<ul>
<li><p><code>type</code>: 구독할 이벤트의 종류를 지정하는 매개변수입니다. 가장 일반적인 값으로는 &quot;logs&quot;를 사용합니다. 이는 스마트 컨트랙트의 이벤트 로그를 구독하고자 할 때 사용됩니다.</p>
</li>
<li><p><code>options</code> (선택적): 구독 옵션을 지정하는 객체입니다</p>
<ul>
<li>address: 구독할 이벤트의 발생 주소를 지정합니다. 
특정 스마트 컨트랙트의 이벤트만 구독하고자 할 때 사용됩니다</li>
</ul>
</li>
<li><p><code>callback</code> (선택적): 이벤트가 발생할 때 실행되는 콜백 함수입니다
콜백 함수는 구독된 이벤트에 대한 정보를 인자로 받습니다</p>
</li>
</ul>
<br>


<pre><code class="language-js">import CounterContract from &quot;../contracts/Counter.json&quot;
...
    const [count, setCount] = useState(0);
    const [deployed, setDeployed] = useState(null);
    const [loading, setLoading] = useState(true);
...

    useEffect(() =&gt; {
        if (!web3 || !account) return;

        const contractAddress = &quot;0xD518c24C0170f4...&quot;
        const Deployed = new web3.eth.Contract(CounterContract.abi, contractAddress);
        setDeployed(Deployed);

        // subscribe() : 구독할 이벤트의 종류는 &quot;logs&quot;, 옵션으로 이벤트의 발생 주소를 지정합니다
        // on() :  이벤트가 발생할 때마다 콜백함수를 실행합니다
        web3.eth.subscribe(&#39;logs&#39;, { address: contractAddress })
          .on(&quot;data&quot;, log =&gt; {
            const params = [
              {
                indexed: false,
                internalType: &quot;uint256&quot;,
                name: &quot;count&quot;,
                type: &quot;uint256&quot;
              }
            ];

            const value = web3.eth.abi.decodeLog(params, log.data);
            console.log(value);
            setCount(value.count);
          });

        Deployed.methods.get().call().then(value =&gt; {
          setLoading(false);
          setCount(value);
        });
      }, []);</code></pre>
<br>


<p>+) <code>then</code>함수로도 이벤트가 담긴 영수증 객체를 반환받을 수 있었습니다
꽁꽁 쌓여있긴 하지만 <code>returnValues</code> 안에 10진수 변환이 끝난 형태로 담겨있네요</p>
<pre><code class="language-js">    const handleClick = async (type) =&gt; {
        if (deployed === null) return console.log(&quot;no deployed&quot;)
        await deployed.methods[type]().send({ from: account }).then(({events}) =&gt; {
            // console.log(events)
            setCount(events.Count.returnValues.count)
        })
    }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Testnet - (23/05/25)]]></title>
            <link>https://velog.io/@kj_code00/Testnet-230525</link>
            <guid>https://velog.io/@kj_code00/Testnet-230525</guid>
            <pubDate>Thu, 25 May 2023 06:24:51 GMT</pubDate>
            <description><![CDATA[<h2 id="testnet-개요">Testnet 개요</h2>
<img src="https://assets.newatlas.com/dims4/default/5ae9131/2147483647/strip/true/crop/2612x1741+0+0/resize/2612x1741!/quality/90/?url=http%3A%2F%2Fnewatlas-brightspot.s3.amazonaws.com%2F06%2F13%2F0afcd4bc415c8a189d095d5c23c2%2Funtitled-1.png">


<br>

<p><strong>체인 아이디(Chain ID)</strong>는 블록체인 네트워크를 식별하는 번호입니다 
그리고 메인넷과 테스트넷도 서로 다른 체인 아이디를 가지고 있습니다
예를 들면 이더리움의 메인넷의 체인 아이디는 1이고, 테스트넷(Goerli)의 체인 아이디는 5입니다
(현재 가동중인 이더리움 테스트넷은 Goelri와 Sepolia뿐)</p>
<p>메인넷과 테스트넷은 동일한 블록체인 프로토콜을 사용하고, 그래서 거의 동일한 방식으로 동작합니다
주요 차이점은 테스트넷에서는 실제 이더를 사용하지 않고 가짜 이더로 트랜잭션을 처리한다는 것
즉, 테스트넷은 메인넷과 같은 동작 구조를 가진 개발자를 위한 네트워크라고 할 수 있습니다</p>
<p>지금까지는 트러플과 가나쉬 네트워크를 통해 디앱을 로컬 환경에서만 개발해왔지만,
이제는 테스트넷 환경에서 디앱 배포를 위한 방법을 익혀볼 차례입니다
<br><br></p>
<h3 id="프로바이더--infrura">프로바이더 ~ Infrura</h3>
<br>

<p>그런데 막상 테스트넷을 이용하려 해도 내가 만든 컨트랙트 블록을 어떻게 네트워크에 띄우고 
또 최신 블록을 어떻게 계속 동기화할 것인지, 노드 관리에 대한 여러 고민이 생겨납니다
블록 동기화는 실시간으로 끊임없이 이루어져야 하니까요</p>
<p>그래서 보통은 이 역할을 대신해줄 몇몇 <strong>프로바이더</strong>의 도움을 받게 됩니다</p>
<blockquote>
<p>Infura는 원격 이더리움 노드를 통해 이더리움 네트워크에 접근할 수 있게 해주는 서비스입니다</p>
</blockquote>
<p>Infura와 같은 프로바이더는 노드를 동기화하고, 네트워크에 접속해서 트랜잭션 전송, 컨트랙트 실행 등 
블록체인 네트워크와 상호작용하는 데에 있어 여러 서비스 기능을 제공합니다
<br></p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/57f24a87-6bae-4a06-ae9c-7a7537438ed3/image.png" alt=""></p>
<ol>
<li><p>Infura 접속
(<a href="https://app.infura.io/dashboard">https://app.infura.io/dashboard</a>)</p>
</li>
<li><p>테스트넷(Goerli)의 API키 발급받기 (<code>web3 API</code> 선택)</p>
</li>
</ol>
<p>↑ API 키는 프로젝트를 구분하고 인증하는데 사용됩니다
그리고 Infura가 관리하는 원격 노드에 액세스할 수 있는 권한을 부여합니다</p>
<p><br><br></p>
<h3 id="truffle--testnet">Truffle + Testnet</h3>
<br>

<p>이제 트러플을 사용해서 컨트랙트를 테스트넷에 배포하는 과정을 구체적으로 알아보겠습니다
먼저 트러플 컨피그 파일에서 아래 코드들의 주석들을 해제합니다</p>
<br>

<p>[truffle-config.js]</p>
<pre><code class="language-js">require(&#39;dotenv&#39;).config();
// .env파일을 만들어서 MNEMONIC에는 개인키를, PROJECT_ID는 Infura에서 발급받은 API 키를 입력
const { MNEMONIC, PROJECT_ID } = process.env;

const HDWalletProvider = require(&#39;@truffle/hdwallet-provider&#39;);


    goerli: {
      provider: () =&gt; new HDWalletProvider(MNEMONIC, `https://goerli.infura.io/v3/${PROJECT_ID}`),
      network_id: 5,       // Goerli&#39;s id
      confirmations: 2,    // # of confirmations to wait between deployments. (default: 0)
      timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
      skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    },</code></pre>
<br>

<p>↓ 설치가 필요한 패키지</p>
<pre><code class="language-sh">npm install dotenv
npm install @truffle/hdwallet-provider</code></pre>
<br>

<p>(컨트랙트와 마이그레이션 파일 작성은 직전 포스트와 동일하므로 생략합니다)</p>
<br>

<p>모든 사전작업이 끝났다면 이제 배포를 진행합시다 </p>
<pre><code>npx truffle migrate --network goerli</code></pre><br>

<p>배포 결과</p>
<pre><code class="language-js">01_deploy_counter.js
====================

   Deploying &#39;Counter&#39;
   -------------------
^[[1;2D   &gt; transaction hash:    0xe94542417818d286c0c53b8888e6136c48fcdc701b96dea0bbc5eee07e25c597
   &gt; Blocks: 1            Seconds: 17
   &gt; contract address:    0x57cC92489F1444C127Cf3CeDef1eB7EB58d8180b
   &gt; block number:        9058558
   &gt; block timestamp:     1684977588
   &gt; account:             0xFA2e0fBBE47B0dbe0B3c4caD0cC81a57F8A663d3
   &gt; balance:             0.199637121777583473
   &gt; gas used:            145151 (0x236ff)
   &gt; gas price:           2.500004977 gwei
   &gt; value sent:          0 ETH
   &gt; total cost:          0.000362878222416527 ETH

   Pausing for 2 confirmations...

   -------------------------------
   &gt; confirmation number: 1 (block: 9058559)
   &gt; confirmation number: 2 (block: 9058560)
   &gt; Saving artifacts
   -------------------------------------
   &gt; Total cost:     0.000362878222416527 ETH

Summary
=======
&gt; Total deployments:   1
&gt; Final cost:          0.000362878222416527 ETH</code></pre>
<p>블록 생성까지 걸린 시간은 17초, 가스는 약 0.0004ETH가 발생했네요</p>
<p><br><br></p>
<p>[Counter.jsx]</p>
<pre><code class="language-js">import { useState, useEffect } from &quot;react&quot;
import CounterContract from &quot;../contracts/Counter.json&quot;

const Counter = ({ account, web3 }) =&gt; {
    const [count, setCount] = useState(0);
    const [deployed, setDeployed] = useState(null);
    const [loading, setLoading] = useState(true);

    const handleClick = async (type) =&gt; {
        if (deployed === null) return console.log(&quot;no deployed&quot;)
        await deployed.methods[type]().send({ from: account })

        deployed.methods.getValue().call().then(value =&gt; {
            setCount(value)
            setLoading(false);
        })
    }

    useEffect(() =&gt; {
        if (!web3 || !account) return
        // Contract(ABI, CA) ~ Goerli의 cahinId는 5
        const Deployed = new web3.eth.Contract(CounterContract.abi, CounterContract.networks[5].address)
        setDeployed(Deployed)

        Deployed.methods.getValue().call().then(value =&gt; {
            setLoading(false);
            setCount(value)
        })
    }, [])

    if (loading) {
        return &lt;div&gt;Loading...&lt;/div&gt;
    }

    return (
        &lt;div&gt;
            &lt;h2&gt;Counter: {count}&lt;/h2&gt;
            &lt;button onClick={()=&gt;handleClick(&quot;increment&quot;)}&gt;+&lt;/button&gt;
            &lt;button onClick={()=&gt;handleClick(&quot;decrement&quot;)}&gt;-&lt;/button&gt;
        &lt;/div&gt;
    );
};

export default Counter;</code></pre>
<p>프론트와의 연동도 잘 됩니다</p>
<p>덧붙여서 이벤트를 발동시킬 때마다 약 0.0001이더가 소모되었습니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Truffle (23/05/24)]]></title>
            <link>https://velog.io/@kj_code00/Truffle-230524</link>
            <guid>https://velog.io/@kj_code00/Truffle-230524</guid>
            <pubDate>Wed, 24 May 2023 06:57:47 GMT</pubDate>
            <description><![CDATA[<img src="https://images.velog.io/images/haerong22/post/dd38a6bd-8a16-4e68-a375-10fbec6b9e86/etg.png">
<br><br>


<h2 id="truffle-소개">Truffle 소개</h2>
<br>

<p><code>Truffle</code> 은 이더리움 디앱 개발을 위해 사용되는 프레임워크입니다</p>
<p>스마트 컨트랙트 컴파일부터 배포와 테스트까지 가능한 올인원 툴로써 디앱 개발에 많은 편의성을 제공합니다
(특히 <code>call</code>l과 <code>send</code>를 날리는 과정이 크게 단축됩니다)</p>
<p>간편한 툴인 만큼 솔리디티와 블록체인 네트워크의 기본원리를 알고 쓰는 것이 중요합니다</p>
<br>


<p><strong>설치 &amp; 실행</strong></p>
<pre><code class="language-sh">npx truffle init</code></pre>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/b113d1d7-b77e-40eb-8897-580e5c842ad5/image.png" alt=""></p>
<p>터미널에 명령어를 입력하면 컨피그 파일 생성과 함께 위와 같은 디렉토리 구조가 형성됩니다</p>
<ul>
<li>contracts : 스마트 컨트랙트 코드를 담을 디렉토리 </li>
<li>tests : 테스트 코드를 담을 디렉토리</li>
<li>migrations : 배포 관련 코드를 담을 디렉토리</li>
</ul>
<p><br><br></p>
<p><strong>컨트랙트 생성</strong></p>
<p>오늘도 카운터 기능을 구현한 스마트 컨트랙트 코드를 작성합니다</p>
<br>

<p>[Counter.sol]</p>
<pre><code class="language-js">// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Counter {
    uint256 value;

    constructor() {

    }
    function getValue() public view returns(uint256) {
        return value;
    }
    function increment() public {
        value += 1;
    }
        function decrement() public {
        value -= 1;
    }
}</code></pre>
<br>

<p><strong>컴파일 실행</strong></p>
<pre><code class="language-sh">npx truffle compile</code></pre>
<p>작성한 컨트랙트의 컴파일을 위 명령어로 실행합니다
(사용 가능한 명령어는 컨피그 파일(<code>truffle-confg</code>)을 참고합시다)</p>
<br>


<p>그 결과 build 디렉토리와 json파일이 생성됩니다 (<code>Counter.json</code>)
이 파일은 단일 객체로 구성되어 있는데, 그 안에는 컨트랙트의 ABI 정보, 바이트 코드 등 
배포에 필요환 정보와 함께<code>web3</code> 메서드도 내장되어 있습니다
즉 통신(RPC 통신)을 위한 기능도 포함되어 있다는 뜻입니다</p>
<pre><code class="language-js">// ↓ web3 인스턴스 생성에 필요한 네트워크 주소는 컨피그 파일에서 자동으로 가져옵니다
const web3 = new Web3(&quot;127.0.0.1:8545&quot;)</code></pre>
<br>



<p>[build/Counter.json]</p>
<pre><code class="language-js">{
  &quot;contractName&quot;: &quot;Counter&quot;,
  &quot;abi&quot;: [
    {
      &quot;inputs&quot;: [],
      &quot;stateMutability&quot;: &quot;nonpayable&quot;,
      &quot;type&quot;: &quot;constructor&quot;
    },
    {
      &quot;inputs&quot;: [],
      &quot;name&quot;: &quot;getValue&quot;,
      &quot;outputs&quot;: [
        {
          &quot;internalType&quot;: &quot;uint256&quot;,
          &quot;name&quot;: &quot;&quot;,
          &quot;type&quot;: &quot;uint256&quot;
        }
      ],
      &quot;stateMutability&quot;: &quot;view&quot;,
      &quot;type&quot;: &quot;function&quot;
    },
    {
      &quot;inputs&quot;: [],
      &quot;name&quot;: &quot;increment&quot;,
      &quot;outputs&quot;: [],
      &quot;stateMutability&quot;: &quot;nonpayable&quot;,
      &quot;type&quot;: &quot;function&quot;
    },
    {
      &quot;inputs&quot;: [],
      &quot;name&quot;: &quot;decrement&quot;,
      &quot;outputs&quot;: [],
      &quot;stateMutability&quot;: &quot;nonpayable&quot;,
      &quot;type&quot;: &quot;function&quot;
    }
  ],
  &quot;metadata&quot;: &quot;{\&quot;compiler\&quot;:{\&quot;version\&quot;:\&quot;0.8.20+commit.a1b79de6\&quot;},\&quot;language\&quot;:\&quot;Solidity\&quot;,\&quot;output\&quot;:{\&quot;abi\&quot;:[{\&quot;inputs\&quot;:[],\&quot;stateMutability\&quot;:\&quot;nonpayable\&quot;,\&quot;type\&quot;:\&quot;constructor\&quot;},{\&quot;inputs\&quot;:[],\&quot;name\&quot;:\&quot;decrement\&quot;,\&quot;outputs\&quot;:[],\&quot;stateMutability\&quot;:\&quot;nonpayable\&quot;,\&quot;type\&quot;:\&quot;function\&quot;},{\&quot;inputs\&quot;:[],\&quot;name\&quot;:\&quot;getValue\&quot;,\&quot;outputs\&quot;:[{\&quot;internalType\&quot;:\&quot;uint256\&quot;,\&quot;name\&quot;:\&quot;\&quot;,\&quot;type\&quot;:\&quot;uint256\&quot;}],\&quot;stateMutability\&quot;:\&quot;view\&quot;,\&quot;type\&quot;:\&quot;function\&quot;},{\&quot;inputs\&quot;:[],\&quot;name\&quot;:\&quot;increment\&quot;,\&quot;outputs\&quot;:[],\&quot;stateMutability\&quot;:\&quot;nonpayable\&quot;,\&quot;type\&quot;:\&quot;function\&quot;}],\&quot;devdoc\&quot;:{\&quot;kind\&quot;:\&quot;dev\&quot;,\&quot;methods\&quot;:{},\&quot;version\&quot;:1},\&quot;userdoc\&quot;:{\&quot;kind\&quot;:\&quot;user\&quot;,\&quot;methods\&quot;:{},\&quot;version\&quot;:1}},\&quot;settings\&quot;:{\&quot;compilationTarget\&quot;:{\&quot;project:/contracts/Counter.sol\&quot;:\&quot;Counter\&quot;},\&quot;evmVersion\&quot;:\&quot;shanghai\&quot;,\&quot;libraries\&quot;:{},\&quot;metadata\&quot;:{\&quot;bytecodeHash\&quot;:\&quot;ipfs\&quot;},\&quot;optimizer\&quot;:{\&quot;enabled\&quot;:false,\&quot;runs\&quot;:200},\&quot;remappings\&quot;:[]},\&quot;sources\&quot;:{\&quot;project:/contracts/Counter.sol\&quot;:{\&quot;keccak256\&quot;:\&quot;0x7a1882e21087d57b479fb97d9e11290fe04f6eb499e06ffbdcdd41b20211c0d4\&quot;,\&quot;license\&quot;:\&quot;MIT\&quot;,\&quot;urls\&quot;:[\&quot;bzz-raw://11449b1a51092d1b3191d0a87b1937d3bcb6ebdc3dbb0920b7f174c0292b077a\&quot;,\&quot;dweb:/ipfs/Qmc3sxpeLSsc11Ats3fw5XckZHQzXd1fTwmGm9iA9TGjKv\&quot;]}},\&quot;version\&quot;:1}&quot;,
  &quot;bytecode&quot;: &quot;0x608060405234801561000f575f...&quot;,
  &quot;deployedBytecode&quot;: &quot;0x608060405234801561000...&quot;,
... </code></pre>
<p><br><br></p>
<p><strong>배포 ~ 마이그레이션</strong></p>
<p>먼저 어떤 블록체인 네트워크에 배포할 것인지를 설정해야 합니다
컨피그 파일에 기본적으로 배포를 위한 여러 세팅값이 전부 주석처리가 되어 있습니다</p>
<p>↓ <code>deployment</code> 섹션의 주석을 해제하기
가나쉬 네트워크(로컬호스트 8545포트를 사용중)가 열려있는 상태라면 자동으로 배포가 진행됩니다</p>
<pre><code class="language-js">  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it&#39;s defined here and no other network is specified at the command line.
    // You should run a client (like ganache, geth, or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    development: {
     host: &quot;127.0.0.1&quot;,     // Localhost (default: none)
     port: 8545,            // Standard Ethereum port (default: none)
     network_id: &quot;*&quot;,       // Any network (default: none)
    },
    //
    // An additional network, but with some advanced options…
    // advanced: {
    //   port: 8777,             // Custom port
    //   network_id: 1342,       // Custom network
    //   gas: 8500000,           // Gas sent with each transaction (default: ~6700000)
    //   gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
    //   from: &lt;address&gt;,        // Account to send transactions from (default: accounts[0])
    //   websocket: true         // Enable EventEmitter interface for web3 (default: false)
    // },
    //
    // Useful for deploying to a public network.
    // Note: It&#39;s important to wrap the provider as a function to ensure truffle uses a new provider every time.
    // goerli: {
    //   provider: () =&gt; new HDWalletProvider(MNEMONIC, `https://goerli.infura.io/v3/${PROJECT_ID}`),
    //   network_id: 5,       // Goerli&#39;s id
    //   confirmations: 2,    // # of confirmations to wait between deployments. (default: 0)
    //   timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
    //   skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    // },
    //
    // Useful for private networks
    // private: {
    //   provider: () =&gt; new HDWalletProvider(MNEMONIC, `https://network.io`),
    //   network_id: 2111,   // This network is yours, in the cloud.
    //   production: true    // Treats this network as if it was a public net. (default: false)
    // }
  },</code></pre>
<p>로컬이 아닌 진짜 블록체인 네트워크에 배포하게 될 때는 메인넷의 도메인을 기입해야 되겠죠</p>
<p><br><br></p>
<p><strong>마이그레이션 파일 생성</strong></p>
<p>컨트랙트 배포를 위해서는 <code>migrations</code> 디렉토리에 마이그레이션 파일을 만들어야 합니다
마이그레이션 파일은 <code>(숫자_)파일명.js</code> 형태로 만드는 것이 규칙이라고 하네요
그리고 Counter.json 파일 안에 담긴 바이트코드가 필요합니다</p>
<pre><code class="language-js">// artifacts는 json 파일을 가리킵니다 Counter는 json 파일 안에 있는 바이트코드
const Counter = artifacts.require(&#39;Counter&#39;)


module.exports = (deployer) =&gt; {
    deployer.deploy(Counter)
}</code></pre>
<br>

<pre><code class="language-sh">npx truffle migration</code></pre>
<p>그리고 명령어 입력시 컨트랙트 배포가 자동으로 진행됩니다</p>
<br>

<pre><code class="language-sh">   Deploying &#39;Counter&#39;
   -------------------
   &gt; transaction hash:    0x96d4a63833340a14ff081153dadb4ef2b863cbee91558be180b9e2a8685ea4b0
   &gt; Blocks: 0            Seconds: 0
   &gt; contract address:    0x0734239Ba8884096Cf1230B9dc4F7D7EDb3934d5
   &gt; block number:        1
   &gt; block timestamp:     1684889555
   &gt; account:             0xEAEa8C196275Bd319533754706cD304401703e6a
   &gt; balance:             99.99689678
   &gt; gas used:            155161 (0x25e19)
   &gt; gas price:           20 gwei
   &gt; value sent:          0 ETH
   &gt; total cost:          0.00310322 ETH</code></pre>
<br>

<blockquote>
<p>지정하지도 않은 어카운트가 자동으로 잡히는 이유?</p>
</blockquote>
<p>트러플은 가나쉬 네트워크에서 임의 생성한 10개의 어카운트 중에서 
첫번째 어카운트을 디폴트로 설정하고 배포를 진행합니다
이는 가나쉬 서버를 리셋하더라도 자동으로 첫번째 계정을 잡아주기 때문에 아주 편리합니다</p>
<p>물론 로컬이 아닌 실제 네트워크(테스트넷이나 메인넷)에서 배포를 진행할 때는
배포를 실행할 어카운트의 비밀키를 컨피그 파일에 기입해 두어야합니다</p>
<br>


<blockquote>
<p>*솔리디티 버전 호환성 이슈...</p>
</blockquote>
<pre><code class="language-sh">Error:  *** Deployment Failed ***

&quot;Counter&quot; hit an invalid opcode while deploying. Try:
   * Verifying that your constructor params satisfy all assert conditions.
   * Verifying your constructor code doesn&#39;t access an array out of bounds.
   * Adding reason strings to your assert statements.
</code></pre>
<p>유효하지 않은 오퍼코드(invalid opcode)가 발생했다고 나와 있는데
아마도 버전 호환성 문제로 추측됩니다</p>
<p>컨피그 파일의 버전(컴파일러 버전)을 낮추는 것으로 해결 완료
(0.8.20 → 0.8.0 ~ 0.8.18)</p>
<p><br><br></p>
<p><strong>truffle console</strong></p>
<pre><code class="language-sh">npx truffle console</code></pre>
<p>명령어를 사용하면 자바스크립트 콘솔 환경에서 솔리디티 코드를 실행할 수 있습니다</p>
<br>

<pre><code class="language-js">
truffle(development)&gt; Counter.address

// &#39;0x0734239Ba8884096Cf1230B9dc4F7D7EDb3934d5&#39;

truffle(development)&gt; Counter.abi
/**
[
  { inputs: [], stateMutability: &#39;nonpayable&#39;, type: &#39;constructor&#39; },
  {
    inputs: [],
    name: &#39;getValue&#39;,
    outputs: [ [Object] ],
    stateMutability: &#39;view&#39;,
    type: &#39;function&#39;,
    constant: true
  },
  {
    inputs: [],
    name: &#39;increment&#39;,
    outputs: [],
    stateMutability: &#39;nonpayable&#39;,
    type: &#39;function&#39;
  },
  {
    inputs: [],
    name: &#39;decrement&#39;,
    outputs: [],
    stateMutability: &#39;nonpayable&#39;,
    type: &#39;function&#39;
  }
]
**/</code></pre>
<p>그리고 콘솔창을 통해서 배포된 컨트랙트에 관한 여러 정보를 확인 할 수 있습니다</p>
<br>


<p>↓ 응용 예시 : Counter 컨트랙트의 인스턴스 생성 &amp; 메서드 호출</p>
<pre><code class="language-js">let counter;
Counter.deployed().then(instance =&gt; counter = instance)</code></pre>
<p>트러플 콘솔 환경에 counter 인스턴스가 생성됩니다</p>
<br>

<p>이제 인스턴스를 통해 컨트랙트 메서드의 작동을 확인 할 수 있습니다</p>
<pre><code class="language-js">// Counter.deployed().then(data =&gt; data.getValue())
counter.getValue()
// BN { negative: 0, words: [ 0, &lt;1 empty item&gt; ], length: 1, red: null }


counter.increment() // 트랜잭션이 발동됩니다
counter.increment()
counter.increment()


counter.getValue()
// BN { negative: 0, words: [ 3, &lt;1 empty item&gt; ], length: 1, red: null }
// increment를 호출한 만큼 값이 증가


counter.decrement()
counter.getValue()
// BN { negative: 0, words: [ 2, &lt;1 empty item&gt; ], length: 1, red: null }</code></pre>
<br>

<p>+) 인스턴스를 얻는 다른 방법</p>
<pre><code class="language-js"> const counter = await Counter.deployed()</code></pre>
<p><br><br></p>
<p><strong>테스트 코드 작성하기</strong></p>
<p>트러플에는 <code>jest</code> 대신 <code>Mocha</code>가 내장되어 있습니다
함수명이 약간씩 다르긴 하지만 기본적인 사용법은 거의 비슷합니다</p>
<p>[Counter.test.js]</p>
<pre><code class="language-js">const Counter = artifacts.require(&quot;Counter&quot;);

// constract &lt;- describe의 상위 개념?
contract(&quot;Counter&quot;, (account) =&gt; {
    console.log(account)
})

/**
[
  &#39;0xEAEa8C196275Bd319533754706cD304401703e6a&#39;,
  &#39;0x8805905ECdbD6CaB78737952319E27B0e60D7B93&#39;,
    ...
]
  0 passing (0ms)
**/</code></pre>
<br>


<p>테스트 코드 실행 명령어</p>
<pre><code class="language-sh">npx truffle test</code></pre>
<br>

<p>트러플의 테스트 기능은 항상 컨트랙트 재배포를 실시한 다음에 테스트할 코드가 실행됩니다
(솔리디티 코드를 작성할 때 글자 하나만 고쳐도 바이트 코드와 ABI가 변하기 때문에 
기능 테스트 작업은 꼭 재배포를 거쳐야만 합니다)</p>
<br>

<p><strong>단위 테스트 코드 예제</strong></p>
<pre><code class="language-js">const Counter = artifacts.require(&quot;Counter&quot;);

contract(&quot;Counter&quot;, (accounts) =&gt; {
  let counterInstance;

  beforeEach(async () =&gt; {
    counterInstance = await Counter.deployed();
  });

  it(&quot;getCount?&quot;, async () =&gt; {
    const counterValue = await counterInstance.getValue();
    assert.equal(counterValue, 0);
  });

  it(&quot;increment?&quot;, async () =&gt; {
    await counterInstance.increment();
    const counterValue = await counterInstance.getValue();
    assert.equal(counterValue, 1);
  });

  it(&quot;decrement?&quot;, async () =&gt; {
    await counterInstance.increment();
    await counterInstance.increment();
    await counterInstance.decrement();
    const counterValue = await counterInstance.getValue();
    assert.equal(counterValue, 2);
  });
});</code></pre>
<p>그리고 테스트 코드가 실행될 때마다 하나씩 블럭이 쌓이는 것도 확인할 수 있습니다</p>
<p><br><br></p>
<p>이것으로 트러플 튜토리얼 마침.</p>
<p><br><br></p>
<h3 id="truffle--react">Truffle + React</h3>
<br>

<p>리액트 프로젝트 생성</p>
<pre><code class="language-sh">npx create-react-app
npm install web3</code></pre>
<br>

<p>contracts 디렉토리를 만든 뒤 컨트랙트 빌드 파일(Counter.json) 파일을 집어넣습니다
이 json 객체는 컨트랙트에 관한 올인원 패키지 역할을 합니다</p>
<br>

<ol>
<li>사용자 어카운트와 web3 인스턴스를 반환할 훅 생성</li>
</ol>
<pre><code class="language-js">import Web3 from &quot;web3&quot;;
import { useEffect, useState } from &quot;react&quot;;

const useWeb3 = () =&gt; {
    const [account, setAccount] = useState(null);
    const [web3, setWeb3] = useState(null);

    const init = async () =&gt; {
        const [account] = await window.ethereum.request({ method: &quot;eth_requestAccounts&quot; });
        const web3Instance = new Web3(window.ethereum);

        setAccount(account);
        setWeb3(web3Instance);
    };

    useEffect(() =&gt; {
        if (!window.ethereum) return;
        init();
    }, []);
    return [account, web3];
};

export default useWeb3;</code></pre>
<p><code>requestAcount</code>도 web3 메서드로 사용하는 것이 좋을지도...?</p>
<br>





<ol start="2">
<li>프론트엔드에서의 이벤트 처리</li>
</ol>
<p><code>send</code>, <code>call</code> 메서드를 발동시키려면 먼저 컨트랙트 인스턴스를 생성해야 합니다</p>
<br>

<pre><code class="language-js">new web3.eth.Contract(ABI, CA) // = deployed())</code></pre>
<p>인스턴스를 생성하려면 빌드파일에서부터 ABI와 컨트렉트 어드레스로 인자로 받아야 합니다
(CA를 어떤 방식으로 가져오는게 좋을지... 일단은 하드코딩)</p>
<br>

<pre><code class="language-js">import CounterContract from &quot;../contracts/Counter.json&quot;
...

    useEffect(() =&gt; {
        if (!web3 || !account) return

        // Contract(ABI, CA)
        const Deployed = new web3.eth.Contract(CounterContract.abi, CounterContract.networks[1684888803590].address) // = deployed())

        Deployed.methods.increment().send({ from: account })
    }, [])</code></pre>
<p>어제까지와 비교하면 메서드 발동 코드가 훨씬 짧아진 것이 눈에 띕니다</p>
<br>

<pre><code class="language-js">    // 트러플 사용전
    const increment = async () =&gt; {
      const incrementData = abi.find((data) =&gt; data.name === &quot;increment&quot;)
      const data = web3.eth.abi.encodeFunctionCall(incrementData, [])

      const from = user.account
      const to = &quot;0xC56b7474E0A1Bf6ab53fdD9DeC1dE098b8b3C03C&quot;

      const tx = {
        from,
        to,
        data,
      }
      await web3.eth.sendTransaction(tx).then((data) =&gt; {
        getCount()
      }).catch(console.error)



    // 트러플 사용
    const increment = async () =&gt; {
        await deployed.methods.increment().send({ from: account })
    }</code></pre>
<p>↑ 확실히 <code>send</code>, <code>call</code> 메서드 발동 코드가 훨씬 간결하고 직관적으로 변했네요
<br></p>
<ol>
<li>abi 파일에서 increment 메서드를 인코딩</li>
<li>인코딩한 데이터로 tx 객체를 생성</li>
<li>send()</li>
</ol>
<p>이것도 트러플이 제공하는 편의성중 하나입니다
어딘가 JSX 문법과 비슷해보이기도 합니다</p>
<br>




<br>


<p>↓ 아직 한참 리팩토링이 필요한 카운터 컴포넌트</p>
<pre><code class="language-js">import { useState, useEffect } from &quot;react&quot;
import CounterContract from &quot;../contracts/Counter.json&quot;

const Counter = ({ account, web3 }) =&gt; {
    const [count, setCount] = useState(0);
    const [deployed, setDeployed] = useState(null)

    const increment = async () =&gt; {
        if (deployed === null) return console.log(&quot;no deployed&quot;)
        await deployed.methods.increment().send({ from: account })

        deployed.methods.getValue().call().then(value =&gt; {
            setCount(value)
        })
    }
    const decrement = async () =&gt; {
        if (deployed === null) return console.log(&quot;no deployed&quot;)
        await deployed.methods.decrement().send({ from: account })

        deployed.methods.getValue().call().then(value =&gt; {
            setCount(value)
        })
    }
    useEffect(() =&gt; {
        if (!web3 || !account) return

        // Contract(abi, CA) ~ Deployed 객체는 일종의 API 서버 역할
        const Deployed = new web3.eth.Contract(CounterContract.abi, CounterContract.networks[1684895478933].address) // == deployed()
        setDeployed(Deployed)

        Deployed.methods.getValue().call().then(value =&gt; {
            setCount(value)
        })}
    , [])


    return &lt;&gt;
        &lt;div&gt;
            &lt;h2&gt;Counter : {count}&lt;/h2&gt;
            &lt;button onClick={increment}&gt;+&lt;/button&gt;
            &lt;button onClick={decrement}&gt;-&lt;/button&gt;
        &lt;/div&gt;
    &lt;/&gt;;
};

export default Counter;</code></pre>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/062bc91d-6e32-4b15-b25f-0e68625d0b7f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Metamask & React (23/05/23)]]></title>
            <link>https://velog.io/@kj_code00/Metamask-React-230523</link>
            <guid>https://velog.io/@kj_code00/Metamask-React-230523</guid>
            <pubDate>Tue, 23 May 2023 08:31:49 GMT</pubDate>
            <description><![CDATA[<img src="https://www.investopedia.com/thmb/-gc_13UZf_6AwpLewXxbwbPrbfk=/fit-in/1500x750/filters:format(png):fill(white):max_bytes(150000):strip_icc():format(webp)/Metamask_logo-aca547fe6081482085662b03e2235f98.jpg">


<br>

<h2 id="월렛-개요">월렛 개요</h2>
<br>


<p>사용자가 블록체인 네트워크와 소통할 때,
일반적으로 브라우저에서 다이렉트로 블록체인 노드에 트랜잭션 요청을 건네지는 않습니다 
(콜 메서드만 사용하는 경우가 아니라면) </p>
<p>트랜잭션을 발동시키기 위해 서명을 생성할 때 사용자에게 직접 개인키를 입력받도록 하는 것이 
보안적으로도, 사용성 측면에서도 그리 좋지 못하기 때문인데요</p>
<p>그래서 블록체인 네트워크에 요청을 보낼 때는 
보통 서명 기능을 담당할 <strong>월렛</strong>이라는 중개자를 거치게 됩니다
(<code>브라우저</code> &gt; <code>월렛</code> &gt; <code>블록체인 네트워크</code>)
<br></p>
<p>이 과정을 풀어쓰면 다음과 같습니다</p>
<ol>
<li>월렛에 데이터 전달: 브라우저에서 생성한 트랜잭션 데이터를 월렛에 전달합니다
이는 일반적으로 월렛 API를 사용해서 데이터를 전달하는 방식으로 이루어집니다</li>
</ol>
<pre><code class="language-js">// 브라우저 단에서 생성할 트렌잭션 데이터 구조 예시
const tx = {
    from: &quot;&quot;,
    to: &quot;&quot;,
    value: &quot;&quot;,
    gas: &quot;&quot;,
    gasPrice: &quot;&quot;,
    // r, v, s (서명은 월렛에서 처리)
}</code></pre>
<br>


<ol start="2">
<li>서명 생성: 월렛은 전달받은 데이터를 기반으로 서명을 생성합니다
서명은 <strong>개인키</strong>를 사용하여 생성되며 트랜잭션의 무결성을 보장합니다</li>
</ol>
<br>


<ol start="3">
<li><p>블록체인 노드에 전달: 월렛은 서명된 트랜잭션을 블록체인 노드에 전송합니다</p>
<br>
</li>
<li><p>트랜잭션 확인과 블록 생성: 블록체인 노드는 전달받은 트랜잭션을 확인하고, 
무결성을 검증한 뒤 새로운 블록을 생성합니다</p>
</li>
</ol>
<p><br><br></p>
<p>그러면 월렛을 통해서 직접 트렌젝션 요청을 전달하는 과정을 실습해보겠습니다
가나쉬 네트워크를 기반으로, 가장 대중적인 월렛 프로그램인 메타마스크를 사용합니다</p>
<p><br><br></p>
<h2 id="metamask--react">Metamask &amp; React</h2>
<br>

<p>먼저 리액트 프로젝트를 생성합니다
(메타마스크 설치 과정은 생략)</p>
<pre><code class="language-sh">npx create-react-app metamask
npm install web3</code></pre>
<br>

<pre><code class="language-js">console.log(window.ethereum)
// Proxy(c) {_events: {…}, _eventsCount: 0, _maxListeners: 100, _log: l, _state: {…}, …}</code></pre>
<p>메타마스크를 설치하면 브라우저의 window객체 안에 <strong>ethereum객체</strong>가 주입됩니다
그리고 이 이더리움 객체를 통해 브라우저와 메타마스크 간의 소통이 이루어집니다
(월렛을 설치하지 않은 브라우저에는 이더리움 객체가 존재하지 않습니다)</p>
<p><br><br></p>
<ol>
<li>브라우저와 메타마스크 연동하기</li>
</ol>
<pre><code class="language-js">useEffect(()=&gt; {
  window.ethereum.request({
    method: &quot;eth_chainId&quot;
  }).then((data) =&gt; console.log(data))  // 0x539 = 1337
}, [])</code></pre>
<p>현재 메타마스크가 바라보고 있는 네트워크의 체인ID(가나쉬/1337)가 찍히는 것을 확인</p>
<br>


<ol start="2">
<li><p>로그인 구현하기</p>
<pre><code class="language-js">function App() {
const [account, setAccount] = useState(null)

useEffect(()=&gt; {
 window.ethereum.request({
   method: &quot;eth_requestAccounts&quot;
 }).then(([data]) =&gt; setAccount(data))

}, [])

return (
 &lt;&gt;
   {account || &#39;로그인해주세요&#39;}
 &lt;/&gt;
);
}</code></pre>
</li>
</ol>
<p><code>requestAccounts</code> 메서드가 발동되면 브라우저의 메타마스크가 반응하는 것을 확인할 수 있습니다
메타마스크에서 사용자의 허가를 거치면 브라우저와 메타마스크가 세션을 맺게 됩니다</p>
<p>이것이 일반적인 탈중앙화의 로그인 방식입니다</p>
<p>이와 같이 디앱 설계의 포커스는 비밀키를 제외한 사용자의 개인정보는 받지 않도록 구현하는 데에 있습니다</p>
<br>




<ol start="3">
<li>RPC통신을 더 간결하게 사용하기 위해 <code>web3</code> 라이브러리를 활용할 수도 있습니다</li>
</ol>
<pre><code class="language-js">const [web3, setWeb3] = useState(null)

...
setWeb3(new Web3(window.ethereum))

...

const handleClick = (e) =&gt; {
    console.log(web3)
    web3.eth.getBalance(account).then(console.log)
}

return &lt;button onClick={handleClick}&gt;balance&lt;/button&gt;</code></pre>
<p><br><br></p>
<ol start="4">
<li><code>sendTransaction</code>도 날려봅시다</li>
</ol>
<pre><code class="language-js">  const handleSubmit = (e) =&gt; {
    e.preventDefault()

    const to = e.target.received.value
    // 입력값을 wei 단위로 변환해야 합니다 (10 =&gt; 10^18)
    const value = web3.utils.toWei(e.target.amount.value, &quot;ether&quot;)

    const tx = {
      from: account,
      to, // 가나쉬에서 임의의 어카운트 선택
      value,
    }
    web3.eth.sendTransaction(tx).then(console.log)
  }

...
&lt;form onSubmit={handleSubmit}&gt;
    &lt;input type=&quot;text&quot; id=&quot;received&quot; placeholder=&quot;received&quot; /&gt;
    &lt;input type=&quot;number&quot; id=&quot;amount&quot; placeholder=&quot;amount&quot; /&gt;
    &lt;button type=&quot;submit&quot;&gt;transfer&lt;/button&gt;
&lt;/form&gt; 
...</code></pre>
<p>위에서 언급한대로 브라우저에서 생성한 트랜젝션 데이터를 메타마스크에 전달하면 
메타마스크는 사용자의 확인을 거친 뒤, 서명을 생성하고 완성된 트랜잭션을 블록체인 네트워크에 요청합니다</p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/ca3b2ed4-5689-4a02-bef3-9845c3e2722c/image.png" alt="">
<img src="https://velog.velcdn.com/images/kj_code00/post/c899c4aa-4e5d-4953-a23f-479d8b5aadd9/image.png" alt="">
이것으로 송금도 성공</p>
<br>

<p>추가로 월렛 커넥션을 맺을 때는 다른 외부 라이브러리를 사용하지 않는 것을 추천합니다
(특별한 경우가 아니라면 이더리움에서 제공하는 <code>web3</code>만으로도 충분합니다)</p>
<p><br><br></p>
<ol start="5">
<li>요청 메서드를 커스텀 훅으로 만들어서 사용하기</li>
</ol>
<p>[web3.hook.js]</p>
<pre><code class="language-js">import { useEffect, useState } from &quot;react&quot;
import Web3 from &quot;web3&quot;

const useWeb3 = () =&gt; {
    const [user, setUser] = useState({
        account: &quot;&quot;,
        balance: 0,
    })
    const [web3, setWeb3] = useState(null)

    useEffect(()=&gt; {
        if (window.ethereum) {
            window.ethereum.request({
                method: &quot;eth_requestAccounts&quot;
            }).then(async ([data])=&gt;{
                const web3Provider =new Web3(window.ethereum)
                setWeb3(web3Provider)
                setUser({
                    ...user,
                    account: data,
                    balance: web3Provider.utils.toWei(await web3Provider.eth.getBalance(data), &quot;ether&quot;),
                })
            }).catch(console.error)
        } else {
            alert(&quot;메타마스크를 설치해주세요&quot;)
        }
    }, [])

    return {
        user,
        web3,
    }
}

export default useWeb3</code></pre>
<br>


<p>[App.js]</p>
<pre><code class="language-js">...
function App() {
    const { user, web3 } = useWeb3();

    console.log(user) // {account: &quot;0xABC...&quot;, value: 100}

    const handleSubmit = async (e) =&gt; {
        e.preventDefault();
        const to = e.target.received.value;
        // gWei는 10**9, ether는 10**18
        const value = web3.utils.toWei(e.target.amount.value, &quot;ether&quot;);
        const tx = {
            from: user.account,
            to,
            value,
        };
        await web3.eth.sendTransaction(tx).then(console.log);
    };

    if (!user.account) return &quot;로그인해주세요&quot;;
    return (
        &lt;&gt;
            &lt;form onSubmit={handleSubmit}&gt;
                &lt;input type=&quot;text&quot; id=&quot;received&quot; placeholder=&quot;received&quot; /&gt;
                &lt;input type=&quot;number&quot; id=&quot;amount&quot; placeholder=&quot;amount&quot; /&gt;
                &lt;button type=&quot;submit&quot;&gt;transfer&lt;/button&gt;
            &lt;/form&gt;
        &lt;/&gt;
    );
}</code></pre>
<br>

<p>이제 지난 포스트에서 배포한 스마트 컨트랙트를 활용해볼 차례입니다</p>
<p><br><br></p>
<h2 id="metamask--solidity">Metamask &amp; Solidity</h2>
<br>

<ol>
<li>우선 리믹스에 간단한 스마트 컨트랙트를 작성해보겠습니다
<code>https://remix.ethereum.org/</code></li>
</ol>
<pre><code class="language-js">// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Counter {
  uint256 value;
  constructor(){}

  function setValue(uint256 _value) public {
    value = _value; // 값 설정
  }

  function getValue() public view returns (uint256) {
    return value;
  }
  // increment
  function increment() public {
    value += 1;
  }
  // decrement
  function decrement() public {
    value -= 1;
  }
}</code></pre>
<br>


<ol start="2">
<li>완성된 스마트 컨트랙트를 메타마스크를 통해 승인 절차를 걸친 뒤 가나쉬 네트워크에 배포합니다</li>
</ol>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/b3489396-d7cf-479c-b251-6b08f3773abe/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/f28bdcd2-96ef-4a50-8c0d-3652cbd6d560/image.png" alt=""></p>
<p>(생성된 CA-컨트랙트 어드레스-를 복사하기)</p>
<br>


<ol start="3">
<li><code>.call</code>~ CA를 통해 브라우저에서 컨트랙트의 상태를 불러올 수 있습니다</li>
</ol>
<br>


<p>먼저 컨트랙트의 인터페이스(<code>abi</code>)를 가져와야 합니다</p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/267dd2ed-129d-499d-8b02-bd1f696238de/image.png" alt=""></p>
<p>[./abi/counter.abi.json]</p>
<pre><code class="language-js">[
    {
        &quot;inputs&quot;: [],
        &quot;name&quot;: &quot;decrement&quot;,
        &quot;outputs&quot;: [],
        &quot;stateMutability&quot;: &quot;nonpayable&quot;,
        &quot;type&quot;: &quot;function&quot;
    },
    {
        &quot;inputs&quot;: [],
        &quot;name&quot;: &quot;increment&quot;,
        &quot;outputs&quot;: [],
        &quot;stateMutability&quot;: &quot;nonpayable&quot;,
        &quot;type&quot;: &quot;function&quot;
    },
      {
        &quot;inputs&quot;: [],
        &quot;name&quot;: &quot;getValue&quot;,
        &quot;outputs&quot;: [
            {
                &quot;internalType&quot;: &quot;uint256&quot;,
                &quot;name&quot;: &quot;&quot;,
                &quot;type&quot;: &quot;uint256&quot;
            }
        ],
        &quot;stateMutability&quot;: &quot;view&quot;,
        &quot;type&quot;: &quot;function&quot;
    }
    ...
]</code></pre>
<br>

<ul>
<li><p><code>name</code>: 함수의 이름</p>
</li>
<li><p><code>inputs</code>: 만약 함수에 전달될 인자들이 있다면 위 배열에 담깁니다</p>
</li>
<li><p><code>outputs</code>: 함수가 반환하는 값들의 배열입니다 (리턴 타입이 <code>void</code>라면 빈 배열)</p>
</li>
<li><p><code>stateMutability</code>: 함수의 상태 변경 가능성을 나타내는 속성입니다. 
<code>pure</code>, <code>view</code>, <code>nonpayable</code>, <code>payable</code> 등이 있습니다. 
(여기서 <code>nonpayable</code>은 외부 송금을 받지 않는 함수라는 의미인데, 
코드상에 컨트랙트의 상태 변경을 유발하는 로직이 담겨 있다면 함수가 발동할 때 가스를 소비합니다)</p>
</li>
</ul>
<br>

<p>그리고 가져온 abi파일을 활용해서 컨트랙트의 현재 상태를 가져올 수 있습니다 
(<code>.call</code> 발동)</p>
<pre><code class="language-js">import abi from &quot;./abi/counter.json&quot;

...
    const [count, setCount] = useState(null)
...
    useEffect(()=&gt; {
      if (!web3) return
        // name값(메서드명)으로 객체 찾기
      const getValueData = abi.find(data =&gt; data?.name === &quot;getValue&quot;)
      // 두번째 인자([])에는 매개변수가 담깁니다
      const data = web3.eth.abi.encodeFunctionCall(getValueData, [])

      web3.eth.call({
        to: &quot;0xB02Fd6b8c7e39A83FDc9c1114024Ea1b1e8bd362&quot;, // CA
        data,
      }).then(data =&gt; {
        const result = web3.utils.toBN(data).toString(10)
        setCount(result)
      })
    }, [web3])

...
return (

        &lt;div&gt;
          &lt;h2&gt;카운터 : {count}&lt;/h2&gt;
          &lt;button onClick={increment}&gt;증가&lt;/button&gt;
          &lt;button onClick={decrement}&gt;감소&lt;/button&gt;
        &lt;/div&gt;

)    </code></pre>
<p><br><br></p>
<ol start="4">
<li><code>.send</code> ~ 이제 컨트랙트의 상태를 바꿔봅시다
이때 가스는 메서드의 <strong>사용자</strong>가 지불하도록 해야 합니다 (from 속성)</li>
</ol>
<pre><code class="language-js">    const increment = async () =&gt; {
      const incrementData = abi.find((data) =&gt; data.name === &quot;increment&quot;)
      const data = web3.eth.abi.encodeFunctionCall(incrementData, [])

      const from = user.account // 사용자 계정
      const to = &quot;0xB02Fd6b8c7e39A83FDc9c1114024Ea1b1e8bd362&quot; // CA

      const tx = {
        from,
        to,
        data
      }
      const result = await web3.eth.sendTransaction(tx)
      setCount(web3.utils.toBN(result).toString(10))
    }</code></pre>
<br>

<p>감소도 같은 방식으로 처리합니다
단, 지금의 코드에서는 상태(타입이 uint)를 음수로 내리는 것은 불가능합니다</p>
<p><br><br><br></p>
<p>↓ 약간의 리팩토링</p>
<pre><code class="language-js">import useWeb3 from &quot;./hooks/web3.hook&quot;;
import { useEffect, useState } from &quot;react&quot;
import abi from &quot;./abi/counter.json&quot;

function App() {
    const { user, web3 } = useWeb3();
    const [count, setCount] = useState(null)
    const [isLoading, setIsLoading] = useState(true)

    const handleSubmit = async (e) =&gt; {
        e.preventDefault();
        const to = e.target.received.value;
        // gWei는 10^9, ether는 10^18
        const value = web3.utils.toWei(e.target.amount.value, &quot;ether&quot;);
        const tx = {
            from: user.account,
            to,
            value,
        };
        await web3.eth.sendTransaction(tx).then(console.log);
    };

    const increment = async () =&gt; {
      const incrementData = abi.find((data) =&gt; data.name === &quot;increment&quot;)
      const data = web3.eth.abi.encodeFunctionCall(incrementData, [])

      const from = user.account
      const to = &quot;0xC56b7474E0A1Bf6ab53fdD9DeC1dE098b8b3C03C&quot;

      const tx = {
        from,
        to,
        data,
        gas: 50000,
        gasPrice: 20000000000,
      }
      await web3.eth.sendTransaction(tx).then((data) =&gt; {
        getCount()
      }).catch(console.error)



      setIsLoading(false)
    }
    const decrement = async () =&gt; {
      const incrementData = abi.find((data) =&gt; data.name === &quot;decrement&quot;)
      const data = web3.eth.abi.encodeFunctionCall(incrementData, [])

      const from = user.account
      const to = &quot;0xC56b7474E0A1Bf6ab53fdD9DeC1dE098b8b3C03C&quot;

      const tx = {
        from,
        to,
        data,
        gas: 50000,
        gasPrice: 20000000000,
      }
      await web3.eth.sendTransaction(tx).then((data) =&gt; {
        getCount()
      }).catch(console.error)
    }

    const getCount = () =&gt; {
      if (!web3) return
      const getValueData = abi.find(data =&gt; data?.name === &quot;getValue&quot;)
      const data = web3.eth.abi.encodeFunctionCall(getValueData, [])

      web3.eth
      .call({
        to: &quot;0xC56b7474E0A1Bf6ab53fdD9DeC1dE098b8b3C03C&quot;,
        data,
      }).then((data) =&gt; {
        const result = web3.utils.toBN(data).toString(10)
        setCount(result)
      })
    }

    useEffect(()=&gt; {
      getCount()
      setIsLoading(false)
    }, [isLoading, web3])

    if (!user.account) return &quot;로그인해주세요&quot;;
    if (isLoading) return &#39;Loading...&#39;
    return (
        &lt;&gt;
            &lt;form onSubmit={handleSubmit}&gt;
                &lt;input type=&quot;text&quot; id=&quot;received&quot; placeholder=&quot;received&quot; /&gt;
                &lt;input type=&quot;number&quot; id=&quot;amount&quot; placeholder=&quot;amount&quot; /&gt;
                &lt;button type=&quot;submit&quot;&gt;transfer&lt;/button&gt;
            &lt;/form&gt;

            &lt;div&gt;
              &lt;h2&gt;카운터 : {count}&lt;/h2&gt;
              &lt;button onClick={increment}&gt;증가&lt;/button&gt;
              &lt;button onClick={decrement}&gt;감소&lt;/button&gt;
            &lt;/div&gt;
        &lt;/&gt;
    );
}

export default App;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스마트 컨트랙트 - 2 (23/05/22)]]></title>
            <link>https://velog.io/@kj_code00/%EC%8A%A4%EB%A7%88%ED%8A%B8-%EC%BB%A8%ED%8A%B8%EB%9E%99%ED%8A%B8-2-230522</link>
            <guid>https://velog.io/@kj_code00/%EC%8A%A4%EB%A7%88%ED%8A%B8-%EC%BB%A8%ED%8A%B8%EB%9E%99%ED%8A%B8-2-230522</guid>
            <pubDate>Mon, 22 May 2023 23:58:14 GMT</pubDate>
            <description><![CDATA[<img src="https://assets.newatlas.com/dims4/default/5ae9131/2147483647/strip/true/crop/2612x1741+0+0/resize/2612x1741!/quality/90/?url=http%3A%2F%2Fnewatlas-brightspot.s3.amazonaws.com%2F06%2F13%2F0afcd4bc415c8a189d095d5c23c2%2Funtitled-1.png">



<h2 id="컨트랙트-배포--실행">컨트랙트 배포 &amp; 실행</h2>
<br>

<p>스마트 컨트랙트의 배포와 실행은 다음과 같은 과정을 따릅니다</p>
<ol>
<li>솔리디티 코드 작성</li>
<li>솔리디티 컴파일</li>
<li>메세지의 데이터 속성에 컴파일된 내용(바이트 코드)을 보내기</li>
<li>트랜잭션 발동</li>
</ol>
<p><br><br></p>
<h3 id="1-솔리디티-코드-작성">1. 솔리디티 코드 작성</h3>
<br>

<p>우선 일반적인 형태의 자바스크립트의 클래스를 먼저 살펴보겠습니다</p>
<br>

<p>[class.js]</p>
<pre><code class="language-js">
class Counter {
  value
  constructor(){}

  // 상태변수의 값을 변화시킬 setter 함수. 인자값이 필요합니다
  setValue(_value){
      this.value = _value
  }
  // 상태변수의 값을 불러올 getter 함수. 리턴이 필요합니다
  getValue(){
      return this.value
  }
}

const counter = new Counter()


counter.getValue // undefined
counter.setValue(1) // counter: { value: 1 }</code></pre>
<p><br><br></p>
<p>아래는 솔리디티 문법으로 만든 스마트 컨트랙트인데요
구조와 형태가 클래스 문법과 아주아주 유사합니다</p>
<p>[class.sol]</p>
<pre><code class="language-js">// SPDX-License-Identifire: MIT
pragma solidity ^0.8.0; // 버전에 대한 명시 필수, &#39;;&#39;도 꼭 기입해야 합니다

// 멤버변수(상태변수)의 타입 지정과 함수의 접근제한자를 생략할 수 없습니다
contract Counter {
  uint256 value;
  constructor(){}

  // setter, send ~ 클라이언트로부터 매개변수를 받아야 합니다
  function setValue(uint256 _value) public {
    value = _value; // 값 설정
  }

  // getter, call  view ~ 상태변수의 현재값을 리턴하는 함수
  function getValue() public view returns (uint256) {
    return value;
  }
}

// { value : 0 } ~ uint 타입의 초기값</code></pre>
<br>




<br>

<p>스마트 컨트랙트는 블록체인 네트워크에서 실행되는 계약이며, 
이 계약을 호출하여 함수를 실행하는 주체는 <strong>클라이언트</strong>입니다 
그리고 코드의 실행은 EVM(이더리움 가상머신)이 담당해서 계약의 상태를 변경합니다</p>
<br>

<blockquote>
<p> 싱글톤과 상태공유</p>
</blockquote>
<p>어떠한 스마트 컨트랙트가 네트워크 상에 배치되었을 때는 하나의 인스턴스만이 존재하게 되는데, 
이를 싱글톤이라고 부릅니다
모든 노드들은 하나의 싱글톤 인스턴스들을 공유하고 같은 메모리 주소를 참조합니다
따라서 어느 노드에서 스마트 컨트랙트의 상태를 변경하면 다른 노드에서도 동일한 상태를 볼 수 있습니다</p>
<br>


<blockquote>
<p><code>call</code> 메서드와 <code>send</code> 메서드</p>
</blockquote>
<p>위 코드에서 getter 함수인 <code>getValue()</code>는 <code>call</code> 메서드에 해당하며, 
코드를 실행할 때 연산이 필요없기 때문에 메서드 호출시 가스를 소모하지 않습니다
(함수에서 <code>view</code> 포함 여부로 판단 가능)</p>
<p>그리고 매개변수를 받아야 하는 <code>setValue()</code>는 값의 변경을 동반하는 <code>sender</code> 메서드로, 
이와 같이 연산식이 포함된 함수를 호출하려면 클라이언트에게는 가스 수수료를 감당할 만큼의 코인이 필요합니다</p>
<br>

<p>스마트 컨트랙트 코드를 작성할 때는 가스 관리가 아주아주 중요합니다
고로 같은 기능을 만들더라도 연산량을 줄이는 방향으로 코드를 짜는 것이 관건입니다</p>
<p><br><br></p>
<h3 id="2-솔리디티-컴파일">2. 솔리디티 컴파일</h3>
<br>


<p>다음은 컴파일 명령어입니다 (*먼저 VSCode에 솔리디티 익스텐션을 설치해둘 것)</p>
<pre><code>npx solc --bin --abi ./class.sol</code></pre><br>

<p>컴파일 결과 2개의 파일이 생성됩니다</p>
<ul>
<li><code>class_sol_Counter.bin</code> : EVM이 읽을 수 있는 형태로 변환된 바이너리 코드입니다</li>
<li><code>class_sol_Counter.abi</code> : 사용자가 읽을 수 있는 코드로 바이너리를 조작하기 위한 파일입니다 
(<code>json</code> 형식)</li>
</ul>
<br>

<p>이제 <code>.bin</code> 파일에 담긴 내용(바이트 코드)을 그대로 복사한 뒤 
메세지 데이터에 실어서 트랜잭션을 발동시켜보겠습니다</p>
<br>

<pre><code class="language-js">web3.eth
    .sendTransaction({
        from: &quot;0x2c3338e54FE9B1Eb3e53DEc6837ebB27EC9388D6&quot;,
        gas: &quot;500000&quot;,
        data: &quot;0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b5780635524107714610059575b600080fd5b610043610075565b60405161005091906100a1565b60405180910390f35b610073600480360381019061006e91906100ed565b61007e565b005b60008054905090565b8060008190555050565b6000819050919050565b61009b81610088565b82525050565b60006020820190506100b66000830184610092565b92915050565b600080fd5b6100ca81610088565b81146100d557600080fd5b50565b6000813590506100e7816100c1565b92915050565b600060208284031215610103576101026100bc565b5b6000610111848285016100d8565b9150509291505056fea26469706673582212207f190c10bb79b204f212237a84288d17cbe5ab62d93b97a504500b25c5f5a52964736f6c63430008120033&quot;
    })
    .then(console.log);


/**
{
  transactionHash: &#39;0x82b9eaf738a0e049db562f07b5c9fd07b4c43eeb38801b1841b2ba299b0d6bbf&#39;,
  transactionIndex: 0,
  blockHash: &#39;0xf9144982523704847c4444373ed26cb65af944eb804c0b6c66a4fab3b15a3836&#39;,
  blockNumber: 5,
  from: &#39;0x2c3338e54fe9b1eb3e53dec6837ebb27ec9388d6&#39;,
  to: null,
  gasUsed: 125677,
  cumulativeGasUsed: 125677,
  contractAddress: &#39;0x14958d12Ced89f9cfC3f3B48BA12822833631e7f&#39;, // &lt;-- CA
  logs: [],
  status: true,
  logsBloom: &#39;0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&#39;
}
**/</code></pre>
<p>컨트랙트 주소가 생성되는 것을 확인할 수 있습니다</p>
<br>

<blockquote>
<p><strong>CA(Contract Address)</strong>?</p>
</blockquote>
<p><code>CA</code>는 스마트 컨트랙트가 배포된 블록체인 네트워크에서, 해당 스마트 컨트랙트를 고유하게 식별하는 주소를 의미합니다
블록체인 네트워크 상에 배포된 컨트랙트들은 모두 고유의 컨트랙트 주소값을 가지고 있습니다</p>
<p><br><br></p>
<blockquote>
<p><strong>ABI(Application Binary Interface)</strong>?</p>
</blockquote>
<p>ABI는 컨트랙트 함수와 매개변수들을 JSON 형식으로 나타낸 리스트입니다
이름처럼 스마트 컨트랙트에서 사용될 인터페이스 역할을 합니다</p>
<p>이 인터페이스는 컨트랙트 내의 어떤 함수를 호출할지를 지정하고, 
예측대로 함수가 데이터를 리턴한다는 것을 보장하기 위해 사용합니다</p>
<p><br><br></p>
<p>[.abi]</p>
<pre><code class="language-json">[{&quot;inputs&quot;:[],&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;constructor&quot;},
{&quot;inputs&quot;:[],&quot;name&quot;:&quot;getValue&quot;,&quot;outputs&quot;:[{&quot;internalType&quot;:&quot;uint256&quot;,&quot;name&quot;:&quot;&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;stateMutability&quot;:&quot;view&quot;,&quot;type&quot;:&quot;function&quot;},
{&quot;inputs&quot;:[{&quot;internalType&quot;:&quot;uint256&quot;,&quot;name&quot;:&quot;newValue&quot;,&quot;type&quot;:&quot;uint256&quot;}],&quot;name&quot;:&quot;setValue&quot;,&quot;outputs&quot;:[],&quot;stateMutability&quot;:&quot;nonpayable&quot;,&quot;type&quot;:&quot;function&quot;}]</code></pre>
<p>[call.js]</p>
<pre><code class="language-js">const Web3 = require(&quot;web3&quot;)
const web3 = new Web3(&quot;http://127.0.0.1:8545&quot;)




const abi = [{inputs:[],stateMutability:&quot;nonpayable&quot;,type:&quot;constructor&quot;},
{inputs:[],name:&quot;getValue&quot;,outputs:[{internalType:&quot;uint256&quot;,name:&quot;&quot;,type:&quot;uint256&quot;}],stateMutability:&quot;view&quot;,type:&quot;function&quot;},
{inputs:[{internalType:&quot;uint256&quot;,name:&quot;newValue&quot;,type:&quot;uint256&quot;}],name:&quot;setValue&quot;,outputs:[],stateMutability:&quot;nonpayable&quot;,type:&quot;function&quot;}]


const dataCode = web3.eth.abi.encodeFunctionCall({inputs:[],name:&quot;getValue&quot;,outputs:[{internalType:&quot;uint256&quot;,name:&quot;&quot;,type:&quot;uint256&quot;}],stateMutability:&quot;view&quot;,type:&quot;function&quot;}, [])
console.log(dataCode)




web3.eth
    // call 메서드는 hex값을 반환받습니다
    .call({
        to: &quot;0xb3c52A71e05cA5A806dA789944a9A462f0eA3D38&quot;,
        data: dataCode,
    })
    .then(data =&gt; {
        // 0x00000000... &gt; hex값을 10진수로 변환하는 메서드
        const result = web3.utils.toBN(data).toString(10)
        console.log(result)
    })

// node call</code></pre>
<br>

<p>정리하자면 다음과 같습니다</p>
<blockquote>
<p><code>deploy</code> - 작성한 스마트 컨트랙트를 이더리움 네트워크에 배포하는 것
 <code>send</code> - 네트워크에 존재하는 스마트 컨트랙트의 상태를 변경하는 것 (트랜잭션)
 <code>call</code> - 네트워크에 존재하는 스마트 컨트랙트의 상태변수를 가져오는 것 (읽기 전용)</p>
</blockquote>
<p>오늘은 여기까지...</p>
<p><br><br></p>
<p>번외</p>
<ul>
<li><strong>truffle</strong> ~ deploy, send, call을 묶어서 하나로 관리할 수 있는 프레임워크입니다
중요한 것은 매커니즘의 이해. send와 call을 단순 메서드가 아닌 통신의 관점으로 이해하는 것</li>
</ul>
<ul>
<li><strong>Remix IDE</strong>(<a href="https://remix.ethereum.org">https://remix.ethereum.org</a>) ~ 가스 설정을 미리 확인할 수 있습니다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스마트 컨트랙트 ~ 1 (23/05/19)]]></title>
            <link>https://velog.io/@kj_code00/%EC%8A%A4%EB%A7%88%ED%8A%B8-%EC%BB%A8%ED%8A%B8%EB%9E%99%ED%8A%B8-1-230519</link>
            <guid>https://velog.io/@kj_code00/%EC%8A%A4%EB%A7%88%ED%8A%B8-%EC%BB%A8%ED%8A%B8%EB%9E%99%ED%8A%B8-1-230519</guid>
            <pubDate>Mon, 22 May 2023 23:54:55 GMT</pubDate>
            <description><![CDATA[<img src="https://assets.newatlas.com/dims4/default/5ae9131/2147483647/strip/true/crop/2612x1741+0+0/resize/2612x1741!/quality/90/?url=http%3A%2F%2Fnewatlas-brightspot.s3.amazonaws.com%2F06%2F13%2F0afcd4bc415c8a189d095d5c23c2%2Funtitled-1.png">



<h2 id="스마트-컨트랙트-기초">스마트 컨트랙트 기초</h2>
 <br>

<ul>
<li><strong>스마트 컨트랙트</strong>란?</li>
</ul>
<blockquote>
<p>스마트 컨트랙트는 계약의 내용과 실행 조건을 코드를 통해 사전에 설정한 후 
해당 조건이 충족되면 블록체인 네트워크에서 자동적으로 계약을 실행하는 기능을 의미합니다</p>
</blockquote>
<p>이 때 계약의 조건과 실행 결과는 블록체인에 영구적으로 기록되며 변경이 불가능하고 검증도 가능합니다
즉, 중개자 없이 계약 당사자끼리도 신뢰도 높은 거래를 할 수 있게 해주는 것이 스마트 컨트랙트의 핵심입니다
<br></p>
<p>다만 스마트 컨트랙트의 실행에는 트랜잭션 처리에 필요한 수수료인 <strong>가스</strong>가 발생합니다
트랜잭션 발동 시점은 가스 소모량에 따라 달라지는데, 일반적으로는 비트코인보다는 훨씬 빠른 속도로 처리됩니다
대신 코드 연산의 복잡도에 따라 그 비용이 높아질 수 있다는 것이 문제점으로 꼽힙니다</p>
<p>현재 날짜 기준으로 이더리움의 시세는 대략 230만원대...가스는 개발 단계에서도 큰 문턱으로 다가옵니다</p>
<p>그래서 다음과 같은 도구들의 도움을 받을 필요성이 있습니다</p>
<p><br><br></p>
<h3 id="ganache">Ganache</h3>
<blockquote>
<p>Ganache</p>
</blockquote>
<ul>
<li>가나쉬(Ganache)는 로컬 환경에서 스마트 컨트랙트를 개발 및 테스트하는 데 사용하는 도구입니다</li>
<li>로컬에서도 실제 블록체인 네트워크와 유사한 환경에서 스마트 컨트랙트의 성능 측정과 디버깅이 가능합니다</li>
</ul>
<br>

<p>그러니까 가나쉬를 사용하면 공짜로 가스 소모량 측정이 가능하다는 것
가나쉬는 브라우저와 노드 양쪽 환경을 모두 지원합니다</p>
<br>

<p>설치 및 실행</p>
<pre><code>npm install ganache-cli
npx ganache-cli</code></pre><br>

<p>실행 명령어 입력 즉시 터미널에 다음과 같은 정보가 출력됩니다
(사용 가능한 어카운트, 각 어카운트의 비밀키, 지갑의 니모닉키, 가스 한도와 소모량 등...)
<br></p>
<pre><code>Ganache CLI v6.12.2 (ganache-core: 2.13.2)

Available Accounts
==================
(0) 0x5f08A8f707aE3B150FC1D83DcD08c7885a11489e (100 ETH)
(1) 0x49Af8F9c3819780Dc8dE49E8Cf1f781E228fA250 (100 ETH)
(2) 0xCEa401ebd7Fe8da7A7E9DeFcC44dbbDaf27D7A58 (100 ETH)
(3) 0xec460E0AE6B5EdbF57EA6252BE4Ab1c72f9276aa (100 ETH)


Private Keys
==================
(0) 0xe4ed3ba8b67a682bf55b2b2539d60c9b493f2d392900897a37eab0d3b50c550a
(1) 0x4c604418f5d04db0165945d0140a448beb3e064bc9a707398c13f75cf412d768
(2) 0x42011cd9c22b231289f95ab873960d52301bc44caaac2ccadbb507bf8c15035a
(3) 0x9c1df16b0bf9dd16b6d357cded344268324bf41e2494775cd7dd5496dee85e7e


HD Wallet
==================
Mnemonic:      vehicle duck brain sock open pigeon crew february quit submit meat lawsuit
Base HD Path:  m/44&#39;/60&#39;/0&#39;/0/{account_index}

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Call Gas Limit
==================
9007199254740991

Listening on 127.0.0.1:8545</code></pre><br>


<p>요청 &amp; 응답 테스트 해보기 </p>
<pre><code>// 요청
curl -X POST --data &#39;{&quot;jsonrpc&quot;:&quot;2.0&quot;, &quot;method&quot;:&quot;web3_clientVersion&quot;, &quot;params&quot;: []}&#39; http://127.0.0.1:8545

// 응답
{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;result&quot;:&quot;EthereumJS TestRPC/v2.13.2/ethereum-js&quot;}</code></pre><br>

<p>블록체인 네트워크(가나쉬)에의 요청은 포스트맨으로도 가능합니다 
단, 요청 내용은 전부 바디 영역에 담아야 합니다</p>
<pre><code class="language-js">// 연결된 모든 어카운트 요청
// 127.0.0.1:8545
{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;method&quot;: &quot;eth_accounts&quot;,
  &quot;params&quot;: []  
}

// 응답
{
    &quot;jsonrpc&quot;: &quot;2.0&quot;,
    &quot;result&quot;: [
        &quot;0x20eb301037015bc07f2ef5c5ba04ef75d784c2c4&quot;,
        &quot;0x3c955d6d8debb3f8484fc45fe59dfee6fd710e21&quot;,
        &quot;0xefaf2bc284b85e9974859d751548fb7099c05d2e&quot;,
        &quot;0x357b3730a02747c6ad6503262563888f94f2897b&quot;,
        &quot;0x552194de28e590bdebe6fa3a34d1428f219d10f4&quot;,
        &quot;0x77a9feca7b55ca87d50a6d3e8e4860cf92efe3ce&quot;,
        &quot;0xf29ac78ce5575c9a6b40c5505559445f0daf43af&quot;,
        &quot;0x31facca9a1613001a07b65ad7f63a632d2cc4cae&quot;,
        &quot;0x9149c54786091c66c1abde33fc5930184b662e27&quot;,
        &quot;0x3d51f729c037b13fe06f5e12b152dd1030ddafc0&quot;
    ]
}

// 어카운트의 밸런스 요청 (인자가 필요한 요청을 보내려면 params에 담아야 합니다)
{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;method&quot;: &quot;eth_getBalance&quot;,
  &quot;params&quot;: [&quot;0x20eb301037015bc07f2ef5c5ba04ef75d784c2c4&quot;]  
}


// 응답 (wei 단위, 10진수로 변환해서 10**18로 나누면 100eth가 됩니다)
{
    &quot;jsonrpc&quot;: &quot;2.0&quot;,
    &quot;result&quot;: &quot;0x56bc75e2d63100000&quot;
}


// 10ETH를 송금하는 트랜잭션 요청 
{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;method&quot;: &quot;eth_sendTransaction&quot;,
  &quot;params&quot;: [{
      &quot;from&quot; : &quot;0x136F87E6DbB4BDf620154F45957Df4D0c08dA0e5&quot;,
      &quot;to&quot; : &quot;0x55d179aDE01ca621D844f6883C75b7c6A6837589&quot;,
      &quot;value&quot; : &quot;10000000000000000000&quot; 
  }]
}

// 응답
{
    &quot;jsonrpc&quot;: &quot;2.0&quot;,
    &quot;result&quot;: &quot;0xcb42a205f58a88e7c5aaa4118f598052274a115a5c1b66240c6bcb4d898fcac5&quot;
}</code></pre>
<p>블록체인도 결국은 요청과 응답이라는 것.
<br></p>
<blockquote>
<p> <code>sendTransaction</code>? <code>sendRawTransaction</code>?</p>
</blockquote>
<p>두 메서드의 차이는 서명 방식의 차이입니다
<code>sendTransaction</code> 은 트랜잭션 객체를 노드에 보내면 노드에서 자동으로 서명을 진행하는 반면,
<code>sendRawTransaction</code>은  직접 트랜잭션을 생성, 서명해서 블록체인 네트워크에 전송하는 방법입니다
더 많은 제어권과 유연성을 지녔지만, 트랜잭션의 구성과 서명을 수동으로 처리해야 하기 때문에 좀 더 복잡합니다</p>
<br>

<p>가나쉬의 사용법에 대해서는 일단 여기까지만 다루겠습니다</p>
<p><br><br></p>
<h3 id="web3">web3</h3>
<blockquote>
<p>web3</p>
</blockquote>
<ul>
<li><p><code>web3</code>는 브라우저 환경에서 rpc 통신(블록체인 네트워크와의 소통)을 
좀 더 간편하게 사용하기 위해 고안된 라이브러리입니다
(노드 환경에서도 사용 가능, 사용성 측면에서 <code>axios</code>와도 비슷한 면이 있습니다)</p>
</li>
<li><p><code>web3</code>는 이더리움 재단에서 직접 만들어서 배포중이며, 유사한 기능을 가진 다른 사설 라이브러리(<code>ethers</code>)도 있습니다</p>
</li>
</ul>
<br>

<p><strong>cdn으로 web3 사용해보기</strong></p>
<p>주요 메서드는 인스턴스의 <code>eth</code>객체 안에 담겨있습니다
그리고 대부분의 함수는 프로미스를 반환합니다</p>
<pre><code class="language-js">&lt;script src=&quot;https://cdn.jsdelivr.net/npm/web3@1.10.0/dist/web3.min.js&quot;&gt;&lt;/script&gt;

const web3 = new Web3(&quot;http://127.0.0.1:8545&quot;)

// 응답은 프로미스 객체로 반환
web3.eth.getAccounts().then(console.log)
/**
  [&#39;0x136F87E6DbB4BDf620154F45957Df4D0c08dA0e5&#39;,
 &#39;0x55d179aDE01ca621D844f6883C75b7c6A6837589&#39;, 
 &#39;0x68971c9A476Ed4BDd77bb38003d64d4be20CFB6d&#39;, 
 &#39;0x5da8D9190d6ec13b1fa43Ce9F1F983b046044997&#39;, 
 &#39;0xaac778BBfaeDc719ba4Ba29Ac5Bde920da0a4815&#39;, 
 &#39;0x26333B7C59955A404c5f926113ec15F07EBD1E4F&#39;, 
 &#39;0xbDB2A53faBF9103035A61277e62356582b545896&#39;, 
 &#39;0xA3B8498B44DFb12554da947AA8a6f72d7e0b5330&#39;, 
 &#39;0x16FC300093e4dd3c67eaA26d485221DEd48bc2BF&#39;, 
 &#39;0x82314Aa618A625c5F35390C0CB279bB29D1dF876&#39;
]
**/

// eth_getBalance
web3.eth.getBalance(&quot;0x136F87E6DbB4BDf620154F45957Df4D0c08dA0e5&quot;).then((data)=&gt; {
  console.log(data) // 84999160000000000000 ~ 현재 잔액

  const value = web3.utils.fromWei (data)
  console.log(value) // 84.99916 ~ wei =&gt; eth 단위 변환
}

// eth_sendTransaction
web3.eth.sendTransaction({
    from:&quot;0x2c3338e54FE9B1Eb3e53DEc6837ebB27EC9388D6&quot;,
    to:&quot;0xb3c52A71e05cA5A806dA789944a9A462f0eA3D38&quot;,                   
    // 보낼 값을 스트링으로 전달해야 합니다  
    value: web3.utils.toWei(&quot;5&quot;, &quot;ether&quot;)
}).then(console.log)

/**
{
  transactionHash: &#39;0x06db6726dc73c17cc5dca952ced7829082e31dc520ded87c3fa2806d630d609d&#39;,
  transactionIndex: 0,
  blockHash: &#39;0x7aea3cb8ea9564ee32b881086a437f0cd1e45e8004c3e2e307b0576cf9e9d611&#39;,
  blockNumber: 1,
  from: &#39;0x2c3338e54fe9b1eb3e53dec6837ebb27ec9388d6&#39;,
  to: &#39;0xb3c52a71e05ca5a806da789944a9a462f0ea3d38&#39;,
  gasUsed: 21000,
  cumulativeGasUsed: 21000,
  contractAddress: null,
  logs: [],
  status: true,
  logsBloom: &#39;0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&#39;
}
**/</code></pre>
<p><br><br></p>
<p><strong>노드 환경에서의 사용법</strong></p>
<pre><code class="language-js">// npm 설치
npm install web3


// index.js
const Web3 = require(&quot;web3&quot;)
const web3= new Web3(&quot;http://127.0.0.1:8545&quot;)

web3.eth.getAccounts().then(console.log)
web3.eth.getBalance(&quot;0x136F87E6DbB4BDf620154F45957Df4D0c08dA0e5&quot;).then(console.log)

// 이하 생략</code></pre>
<br>

<p>web3를 쓰는 것이 RPC통신보다는 훨씬 간편하기는 하네요</p>
<p><br><br></p>
<blockquote>
<p>끝으로 <code>promise.then(onFulfilled, onRejected)</code> 잠깐 복습!</p>
</blockquote>
<p><code>.then</code>은 두개의 콜백함수를 인자로 받습니다</p>
<ul>
<li><code>onFulfilled</code>: 프로미스가 성공적으로 처리되었을 때 실행할 콜백 함수</li>
<li><code>onRejected</code>: 프로미스가 실패했을 때 실행할 콜백 함수</li>
</ul>
<p>다만 두번째 인자는 잘 사용하지 않습니다
에러 처리를 위해서는 별도의 <code>.catch</code> 함수를 사용하는 것이 보편적
<br></p>
<pre><code class="language-js">promise.then((result) =&gt; {
  // 성공적으로 처리되었을 때 실행할 코드
  console.log(&#39;결과값:&#39;, result);
}).catch((error) =&gt; {
  // 실패했을 때 실행할 코드
  console.error(&#39;에러:&#39;, error);
});

// 사용성은 같지만 명시적인 콜백함수를 사용하는 것을 추천
.then(console.log) = .then((result) =&gt; {console.log(result}) </code></pre>
<p><br><br></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[블록체인 용어 정리 (23/05/04)]]></title>
            <link>https://velog.io/@kj_code00/%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC-230504</link>
            <guid>https://velog.io/@kj_code00/%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC-230504</guid>
            <pubDate>Tue, 16 May 2023 00:03:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>분산 원장에 대해</p>
</blockquote>
<p>블록체인 기술에서는 모든 거래 내역과 사용자 정보가 블록체인 네트워크에 <strong>분산</strong>되어 저장됩니다
이 말은 사용자 계정에 관한 정보를 얻으려면 요청과 응답, 그리고 내부적으로 여러 연산 과정을 거쳐야 함을 뜻합니다 
(*metamask가 유명)</p>
<p>먼저 블록체인 네트워크에서 사용자의 어카운트에 관한 정보를 얻으려면 해당 사용자의 공개키를 요청에 실어보내야 합니다
이 요청은 블록체인 네트워크 상의 여러 노드들을 통해 전파되고, 해당 계정의 정보를 담은 트랜잭션이 생성됩니다
이 트랜잭션은 생성이 예정된 블록에 포함되어 검증 과정을 거치게 됩니다
<br></p>
<p>블록체인 기술에서는 블록 생성 주기가 일정하게 설정되어 있는데, 비트코인의 경우 생성 주기가 10분입니다
그래서 비트코인 네크워크에서 가져올 수 있는 최신 정보에는 최대 10분의 텀이 생길 수 있습니다</p>
<p>그리고 블록체인에서는 모든 거래 내역이 블록 단위로 저장되기 때문에 
특정 거래 내역을 조회하려면 해당 거래가 포함된 블록을 찾아야 합니다</p>
<p>이 과정에서 여러 노드들에게 요청을 보내고 응답을 받는 과정을 거치게 되는데,
요청과 응답이 여러 번 오가기 때문에 일반적인 중앙집중식 데이터베이스보다는 다소 느릴 수 있습니다
하지만 중앙집중식 데이터베이스에 비해 데이터를 위조하는 것이 훨씬 어렵기 때문에 높은 보안성을 지녔다고 평가받습니다</p>
<p><br><br></p>
<blockquote>
<p>트랜잭션</p>
</blockquote>
<p>트랜잭션을 보내려면 서명을 위해 사용자의 <strong>개인키</strong>(Private Key)를 필요로 합니다
트랜잭션의 형태는 일반적으로 다음과 같은 객체 형태를 따르는데요
<br></p>
<p><code>영수증 = { 인풋(보내는이), 아웃풋(받는이), 서명 }</code></p>
<ul>
<li>인풋 데이터는 보내려는 비트코인 양과 이전 트랜잭션의 출력을 가리키는 정보를 담고 있습니다</li>
<li>아웃풋 데이터는 받는 이의 비트코인 주소와 송금액을 가리키는 정보를 담고 있습니다 </li>
<li>서명은 개인키를 통해 생성되며, 트랜잭션의 무결성과 보안성을 보장합니다<br>

</li>
</ul>
<p>트랜잭션 생성 과정에서는 이전 트랜잭션의 아웃풋 데이터를 인풋에서 참조하는 것이 아주 중요합니다
이는 블록체인 기술에서 <strong>이중 지불</strong> 문제를 방지하기 위한 로직 중 하나입니다
(만약 이전 트랜잭션의 출력 정보를 다른 트랜잭션에서도 사용하게 되면, 
동일한 비트코인을 두 번 이상 사용하는 것이 가능해지기 때문)</p>
<br>

<blockquote>
<p>트랜잭션 풀 &amp; UTXO</p>
</blockquote>
<p>트랜잭션을 생성한 후에는 이를 블록체인 네트워크에 전송하기 위해 <strong>트랜잭션 풀</strong>에 담아야 합니다 
트랜잭션 풀은 아직 블록에 채굴되지 않은 트랜잭션을 한 데 모아 보관하는 곳<code>([{tx}, {tx}...])</code>으로 
채굴이 끝났을 때 생성된 최신 블록에 이 트랜잭션 내용들이 반영됩니다 </p>
<p>그리고 트랜잭션을 보내기 위해서는 해당 트랜잭션의 인풋에 해당하는 비트코인이 충분한지를 확인해야 합니다 
이는 <strong>UTXO</strong>(Unspent Transaction Output, 미사용 객체)를 활용하여 확인할 수 있습니다</p>
<p>UTXO란 이전에 생성된 트랜잭션 아웃풋 중에서 아직 소비되지 않은 아웃풋을 말합니다
이전 트랜잭션의 아웃풋은 UTXO로 존재하며, 이 UTXO를 인풋으로 사용하여 새로운 트랜잭션을 만들 수 있습니다</p>
<p>트랜잭션을 생성할 때 소비하고자 하는 UTXO의 금액과 받는이의 어카운트를 인풋으로 지정합니다
이때, 소비하고자 하는 UTXO의 금액보다 큰 금액을 입력으로 사용하면 거스름돈이 발생합니다 
거스름돈은 새로운 트랜잭션의 아웃풋으로 생성되며, 이 출력은 새로운 UTXO가 됩니다</p>
<br>

<blockquote>
<p><strong>이중지불 문제에 관한 해법</strong></p>
</blockquote>
<p>블록체인 네트워크에서 이중지불 문제를 막기 위한 가장 간편한 방법은
트랜잭션 상태(트랜잭션 풀)를 다른 노드들과 공유(broadcast)하는 것입니다</p>
<p>각 노드들은 트랜잭션 풀에 있는 트랜잭션들이 블록에 포함될 수 있는지 검증을 거친 후, 
블록에 포함될 수 있는 유효한 트랜잭션들을 블록에 추가합니다
이렇게 블록에 추가된 트랜잭션들은 해당 블록이 생성(체인에 연결)되면 UTXO 리스트에 반영됩니다</p>
<p>이렇게 싱크를 맞춤으로써 블록체인 네트워크의 모든 노드들이 동일한 UTXO 리스트를 가지게 되며, 
각각의 노드들 안에서는 내부 검증 처리과정을 거치게 됩니다
이러한 검증과정을 통해 통해 이중지불 문제를 예방할 수 있습니다</p>
<p>하지만...</p>
<br>

<blockquote>
<p><strong>서로의 채굴 결과가 거의 동시에 이루어졌다면</strong></p>
</blockquote>
<p>n번 블록이 거의 동시에 생성돼서 각 블록이 내부적으로 서로 다른 데이터를 담고 있을 때는
각 노드들은 자신이 받은 블록들 중에서 높이가 가장 긴 블록체인을 따르게 됩니다
즉, 블록 높이가 긴 쪽을 우선해서 짧은 쪽을 덮어씁니다</p>
<p>덮어씌워진 블록은 더 이상 유효하지 않은 블록이 되며, 다음 블록이 생성될 때 해당 블록의 내용은 블록체인 네트워크에서 제외됩니다
따라서 해당 블록에 대한 보상 또한 무효화되며, 채굴자는 해당 블록에 대한 보상을 받지 못하게 됩니다</p>
<br>




<blockquote>
<p>메인넷, 테스트넷</p>
</blockquote>
<p>메인넷은 블록체인의 본래 네트워크 또는 본래 버전을 가리킵니다 
예를 들어, 이더리움의 메인넷은 실제로 이더(ETH)를 사용하는 공식 네트워크입니다</p>
<p>메인넷에서는 실제로 가치를 가지는 토큰이 교환되고, 트랜잭션이 발생하며, 채굴이 이루어집니다
반면, 테스트넷(Testnet)은 메인넷과는 별개의 테스트용 네트워크이며, 
가치를 갖지 않는 테스트용 토큰을 사용하여 개발자가 블록체인 애플리케이션을 실험하고 테스트할 수 있도록 합니다</p>
<br>

<blockquote>
<p>코인? 토큰?</p>
</blockquote>
<p>이 명칭들은 <strong>메인넷</strong>의 존재여부에 따라 나뉩니다
일반적으로 코인은 독립적으로 자체적인 블록체인을 가진 암호화폐를 말합니다</p>
<p>예를 들어 비트코인은 독자적인 블록체인을 가지고 있으므로 코인입니다 </p>
<p>반면에 토큰은 이미 존재하는 블록체인 위에서 동작하는 암호화폐입니다 
대부분의 토큰은 이더리움과 같은 블록체인 네트워크를 기반으로 작동하며, <strong>스마트 컨트랙트</strong>를 통해 발행되고 관리됩니다</p>
<p>+_앞으로 배울 솔리디티의 런타임은 이더리움 네트워크 내부에 있다는 점</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[블록체인 -3 트랜잭션 (23/05/02)]]></title>
            <link>https://velog.io/@kj_code00/%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-3-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-230502</link>
            <guid>https://velog.io/@kj_code00/%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-3-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-230502</guid>
            <pubDate>Mon, 15 May 2023 23:59:59 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개인키와-공개키">1. 개인키와 공개키</h2>
<p><a href="https://brunch.co.kr/@nujabes403/13">https://brunch.co.kr/@nujabes403/13</a></p>
<blockquote>
<p>서명 : 본인임을 증명하는 행위</p>
</blockquote>
<p>온라인상에서 전자서명은 본인만이 알고있는 패스워드를 사용하는 것이 일반적입니다</p>
<p>하지만 블록체인 시스템은 중앙 데이터베이스에  블록의 흐름(트랜잭션 데이터)만 저장할 뿐,
사용자에 관한 정보를 저장하지 않습니다
그러면 사용자에 대한 증명은 어떤 방식으로 처리하고 있을까요?
<br><br></p>
<blockquote>
<p>공개키(자물쇠) &amp; 개인키(열쇠)</p>
</blockquote>
<br>

<p>블록체인에서는 사용자의 증명을 위해 <strong>공개키</strong>와 <strong>개인키</strong> 암호화 방식을 사용합니다</p>
<p>먼저 사용자는 공개키와 개인키 한 쌍을 생성합니다
공개키는 누구나 볼 수 있지만 개인키는 해당 사용자만이 알고 있습니다
그래서 이를 자물쇠(공개키)와 열쇠(개인키)의 관계로 비유하기도 합니다</p>
<p>사용자는 자신만이 알고 있는 개인키를 써서 트랜잭션에 대한 디지털 서명을 생성합니다
그리고 이 서명은 개인키와 페어를 이루는 공개키를 통해 검증할 수 있습니다
검증 과정에서는 공개키를 사용하여 서명이 올바른지 확인하고, 이를 통해 해당 트랜잭션의 유효성도 검증합니다
이러한 방식으로 블록체인의 검증은 사용자에 대한 증명과 트랜잭션의 유효성 검증을 한꺼번에 처리하면서도 높은 신뢰도를 보장합니다
<br></p>
<p>블록체인의 검증 방식은 여러 장점을 지니고 있지만 특히 중요한 점은
분산된 네트워크 상에서 자체적으로 사용자의 증명을 처리할 수 있기 대문에 중앙기관에 의존하지 않아도 된다는 점
그리고 단점은 체인의 유효성을 검증하는 과정에서 적지 않은 딜레이가 발생한다는 점입니다</p>
<br>


<p>공개키 생성을 위한 라이브러리</p>
<pre><code class="language-sh">npm install elliptic</code></pre>
<br>

<p>영수증 인터페이스 </p>
<pre><code class="language-ts">export class Sender {
      //
    publicKey?: string
    account!: string
}

export class Receipt {
    sender!: Sender
    received!: string
    amount!: string
    signature?: unknown
}</code></pre>
<p><br><br></p>
<h2 id="2-트랜잭션">2. 트랜잭션</h2>
<blockquote>
<p>트랜잭션, 트랜잭션 풀, 코인베이스</p>
</blockquote>
<ul>
<li><p>트랜잭션 : </p>
</li>
<li><p>트랜잭션 풀 : 아직 블록체인에 포함되지 않은 트랜잭션 객체(거래내역)들이 저장되는 공간입니다
여기에 담긴 트랜잭션들은 다음 코인이 생성되는 시점에서 반영됩니다</p>
</li>
<li><p>코인베이스 : 블록 생성에 대한 보상금을 지급하는 트랜잭션(새 블록의 첫번째 거래)으로 
블록체인 상에 새로운 블록이 생성될 때마다 생성됩니다</p>
</li>
</ul>
<br>

<p>블록 마이닝에 걸리는 시간은 대략적으로 10분, 
블록 마이닝 도중에 일어나는 트랜잭션은 다음번 블록 생성시에 반영됩니다</p>
<p>사용자가 영수증을 바탕으로 블록체인 네트워크에 요청 -&gt; 트랜잭션 풀에 담김 -&gt; 
다음 마이닝이 성공되면 트랜잭션 처리 완료
(비트코인이 송금이 느리다고 말하는 이유이기도 합니다)</p>
<ul>
<li>트랜잭션의 구조<pre><code class="language-ts">txInput {
txOutId?: string
txOutIndex!: number
signature? : signatureInput
}

</code></pre>
</li>
</ul>
<p>txOut {
    account! : string // 받는 사람
    amount!: number
}</p>
<p>class TransactionRow {
  // 인풋
  txIns? : txInput[],
  // 아웃풋
  txOuts!: txOut[],
  // Transaction에 대한 고유한 식별자
  hash?: string 
}</p>
<pre><code>
- `TxOut` : 트랜잭션의 아웃풋(출력 결과)입니다 
아웃풋은 트랜잭션의 송신자가 수신자에게 전송할 금액과 수신자의 공개키 정보를 담고 있습니다
하나의 트랜잭션에서 여러 개의 아웃풋이 생성될 수 있기에 배열 형태로 표현합니다

- `TxIn` : 이전 거래의 TxOut을 참조하는데, 이전 거래에서 송금받은 TxOut의 인덱스(txOutIndex)와 해시(txOutId)를 저장합니다
타입을 배열로 받는 이유는 하나의 트랜잭션에서 여러 개의 입력을 참조할 수 있기 때문

인풋과 아웃풋이 모여서 하나의 `TransactionRow`를 생성합니다


- 트랜잭션 클래스
```ts
class Transaction {
    private readonly REWARD = 50
    constructor(private readonly crypto: CryptoModule) { }

    create(receipt: Receipt) {
        const totalAmount = 50
        // txin =&gt; 영수증에 있는 sender의 잔액을 확인해야한다.
        const txin1 = this.createTxIn(1, &#39;&#39;, receipt.signature)
        const txout_sender = this.createTxOut(receipt.sender.account, totalAmount - receipt.amount)
        const txout_received = this.createTxOut(receipt.received, receipt.amount)
        return this.createRow([txin1], [txout_sender, txout_received])
    }

    createTxOut(account: string, amount: number): TxOut {
        if (account.length !== 40) throw new Error(&quot;Account 형식이 올바르지 않습니다.&quot;)
        const txout = new TxOut()
        txout.account = account
        txout.amount = amount
        return txout
    }

    serializeTxOut(txOut: TxOut): Hash {
        const { account, amount } = txOut
        const text = [account, amount].join(&#39;&#39;)
        return this.crypto.SHA256(text)
    }

      // 코인베이스 생성시에는 index 인자만  필요합니다
    createTxIn(txOutIndex: number, txOutId?: string, signature?: SignatureInput): TxIn {
        const txIn = new TxIn()
        txIn.txOutIndex = txOutIndex
        txIn.txOutId = txOutId
        txIn.signature = signature
        return txIn
    }

    serializeTxIn(txIn: TxIn): Hash {
        const { txOutIndex } = txIn
        const text = [txOutIndex].join(&#39;&#39;)
        return this.crypto.SHA256(text)
    }

      // 트랜잭션 생성
    createRow(txIns: TxIn[], txOuts: TxOut[]) {
        const transactionRow = new TransactionRow()
        transactionRow.txIns = txIns
        transactionRow.txOuts = txOuts
        transactionRow.hash = this.serializeRow(transactionRow)
        return transactionRow
    }

      // txOut[] or txIn[]을 받는 리듀서 함수
    serializeTx&lt;T&gt;(data: T[], callback: (item: T) =&gt; string) {
        return data.reduce((acc: string, item: T) =&gt; acc + callback(item), &#39;&#39;)
    }

    // TransactionRow 객체를 해시화하는 메서드 (배열(txIn[], txOut[])에서 추출한 데이터로 해시 생성) 
    // this바인딩 처리를 위해 애로우 함수로 감쌉니다
    serializeRow(row: TransactionRow) {
        const { txIns, txOuts } = row
        const txoutText = this.serializeTx&lt;TxOut&gt;(txOuts, (item) =&gt; this.serializeTxOut(item))
        const txinText = this.serializeTx&lt;TxIn&gt;(txIns, (item) =&gt; this.serializeTxIn(item))

        return this.crypto.SHA256(txoutText + txinText)
    }

      // 채굴자에 대한 보상 ~ 보상을 받을 계정 + 직전 블록의 높이 정보가 필요합니다
    createCoinbase(account: string, latestBlockHeight: number) {
        const txin = this.createTxIn(latestBlockHeight + 1)
        const txout = this.createTxOut(account, this.REWARD)
        return this.createRow([txin], [txout])
    }
}</code></pre><p><br><br></p>
<ul>
<li>트랜잭션 생성<pre><code class="language-ts">// 코인베이스
// 영수증 --&gt; transaction --&gt; block 생성
const privateKey = &#39;6fb6a1482159a4b05a96636d0a390f7be0f29552c1a2edef79e83998221bc261&#39;
const publicKey = digitalSignature.createPublicKey(privateKey)
const account = digitalSignature.createAccount(publicKey)
</code></pre>
</li>
</ul>
<p>// 코인베이스(새 트랜잭션) 생성 ~ 계정 + 직전 블록의 높이
const coinbase2 = transaction.createCoinbase(account, GENESIS.height)</p>
<p>// ↓ 생성된 블록 예제
{
  nonce: 1,
  difficulty: 0,
  version: &#39;1.0.0&#39;,
  height: 2,
  timestamp: 1683016061012,
  previousHash: &#39;84ffab55c48e36cc480e2fd4c4bb0dc5ee1bb2d41a4f2a78a1533a8bb7df8370&#39;,
  merkleRoot: &#39;739FC2180DC050B0E4AE51CA155F4F648C78E49728D72732A60224347E2BA829&#39;,
  data: [
    TransactionRow {
      txIns: [Array],
      txOuts: [Array],
      hash: &#39;05b7576fae0b0afbbfed02f82a32e661d7aaeeb39180c54d678118bff6c6b393&#39;
    }
  ],
  hash: &#39;0b998544753a8d7b7792883d5ce72a568b55c2cd51aca8d82fa11554f23f8479&#39;
}</p>
<pre><code>

&lt;br&gt;

- 세번째 블록에 트랜잭션 반영시키기
```ts
</code></pre><p><br><br></p>
<h3 id="2-2-미사용-트랜잭션-unspenttxouts">2-2. 미사용 트랜잭션 (UnspentTxOuts)</h3>
<br>

<p>미사용 객체는 이더리움에서 사용하는 방법론은 아닙니다
비트코인에서만 사용되는 방식인데, <strong>UTXO</strong>라고 줄여서 부르기도 합니다</p>
<p>코인의 총량은 항상 총 발행량과 같아야 합니다
즉, 트랜잭션이 발생할 때의 거래량과 <strong>미사용 객체</strong>(거래에 사용되지 않은 코인)의 합은 코인의 총량과 같아야 합니다</p>
<br>

<p>예를 들어 코인의 총 발행량이 100이고, 현재 미사용객체에 총 50의 코인이 남아있다고 가정해보겠습니다
이때 새로운 트랜잭션이 발생하여 A 계정에 20 코인을 보내고, B 계정에 30 코인을 보낸다고 가정해보겠습니다
그러면 트랜잭션 객체는 다음과 같겠죠</p>
<pre><code class="language-ts">txoutIndex: 0
account: &#39;A&#39;
amount: 20

txoutIndex: 1
account: &#39;B&#39;
amount: 30</code></pre>
<p>이후에는 미사용객체에 남아있는 코인은 50에서 20과 30을 합산한 50이 되며
이를 통해 전체 코인의 총량인 100을 유지할 수 있습니다</p>
<br>

<pre><code class="language-ts">// 미사용 객체 인터페이스
// {[{}, {}, {} ... ]}

class UnspentTxOut {
    txOutId!: string // TransactionRow의 hash
    txOutIndex!: number // txouts의 index
    account!: string // Transaction.txOuts
    amount!: number // Transaction.txOuts
}

export type UnspentTxOutPool = UnspentTxOut[]


// 미사용 트랜잭션
class Unspent {
    private readonly UnspentTxOuts: UnspentTxOutPool = []
    constructor() { }

    createUTXO(transaction: TransactionRow) {
        const { hash, txOuts } = transaction
        if (!hash) throw new Error(&quot;hash값이 존재하지 않습니다&quot;)

        // TxOuts로 미사용 트랜잭션 객체를 만드는데 txOuts 갯수가 가변적

        const newUnspentTxOut: UnspentTxOut[] = txOuts.map((txout: TxOut, index: number) =&gt; {
            const unspentTxOut = new UnspentTxOut()
            unspentTxOut.txOutId = hash
            unspentTxOut.txOutIndex = index
            unspentTxOut.account = txout.account
            unspentTxOut.amount = txout.amount

            return unspentTxOut
        })
        return newUnspentTxOut
    }
}

export default Unspent;


//
[
  UnspentTxOut {
    txOutId: &#39;05b7576fae0b0afbbfed02f82a32e661d7aaeeb39180c54d678118bff6c6b393&#39;,
    txOutIndex: 0,
    account: &#39;a7f413bcd5bf5d7272eb3bf3570c79caac848aac&#39;,
    amount: 50
  }
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[블록체인 -2 2번째 블록 생성 (23/04/27)]]></title>
            <link>https://velog.io/@kj_code00/%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-2-2%EB%B2%88%EC%A7%B8-%EB%B8%94%EB%A1%9D-%EC%83%9D%EC%84%B1-230427</link>
            <guid>https://velog.io/@kj_code00/%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-2-2%EB%B2%88%EC%A7%B8-%EB%B8%94%EB%A1%9D-%EC%83%9D%EC%84%B1-230427</guid>
            <pubDate>Tue, 09 May 2023 07:19:29 GMT</pubDate>
            <description><![CDATA[<h2 id="2번째-블록-생성하기">2번째 블록 생성하기</h2>
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ950a8r6A2mLtU2FNwd4cOPgtBy7GJje6LCFsfmrX8lrdg-ON6a0ic7Ofl-qflxeXNo_c&usqp=CAU">

<br>

<p>제네시스 블록으로부터 다음 블록을 생성하려면 생성될 블록은 이전 블록에 대한 정보를 참조할 수 있어야 합니다
그리고 이 생성 준비과정에 도달하려면 블록 해시를 제외한 모든 데이터가 완성된 상태여야 합니다</p>
<br>

<pre><code class="language-ts">// 두번째 블록 생성을 위한 생성자
class Block {
    constructor() {}

    // 직전 블록 데이터를 의존합니다
    createBlockInfo(previousBlock: IBlock): BlockInfo {
        // const blockInfo: BlockInfo = {
        //     version: VERSION,
        //     height: previousBlock.height + 1,
        //     timestamp: new Date().getTime(),
        //     previousHash: previousBlock.hash,
        //     nonce: 0,
        //     difficulty: 0
        // }
        // return blockInfo;

        // ↓ 속성값을 대입하는 방식이 요즘 트렌드라는데...
        const blockInfo = new BlockInfo()
        blockInfo.version = VERSION
        blockInfo.height = previousBlock.height + 1
        blockInfo.timestamp = new Date().getTime()
        return blockInfo
    }
}

export default Block</code></pre>
<br>

<h3 id="1-블록-검증-함수">1. 블록 검증 함수</h3>
<br>

<p>블록 생성은 이전 블록 데이터에 대한 신뢰를 전제로 합니다
그래서 새 블록을 생성하기 전에 이전 블록의 해시값이 올바른지에 대한 별도의 검증 과정이 필요합니다</p>
<p>여러 방법론이 있을 수 있겠지만 여기서는 직전 블록의 평문 데이터를 가져와서 또 한 번 해시화를 진행한 뒤 
직전 블록이 가진 해시 속성과의 일치여부를 검사하는 방법을 택했습니다</p>
<br>

<p>직전 블록의 신뢰성을 검증하는 함수를 생성합니다</p>
<pre><code class="language-ts"> // crypto.createBlockHash() === block.hash ?

    isValidPreviousBlock(previousBlock: IBlock): void {
        this.crypto.isValidHash(previousBlock.hash)

        const validHash = this.crypto.createBlockHash(previousBlock)
        if (validHash !== previousBlock.hash) throw new Error(`이전 블록의 해시값이 올바르지 않습니다 ${validHash} !== ${previousBlock.hash}`)
    }</code></pre>
<br>


<p><strong>테스트 코드</strong></p>
<pre><code class="language-ts">describe(&#39;Block&#39;, () =&gt; {
    let block: Block
    let crypto: CryptoModule

    beforeEach(() =&gt; {
        crypto = new CryptoModule()
        block = new Block(crypto) // 인스턴스가 crypto의 메서드를 사용할 수 있도록
    })

    describe(&#39;isValidPreviousBlock&#39;, () =&gt; {
        let previousBlock: IBlock
        beforeEach(() =&gt; { previousBlock = { ...GENESIS } })

        it((&#39;매개변수로 넘겨받은 블럭 해시값이 올바른가&#39;), () =&gt; {
            expect(() =&gt; block.isValidPreviousBlock(previousBlock)).not.toThrowError() // not은 반대 입장을 검사
        })
        it((&#39;매개변수로 넘겨받은 블럭 해시값이 올바르지 않을 경우 에러가 발생하는가&#39;), () =&gt; {
            previousBlock.hash = &quot;84ffab55c48e36cc480e2fd4c4bb0dc5ee1bb2d41a4f2a78a1533a8bb7df8371&quot;
            expect(() =&gt; block.isValidPreviousBlock(previousBlock)).toThrowError()
        })
        it((&#39;블럭 해시값이 변조된 적이 있는가&#39;), () =&gt; {
            block.isValidPreviousBlock(previousBlock)
        })
        it((&#39;블럭 해시값이 올바르지 않을 때 에러가 발생하는가&#39;), () =&gt; {

        })
    })

    describe((&#39;createBlockInfo&#39;), () =&gt; {
        const previousBlock = GENESIS
        it((&#39;createBlockHash 메서드가 존재하는가&#39;), () =&gt; {
            expect(typeof block.createBlockInfo).toBe(&quot;function&quot;)
        })

        it((&#39;createBlock에서 BlockInfo가 잘 생성되는가&#39;), () =&gt; {
            const newBlock = block.createBlockInfo(previousBlock)
            expect(typeof newBlock).toBe(&quot;object&quot;) // failed ~ undefined
        })

        it((&#39;createBlock에서 BlockInfo의 내용이 올바른가&#39;), () =&gt; {
            const newBlock = block.createBlockInfo(previousBlock)

            expect(newBlock.previousHash).toBe(previousBlock.hash)
            expect(newBlock.height).toBe(previousBlock.height + 1)
        })
    })
})</code></pre>
<p><br><br></p>
<h3 id="2-블록-생성-함수">2. 블록 생성 함수</h3>
<br>


<p>2번째 블록을 생성할 함수(<code>createBlock()</code>)에는 <strong>작업증명</strong>(POW)에 관한 로직이 담겨야 합니다
그리고 난이도 조절을 위해서는 10번째로 생성될 블록의 정보도 필요합니다</p>
<br>

<p><strong>합의 알고리즘</strong></p>
<blockquote>
<p><strong>POW</strong> (Proof Of Work): 작업증명
<strong>POS</strong> (Proof Of Stake): 지분증명
<strong>POA</strong> (Proof Of Authority): 권한증명</p>
</blockquote>
<br>

<p>그러면 작업증명을 위한 코드 설계를 시작...</p>
<ul>
<li><p>전략 패턴을 사용해서 증명 방식을 쉽게 갈아끼울 수 있는 형태로 클래스를 설계합니다
같은 인터페이스를 사용하되 내부 로직(POS or POW)만 달라지는 형태로 구현해야 합니다</p>
</li>
<li><p>클래스 설계도 ERD와 마찬가지로 스키마를 그리는 과정이 필요합니다 
이를 <strong>UML</strong>(Unified Modeling Language)이라고 합니다</p>
</li>
</ul>
<br>

<p><strong>UML 예제</strong>
<img src="https://velog.velcdn.com/images/kj_code00/post/228e40c1-118c-407b-926e-49ea62b327e6/image.png" alt=""></p>
<br>

<p><strong>테스트 코드 예제</strong></p>
<pre><code class="language-ts">describe(&#39;WorkProof&#39;, () =&gt; {
    let workProof: WorkProof
    let proof: Proof

    describe(&#39;POW&#39;, () =&gt; {
        beforeEach(() =&gt; {
            proof = new ProofOfWork()
            workProof = new WorkProof(proof)
        })
        it((&#39;POW 실행&#39;), () =&gt; {
            workProof.run()
        })
    })

    describe(&#39;POS&#39;, () =&gt; {
        beforeEach(() =&gt; {
            proof = new ProofOfStake()
            workProof = new WorkProof(proof)
        })
        it((&#39;POS 실행&#39;), () =&gt; {
            workProof.run()
        })
    })
})</code></pre>
<p><br><br></p>
<p><strong>POW 방식으로 블록 100개 생성하기</strong></p>
<pre><code class="language-ts">// 제네시스 블록을 체인(블록 배열)에 담은 상태에서 시작합니다
const BlockList:IBlock[] = [ GENESIS ]


for (let i = 1; i &lt; 100; i++) {
    const adjustmentBlock = (i &gt;= 19) ? BlockList[Math.floor(i / 10)*10-10] : GENESIS
    // 3번째 인자를 통해 난이도 조절. (20-29번 블록의 생성 난이도는 10번째 블록을 기준으로 합니다)
    const bitcoin = block.createBlock(BlockList[i-1], &quot;data&quot;, adjustmentBlock)
    BlockList.push(bitcoin)
}

console.log(BlockList)


/**
[
  {
    version: &#39;1.0.0&#39;,
    height: 0,
    timestamp: 1231006506,
    previousHash: &#39;0000000000000000000000000000000000000000000000000000000000000000&#39;,
    merkleRoot: &#39;DC24B19FB7508611ACD8AD17F401753670CFD8DD1BEBEF9C875125E98D82E3D8&#39;,
    nonce: 0,
    difficulty: 0,
    hash: &#39;84ffab55c48e36cc480e2fd4c4bb0dc5ee1bb2d41a4f2a78a1533a8bb7df8370&#39;,
    data: &#39;2009년 1월 3일 더 타임스, 은행들의 두번째 구제금융을 앞두고 있는 U.K 재무장관&#39;
  },
  {
    nonce: 1,
    difficulty: 0,
    version: &#39;1.0.0&#39;,
    height: 1,
    timestamp: 1682640185673,
    previousHash: &#39;84ffab55c48e36cc480e2fd4c4bb0dc5ee1bb2d41a4f2a78a1533a8bb7df8370&#39;,
    merkleRoot: &#39;3A6EB0790F39AC87C94F3856B2DD2C5D110E6811602261A9A923D3BB23ADC8B7&#39;,
    data: &#39;data&#39;,
    hash: &#39;a07aef620d2ba153b5173a42371db8fbd2da5873eecc82c0ef89890461c6d0e1&#39;
  },
  ...
  {
    nonce: 3,
    difficulty: 2,
    version: &#39;1.0.0&#39;,
    height: 100,
    timestamp: 1682640185677,
    previousHash: &#39;16b455db348f232bba09ca26ac21d798ff323390ef1bf468abaef573843f9f2c&#39;,
    merkleRoot: &#39;3A6EB0790F39AC87C94F3856B2DD2C5D110E6811602261A9A923D3BB23ADC8B7&#39;,
    data: &#39;data&#39;,
    hash: &#39;335fb98039f66c68f16e9a2a761bb2d9dd2a08bba90b7d7c62bfe2d54b58c21e&#39;
  }
]
*/  </code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[블록체인 -1 제네시스 블록 (23/04/25)]]></title>
            <link>https://velog.io/@kj_code00/%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-1-%EC%A0%9C%EB%84%A4%EC%8B%9C%EC%8A%A4-%EB%B8%94%EB%A1%9D-230425</link>
            <guid>https://velog.io/@kj_code00/%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-1-%EC%A0%9C%EB%84%A4%EC%8B%9C%EC%8A%A4-%EB%B8%94%EB%A1%9D-230425</guid>
            <pubDate>Tue, 09 May 2023 06:57:54 GMT</pubDate>
            <description><![CDATA[<h2 id="1-제네시스-블록">1. 제네시스 블록</h2>
<br>

<img src="https://mobile.busan.com/nas/wcms/wcms_data/photos/2023/01/04/2023010413094856906_l.jpeg">


<br>

<p><strong>제네시스 블록</strong>은 비트코인 네트워크에서 최초로 생성된 블록입니다</p>
<ul>
<li>제네시스: 창세기, 기원 등의 의미</li>
<li>블록: 코인의 생성, 전송이 기록된 장부</li>
</ul>
<br>

<p>최초의 블록인 제네시스 블록은 한국시간으로 2009년 1월 4일에 처음 생성되었습니다
제네시스 블록의 바디 데이터에는 나카모토 사토시가 작성한 메시지가 포함되어 있습니다</p>
<blockquote>
<p>&quot;The Times 03/Jan/2009 Chancellor on brink of second bailout for banks&quot;</p>
</blockquote>
<p>이 메시지는 영국의 일간지인 &#39;더 타임즈&#39;의 2009년 1월 3일자 헤드라인에서 가져온 것입니다
해당 시기에 발생한 세계 금융 위기와 중앙 은행의 역할을 비판하는 내용을 담고 있으며, 
나카모토 사토시가 지닌 비트코인의 철학을 대변하는 메시지로 여겨지고 있습니다
<br></p>
<p><strong>제네시스 블록의 구조 예제</strong></p>
<pre><code class="language-js">  {
    version: &#39;1.0.0&#39;,
    height: 0,
    timestamp: 1231006506,
    previousHash: &#39;0000000000000000000000000000000000000000000000000000000000000000&#39;,
    merkleRoot: &#39;DC24B19FB7508611ACD8AD17F401753670CFD8DD1BEBEF9C875125E98D82E3D8&#39;,
    nonce: 0,
    difficulty: 0,
    hash: &#39;84ffab55c48e36cc480e2fd4c4bb0dc5ee1bb2d41a4f2a78a1533a8bb7df8370&#39;,
    data: &#39;2009년 1월 3일 더 타임스, 은행들의 두번째 구제금융을 앞두고 있는 U.K 재무장관&#39;
  }</code></pre>
<ul>
<li>height: &#39;높이&#39;로 표현하지만 순서를 나타내며 새로운 블록이 생성될 때마다 1씩 증가합니다
제네시스 블록은 최초의 블록이므로 이 값은 0입니다 (2023년 5월 기준 최신 블록의 높이는 약 78만)</li>
<li>previousHash: 이전 블록의 해시 값입니다 
제네시스 블록은 이전 블록이 없기에 모든 비트가 0인 64자리 16진수 값으로 설정했습니다</li>
</ul>
<br>
블록의 세부 구조(머클루트, 논스, 난이도 등)에 대해서는 차차 다루기로 하겠습니다

<br>




<p><br><br></p>
<h2 id="2-해시화단방향-암호화">2. 해시화(단방향 암호화)</h2>
<br>


<blockquote>
<p>단방향 암호화, 해시, SHA-256</p>
</blockquote>
<br>

<p>블록체인의 원리와 구조에 대해 파악하려면 먼저 블록체인 네트워크에서 사용중인
암호화 방식에 대해 파악할 필요가 있습니다</p>
<p>블록체인에서 사용되는 암호화 방식 중 가장 중요한 것은 <strong>해시화</strong>(Hashing)로,
해시화는 <strong>단방향 암호화</strong>의 일종으로 임의의 데이터를 고정된 길이의 문자열로 변환하는 과정을 말합니다</p>
<p>*단방향 암호화란 입력값(평문)을 함수에 넣어 암호화된 코드를 얻을 수는 있지만 
암호문을 역추적해서 평문을 얻는 것이 극히 어려운 암호화 방식입니다
<br></p>
<p>또한 블록체인에서 해시 알고리즘으로는 <strong>SHA-256</strong>이 주로 사용되는데,
SHA256 함수는 해시값의 길이를 256비트(32바이트)로 고정시킵니다
이 출력값을 16진수(64자리의 문자열)나 2진수로 변환하여 표현할 수 있습니다</p>
<p>이러한 단방향 해시 함수는 데이터 무결성을 검증하는 용도로 사용됩니다
(원본 데이터와 암호화된 결과를 비교하여 같은지 다른지를 확인하는 용도)</p>
<br>

<p><strong>짧막 복습</strong></p>
<ul>
<li><p><strong>bit</strong>: binary digit에서 파생된 단어로 0 또는 1의 값을 가지는 이진수 데이터입니다 
컴퓨터에서 비트는 정보의 최소 단위를 뜻합니다</p>
</li>
<li><p><strong>byte</strong>: 컴퓨터가 조작하는 정보의 최소 <strong>처리</strong> 단위입니다. 1바이트는 8개의 비트로 이루어져 있습니다</p>
</li>
<li><p><strong>nibble</strong>: 1byte의 절반. 즉 4bit입니다</p>
</li>
<li><p><strong>Hex(16진수)</strong>: 0123456789ABCDEF, 이렇게 총 16개의 기호로 표현되는데 
0x(or 0X) 접두사를 사용하여 나타내기도 합니다
예를 들어, 0x1A는 10진수로 나타내면 26을 의미합니다 (10은 10진수 16, A는 10)</p>
</li>
</ul>
<p><br><br></p>
<p><code>SHA256</code> 함수를 사용하는 클래스 예제</p>
<pre><code class="language-ts">class CryptoModule {
    // SHA-256

    createBlockHash(data: BlockData) {
        // object &gt; sort &gt; string &gt; SHA256 호출
        // 해시화를 진행할 때는 객체 속성의 순서도 중요합니다
        const { version, height, timestamp, merkleRoot, previousHash, difficulty, nonce} = data
        const value = `${version}${height}${timestamp}${merkleRoot}${previousHash}${difficulty}${nonce}`
        return this.SHA256(value)
    }

    SHA256 (data: string): Hash {
        const hash = cryptojs.SHA256(data).toString()
        return hash
    }

    // hex(16진수) -&gt; binary(2진수)
    // 1) `parseInt(hexByte, 16)`를 사용하여 16진수(hexadecimal) 문자열(hexByte)을 10진수(decimal)로 변환합니다
    // 2) `decimal.toString(2)`를 사용해서 10진수(decimal)을 2진수(binary) 문자열로 변환합니다
    // 그리고 변환된 2진수 문자열의 자릿수는 8자리로 고정되도록 하기 위해 `padStart(8, &quot;0&quot;)`를 사용합니다
    // 3) 위 두 줄의 코드를 합쳐서 16진수(decimal)를 2진수(binary)로 변환하고, 8자리로 채워진 2진수 문자열(binaryByte)을 얻습니다
    hashToBinary(hash: Hash): string {
        let binary = &#39;&#39;
        for (let i = 0; i &lt; hash.length; i += 2) {
            const hexByte = hash.substr(i, 2)
            const decimal = parseInt(hexByte, 16)
            const binaryByte = decimal.toString(2).padStart(8, &quot;0&quot;) // padStart ~ 8자리수 고정, 빈자리는 0
            binary += binaryByte
        }
        return binary
    }

    merkleRoot(data: TransactionData) {
        let merlkeData = []
        if (data instanceof TransactionRow) {
            // data =&gt; transactionRow

        } else {
            // data =&gt; string
            // npm install merkle
            return merkle(&#39;sha256&#39;).sync([data]).root()
        }
    }

    isValidHash(hash: Hash): void {
        const hexRegExp = /^[0-9a-fA-F]{64}$/
        if (!hexRegExp.test(hash)) throw new Error (`해시값이 올바르지 않습니다 ${hash}`)
    }
}


export default CryptoModule</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jest 기초 (23/04/24)]]></title>
            <link>https://velog.io/@kj_code00/Jest-%EA%B8%B0%EC%B4%88-230424</link>
            <guid>https://velog.io/@kj_code00/Jest-%EA%B8%B0%EC%B4%88-230424</guid>
            <pubDate>Tue, 25 Apr 2023 00:15:34 GMT</pubDate>
            <description><![CDATA[<h2 id="tdd--jest">TDD &amp; Jest</h2>
<img src="https://media.geeksforgeeks.org/wp-content/uploads/20200117152116/jest.png">

<br>

<p><strong>TDD</strong>(Test Driven Development)는 테스트 주도 개발이라는 소프트웨어 개발 방법론 중 하나입니다
일반적으로 TDD는 테스트 코드를 먼저 작성하고, 이를 통과하는 코드를 작성하는 과정을 거칩니다 
이를 통해 코드가 어떻게 동작해야 하는지에 대한 명확한 이해를 가지게 되며, 이를 바탕으로 개발을 진행할 수 있습니 다</p>
<p>그리고 <strong>Jest</strong>는 메타(구 페이스북)에서 만든 TDD를 위한 프레임워크입니다
Jest는 자동화된 테스트를 지원하여, 개발자가 코드 변경 사항을 만들 때마다 테스트를 수행할 수 있도록 도와줍니다 이를 통해 개발 과정에서 코드 변경 사항이 예상대로 작동하는지에 대한 신뢰를 가질 수 있게 해줍니다</p>
<p><br><br></p>
<p>실행 명령어</p>
<pre><code class="language-js">npx jest</code></pre>
<p>*jest는 기본적으로 <code>[파일네임].test.[확장자]</code>를 찾아서 실행시키도록 설정되어 있습니다</p>
<br>


<p>jest는 원래 브라우저의 자바스크립트를 테스트할 목적으로 만들어졌습니다
그리고 타입스크립트는 컴파일 과정을 거쳐야만 자바스크립트 파일로 바뀝니다
jest가 타입스크립트를 기본적으로 지원하지 않는 관계로, 둘을 함께 사용하려면 몇가지 설정이 필요합니다</p>
<br>



<h3 id="1-타입스크립트-설정">1. 타입스크립트 설정</h3>
<pre><code class="language-sh">npm init -y
npm install -D typescript tsc-alias ts-node tsconfig-paths nodemon</code></pre>
<br>

<p>tsconfig.json</p>
<pre><code class="language-js">{
    &quot;compilerOptions&quot;: {
        &quot;module&quot;: &quot;CommonJS&quot;,
        &quot;outDir&quot;: &quot;./dist&quot;,
        &quot;target&quot;: &quot;ES6&quot;,
        &quot;lib&quot;: [&quot;ES6&quot;, &quot;dom&quot;],
        &quot;esModuleInterop&quot;: true,
        &quot;strict&quot;: true,
        &quot;baseUrl&quot;: &quot;./src&quot;,
        &quot;paths&quot;: {},
    },
    &quot;include&quot;: [&quot;src/**/*&quot;],
    &quot;exclude&quot;: [&quot;**/*.test.js&quot;]
}</code></pre>
<p>nodemon.json</p>
<pre><code class="language-js">{
    &quot;watch&quot;: [&quot;src/**/*&quot;],
    &quot;ext&quot; : &quot;ts&quot;,
    &quot;exec&quot;: &quot;ts-node -r tsconfig-paths/register ./src/index.ts&quot;
}</code></pre>
<p>package.json</p>
<pre><code class="language-js">  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;nodemon&quot;,
    &quot;build&quot; : &quot;tsc &amp;&amp; tsc-alias&quot;
    &quot;test&quot;: &quot;jest&quot;,
  },</code></pre>
<p><br><br></p>
<h3 id="2-jest-설정">2. jest 설정</h3>
<pre><code class="language-sh">npm install -D jest @types/jest ts-jest</code></pre>
<ul>
<li><code>@types/jest</code>는 jest에서 사용하는 모든 타입을 제공하는 라이브러리입니다
에디터가 jest 명령어를 읽을 수 있도록 해줍니다
(<code>tsconfig.json</code>에서 테스트용 디렉토리를 include안에 포함시켜야 합니다)</li>
</ul>
<ul>
<li><code>ts-jest</code>는  타입스크립트로 jest를 실행할 수 있도록 해줍니다</li>
</ul>
<br>


<p>방법1) jest 실행</p>
<pre><code class="language-js">npx jest --preset ts-jest</code></pre>
<br>

<p>방법2) <code>package.json</code> 파일을 수정합니다</p>
<pre><code class="language-js">  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;nodemon&quot;,
    &quot;build&quot;: &quot;tsc &amp;&amp; tsc-alias&quot;,
    &quot;test&quot;: &quot;npx jest&quot;
  },
  &quot;jest&quot;: {
    &quot;preset&quot; : &quot;ts-jest&quot;,
    &quot;testEnvironment&quot;: &quot;node&quot;
  },</code></pre>
<p>( --testEnvironment node는 테스트 환경을 노드로)</p>
<p>jest 실행</p>
<pre><code class="language-js">npm run test
// or
npx jest</code></pre>
<br>


<p>방법3) <code>jest.config.json</code> 파일을 생성해서 따로 관리할 수도 있습니다</p>
<pre><code class="language-js">{
    &quot;preset&quot; : &quot;ts-jest&quot;,
    &quot;testEnvironment&quot;: &quot;node&quot;
}</code></pre>
<p><br><br></p>
<h3 id="2-2-jest-구문">2-2. jest 구문</h3>
<br>

<p>다음은 자주 사용하는 jest 구문에 대한 설명과 예제 코드입니다</p>
<br>

<blockquote>
<p><code>describe()</code></p>
</blockquote>
<p><code>describe()</code>는 테스트 그룹을 묶는 역할을 합니다
<code>it()</code> or <code>test()</code> : <code>it()</code> 함수나 <code>test()</code> 함수는 하나의 테스트 케이스를 나타냅니다
첫 번째 인자는 테스트 케이스의 이름을 나타내고, 두 번째 인자는 실제 테스트를 수행할 콜백 함수입니다
<br></p>
<blockquote>
<p><code>expect()</code> </p>
</blockquote>
<p><code>expect()</code> 함수는 테스트 결과를 검증하는 역할을 합니다
인자에 <code>it</code> 함수의 결과값을 할당하여 검증합니다 
(<code>expect(add(1, 2)).toBe(3)</code>)
<br></p>
<blockquote>
<p><code>matcher</code> </p>
</blockquote>
<p><code>matcher</code>는 <code>expect()</code> 함수 결과값에 사용할 수 있는 메서드입니다
Jest에서는 <code>toBe()</code>나 <code>toEqual()</code> 등 다양한 <code>matcher</code> 메서드를 제공합니다
<br></p>
<blockquote>
<p><code>beforeEach()</code> </p>
</blockquote>
<p>각각의 <code>it()</code> 함수가 실행될 때마다 항상 먼저 실행될 콜백함수입니다 
(<code>beforeEach()</code> =&gt; <code>it()</code> =&gt; <code>beforeEach()</code> =&gt; <code>it()</code> ...)
<code>beforeAll()</code> : 모든 <code>it()</code> 함수가 실행되기 전에 단 한 번만 먼저 실행될 콜백함수입니다
<br></p>
<blockquote>
<p><code>mok()</code> </p>
</blockquote>
<p><code>mock()</code> 함수는 실제로 동작하는 함수를 임시로 대체하는 역할을 합니다 
예를 들어, 데이터베이스 연결을 테스트하려면 데이터베이스에 접속할 필요 없이 
<code>mock()</code> 함수를 사용하여 가짜 데이터베이스를 만들어서 테스트를 수행할 수 있습니다
<br><br></p>
<pre><code class="language-ts">// user.controller

describe(&#39;user controller 검증&#39;, () =&gt; {
    beforeEach(() =&gt; { })

    it(&quot;create() 검증&quot;, () =&gt; {
        // req.body가 잘 들어오는지, service 메서드가 잘 작동하는지, res.send 혹은 res.json 응답객체가 잘 담겼는지
        const a = 1 + 1
        expect(1).toBe(a) // failed
    })

    it(&quot;create() 예외처리 검증&quot;, () =&gt; {
        // req.body를 강제로 다른 값으로 바꿔서 에러 발생시키기
        // catch문이 잘 발동되는지, next 함수가 잘 작동하는지...
        const a = 1 + 1
        expect(2).toBe(a) // passed
    })
})
</code></pre>
<ul>
<li><code>it</code> 메서드는 <code>test()</code>로 바꿔쓸 수도 있습니다</li>
</ul>
<p><br><br></p>
<p><code>beforeEach()</code> 예제</p>
<pre><code class="language-ts">class UserController {
    public num: number = 0
    constructor() {}
}

describe(&#39;user controller 검증&#39;, () =&gt; {
    let user: UserController
    afterAll(() =&gt; { })
    afterEach(() =&gt; { })
    beforeAll(() =&gt; { })
    beforeEach(() =&gt; {
        // beforeEach에서 인스턴스를 생성하면 코드 중복을 막을 수 있습니다
        user = new UserController()
    })

    it(&quot;create() 검증&quot;, () =&gt; {
        user.num = 10
        expect(0).toBe(user.num) // failed
    })

    it(&quot;create() 예외처리 검증&quot;, () =&gt; {
        expect(0).toBe(user.num) // passed
    })
})</code></pre>
<p><br><br></p>
<p><code>beforeAll()</code> 예제</p>
<pre><code class="language-ts">class UserController {
    public num: number = 0
    constructor() {}
}
describe(&#39;user controller 검증&#39;, () =&gt; {
    let user: UserController
    let result: { name: string} = {name: &quot;&quot;}
    afterAll(() =&gt; { })
    afterEach(() =&gt; { })
    beforeAll(() =&gt; { 
        result = { name: &quot;hello world&quot; }
    })
    beforeEach(() =&gt; {
        user = new UserController()
    })

    it(&quot;create() 검증&quot;, () =&gt; {
        // req.body가 잘 들어오는지, service 메서드가 잘 작동하는지, res.send 혹은 res.json 응답객체가 잘 담겼는지
        user.num = 10
        expect(10).toBe(user.num)
    })

    it(&quot;create() 예외처리 검증&quot;, () =&gt; {
        // req.body를 강제로 다른 값으로 바꿔서 에러 발생시키기
        // catch문이 잘 발동되는지, next 함수가 잘 작동하는지...
        expect(0).toBe(user.num)
    })
})</code></pre>
<p><br><br></p>
<p>인터페이스만 잘 지킨다면 테스트 코드는 문제없이 잘 돌아갑니다
그런데 테스트코드의 역할은 <strong>값</strong>까지 확인하는 것인데... DB를 연동할 수 없는데 값에 대한 확인은 어떻게 해야 할까요?</p>
<p>이것을 해결해주는 것이 위에서 설명한 <code>Mock()</code>함수의 역할입니다</p>
<p>아래 예제에서는 &#39;인자값&#39;(요청)이 잘 들어가는지, &#39;리턴값&#39;(응답)을 잘 뱉어내는지를 확인할 수 있어야 합니다
(다른 의존성 코드는 제쳐두고 서비스 코드의 로직이 잘 돌아가는지만 확인하면 됩니다)</p>
<p><code>Mock()</code> 예제</p>
<pre><code class="language-ts">interface BoardModel {
    email?: string
    username?: string
    subject?: string
    content?: string
    hashtag?: string
    category?: string
    images?: string
    thumbnail?: string

}


interface BoardRepository {
    getUserById: (email: string) =&gt; Promise&lt;BoardModel&gt;
}

// DTO
interface BoardWriteDTO {
    email: string
    subject: string
    content: string
    hashtag: string
    category: string
    images: string
    thumbnail: string
    tel1?: string
    tel2?: string
    tel3?: string
}

class BoardService {
    constructor(private readonly boardRepository: BoardRepository) { }

    // 리턴값은 BoardModel
    public async postWrite(data: BoardWriteDTO): Promise&lt;BoardModel&gt; {
        const { email, tel1, tel2, tel3 } = data
        const tel = `${tel1}-${tel2}-${tel3}`
        const { username } = await this.boardRepository.getUserById(email)
        return { username }
    }
}

class BoardController {
    constructor(private readonly boardService: BoardService) { }
    public write() {
        const data: BoardWriteDTO = {
            email: &quot;&quot;,
            subject: &quot;&quot;,
            content: &quot;&quot;,
            hashtag: &quot;&quot;,
            category: &quot;&quot;,
            images: &quot;&quot;,
            thumbnail: &quot;&quot;,
        }
        this.boardService.postWrite(data)
    }
 }



 describe(&#39;Board Service&#39;, () =&gt; {
    let boardService: BoardService
    let boardRepository: BoardRepository
    // 인스턴스 생성을 위해
    beforeEach(() =&gt; {
        // const boardRepository: BoardRepository = {
        //     getUserById: (email: string) =&gt; {
        //         return {} as BoardModel
        //     }
        // }

        // Mock() ~ 실제 레포지토리 대신 실행될 테스트용 함수
        boardRepository = {
            // 함수가 호출되면 반환될 결과값 (web7722 in 프로미스 객체)
            getUserById: jest.fn().mockResolvedValue(&quot;web7722&quot;),
        }
        boardService = new BoardService(boardRepository)
    })
    it(&#39;postWrite test&#39;, async () =&gt; {
        const dto: BoardWriteDTO = {
            email: &quot;testAddress&quot;,
            subject: &quot;a&quot;,
            content: &quot;a&quot;,
            hashtag: &quot;a&quot;,
            category: &quot;a&quot;,
            images: &quot;a&quot;,
            thumbnail: &quot;a&quot;,
            tel1: &quot;010&quot;,
            tel2: &quot;1234&quot;,
            tel3: &quot;5678&quot;
        }
        const { username } = await boardService.postWrite(dto)

        // 실행결과 getUserById가 실행이 되는지를 검증
        expect(boardRepository.getUserById).toBeCalled() // passed

        // 인자값이 잘 들어가고 있는지를 검증
        expect(boardRepository.getUserById).toBeCalledWith(`${dto.tel1}-${dto.tel2}-${dto.tel3}`)
    })
})</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Typescrpit -4 추상 클래스  (23/04/21)]]></title>
            <link>https://velog.io/@kj_code00/Typescrpit-4-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@kj_code00/Typescrpit-4-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Fri, 21 Apr 2023 07:33:07 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/real-bird/post/f930709b-b453-4499-9375-82c9481b1d97/image.png">

<br>

<h2 id="1-추상-클래스란">1. 추상 클래스란</h2>
<br>

<p>추상 클래스는 인터페이스의 역할도 하면서 클래스의 기능도 가지고 돌연변이 같은 클래스입니다</p>
<p>일반 클래스와의 차이는 선언할 때 코드 말머리에 <code>abstract</code>를 추가한다는 것인데요,
이렇게 선언한 클래스는 인스턴스 생성구문(<code>new</code>)를 사용할 수 없습니다
그리고 용어 정의적으로는 하나 이상의 추상 메서드를 가진 클래스를 추상 클래스라고 합니다</p>
<br>

<pre><code class="language-ts">// 추상 클래스 생성
abstract Class App

// 추상 메서드 생성
abstract fc(): void;</code></pre>
<br>

<p>추상 클래스에서 선언한 추상 메서드들은 이를 상속받은 자식 클래스에서 반드시 구체화되어야 합니다
이와 같이 실제로 발동될 코드에 조건을 걸어준 다는 점에서 추상 클래스는 인터페이스와 유사한 면이 있습니다</p>
<p><br><br></p>
<h3 id="2-추상-클래스-vs-인터페이스">2. 추상 클래스 vs 인터페이스</h3>
<br>


<p>추상 클래스는 일종의 <strong>미완성 설계도</strong>이자 클래스 생성의 중간 단계 정도의 역할을 합니다
공통적으로 사용될 일반 메서드도 가질 수 있으면서, 거기서 선언된 추상 메서드는 하위 클래스에서 반드시 구현하도록 강제합니다
<br></p>
<pre><code class="language-ts">abstract class Component {
      ...
    abstract setup(): void;
    abstract template(): string;
    render(): void {
        this.target.innerHTML = this.template();
    }
}

// setup()과 template()는 Component를 상속받은 하위 클래스에서 구체화</code></pre>
<br>


<p>반면 인터페이스의 역할은 <strong>체크리스트</strong>와 유사합니다
간단한 예제 코드를 살펴보겠습니다</p>
<pre><code class="language-ts">interface IAnimal {
  name: string;
  makeSound(): void;
}</code></pre>
<br>

<p>이 인터페이스는 <code>name</code>이라는 속성과 <code>makeSound()</code>라는 메서드를 정의하고 있습니다
만약 <code>IAnimal</code> 인터페이스를 구현하는 클래스가 있다면, 해당 클래스는 인터페이스가 정의한 속성과 메서드를 모두 구현해야 합니다 
마치 체크리스트에서 모든 항목을 체크해야 하는 것과 같습니다</p>
<p>또 인터페이스는 클래스와 클래스 간의 상호 작용을 가능하게 하기 위해서도 사용됩니다 
그리고 인터페이스는 추상 클래스와 달리 다중 상속을 지원하기 때문에, 하나의 클래스는 여러 인터페이스를 동시에 구현할 수도 있습니다</p>
<p><br><br></p>
<h3 id="3-추상-클래스-연습하기">3. 추상 클래스 연습하기</h3>
<br>

<pre><code class="language-ts">interface IList {
    id: number;
    userid: string;
    content: string;
    register: Date;
}

interface IUser {
    userid: string;
    username: string;
}

interface IState {
    user: IUser;
    list: IList[]; // [{}, {}, {}...]
}

// HTMLElement와 제네릭을 사용한 이유는 상속받을 자식 클래스에서 엘리먼트와 타입을 구체화하기 위해 (확장성 ↑)
abstract class Component&lt;T&gt; {
    target: HTMLElement;
    state!: T;

    constructor(_target: HTMLElement) {
        this.target = _target;
        this.setup();
        this.render();
    }

    abstract setup(): void;
    abstract template(): string;
    render(): void {
        this.target.innerHTML = this.template();
    }
}

// 타입을 구체화합니다 (IState -&gt; this.state)
class App extends Component&lt;IState&gt; {
    setup(): void {
        this.state = {
            list: [
                { id: 1, userid: &quot;web7722&quot;, content: &quot;hello1&quot;, register: new Date(&quot;2023-01-09&quot;) },
                { id: 2, userid: &quot;web7722&quot;, content: &quot;hello2&quot;, register: new Date(&quot;2023-01-09&quot;) },
                { id: 3, userid: &quot;web7722&quot;, content: &quot;hello3&quot;, register: new Date(&quot;2023-01-09&quot;) },
            ],
            user: {
                userid: &quot;web7722&quot;,
                username: &quot;testname&quot;,
            },
        };
    }

    template(): string {
        const { list } = this.state;

        return `
            &lt;div id=&#39;comment-list&#39;&gt;
                ${list
                .map((comment) =&gt; {
                    return `&lt;ul class=&quot;comment-row&quot; data-index=&quot;${comment.id}&quot;&gt;
                                &lt;li class=&quot;comment-id&quot;&gt;${comment.userid}&lt;/li&gt;
                                &lt;li class=&quot;comment-content&quot;&gt;${comment.content}&lt;/li&gt;
                                &lt;li class=&quot;comment-date&quot;&gt;${comment.register}&lt;/li&gt;
                            &lt;/ul&gt;`;
                })
                .join(&quot;&quot;)}
            &lt;/div&gt;
            &lt;button id=&#39;btn&#39;&gt;버튼!&lt;/button&gt;
        `;
    }
}

const div = document.createElement(&quot;div&quot;);
div.id = &quot;app&quot;;
const app = new App(div);</code></pre>
<p><br><br></p>
<h3 id="3-2-리액트--타입스크립트-tsx">3-2. 리액트 + 타입스크립트 (tsx)</h3>
<br>

<pre><code class="language-ts">// App.tsx
import { TestComponent } from &quot;./common/Test&quot;

const App = () =&gt; {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;TestComponent userid=&quot;web7722&quot; username=&quot;testname&quot; /&gt;
    &lt;/div&gt;
  );
}

export default App;



// Test.tex
import { useState, useEffect } from &quot;react&quot;

// 내려받는 쪽에서 프로퍼티에 대한 타입 정의가 필요
interface IUser {
    userid: string;
    username: string;
}

interface IList {
    id: number;
    userid: string;
    content: string;
    register: string;
}

export const TestComponent= ({ userid, username }: IUser) =&gt; {
    // 전달받은 프로퍼티의 타입과 상태값의 타입을 명시해야 합니다 (IUser, IList[])
    const [ commentList, setCommentList ] = useState&lt;IList[]&gt;([])
        const comments: IList[] = [
        { id: 1, userid: &quot;web7722&quot;, content: &quot;hello1&quot;, register: &quot;2023-01-09&quot; },
        { id: 2, userid: &quot;web7722&quot;, content: &quot;hello2&quot;, register: &quot;2023-01-09&quot; },
        { id: 3, userid: &quot;web7722&quot;, content: &quot;hello3&quot;, register: &quot;2023-01-09&quot; },
    ]

    useEffect(() =&gt; {
        setCommentList(comments)
    }, [])

    return (
      &lt;div&gt;
        &lt;div&gt;userid: {userid}&lt;/div&gt;
        &lt;div&gt;username: {username}&lt;/div&gt;
            &lt;form&gt;input&lt;/form&gt;
             &lt;div id=&#39;comment-list&#39;&gt;
               {commentList?.map(comment =&gt; 
                  &lt;ul data-index={comment.id}&gt;
                        &lt;li&gt;{comment.userid}&lt;/li&gt;
                        &lt;li&gt;{comment.content}&lt;/li&gt;
                        &lt;li&gt;{comment.register}&lt;/li&gt;
                &lt;/ul&gt;)}
           &lt;/div&gt;
           &lt;button id=&#39;btn&#39;&gt;버튼!&lt;/button&gt;
      &lt;/div&gt;
    )
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Typescript -3 함수 오버로드 & 제네릭 (23/04/20)]]></title>
            <link>https://velog.io/@kj_code00/Typescript-3-%EC%A0%9C%EB%84%A4%EB%A6%AD-230420</link>
            <guid>https://velog.io/@kj_code00/Typescript-3-%EC%A0%9C%EB%84%A4%EB%A6%AD-230420</guid>
            <pubDate>Thu, 20 Apr 2023 04:53:37 GMT</pubDate>
            <description><![CDATA[<h2 id="typescript-문법-2편">Typescript 문법 2편</h2>
<img src="https://velog.velcdn.com/images/real-bird/post/f930709b-b453-4499-9375-82c9481b1d97/image.png">


<h3 id="1-함수-2번째">1. 함수 2번째</h3>
<br>


<p>타입스크립트를 사용할 때, 함수에서 인자값을 넣을 수도 안 넣을수도 있게끔 하려면 어떻게 해야 할까요?</p>
<pre><code class="language-ts">const hello = (name: string | null = null): void =&gt; {
    console.log(`hello ${name}`)
}

hello()
hello(&quot;admin&quot;)</code></pre>
<p>먼저 이렇게 유니온 타입을 활용해서 디폴트값으로 null이나 undefined를 대입시키는 방법이 있습니다</p>
<p><br><br></p>
<p>혹은 선택적 매개변수(optional parameter, 값이 없으면 undefined)를 이용하는 방법도 있구요</p>
<pre><code class="language-ts">const hello = (name?: string): void =&gt; {
    console.log(`hello ${name}`)
}</code></pre>
<p>*인자가 없을 경우 &quot;hello undefined&quot;라는 문자열이 출력됩니다</p>
<p><br><br></p>
<h3 id="1-2-함수-오버로드">1-2. 함수 오버로드</h3>
<br>

<p>아래의 reverse 함수는 문자열, 혹은 숫자를 인자로 받았을 때 그 순서를 뒤집는 기능을 합니다</p>
<pre><code class="language-ts">const reverse = (x: number | string): string | number =&gt; {
    const result = x.toString().split(&quot;&quot;).reverse().join(&quot;&quot;)
    return (typeof x === &quot;number&quot;) ? parseInt(result) : result
}

console.log(reverse(123), reverse(&quot;abc&quot;))
</code></pre>
<p>당장은 결과가 잘 출력되는 것 같지만, 
위 코드는 타입추론상 result의 타입이 number일 수도, string일 수도 있기 때문에
result에 대해 공통적으로 사용되는 메서드만 사용할 수 있다는 문제가 발생합니다</p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/f0322260-7fda-4028-ab88-f7db28382e50/image.png" alt=""></p>
<br>

<p>그러면 인자가 number면 리턴값도 number를, string이면 string을 반환받으려면 어떻게 해야 할까요</p>
<br>

<p>우선 함수 오버로드를 사용하는 방법이 있습니다</p>
<blockquote>
<p>함수 오버로딩(overloading)이란?</p>
</blockquote>
<p>타입스크립트에서는 같은 이름을 가진 함수를 여러 개 정의할 수 있으며,
이 때 각 함수는 서로 다른 타입을 가지는 매개변수로 정의해야 합니다
이와 같이 매개변수가 다르지만 이름이 동일한 함수를 여러개 생성하는 것을 함수 오버로딩이라고 합니다</p>
<br>

<pre><code class="language-ts">// 함수 오버로드

// 표현식
function reverse(x: string): string
function reverse(x: number): number
function reverse(x: string | number): string | number {
    const result = x.toString().split(&quot;&quot;).reverse().join(&quot;&quot;)
    return (typeof x === &quot;number&quot;) ? parseInt(result) : result
}

// 화살표 문법은??
// const reverse = (x: number| string): string|number =&gt; {
//     const result = x.toString().split(&quot;&quot;).reverse().join(&quot;&quot;)
//     return (typeof x === &quot;number&quot;) ? parseInt(result) : result
// }

const resultStr = reverse(&quot;abc&quot;)
const resultNum = reverse(123)</code></pre>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/c3eb91eb-4fa8-48b3-9044-1273df2f6a46/image.png" alt="">
<img src="https://velog.velcdn.com/images/kj_code00/post/fb24bcaf-08af-48b5-8460-70f5826f65a9/image.png" alt=""></p>
<p>결과 인자의 타입과 리턴값의 타입이 일치하는 것을 확인할 수 있습니다
이처럼 함수 오버로드는 컴파일러에게 함수 호출 시에 매개변수와 리턴값의 타입을 추론하는데에 도움을 줄 수 있습니다</p>
<p><br><br></p>
<h3 id="2-제네릭">2. 제네릭</h3>
<blockquote>
<p>제네릭: 제네릭은 타입의 일반화를 가능하게 해줍니다
함수나 클래스에서 타입이 고정되지 않고 유연하게 대처할 수 있도록 합니다</p>
</blockquote>
<br>


<p>제네릭은 함수나 클래스에서 사용되는 타입을 미리 정하지 않고, 
호출되거나 인스턴스화될 때 타입을 동적으로 결정할 수 있도록 하는 기능입니다</p>
<ul>
<li><p>함수 x 제네릭 :  함수 <strong>호출</strong> 시 전달되는 인자의 타입에 따라 리턴값의 타입이 결정됩니다 
제네릭 타입은 일반적으로 <code>T</code>라는 이름으로 표현되며, (특별한 의미는 없지만)
함수에서 T를 사용하여 입력된 인자의 타입과 리턴값의 타입을 추론합니다</p>
</li>
<li><p>클래스 × 제네릭 : 클래스가 <strong>인스턴스</strong>화될 때 제네릭 타입이 결정됩니다
클래스에서 제네릭 타입은 클래스 이름 뒤에 <code>&lt;T&gt;</code>와 같은 형태로 선언됩니다</p>
</li>
</ul>
<br>

<p>제네릭은 타입 안정성을 보장하는 데에도 도움이 됩니다
예를 들어서, 배열 요소의 타입을 일치하지 않는 값으로 설정하면 
컴파일 과정에서 오류가 발생하지 않고 런타임으로 넘어가는데,
제네릭을 사용하면 컴파일 시에도 타입 안정성을 보장할 수 있습니다</p>
<p>아래는 제네릭에 대한 이해를 돕기 위한 예제 코드들입니다</p>
<p><br><br></p>
<p>예제 1)</p>
<pre><code class="language-ts">// 1 -&gt; 1
// &#39;a&#39; -&gt; &#39;a&#39;
// {} -&gt; {}
// [] -&gt; []

const echo = (type: any): any =&gt; {
    console.log(type)
}

echo(1)
echo(&#39;a&#39;)
echo({})
echo([])</code></pre>
<p>any타입이 리턴값의 타입을 추론하지 못하는 문제를 제네릭을 사용하면 해결할 수 있습니다</p>
<br>

<pre><code class="language-ts">// &lt;T&gt;(1): 타입 매개변수를 선언
// T(2) : 매개변수의 타입을 정의
// T(3) : 리턴값의 타입을 정의
const echo = &lt;T&gt;(type: T): T =&gt; {
    console.log(type)
    return type
}

echo(1)
echo(&#39;a&#39;)
echo({})
echo([])</code></pre>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/6216351d-f4a3-40d9-9af0-3fa3d7a064b3/image.png" alt="">
<img src="https://velog.velcdn.com/images/kj_code00/post/f125bb8d-ee2e-4906-84e5-456037853f51/image.png" alt=""></p>
<p>인자의 타입에 따라 매개변수와 리턴값의 타입이 결정되었습니다</p>
<p><br><br></p>
<p>예제 2)</p>
<pre><code class="language-ts">interface Props {
    name: string
    id: string
}
const props: Props = {
    name: &quot;kim&quot;,
    id: &quot;admin&quot;
}

const echo = &lt;T&gt;(type: T): T =&gt; {
    console.log(type)
    return type
}
echo(props) // 타입추론 발동
echo&lt;Props&gt;(props) // 타입 명시</code></pre>
<p><br><br></p>
<pre><code class="language-ts">const push = &lt;T&gt;(arr: T): T[] =&gt; {
    const result = [arr]
    console.log(result)
    return result
}

push(1) // number[]
push(&#39;a&#39;) // string[]</code></pre>
<p><br><br></p>
<p>예제3)</p>
<p>좀 전의 함수 오버로드 예제에서 살펴본 이슈도 제네릭을 써서 해결할 수 있습니다</p>
<pre><code class="language-ts">// 리턴 타입을 미리 명시합니다. 대신 number나 string 인자만 받을 수 있습니다
const reverse = &lt;T&gt;(x: T): number | string =&gt; {
      // 어느정도 타입 추론이 되기는 하지만... 
    // x에 대한 타입을 as string으로 고정해주지 않으면 split 메서드에서 오류 발생
    const params = (typeof x === &quot;number&quot;) ? x.toString() : x as string
    const result = params.split(&quot;&quot;).reverse().join(&quot;&quot;)
    return (typeof x === &quot;number&quot;) ? parseInt(result) : result
}

const resultStr = reverse(&quot;abc&quot;) // &quot;cba&quot;
const resultNum = reverse(123) // 321





// 위 코드를 개선한 버전
const reverse = &lt;T&gt;(x: T): T =&gt; {
    const params = (typeof x === &quot;number&quot;) ? x.toString() : x
    const result = (typeof params === &quot;string&quot;) ? params.split(&quot;&quot;).reverse().join(&quot;&quot;) : params
    return result as T // 리턴 타입을 제네릭으로 형변환
}

const resultStr = reverse(&quot;abc&quot;) // &quot;cba&quot;
const resultNum = reverse(123) // 321
const resultObj = reverse({abc: &quot;abc&quot;}) // 객체나 배열 등은 원본 그대로 반환합니다



// 개선 버전2. 객체랑 배열도 뒤집어보기
const reverse2 = &lt;T&gt;(x: T): T =&gt; {
       // 먼저 unknown을 할당합니다
    let result: unknown = undefined

    if (typeof x === &quot;string&quot;) {
        result = x.split(&quot;&quot;).reverse().join(&quot;&quot;)
    } else if (typeof x === &quot;number&quot;) {
        result = x.toString().split(&quot;&quot;).reverse().join(&quot;&quot;)
    } else if (x instanceof Array) {
        result = x.reverse()
    } else if (typeof x === &quot;object&quot; &amp;&amp; x !== null) {
        result = Object.fromEntries(Object.entries(x).reverse())
    }
      // 다시 제네릭 타입으로
    return result as T
}

console.log(reverse2(123), reverse2(&quot;abc&quot;), reverse2({ abc: &quot;abc&quot;, 123: &quot;123&quot; }), reverse2([1, 2, 3]))
// 321 cba { &#39;123&#39;: &#39;123&#39;, abc: &#39;abc&#39; } [ 3, 2, 1 ]



// 버전3. 버전2에 대한 리팩토링
const reverse2 = &lt;T&gt;(x: T): T =&gt; {

    if (typeof x === &quot;string&quot; || typeof x === &quot;number&quot;) {
        return x.toString().split(&quot;&quot;).reverse().join(&quot;&quot;) as T
    } 
    if (x instanceof Array) {
        return x.reverse() as T
    } 
    if (typeof x === &quot;object&quot; &amp;&amp; x !== null) {
        return Object.fromEntries(Object.entries(x).reverse()) as T
    }
    return null as T
}

console.log(reverse2(123), reverse2(&quot;abc&quot;), reverse2({ abc: &quot;abc&quot;, 123: &quot;123&quot; }), reverse2([1, 2, 3]))
// 321 cba { &#39;123&#39;: &#39;123&#39;, abc: &#39;abc&#39; } [ 3, 2, 1 ]

// if 대신 스위치문을 쓸 수도 있겠네요</code></pre>
<p>제네릭을 사용할 때는 어떠한 타입의 인자가 들어올 수도 있음을 가정하고 코드를 설계해야 합니다
그래서 함수 내부에서 <code>if (typeof...)</code>나 스위치문을 자주 사용하게 됩니다</p>
<br>

<ul>
<li>내부구조를 예측하기 어려운 참조타입을 다루려면 제네릭을 쓸 수 밖에...</li>
<li>될 것 같은 코드가 타입추론으로 해결이 되지 않을 때는 타입을 명시해주는 것이 방법...?</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Typescript -2 문법 (23/04/18)]]></title>
            <link>https://velog.io/@kj_code00/Typescript-2-%EB%AC%B8%EB%B2%95-230418</link>
            <guid>https://velog.io/@kj_code00/Typescript-2-%EB%AC%B8%EB%B2%95-230418</guid>
            <pubDate>Wed, 19 Apr 2023 06:11:58 GMT</pubDate>
            <description><![CDATA[<h2 id="typescript-문법">Typescript 문법</h2>
<img src="https://velog.velcdn.com/images/real-bird/post/f930709b-b453-4499-9375-82c9481b1d97/image.png">

<p><br><br></p>
<h3 id="1-변수와-원시타입">1. 변수와 원시타입</h3>
<pre><code class="language-ts">// JS 변수선언
let num = 10
const str = &quot;hello Javascript&quot;

// TS 변수선언
let num2: number = 10
const str2: string = &quot;hello Typescript&quot; </code></pre>
<p>사실 이렇게 명확한 타입은 굳이 지정해줄 필요가 없습니다
타입스크립트가 대입연산자로 넣은 값을 통해서 타입추론을 할 수 있기 때문
(<code>타입 추론</code> : 타입이 정해지지 않은 변수에 대해서 컴파일러가 변수의 타입을 스스로 찾아낼 수 있도록 하는 기능)</p>
<br>

<p><img src="https://velog.velcdn.com/images/kj_code00/post/e6bb35c3-fcc1-4eea-851f-3bd5cbfdb695/image.png" alt="">
(↑ 넘버타입에 관한 메서드가 따라오는 것을 확인할 수 있습니다)</p>
<p><br><br></p>
<p>↓ 기타 원시타입들</p>
<pre><code class="language-ts">{
    const num:number = 10
    const float:number = 3.14
    const nan:number = NaN
    const infinity:number = Infinity
}


{
    const str: string = &quot;Hello TypeScript&quot;
}

{
    const bool: boolean = true
}

{
    const nullValue: null = null
    const undefinedValue: undefined = undefined
}</code></pre>
<p><br><br></p>
<p><strong>함수</strong>에 대해서...</p>
<p>함수는 크게 명시적인 반환값의 존재여부에 따라서 타입이 갈립니다
값이 undefined인 함수를 사용할 경우 그 함수의 타입은 <strong>void</strong>로 지정합니다
(값이 null인 함수는 null타입)
우선 이 void 타입에 대해서만 알아보기로 하겠습니다</p>
<br>


<p><strong>void</strong></p>
<pre><code class="language-ts">// void 타입

function print(): void {
    console.log(`hello TypeScript`)
}
print()

const print2 = (): void =&gt; {
    console.log(`hello TypeScript`)
}
print2()</code></pre>
<br>

<p>함수의 매개변수에도 타입 지정이 필요합니다
(매개변수는 변수와 달리 타입추론이 되지 않기 때문에 꼭 기입하는 습관을 들이기로)</p>
<pre><code class="language-ts">// 전자는 매개변수에 대한 타입지정, 후자는 함수의 반환값에 대한 타입지정
const print = (str: string): void =&gt; {
    console.log(`hello ${str}`)
}
print(&quot;TypeScript&quot;)</code></pre>
<p><br><br></p>
<p>↓ 정리</p>
<pre><code class="language-ts">const repository = (): number =&gt; {
    return 10
}

const service = (): string =&gt; {
    const num = repository()
    return &quot;hello&quot; + num
}

const controller = (): void =&gt; {
    try {
        const result = service()    
    } catch (e) {
        console.log(e)
    }
}
controller()</code></pre>
<p>여기서 <code>controller</code> 함수는 명시적인 반환값이 없기 때문에 void 타입입니다</p>
<br>

<p>↓ 잘 이해가 안간다면...</p>
<pre><code class="language-ts">const controller = (): void =&gt; {
    const result: string = service()    
}
controller()


const controller = (): string =&gt; {
    const result = service()
    return result    
}
controller()</code></pre>
<p><br><br></p>
<p>+) <strong>never</strong>: 함수가 무한루프를 돌거나 반환할 수 없는 타입을 지정할 때 사용합니다
하지만 거의 쓰이지 않습니다</p>
<p><strong>never</strong></p>
<pre><code class="language-ts">const throwErr = (): never =&gt; {
    throw new Error(&quot;에러 발생&quot;)
}</code></pre>
<p><br><br></p>
<h3 id="2-참조타입--any-vs-unknown">2. 참조타입 ~ any vs unknown</h3>
<br>

<pre><code class="language-ts">{
    // any : 어떤 타입이든 할당할 수 있습니다, 대신 타입추론이 불가능하며 타입 안정성을 보장하지 않습니다
    const a: any = 10
    const b: number = 10
}


{
    // unknown : 어떤 타입이든 할당할 수 있습니다, 타입추론이 가능하며 타입 안정성을 보장합니다
    const getValue = (value: unknown) =&gt; {
        return value
    }

    const fn: unknown = getValue(&quot;hello world&quot;)
}</code></pre>
<p>any 타입을 남발하면 굳이 타입스크립트를 쓸 이유도 사라집니다
그래서 타입을 알 수 없는 값을 다루려면 any 대신 unknown을 사용하는 것이 좋습니다</p>
<p>unknown 타입은 any와 마찬가지로 모든 값을 할당할 수 있습니다
하지만 unknown은 any를 대체하고 타입 안정성을 보장하기 위해 타입스크립트 3.0부터 도입된 타입으로, 
값을 할당하기 전에 반드시 타입 검사를 거쳐야 합니다</p>
<p>(이 역시 쓸 일은 잘 없지만, 내부구조를 파악하기 힘든 외부 라이브러리를 가져다 쓸 때 사용할 일이 생기기도 합니다)</p>
<br>

<p>위 코드에서 변수 fn의 타입은 미리 지정한 value와 같이 unknown이 됩니다
그러면 함수 fn의 타입을 string으로 지정하려면 어떻게 해야할까요?</p>
<br>

<pre><code class="language-ts">{
    const getValue = (value: unknown): string =&gt; {
        if(typeof value === &quot;string&quot;) return value
        return &quot;&quot;
    }

    const fn: unknown = getValue(&quot;hello world&quot;)
}</code></pre>
<p>위와 같이 else 상황에 대한 처리까지 끝마쳐야만 타입지정 에러가 발생하지 않습니다</p>
<p><br><br></p>
<h3 id="2-2-참조타입--배열">2-2. 참조타입 ~ 배열</h3>
<br>

<p>참조타입중에서 배열은 배열 안에 있는 요소들의 타입까지 지정해줘야 합니다</p>
<pre><code class="language-ts">const strArr: string[]  = [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;]
const numArr: number[] = [1, 2, 3]</code></pre>
<br>


<p>+) 각 오소의 타입이 다를 때에는</p>
<pre><code class="language-ts">const tuple:[string, number] = [&quot;hello&quot;, 123]</code></pre>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/85b2c1a0-8aab-4a3a-88c6-afbbaf82c350/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kj_code00/post/09f94c43-507c-4f3f-8420-34abfef62d53/image.png" alt=""></p>
<p>↑ 요소의 타입에 따라 따라붙는 메서드들이 다른 것을 확인할 수 있습니다</p>
<p><br><br></p>
<h3 id="3-인터페이스--클래스">3. 인터페이스 &amp; 클래스</h3>
<br>

<p>타입스크립트에서 <strong>인터페이스</strong>는 코드 설계 단계에서 데이터의 타입을 정의하는 역할을 합니다
주로 객체의 속성들의 타입을 &#39;미리&#39; 정의할 때, 그리고 함수나 클래스를 다룰 때에도 자주 사용됩니다</p>
<p>인터페이스는 추상적이며 선언적인 개념입니다
그래서 인터페이스 코드는 컴파일러가 타입 체크를 수행할 때만 사용되며, 컴파일 후에는 빌드파일에 포함되지 않습니다</p>
<br>


<p>예제1</p>
<pre><code class="language-ts">interface Product {
    // 인터페이스는 객체가 아니기 때문에 속성 구분을 위한 쉼표가 필요하지 않습니다
    name: string;
    price: number;
}

const product1:{name: string, price: number} = { name: &quot;macbook&quot;, price: 22_000 }
const product2: Product = { name: &quot;iphone&quot;, price: 12_000 }
const products: Product[] = [product, product2]</code></pre>
<p>↑ 위와 같이 인터페이스는 객체의 구조를 정의하는 일종의 가이드 역할을 합니다</p>
<br>


<p>예제2</p>
<pre><code class="language-ts">interface Board {
    readonly id: number; // 읽기 전용
    readonly writer: string;
    subject: string;
    content: string;
    date: Date;
    hit: number;
      like?: number // 선택적 속성
}


const board1: Board = { id: 1, writer: &quot;kim&quot;, subject: &quot;hello&quot;, content: &quot;world&quot;, date: new Date(&quot;2022-01-01&quot;), hit: 0 }
const board2: Board = { id: 2, writer: &quot;lee&quot;, subject: &quot;hello2&quot;, content: &quot;world2&quot;, date: new Date(&quot;2022-01-02&quot;), hit: 2, like: 1 }

console.log(board1)
console.log(board2)

board1.id = 3 // 에러 발생</code></pre>
<ul>
<li>상황에 따라 속성의 사용유무가 달라진다면 해당 속성을 인터페이스에서 선택적 속성으로 지정해야 합니다</li>
<li>인터페이스에 readonly(읽기전용)를 선언하면 const를 쓸 때처럼 객체 생성 후 속성의 재할당이 불가능해집니다</li>
</ul>
<br>

<p>다음은 class에서의 인터페이스 예제입니다</p>
<pre><code class="language-ts">class Product {
    name: string
    price: number
    constructor(name: string, price:number) {
        this.name = name;
        this.price = price;
    }
}

const product3: Product = new Product(&quot;iphone&quot;, 12_000)</code></pre>
<p>여기서 클래스의 속성은 클래스 레벨에서 정의되는 것이고, 생성자 함수의 매개변수는 로컬 변수입니다
이들은 서로 다른 변수로 취급되기 때문에 각각의 타입을 별도로 지정해야 합니다</p>
<p>따라서 Product 클래스에서 name과 price 속성의 타입을 선언하고, 
생성자 함수에서도 (인스턴스 생성시 사용할) name과 price 매개변수의 타입을 지정해주어야 에러가 발생하지 않습니다</p>
<p><br><br>  </p>
<p>덧붙여서 클래스에도 인터페이스 적용이 가능합니다</p>
<pre><code class="language-ts">class Product2 implements Product {
    name: string
    price: number
    constructor(name: string, price:number) {
        this.name = name;
        this.price = price;
    }
}

const product4: Product2 = new Product2(&quot;airpod&quot;, 4_000)</code></pre>
<p>인터페이스를 클래스에 적용시킬 때는 <code>implements</code> 키워드를 사용해야 합니다
그리고 인터페이스에 따라 구현된 클래스는 해당 인터페이스의 요구 사항을 충족해야 합니다</p>
<p>이런 식으로 클래스에서 인터페이스를 사용하면 다른 코드에서도 해당 클래스가 구현한 인터페이스를 참조하여 타입 검사를 수행할 수 있습니다
클래스를 만들 때에도 항상 인터페이스를 미리 정의하는 습관을 들이는 것을 추천합니다 </p>
<br>

<p>그런데 사실 위와 같은 클래스 설계는 올바른 방식이라 볼 수 없습니다</p>
<p><br><br></p>
<h3 id="3-2-클래스-설계">3-2. 클래스 설계</h3>
<br>

<p>클래스 설계는 생성될 객체의 <strong>기능</strong>(메서드)에 초점을 맞춰야 합니다</p>
<br>

<p>예제1</p>
<pre><code class="language-ts">// 생성될 객체(인스턴스)의 모양을 설계하는 인터페이스
interface UserInfo {
    userid: string;
    username: string;

}

// 클래스의 기능 설계(메서드 정의)는 추상 클래스로
abstract class Person {
      // 여기서 addUser 함수의 리턴값은 UserInfo 형태를 따라야 합니다
    abstract addUser( userid: string, username: string ): UserInfo;
}


class User extends Person {
      // 상속받은 추상 클래스를 구체적으로 구현합니다
    addUser (userid: string, username: string): UserInfo {
        return { userid, username }
    }
}</code></pre>
<p><br><br></p>
<p>예제2</p>
<p><strong>접근제한자</strong>(<code>private</code> 혹은 <code>#</code>) ~ 클래스 설계는 직접적으로 속성값에 접근할 수 없도록 해야 합니다
(메서드를 통해서만 접근할 수 있도록... 객체지향 설계의 캡슐화)</p>
<pre><code class="language-ts">interface IProduct {
    name: string;
    price: number;
}

class Product {
    private name: string
    private price: number
    private amount: number
    private discountRate: number

    constructor(name: string, price: number, amount: number, discountRate: number) {
        this.name = name;
        this.price = price;
        this.amount = amount;
        this.discountRate = discountRate;
    }

    getProduct() {
        return {
            name: this.name,
            price: this.price,
            amount: this.amount,
            discountRate: this.discountRate
        }
    }
    getName(): string {
        return this.name
    }
    getPrice(): number {
        return this.price * (1 - this.discountRate)
    }
    setDiscountRate(rate: number): void {
        this.discountRate = rate
    }
    getToalPrice(): number {
        return this.price * this.amount
    }
}


const product = new Product(&quot;apple&quot;, 1_000, 20, 0);
const product2 = new Product(&quot;banana&quot;, 800, 55, 0.1);

console.log(product.getToalPrice())
// 20000
console.log(product2.getName() + &quot; &quot; + product2.getPrice())
// banana 720</code></pre>
<p><br><br></p>
<p>두번째 예제를 읽으면서 느껴지는 것은 기능을 추가할 때마다 클래스가 비대해진다는 것입니다
메서드가 추가될수록 클래스의 기능적 구분도 불분명해지고, 클래스를 사용중인 코드의 관리도 어려워집니다
그리고 인스턴스 생성시 기입해야할 인자도 너무 많습니다</p>
<br>

<p>이에 대해서는 여러 방편이 있겠지만, 객체지향 설계(OOP)의 방법론 중에는
<strong>단일 책임 원칙</strong>(<strong>S</strong>ingle <strong>R</strong>esponsibility <strong>P</strong>rinciple)을 적용하여 클래스를 분리하는 방법이 있습니다 
SRP는 클래스는 단 하나의 책임만 가져야 한다는 원칙으로, 이에 따르면 각 클래스는 하나의 기능에만 집중해서 구현해야 합니다</p>
<p>또 하나는 <strong>개방 폐쇄 원칙</strong>(<strong>O</strong>pen <strong>C</strong>losed <strong>P</strong>rinciple)인데, OCP는 확장에 대해서는 개방적(open)이고, 
수정에 대해서는 폐쇄적(closed)이어야 한다는 의미로 정의됩니다</p>
<p>이러한 몇가지 객체지향 설계 원칙을 적용해서 코드의 유지보수성과 확장성을 좀 더 높여봅시다</p>
<br>

<p>예제3</p>
<pre><code class="language-ts">// 제품에 대한 기본정보를 설정하는 클래스
class Product {
    private name: string
    private price: number

    constructor(name: string, price: number) {
        this.name = name;
        this.price = price;
    }

    getName(): string {
        return this.name;
    }

    getPrice(): number {
        return this.price;
    }
}


// 할인 기능을 위한 인터페이스 생성
interface Discount {
      // 추상 메서드
    getDiscount(price: number): number;
}


// 고정 할인가 적용을 위한 클래스
class DiscountAmount implements Discount {
    private amount: number // 고정 할인액
    constructor(amount: number) {
        this.amount = amount
    }

    getDiscount(price: number): number {
        return price - this.amount   
    }
}

// 할인율 적용을 위한 클래스
class DiscountRate implements Discount {
    private rate: number // 할인율
    constructor(rate: number) {
        this.rate = rate
    }

    getDiscount(price: number): number {
        return price * (1 - this.rate / 100)   
    }
}


// 제품에 할인(최종 가격)을 적용하는 클래스
class ProductDiscount {
    private product: Product
    private discount: Discount
    constructor(product: Product, discount: Discount) {
        this.product = product
        this.discount = discount
    }
    getNewPrice(): number {
        return this.discount.getDiscount(this.product.getPrice())
    }
}


const product1 = new Product(&#39;macbook&#39;, 10_000);
const product2 = new Product(&#39;iphone&#39;, 6_000);

const productWithDiscountRate = new DiscountRate(10);
const productWithDiscountAmount = new DiscountAmount(2_000);

const product1WithDiscount = new ProductDiscount(product1, productWithDiscountRate);
const product2WithDiscount = new ProductDiscount(product2, productWithDiscountAmount);


console.log(product1WithDiscount.getNewPrice()) // 9000
console.log(product2WithDiscount.getNewPrice()) // 4000</code></pre>
<ul>
<li>앞으로의 코드 설계는 if문과 인자값을 최소화하는 방향으로...</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Typescript -1 환경설정 (23/04/18)]]></title>
            <link>https://velog.io/@kj_code00/BlockChain-2-230418</link>
            <guid>https://velog.io/@kj_code00/BlockChain-2-230418</guid>
            <pubDate>Tue, 18 Apr 2023 05:27:25 GMT</pubDate>
            <description><![CDATA[<h2 id="1-typescript">1. Typescript</h2>
<img src="https://velog.velcdn.com/images/real-bird/post/f930709b-b453-4499-9375-82c9481b1d97/image.png">

<br>


<p>타입스크립트 사전 요약</p>
<blockquote>
<ol>
<li><strong>런타임</strong>이 존재하지 않습니다. 대신 <strong>컴파일러</strong>를 필요로 합니다</li>
<li>자바스크립트 실행(런타임) 전에 여러 오류들을 캐치할 수 있습니다</li>
</ol>
</blockquote>
<ol>
<li><p>타입스크립트는 런타임이 존재하지 않습니다
바벨과 마찬가지로 컴파일 과정을 거쳐서 최종적으로는 &#39;자바스크립트&#39;가 실행되는 것,
그래서 별도의 빌드 과정이 필요하며 에디터에 대한 의존도가 큽니다</p>
</li>
<li><p>코드 실행 이전에 자바스크립트의 예측하기 어려운 오류(타입 에러)를 잡아낼 수 있습니다.
즉 디버깅을 하기 전에 에러를 예방할 수 있다는 뜻입니다.
반면 타입스크립트를 적용하면 간단한 코드조차 작성이 길어진다는 단점도 존재합니다 
타입스크립트를 사용함으로써 얻는 장점이 보다 크기 때문(코드의 가독성과 유지보수성 ↑)에 
2번과 같은 단점은 장점에 비해 크게 중요하지 않을 수 있습니다</p>
</li>
</ol>
<br>

<pre><code class="language-js">const sum (a:number, b:number)=&gt; a + b
sum(1, &#39;a&#39;)
// 에러 발생. b가 number 타입이 아니기 때문</code></pre>
<br>


<pre><code class="language-ts">const a:number =10
// 선언과 동시에 에디터는 a라는 변수의 타입을 기억하게 됩니다

a.map()
// a(number)의 프로토타입에는 map 함수가 존재하지 않기 때문에 에러가 발생합니다</code></pre>
<p><br><br></p>
<h3 id="2-typescript-runtime">2. Typescript Runtime?</h3>
<br>

<p>TS는 런타임이 없다는 말에 대한 보충 설명을 예제 코드로 대신하겠습니다</p>
<br>

<p><strong>message.ts</strong></p>
<pre><code class="language-ts">const message:string = &quot;hello typescript&quot;
console.log(message)

node message
// Error: Cannot find module &#39;/.../message&#39;
// 자바스크립트 파일을 찾지 못했다는 의미입니다

node message.ts
// SyntaxError: Missing initializer in const declaration
// 타입 지정에서 오류가 발생합니다 &#39;:string&#39;은 NodeJs가 알지 못하는 문법입니다



let message:string = &quot;hello typescript&quot;
console.log(message)

node message.ts
// SyntaxError: Unexpected token &#39;:&#39;
// NodeJs는 여전히 &#39;:&#39;를 읽지 못하네요</code></pre>
<br>
<br>


<p>그러면 타입스크립트의 실행을 확인하려면 어떻게 해야 할까요?</p>
<ol>
<li>별도의 타입스크립트 컴파일러를 설치하는 것</li>
<li>babel을 통해 타입스크립트 플러그인을 설정하는 것</li>
<li>웹팩을 통해서 관련 설정을 마친 뒤 번들링해서 자바스크립트로 변환하는 것</li>
</ol>
<p>사실 셋 다 같은 의미입니다
(리액트에서 타입스크립트를 사용하려면 3번 세팅이 필수적)</p>
<br>


<p><strong>타입스크립트 설치</strong></p>
<pre><code class="language-sh">npm init -y
npm install -D typescript</code></pre>
<br>


<h3 id="3-tsc-명령어-기초">3. tsc 명령어 기초</h3>
<br>

<p>먼저 package.json을 수정해야 합니다 </p>
<p><strong>package.json</strong></p>
<pre><code class="language-js">  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,
    &quot;build&quot;: &quot;tsc message.ts&quot; // 추가
  },


npm run build // 빌드를 실행합니다</code></pre>
<br>

<p><strong>message.js</strong></p>
<pre><code class="language-js">{
    var message = &quot;hello typescript&quot;;
    console.log(message);
}</code></pre>
<p>빌드 결과 위와 같은 자바스크립트 파일이 생성되었습니다</p>
<p><br><br></p>
<p>다음은 몇가지 TSC 명령어 옵션들을 활용해보겠습니다</p>
<pre><code class="language-js">  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,
    &quot;build&quot;: &quot;tsc message.ts --outDir ./dist --target ES6&quot;
  },</code></pre>
<ul>
<li>루트 디렉토리 바깥의 dist 디렉토리에서 컴파일된 파일이 생성됩니다</li>
<li>ES6 문법을 따르도록 합니다 (<code>var</code> -&gt; <code>let</code>)</li>
</ul>
<p><br><br></p>
<p>일반적으로는 타입스크립트 옵션을 아래와 같이 컨피그 파일로 만들어서 관리합니다
(package.json에는 <code>&quot;build&quot;: &quot;tsc&quot;</code>만 남겨주세요)</p>
<br>

<p><strong>tsconfig.json</strong></p>
<pre><code class="language-ts">{
      &quot;compilerOptions&quot;: {
          &quot;outDir&quot;: &quot;./dist&quot;,
        &quot;target&quot;: &quot;ES6&quot;,
         ...
    }
}  </code></pre>
<p>(종종 <code>tsconfig.build.json</code>, <code>tsconfig.dev.json</code> 등과 같이
빌드모드와 개발모드를 따로 두고 컨피그 파일을 분리해서 사용하는 경우도 있습니다)</p>
<p><br><br></p>
<h3 id="4-tsconfig">4. tsconfig</h3>
<br>

<p>다음은 <code>tsconfing.json</code>에 대한 추가 설명입니다</p>
<br>


<p><strong>tsconfing.json</strong></p>
<pre><code class="language-ts">{
      &quot;compilerOptions&quot;: {
      &quot;outDir&quot;: &quot;./dist&quot;,
    },
    &quot;include&quot;: [&quot;src/*&quot;],
      &quot;exclude&quot;: [&quot;**/*.test.ts&quot;],
}  </code></pre>
<br>

<blockquote>
<ul>
<li><code>compileOptions</code> : 어떤 형태로 컴파일을 진행할 것인지에 대한 세부 설정을 정의합니다</li>
</ul>
</blockquote>
<ul>
<li><code>include</code> : 컴파일을 진행할 디렉토리를 지정합니다 ( ~ <code>./src</code>)<pre><code>  - `/**` : 모든 디렉토리
  - `/*` : 모든 파일</code></pre></li>
<li><code>exclude</code> : 컴파일에서 제외할 항목을 지정합니다 (패턴화)</li>
</ul>
<p><br><br></p>
<h3 id="4-1-compileoptions">4-1 compileOptions</h3>
<br>

<p>다음은 컨피그파일 견본과 각 옵션에 대한 해설입니다</p>
<pre><code class="language-ts">{
  &quot;compilerOptions&quot;: {
    &quot;module&quot; : &quot;CommonJS&quot;,
    &quot;outDir&quot;: &quot;./dist&quot;,
    &quot;target&quot;: &quot;ES6&quot;,
    &quot;esModuleInterop&quot;: true,
    &quot;strict&quot;: true,
    &quot;baseUrl&quot;: &quot;./src&quot;,
    &quot;paths&quot;: {
      &quot;@user/*&quot; : [&quot;user/*&quot;]
    }
  },
  &quot;include&quot;: [&quot;src/*&quot;],
  &quot;exclude&quot;: [&quot;**/*.test.ts&quot;],
}  </code></pre>
<blockquote>
<ul>
<li><code>module</code> : 번들링시 어떤 모듈 시스템을 사용할 지를 결정합니다 (<code>ES6</code>의 <strong>import</strong> vs <code>commonJs</code>의 <strong>require</strong>)</li>
</ul>
</blockquote>
<ul>
<li><code>outDir</code> : 빌드 파일을 내보낼 경로를 설정합니다</li>
<li><code>target</code> : 굳이 하위호환(ES5)을 고려해야 할 필요가 없다면 퍼포먼스가 높은 방향으로...</li>
<li><code>esModuleInterop</code> : import 문법을 바꿔쓸 수 있습니다 (import * as react from &#39;react&#39; → import react from &#39;react&#39;)</li>
<li><code>strict</code> : true 설정시 더 엄격한 타입 체크를 수행합니다</li>
<li><code>baseUrl</code> : 상대 경로를 기준으로 할 때의 기본 경로를 설정하는데 사용됩니다</li>
<li><code>paths</code> : <code>baseUrl</code>을 기준으로 상대 경로를 별칭으로 설정할 수 있습니다
(<code>../../</code> → <code>@</code> ~ import {userService} from &#39;@user/service/user.service.ts&#39;)</li>
</ul>
<br>


<p>*여기서 <code>paths</code>에 추가된 <code>@</code>로 인해 번들링된 파일이 NodeJs로는 실행할 수 없는 상태가 됩니다
이를 해결하기 위해서는 다음 모듈을 설치해야 합니다</p>
<p><br><br></p>
<h4 id="tsc-alias">tsc-alias</h4>
<br>

<p><strong>tsc-alias</strong>는 @와 같은 별칭(Alias)을 사용할 수 있도록 도와주는 라이브러리입니다</p>
<pre><code class="language-sh">npm install -D tsc-alias</code></pre>
<br>

<p>+) package.json 설정</p>
<pre><code class="language-js">  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,
    &quot;build&quot;: &quot;tsc &amp;&amp; tsc-alias&quot;
  },</code></pre>
<br>

<h4 id="tsconfig-path">tsconfig-path</h4>
<p><strong>tsconfig-path</strong>는 타입스크립트 프로젝트에서 별칭(alias)을 사용할 때, 
해당 별칭이 실제 파일 경로와 일치하지 않는 문제를 해결하기 위한 라이브러리입니다</p>
<pre><code class="language-sh">npm install -D tsconfig-path</code></pre>
<p>+) package.json 설정</p>
<pre><code class="language-js">  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,
    &quot;dev&quot;: &quot;ts-node -r tsconfig-paths/register --project tsconfig.json ./src/message&quot;,
    &quot;build&quot;: &quot;tsc &amp;&amp; tsc-alias&quot;
  },</code></pre>
<br>

<p>그리고 tsconfig.json 파일에서 별칭(alias)을 설정하는 대신, 
tsconfig-paths 패키지가 제공하는 NODE_PATH 환경변수를 사용하여 별칭을 지정할 수 있습니다</p>
<br>

<pre><code class="language-ts">{
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;NODE_PATH=./src ts-node index.ts&quot;
  }
}</code></pre>
<p><br><br></p>
<h3 id="5-ts-node">5. ts-node</h3>
<p>타입스크립트로 코드를 작성하면 개발단계에서 작성 &gt; 확인 과정에서 많은 시간을 잡아먹게 됩니다
<code>ts-node</code>를 사용하면 가상으로 빌드를 처리하여 실시간으로 코드에 대한 피드백을 받을 수 있습니다</p>
<br>

<pre><code class="language-sh">npm install -g ts-node</code></pre>
<p>(ts-node는 주로 글로벌로 설치하는 편입니다)</p>
<p><br><br></p>
<p>+) 만약 <strong>nodemon</strong>을 사용한다면</p>
<pre><code class="language-js">npm install -D nodemon</code></pre>
<p><strong>package.json</strong></p>
<pre><code class="language-json">&quot;script&quot;: {
  &quot;dev&quot;: &quot;nodemon&quot;
  ...
}</code></pre>
<br>

<p><strong>nodemon.json</strong></p>
<pre><code class="language-js">{
    &quot;watch&quot;: [&quot;src/**/*&quot;],
      &quot;ext&quot; : &quot;ts&quot;,
      &quot;exec&quot; : &quot;ts-node -r tsconfig-paths/register ./src/text.ts&quot;
}</code></pre>
<pre><code>npm run dev

// 실행 성공</code></pre><p><br><br></p>
<p>↓ 정리</p>
<p><strong>package.json</strong></p>
<pre><code class="language-js">{
  &quot;name&quot;: &quot;ts2&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,
    &quot;dev&quot;: &quot;nodemon&quot;,
    &quot;build&quot;: &quot;tsc &amp;&amp; tsc-alias&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;dependencies&quot;: {
    &quot;nodemon&quot;: &quot;^2.0.22&quot;,
    &quot;tsc-alias&quot;: &quot;^1.8.5&quot;,
    &quot;tsconfig-paths&quot;: &quot;^4.2.0&quot;,
    &quot;typescript&quot;: &quot;^5.0.4&quot;
  }
}</code></pre>
<br>

<p><strong>nodemon.json</strong></p>
<pre><code class="language-js">{
    &quot;watch&quot;: [&quot;src/**/*&quot;],
      &quot;ext&quot; : &quot;ts&quot;,
      &quot;exec&quot; : &quot;ts-node -r tsconfig-paths/register ./src/index.ts&quot;
}</code></pre>
<p>엔트리포인트(진입점)는 src 디렉토리의 index파일이 되도록</p>
<br>


<p><strong>tsconfig.json</strong></p>
<pre><code class="language-js">{
    &quot;compilerOptions&quot;: {
        &quot;module&quot; : &quot;CommonJS&quot;,
        &quot;outDir&quot;: &quot;./dist&quot;,
        &quot;target&quot;: &quot;ES6&quot;,
        &quot;esModuleInterop&quot;: true,
        &quot;strict&quot;: true,
        &quot;baseUrl&quot;: &quot;./src&quot;,
        &quot;paths&quot;: {
            &quot;@common/*&quot;: [&quot;common/*&quot;],
            &quot;@hooks/*&quot;: [&quot;hooks/*&quot;],
            ...  
        }
    },
    &quot;include&quot;: [&quot;src/**/*&quot;],
    &quot;exclude&quot;: [&quot;**/*.test.ts&quot;],
}</code></pre>
<p>위와 같이 <code>common</code>, <code>hooks</code> 등의 디렉토리를 별칭으로 불러오려면 사용자가 직접 추가해야 합니다</p>
<p><code>tsconfig-paths</code>를 사용하면 index 파일을 엔트리포인트로 잡고
이하의 디렉토리는 따로 baseUrl과 paths 설정을 하지 않아도 별칭(<code>@</code>)으로 불러올 수 있게 됩니다</p>
<br>




<p><br><br></p>
<p>다음은 타입스크립트 문법편입니다</p>
]]></description>
        </item>
    </channel>
</rss>