
로컬 이더리움 네트워크를 세팅한다.스마트 컨트랙트 코드에 대해 컴파일, 테스트 등을 해볼 수 있다.스마트 컨트랙트란 블록체인 상에 있는 코드 조각으로 기억하면 된다.블록체인이란 누구나 안전하게 정보를 읽고 쓸 수 있는 공간이다.
스마트 컨트랙트를 작성한다.
- 서버의 코드와 같이 생각하면 된다.
- 작성한
스마트 컨트랙트를 블록체인 상에배포(deploy)한다.
- 전 세계 누구든 우리로부터 허용되었다면
스마트 컨트랙트에접근하고실행시킬 수 있다.
클라이언트 웹사이트를 만든다.
- 일반 유저들이 우리의
스마트 컨트랙트와 쉽게 상호작용 할 수 있도록 한다.
안녕이라고 인사해주는 웹사이트를 만드는 것이다!hardhat이라는 툴을 통해, 로컬 이더리움 네트워크환경을 만들 수 있으며 fake 이더리움, fake 계정등을 이용해 테스트도 진행할 수 있다.서버가 블록체인임을 기억하도록 하자.hardhat을 통해 스마트 컨트랙트를 컴파일하는 것이 가능하다. 로컬 이더리움 네트워크에서 이를 테스트할 수도 있다.hardhat을 이용하기 위해서는 node/npm이 필요하다.my-wave-portal 이라는 작업 디렉토리를 따로 만들어 진행하도록 한다.npm의 설치가 완료되었으면 아래 코드를 입력해 튜토리얼을 진행한다.mkdir my-wave-portal cd my-wave-portal npm init -y npm install --save-dev hardhat
hardhat을 설치 완료한 후 샘플 프로젝트를 받는 작업을 진행한다.npx hardhat

Create a basic sample project를 선택하면 몇 가지를 더 묻는데 모두 Enter키로 넘기면 아래와 같이 설치되는 과정이 나온다.

Project created라는 문구와 함께 샘플 프로젝트가 완성된 것을 확인할 수 있다.
hardhat-waffle과 hardhat-ethers를 추가로 설치해야 한다. 아래의 명령을 입력하면 된다.npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
npx hardhat accounts

hardhat이 생성한 이더리움 주소 이다.테스트에 활용할 수 있다.0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (첫 번째 주소)가 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (두 번째 주소)에게 0.1 ETH를 보낸다.npx hardhat compile
npx hardhat run

scripts, contract, test 디렉토리에 있는 모든 파일들을 지우도록 한다.contract 디렉토리에 WavePortal.sol이라는 이름으로 솔리디티 파일을 하나 만든다.// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "hardhat/console.sol";
contract WavePortal {
constructor() {
console.log("Yo yo, I am a contract and I am smart");
}
}
// SPDX-License-Identifier: UNLICENSED
- 라이센스 (저작권) 에 관한 내용이다.
- Ref. https://devocean.sk.com/opensource/techBoardDetail.do?ID=159339
pragma solidity ^0.8.4;
- 우리의
스마트 컨트랙트가 이용할컴파일러의버전을 명시한다.- 중요 :
hardhat.config.js에 명시된 것과 동일한버전을 사용해야 한다.
import "hardhat/console.sol";
console log에 관한 코드를import한다.hardhat에서는스마트 컨트랙트를 쉽게디버깅할 수 있도록console log를 사용할 수 있는 코드를 제공한다.
contract WavePortal { constructor() { console.log("Yo yo, I am a contract and I am smart"); } }
스마트 컨트랙트의 구현에 대한 내용이다.스마트 컨트랙트(=컨트랙트)는 다른 언어에서의클래스와 상당히 비슷하게 보인다.- 이
컨트랙트를 초기화 하면,constructor(생성자)가 호출되며console을 통해 문장을 출력할 것이다.
스마트 컨트랙트를 작성하였으므로, 아래 세 단계를 거쳐 이를 실행해 볼 수 있다.컨트랙트를 컴파일한다.로컬 블록체인 네트워크에 컴파일 된 컨트랙트를 배포(deploy) 한다.블록체인 네트워크 상의 컨트랙트를 호출하면 console.log()가 실행된다.블록체인에서도 컨트랙트는 네트워크상에 존재하며, 이를 누군가 호출하는 방식으로 동작한다.컨트랙트를 컴파일하고 실행하는 것은 가능하다.Solidity가 블록체인 네트워크, 그리고 이더리움 지갑(wallet)과 어떻게 상호작용 할 수 있는지를 이해하는 것이 중요하다.scripts 디렉토리에 run.js라는 자바스크립트 파일을 만든다.컨트랙트를 실행하기 위해서는 컴파일, 배포, 실행 세 가지를 해야 한다.const main = async () => {
const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
console.log("Contract deployed to:", waveContract.address);
};
const runMain = async () => {
try {
await main();
process.exit(0); // exit Node process without error
} catch (error) {
console.log(error);
process.exit(1); // exit Node process while indicating 'Uncaught Fatal Exception' error
}
// Read more about Node exit ('process.exit(num)') status codes here: https://stackoverflow.com/a/47163396/7974948
};
runMain();
const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
컨트랙트를컴파일하고artifacts/디렉토리에컨트랙트를 다루기 위해 필요한 파일들을 생성한다.
const waveContract = await waveContractFactory.deploy();
deploy()메서드를 통해,hardhat은로컬 이더리움 네트워크를 만든다.- 그러나, 이 때 만들어진
로컬 이더리움 네트워크는스크립트의 실행이 끝나면 삭제된다.- 따라서,
컨트랙트를 실행할 때 마다네트워크를 초기 상태로 다룰 수 있고, 이는디버깅등에 유용하다.
await waveContract.deployed();
컨트랙트가블록체인 네트워크상에 잘배포될 때까지 기다린다.배포가 완료되면,constructor()가 실행된다.
console.log("Contract deployed to:", waveContract.address);
배포가 되면블록체인 네트워크상의컨트랙트의 주소를 받을 수 있다.- 실제
블록체인 네트워크상에는 무수히 많은컨트랙트들이 배포되어 있고,주소를 통해 우리가배포한컨트랙트를 찾아서 사용할 수 있다.
스크립트를 실제로 실행해보자.npx hardhat run scripts/run.js

hre.ethers를 사용하고 있다.HRE에 관한 설명은 아래와 같다.
hre는Hardhat Runtime Environment를 의미하며 일종의객체(object)이다.hardhat이 작업, 테스트 또는 스크립트를 실행할 때 표시하는 모든 기능을 포함한다.- 따라서,
Hardhat은HRE이다.
npx hardhat으로 시작되는 명령을 터미널에서 입력할 때마다, 코드에 지정된 hardhat.config.js를 사용해서 이 hre라는 객체를 즉시 만들 수 있다.const hre = require("hardhat");
컨트랙트에 데이터를 저장할 수 있다.함수로 구현할 수 있다.함수 내에 인사에 대한 정보를 저장하도록 한다.
블록체인 네트워크를클라우드 서버처럼 생각하면 편하다.- 이
서버를 유지하는 전 세계의 수 많은 사람들을 보통채굴자(miner)라고 부른다.- 우리는
블록체인 네트워크의 코드를 실행하기 위해서 이런채굴자들에게 값을 지불하는 것이다.
- 같은 맥락으로,
스마트 컨트랙트는서버상의프로그램 코드라 생각하면 편하다.- 즉, 누구나
서버상에 존재하는프로그램을 실행할 수 있는 것이다.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "hardhat/console.sol";
contract WavePortal {
uint256 totalWaves;
constructor() {
console.log("Yo yo, I am a contract and I am smart");
}
function wave() public {
totalWaves += 1;
console.log("%s has waved!", msg.sender);
}
function getTotalWaves() public view returns (uint256) {
console.log("We have %d total waves!", totalWaves);
return totalWaves;
}
}
uint256 totalWaves;
컨트랙트상에 저장되는상태 변수(state variable)로,블록체인상에 기록된다.- 즉,
컨트랙트와 마찬가지로 한 번 블록체인에 기록되면 영원히 존재한다.
function wave() public { totalWave += 1; console.log("%s has waved!", msg.sender); }
함수를 선언했다.public키워드는 이함수가 누구나 호출할 수 있음을 의미한다.totalWaves += 1;는 산술 연산이 다른 언어들과 크게 다르지 않음을 보여준다.- 주목해야 할 점은
msg.sender에 관한 것이다.
- 여기서
msg.sender는 이컨트랙트를 호출한 사람의지갑 주소(wallet address)이며,빌트인 인증(built-in authentication)과도 같다.
스마트 컨트랙트를 호출할 때에도유효한 지갑이 필요함 반드시 필요하기 때문에,
우리는 이를 통해 누가 이컨트랙트를 호출했는지 알 수 있다.- 이를 응용하면, 특정인만
컨트랙트를 호출하도록 할 수 있다.
function getTotalWaves() public view returns (uint256) { console.log("We have %d total waves!", totalWaves); return totalWaves; }
view는 이함수에서state variable을읽기만 하고쓰기는 하지 않음을 명시한다.return값을 지정하는 방식이 조금 특이하다.
함수의바디가 시작되기 직전에returns라는 키워드와 그 뒤에(_type_)형태로 자료형을 명시한다.
함수들을 호출하기 위해 run.js를 다시 수정할 필요가 있다.public 키워드를 사용한 함수들은 블록체인에 배포된 뒤 사용할 수 있게 된다.public API endpoint를 생각하면 편하다.const main = async () => {
// getSigners()
const [owner, randomPerson] = await hre.ethers.getSigners();
const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
// logs
console.log("Contract deployed to:", waveContract.address);
console.log("Contract deployed by:", owner.address);
// counts the wave using getTotalWaves() from our contract
let waveCount = await waveContract.getTotalWaves();
// call wave()
let waveTxn = await waveContract.wave();
await waveTxn.wait();
// grab the waveCount one more to see if it changed
waveCount = await waveContract.getTotalWaves();
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
const [owner, randomPerson] = await hre.ethers.getSigners();
블록체인에 어떤 것이든배포하기 위해서는 유효한지갑 주소가 필요하다.hardhat은 백그라운드에서 이것을 알아서 처리해주지만, 이 코드에서는 명시적으로지갑 주소를 가져오고 있다.getSigners()함수는npx hardhat accounts로 확인했던지갑들을 반환한다.
console.log("Contract deployed by:", owner.address);
컨트랙트를배포한 사람이 누구인지 확인할 수 있다.
let waveCount = await waveContract.getTotalWaves(); let waveTxn = await waveContract.wave(); await waveTxn.wait(); waveCount = await waveContract.getTotalWaves();
- 보통
API들을 사용하는 것과 같이, 여기서도함수들을 하나 하나 호출해야 한다.컨트랙트상의totalWave가 바뀌는지 확인하는 코드이다.
컨트랙트를 실행하면 다음과 같은 결과가 나온다.npx hardhat run scripts/run.js

wave() 함수에서 인사 받는 사람으로 msg.sender를 했기 때문에, 나 자신에게 인사를 한 꼴이 된다.현재 컨트랙트에서 수행하는 동작은 아래와 같다.
wave()함수를 호출한다. ( function call )상태 변수(state variable)를 수정한다. ( write )상태 변수의 새로운 값을 읽는다. ( read )
튜토리얼을 진행함에 따라, React앱을 만들어서 동작하게 할 것이다.
wave() 함수로 인사를 하는 사람도 나이고 인사를 받는 사람도 나인 점이 좀 어색하다.다른 유저가 나에게 인사하도록 코드를 조금 수정한다.randomPerson을 활용하면 아래와 같이 코드를 수정할 수 있다. // counts the wave using getTotalWaves() from our contract
let waveCount = await waveContract.getTotalWaves();
// call wave(), but the msg.sender is __randomPerson__ now
let waveTxn = await waveContract.connect(randomPerson).wave();
await waveTxn.wait();
// grab the waveCount one more to see if it changed
waveCount = await waveContract.getTotalWaves();
wave()함수를 호출하는 msg.sender가 randomPerson이 되었으므로, 실행하면 아래와 같은 결과가 나온다.
다른 주소가 나에게 인사하고 있음을 확인할 수 있다.0x04 ~ 0x0C 까지의 내용은 아래와 같다.스마트 컨트랙트 작성스크립트 작성컨트랙트는 블록체인 네트워크로부터 호출된다고 했으므로, 이미 로컬 블록체인 네트워크를 구축한 것이라 생각할 수 있다.0x07에서 짧게 언급한 것처럼 스크립트를 실행할 때 사용한 네트워크는 스크립트 실행이 끝남과 동시에 삭제된다.로컬 블록체인 네트워크를 구축할 필요가 있다.hardhat에서는 이를 간단히 구현할 수 있도록 해준다.터미널 윈도우에서 실행한다.npx hardhat node

hardhat이 로컬 블록체인 네트워크를 돌리고 있는 것을 확인할 수 있다.이더리움을 설정해 두었다.블록체인 네트워크는 블록이 하나도 없는 상태이다.새로운 블록을 생성하고 우리의 컨트랙트를 그 블록 위에 기록할 것이다.script/ 디렉토리에 deploy.js라는 파일을 만들고 아래와 같이 코딩한다.const main = async () => {
// get deployer
const [deployer] = await hre.ethers.getSigners();
// get balance of deployer
const accountBalance = await deployer.getBalance();
console.log("Deploying contracts with account: ", deployer.address);
console.log("Account balance: ", accountBalance.toString());
const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
console.log("WavePortal address: ", waveContract.address);
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
로컬 블록체인 네트워크에 컨트랙트를 배포할 수 있다.npx hardhat run scripts/deploy.js --network localhost
npx hardhat node로 서버를 돌리고 있는 터미널이 아닌 다른 터미널 창에서 명령어를 입력해야 한다.
배포 하는 스크립트를 4 번 실행한 결과, 블록이 4개까지 만들어진 것을 확인할 수 있다.
- 앞서 이용한
run.js도 생각해보면..코드내에deploy()를 쓰고 있기 때문에 아래와 같이로컬 블록체인 네트워크에서 실행하는 것이 가능하다.npx hardhat run ./scripts/run.js --network localhost
터미널이 아니라 웹 상에서 Web3를 이용하여, 우리가 만든 컨트랙트를 활용해보도록 한다.Replit은 브라우저 기반 IDE로, 웹 앱을 쉽게 빌드하고 배포할 수 있도록 해준다.Replit을 이용하기 위해서는 계정을 만들고, 로그인 하면 된다.fork해서 개발을 이어나가면 된다. (링크)
Replit에서는.js대신.jsx확장자를 사용한다.
이더리움 지갑으로 Metamask를 활용한다.지갑을 통해서 컨트랙트 상의 함수 호출 등을 할 수 있다.로그인하는 것처럼 활용할 수 있다.0x12부터는 npx hardhat node로 구축했던 로컬 블록체인 네트워크를 사용하지 않는다.0x12부터는 실제 블록체인을 이용해서 튜토리얼을 진행한다.블록체인 네트워크에 컨트랙트를 편리하게 배포하기 위해 Alchemy 를 이용하면 좋다.이더리움 블록체인 상에서 행하는 모든 행위들은 모두 트랜잭션이라 불린다.이더리움을 전송, 컨트랙트 내의 변수 업데이트wave()함수를 호출하고 내부에서 totalWaves += 1;을 수행하면, 이것이 트랜잭션인 것이다.스마트 컨트랙트를 배포하는 것 자체도 트랜잭션이다.블록체인은 소유자가 없고, 네트워크에 참여하는 모든 채굴자가 블록체인 원장을 갖고 있어야 함을 명심하자.컨트랙트를 블록체인 네트워크상에 올리기 위해서는 다음의 과정을 거쳐야 한다.
컨트랙트 코드에 대한트랜잭션을채굴자들이블록으로 만들 수 있도록 브로드캐스팅한다.- 생성된
블록을 브로드캐스팅하여네트워크참여자 모두에게 전송한다.
이더리움 메인넷은 실제 돈을 사용해야 하고, 실습을 하는 데 그만한 돈을 지불하는 것은 크게 가치가 없다.가짜 이더리움을 사용하는 테스트넷에서 실습하는 것이 효과적이다.테스트넷도 블록체인 네트워크이기 때문에 블록의 생성 과정 등 이더리움 메인넷이 갖는 특성과 동일하므로 유의해야 한다.테스트넷에서 다음을 실습할 수 있다.
- 우리의
트랜잭션을브로드캐스트한다.채굴자들에 의해트랜잭션이 선택될 떄까지 기다린다.채굴과정을 거친다.- 생성된
블록이블록체인 네트워크상의 모든채굴자들에게브로드캐스트되기를 기다린다.
테스트넷으로 Rinkeby를 이용한다.hardhat.config.js파일을 수정해야 한다.require("@nomiclabs/hardhat-waffle");
module.exports = {
solidity: "0.8.4",
networks: {
rinkeby: {
url: "YOUR_ALCHEMY_API_URL",
accounts: ["YOUR_PRIVATE_RINKEBY_ACCOUNT_KEY"]
},
},
};
Alchemy API URL과 본인 지갑 계정의 비밀키를 넣으면 된다.비밀키는 마치 블록체인에 로그인할 때 사용되는 비밀번호 처럼 사용된다.트랜잭션을 채굴자에게 보낼 때, 전송자가 본인임을 인증하는 전자 서명을 위해 비밀키를 필요로 한다.비밀키를 사용하지 않으면 트랜잭션의 유효성을 검사할 수 없기 때문에, 필요하다.Rinkeby 네트워크로 배포하는 것은 아래의 명령어로 할 수 있다.npx hardhat run scripts/deploy.js ---network rinkeby

WavePortal address: 뒤에 있는 내용이 블록 내의 컨트랙트주소 이다.블록체인 네트워크 상에 우리가 만든 스마트 컨트랙트를 올린 것이다.웹 사이트가 블록체인과 상호작용하기 위해서는, 어쨋든 우리의 지갑을 연결해야 한다.지갑을 연결하고 나면 스마트 컨트랙트 호출 권한을 받을 수 있다.Replit으로 가서 src/디렉토리 밑에 있는 App.jsx를 찾는다.Metamask에 로그인 되어 있다면, 윈도우에 ethereum이라는 특별한 객체가 자동으로 삽입된다.ethereum 이라는 객체가 존재하는지 부터 확인해보자.App.jsx를 수정한다.import React, { useEffect } from "react";
import "./App.css";
const App = () => {
const checkIfWalletIsConnected = () => {
/*
* First make sure we have access to window.ethereum
*/
const { ethereum } = window;
if (!ethereum) {
console.log("Make sure you have metamask!");
} else {
console.log("We have the ethereum object", ethereum);
}
}
/*
* This runs our function when the page loads.
*/
useEffect(() => {
checkIfWalletIsConnected();
}, [])
return (
<div className="mainContainer">
<div className="dataContainer">
<div className="header">
👋 Hey there!
</div>
<div className="bio">
I am farza and I worked on self-driving cars so that's pretty cool right? Connect your Ethereum wallet and wave at me!
</div>
<button className="waveButton" onClick={null}>
Wave at Me
</button>
</div>
</div>
);
}
export default App
Metamask가 있는 경우 아래와 같이 콘솔창에서 확인할 수 있다.

Metamask가 없는 경우 아래와 같이 콘솔창에서 확인할 수 있다.(시크릿탭 켜고 들어감)

지갑이 연결되었는지 여부는 확인했으니 유저의 지갑 계정에 접근할 수 있는지를 확인해야 한다.Metamask는 반드시 우리가 승인한 웹 사이트에서만 우리의 지갑 계정 정보를 제공한다.App.jsx를 아래의 코드로 업데이트한다.import React, { useEffect, useState } from "react";
import "./App.css";
const App = () => {
/*
* Just a state variable we use to store our user's public wallet.
*/
const [currentAccount, setCurrentAccount] = useState("");
const checkIfWalletIsConnected = async () => {
try {
const { ethereum } = window;
if (!ethereum) {
console.log("Make sure you have metamask!");
return;
} else {
console.log("We have the ethereum object", ethereum);
}
/*
* Check if we're authorized to access the user's wallet
*/
const accounts = await ethereum.request({ method: "eth_accounts" });
if (accounts.length !== 0) {
const account = accounts[0];
console.log("Found an authorized account:", account);
setCurrentAccount(account)
} else {
console.log("No authorized account found")
}
} catch (error) {
console.log(error);
}
}
useEffect(() => {
checkIfWalletIsConnected();
}, [])
return (
<div className="mainContainer">
<div className="dataContainer">
<div className="header">
👋 Hey there!
</div>
<div className="bio">
I am farza and I worked on self-driving cars so that's pretty cool right? Connect your Ethereum wallet and wave at me!
</div>
<button className="waveButton" onClick={null}>
Wave at Me
</button>
</div>
</div>
);
}
export default App
eth_accounts라는 특별한 메서드를 사용하여, 지갑에 있는 계정들 중 하나라도 접근할 수 있는지 확인할 수 있다.지갑에 여러 개의 계정을 가질 수 있으므로, 여기서는 첫 번째 것을 사용하기로 한다.( 링크 )