일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 블록 만들기
- useContext
- buffer.from
- 라우트 매개변수
- css기초
- nodejs파일업로드
- mysql wsl
- node.js path
- express실행
- 라우터와 미들웨어
- cookie-parser 만들어보기
- next 매개변수
- OAuth 카카오
- 세션으로 로그인 구현
- JWT 하드코딩
- express session
- javascript기초
- FormData()
- JWT 로그인 기능 구현
- 아이디 중복체크기능
- 시퀄라이즈 기본설정
- express.static
- express router
- JWT 만들어보기
- 비동기파일업로드
- Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
- 라우터미들웨어 분리
- useEffect clean up
- 라우터 분리
- ws 라이브러리
- Today
- Total
즐코
ETHER 전송을 위한 스마트 컨트랙트 작성 (payable) 본문
Payable
솔리디티만이 가지고 있는 특이한 키워드인 payable 에 대해서 알아보기 위해 사과를 이더(ether)로 사고 환불할 수 있는 스마트 컨트랙트를 작성해보려고 한다.
payable을 직역하면 "지불가능한, 지불해야하는" 이란 뜻이다.
솔리디티 언어 자체가 코인이나 토큰을 다루는 언어이기 때문에 이 payable 이란 키워드가 필요한 것이다.
따라서 스마트 컨트랙트 내에서 payable 키워드를 붙인 함수에서만 이더 전송이 가능하다.
코드를 보면서 이해하는게 더 편하므로 사과를 이더를 주고 사는 스마트 컨트랙트를 작성해본다.
늘 하듯이 편하게 가나쉬 네트워크 + 트러플 조합으로 세팅해준다.
// contracts/AppleShop.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract AppleShop{
mapping (address => uint) myApple;
function buyApple() public payable {
myApple[msg.sender] += 1;
}
function refundApple(uint _appleQty, uint _applePrice) public payable {
uint refund = _appleQty * _applePrice;
myApple[msg.sender] -= _appleQty;
payable(msg.sender).transfer(refund); // 이더 전송 파트!
}
function getApple() public view returns(uint) {
return myApple[msg.sender];
}
}
위의 코드를 보면 알겠지만 buyApple, refundApple 두 함수에만 payable 키워드가 붙어있다.
사과를 사고, 환불하는 두 함수를 실행시킴으로써 트랜잭션이 발생하는데, 이 payable 키워드로 인해 트랜잭션 발생시 CA쪽으로 이더를 전송할 수 있게 된다. payable 이 있어야지만 CA (컨트랙트 계정, 주소)가 이더를 받아서 보관해둘 수 있다.
EOA만 잔고를 가지고 있다고 생각하면 이해하기 어렵다. CA도 하나의 계정, 주소이기 때문에 이더를 가지고 있을 수 있다.
프론트 단에서 메타마스크를 통하여 이 사과를 사고 환불하는 트랜잭션을 일으켜 보자. 프론트는 리액트로 구성하였다.
우선 메타마스크에 web3 요청을 날리기 위해서 useWeb3라는 커스텀 훅 코드부터 작성한다.
// appleshop/src/hooks/useWeb3.js
import Web3 from "web3/dist/web3.min.js";
import { useState, useEffect } from "react";
const useWeb3 = () => {
const [web3, setWeb3] = useState(null);
const [account, setAccount] = useState(null);
const getReqAcct = async () => {
if(!window.ethereum) return;
const [acct] = await window.ethereum.request({
method: "eth_requestAccounts",
});
return acct;
};
useEffect(()=>{
(async () => {
const acct = await getReqAcct();
setAccount(acct);
const web3 = new Web3(window.ethereum);
setWeb3(web3);
})()
},[])
return [web3, account];
}
App 컴포넌트에서 useWeb3 훅으로 web3, account를 받아 AppleShop 컴포넌트에게 props 전달
// appleshop/src/App.js
import useWeb3 from "./hooks/useWeb3";
import AppleShop from "./components/AppleShop";
function App() {
const [web3, account] = useWeb3();
if (!account) return <>메타마스크 연결 이후 사용해주세요</>;
return (
<div>
<h2>사과 사실?</h2>
<AppleShop web3={web3} account={account} />
</div>
);
}
export default App;
truffle 디렉토리에서 AppleShop.json 빌드파일을 복사해와서 src 폴더 상에 저장해준다.
// appleshop/src/components/AppleShop.jsx
import { useEffect, useState } from "react";
import AppleShopContract from "../contracts/AppleShop.json";
const AppleShop = ({ web3, account }) => {
const [deployed, setDeployed] = useState(null);
const [apple, setApple] = useState(0);
const [balance, setBalance] = useState(0);
const [input, setInput] = useState(0);
const buy = async () => {
await deployed.methods.buyApple().send({
from: account, // 현재 연결된 EOA
to: deployed._addres, // 배포된 컨트랙트의 CA
value: web3.utils.toWei("1", "ether") // wei단위로 보내줘야해서,,
});
};
const refund = async () => {
if(input > apple || input <= 0) return;
// 환불하려는 사과개수가 가지고 있는 사과보다 많거나 0 또는 음수일경우 실행되지 않는다.
const eth = web3.utils.toWei("1", "ether"); // 사과 한개당 1이더로 환불
await deployed.methods.refundApple(input, eth).send({
from: account,
to: deployed._address,
});
};
const getApple = async () => {
if (!deployed) return;
const appleQty = await deployed.methods.getApple().call();
setApple(appleQty);
};
const getBalance = async () => {
const bal = await web3.eth.getBalance(account);
const balToEth = await web3.utils.fromWei(bal, "ether");
setBalance(balToEth);
};
const getInput = (e) => {
setInput(e.target.value);
};
// 아래서 배포된 컨트랙트로 deployed 상태 업데이트 시 아래 코드 실행
useEffect(()=>{
getApple(); // 컨트랙트 상태변수인 myApple 가져오기
getBalance(); // 연결된 계정 잔고 가져오기
},[deployed]);
useEffect(() => {
(async () => {
// 배포된 컨트랙트 가져오기
const abi = AppleShopContract.abi;
const networkId = await web3.eth.net.getId();
const ca = AppleShopContract.networks[networkId].address;
const deployedCont = await new web3.eth.Contract(abi, ca);
setDeployed(deployedCont);
})();
},[]);
return (
<div>
<h3>내 연결 계정 : {account}</h3>
<h3>내 잔고 : {balance} ETH</h3>
<h4>사과 가격 : 1 ETH</h4>
<h4>내가 가진 사과 : {apple} </h4>
<button onClick={buy}>구매하기</button>
<div>
사과 판매 가격 : {apple} * 1 ETH = 총 {apple * 1} ETH
</div>
<input type="number" value={input} onChange={getInput} />
<button onClick={refund}>환불</button>
</div>
);
}
export default AppleShop;
구매하기 버튼 클릭시
배포된 컨트랙트 인스턴스에 접근하여 buyApple() 함수를 실행시키는 트랜잭션이 발생된다.
이때, send 메소드 상에는 늘 하던대로 트랜잭션 객체를 넣어주는데, 자세히 말하면 아래와 같다.
- from : 현재 계정주소(EOA)
- to : 배포된 컨트랙트 (현재 실행시키려는 컨트랙트)의 CA
- value : 얼마만큼의 이더를 전송할지 작
환불 버튼 클릭시
환불할 사과 개수를 프론트 단에서 받아 이 사과 개수와 사과 한개당 환불 가격을 인자로 넣어주었다.
이때, send 메소드에 들어가는 from, to는 위의 buyApple() 과 같으나, value는 생략이 가능하다.
왜냐하면 환불 금액을 결정하는 코드를 솔리디티 코드 상에서 작성해두었기 때문이다.
솔리디티 코드로 돌아가 refundApple() payable 함수를 다시 보면, 사과 갯수와 사과 한개당 환불 금액을 인자로 받아 refund값을 계산해버린다. 그리고 payable 속성이 붙으면 address를 가지고 메소드를 쓰는게 가능해지는데, 아래 payable 코드를 통해 msg.sender, 즉 refundApple 함수 호출자 계정 앞으로 환불된 사과 금액을 전송해준다. 이때 환불되는 이더는 컨트랙트 계정인 CA에 보관하고 있던 이더이다.
// payable([address 타입의 데이터]).메소드
payable(msg.sender).transfer(refund);
'BlockChain' 카테고리의 다른 글
ERC-20 나만의 토큰 작성 / fallback, receive? / 이더로 토큰 사기 (0) | 2022.07.22 |
---|---|
ERC20 interface / ERC20 (0) | 2022.07.22 |
스마트 컨트랙트로 초간단 투표 dApp 만들기 (0) | 2022.07.20 |
스마트 컨트랙트로 토큰 발행 및 전송해보기 (0) | 2022.07.19 |
Crypto Zombie / lesson2 (0) | 2022.07.19 |