概要
【実践編②】あなただけの独自トークンをゼロから構築します。constructorで総供給量を一度だけ鋳造し、mappingで全ユーザーの残高を管理。
そして「Checks-Effects-Interactions」パターンを用いた安全なtransfer関数で送金機能を実装します。Transferイベントの発行まで、通貨の基本メカ- ニズムをコードレベルで理解できる、超実践的なレポートです。
-

-
Solidity学習講座:最終版 目次(全20話)
基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...
続きを見る
目次
はじめに
第16話では、これまでの知識を総動員して、一つの完成されたDApp「投票システム」を構築しました。様々な部品を組み合わせ、一つの目的を持ったアプリケーションを作り上げる達成感を味わっていただけたと思います。
さて、実践編の第二弾となる今回のテーマは、**「独自トークンの作成」**です。
DeFi、GameFi、DAO…今日のブロックチェーンエコシステムにおいて、ほぼ全てのプロジェクトが独自の「トークン」を発行し、それを価値の交換やガバナンス(意思決定)の手段として利用しています。トークンを理解することは、現代のDAppを理解することと同義と言っても過言ではありません。
もちろん、世界中で広く使われているトークンは「ERC20」という標準規格に準拠していますが、今回はその基礎となるトークンの本質的な機能、すなわち、
- 鋳造(Minting): トークンを生成する
- 残高管理(Balance): 誰がどれだけ持っているかを記録する
- 送金(Transfer): ある人から別の人へトークンを移転する
この3つの核心的なメカニズムを、ゼロから実装することに焦点を当てます。このレッスンを終える頃、あなたはデジタル通貨がブロックチェーン上でどのように機能するのか、その心臓部を自らの手で作り上げたという、深い理解と自信を得ていることでしょう。
ステップ1:トークンの基本設計と状態変数
まず、私たちが作るトークンがどのような性質を持つかを定義します。一般的に、トークンには以下の基本情報が必要です。
- 名前 (Name): トークンの正式名称(例: "My Simple Token")
- シンボル (Symbol): 取引所などで使われる短い略称(例: "MST")
- 総供給量 (Total Supply): この世に存在するトークンの総数
これらの情報を保存するために、string型とuint型のpublicな状態変数を定義します。
そして、最も重要なのが**「誰が、どれだけのトークンを持っているか」**を記録する台帳です。これはまさに、第7話で学んだmappingの独壇場ですね。addressをキーとして、そのアドレスが持つ残高(uint)を値とするマッピングを定義します。
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleToken {
// === トークンの基本情報 ===
string public name;
string public symbol;
uint public totalSupply;
// === 残高台帳 ===
// どのアドレス(address)が、どれだけの量(uint)のトークンを持っているか
mapping(address => uint) public balances;
}
これが、私たちのトークンコントラクトの骨格となります。
ステップ2:constructorでトークンを鋳造(ミント)する
次に、トークンを「鋳造(ミント)」、つまり生成するプロセスを実装します。今回のシンプルなモデルでは、コントラクトがデプロイされる時に一度だけ、総供給量のすべてが発行され、その全量がコントラクトの作成者に割り当てられるという仕様にします。
このような「一度きりの初期設定」に最適なのが、第11話で学んだ**constructor**です。
Solidity
contract SimpleToken {
// ... 状態変数の定義 ...
// constructor:コントラクトデプロイ時に一度だけ実行される
constructor(string memory _name, string memory _symbol, uint _initialSupply) {
// トークンの基本情報を設定
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
// 【重要】総供給量のすべてを、デプロイ者のアドレスの残高として設定
balances[msg.sender] = _initialSupply;
}
}
このconstructorは、デプロイ時にトークンの名前、シンボル、初期供給量を引数として受け取ります。そして最も重要なのがbalances[msg.sender] = _initialSupply;の行です。これにより、デプロイした瞬間に、totalSupplyと同量のトークンが、デプロイしたあなた(msg.sender)の残高としてbalancesマッピングに記録されます。最初のトークンが、ここに誕生したのです。
ステップ3:送金機能transferの実装
トークンが存在するだけでは意味がありません。それを他者へ送金できて初めて、「通貨」としての価値が生まれます。送金機能であるtransfer関数を実装しましょう。この関数は、誰でも呼び出せるpublicな関数です。
transfer関数には、**「誰に (_to)」「いくら (_amount)」**という2つの情報が必要です。そして、不正な送金が行われないよう、requireを使った厳格なチェックが欠かせません。
Solidity
// ... constructor の後ろに追記 ...
// 送金機能
function transfer(address _to, uint _amount) public {
// === Checks ===
// 1. 送金元(この関数を呼び出した人)の残高が、送金額以上であるか
require(balances[msg.sender] >= _amount, "SimpleToken: transfer amount exceeds balance");
// 2. 送金先アドレスが、ゼロアドレス(無効なアドレス)でないか
require(_to != address(0), "SimpleToken: transfer to the zero address");
// === Effects ===
// 送金元の残高を減らす
balances[msg.sender] -= _amount;
// 送金先の残高を増やす
balances[_to] += _amount;
// === Interactions ===
// この後、送金イベントを放出する
}
ここでも、「Checks-Effects-Interactions」のパターンに従っていることに注目してください。まずrequireで条件をチェックし、次に内部の状態(balancesマッピング)を変更します。これにより、リエントランシー攻撃などに対する安全性を確保しています。
ステップ4:Transferイベントの定義と放出
トークンの送金は、ブロックチェーン上で最も重要な出来事の一つです。誰から誰へ、いくら送金されたのかを、後から追跡できるように、Transferイベントを記録するのが一般的です。これはERC20規格でも定められている、非常に重要な作法です。
Solidity
contract SimpleToken {
// ... 状態変数の定義 ...
// Transferイベントの定義
event Transfer(address indexed from, address indexed to, uint value);
// ... constructor ...
// transfer関数の完成形
function transfer(address _to, uint _amount) public {
// Checks
require(balances[msg.sender] >= _amount, "SimpleToken: transfer amount exceeds balance");
require(_to != address(0), "SimpleToken: transfer to the zero address");
// Effects
balances[msg.sender] -= _amount;
balances[_to] += _amount;
// Interactions
emit Transfer(msg.sender, _to, _amount);
}
}
from(送金元)とto(送金先)にindexedを付けることで、特定のアドレスが関与する送金履歴を、外部のアプリケーションが効率的に検索できるようになります。transfer関数の最後にemit文を追加し、送金が成功したことをブロックチェーンに記録します。
完成版コードとRemixでのテスト
おめでとうございます!これがあなたの最初の独自トークンコントラクトの全コードです。
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleToken {
string public name;
string public symbol;
uint public totalSupply;
mapping(address => uint) public balances;
event Transfer(address indexed from, address indexed to, uint value);
constructor(string memory _name, string memory _symbol, uint _initialSupply) {
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
balances[msg.sender] = _initialSupply;
emit Transfer(address(0), msg.sender, _initialSupply); // 鋳造イベント
}
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount, "SimpleToken: transfer amount exceeds balance");
require(_to != address(0), "SimpleToken: transfer to the zero address");
balances[msg.sender] -= _amount;
balances[_to] += _amount;
emit Transfer(msg.sender, _to, _amount);
}
}
(constructorに、鋳造を示すためのTransferイベントを追加しました。ゼロアドレスからの送金は、一般的に新規発行を意味します。)
ぜひRemixでデプロイして、その動きを確かめてください。
- デプロイ時に、
_nameに「MyToken」、_symbolに「MYT」、_initialSupplyに1000000などを入力します。 balancesのボタンにあなたのアドレスを入力し、残高が1,000,000になっていることを確認します。- Remixのアカウント選択欄から別のアドレスをコピーし、
transfer関数の_toに貼り付け、_amountに100などを入力して実行します。 - 再度、あなたと相手のアドレスの残高を確認し、数値が正しく変動していることを確かめてください。
- ターミナルでトランザクションログを開き、
Transferイベントが正しく記録されているのを見てみましょう。
まとめ:価値の移転を実装する力
お疲れ様でした!あなたは今、デジタル通貨の根幹をなすロジックを自らの手で実装しました。これは、ブロックチェーン開発における非常に大きな一歩です。
mapping(address => uint)が、トークンの残高台帳の正体であること。constructorが、トークンの初期発行(ミント)に最適であること。- 安全な
transfer関数が、「Checks-Effects-Interactions」パターンで構築されること。 Transferイベントが、全ての価値の移転を記録する上で不可欠であること。
これらの知識は、今後あなたがより複雑なDeFiアプリケーションやNFTの仕組みを理解する上での、強固な基盤となります。
次回、**【実践編③】では、ETHのやり取りがさらに活発になる「簡易オークションコントラクト」**の開発に挑戦します。入札、最高入札者の更新、オークションの終了といった、よりダイナミックな状態変化を伴うDAppを構築し、あなたの実践スキルをさらに磨き上げていきましょう。
-

-
Solidity学習講座:最終版 目次(全20話)
基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...
続きを見る

