ERC6551という規格が6月頃に登場し、一時話題になりました。ここ数日でもギャルバースが導入して話題になっています。ではERC6551とはなんなのでしょうか?どのように実装するのでしょうか?
ERC6551とは
ERC6551はトークンバウンドアカウントというNFTに紐づいたスマートアカウントを作成する規格です。詳しくは以下の記事を参照して下さい。
【完全保存版】ERC6551とは何か。トークンバウンドアカウントについて学びましょう。
またスマートアカウントについての解説はこちらを参照してください。
デモアプリの作成
今回はこちらの記事を参考に進めていきます。
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'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;
上記の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]);
以下2つを完成させます。
- 新しい「TBA」を「newSmartWallet」関数で作成すること
- 「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できるようになりました。
NFTを他のウォレットにTransferしてみます。
もう一度ERC20をClaimしようとすると、元々NFTのオーナーだったアドレスでは失敗し、Transfer後にオーナーとなったアドレスでは成功しました。Smart Walletのコントロール権限がNFTに追従して移動したことが分かります(Token Bound)。
コードはこちらに掲載しておきます。
まとめ
ERC6551をDapps上で作成・やり取りする実装を見ていきました。今回はimplementationコントラクトとしてThirdweb提供のものを使いましたが、ここに独自ロジックを乗せれば色々と特殊なTBAも作成可能です。ERC6551を導入したいNFTプロジェクトのオーナーはお問い合わせください。
弊社Pontechはweb3に関わる開発を得意とするテック企業です。サービス開発に関するご相談はこちらのフォームからお願いいたします。
また、受託開発案件に共に取り組むメンバーを募集しています!ご興味のある方はぜひお話させてください!