SmartContract 배포 및 Token 발행

심재원·2023년 12월 6일
1

Remix-vscode 연결

https://velog.io/@dnjs0397/Remix-vscode-연결

contract folder 생성

Terminal

sudo npm install -g @remix-project/remixd
cd contracts
sudo remixd -s . --remix-ide https://remix.ethereum.org

MintToken.sol File

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MintToken is ERC20 {
    constructor(uint256 initialSupply, string memory _name, string memory _symbol) ERC20(_name, _symbol) {
        _mint(msg.sender, initialSupply * 10 ** 18);
    }
}

DEPLOY

INITIALSUPPLY: uint256(발행량)
_NAME: string
_SYMBOL: string

발행!

Remix

Ether & Gwei & Wei & Gas

https://velog.io/@kich555/Ether-Gwei-Wei-Gas

ERC-20: Token Standard

https://eips.ethereum.org/EIPS/eip-20

OpenZeppelin | Contracts

https://www.openzeppelin.com/contracts

Approve

제3자의 토큰을 보내는 건데 권한이 필요. 그 권한을 설정할 수 있는 게 Approve함수. "누구에게 얼마를 보낼 수 있는 권한을 주겠다!"
실제 서비스 쓸 때는 조심해야 함. 우리의 권한을 넘기는 것이기 때문.

transfer

: 내 토큰을 전송하는 함수. From이 필요없음. 실행시키는 사람이 나니까. 누구에게 얼마를 줄 것인가.

transferFrom

: 누구의 토큰을 누구에게 얼마를 보낼 것인가. 권한 설정을 잘 해야 함. 권한이 없는 상태에서 막 전송하면 안 되기 때문. 제 3자의 코인을 제 3자에게 전달할 수 있는 함수. 그러려면 권한을 부여해야 함.

allowance

: 그 사람이 권한을 갖고 있는지 체크하는 게 위 함수.

balanceOf

: 주소 입력하면 이 토큰을 얼마나 들고 있냐를 확인할 수 있는 함수

erc20-practice/Frontend 생성

Terminal

git clone https://github.com/h662/cra-tailwind-template-2.git .
npm install
npm run start
npm i @metamask/sdk-react

index.js : MetaMaskProvider 생성

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { MetaMaskProvider } from "@metamask/sdk-react";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <MetaMaskProvider
      sdkOptions={{
        dappMetadata: "ERC20 Practice",
        url: window.location.host,
      }}
    >
      <App />
    </MetaMaskProvider>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

App.jsx

import { useSDK } from "@metamask/sdk-react";
import { useState } from "react";

const App = () => {
  const [account, setAccount] = useState("");

  const { sdk } = useSDK();

  const onClickMetaMask = async () => {
    try {
      const accounts = await sdk?.connect();

      setAccount(accounts[0]);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="bg-red-100 min-h-screen flex flex-col justify-center items-center">
      {account ? (
        <>
          <div>
            Hello, {account.substring(0, 7)}...
            {account.substring(account.length - 5)}
          </div>
          <button onClick={() => setAccount("")}>🦊 MetaMask Login</button>
        </>
      ) : (
        <button onClick={onClickMetaMask}>🦊 MetaMask Login</button>
      )}
    </div>
  );
};

export default App;

Terminal - frontend

npm i web3 (web3 library 설치)

mintTokenAbi.json file 생성

Remix의 compiler 탭에서 가장 하단 Abi 복사해서 abi file에 붙여넣기

App.jsx

import { useSDK } from "@metamask/sdk-react";
import { useEffect, useState } from "react";
import { useSpring, animated } from "react-spring";
import Web3 from "web3";
import "animate.css";
import contractAddress from "./contractAddress.json";
import TokenCard from "./components/TokenCard";

const App = () => {
  const [account, setAccount] = useState("");
  const [web3, setWeb3] = useState();

  const { sdk, provider } = useSDK();

  const onClickMetaMask = async () => {
    try {
      const accounts = await sdk?.connect();

      setAccount(accounts[0]);
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    if (!provider) return;

    setWeb3(new Web3(provider));
  }, [provider]);

  const buttonAnimation = useSpring({
    from: { transform: "scale(0.8)", opacity: 0 },
    to: { transform: "scale(1)", opacity: 1 },
    config: { tension: 300, friction: 10 },
  });

  return (
    <div className="bg-gradient-to-r from-black to-purple-800 text-white min-h-screen flex flex-col justify-center items-center">
      {account ? (
        <>
          <div>
            {account.substring(0, 7)}...
            {account.substring(account.length - 5)}
          </div>
          {contractAddress.map((v, i) => (
            <TokenCard
              key={i}
              account={account}
              web3={web3}
              address={v.address}
              owner={v.owner}
              walletAccount={v.walletAccount}
            />
          ))}
          <button
            className="whitespace-pre mt-4"
            onClick={() => setAccount("")}
          >
            🦊 Logout
          </button>
        </>
      ) : (
        <>
          <button className="mb-4 text-7xl">
            <animated.button style={buttonAnimation}>
              <div className="animate__animated animate__bounce">🦊</div>
            </animated.button>
          </button>
          <button
            onClick={onClickMetaMask}
            className="bg-gradient-to-r from-blue-700 to-purple-700 px-8 py-4 text-black font-semibold font-serif rounded-md hover:from-blue-600 hover:to-purple-600"
          >
            <animated.button style={buttonAnimation}>
              MetaMask login
            </animated.button>
          </button>
        </>
      )}
    </div>
  );
};

export default App;

Components/TokenCard.jsx

import { useEffect, useState } from "react";

import mintTokenAbi from "../mintTokenAbi.json";
import contractAddress from "../contractAddress.json";
import OptionCard from "./OptionCard";

const TokenCard = ({ account, web3, address, owner, walletAccount }) => {
  const [name, setName] = useState("TOKEN");
  const [symbol, setSymbol] = useState("TOKEN");
  const [balance, setBalance] = useState(0);
  const [contract, setContract] = useState();
  const [inputAccount, setInputAccount] = useState("");
  const [inputValue, setInputValue] = useState("0");

  const getName = async () => {
    try {
      const response = await contract.methods.name().call();

      setName(response);
    } catch (error) {
      console.error(error);
    }
  };

  const getSymbol = async () => {
    try {
      const response = await contract.methods.symbol().call();

      setSymbol(response);
    } catch (error) {
      console.error(error);
    }
  };

  const getBalanceOf = async () => {
    try {
      const response = await contract.methods.balanceOf(account).call();

      setBalance(Number(web3.utils.fromWei(response, "ether")));
    } catch (error) {
      console.log(error);
    }
  };

  const onSubmitSend = async (e) => {
    try {
      e.preventDefault();

      await contract.methods
        .transfer(inputAccount, web3.utils.toWei(inputValue, "ether"))
        .send({
          from: account,
        });

      getBalanceOf();

      setInputAccount("");
      setInputValue("0");
      alert("성공적으로 토큰을 전송하였습니다.");
    } catch (error) {
      console.log(error);
    }
  };

  const onClickClipBoard = async () => {
    try {
      await navigator.clipboard.writeText(walletAccount);
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    if (!contract || !account) return;

    getName();
    getSymbol();
    getBalanceOf();
  }, [contract, account]);

  useEffect(() => {
    if (!web3) return;

    setContract(new web3.eth.Contract(mintTokenAbi, address));
  }, [web3]);

  useEffect(() => console.log(inputAccount), [inputAccount]);

  return (
    <li className="flex flex-col justify-center items-center gap-1 mt-5">
      <div>
        <button className="text-blue-500 underline" onClick={onClickClipBoard}>
          {owner}
        </button>
        가 발행한 코인
      </div>
      <div className="flex">
        <span className="flex justify-center bg-black w-48">{name}</span>
        <span className="flex justify-center bg-black w-60">
          {balance.toFixed(4)}
        </span>
        <span className="flex justify-center bg-black w-20">{symbol}</span>
        <form className="flex" onSubmit={onSubmitSend}>
        <select
            value={inputAccount}
            onChange={(e) => setInputAccount(e.target.value)}
          >
            <option value=""></option>
            {contractAddress.map((v, i) => (
              <OptionCard
                key={i}
                owner={v.owner}
                walletAccount={v.walletAccount}
              />
            ))}
          </select>
          <input
            className="bg-black w-32"
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
          />
          <button className="bg-black" type="submit">
            Send
          </button>
        </form>
      </div>
    </li>
  );
};

export default TokenCard;

OptionCard.jsx

const OptionCard = ({ walletAccount, owner }) => {
  return <option value={walletAccount}>{owner}</option>;
};

export default OptionCard;

MintToken.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;
-> compile버전 명시, 오픈제플린 코드 참고

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
-> 오픈제플린 라이브러리 활용해서 erc20 모델 갖다 씀. 

contract MintToken is ERC20 {
    constructor(uint256 initialSupply, string memory _name, string memory _symbol) ERC20(_name, _symbol) {
        _mint(msg.sender, initialSupply * 10 ** 18);
    }
}
-> solidity는 contract 단위로 코드를 짬.
앞에는 contract로 시작.
오픈제플린 라이브러리를 상속받아서 씀. 그래서 approve, transfer, transferfrom 등의 기능이 담겨있는 것.

constructor는 스마트 컨트랙트가 배포될 때 실행되는 특별한 함수

_mint(msg.sender, initialSupply * 10 ** 18)
-> token이 생성되는 함수. 초기 한 번만 할 수 있고 추가발행 안 됨.

추가발행을 원하면 function 추가발행() 이런 식으로 함수를 더 써서 추가발행을 했어야 함.
소각 모델은 burn 함수

이 코드는 한 번 발행되면 수정배포가 불가능함.
수정한다는 사실 자체가 프로젝트에 독으로 작용할 것.
수정을 할 수 없으니 커뮤니티합의만 이뤄지면 컨트랙트 새로 만들고 새로 만든 컨트랙트를 진짜로 인정하면 되는 것. 수정배포가 아니라 다시 배포->커뮤 인정

ERC20(_name, _symbol)
-> erc20 이용해서 이름과 심볼 사용해라

uint256 initialSupply, string memory _name, string memory _symbol
-> 바깥 deploy탭에서 받아온 것. 효과적이라 토큰 발행 여러개도 하기 수월.

0개의 댓글