スマートコントラクト入門 thirdwebでERC20トークン発行手順[2024年2月版]
今回の記事ではthirdwebというスマートコントラクトの開発を容易にするプラットフォームを用いたERC20トークン発行手順を紹介します。 本記事ではノーコードで進めており、ブロックチェーンの概念を学び始めた時のハンズオンとしてお試し頂ければと考えております。 スマートコントラクトとはブロックチェーン上で自動的に実行されるプログラムであり、透明性、信頼性、および自動化を実現する重要な機能です。 ブロックチェーンとスマートコントラクトの解説については以下の記事をご参照ください。 https://gaiax-blockchain.com/blockchain-first-book https://gaiax-blockchain.com/smart-contract 以下の手順ではブロックチェーンに接続するウォレットというものが必要になります。 もしお持ちでなければ、以下の記事を参照してウォレットの中でも代表的なMetaMaskの設定の事前準備が必要になります。 https://gaiax-blockchain.com/metamask-install-report Thirdwebへサインイン 以下のthirdwebのページの「Get started」をクリックします。 https://thirdweb.com/ 以下のページに遷移するので、右上の「Connect Wallet」をクリックします。 すると、ご自身のウォレットとの接続を求めるポップアップ画面が表示されるので、接続を完了します。 その際、ウォレットの署名とメールアドレスの入力を求められるのでそちらを入力、 今回は無料プランで問題ないので、クレジットカードの入力はスキップして進めて頂いて構いません。 ERC20コントラクトのデプロイ ログインが完了すると以下のダッシュボードが表示されますので、ここで「Contracts」をクリックします。 <style="margin: 30px 0 var(--wpex-el-margin-bottom); font-style: normal; font-weight: var(--wpex-heading-font-weight); font-family: 'Noto Sans'; line-height: var(--wpex-heading-line-height); letter-spacing: normal; text-transform: none; font-size: var(--wpex-text-2xl);"> ここでコントラクトのデプロイの方法を選択します。 今回は「Ready-to-deploy」をクリックします。 thirdwebで用意されているコントラクトの一覧するExploreページが表示されます。 この一覧を下にスクロールして「Token」の項目をクリックします。 ERC20のトークンのコントラクトのデプロイ画面が表示されます。 ここで「Deploy now」をクリックします。 デプロイするERC20のトークンの設定画面となります。 Name : トークンの名前[必須] Symbol : トークンの単位 Description : トークンの説明 Image : トークンの画像 Primary Sale Recipient : 最初にトークンを受け取るアドレス(デフォルトで接続している自分のアドレスが表示される)[必須] 今回、NetworkはPolygon Mumbaiテストネットを選択しています。 その他、入力項目がありますが、NameとPrimary Sale RecipientとNetworkを設定されていれば、他は空欄で問題ありません。 上記入力完了後、「Deploy now」をクリックします。 この時、Polygon mumbaiのmaticを保持していない時、以下のポップアップが表示されます。 コントラクトをデプロイするためにガス代というチェーンに対する手数料のようなものをトークンで支払う必要があります。 このオレンジの部分をクリックするとPolygon Mumbaiのmaticというトークンが無料でもらえるページへ遷移します。 ここではPolygonのDiscordに参加する手順を踏むことでmaticを受け取ることができます。 maticがある状態で実行後、ウォレットの署名をすることでデプロイが実行されます。 デプロイが完了すると上記のような画面になり、コントラクトがデプロイされたことが確認できます。 コントラクトのデプロイまでノーコードで進めることができます。 このダッシュボードからトークンのmint, transfer, burnも実行出来るので、もしよければお試しください。 Thirdwebでは他にも様々なコントラクトの生成が出来るようになっております。 初学者にはオススメなので是非お試しください。 Blockchain Biz Community入会 2/22(木)20-21時にてBlockchain Biz CommunityのDiscordでこの記事のハンズオンを行うのでもしよければご参加ください。 Blockchain Biz Community 入会申し込み Blockchain Biz Communityでは他にもブロックチェーンの勉強会や記事執筆を行う仲間も募集中なので是非お気軽にお問い合わせください。
【メタマスク×Cypress】Dappsで結合テストをするには?
Account Abstraction (ERC-4337) に触れてみよう <前編>
ethers.js v6とは? v5のマイグレーション・バージョンの違いを解説!
ethers.jsはEthereumプロジェクトのためのJavaScriptライブラリであり、v6とv5のバージョン間にはいくつかの重要な変更があります。ここでは、それらの主な変更点について解説します。説明およびサンプルコードは、公式ドキュメントから和訳・引用しています。詳細な情報はこちらを参照してください。
Import
開発者が心に留めておく必要がある最大の変更です。
v5では、プロジェクトは大きなサブパッケージの集合のmonorepoとして管理されていました。v6では、すべての Import はルートパッケージで利用可能です。また、より細かい制御を行いたい人のために、pkg.exportsは特定のフォルダを直接利用可能にしています。
v5の Import
v5では、多くのもの(全てではない)がルートパッケージで利用可能でした。
import { ethers } from "ethers"
いくつかのパッケージは追加プロパティの後ろにグループ化されていますが、
import { providers } from "ethers" const { InfuraProvider } = providers
詳細なアクセス制御を行うには、サブパッケージからのインポートが必要でした。
import { InfuraProvider } from "@ethersproject/providers"
v6の Import
v6では、すべてがルートパッケージで利用可能です。
import { ethers } from "ethers" import { InfuraProvider } from "ethers"
pkg.exportsは、詳細なアクセス制御を提供します。
import { InfuraProvider } from "ethers/providers"
Provider
これも最大の変更です。Web3Provider(これまで link-web3 Provider をラップするために使用されていた)がBrowserProviderと呼ばれるようになりました。
EIP-1193 Providerのラッピング
v5
provider = new ethers.providers.Web3Provider(window.ethereum)
v6
provider = new ethers.BrowserProvider(window.ethereum)
トランザクションのブロードキャスト
また、トランザクションをネットワークにブロードキャストする方法が変更されました。
v5
provider.sendTransaction(signedTx)
v6
provider.broadcastTransaction(signedTx)
Contracts
v6では、コントラクトがES6 Proxyであり、実行時にメソッド名を解決できます。
Ambiguous Methods(曖昧なメソッド)
v5のコントラクト
v5では、曖昧なメソッドの場合、正規化されたシグネチャでメソッドを検索する必要がありました。また、重複した定義を実行時に検出する方法がなかったため、コンソールに警告が表示されていました。
abi = [ "function foo(address bar)"、 "function foo(uint160 bar)"、 ] contract = new Contract(address, abi, provider)
// 必要なメソッドにアクセスするために、 正規化された完全修飾シグネチャを指定する必要がある
contract["foo(address)"](addr)
// シグネチャが正規化されていないため、これらは失敗する
contract["foo(address )"](addr)
contract["foo(address addr)"](addr)
// こちらもメソッドが曖昧なので、失敗する
contract.foo(addr)
v6のコントラクト
v6ではシグネチャを正規化する必要はなく、Typed APIは目的のメソッドにアクセスするためのよりクリーンな方法を提供しています。
abi = [ function foo(address bar)", function foo(uint160 bar)", ] contract = new Contract(address, abi, provider)
// どれも成功する contract["foo(address)"](addr) contract["foo(address )"](addr) contract["foo(address addr)"](addr) // これはまだ失敗する // どのメソッドが意図されたものなのかを知る方法がないため contract.foo(addr) // しかし、型付きAPIなら成功する // Contractに型付け情報を提供することができるため contract.foo(Typed.address(addr))
その他のメソッド操作
v5では、コントラクトは一連のメソッド・バケットを含み、そのメソッド・バケットに、より一般的でない操作を実行するためのすべてのシグネチャと曖昧でない名前が付けられていました。v6では、各メソッドには、それ自身のあまり一般的でない操作が直接付加されています。
v5のその他の操作
// デフォルトのアクションは、メソッドに基づいて送信または呼び出しを選択する // type (pure, view, constant, non-payable or payable) contract.foo(addr) // これは、デフォルトのアクションを実行するが // Resultオブジェクトを返す contract.functions.foo(addr) // 強制的に呼び出す contract.callStatic.foo(addr) // ガスを見積もる contract.estimateGas.foo(addr) // トランザクションを生成する contract.populateTransaction.foo(addr)
v6のその他の操作
// 動作は同じ contract.foo(addr) // 呼び出しを実行し、結果オブジェクトを直接返す contract.foo.staticCallResult(addr) // 呼び出しを強制的に使用する (支払い可能、支払い不可能の場合でも) contract.foo.staticCall(addr) // トランザクションを強制的に送信する(pureとviewの場合でも同様) contract.foo.send(addr) // ガスを見積もる contract.foo.estimateGas(addr) // トランザクションを入力する contract.foo.populateTransaction(addr)
BigNumber
v6では、BigNumberクラスが、ES2020 BigIntに置き換わりました。ES2020 BigIntは、最近のJavaScript環境で提供されている組み込みです。BigNumber と同様、ES2020 BigInt は整数しか扱えないことに注意しましょう。
JavaScriptのES2020 BigIntを使い始めるためのオンラインドキュメントはたくさんあります
( 例:ES2020の新機能 – とほほのWWW入門 )。
また、FixedNumber クラスは、固定小数点演算を行うために存在します。
大きな数値の作成
v5でBigNumberを使う
value = BigNumber.from("1000")
v6でBigIntを使う(リテラル記法を使う)
// 接尾辞 n に注意 value = 1000n // 文字列に対するBigInt関数の使用 value = BigInt("1000")
大きな数値の簡単な計算
v5 で2つの値を加算する
sum = value1.add(value2)
v6 で BigInt を使用して2つの値を加算する
// 両方の値がBigIntでなければならない sum = value1 + value2
大きな数値の単純比較
v5 で等しいかどうかをチェックする
isEqual = value1.eq(value2)
v6 で BigInt を使用
isEqual = (value1 == value2)
Signature(シグネチャ)
Signatureは、すべてのパースとシリアライズを容易にするクラスとなりました。
シグネチャ操作
v5
splitSig = splitSignature(sigBytes) sigBytes = joinSignature(splitSig)
v6
splitSig = ethers.Signature.from(sigBytes) sigBytes = ethers.Signature.from(splitSig).serialized
Transaction(トランザクション)
v5に存在したトランザクションヘルパーは、すべて Transaction クラスに含まれます。さらに、処理するためにサポートされている、任意のトランザクション形式を扱うことができます。
トランザクションの解析
v5
tx = parseTransaction(txBytes) txBytes = serializeTransaction(tx) txBytes = serializeTransaction(tx, sig)
v6
tx = Transaction.from(txBytes)
v6( tx はオプションでシグネチャを含むことができる)
txBytes = Transaction.from(tx).serialized
Utilities(ユーティリティ)
Bytes32 string helpers
v5
bytes32 = ethers.utils.formatBytes32String(text) text = ethers.utils.parseBytes32String(bytes32)
v6
bytes32 = ethers.encodeBytes32String(text) text = ethers.decodeBytes32String(bytes32)
constants(定数)
v5
ethers.constants.AddressZero ethers.constants.HashZero
v6
ethers.ZeroAddress ethers.ZeroHash
データ操作
v5
slice = ethers.utils.hexDataSlice(value, start, end) padded = ethers.utils.hexZeroPad(value, length)
v5(数値を16進数に変換する)
hex = hexlify(35)
v6
slice = ethers.dataSlice(value, start, end) padded = ethers.zeroPadValue(value, length)
v6(数値を16進数に変換する)
hex = toBeHex(35)
defaultAbiCoder
v5では、AbiCoderのプロパティを使用していました。
coder = AbiCoder.defaultAbiCoder
v6 では、AbiCoder の静的関数であり、シングルトン・パターンを使用します。初めて呼び出されたときに AbiCoder が生成され、それ以降の呼び出しではその最初のインスタンスが返されます。
coder = AbiCoder.defaultAbiCoder()
コンテンツの取得
v5(body付属、特殊なものはなし)
data = await ethers.utils.fetchJson(url, json, processFunc)
v5で接続をオーバーライドする
req = { url, user: "username", password: "password" // これらなどのプロパティは FetchRequest と同等 }; data = await ethers.utils.fetchJson(req, json, processFunc)
v6
req = new ethers.FetchRequest(url) // bodyを設定(任意) req.body = json // 認証情報を設定(任意) req.setCredentials("username", "password") // processFunc を設定する(任意) req.processFunc = processFunc // リクエストを送信する resp = await req.send()
希望する形式によって、response bodyを取得します。
// Uint8Array data = resp.body // Utf8String(無効な場合に返す) data = resp.bodyText // Object(無効な場合に返す) data = resp.bodyJson
16進数変換
v5
hex = ethers.utils.hexValue(value) array = ethers.utils.arrayify(value)
v6
hex = ethers.toQuantity(value) array = ethers.getBytes(value)
solidity 非標準パック
v5
ethers.utils.solidityPack(types, values) ethers.utils.solidityKeccak256(types, values) ethers.utils.soliditySha256(types, values)
v6
ethers.solidityPacked(types, values) ethers.solidityPackedKeccak256(types, values) ethers.solidityPackedSha256(types, values)
プロパティ操作
v5
ethers.utils.defineReadOnly(obj, "name", value)
v6
ethers.defineProperties(obj, { name: value });
commify
v5
ethers.utils.commify("1234.5")
v6では、これらのローカル固有のユーティリティの一部を削除しています。しかしこの機能は簡単に再現でき、希望の出力形式に応じて調整できます。
function commify(value) { const match = value.match(/^(-?)([0-9]*)(\.?)([0-9]*)$/); if (!match || (!match[2] && !match[4])) { throw new Error(`bad formatted number: ${ JSON.stringify(value) }`); } const neg = match[1]; const whole = BigInt(match[2] || 0).toLocaleString("en-us"); const frac = match[4] ? match[4].match(/^(.*?)0*$/)[1]: "0"; return `${ neg }${ whole }.${ frac }`; } commify("1234.5");
削除されたクラスと関数
Loggerクラスは、いくつかのErrorユーティリティ関数に置き換えられました。
checkPropertiesと shallowCopy は削除され、.map と Object.assign が使われるようになりました。
参考にしたページ
Account Abstraction と ERC-4337 :現在策定が進む新しいアカウントの概念
Next.js(App Router)でWeb3.0アプリ開発
はじめに この記事では、Web3.0アプリケーション開発現場で採用されやすいNext.jsについて、その人気の理由について考察します。さらに2023年5月にリリースされた新機能(App Router)を使って、Web3.0のアプリを作成するにあたって、App Routerの特徴を紹介しつつ、ウォレットに接続する方法をご紹介します。 Web3.0でNext.jsが人気の理由 4選 1. サーバーサイドレンダリング(SSR) Next.jsはSSR(サーバーサイドレンダリング)をサポートしており、これにより、初期読み込みの高速化やSEOの向上が可能です。Web3のアプリケーションにおいては特に、ブロックチェーン上のデータを取得して表示する必要がある場合、SSRは非常に有用です。 2. 静的サイト生成(SSG) Next.jsはSSGもサポートしています。SSGは、事前にページを生成し、静的なファイルとして提供することで、パフォーマンスを向上させます。Web3のアプリケーションでは、ブロックチェーン上のデータが頻繁に変化する場合でも、SSGを使用して静的なコンテンツを提供することができます。 3. ルーティングと状態管理 Next.jsは、ルーティング機能と状態管理を簡単に実装できる機能を提供しています。Web3のアプリケーションでは、複数の画面や状態の管理が必要な場合があります。Next.jsのルーティング機能と状態管理のツールセットは、このような要件に対応するのに役立ちます。 4. モジュールの管理 Next.jsは、npmやyarnを介してモジュールを管理するための便利なツールを提供します。Web3開発では、Web3ライブラリや関連するパッケージが多数準備されています。Next.jsのモジュール管理機能により、依存関係の解決やパッケージのバージョン管理が容易になります。 Next.jsのApp Routerとは? 2023年5月にリリースされたバージョン13.4.0から、Next.jsにおけるルーティングの仕様が大きく変更しました。これまでは、Pages Router という仕組みを使用しておりましたが、ルーティングのベースとなる機能の仕様が変更されたことにより、画面遷移がより高速になる可能性を秘めております。 以下に主な特徴を2点記載します。 1. ファイルパスの使い方を刷新 App Routerではトップレベルのフォルダとして pages/ ではなく app/ を利用します。App Routerという名前は、このフォルダ名に由来しています。 Pages Routerでは、レイアウトと呼ばれる機構を後付けした際にAPIが複雑になってしまいましたが、App Routerではレイアウトを初めから設計に組み込んでいるため、直感的に扱えるようになっています。 2. サーバーをより活用したレンダリング Pages Routerにも、画面遷移時にサーバー側でデータ取得を行う機能はありました。しかし、レンダリングまでサーバー側で請け負うのは、ページを直接リクエストする初期表示のときのみで、画面遷移後の(複雑な条件分岐や反復処理を含む)レンダリングや、ページ内の個別のコンポーネントによる非同期通信は、ブラウザ側に任されていました。 そのため、複雑なロジックが関係するアプリケーションの場合、ユーザー側の環境だけでは利用体験が制限されるケースが多々発生することになりました。 そのような課題を解決するため、App Routerでは原則として、全てのReactコンポーネントをサーバー側でレンダリングすることにしました。通信も、条件分岐も、反復処理も、全てサーバーで行うことで、ユーザーの環境によってパフォーマンスが落ちる要因を最小限にできます。 ※ここでいうレンダリングとは、あるデータに基づいて、条件分岐や反復処理を行い、コンポーネントの構造を決定する処理を指し、必ずしもHTMLの生成は伴いません。 Web3.0アプリの開発ステップ(概要) 1. スマートコントラクトの作成 Web3.0アプリの開発はまずスマートコントラクトの作成から始まります。Solidityなどの言語を用いて開発し、Alchemyなどのプロバイダーにデプロイします。このステップはアプリの基礎となるため、非常に重要です。 2. フロントエンドの開発 次に、フロントエンドの開発に移ります。ここではNext.jsなどを使用して画面を開発し、ライブラリ(例えばThirdweb.js)を使ってウォレットやプロバイダーへの接続を行います。しかし、App Routerでウォレットを接続するコードサンプルはほとんどなく、Thirdweb.jsの公式サイトでさえ、Pages Routerの例しか提供していません。 RainbowKit の導入 この問題を解決するために、RainbowKitの使用を提案します。RainbowKitはWeb3.0アプリ向けのReactライブラリで、ウォレット接続機能を簡単に実装できます。デフォルトのウォレットボタンのカスタマイズやどのウォレットを表示するかの設定も可能です。 RainbowKitの使い方 新規Next.jsプロジェクトの場合 $ npm init @rainbow-me/rainbowkit@latest 既存プロジェクトへの導入、Next.js以外の場合 $ npm install @rainbow-me/rainbowkit wagmi viem RainbowKitを使ったプロバイダーとの接続 app/provider.tsx のコード例を以下に示します。 import * as React from 'react'; import { RainbowKitProvider, getDefaultWallets, connectorsForWallets } from '@rainbow-me/rainbowkit'; import { argentWallet, trustWallet, ledgerWallet } from '@rainbow-me/rainbowkit/wallets'; import { configureChains, createConfig, WagmiConfig } from 'wagmi'; import { mainnet, polygon, optimism, arbitrum, base, zora, goerli } from 'wagmi/chains'; import { publicProvider } from 'wagmi/providers/public'; // チェーンの設定 const { chains, publicClient, webSocketPublicClient } = configureChains( [ mainnet, polygon, optimism, ...(process.env.NEXT_PUBLIC_ENABLE_TESTNETS === 'true' ? [goerli] : []), ], [publicProvider()] ); // プロジェクトID・ウォレットの設定 const projectId = 'YOUR_PROJECT_ID'; const { wallets } = getDefaultWallets({ appName: 'RainbowKit demo', projectId, chains, }); // コネクタの設定 const connectors = connectorsForWallets([ ...wallets, { groupName: 'Other', wallets: [ argentWallet({ projectId, chains }), trustWallet({ projectId, chains }), ledgerWallet({ projectId, chains }), ], }, ]); // wagmi の設定 const wagmiConfig = createConfig({ autoConnect: true, connectors, publicClient, webSocketPublicClient, }); //…