Thirdweb을 사용하여 클레이튼에서 dApp 구축하기
개요
토큰 스테이킹은 암호화폐 분야의 개발자나 구축자가 사용자가 앱이나 네트워크의 보안을 유지하고, 운영을 지원하는 데 적극적으로 참여하는 한편 보상을 받을 수 있도록 하는 방법 중 하나입니다. ‘스테이킹’은 거래 검증을 돕고 블록체인 네트워크의 운영을 지원하기 위해 네트워크에 암호화폐를 잠그는 행위를 말합니다.
클레이튼 생태계에서 인기 있는 스테이킹 DApp의 예로는 Klaystation,Everstake, Swapscanner,Klayswap 등이 있습니다. 이 튜토리얼에서는 Klaytn의 Thirdweb을 사용해 ERC20 토큰 디앱을 구축해 봅니다. 이 스테이킹 DApp은 사용자가 ERC20 토큰을 스테이킹하고, 스테이킹을 해제하고, 각각의 보상을 받을 수 있는 간단한 기능을 제공합니다.
먼저, 최종 결과물을 먼저 보자면 아래와 같습니다.
전제 조건
전체 프로젝트를 살펴보기 전에, 완성된 양식은 erc20-staking-example에서 찾을 수 있습니다. 우선, 이 듀토리얼을 따라 하려면 다음과 같은 전제 조건이 필요합니다:
- Command-line 기초
- 기초적인 React 지식
ThirdWeb 계정과 ClientID 생성
시작하기
Thirdweb은 앱과 게임을 분산형 네트워크에 연결하는 데 필요한 모든 것을 제공하는 완전한 Web3 개발 툴킷입니다. 이 튜토리얼에서는 Thirdweb의 스테이크 ERC20 컨트랙트를 사용하여 사용자가 자신의 ERC-20 토큰을 스테이킹하고, Klaytn Cypress에서 (스테이킹한 토큰과 다른) ERC-20 토큰을 보상으로 받을 수 있는 스테이킹 애플리케이션을 만들겠습니다. 결과적으로 이 애플리케이션은 다음과 같은 세 가지 주요 기능을 갖추게 됩니다.
- 스테이킹: 사용자가 일정 기간 동안 자신의 토큰을 스테이킹 컨트랙트에 고정할 수 있습니다.
- 언스테이킹: 사용자가 스테이킹 계약에서 각자의 지분을 인출할 수 있습니다.
- ClaimRewards(보상청구): 사용자가 스테이킹한 금액과 토큰이 스테이킹된 기간에 따라 각각의 스테이킹 보상을 청구할 수 있습니다. 보상 청구 시 보상 계산에 대한 자세한 내용은 Stake ERC20을 참조하세요.
문제 해결
이 가이드에서 따라하기 위해, Thirdweb 대시보드를 사용하여 Klaytn Cypress 대신 Klaytn Baobab에 배포할 수 있습니다. 이 튜토리얼의 배포(deployment) 섹션에 있는 단계를 따르는 동안 Baobab에 배포하려고 할 때 아래와 같은 오류 메시지가 표시될 수 있습니다.
이를 해결하기 위해, 아래의 단계를 따르세요.
- 네트워크 선택기에서 Klaytn Testne 을 검색합니다.
- 옆에 있는 settings 아이콘을 클릭합니다.
- 기존 RPC URL을 https://klaytn-baobab-rpc.allthatnode.com:8551 또는 여기 RPC 제공자의 아무 URL로 바꿉니다.
- update network 버튼을 클릭한 다음 페이지를 새로고침합니다.
다음 섹션에서는 디앱을 만들고, 배포하고, 구축하는 과정을 살펴보겠습니다.
Thirdweb을 사용하여 스테이킹 스마트 컨트랙트 생성 및 배포하기
이 섹션에서는 thirdweb 대시보드를 사용하여 스테이킹 스마트 컨트랙트를 생성하고 배포하겠습니다.
시작하려면 StakeERC20 컨트랙트 자체를 배포하기 전에 다음과 같은 종속 컨트랙트를 생성하고 배포해야 합니다.
a: Staking Token Contract: 이것은 우리의 erc20 스테이킹 토큰을 의미합니다.
b. Reward Token Contract: 이 역시 스테이크 보상을 위한 별도의 erc20 토큰입니다.
*Note: thirdweb 대시보드를 사용하여 계약을 만들고 배포하려면 로그인에 성공했는지 확인하세요.
스테이킹 토큰 컨트랙트 생성 및 배포하기
다음 단계에 따라 스테이킹 토큰 컨트랙트를 생성하고 배포해 보겠습니다.
- contracts 탭의 탐색 섹션으로 이동하여 토큰 컨트랙트를 선택하여 ERC20 스테이킹 토큰을 생성합니다.
2. Deploy Now 버튼을 누릅니다.
3. NAME (KCT), SYMBOL (KCT)등의 컨트랙트 파라미터를 입력하고, 배포할 체인(Klaytn Cypress)을 설정한 후 Deploy Now 버튼을 누릅니다.
4. 트랜잭션을 확인하고 컨트랙트가 배포되어 컨트랙트 대시보드에 추가될 때까지 기다립니다.
5. 새로 배포된 컨트랙트 대시보드가 로드되면 토큰 섹션으로 이동하여 새 토큰을 발행합니다.
6. 추가 공급량을 입력합니다. 이 경우 2,000,000개의 KCT 토큰을 발행할 수 있습니다.
7. 거래를 확인하고 토큰이 성공적으로 발행될 때까지 기다립니다.
보상 토큰 컨트랙트 생성 및 배포하기
아래의 단계에 따라 보상 토큰 컨트랙트를 생성하고 배포합니다.
- contracts 탭과 explore 섹션으로 이동하여 토큰 컨트랙트를 선택하여 ERC20 스테이킹 보상 토큰을 생성합니다.
- Deploy Now 버튼을 클릭합니다.
- 컨트랙트 파라미터인 NAME(rewardsKCT), SYMBOL(rKCT) 등을 입력하고, 배포할 체인(Klaytn Cypress)을 설정한 후 Deploy Now 버튼을 누릅니다.
- 트랜잭션을 확인하고 컨트랙트가 배포되어 컨트랙트 대시보드에 추가될 때까지 기다립니다.
- 새로 배포한 컨트랙트 대시보드가 로드되면 토큰 섹션으로 이동하여 새 토큰을 발행합니다.
- 추가 공급 가치를 입력합니다. 이 경우 2,000,000개의 rKCT 토큰이 발행됩니다.
- 거래를 확인하고 토큰이 성공적으로 발행될 때까지 기다립니다.
스테이킹 컨트랙트 생성 및 배포
다음 단계에 따라 스테이킹 토큰 컨트랙트를 생성하고 배포합니다.
- contracts 탭과 explore 섹션으로 이동하여 스테이킹 섹션에서 StakeERC20 컨트랙트를 선택하여 스테이킹 컨트랙트를 생성합니다.
2. Deploy Now 버튼을 누릅니다.
3. NAME(ERC20Stake), SYMBOL(ERC20SC)등의 컨트랙트 파라미터와 다음과 같은 종속 인수를 입력합니다:
1) ERC20 보상 토큰 주소: 이전에 배포한 보상 토큰(rKCT)의 컨트랙트 주소를 붙여넣습니다.
2) ERC20 스테이킹 토큰 주소: 이전에 배포한 스테이킹 토큰(KCT)의 컨트랙트 주소를 붙여넣습니다.
3) 보상 지급 시간 단위: 보상을 지급할 시간(초)을 의미합니다. 저희의 경우 60을 입력했는데, 이는 1분마다 보상을 지급한다는 의미입니다.
4) 보상 비율 분자: 스테이킹된 모든 토큰에 대한 보상 금액을 나타냅니다. 이 경우 보상 비율 분자는 1, 보상 비율 분모는 50으로, 이는 스테이킹한 토큰 50개당 보상 토큰 1개가 지급된다는 의미입니다.
5) 보상 비율 분모: 50으로 설정합니다.
4. 배포할 체인(Klaytn Cypress)을 구성하고 “Deploy Now” 버튼을 누릅니다.
5. 지갑에서 트랜잭션을 확인하고 컨트랙트가 배포되어 컨트랙트 대시보드에 추가될 때까지 기다립니다
컨트랙트 연결하기
이 섹션에서는 스테이킹 컨트랙트가 1,000,000개의 rewardsKCT (rKCT) 토큰을 사용하도록 승인한 다음, 입금 리워드토큰() 함수를 호출하여 해당 금액을 스테이킹 컨트랙트에 입금하여 보상 토큰으로 지급할 것입니다.
다음 단계에서 자세한 과정을 살펴보겠습니다.
리워드KCT 컨트랙트에서 ERC스테이크 컨트랙트 승인하기
- 컨트랙트 대시보드에서 ERC20Stake 컨트랙트 주소를 복사합니다.
- 컨트랙트 대시보드에서 리워즈KCT 컨트랙트로 이동합니다.
- 리워드KCT 컨트랙트 패널에서 탐색기 섹션으로 이동하여 approve 함수를 클릭합니다.
- 승인 함수 인수인 주소(ERC20Stake 컨트랙트 주소)와 금액(1000000000000000000000000)을 입력하고 실행을 클릭합니다.
- 트랜잭션을 확인하고 트랜잭션이 실행될 때까지 기다립니다.
ERC20 스테이크 컨트랙트에 보상 토큰 입금하기
- 컨트랙트 대시보드에서 ERC20Stake 컨트랙트로 이동합니다.
- ERC20Stake 컨트랙트 패널에서 탐색기 섹션으로 이동하여 depositRewardTokens 기능을 클릭합니다.
- depositRewardTokens 함수 인수인 금액(1000000000000000000000000)과 native token value (0)을 입력하고 실행을 클릭합니다.
- 트랜잭션을 확인하고 트랜잭션이 실행될 때까지 기다립니다.
- 트랜잭션이 실행되면 토큰 소유자가 보유한 보상 토큰(rewardsTokens)의 양이 줄어듭니다.
이제 계약이 모두 설정되었으므로 이제 상호작용을 위한 프론트엔드를 구축할 수 있습니다.
thirdweb-create-app을 사용하여 프론트엔드 구축하기
이 섹션에서는 thirdweb-create-app을 사용하여 스테이킹 dApp의 프론트엔드를 구축하겠습니다.
bash
npx thirdweb@latest create app
실제로 작동하는지 확인하려면 아래 명령을 실행하세요:
- project name:프로젝트 이름을 입력합니다.
- framework: 이 가이드에서는 NextJs를 사용합니다.
- language: 이 가이드에서는 Typescript를 사용합니다.
종속 요소가 성공적으로 설치되면 아래와 같은 성공 메시지가 표시됩니다.
기본 설정
프로젝트를 성공적으로 초기화한 후 올바른 프로젝트 디렉터리에 있는지 확인하세요.
체인 설정
`_app.tsx` 파일로 이동하여 파일 상단에 아래 코드를 붙여넣습니다.
ts
import KlaytnCypress from "@thirdweb-dev/chains";
*Note: 디앱이 작동할 체인을 설정하려고 하는데, 여기서는 Klaytn Cypress를 설정하려고 합니다. 이를 위해 `@thirdweb-dev/chains`에서 Klaytn Cypress를 가져와 ThirdWebProvider 구성 요소의 activeChain 변수에 직접 전달했습니다. 이제 _app.tsx 파일은 다음과 같이 보일 것입니다:
ts
import type AppProps from "next/app";
import ThirdwebProvider from "@thirdweb-dev/react";
import KlaytnCypress from "@thirdweb-dev/chains";
import "../styles/globals.css";
// This is the chain your dApp will work on.
// Change this to the chain your app is built for.
// You can also import additional chains from `@thirdweb-dev/chains` and pass them directly.
function MyApp( Component, pageProps: AppProps)
return (
clientId=process.env.NEXT_PUBLIC_TEMPLATE_CLIENT_ID
activeChain=KlaytnCypress
>
);
export default MyApp;
상수 설정
앱에서 다음 컨트랙트의 주소를 사용 가능하게 만들어 보겠습니다:
- Stake Token Address
- Reward Token Address
- Stake Contract Address
이렇게 하려면 프로젝트 루트 폴더에 constants라는 새 폴더를 만들고 “contract-addresses.ts”라는 새 파일을 만듭니다. 새로 만든 파일에 다음을 붙여넣습니다:
ts
// Copy each address from your contract deployed dashboard
export const STAKE_TOKEN_ADDRESS = "PASTE STAKE TOKEN ADDRESS"
export const REWARD_TOKEN_ADDRESS = "PASTE REWARD TOKEN ADDRESS"
export const STAKE_CONTRACT_ADDRESS = "PASTE STAKE CONTRACT ADDRESS"
클라이언트 ID 설정
API 키 대시보드 아래의 세 번째 웹 설정 패널에서 클라이언트 ID를 복사했는지 확인합니다. 다음으로 .env.example 의 이름을 .env로 바꾼 다음 클라이언트 ID를 .env 파일에 붙여넣습니다.
앱 컴포넌트 빌딩
이 섹션에서는 애플리케이션에 필요한 다양한 컴포넌트를 만들겠습니다. 이를 위해 프로젝트 루트 폴더에 components라는 이름의 새 폴더를 만듭니다. 이 폴더에는 다음과 같은 컴포넌트가 하위 폴더로 포함됩니다:
- NavBar
- StakeToken
- RewardStakeToken
- Stake
> Note: You can decide to style each component using your own styles or by using any CSS framework of your choice.
NavBar.tsx
ts
import ConnectWallet from "@thirdweb-dev/react";
import styles from './NavBar.module.css'
export default function NavBar()
return (
Staking dApp
)
위의 코드는 사용자가 지정된 지갑에 연결할 수 있는 모달을 여는 버튼을 렌더링하는 ConnectWallet component를 thirdweb에서 가져온 것입니다. 또한 해당 스타일시트도 가져왔습니다. NavBar 컴포넌트는 두 가지 주요 요소로 구성됩니다.
- h1 태그로 표현되는 앱 헤더
- connectWallet 컴포넌트
StakeToken.tsx
ts
import useAddress, useContract, useTokenBalance from "@thirdweb-dev/react";
import STAKE_TOKEN_ADDRESS from "../../constants/contract-addresses";
import styles from "./StakeToken.module.css";
export default function StakeToken()
const address = useAddress();
const contract: stakeTokenContract, isLoading: loadingStakeToken =
useContract(STAKE_TOKEN_ADDRESS);
const data: tokenBalance, isLoading: loadingTokenBalance =
useTokenBalance(stakeTokenContract, address);
const contract: stakeTokenCont, isLoading: loadingStakeTok =
useContract(STAKE_TOKEN_ADDRESS);
return (
Stake Tokens
!loadingStakeToken && !loadingTokenBalance ? ($tokenBalance?.symboltokenBalance?.displayValue) : (loading....));
위의 컴포넌트는 연결된 지갑 주소의 스테이크 토큰 잔액을 UI에 표시합니다. 이를 위해 다음을 임포트했습니다.
- UseAddress: 현재 연결된 지갑 주소를 가져오기 위해 thirdweb에서 제공하는 훅입니다.
- UseContract: 스마트 컨트랙트에 연결하기 위해 thirdweb에서 제공하는 훅입니다.
- UseTokenBalance: 특정 ERC20 토큰에 대한 지갑의 잔액을 가져오기 위해 thirdweb에서 제공하는 훅입니다.
- StakeToken Address: thirdweb 대시보드를 사용하여 배포된 스테이크 토큰 컨트랙트의 컨트랙트 주소입니다.
RewardToken.tsx
ts
import useAddress, useContract, useTokenBalance from "@thirdweb-dev/react";
import REWARD_TOKEN_ADDRESS from "../../constants/contract-addresses";
import styles from "./RewardStakeToken.module.css";
export default function RewardStakeToken()
const address = useAddress();
const contract: rewardStakeTokenContract, isLoading: loadingStakeToken =
useContract(REWARD_TOKEN_ADDRESS);
const data: tokenBalance, isLoading: loadingTokenBalance =
useTokenBalance(rewardStakeTokenContract, address);
return (
Reward Tokens
!loadingStakeToken && !loadingTokenBalance ? ($tokenBalance?.symbolNumber(tokenBalance?.displayValue).toFixed(1)) : (loading....));
위의 컴포넌트는 연결된 지갑 주소의 보상 토큰 잔액을 UI에 표시합니다. 위의 코드는 `stakeToken.tsx`,에서 사용한 것과 동일한 훅을 사용하지만, 보상 토큰 컨트랙트 주소를 추가로 가져왔다는 점을 제외하면 동일합니다.
Stake.tsx
ts
import
Web3Button,
useAddress,
useContract,
useContractRead,
useTokenBalance,
from "@thirdweb-dev/react";
import
STAKE_TOKEN_ADDRESS,
REWARD_TOKEN_ADDRESS,
STAKE_CONTRACT_ADDRESS,
from "../../constants/contract-addresses";
import React, useEffect, useState from "react";
import ethers from "ethers";
import styles from "./Stake.module.css";
export default function Stake()
const address = useAddress();
const contract: stakeTokenContract = useContract(
STAKE_TOKEN_ADDRESS,
"token"
);
const contract: rewardStakeTokenContract = useContract(
REWARD_TOKEN_ADDRESS,
"token"
);
const contract: stakeContract = useContract(
STAKE_CONTRACT_ADDRESS,
"custom"
);
const
data: stakeInfo,
refetch: refetchStakeInfo,
isLoading: loadingStakeInfo,
= useContractRead(stakeContract, "getStakeInfo", [address]);
const data: stakeTokenBalance, isLoading: loadingStakeTokenBalance =
useTokenBalance(stakeTokenContract, address);
const
data: rewardStakeTokenBalance,
isLoading: loadingRewardStakeTokenBalance,
= useTokenBalance(rewardStakeTokenContract, address);
useEffect(() =>
setInterval(() =>
refetchStakeInfo();
, 10000);
, []);
const [stakeAmount, setStakeAmount] = useState("0");
const [unstakeAmount, setUnstakeAmount] = useState("0");
function resetValue()
setStakeAmount("0");
setUnstakeAmount("0");
return (
Earn Reward Tokens
Stake Token:!loadingStakeInfo && !loadingStakeTokenBalance ? (stakeInfo && stakeInfo[0] ? (ethers.utils.formatEther(stakeInfo[0])" "
"$ " + stakeTokenBalance?.symbol) : (0)) : (loading...)type="number"
max=stakeTokenBalance?.displayValue
value=stakeAmount
onChange=(e) =>
setStakeAmount(e.target.value);
/>
contractAddress=STAKE_CONTRACT_ADDRESS
action=async (contract) =>
await stakeTokenContract?.setAllowance(
STAKE_CONTRACT_ADDRESS,
stakeAmount
);
await contract.call("stake", [
ethers.utils.parseEther(stakeAmount),
]);
resetValue();
onSuccess=() =>
alert("Staking was successful");
>
Staketype="number"
value=unstakeAmount
onChange=(e) =>
setUnstakeAmount(e.target.value);
/>
contractAddress=STAKE_CONTRACT_ADDRESS
action=async (contract) =>
await contract.call("withdraw", [
ethers.utils.parseEther(unstakeAmount),
]);
resetValue();
onSuccess=() =>
alert("Unstake was successful");
>
UnStakeReward Token!loadingStakeInfo && !loadingStakeTokenBalance ? (stakeInfo && stakeInfo[0] ? (ethers.utils.formatEther(stakeInfo[1])" "
"$ " + rewardStakeTokenBalance?.symbol) : (0)) : (loading...)
contractAddress=STAKE_CONTRACT_ADDRESS
action=async (contract) =>
await contract.call("claimRewards");
resetValue();
onSuccess=() =>
alert("Claim Reward was successful");
>
Claim);
위의 컴포넌트는 사용자가 스테이킹, 스테이킹 해제, 스테이킹 보상 수령을 할 수 있는 기능을 제공합니다. 이를 위해 다음과 같은 후크와 컴포넌트를 추가로 가져왔습니다:
- Web3Button: 연결된 지갑에서 클릭 시 스마트 컨트랙트의 기능을 실행하기 위해 thirdweb에서 제공하는 UI 컴포넌트입니다.
- useContractRead: 함수/보기/변수 이름을 통해 스마트 컨트랙트에서 데이터를 읽기 위해 thirdweb에서 제공하는 후크입니다.
또한, 각 컨트랙트 주소인 stakeTokenAddress, rewardTokenAddress, stakeContractAddress를 가져왔습니다.
컴포넌트는 useState 훅을 사용하여 stakeAmount 및 unstakeAmount 상태 변수를 초기화합니다.
useEffect 훅은 컴포넌트가 마운트될 때 1초마다 연결된 사용자의 stakeInfo를 가져오는 데 사용됩니다.
Index.tsx
ts
import styles from "../styles/Home.module.css";
import StakeToken from "../components/StakeToken/StakeToken";
import RewardStakeToken from "../components/RewardStakeTokens/RewardStakeToken";
import Stake from "../components/Stake/Stake";
import NextPage from "next";
import useAddress from "@thirdweb-dev/react";
const Home: NextPage = () =>
const address = useAddress()
if (!address)
return (Please Connect Your Wallet)
return ();
;
export default Home;
위의 코드는 다음을 수행합니다.
- 사용자가 지갑을 연결했는지 확인하고 그렇지 않은 경우 “Please Connect Your Wallet”를 표시합니다.
- 서로 다른 모든 앱 컴포넌트를 한 곳에 연결합니다.
_app.tsx
`_app.tsx` 파일의 모습은 아래와 같습니다.
ts
import type AppProps from "next/app";
import ThirdwebProvider from "@thirdweb-dev/react";
import KlaytnCypress from "@thirdweb-dev/chains";
import "../styles/globals.css";
import NavBar from "../components/NavBar/NavBar";
// This is the chain your dApp will work on.
// Change this to the chain your app is built for.
// You can also import additional chains from `@thirdweb-dev/chains` and pass them directly.
function MyApp( Component, pageProps : AppProps)
return (
clientId=process.env.NEXT_PUBLIC_TEMPLATE_CLIENT_ID
activeChain=KlaytnCypress
>
);
export default MyApp;
애플리케이션 실행
필요에 따라 애플리케이션을 성공적으로 구성했으면 이제 애플리케이션이 어떻게 작동하는지 살펴봅시다. 이를 위해 아래 명령을 실행합니다.
bash
npm run dev
로컬 호스트에서 앱을 열라는 메시지가 표시되어야 합니다: http://localhost:3000
나가며
Congratulations! 🥳 thirdweb을 사용하여 클레이튼 블록체인에서 본격적인 애플리케이션을 성공적으로 구축하셨습니다. 이 튜토리얼에서는 사용자가 스테이킹, 스테이킹 해제, 스테이크 보상을 받을 수 있는 간단한 스테이킹 디앱을 만들었습니다.
이 튜토리얼은 탈중앙화 애플리케이션 기능에 써드웹을 사용하는 방법의 표면적인 부분만 다루었습니다. 써드웹을 사용해 Klaytn을 구축하는 방법에 대한 자세한 내용은 Klaytn Docs 와 ThirdWeb Docs 문서를 참조하세요. 그리고 다른 질문이 있으시면 언제든지 Discord에 들러서 물어보세요!