まいにちDapps#16 Token Bound Account (ERC6551) を導入する

ponta

ponta

· 4 min read
Thumbnail

ERC6551という規格が6月頃に登場し、一時話題になりました。ここ数日でもギャルバースが導入して話題になっています。ではERC6551とはなんなのでしょうか?どのように実装するのでしょうか?

ERC6551とは

ERC6551はトークンバウンドアカウントというNFTに紐づいたスマートアカウントを作成する規格です。詳しくは以下の記事を参照して下さい。

【完全保存版】ERC6551とは何か。トークンバウンドアカウントについて学びましょう。

またスマートアカウントについての解説はこちらを参照してください。

まいにちDapps#13 ERC4337を理解する

デモアプリの作成

今回はこちらの記事を参考に進めていきます。

How to Create Token Bound Accounts (ERC-6551)

Nextのアプリを作成していきます。

npx thirdweb create app


constants.tsというファイルを作成し、定数を定義します。

import { Mumbai, Chain } from '@thirdweb-dev/chains'

// your token bound factory address
export const factoryAddress: string = '<your-factory-address>'

// Your thirdweb api key - you can get one at https://thirdweb.com/dashboard/api-keys
export const TWApiKey: string =
    process.env.NEXT_PUBLIC_TEMPLATE_CLIENT_ID
export const activeChain: Chain = Mumbai

export const nftDropAddress: string = '<your-nft-drop-contract-address>'
export const tokenAddress: string = '<your-token-drop-contract-address>'

今回のアプリではERC721としてNFTDrop、ERC20としてTokenDrop、そしてアカウントとしてTBAのimplementationを利用します。それぞれコントラクトをデプロイしてください。そしてAPIキーも準備してください。

factoryAddressにはこちらのregistry addressを入れてください。

_app.tsxを以下のようにします。

import type { AppProps } from "next/app";
import {
  ThirdwebProvider,
  coinbaseWallet,
  metamaskWallet,
  walletConnect,
} from "@thirdweb-dev/react";
import "../styles/globals.css";
import { TWApiKey, activeChain } from "../const/constants";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ThirdwebProvider
      activeChain={activeChain}
      supportedWallets={[metamaskWallet(), coinbaseWallet(), walletConnect()]}
      clientId={TWApiKey}
    >
      <Component {...pageProps} />
    </ThirdwebProvider>
  );
}

export default MyApp;

index.tsxをこのようにします。componentはgithubのサンプルから取得してきました。

import type { NextPage } from "next";
import styles from "../styles/Home.module.css";
import NFTGrid from "../components/NFT/NFTGrid";
import {
  ConnectWallet,
  useAddress,
  useContract,
  useOwnedNFTs,
  Web3Button,
  useClaimNFT,
} from "@thirdweb-dev/react";
import { nftDropAddress } from "../const/constants";

/**
 * The home page of the application.
 */
const Home: NextPage = () => {
  const address = useAddress();
  const { contract: nftDropContract } = useContract(nftDropAddress, "nft-drop");
  const { data: nfts, isLoading } = useOwnedNFTs(nftDropContract, address);
  return (
    <div>
      {address ? (
        <div className={styles.container}>
          <h1>Your NFTs</h1>
          <p>
            Browse the NFTs inside your personal wallet, select one to connect a
            token bound smart wallet & view it&apos;s balance.
          </p>
          <NFTGrid
            nfts={nfts}
            isLoading={isLoading}
            emptyText={
              "Looks like you don't own any NFTs. Did you import your contract on the thirdweb dashboard? https://thirdweb.com/dashboard"
            }
          />
          <div className={styles.btnContainer}>
            <Web3Button
              contractAddress={nftDropAddress}
              action={async (contract) => await contract?.erc721.claim(1)}
            >
              Claim NFT
            </Web3Button>
          </div>
        </div>
      ) : (
        <div className={styles.container}>
          <h2>Connect a personal wallet to view your owned NFTs</h2>
          <ConnectWallet />
        </div>
      )}
    </div>
  );
};

export default Home;
Image

上記のNFT一覧からトークンを選択した際に、トークン個別のページへ遷移して紐づくTBAを操作するページを作りたいので、pages/contract/[contractAddress]/[tokenId].tsxを作成します。

export const getStaticProps: GetStaticProps = async (context) => {
  const tokenId = context.params?.tokenId as string;

  const sdk = new ThirdwebSDK(activeChain, {
    secretKey: process.env.SECRET_KEY,
  });

  const contract = await sdk.getContract(nftDropAddress);

  const nft = await contract.erc721.get(tokenId);

  let contractMetadata;

  try {
    contractMetadata = await contract.metadata.get();
  } catch (e) {}

  return {
    props: {
      nft,
      contractMetadata: contractMetadata || null,
    },
    revalidate: 1, // https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration
  };
};

export const getStaticPaths: GetStaticPaths = async () => {
  const sdk = new ThirdwebSDK(activeChain, {
    secretKey: process.env.SECRET_KEY,
  });

  const contract = await sdk.getContract(nftDropAddress);

  const nfts = await contract.erc721.getAll();

  const paths = nfts.map((nft) => {
    return {
      params: {
        contractAddress: nftDropAddress,
        tokenId: nft.metadata.id,
      },
    };
  });

  return {
    paths,
    fallback: "blocking", // can also be true or 'blocking'
  };
};

useEffect内でトークンに紐づくSmart Walletがすでに作成されているかを確認します。作成されていない場合、後ほど作成するnweSmartWallet関数でsmart walletを作成します。

// create a smart wallet for the NFT
  useEffect(() => {
    const createSmartWallet = async (nft: NFT) => {
      if (nft && smartWalletAddress == null && address && wallet) {
        const smartWallet = newSmartWallet(nft);
        console.log("personal wallet", address);
        await smartWallet.connect({
          personalWallet: wallet,
        });
        setSigner(await smartWallet.getSigner());
        console.log("signer", signer);
        setSmartWalletAddress(await smartWallet.getAddress());
        console.log("smart wallet address", await smartWallet.getAddress());
        return smartWallet;
      } else {
        console.log("smart wallet not created");
      }
    };
    createSmartWallet(nft);
  }, [nft, smartWalletAddress, address, wallet]);
Image

以下2つを完成させます。

  1. 新しい「TBA」を「newSmartWallet」関数で作成すること
  2. 「SmartWalletConnected」 - ウォレットの情報を表示するコンポーネントで、トークンの請求やウォレットの残高表示も含む

では、新しい「newSmartWallet」関数を作成しましょう。TBAスマートウォレットを作成するために、ERC-6551 registry factory コントラクトを使ってimplementationをクローンします。コンポーネントの中に「SmartWallet」というファイルを作成し、その中に「SmartWallet.ts」というファイルを作成す。このファイルには、スマートウォレットを作成するためのロジックが含まれていて、ERC-6551レジストリを使用してスマートウォレットを設定するには、以下の内容を追加します。

import { ethers } from "ethers";
import { SmartWallet } from "@thirdweb-dev/wallets";
import {
  TWApiKey,
  factoryAddress,
  activeChain,
  nftDropAddress,
  implementation,
} from "../../const/constants";
import { SmartContract, NFT } from "@thirdweb-dev/sdk";
import { WalletOptions } from "@thirdweb-dev/wallets";
import type { SmartWalletConfig } from "@thirdweb-dev/wallets";
import type { BaseContract } from "ethers";

export default function newSmartWallet(token: NFT) {
  //Smart Wallet config object
  const config: WalletOptions<SmartWalletConfig> = {
    chain: activeChain, // the chain where your smart wallet will be or is deployed
    factoryAddress: factoryAddress, // your own deployed account factory address
    thirdwebApiKey: TWApiKey, // obtained from the thirdweb dashboard
    gasless: true, // enable or disable gasless transactions
    factoryInfo: {
      createAccount: async (
        factory: SmartContract<BaseContract>,
        owner: string
      ) => {
        const account = factory.prepare("createAccount", [
          implementation,
          activeChain.chainId,
          nftDropAddress,
          token.metadata.id,
          0,
          ethers.utils.toUtf8Bytes("")
        ]);
        console.log("here", account);
        return account;
      }, // the factory method to call to create a new account
      getAccountAddress: async (
        factory: SmartContract<BaseContract>,
        owner: string
      ) => {
        return factory.call("account", [
          implementation,
          activeChain.chainId,
          nftDropAddress,
          token.metadata.id,
          0
        ]);
      }, // the factory method to call to get the account address
    },
  };
  return new SmartWallet(config);
}

Smart Walletが生成されて、ERC20がmintできるようになりました。

Image

NFTを他のウォレットにTransferしてみます。

Image

もう一度ERC20をClaimしようとすると、元々NFTのオーナーだったアドレスでは失敗し、Transfer後にオーナーとなったアドレスでは成功しました。Smart Walletのコントロール権限がNFTに追従して移動したことが分かります(Token Bound)。

コードはこちらに掲載しておきます。

まとめ

ERC6551をDapps上で作成・やり取りする実装を見ていきました。今回はimplementationコントラクトとしてThirdweb提供のものを使いましたが、ここに独自ロジックを乗せれば色々と特殊なTBAも作成可能です。ERC6551を導入したいNFTプロジェクトのオーナーはお問い合わせください。

弊社Pontechはweb3に関わる開発を得意とするテック企業です。サービス開発に関するご相談はこちらのフォームからお願いいたします。

また、受託開発案件に共に取り組むメンバーを募集しています!ご興味のある方はぜひお話させてください!

ponta

About ponta

2019年からEthereumを中心にDapp開発に従事。スキーとNBAとTWICEが好き。

Copyright © 2023 Pontech.Inc