2. Smart contract deployment, transaction execution

이전 튜토리얼인 [Development environment setup and smart contract writing / debugging]에서 간단한 메시지 컨트랙트를 작성해보고 테스트해 보았습니다. 이번 튜토리얼에서는 메시지 컨트랙트를 이더리움 환경에 배포해보고 Call을 통해 트랜잭션을 실행해 보도록 하겠습니다.

📘

작성한 스마트 컨트랙트가 없나요?

아래 링크를 통해 Development environment setup and smart contract writing / debugging 튜토리얼을 진행할 수 있습니다. 이를 통해 간단한 메시지 컨트랙트를 작성할 수 있으니 스마트 컨트랙트를 만들어 보세요!

Development environment setup and smart contract writing / debugging Tutorial


스마트 컨트랙트 배포

작성한 스마트 컨트랙트를 블록체인 환경에 배포하기 위해서는 EOA를 이용해 블록체인 네트워크에 스마트 컨트랙트 배포 트랜잭션을 실행해야 합니다. 즉, 배포를 위해서는 아래 3가지가 준비되어야 합니다.

  1. 배포할 블록체인 네트워크의 노드 연결
    • 스마트 컨트랙트 배포 트랜잭션을 검증하고 실행하기 위해 사용할 블록체인 네트워크 노드를 설정합니다.
  2. 배포 트랜잭션을 실행할 EOA
    • 스마트 컨트랙트 배포 트랜잭션을 실행할 계정으로 배포에 필요한 ABI, 바이트코드 등을 이용해 트랜잭션을 만들고 실행합니다.
  3. 배포 트랜잭션 수수료
    • 블록체인 네트워크에서 트랜잭션을 실행하기 위해 수수료인 가스비를 지불해야 합니다.

본 튜토리얼에서는 이더리움 Holesky 네트워크에 스마트 컨트랙트를 배포하고 호출해보는 과정을 진행해 보겠습니다!


노드 연결하기

먼저 스마트 컨트랙트 배포 트랜잭션을 전송하기 위한 노드를 준비해야 합니다. 직접 노드 클라이언트를 실행하여 노드를 운영하거나, 노드 Provider 서비스를 이용하여 노드 Endpoint를 획득할 수 있습니다.

가장 안정적인 Node Provider Service 중 하나인 Nodit을 이용해 이더리움 Holesky의 노드를 쉽고 간편하게 연결해 보도록 하겠습니다. 아래 링크를 클릭해 Nodit 콘솔 회원가입 및 온보딩 방법을 확인해 보세요!


회원가입 및 온보딩을 완료했다면 콘솔에서 아래 사진과 같이 연결할 수 있는 노드를 확인할 수 있고 [HTTPS Endpoint] 버튼을 클릭해 특정 노드를 연결할 수 있습니다.

스크린샷 2024-09-11 오후 1.46.21.png

만약 연결하려는 노드가 없다면 좌측 메뉴의 [Nodes] 탭을 클릭해 연결할 수 있는 노드가 무엇이 있는지 확인해 보세요!

스크린샷 2024-09-11 오후 2.32.49.png

트랜잭션을 실행할 EOA 생성

Metamask를 이용한 EOA 생성 방법

Metamask를 이용하기 위해서는 먼저 Metamask를 설치해야 합니다. 아래 링크를 클릭하여 설치 방법을 확인할 수 있습니다.


설치가 완료되었다면 Nodit 콘솔에 로그인하여 아래 사진과 같이 프로젝트에 접근합니다.

스크린샷 2024-09-11 오후 2.49.39.png

[프로젝트 Overview] 화면에서 [Connected Nodes] 영역에서 이더리움 Holesky 박스의 [Metamask] 버튼을 클릭하여 손쉽게 Metamask와 블록체인 네트워크를 연결할 수 있으며 Wallet 애플리케이션이 아니라 직접적으로 노드와 연결하고 싶다면 [HTTPS Endpoint] 버튼을 클릭하여 노드를 연결할 수 있습니다.

스크린샷 2024-09-10 오후 1.53.22.png

만약 Nodit 콘솔에서 이더리움 Holesky 노드를 연결하지 않은 경우, 좌측 메뉴 중 [Nodes] 탭을 클릭하여 추가할 수 있습니다.


라이브러리를 이용한 EOA 생성 방법

Metamask와 같이 Wallet 어플리케이션을 사용하지 않고 라이브러리를 이용해 EOA를 생성하는 방법도 있습니다. 아래 명령어를 터미널에 입력하여 ethers.js 라이브러리를 설치합니다.

$ npm install ethers

프로젝트 디렉토리에서 TS 파일을 생성한 후 다음과 같이 코드를 작성하고 실행하면 Wallet 객체를 반환받을 수 있습니다.

// createEoa.ts

import { ethers } from "ethers";
const myWallet = ethers.Wallet.createRandom();
console.log(myWallet); 
$ ts-node createEoa.ts
//response

Wallet {
  _isSigner: true,
  _signingKey: [Function (anonymous)],
  address: '0xc675Dda1d2d545E20AAC8640f2BaA402D8a05887',
  _mnemonic: [Function (anonymous)],
  provider: null
}

Metamask로 생성한 계정에서 니모닉을 추출해 이용할 수도 있습니다! 아래 사진에 따라 니모닉을 추출할 수 있습니다.

❗️

니모닉과 Private Key는 절대 유출되지 않아야 합니다!

니모닉이나 Private Key가 유출될 경우 해당 계정이 보유한 모든 자산의 권한을 탈취당할 수 있으므로 절대 유출되지 않도록 주의가 필요합니다. 안전하게 튜토리얼을 진행하기 위해 튜토리얼용 EOA를 새로 생성해 진행하는 것을 권장 드립니다.


  1. Metamask를 켜고 우측 상단의 메뉴를 클릭합니다.
스크린샷 2024-09-11 오전 11.25.44.png
  1. 메뉴 중 Settings을 클릭합니다.
스크린샷 2024-09-11 오전 11.26.30.png
  1. Security & privacy 메뉴를 클릭합니다.
스크린샷 2024-09-11 오전 11.26.54.png
  1. Security 탭에서 [Reveal Secret Recovery Phrase] 버튼을 클릭해 니모닉을 추출합니다.
스크린샷 2024-09-11 오전 11.27.28.png

이후 추출한 니모닉을 다음과 같이 코드에 첨부하여 Metamask 계정을 코드에 불러올 수 있습니다.

const mnemonic = "your_mnemonic_words"
const walletFromMnemonic = ethers.Wallet.fromMnemonic(mnemonic);

트랜잭션 수수료 받기

이제 블록체인 노드와 EOA가 준비되었습니다. 마지막으로 트랜잭션 실행 시 필요한 수수료인 가스비만 있으면 트랜잭션을 실행할 수 있습니다. 이러한 기능을 Faucet이라고 부르며 인터넷에서 “Ethereum Holesky faucet”을 검색해 다양한 Faucet 제공자의 서비스를 이용할 수 있습니다.


스마트 컨트랙트 배포

스마트 컨트랙트를 배포하기 위한 모든 준비가 완료되었습니다! 이제 스마트 컨트랙트를 이더리움 Holesky에 배포해 보도록 하겠습니다.

Ethers.js 라이브러리를 이용해 EOA를 생성한 뒤 해당 EOA를 이용해 스마트 컨트랙트를 배포하는 코드를 작성해 보도록 하겠습니다. 우선 스마트 컨트랙트를 컴파일하고 반환받은 ABI와 bytecode를 별도의 파일로 저장해 관리합니다.

// data.ts

export const abi = [
  {
    inputs: [],
    stateMutability: "nonpayable",
    type: "constructor",
  },
...
];

export const bytecode = "your_bytecode";


Ethers.js를 이용해 스마트 컨트랙트를 배포할 코드를 작성합니다.

//deployContract.ts

import { ethers } from "ethers";
import { abi, bytecode } from "./data";

const provider = new ethers.providers.JsonRpcProvider(
  "https://ethereum-holesky.nodit.io/your_node_api_key"
);

const mnemonic = "your_memonic_words";
const accountFromMnemonic = ethers.Wallet.fromMnemonic(mnemonic);
const connectWalletToProvider = accountFromMnemonic.connect(provider);
const contractFactory = new ethers.ContractFactory(
  abi,
  bytecode,
  connectWalletToProvider
);

(async () => {
  try {
    const deployingContract = await contractFactory.deploy();
    await deployingContract.deployed();
    console.log(deployingContract.address);
  } catch (error) {
    console.error(error);
  }
})();


코드의 자세한 내용은 다음과 같습니다.

Nodit이 제공하는 이더리움 Holesky 노드를 HTTP Endpoint를 이용하여 연결할 수 있는 provider 인스턴스를 생성합니다.

const provider = new ethers.providers.JsonRpcProvider(
  "https://ethereum-holesky.nodit.io/your_node_api_key"
);

니모닉 단어들을 이용해 Wallet 인스턴스를 생성하고 이를 provider 인스턴스를 이용해 이더리움 Holesky 노드에 연결하여 사용할 수 있도록 합니다.

const mnemonic = "your_memonic_words";
const accountFromMnemonic = ethers.Wallet.fromMnemonic(mnemonic);
const connectWalletToProvider = accountFromMnemonic.connect(provider);

ABI와 bytecode 그리고 노드와 연결된 지갑을 이용해 컨트랙트 배포 트랜잭션에 특화된 ContractFactory 인스턴스를 생성합니다.

const contractFactory = new ethers.ContractFactory(
  abi,
  bytecode,
  connectWalletToProvider
);

즉시실행함수를 이용하여 컨트랙트 배포 함수를 작성합니다. contractFactory의 deploy 메서드를 이용해 컨트랙트를 배포할 수 있으며 deployed 메서드를 이용해 블록체인 네트워크 상에서 배포가 완료될 때까지 대기합니다. console.log를 이용해 배포가 완료된 후 컨트랙트의 주소를 확인할 수 있습니다.

(async () => {
  try {
    const deployingContract = await contractFactory.deploy();
    await deployingContract.deployed();
    console.log(deployingContract.address);
  } catch (error) {
    console.error(error);
  }
})();


터미널에 아래 명령어를 실행하여 컨트랙트를 배포해 보세요! 그리고 Etherscan에서 컨트랙트 주소를 검색해 보세요!

$ts-node deployContract.ts

스마트 컨트랙트 호출

배포된 스마트 컨트랙트의 함수를 호출해 결과가 테스트에서 진행한 내용과 동일한지 확인해 보도록 하겠습니다. 배포한 컨트랙트에 작성된 함수는 다음과 같습니다.

  1. getMessage
    • 현재 메시지를 조회한다.
  2. setMessage
    • 현재 메시지를 입력한 값으로 변경한다.
  3. initMessage
    • 현재 메시지를 빈 string 문자열로 초기화한다.

배포 시 constructor 함수에서 “Hello World! This is Nodit!” 이라는 문자열을 현재 메시지로 설정했기 때문에 getMessage 함수를 호출했을 때 동일한 메시지가 반환되어야 합니다.


getMessage 호출

getMessage를 호출하는 전체 코드는 다음과 같습니다.

//getMessage.ts

import { ethers } from "ethers";
import { abi, bytecode } from "./data";

const provider = new ethers.providers.JsonRpcProvider(
  "https://ethereum-holesky.nodit.io/your_nodit_api_key"
);
const contractAddress = "your_contract_address";

(async () => {
  try {
    const contract = new ethers.Contract(contractAddress, abi, provider);
    const getMessage = await contract.getMessage();
    console.log(getMessage);
  } catch (error) {
    console.error(error);
  }
})();

컨트랙트의 주소와 abi 그리고 노드와의 연결을 위해 provider 인스턴스를 이용해 새로운 Contract 인스턴스를 생성합니다.

const contract = new ethers.Contract(contractAddress, abi, provider);

Contract 인스턴스는 abi와 컨트랙트 주소로 특정되어 있기 때문에 해당 컨트랙트에 작성된 함수를 특정할 수 있습니다. 스마트 컨트랙트에 작성된 함수와 동일한 이름으로 원하는 함수를 호출할 수 있습니다.

const getMessage = await contract.getMessage();
console.log(getMessage);

이제 해당 코드를 실행해 결과를 확인해 보세요! constructor 함수에 입력한 메시지와 동일한 메시지를 확인할 수 있습니다!

$ ts-node getMessage.ts
Hello World! This is Nodit!

setMessage 호출

setMessage 함수를 호출하는 전체 코드 역시 getMessage 함수 호출 코드와 유사하게 작성해 실행할 수 있습니다.

//setMessage.ts

import { ethers } from "ethers";
import { abi, bytecode } from "./data";

const provider = new ethers.providers.JsonRpcProvider(
  "https://ethereum-holesky.nodit.io/your_nodit_api_key"
);
const contractAddress = "your_contract_address";

const mnemonic =
  "your_mnemonic_words";
const accountFromMnemonic = ethers.Wallet.fromMnemonic(mnemonic);
const connectWalletToProvider = accountFromMnemonic.connect(provider);

(async () => {
  try {
    const contract = new ethers.Contract(
      contractAddress,
      abi,
      connectWalletToProvider
    );
    const setMessage = await contract.setMessage(
      "Nodit is one of the best Node Provider Service in the world!"
    );
    await setMessage.wait();
  } catch (error) {
    console.error(error);
  }
})();


앞서 설명한 것과 같이 노드를 연결할 수 있는 provider 인스턴스를 생성하고 이를 이용해 Wallet 인스턴스를 노드에 연결해 이용할 수 있게 합니다.

const provider = new ethers.providers.JsonRpcProvider(
  "https://ethereum-holesky.nodit.io/your_nodit_api_key"
);
const contractAddress = "your_contract_address";

const mnemonic =
  "your_mnemonic_words";
const accountFromMnemonic = ethers.Wallet.fromMnemonic(mnemonic);
const connectWalletToProvider = accountFromMnemonic.connect(provider);

getMessage와 같이 Contract 인스턴스를 생성하나 인자로 provider 인스턴스가 아닌 provider가 연결된 Wallet 인스턴스를 입력합니다.

const contract = new ethers.Contract(
  contractAddress,
  abi,
  connectWalletToProvider
);

인자로 변경할 메시지를 입력하고 Contract 인스턴스를 이용해 setMessage를 호출합니다. 그리고 트랜잭션 실행 후 트랜잭션이 처리될 때 까지 대기합니다.

const setMessage = await contract.setMessage(
  "Nodit is one of the best Node Provider Service in the world!"
);
await setMessage.wait();


코드를 실행해 결과를 확인해 보세요! setMessage 트랜잭션에 대한 정보를 확인할 수 있습니다.

ts-node setMessage.ts

그리고 다시 getMessage를 호출해 메시지를 확인해 보세요. 메시지가 변경된게 보이시나요?

Nodit is one of the best Node Provider Service in the world!

initMessage 호출

마지막으로 initMessage 함수를 호출해 현재 저장된 문자열을 초기화 합니다. initMessage의 경우, setMessage와 마찬가지로 트랜잭션을 이용해 상태를 변경하기 때문에 동일한 코드로 실행할 수 있습니다.

// initMessage.ts

import { ethers } from "ethers";
import { abi, bytecode } from "./data";

const provider = new ethers.providers.JsonRpcProvider(
  "https://ethereum-holesky.nodit.io/your_nodit_api_key"
);
const contractAddress = "your_contract_address";

const mnemonic =
  "your_mnemonic_words";
const accountFromMnemonic = ethers.Wallet.fromMnemonic(mnemonic);
const connectWalletToProvider = accountFromMnemonic.connect(provider);

(async () => {
  try {
    const contract = new ethers.Contract(
      contractAddress,
      abi,
      connectWalletToProvider
    );
    const initMessage = await contract.initMessage();
    await initMessage.wait();
  } catch (error) {
    console.error(error);
  }
})();

위와 같이 작성한 후 파일을 실행해 메시지를 초기화 해 보세요! 그리고 getMessage를 이용해 값을 확인해 보세요!


지금까지 간단한 컨트랙트를 작성하고 이를 블록체인 네트워크에 배포하고 실제 호출해 보는 시간을 가져보았습니다. 블록체인에서 스마트 컨트랙트가 어떻게 동작하고 사용할 수 있는지 이해가 되셨나요?

다음 튜토리얼에서는 Nodit에서 제공하는 API를 이용해 이번 튜토리얼을 진행하며 발생한 데이터를 조회해 확인해 보는 방법을 알아보도록 하겠습니다!