티스토리 뷰

소셜 로그인과 zkSync native account abstraction을 활용해 Web2 사용자들을 어떻게 Web3로 온보딩할 수 있는지에 대한 빠르고 간결한 가이드.

개요

이 튜토리얼에서는 다음 주제를 다룹니다:

  • 이 튜토리얼에서는 Privy와 zkSync 네트워크를 사용한 소셜 로그인을 구현합니다.
  • Next.js로 프론트엔드 앱을 만들고 Vercel에 배포합니다.
  • zkSync 네트워크와 상호작용하여 zkSync Sepolia 테스트넷에서 NFT를 민팅합니다.

이 튜토리얼은 이런 개발자를 대상으로 합니다:

  • 원활한 소셜 로그인 경험을 제공하여 Web2 사용자들을 Web3로 온보딩하고자 하는 분
  • Viem과 같은 Web3 라이브러리를 통해 zkSync 네트워크와 상호작용하고자 하는 분
  • zkSync 네트워크와 상호작용하는 프로젝트를 구축하고 다음 해커톤을 준비하고자 하는 분

전체 코드

 

GitHub - piatoss3612/LibroLink

Contribute to piatoss3612/LibroLink development by creating an account on GitHub.

github.com


목차

  • 요구 사항
  • 설치
  • Privy와 zkSync 네트워크를 사용한 소셜 로그인 구현
  • 소셜 로그인 테스트
  • 프론트엔드를 Vercel에 배포
  • zkSync 네트워크와 상호작용
  • 결론
  • 다음 단계
  • 참조

요구 사항


설치

스마트 컨트랙트

1. zksync-cli로 프로젝트 초기화

$ npx zksync-cli create --template hardhat_solidity contracts
Using Hardhat + Solidity template
? Private key of the wallet responsible for deploying contracts (optional)
? Package manager yarn

Setting up template in zkSync-native-aa-demo/contracts...
✔ Cloned template
✔ Environment variables set up
✔ Dependencies installed

🎉 All set up! 🎉

--------------------------

Navigate to your project: cd contracts

...
  • 프로젝트가 Hardhat + Solidity 템플릿으로 초기화되었습니다.
  • 지갑의 비공개 키는 제공하거나 나중에 .env 파일에 설정할 수 있습니다.

2. 종속성 설치

$ cd contracts && yarn add @openzeppelin/contracts@latest
  • 프로젝트 생성 시 @openzeppelin/contracts4.6.x 버전으로 설치되었습니다. 이 튜토리얼을 작성할 시점의 최신 버전은 5.0.2이므로, 최신 보안 패치 및 업데이트를 적용하기 위해 종속성을 최신 버전으로 업데이트합니다.

프론트엔드

1. 새로운 Next.js 앱 생성

$ yarn create next-app
yarn create v1.22.21
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...

$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
success Installed "create-next-app@14.2.3" with binaries:
      - create-next-app
✔ What is your project named? … frontend
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … No
✔ Would you like to use `src/` directory? … No
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No
Creating a new Next.js app in zkSync-native-aa-demo/frontend.

Using yarn.

Initializing project with template: app

...

Done in 34.30s.

2. 종속성 설치

$ cd frontend
  • 프론트엔드 폴더로 디렉토리를 변경합니다.
$ yarn add @privy-io/react-auth viem @tanstack/react-query
  • @privy-io/react-authPrivy가 지원하는 프론트엔드에서 소셜 로그인을 처리하는 라이브러리입니다.
  • viem은 프론트엔드에서 Web3 상호작용을 처리하는 라이브러리입니다.
  • @tanstack/react-query는 프론트엔드에서 데이터 가져오기 및 캐싱을 처리하는 라이브러리입니다.
$ yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
  • Chakra UI 라이브러리는 프론트엔드 컴포넌트 스타일링에 사용됩니다.
  • 다른 스타일링 라이브러리를 사용할 수도 있습니다. 이 튜토리얼에서는 경량화 및 사용이 쉬운 스타일링을 위해 Chakra UI를 사용합니다.

Privy

1. 새로운 Privy 앱 생성

 

Privy란? Google, Facebook, Twitter 등과 같은 소셜 미디어 계정으로 사용자가 로그인할 수 있게 해주는 소셜 로그인 솔루션입니다. 새로운 계정을 생성하는 불편함을 줄이고 사용자가 원활하게 로그인할 수 있도록 도와줍니다. 또한 프론트엔드에서 소셜 로그인을 처리하기 위한 간편한 훅을 제공합니다.

2. Privy 앱 ID를 환경 변수로 설정

$ cp .env.local.example .env.local
  • .env.local.example 파일을 .env.local로 복사합니다.
  • 방금 생성한 Privy 앱 ID를 .env.local 파일의 NEXT_PUBLIC_PRIVY_APP_ID 변수에 설정합니다.

Privy와 zkSync 네트워크를 사용한 소셜 로그인 구현

1. providers.tsx 생성

  • app 디렉토리에 providers.tsx라는 새 파일을 생성합니다.
// frontend/app/providers.tsx

2. Providers 컴포넌트 생성

  • providers.tsx 파일에 Providers라는 새 컴포넌트를 생성합니다.
// frontend/app/providers.tsx
"use client";

import { useEffect, useState } from "react";
import { PrivyProvider } from "@privy-io/react-auth";
import { ChakraProvider } from "@chakra-ui/react";
import { zkSyncSepoliaTestnet } from "viem/zksync";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";

const Providers = ({ children }: { children: React.ReactNode }) => {
  const queryClient = new QueryClient();
  const [mounted, setMounted] = useState<boolean>(false);
  useEffect(() => {
    setMounted(true);
  }, []);

  return (
    <PrivyProvider
      appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID || ""}
      config={{
        // zkSyncSepoliaTestnet을 사용하여 기본 체인과 지원되는 체인을 구성합니다.
        defaultChain: zkSyncSepoliaTestnet,
        supportedChains: [zkSyncSepoliaTestnet],
        // 첫 번째 로그인 시 지갑이 없는 사용자에게 임베디드 지갑 생성
        embeddedWallets: {
          createOnLogin: "users-without-wallets",
        },
      }}
    >
      <ChakraProvider>
        <QueryClientProvider client={queryClient}>
          {mounted && children}
        </QueryClientProvider>
      </ChakraProvider>
    </PrivyProvider>
  );
};

export { Providers };
  • Providers 컴포넌트는 PrivyProvider, ChakraProvider, 및 QueryClientProvider 컴포넌트를 children 컴포넌트 주위에 감쌉니다.
  • PrivyProvider 컴포넌트는 Privy를 사용한 소셜 로그인을 처리하는 데 사용됩니다.
    • defaultChainsupportedChains 옵션은 zkSyncSepoliaTestnet을 사용하여 기본 체인과 지원되는 체인을 구성하는 데 사용됩니다.
    • embeddedWallets 옵션은 첫 번째 로그인 시 지갑이 없는 사용자에게 임베디드 지갑을 생성하는 데 사용됩니다.
  • ChakraProvider 컴포넌트는 Chakra UI를 사용한 스타일링을 처리하는 데 사용됩니다.
  • QueryClientProvider 컴포넌트는 React Query를 사용한 데이터 가져오기 및 캐싱을 처리하는 데 사용됩니다.

3. ZkSyncClientContext 및 ZkSyncClientProvider 생성

  • 동일한 파일에서 ZkSyncClientContext라는 새로운 컨텍스트를 생성합니다.
  • ZkSyncClientContext 컨텍스트는 지갑 및 zkSync 클라이언트를 저장하는 데 사용됩니다.
  • ZkSyncClientProvider 컴포넌트는 지갑 및 zkSync 클라이언트를 children 컴포넌트에 제공합니다.
"use client";

import { createContext, useEffect, useState } from "react";
import {
  ConnectedWallet,
  PrivyProvider,
  usePrivy,
  useWallets,
} from "@privy-io/react-auth";
import { ChakraProvider } from "@chakra-ui/react";

import { WalletClient, createWalletClient, custom } from "viem";
import { eip712WalletActions, zkSyncSepoliaTestnet } from "viem/zksync";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";

interface ZkSyncClientContextValue {
  wallet: ConnectedWallet | null;
  zkSyncClient: WalletClient | null;
}

const ZkSyncClientContext = createContext({} as ZkSyncClientContextValue);

const ZkSyncClientProvider = ({ children }: { children: React.ReactNode }) => {
  const { ready, authenticated } = usePrivy();
  const { wallets } = useWallets();
  const [wallet, setWallet] = useState<ConnectedWallet | null>(null);
  const [zkSyncClient, setZkSyncClient] = useState<WalletClient | null>(null);

  const zkSyncSetup = async (wallet: ConnectedWallet) => {
    await wallet.switchChain(zkSyncSepoliaTestnet.id); // zkSync 체인으로 전환
    const provider = await wallet.getEthereumProvider(); // EIP-1193 공급자 가져오기

    const client = createWalletClient({
      account: wallet.address as `0x${string}`,
      chain: zkSyncSepoliaTestnet,
      transport: custom(provider),
    }).extend(eip712WalletActions()); // EIP-712 지갑 작업으로 클라이언트 확장

    setWallet(wallet);
    setZkSyncClient(client);
  };

  useEffect(() => {
    if (ready && authenticated) {
      // 임베디드 지갑 찾기
      const embeddedWallet: ConnectedWallet | undefined = wallets.find(
        (wallet) => wallet.walletClientType === "privy"
      );

      // zkSync 클라이언트 설정
      if (embeddedWallet) {
        zkSyncSetup(embeddedWallet);
      }
    }
  }, [ready, authenticated, wallets]);

  return (
    <ZkSyncClientContext.Provider
      value={{
        wallet,
        zkSyncClient,
      }}
    >
      {children}
    </ZkSyncClientContext.Provider>
  );
};
  • zkSyncSetup 함수는 지갑과 함께 zkSync 클라이언트를 설정하는 데 사용됩니다.
  • switchChain 메서드를 사용하여 네트워크를 zkSync 체인으로 전환합니다.
  • 지갑에서 EIP-1193 공급자를 가져오고 zkSync 클라이언트용 맞춤형 전송을 만듭니다.
  • zkSync 클라이언트는 다음 단계를 위해 필요한 구조화된 데이터 서명을 위한 EIP-712 지갑 작업을 확장합니다.
const zkSyncSetup = async (wallet: ConnectedWallet) => {
  await wallet.switchChain(zkSyncSepoliaTestnet.id); // zkSync 체인으로 전환
  const provider = await wallet.getEthereumProvider(); // EIP-1193 공급자 가져오기

  const client = createWalletClient({
    account: wallet.address as `0x${string}`,
    chain: zkSyncSepoliaTestnet,
    transport: custom(provider),
  }).extend(eip712WalletActions()); // EIP-712 지갑 작업으로 클라이언트 확장

  setWallet(wallet);
  setZkSyncClient(client);
};
  • Providers 컴포넌트를 수정하여 ZkSyncClientProvider 컴포넌트를 포함합니다.

참고: usePrivy 훅이 작동하려면 PrivyProvider 컴포넌트 주위에 ZkSyncClientProvider 컴포넌트가 래핑되어야 합니다.

const Providers = ({ children }: { children: React.ReactNode }) => {
  ...

  return (
    <PrivyProvider
      ...
    >
      <ChakraProvider>
        <ZkSyncClientProvider>
          <QueryClientProvider client={queryClient}>
            {mounted && children}
          </QueryClientProvider>
        </ZkSyncClientProvider>
      </ChakraProvider>
    </PrivyProvider>
  );
};

export { Providers, ZkSyncClientContext };

4. layout.tsx 수정

  • RootLayout 컴포넌트를 수정하여 Providers 컴포넌트를 포함합니다.
// frontend/app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { Providers } from "./providers";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "zkSync AA Demo",
  description: "zkSync AA Demo",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

5. useZkSyncClient 훅 생성

  • hooks 디렉토리에 useZkSyncClient라는 새로운 훅을 생성합니다.
// frontend/hooks/useZkSyncClient.ts
import { ZkSyncClientContext } from "@/app/providers";
import { useContext } from "react";
import { createPublicClient, http } from "viem";
import { zkSyncSepoliaTestnet } from "viem/chains";

const useZkSyncClient = () => {
  const publicClient = createPublicClient({
    chain: zkSyncSepoliaTestnet,
    transport: http(),
  });
  const { wallet, zkSyncClient } = useContext(ZkSyncClientContext);

  return { wallet, publicClient, zkSyncClient };
};

export default useZkSyncClient;
  • useZkSyncClient 훅은 ZkSyncClientContext에서 지갑 및 zkSync 클라이언트를 가져오는 데 사용됩니다.
  • 추가로 zkSync 네트워크에서 계약을 읽기 위한 퍼블릭 클라이언트를 생성합니다.

6. Main 컴포넌트 생성

  • components 디렉토리에 Main이라는 새로운 컴포넌트를 생성합니다.
// frontend/components/Main.tsx
"use client";

import useZkSyncClient from "@/hooks/useZkSyncClient";
import { Box, Button, Center, Text, VStack } from "@chakra-ui/react";
import { usePrivy } from "@privy-io/react-auth";

const Main = () => {
  const { ready, authenticated, login, logout } = usePrivy();
  const { wallet } = useZkSyncClient();

  if (!authenticated) {
    return (
      <Box
        display="flex"
        flexDirection="column"
        minHeight="100vh"
        bg="gray.300"
      >
        <Center my="auto">
          <Button onClick={login} isLoading={!ready}>
            Login
          </Button>
        </Center>
      </Box>
    );
  }

  return (
    <Box display="flex" flexDirection="column" minHeight="100vh" bg="gray.300">
      <Center my="auto">
        <VStack
          spacing={4}
          direction="column"
          alignItems="center"
          justifyContent="center"
        >
          <Button onClick={logout} isLoading={!ready}>
            Logout
          </Button>
          {wallet && <Text>Wallet address: {wallet.address}</Text>}
        </VStack>
      </Center>
    </Box>
  );
};

export default Main;
  • Main 컴포넌트는 사용자가 인증되지 않은 경우 로그인 버튼을 표시합니다.
  • 사용자가 인증된 경우 로그아웃 버튼과 지갑 주소를 표시합니다.
  • usePrivy 훅은 Privy를 사용한 소셜 로그인을 처리하는 데 사용됩니다.
  • useZkSyncClient 훅은 ZkSyncClientContext에서 지갑을 가져오는 데 사용됩니다.
  • 사용자가 인증된 경우 임베디드 지갑 주소가 표시됩니다.

7. page.tsx 수정

  • Home 페이지를 수정하여 Privy로 로그인하고 사용자에게 임베디드 지갑을 생성합니다.
// frontend/app/page.tsx
import Main from "@/components/Main";

export default function Home() {
  return <Main />;
}

8. 프론트엔드 실행

$ yarn dev
  • 프론트엔드는 http://localhost:3000에서 실행됩니다.

소셜 로그인 테스트

 

  • 브라우저를 열고 http://localhost:3000으로 이동합니다.
  • Login 버튼을 클릭하여 Privy로 로그인합니다.
  • 로그인 후, 지갑 주소가 화면에 표시됩니다.
  • 새 사용자는 Privy 대시보드에서 확인할 수 있습니다.

프론트엔드를 Vercel에 배포

 

  • Vercel은 정적 사이트 및 서버리스 기능을 위한 클라우드 플랫폼입니다.
  • Next.js 앱에 대한 원활한 배포 경험을 제공합니다.
  • Vercel 계정에 가입하고 배포를 시작합니다.

zkSync 네트워크와 상호작용

1. ERC-721 계약 생성

  • hardhat.config.ts 파일을 수정하여 컴파일러 버전을 0.8.24로 변경합니다. 작성 당시 컴파일러의 최신 버전은 0.8.26이지만, 0.8.250.8.26은 zkSync에서 완전히 지원되지 않으므로 호환성을 위해

0.8.24를 사용합니다.

// contracts/hardhat.config.ts
const config: HardhatUserConfig = {
  defaultNetwork: "zkSyncSepoliaTestnet",
  ...
  solidity: {
    version: "0.8.24",
  },
};

export default config;
  • contracts 디렉토리에 LibroNFT.sol이라는 새 파일을 생성합니다.
// contracts/contracts/LibroNFT.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

/**
 * @title LibroNFT
 * @dev Basic ERC721 token.
 */
contract LibroNFT is ERC721 {
    uint256 private _tokenId;
    string private _tokenURI;

    constructor(string memory uri) ERC721("LibroNFT", "LIBRO") {
        _tokenURI = uri;
    }

    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        _requireOwned(tokenId);

        return _tokenURI;
    }

    /**
     * @dev Mints a new token to the sender.
     */
    function mint() external {
        uint256 tokenId = _tokenId++;
        _safeMint(msg.sender, tokenId);
    }
}
  • LibroNFT 계약은 기본 ERC-721 토큰 계약입니다.
  • mint 함수는 새 토큰을 발신자에게 민팅합니다.
  • 모든 토큰에 대해 고정된 메타데이터 URI를 갖고 있습니다. URI는 계약 배포 시 설정됩니다.

2. ERC-721 계약 배포

  • 계약을 먼저 컴파일합니다.
$ yarn hardhat compile
  • deploy 디렉토리에 deployLibroNFT.ts라는 새 파일을 생성합니다.
import { deployContract } from "./utils";

// 이 스크립트는 NFT 계약을 배포하고 네트워크에서 가능한 경우 블록 탐색기에 검증합니다.
export default async function () {
  const tokenURI =
    "https://green-main-hoverfly-930.mypinata.cloud/ipfs/QmXeQG8Kd3KT6rWaDKD9Eg2MrmRR7GG2jijgFDpcWK1Dyk";
  const contract = await deployContract("LibroNFT", [tokenURI]);
}
  • 배포 스크립트를 실행합니다.
$ yarn hardhat deploy-zksync --script deployLibroNFT.ts
yarn run v1.22.21

Starting deployment process of "LibroNFT"...
Estimated deployment cost: 0.242569857421466748 ETH

"LibroNFT" was successfully deployed:
 - Contract address: 0x76b69a3E8D11673E547d54511831d64b81Dc9ce0
 - Contract source: contracts/LibroNFT.sol:LibroNFT
 - Encoded constructor arguments: 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006268747470733a2f2f677265656e2d6d61696e2d686f766572666c792d3933302e6d7970696e6174612e636c6f75642f697066732f516d58655147384b64334b5436725761444b44394567324d726d5252374747326a696a6746447063574b3144796b000000000000000000000000000000000000000000000000000000000000

Requesting contract verification...
Your verification ID is: 14699
Contract successfully verified on zkSync block explorer!
Done in 20.29s.

3. LibroNFT.ts 생성

  • 계약 ABI 및 주소를 프론트엔드에 복사합니다.
  • ABI는 계약을 컴파일한 후 artifacts 디렉토리에서 또는 계약을 배포한 후 deployments-zk 디렉토리에서 가져올 수 있습니다.
  • libs 디렉토리에 LibroNFT.ts라는 새 파일을 생성합니다.
const LIBRO_NFT_ADDRESS = "0x76b69a3E8D11673E547d54511831d64b81Dc9ce0";
const LIBRO_NFT_ABI = [
  ...
] as const;

export { LIBRO_NFT_ADDRESS, LIBRO_NFT_ABI };

4. Main 컴포넌트 수정

  • Main 컴포넌트를 수정하여 다음 기능을 포함합니다:
    1. 계정의 ETH 잔액 표시
    2. 계정의 NFT 잔액 표시
    3. 계정에 새 NFT 민팅
"use client";

import useZkSyncClient from "@/hooks/useZkSyncClient";
import { LIBRO_NFT_ABI, LIBRO_NFT_ADDRESS } from "@/libs/LibroNFT";
import { Box, Button, Center, Text, VStack } from "@chakra-ui/react";
import { usePrivy } from "@privy-io/react-auth";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { formatEther } from "viem";
import { zkSyncSepoliaTestnet } from "viem/chains";

const Main = () => {
  const { ready, authenticated, login, logout } = usePrivy();
  const { wallet, publicClient, zkSyncClient } = useZkSyncClient();

  // ====== Transaction status ======

  const [txStatus, setTxStatus] = useState<string>("");
  const [txHash, setTxHash] = useState<string>("");

  // ====== ETH balance ======

  const getEthBalance = async (): Promise<bigint> => {
    if (!wallet || !publicClient) {
      throw new Error("Wallet or public client not initialized");
    }

    const address = wallet.address as `0x${string}`;
    return await publicClient.getBalance({
      address,
    });
  };

  const { data: ethBalance } = useQuery({
    queryKey: ["balance", wallet?.address],
    queryFn: getEthBalance,
    enabled: !!wallet && !!publicClient,
    refetchInterval: 3000,
  });

  const ethBalanceValue = formatEther(ethBalance || BigInt(0));

  // ====== Token balance ======

  const getTokenBalance = async (): Promise<bigint> => {
    if (!wallet || !publicClient) {
      throw new Error("Wallet or public client not initialized");
    }

    const address = wallet.address as `0x${string}`;
    return await publicClient.readContract({
      address: LIBRO_NFT_ADDRESS as `0x${string}`,
      abi: LIBRO_NFT_ABI,
      functionName: "balanceOf",
      args: [address],
    });
  };

  const { data: tokenBalance } = useQuery({
    queryKey: ["tokenBalance", wallet?.address],
    queryFn: getTokenBalance,
    enabled: !!wallet && !!publicClient,
    refetchInterval: 3000,
  });

  const tokenBalanceValue = (tokenBalance || BigInt(0)).toString();

  // ====== Mint Libro NFT ======

  const mintLibroNFT = async () => {
    try {
      if (!wallet || !zkSyncClient) {
        throw new Error("Wallet or zkSync client not initialized");
      }

      const address = wallet.address as `0x${string}`;

      const hash = await zkSyncClient.writeContract({
        account: address,
        address: LIBRO_NFT_ADDRESS as `0x${string}`,
        abi: LIBRO_NFT_ABI,
        functionName: "mint",
        chain: zkSyncSepoliaTestnet,
      });

      const receipt = await publicClient.waitForTransactionReceipt({ hash });

      setTxHash(hash);
      setTxStatus(receipt.status);
    } catch (error) {
      console.error(error);
    }
  };

  if (!authenticated) {
    return (
      <Box
        display="flex"
        flexDirection="column"
        minHeight="100vh"
        bg="gray.300"
      >
        <Center my="auto">
          <Button onClick={login} isLoading={!ready}>
            Login
          </Button>
        </Center>
      </Box>
    );
  }

  return (
    <Box display="flex" flexDirection="column" minHeight="100vh" bg="gray.300">
      <Center my="auto">
        <VStack
          spacing={4}
          direction="column"
          alignItems="center"
          justifyContent="center"
        >
          <Button onClick={logout} isLoading={!ready}>
            Logout
          </Button>
          {wallet && (
            <VStack>
              <Text>Address: {wallet.address}</Text>
              <Text>Balance: {ethBalanceValue} ETH</Text>
              <Text>Token Balance: {tokenBalanceValue}</Text>
              <Button onClick={mintLibroNFT}>Mint Libro NFT</Button>
              {txHash && (
                <VStack>
                  <Text>Transaction Hash: {txHash}</Text>
                  <Text>Transaction Status: {txStatus}</Text>
                </VStack>
              )}
            </VStack>
          )}
        </VStack>
      </Center>
    </Box>
  );
};

export default Main;
  • Main 컴포넌트는 이제 ETH 잔액, NFT 잔액을 표시하고 계정에 새 NFT를 민팅할 수 있습니다.
  • getEthBalance 함수는 계정의 ETH 잔액을 가져오는 데 사용됩니다.
  • getTokenBalance 함수는 계정의 NFT 잔액을 가져오는 데 사용됩니다.
  • 두 잔액은 계약을 읽기 위한 퍼블릭 클라이언트를 사용하여 react-query를 통해 데이터 가져오기 및 캐싱합니다.
  • mintLibroNFT 함수는 계정에 새 NFT를 민팅하는 데 사용됩니다.
  • 민팅된 새 NFT의 거래 상태 및 해시가 표시됩니다.

5. 계정에 ETH 얻기

  • Network Faucet
    • Sepolia 테스트넷에서 다른 계정으로부터 계정에 ETH를 얻습니다.
  • zkSync Bridge
    • Sepolia 테스트넷에서 zkSync Sepolia로 ETH를 브리징하여 Privy가 생성한 임베디드 지갑 주소로 전송합니다.

 

6. NFT 민팅 테스트

 

결론

  • 이 튜토리얼에서는 Privy와 zkSync Sepolia 테스트넷을 사용한 소셜 로그인을 구현했습니다.
  • 프론트엔드는 테스트 및 데모 목적으로 Vercel에 배포되었습니다.
  • 소셜 로그인 경험은 원활했으며 NFT 민팅 프로세스도 성공적이었습니다.
  • zkSync 네트워크는 NFT 민팅을 위한 트랜잭션을 빠르게 처리하고 저렴한 가스비를 요구합니다.

다음 단계

  • 가스비 대납을 위한 커스텀 paymaster 구현
  • 멤버십 기반 가스비 대납을 구현하기 위해 필요한 사항에 대해 고민하기

참조

최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
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 29 30 31
Total
Today
Yesterday
글 보관함