Svelte로 메타마스크 연결하기

박재훈·2023년 1월 23일
0

Node.js

목록 보기
2/3
post-thumbnail

최근에 Svelte를 공부하게 됐는데, 프론트엔드를 전문적으로 하지 않는 내 입장에서는 React나 Angular에 비해 훨씬 편리한 것 같다.
특히 이더리움 기반 블록체인의 유틸성 스크립트 개발에 있어서는 단순히 Node.js나 Go로 짜는 것보다 좀 더 편리한 면도 있는 것 같다. 아마 최대 장점이라면 프라이빗 키를 하드코딩 할 필요가 없다는 게 아닐까 싶다.

그래서 이 글에서는 SvelteKit과 메타마스크를 이용하여 간단한 조작을 하는 예시를 작성해본다.
크게 두 가지를 해볼 것이다.

  • 다른 계정으로 송금하기
  • 컨트랙트 실행시키기

테스트 네트워크 띄우기

Sepolia나 Goerli를 사용하는 방법도 있겠지만 그런 공식 테스트넷들은 이더 구하기가 은근히 쉽지 않아서... 본 예제에서는 가나슈를 이용해서 테스트 네트워크를 띄우기로 했다.

가나슈를 띄우는 방법은 다음과 같이 CLI와 도커 두 가지가 있으며 명령어 옵션은 여기에서 확인할 수 있다.
(ganache-cli는 deprecated 되었으며 ganache를 이용하여야 한다.)

# CLI
ganache \
    -p=7545 \
    --server.ws=true \
    --chain.chainId=1207 \
    -h=0.0.0.0 \
    -b=5 \
    --wallet.accounts="$PK,900000000000000000000000000"
# docker-compose
version: "3"

services:
  ganache:
    image: trufflesuite/ganache:latest
    restart: always
    container_name: ganache
    ports:
      - "7545:7545"
    env_file:
      - .env
    command:
      - -p=7545
      - -b=5
      - --server.ws=true
      - --chain.chainId=1207
      - --wallet.accounts="$PK,900000000000000000000000000"

이것들의 의미는 대충 7545 포트에 블록타임 5초, 웹소켓 접근이 가능하며 체인ID가 1207인 네트워크에 기본적으로 PK에 해당하는 계정에 900000000 이더를 넣어놓은 상태로 네트워크를 띄우겠다는 뜻이다.
간단하게 할 것이기 때문에 CLI를 이용하기로 했다.

메타마스크에 추가하면 위처럼 된다. 대충 준비는 되었다.

SvelteKit 프로젝트 생성

npm create svelte@latest svelte-metamask
cd svelte-metamask
npm install
npm run dev

위 명령어를 수행하는 도중 몇가지 옵션을 선택할 수 있게 되는데, js/ts 선택에서 나는 ts를 골랐다.

npm run dev로 프론트엔드를 띄우면 localhost:5173으로 접근할 수 있다.
그러면 src/routes/+page.svelte 파일에 있는 내용이 띄워지게 된다.

메타마스크 연결

Node.js 이더리움 라이브러리 중 나는 ethers를 선호해서 ethers를 설치했다.

npm i ethers

Svelte에서 ethers로 메타마스크에 접근하는 방법은 다른 프론트엔드들과 크게 다르진 않다.
Web3Provider를 만들고 window.ethereum으로 초기화 하면 된다.

<script lang="ts">
	import { ethers } from 'ethers';

	let provider: ethers.providers.Web3Provider;
	let signer: ethers.Signer;
	let address: string;

	const connect = async () => {
		provider = new ethers.providers.Web3Provider(window.ethereum);
		await provider.send('eth_requestAccounts', []);
		signer = provider.getSigner();
		address = await signer.getAddress();
	};
</script>

<button on:click={connect}>connect</button>
<p>{address}</p>

이러면 화면에 connect 버튼 하나가 나오게 되고 그걸 누르면 메타마스크에 연결할 수 있다.

위와 같은 팝업이 뜨게 되며 다음을 눌러 진행하면 된다.
연결이 완료되면 address 변수에 현재 연결된 계정의 주소가 들어가서 화면에 출력된다.

송금하기

송금을 하기 위해 금액을 입력받는 변수, 송금할 주소를 입력받는 변수를 선언하고 그에 대한 form을 만들었다. 금액은 wei 단위로 받았으며 그렇기 때문에 문자열이어야 한다.

let wei = ethers.utils.parseEther('1').toString();
let toAddress: string;

const send = async () => {
    if (!/^[0-9]+$/gi.test(wei)) {
        alert(`${wei} must be numeric`);
        return;
    }
    if (!/^(0x)[a-fA-F0-9]{40,40}/gi.test(toAddress)) {
        alert(`${toAddress} is not an ethereum address`);
        return;
    }
  
    // send logic
};

먼저 위와 같은 변수를 선언한다. send 함수는 검증 로직만 넣어놨고 아직 메타마스크를 실행시키는 코드는 안넣었다.

<p><input type="text" bind:value={wei} size="30" style="text-align: right;" /> wei</p>
<p>{ethers.utils.formatEther(wei)} ether</p>
<p>
	<input type="text" bind:value={toAddress} size="50" placeholder="toAddress" />
	<button on:click={send}>send</button>
</p>

form은 위와 같이 만들었다. 접속해보면

위와 같은 form이 나온다. 이제 send 함수를 완성해보겠다.

const send = async () => {
    if (!/^[0-9]+$/gi.test(wei)) {
        alert(`${wei} must be numeric`);
        return;
    }
    if (!/^(0x)[a-fA-F0-9]{40,40}/gi.test(toAddress)) {
        alert(`${toAddress} is not an ethereum address`);
        return;
    }

    const tx = await signer.sendTransaction({
        value: ethers.BigNumber.from(wei),
        to: toAddress
    });
    const receipt = await tx.wait();
    alert(`ether successfully sent! hash: ${receipt.transactionHash}`);
};

트랜잭션을 만든 뒤 아까 생성한 signer를 통해 날리는 아주 간단한 로직이다.
트랜잭션이 성공하면 alert를 통해 해시를 띄운다.

잘 되는 것 같다.

컨트랙트 실행시키기

컨트랙트 실행도 크게 다르지 않다. ABI만 있으면 바로 할 수 있다.

어떤 컨트랙트로 할까 고민하다가 그냥 단순하게 ERC20으로 하기로 했다.
설치는 하드햇으로 하려다가 귀찮아서 그냥 리믹스로...

리믹스에서 JHToken이라는 컨트랙트를 배포하려고 JHToken.sol이라는 솔리디티 파일을 만들고서 컴파일 했다.

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.7;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract JHToken is ERC20 {
    constructor(
        uint256 totalSupply_,
        string memory name_,
        string memory symbol_
    ) ERC20(name_, symbol_) {
        _mint(msg.sender, totalSupply_);
    }
}

JHToken.sol 파일 내용은 위와 같다.

컴파일 했으면 위와 같이 메타데이터 JSON 파일이 생성되는데 여기서 abi 안의 내용이 필요하다.
이걸 따로 빼서 JSON 파일로 만든 뒤 아까 만들던 Svelte 프로젝트에 넣으면 된다.

리믹스에 메타마스크 주입 후 위와 같이 배포 트랜잭션을 날릴 수 있다.

위처럼 Deployed Contracts에 우리가 배포하려는 컨트랙트가 나오면 된다. 리믹스를 통해서도 트랜잭션을 날릴 수 있지만 우리는 Svelte를 통해 컨트랙트에 접근하는게 목적이니 저기서 복사 버튼을 눌러 컨트랙트 주소만 가져오면 된다.

가져온 컨트랙트 주소는 메타마스크에 커스텀 토큰으로 등록해놓으면 된다.

import Erc20ABI from '../abi/erc20.abi.json';

const erc20Address = '0x09f564d2c4917cff06bf4a06c9Cc659Eb9CecBC2';

let erc20: ethers.Contract;
let erc20Symbol: string;

아까 리믹스에서 가져온 ABI 파일을 적당한 위치에 넣어놓은 뒤 위와 같은 코드를 상단에 추가하였다.
컨트랙트 주소는 본인이 가져온 걸 넣으면 된다.

erc20 = new ethers.Contract(erc20Address, Erc20ABI, signer);
erc20Symbol = await erc20.symbol();

connect 함수에는 위 코드를 추가했다. 이걸로 컨트랙트 객체를 초기화한다.

나머지는 이더 송금과 동일하며, ERC20을 보내는 부분만 조금 다르다.

let weiErc20 = ethers.utils.parseEther('1').toString();
let toAddressErc20: string;

const sendErc20 = async () => {
    if (!/^[0-9]+$/gi.test(weiErc20)) {
        alert(`${weiErc20} must be numeric`);
        return;
    }
    if (!/^(0x)[a-fA-F0-9]{40,40}/gi.test(toAddressErc20)) {
        alert(`${toAddressErc20} is not an ethereum address`);
        return;
    }

    const tx = await erc20.transfer(toAddressErc20, weiErc20);
    const receipt = await tx.wait();
    alert(`${erc20Symbol} successfully sent! hash: ${receipt.transactionHash}`);
};
<p><input type="text" bind:value={weiErc20} size="30" style="text-align: right;" /> wei</p>
<p>{ethers.utils.formatEther(weiErc20)} {erc20Symbol}</p>
<p>
	<input type="text" bind:value={toAddressErc20} size="50" placeholder="toAddress" />
	<button on:click={sendErc20}>send {erc20Symbol}</button>
</p>

돌려보면...

잘 된다.

Github

https://github.com/p9595jh/svelte-metamask

profile
코딩 좋아합니다

0개의 댓글