おめでとうございます! Solidityの学習もいよいよ実践的なDApp開発の段階に入りました。これまでのステップで学んだ知識を活かして、ブロックチェーン上で最もポピュラーなアプリケーションの一つである「トークン」と「NFT」の基本的な発行コントラクトを作成してみましょう。
このステップでは、ERC-20(代替可能トークン)やERC-721(非代替性トークン、NFT)の標準規格に準拠しない、非常にシンプルなバージョンを作成します。目的は、トークンやNFTがどのように機能するかの基本的な概念を理解することです。本格的な標準規格については、Step 8で詳しく学びます。
1. 簡単な代替可能トークン(Fungible Token)コントラクト
代替可能トークンは、どれも同じ価値を持つトークンです。例えば、日本円や米ドルのような通貨、あるいは特定のサービスのポイントなどがこれにあたります。基本的な機能は以下の通りです。
- 総供給量 (Total Supply): 発行されるトークンの総数。
- 残高 (Balance): 各アドレスが保有するトークンの量。
- 送金 (Transfer): あるアドレスから別のアドレスへトークンを送る機能。
以下に、これらの基本機能を持つシンプルなトークンコントラクトの例を示します。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleToken { string public name = "Simple Token"; // トークン名 string public symbol = "SIM"; // トークンシンボル uint256 public totalSupply; // 総供給量 // 各アドレスの残高を記録するマッピング mapping(address => uint256) public balances; // コントラクト作成者 address public owner; // イベント: トークンが送金されたときに発行 event Transfer(address indexed from, address indexed to, uint256 value); // コンストラクタ: コントラクトデプロイ時に実行 constructor(uint256 _initialSupply) { totalSupply = _initialSupply; // デプロイ者のアドレスに初期供給量を割り当てる balances[msg.sender] = _initialSupply; owner = msg.sender; // デプロイ者をオーナーに設定 } // 指定されたアドレスにトークンを送金する関数 function transfer(address _to, uint256 _value) public returns (bool success) { // 送金元に十分な残高があるか確認 require(balances[msg.sender] >= _value, "Insufficient balance"); // 送金元の残高を減らす balances[msg.sender] -= _value; // 送金先の残高を増やす balances[_to] += _value; // Transferイベントを発行 emit Transfer(msg.sender, _to, _value); return true; } // 指定されたアドレスの残高を返す関数 function balanceOf(address _account) public view returns (uint256 balance) { return balances[_account]; }
}
コード解説
name
,symbol
: トークンの名前とシンボルを定義します。totalSupply
: 発行されるトークンの総量を格納します。balances
:mapping
を使用して、各アドレスがどれだけのトークンを持っているかを記録します。owner
: コントラクトをデプロイしたアドレスを記録します(ここでは簡単な例として)。Transfer
イベント: トークンの移動が発生したことを外部に通知します。constructor
: コントラクトがデプロイされるときに一度だけ実行されます。初期供給量 (_initialSupply
) を設定し、デプロイ者に全トークンを割り当てます。transfer
関数:msg.sender
(関数呼び出し元) から_to
(送金先) へ_value
量のトークンを送ります。require
で残高が十分かを確認しています。balanceOf
関数: 特定のアドレスの残高を照会できます。
approve
や transferFrom
などの機能は含まれていません。 2. 簡単なNFT(Non-Fungible Token)コントラクト
NFTは、それぞれが固有の価値や情報を持つ、代替不可能なトークンです。デジタルアート、ゲーム内アイテム、会員権など、ユニークなものを表現するのに使われます。基本的な機能は以下の通りです。
- 固有ID (Token ID): 各NFTを一意に識別するための番号。
- 所有権 (Ownership): 各NFTがどのアドレスに所有されているか。
- 発行 (Mint): 新しいNFTを作成し、特定のアドレスに割り当てる機能。
- 転送 (Transfer): NFTの所有権を別のアドレスに移す機能。
以下に、これらの基本機能を持つシンプルなNFTコントラクトの例を示します。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleNFT { string public name = "Simple NFT"; // NFTコレクション名 string public symbol = "SNFT"; // NFTシンボル // 各トークンIDの所有者を記録するマッピング mapping(uint256 => address) public ownerOf; // 各アドレスが保有するNFTの数を記録するマッピング mapping(address => uint256) public balanceOf; // 発行されたNFTの総数 (次のトークンIDとしても使用) uint256 private _currentTokenId = 0; // イベント: NFTが転送されたときに発行 event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); // 新しいNFTを発行 (mint) する関数 // 簡単のため、誰でも発行できるようにしています (実際の利用ではアクセス制御が必要) function mint(address _to) public returns (uint256) { uint256 newTokenId = _currentTokenId; // 新しいトークンIDの所有者を _to に設定 ownerOf[newTokenId] = _to; // _to の保有数を増やす balanceOf[_to]++; // Transferイベントを発行 (発行時は from アドレスを 0x0 とする慣習) emit Transfer(address(0), _to, newTokenId); // 次のトークンIDをインクリメント _currentTokenId++; return newTokenId; } // NFTの所有権を転送する関数 function transferFrom(address _from, address _to, uint256 _tokenId) public { // 呼び出し元が所有者であることを確認 require(ownerOf[_tokenId] == msg.sender, "Caller is not the owner"); // 送信元と受信先が正しいか確認 (簡単のため _from のチェックは省略) require(_from == ownerOf[_tokenId], "From address is not owner"); require(_to != address(0), "Cannot transfer to zero address"); // 所有者の保有数を減らす balanceOf[_from]--; // 受信者の保有数を増やす balanceOf[_to]++; // トークンIDの所有者を更新 ownerOf[_tokenId] = _to; // Transferイベントを発行 emit Transfer(_from, _to, _tokenId); }
}
コード解説
name
,symbol
: NFTコレクションの名前とシンボル。ownerOf
:mapping
を使い、各トークンID (uint256
) がどのアドレスに所有されているかを記録します。balanceOf
:mapping
を使い、各アドレスがいくつのNFTを所有しているかを記録します。_currentTokenId
: 新しく発行するNFTのIDを管理します。発行ごとに1ずつ増やします。private
なのでコントラクト内部からのみアクセス可能です。Transfer
イベント: NFTの所有権が移動したときに通知します。mint
関数: 新しいNFTを発行します。_to
アドレスに新しいトークンIDを割り当て、関連するマッピングを更新し、イベントを発行します。最後に_currentTokenId
を増やします。注意: この例では誰でもmintできてしまうため、実際にはアクセス制御 (例:onlyOwner
modifier) が必要です。transferFrom
関数: NFTの所有権を移転します。msg.sender
が現在の所有者であることを確認し、所有者情報 (ownerOf
) と各アドレスの保有数 (balanceOf
) を更新します。
3. デプロイとテスト
これらのコントラクトは、Remix IDEやHardhatなどの開発環境を使ってデプロイし、テストすることができます。
- コンパイル: 上記のコードを開発環境に貼り付け、Solidityコンパイラでコンパイルします。
- デプロイ:
- SimpleToken: デプロイ時にコンストラクタ引数として初期供給量 (例: `1000000`) を指定します。
- SimpleNFT: コンストラクタに引数はありません。
- 対話: デプロイ後、開発環境のインターフェースを使って関数を呼び出してみましょう。
- SimpleToken:
balanceOf
で自分の残高を確認し、transfer
で別のアドレス(テスト用アカウントなど)にトークンを送ってみます。Transfer
イベントがログに出力されることも確認しましょう。 - SimpleNFT:
mint
で自分や別のアドレスにNFTを発行してみます。発行されたトークンIDを確認し、ownerOf
で所有者を、balanceOf
で保有数を確認します。その後、transferFrom
でNFTを別のアドレスに転送してみましょう。
- SimpleToken:
これらの操作を通じて、トークンやNFTがブロックチェーン上でどのように管理・移転されるかの具体的なイメージを掴むことができます。開発環境の使い方については、Step 1やStep 6の内容を参考にしてください。
4. 次のステップへ
今回は、トークンとNFTの非常に基本的な仕組みを実装しました。これにより、状態変数、マッピング、関数、イベントなどが実際にどのように使われるかを体験できたはずです。
しかし、実際のDApp開発では、互換性やセキュリティのために標準規格に従うことが非常に重要です。次のステップ (Step 8) では、業界標準である以下の規格について詳しく学び、実装に挑戦します。
- ERC-20: 代替可能トークンの標準規格
- ERC-721: 非代替性トークン(NFT)の標準規格
- ERC-1155: 代替可能トークンとNFTの両方を扱えるマルチトークン標準
標準規格を学ぶことで、より本格的で汎用的なスマートコントラクトを作成できるようになります。頑張っていきましょう!
参考情報
- Solidity 公式ドキュメント: Solidityの言語仕様に関する公式ドキュメントです。 https://docs.soliditylang.org/
- Ethereum ERC-20 Token Standard: ERC-20規格の公式な定義です。 https://eips.ethereum.org/EIPS/eip-20
- Ethereum ERC-721 Non-Fungible Token Standard: ERC-721規格の公式な定義です。 https://eips.ethereum.org/EIPS/eip-721
- OpenZeppelin Contracts: セキュアで標準に準拠したスマートコントラクト実装のライブラリです。ERC-20やERC-721の実装が含まれており、学習や開発に役立ちます。 https://docs.openzeppelin.com/contracts/4.x/