🔖
Solidity基礎学習21日目(Truffle, Ganache)
NFTプロジェクト開発 - Truffle、Hardhat、Foundryの比較とTruffleでのデプロイ実習
日付: 2025年9月17日
学習内容: NFTプロジェクトの開発フレームワーク比較とTruffleを使用したNFTコントラクトのデプロイ、Ganacheでの対話、NFTのミントについて
1. 開発フレームワークの比較
1.1 Truffle、Hardhat、Foundryの特徴比較
Truffle
// Truffleの特徴
- 最も古い開発フレームワーク(2015年から)
- 豊富なエコシステムとコミュニティ
- 直感的な設定と使いやすさ
- 内蔵のテストフレームワーク
- マイグレーション機能
- Ganacheとの統合
Truffleの利点:
- 学習コストが低い: 初心者に優しい設計
- 豊富なドキュメント: 充実したチュートリアルとサンプル
- 安定性: 長期間の開発実績
- Ganache統合: ローカル開発環境との連携
Truffleの欠点:
- パフォーマンス: 他のフレームワークより遅い
- 設定の複雑さ: 大規模プロジェクトでの設定が複雑
- 依存関係: Node.jsに依存
Hardhat
// Hardhatの特徴
- モダンな開発フレームワーク
- 高速なコンパイルとテスト
- 豊富なプラグインエコシステム
- TypeScriptサポート
- デバッグ機能
- タスクシステム
Hardhatの利点:
- 高速: インクリメンタルコンパイル
- 柔軟性: プラグインによる機能拡張
- デバッグ: 優れたデバッグツール
- TypeScript: 型安全性のサポート
Hardhatの欠点:
- 学習コスト: 設定が複雑
- 新しさ: 比較的新しいフレームワーク
- 依存関係: Node.jsに依存
Foundry
// Foundryの特徴
- Rustで書かれた高速フレームワーク
- ネイティブのSolidityテスト
- 低レベルな制御
- ガス最適化
- フォージ(コンパイル・テスト)
- キャスト(ブロックチェーンとの対話)
Foundryの利点:
- 最高速度: Rustによる高速実行
- ガス最適化: 詳細なガス分析
- 低レベル制御: 細かい設定が可能
- 依存関係なし: 単体で動作
Foundryの欠点:
- 学習コスト: 高難易度
- 新しさ: 比較的新しい
- コミュニティ: まだ小さい
1.2 フレームワーク選択の指針
プロジェクト規模による選択
// 小規模プロジェクト(学習・プロトタイプ)
推奨: Truffle
理由: 学習コストが低く、迅速な開発が可能
// 中規模プロジェクト(本格的なDApp)
推奨: Hardhat
理由: バランスの取れた機能とパフォーマンス
// 大規模プロジェクト(エンタープライズ)
推奨: Foundry
理由: 最高のパフォーマンスとガス最適化
開発チームの経験による選択
// 初心者チーム
推奨: Truffle
理由: 豊富なドキュメントとコミュニティサポート
// 中級者チーム
推奨: Hardhat
理由: モダンな機能と柔軟性
// 上級者チーム
推奨: Foundry
理由: 最高のパフォーマンスと制御性
2. Truffleプロジェクトの構造
2.1 プロジェクトディレクトリ構成
truffle/
├── contracts/ # スマートコントラクト
│ └── SpaceTiger.sol # NFTコントラクト
├── migrations/ # デプロイスクリプト
│ └── 01-spacetiger-deployment.js
├── test/ # テストファイル
├── build/ # コンパイル済みコントラクト
├── truffle-config.js # Truffle設定ファイル
├── package.json # NPM設定
└── README.md # プロジェクト説明
2.2 重要なファイルの役割
truffle-config.js
module.exports = {
networks: {
ganache: {
host: "127.0.0.1",
port: 8545,
network_id: "*",
}
},
compilers: {
solc: {
version: "0.8.24",
settings: {
optimizer: {
enabled: true,
runs: 200
},
evmVersion: "shanghai"
}
}
}
};
設定の説明:
- networks: 接続先ブロックチェーンネットワークの設定
- compilers: Solidityコンパイラの設定
- optimizer: ガス最適化の設定
- evmVersion: EVMバージョンの指定
package.json
{
"name": "spacetiger-nft",
"version": "1.0.0",
"dependencies": {
"@openzeppelin/contracts": "^5.4.0"
},
"scripts": {
"compile": "truffle compile",
"migrate": "truffle migrate --network ganache",
"test": "truffle test"
}
}
3. SpaceTiger NFTコントラクトの実装
3.1 コントラクトの基本構造
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract SpaceTiger is ERC721, ERC721URIStorage, Ownable {
uint256 private _nextTokenId;
constructor(address initialOwner)
ERC721("SpaceTiger", "STG")
Ownable(initialOwner)
{}
function _baseURI() internal pure override returns (string memory) {
return "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/";
}
function safeMint(address to, string memory uri)
public
onlyOwner
returns (uint256)
{
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
return tokenId;
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
3.2 継承関係の詳細
ERC721の継承
// ERC721の基本機能
contract SpaceTiger is ERC721 {
// 基本NFT機能
// - transferFrom()
// - approve()
// - ownerOf()
// - balanceOf()
}
ERC721URIStorageの継承
// 個別URI管理機能
contract SpaceTiger is ERC721, ERC721URIStorage {
// 個別URI機能
// - _setTokenURI()
// - tokenURI()
}
Ownableの継承
// 所有者権限管理
contract SpaceTiger is ERC721, ERC721URIStorage, Ownable {
// 所有者機能
// - onlyOwner修飾子
// - owner()
// - transferOwnership()
}
3.3 重要な関数の詳細
safeMint関数
function safeMint(address to, string memory uri)
public
onlyOwner
returns (uint256)
{
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
return tokenId;
}
機能の説明:
- onlyOwner: 所有者のみが実行可能
- _nextTokenId++: 後置インクリメントでユニークID生成
- _safeMint: 安全なNFT発行
- _setTokenURI: メタデータURIの設定
_baseURI関数
function _baseURI() internal pure override returns (string memory) {
return "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/";
}
機能の説明:
- ベースURI: 全NFTの共通ベースURL
- 内部関数: コントラクト内部でのみ使用
- pure: 状態を変更しない
- override: 親クラスの関数をオーバーライド
4. デプロイスクリプトの実装
4.1 マイグレーションファイル
const SpaceTiger = artifacts.require("SpaceTiger");
module.exports = function(deployer, network, accounts) {
// デプロイ時にinitialOwnerを指定
// accounts[0]はデプロイに使用されるアカウント
console.log("Network:", network);
console.log("Deployer:", accounts[0]);
deployer.deploy(SpaceTiger, accounts[0]);
};
4.2 デプロイプロセスの詳細
デプロイの流れ
// 1. コントラクトのコンパイル
npx truffle compile
// 2. Ganacheネットワークへのデプロイ
npx truffle migrate --network ganache
// 3. デプロイ結果の確認
npx truffle console --network ganache
デプロイ成功時の出力
// デプロイ成功時の出力例
Deploying 'SpaceTiger'
----------------------
> transaction hash: 0x1234...
> contract address: 0x5678...
> block number: 123
> block timestamp: 1640995200
> account: 0x9abc...
> balance: 99.9 ETH
> gas used: 1234567
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.02469134 ETH
5. Ganacheでの対話
5.1 Truffle Consoleの起動
# Ganacheネットワークでコンソールを起動
npx truffle console --network ganache
5.2 コントラクトインスタンスの取得
// コンソール内でのコマンド
// コントラクトのインスタンスを取得
let spaceTiger = await SpaceTiger.deployed()
// コントラクトの基本情報を確認
await spaceTiger.name() // "SpaceTiger"
await spaceTiger.symbol() // "STG"
await spaceTiger.owner() // デプロイ者のアドレス
spaceTiger.address // コントラクトアドレス
5.3 アカウント情報の確認
// 利用可能なアカウントを取得
const accounts = await web3.eth.getAccounts()
// アカウント情報の表示
console.log("Account 0:", accounts[0]) // デプロイ者
console.log("Account 1:", accounts[1]) // 受信者
console.log("Account 2:", accounts[2]) // 受信者
6. NFTのミント(Mint)実習
6.1 基本的なミント操作
// コントラクトインスタンスとアカウントを取得
let spaceTiger = await SpaceTiger.deployed()
const accounts = await web3.eth.getAccounts()
// NFTをミント(accounts[1]にspacetiger_1.jsonをミント)
// 注意: safeMintはオーナー(accounts[0])のみが実行可能
await spaceTiger.safeMint(accounts[1], "spacetiger_1.json")
// 返り値にはトランザクション情報が含まれる
// トランザクションハッシュやイベントログを確認可能
6.2 ミントされたNFTの詳細確認
// NFTの所有者を確認(tokenId: 0の所有者を取得)
await spaceTiger.ownerOf(0)
// → accounts[1]のアドレスが返される
// NFTのメタデータURIを確認
await spaceTiger.tokenURI(0)
// → "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacetiger_1.json"が返される
// 複数のNFTをミントした場合の例
await spaceTiger.safeMint(accounts[2], "spacetiger_2.json") // tokenId: 1
await spaceTiger.safeMint(accounts[3], "spacetiger_3.json") // tokenId: 2
// それぞれのNFTの所有者を確認
await spaceTiger.ownerOf(1) // → accounts[2]
await spaceTiger.ownerOf(2) // → accounts[3]
// それぞれのトークンURIを確認
await spaceTiger.tokenURI(1) // → ベースURI + "spacetiger_2.json"
await spaceTiger.tokenURI(2) // → ベースURI + "spacetiger_3.json"
6.3 高度なNFT操作
// 特定アドレスが所有するNFTの数を確認
await spaceTiger.balanceOf(accounts[1])
// NFTの転送(accounts[1]からaccounts[2]へtokenId:0を転送)
// 注意: 転送はトークンの所有者のみが実行可能
await spaceTiger.transferFrom(accounts[1], accounts[2], 0, {from: accounts[1]})
// 転送後の所有者を確認
await spaceTiger.ownerOf(0) // → accounts[2]
// 承認の設定
await spaceTiger.approve(accounts[3], 0, {from: accounts[2]})
// 承認されたアドレスからの転送
await spaceTiger.transferFrom(accounts[2], accounts[3], 0, {from: accounts[3]})
7. スクリプトを使用した対話
7.1 対話スクリプトの作成
// interact.jsファイルを作成
const SpaceTiger = artifacts.require("SpaceTiger");
module.exports = async function(callback) {
try {
const spaceTiger = await SpaceTiger.deployed();
console.log("=== SpaceTiger Contract Information ===");
console.log("Contract address:", spaceTiger.address);
console.log("Contract name:", await spaceTiger.name());
console.log("Contract symbol:", await spaceTiger.symbol());
console.log("Contract owner:", await spaceTiger.owner());
// アカウント情報の取得
const accounts = await web3.eth.getAccounts();
console.log("\n=== Available Accounts ===");
for(let i = 0; i < 3; i++) {
console.log(`Account ${i}:`, accounts[i]);
}
// NFTのミント
console.log("\n=== Minting NFT ===");
const mintResult = await spaceTiger.safeMint(accounts[1], "spacetiger_1.json");
console.log("Mint transaction:", mintResult.tx);
// ミントされたNFTの確認
console.log("\n=== NFT Details ===");
console.log("Owner of token 0:", await spaceTiger.ownerOf(0));
console.log("Token URI:", await spaceTiger.tokenURI(0));
console.log("Balance of account 1:", await spaceTiger.balanceOf(accounts[1]));
callback();
} catch (error) {
console.error("Error:", error);
callback(error);
}
};
7.2 スクリプトの実行
# スクリプトを実行
npx truffle exec interact.js --network ganache
7.3 スクリプト実行結果
// スクリプト実行時の出力例
=== SpaceTiger Contract Information ===
Contract address: 0x1234567890abcdef...
Contract name: SpaceTiger
Contract symbol: STG
Contract owner: 0x9abcdef123456789...
=== Available Accounts ===
Account 0: 0x9abcdef123456789...
Account 1: 0x1234567890abcdef...
Account 2: 0x567890abcdef1234...
=== Minting NFT ===
Mint transaction: 0xabcdef1234567890...
=== NFT Details ===
Owner of token 0: 0x1234567890abcdef...
Token URI: https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacetiger_1.json
Balance of account 1: 1
8. メタデータの構造
8.1 NFTメタデータの標準
{
"name": "SpaceTiger #1",
"description": "A majestic space tiger exploring the cosmos",
"image": "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacetiger_1.png",
"attributes": [
{
"trait_type": "Background",
"value": "Nebula"
},
{
"trait_type": "Fur Color",
"value": "Silver"
},
{
"trait_type": "Eyes",
"value": "Glowing Blue"
},
{
"trait_type": "Rarity",
"value": "Legendary"
}
]
}
8.2 メタデータの重要性
NFTの価値決定要因
// メタデータの要素
{
"name": "NFTの名前", // 識別性
"description": "NFTの説明", // ストーリー性
"image": "画像URL", // 視覚的価値
"attributes": [ // 希少性
{
"trait_type": "特性名",
"value": "特性値"
}
]
}
メタデータの活用
// メタデータに基づく機能
function getRarityScore(uint256 tokenId) public view returns (uint256) {
// メタデータのattributesから希少性スコアを計算
// 例:Legendary = 100, Rare = 50, Common = 10
}
function getTraitValue(uint256 tokenId, string memory traitType) public view returns (string memory) {
// 特定の特性値を取得
// 例:traitType = "Fur Color" → "Silver"
}
9. トラブルシューティング
9.1 よくあるエラーと解決方法
コンパイルエラー
# エラー例
Error: Compilation failed. See above.
# 解決方法
1. Solidityバージョンの確認
2. OpenZeppelinバージョンの確認
3. インポートパスの確認
デプロイエラー
# エラー例
Error: insufficient funds for gas * price + value
# 解決方法
1. Ganacheの起動確認
2. アカウントの残高確認
3. ガス制限の調整
ミントエラー
# エラー例
Error: VM Exception while processing transaction: revert
# 解決方法
1. onlyOwner権限の確認
2. 受信者アドレスの確認
3. URI形式の確認
9.2 デバッグのベストプラクティス
ログの活用
// デバッグ用のログ出力
console.log("Current account:", accounts[0]);
console.log("Contract address:", spaceTiger.address);
console.log("Owner:", await spaceTiger.owner());
段階的なテスト
// 1. 基本情報の確認
await spaceTiger.name()
await spaceTiger.symbol()
// 2. 権限の確認
await spaceTiger.owner()
// 3. ミントの実行
await spaceTiger.safeMint(accounts[1], "test.json")
// 4. 結果の確認
await spaceTiger.ownerOf(0)
10. 実用的な応用
10.1 バッチミント機能
// 複数NFTの一括ミント
function batchMint(address[] memory recipients, string[] memory uris) public onlyOwner {
require(recipients.length == uris.length, "Array length mismatch");
for(uint i = 0; i < recipients.length; i++) {
safeMint(recipients[i], uris[i]);
}
}
10.2 条件付きミント
// 条件付きミント
mapping(address => bool) public whitelist;
uint256 public constant MAX_SUPPLY = 1000;
function whitelistMint(string memory uri) public {
require(whitelist[msg.sender], "Not whitelisted");
require(_nextTokenId < MAX_SUPPLY, "Max supply reached");
safeMint(msg.sender, uri);
}
10.3 ロイヤリティ機能
// ロイヤリティの実装
uint256 public constant ROYALTY_PERCENTAGE = 250; // 2.5%
function royaltyInfo(uint256 tokenId, uint256 salePrice)
public view returns (address, uint256) {
return (owner(), (salePrice * ROYALTY_PERCENTAGE) / 10000);
}
11. セキュリティの考慮事項
11.1 アクセス制御
// 適切なアクセス制御
modifier onlyOwner() {
require(msg.sender == owner(), "Not the owner");
_;
}
// 緊急停止機能
bool public paused;
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
function emergencyPause() public onlyOwner {
paused = true;
}
11.2 リエントランシー攻撃への対策
// リエントランシー攻撃の防止
bool private _locked;
modifier nonReentrant() {
require(!_locked, "ReentrancyGuard: reentrant call");
_locked = true;
_;
_locked = false;
}
function safeMint(address to, string memory uri)
public onlyOwner nonReentrant returns (uint256) {
// ミント処理
}
12. 学習の成果
12.1 習得した概念
- 開発フレームワーク比較: Truffle、Hardhat、Foundryの特徴と選択指針
- Truffleプロジェクト構造: ディレクトリ構成とファイルの役割
- NFTコントラクト実装: ERC721標準の実装と継承関係
- デプロイプロセス: マイグレーションとGanacheでのデプロイ
- コントラクト対話: Truffle Consoleとスクリプトでの対話
- NFTミント: 安全なNFT発行とメタデータ管理
12.2 実装スキル
- Truffleプロジェクトの設定と管理
- ERC721コントラクトの実装とカスタマイズ
- デプロイスクリプトの作成と実行
- Ganacheでの開発環境の構築
- NFTのミントと管理の実装
- メタデータの構造と標準化
12.3 技術的な理解
- 開発フレームワーク: 各フレームワークの特徴と使い分け
- NFT標準: ERC721の実装と拡張機能
- デプロイメント: ブロックチェーンへのコントラクト展開
- ローカル開発: Ganacheでの開発環境構築
- メタデータ: NFTの価値決定要因
- セキュリティ: スマートコントラクトのセキュリティ考慮事項
13. 今後の学習への応用
13.1 Hardhatでの実装
- Hardhatプロジェクトの設定と管理
- TypeScriptサポートの活用
- プラグインエコシステムの利用
- デバッグ機能の活用
13.2 Foundryでの実装
- Rustベースの高速開発環境
- ガス最適化の詳細分析
- 低レベル制御の活用
- フォージとキャストの使用
13.3 高度なNFT機能
- 動的メタデータの実装
- ロイヤリティシステムの構築
- マーケットプレイスとの連携
- クロスチェーン対応の実装
参考:
Discussion