Technology, Tutorials, for Developer, Tool support

심리스한 토큰 스왑 (2): Thirdweb과 Next.js를 사용하여 MiniKLAY 스왑 컨트랙트를 위한 미니멀리즘 UI 구축하기

개요

Part1 에서는 유니버설라우터를 사용하여 스마트 컨트랙트에서 프로그래밍 방식으로 KLAYswap V2를 구현하는 방법과 KLAYswap에 대해 배웠습니다. 이 가이드에서는 thirdweb과 Next.js를 사용하여 1부에서 만든 MiniKLAYswap 컨트랙트와 상호작용하는 최소한의 프론트엔드를 구축하는 방법을 배워보겠습니다. 

빠르게 시작하기 위해 이 튜토리얼의 전체 코드는 GitHub에서 찾을 수 있습니다. 이렇게 하면 따라하면서 애플리케이션의 내부 작동을 살펴볼 수 있습니다.

먼저, 이 듀토리얼을 잘 따랐을 때 최종 결과물은 아래와 같습니다. 

전제 조건

시작하기 전, 아래의 전제 조건을 확인하세요. 

프로젝트 설정 및 설치

프로젝트 설정 및 설치를 빠르게 시작하려면 다음 명령을 사용하여 GitHub에서 아래의 프로젝트를 복제하세요.

git clone https://github.com/ayo-klaytn/mini-klayswap-example.git

Next, change the directory into the cloned folder and install the project locally using npm with the following command:

cd mini-klayswap-example && npm i && npm run dev

스마트 컨트랙트로 Next.js 프론트엔드 애플리케이션 구축하기

Part1에서는 MiniKLAYswap 스마트 컨트랙트를 성공적으로 빌드하고 배포했습니다. 이제 일반적으로 웹에서 dApp을 사용할 때와 마찬가지로 프론트엔드에서 상호작용할 차례입니다.

이미 Next.js 프론트엔드 프로젝트를 복제했으므로, 이제 기존 애플리케이션을 업데이트하고 테스트와 상호작용을 위해 스마트 컨트랙트를 프론트엔드에 연결할 수 있습니다.

초기 설정

이 섹션에서는 필요한 모든 구성 요소와 부품이 모두 설정되었는지 확인합니다. 

  1. .env 파일의 NEXT_PUBLIC_TEMPLATE_CLIENT_ID를 thirdweb 대시보드에서 생성된 고유한 CLIENT ID로 바꿉니다.
  2. 프로젝트 폴더의 const/details.ts 파일로 이동하여 다음이 있는지 확인합니다.
    b. sKAI_TOKEN_ADDRESS = 0x37d46C6813B121d6A27eD263AeF782081ae95434
    c. KLAYSWAP_TEST_ADDRESS = 0xF9FB3C6f132CE967DE7790DD5aE670B8ddBfa246 // 여기에 MiniKLAYswap 컨트랙트 주소 붙여넣기
*Note: KLAY/토큰의 유동성 풀이 존재하는 한, sKAI_TOKEN_ADDRESS를 어떤 토큰 주소로든 변경할 수 있습니다.  

DApp 구축하기

이 섹션에서는 페이지 폴더의 프론트엔드 코드를 집중적으로 살펴보겠습니다. 

A. _app.tsx

import { ACTIVE_CHAIN } from "../const/details";
import "../styles/globals.css";
import { ChakraProvider } from "@chakra-ui/react";
import { ThirdwebProvider } from "@thirdweb-dev/react";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
  return (
    <ThirdwebProvider
      clientId={process.env.NEXT_PUBLIC_TEMPLATE_CLIENT_ID}
      activeChain={ACTIVE_CHAIN}
      supportedChains={[ACTIVE_CHAIN]}
    >
      <ChakraProvider>
        <Component {...pageProps} />
      </ChakraProvider>
    </ThirdwebProvider>
  );
}

위 코드에서,

  • ACTIVE_CHAIN은 const/details 파일에 명시된 대로 지원되는 체인(KlaytnCypress)을 나타냅니다.
  • 다음으로, styles 폴더에서 global.css 파일을 가져왔습니다.
  • 또한 애플리케이션의 루트에 ChakraProvider와 ThirdwebProvider를 모두 설정했습니다.
  • Thirdweb React SDK에서 API 키를 사용하기 위해 ThirdwebProvider에 clientId를 전달하고 activeChainSupportedChains도 설정했습니다.

B. Index.tsx 

이 파일에는 MiniKLAYswap 프론트엔드 구축의 핵심 기능과 컴포넌트가 들어있습니다. 아래 표시된 컴포넌트를 포함하여 모든 필수 기능을 thirdweb React SDK, chakra-ui/react에서 가져와야 합니다: 

//...
import Head from "next/head";
import { Inter } from "next/font/google";
import Navbar from "../components/NavBar";
import SwapInput from "../components/SwapInput";
import {
  Button,
  Flex,
  Spinner,
  useToast,
} from "@chakra-ui/react";
import { ACTIVE_CHAIN, KLAYSWAP_TEST_ADDRESS, sKAI_TOKEN_ADDRESS } from "../const/details";
import {
  ConnectWallet,
  toWei,
  useAddress,
  useBalance,
  useContract,
  useContractMetadata,
  useContractRead,
  useContractWrite,
  useNetworkMismatch,
  useSDK,
  useSwitchChain,
  useTokenBalance,
} from "@thirdweb-dev/react";
import { useState } from "react";
const inter = Inter({ subsets: ["latin"] });
export default function Home() {
//...
}

다음으로, 이전에 생성한 스마트 컨트랙트와 상호작용하기 위해 다음과 같은 상태 변수와 함수를 생성합니다.

//.. 
export default function Home() {
  const toast = useToast();
  const address = useAddress();
  const { contract: sKaitokenContract } = useContract(sKAI_TOKEN_ADDRESS, "token");
  const { contract: dexContract } = useContract(KLAYSWAP_TEST_ADDRESS, "custom");
  const { data: symbol } = useContractRead(sKaitokenContract, "symbol");
  const { data: tokenMetadata } = useContractMetadata(sKaitokenContract);
  const { data: sKaiTokenBalance } = useTokenBalance(sKaitokenContract, address);
  const { data: nativeBalance } = useBalance();
  const isMismatched = useNetworkMismatch();
  const switchChain = useSwitchChain();
  const sdk = useSDK();
  const [nativeValue, setNativeValue] = useState<string>("0");
  const [tokenValue, setTokenValue] = useState<string>("0");
  const [currentFrom, setCurrentFrom] = useState<string>("native");
  const [loading, setLoading] = useState<boolean>(false);
  const { mutateAsync: swapNativeToToken } = useContractWrite(
    dexContract,
    "swapExactKLAYForTokens"
  );
  const { mutateAsync: swapTokenToNative } = useContractWrite(
    dexContract,
    "swapExactTokensForKLAY"
  );
  const { mutateAsync: approveTokenSpending } = useContractWrite(
    sKaitokenContract,
    "approve"
  );
  const executeSwap = async () => {
    setLoading(true);
    if (isMismatched) {
      switchChain(ACTIVE_CHAIN.chainId);
      setLoading(false);
      return;
    }
    try {
      if (currentFrom === "native") {
        await swapNativeToToken({ overrides: { value: toWei(nativeValue) } });
        toast({
          status: "success",
          title: "Swap Successful",
          description: `You have successfully swapped your ${
            ACTIVE_CHAIN.nativeCurrency.symbol
          } to ${symbol || "tokens"}.`,
        });
      } else {
        // Approve token spending
        await approveTokenSpending({ args: [KLAYSWAP_TEST_ADDRESS, toWei(tokenValue)] });
        // Swap!
        await swapTokenToNative({ args: [toWei(tokenValue)] });
        toast({
          status: "success",
          title: "Swap Successful",
          description: `You have successfully swapped your ${
            symbol || "tokens"
          } to ${ACTIVE_CHAIN.nativeCurrency.symbol}.`,
        });
      }
      setLoading(false);
    } catch (err) {
      console.error(err);
      toast({
        status: "error",
        title: "Swap Failed",
        description:
          "There was an error performing the swap. Please try again.",
      });
      setLoading(false);
    }
  };
//..
return  (
//..
)
}

    

위 코드에서, 

  • toast 변수는 애플리케이션에서 토스트를 표시하기 위해 useToast 훅을 사용하여 초기화되었습니다. 
  • 주소 변수는 연결된 지갑의 주소를 가져오기 위해 useAddress 훅을 사용하여 초기화되었습니다.
  • 이 코드는 useContract 훅을 두 번 사용하여 먼저 sKAI 컨트랙트를 인스턴스화한 다음 MiniKLAYswap 컨트랙트를 인스턴스화합니다. 
  • 또한 useContractRead 훅을 사용하여 sKAI 컨트랙트의 토큰 심볼을 읽습니다.
  • 이 코드는 useContractMetadata를 사용하여 배포된 sKAI 컨트랙트의 컨트랙트 메타데이터를 가져옵니다.
  • 또한 useTokenBalance를 사용하여 주어진 지갑 주소(이 경우 연결된 주소)에 대한 토큰 잔액을 가져옵니다. 
  • useBalance 훅은 네이티브 토큰 잔액을 가져오는 데 사용되었으며 nativeBalance 변수에 저장되었습니다.
  • useState 훅은 nativeValue, tokenValue, currentForm, loading을 포함한 여러 상태 변수를 생성하는 데 사용되었습니다. 
  • 또한 이 코드는 useContractWrite 훅을 사용하여 MiniKLAYswap 컨트랙트에서 swapExactKLAYForTokens, swapExactTokensForKLAY 함수를 호출하기 위한 인스턴스를 설정하고, sKAI 컨트랙트에서 approve 함수를 설정합니다. 
  • 마지막으로 executeSwap 함수를 선언하여 swapNativeToToken(KLAY에서 sKAI로)과 swapTokenToNative(sKAI에서 KLAY로)를 교환했습니다.

다음으로 스왑 기능을 처리하기 위해 UI 컴포넌트를 업데이트했습니다. 

//..
export default function Home() {
//.. 
return (
    <>
      <Head>
        <title>Decentralised Exchange</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Navbar />
      <Flex
        direction="column"
        gap="5"
        mt="40"
        p="5"
        mx="auto"
        maxW={{ base: "sm", md: "xl" }}
        w="full"
        rounded="2xl"
        borderWidth="1px"
        borderColor="gray.300"
      >
        <Flex
          direction={currentFrom === "native" ? "column" : "column-reverse"}
          gap="3"
        >
          <SwapInput
            current={currentFrom}
            type="native"
            max={nativeBalance?.displayValue}
            value={nativeValue}
            setValue={setNativeValue}
            tokenSymbol="KLAY"
            tokenBalance={nativeBalance?.displayValue || "0"}
          />
          <Button
            onClick={() =>
              currentFrom === "native"
                ? setCurrentFrom("token")
                : setCurrentFrom("native")
            }
            maxW="5"
            mx="auto"
          >
            ↓
          </Button>
          <SwapInput
            current={currentFrom}
            type="token"
            max={sKaiTokenBalance?.displayValue}
            value={tokenValue}
            setValue={setTokenValue}
            tokenSymbol={tokenMetadata?.symbol}
            tokenBalance={sKaiTokenBalance?.displayValue || "0"}
            />
        </Flex>
        {address ? (
          <Button
            onClick={executeSwap}
            py="7"
            fontSize="2xl"
            colorScheme="twitter"
            rounded="xl"
            isDisabled={loading}
          >
            {loading ? <Spinner /> : "Execute Swap"}
          </Button>
        ) : (
          <ConnectWallet
            style={{ padding: "20px 0px", fontSize: "18px" }}
            theme="light"
          />
        )}
      </Flex>
    </>
  );
}

    

위 코드에는 세 가지 주요 컴포넌트가 있습니다.

  • NavBar: 이 컴포넌트에는 앱 헤더(MiniKLAYswap 예시)와 사용자가 다양한 지갑에 연결할 수 있는 모달을 여는 버튼을 렌더링하는 ConnectWallet  컴포넌트가 들어 있습니다. 
  • SwapInput: 스왑을 위한 입력 필드(네이티브(KLAY) 및 토큰(sKAI) 모두)를 처리하는 컴포넌트입니다.
  • Execute swap button: 이 버튼은 이전에 선언한 executeSwap 함수를 호출하여 핵심 스왑 기능을 수행합니다. 

애플리케이션 테스트

축하합니다! 이제 여러분은 thirdweb과 Next.js를 사용하여 Part 1에서 생성한 MiniKLAYswap 컨트랙트와 상호작용하는 미니멀한 프론트엔드를 성공적으로 구축했습니다. 

결론

결론적으로, 이 튜토리얼에서는 thirdweb과 Next.js를 사용해 미니클레이스왑 컨트랙트와 상호작용하는 최소한의 프론트엔드를 구축하는 방법을 보여드렸습니다. 결과적으로 여러분은 이제 KLAYswap과 컨트랙트와 DApp에 스왑 기능을 통합하는 방법을 더 잘 이해하게 되었을 것입니다. 더 많은 정보를 원하신다면 Klaytn DocsKLAYswap Docs를 참고하시기 바랍니다. 궁금한 점이 있으시다면 Klaytn Forum을 방문해주세요.