はじめに
前回は、日本円にペッグしたステーブルコイン(JPYS)を作り、発行(mint)・送金(transfer)・償還(burn)・管理者権限・緊急停止までを実際に動かして確認しました。供給量が現実のお金の出入りに連動して増減する、という金融に一歩踏み込んだトークンでした。
今回はさらに進んで、セキュリティトークン(証券トークン) を作ります。テーマは、いま多くの金融機関が注目している RWA(Real World Assets/現実資産のトークン化) です。
具体例として、100億円の不動産を100万口に分割するケースを考えます。1口 = 1トークン(シンボル REST)として、100万口を発行します。1口あたり1万円分の不動産の持分、というイメージです。
ここで、これまでのトークンとの決定的な違いが出てきます。
- ステーブルコイン(JPYS)は、誰でも自由に送金できました。
- しかしセキュリティトークンは証券です。証券は法規制の対象で、本人確認(KYC/AML)を通過し、許可された投資家の間でしか動かせない——つまり譲渡制限がかかります。
この記事では、参照したロードマップに沿って、セキュリティトークンに必要な4つの機能を実装します。
- トークン保有者管理(誰が何口持っているかを把握する)
- 譲渡制限(KYC済みの投資家間でしか移転できない)
- 管理者承認(発行体だけがホワイトリスト登録や強制移転を行える)
- 保有割合算出(各投資家の持分比率を出す)
これらを OpenZeppelin の監査済みライブラリを組み合わせて実装し、ローカルのHardhat上で実際に動かしながら、「なぜ証券トークンにはこれらの仕組みが必要なのか」を確認していきます。
今回作るものの全体像
以下は、不動産を口数に分割し、KYCを通過した投資家の間だけで移転する流れをイメージにしたものです。
1. そもそもセキュリティトークンとは何か
セキュリティトークン(Security Token)とは、株式・債券・不動産といった「証券(=価値の裏付けがある資産)」を、ブロックチェーン上のトークンとして表現したものです。日本では「電子的に記録された有価証券」の一種として、法律(金融商品取引法)の対象になります。
そして、証券を現実資産(不動産・貴金属・アートなど)にまで広げてトークン化する考え方が、いま話題の RWA(Real World Assets) です。
なぜ不動産を「口数」に分けるのか
100億円の不動産は、そのままでは個人が買えません。しかし100万口に分割すれば、1口=1万円分の持分として、多くの投資家が少額から投資できるようになります。
これがトークン化(Tokenization)の大きな狙いです。
- 分割できる … 高額な資産を小口に分けて売買できる
- 流動性が上がる … 従来は売買しづらかった不動産の持分を、トークンとして移転できる
- 透明性がある … 誰がどれだけ保有しているかが台帳(ブロックチェーン)に記録される
☝ ここで、前回のステーブルコインとの決定的な違いが出てきます。ステーブルコインは「通貨」なので、誰から誰へ渡っても構いませんでした。しかしセキュリティトークンは「証券」です。証券には「誰が持てるか(=投資家として適格か)」という規制があり、本人確認を通った相手にしか渡せない——この「譲渡制限」こそ、今回の主役です。
2. 普通のERC20・ステーブルコインと何が違うのか
これまで作ってきたトークンと並べて比べると、証券トークンの特徴がはっきりします。
| 機能 | MyToken(Step2) | ステーブルコイン(Step3) | セキュリティトークン(今回) |
|---|---|---|---|
| 送金相手 | 誰でも自由 | 誰でも自由 | KYC済みの投資家に限定 |
| 供給量 | 固定 | 入出金で増減(mint/burn) | 発行時に総口数を確定 |
| 管理者の役割 | なし | 発行・緊急停止 | KYC登録・強制移転・緊急停止 |
| 保有者の把握 | しない | しない | 一覧として管理する |
| 保有割合 | — | — | 持分比率を算出できる |
とくに重要なのが 「誰が保有・受け取りできるのか」 という点です。証券は、反社会的勢力やマネーロンダリングを防ぐため、本人確認(KYC/AML)を通過した投資家しか扱えません。そこで、許可された投資家をホワイトリストとして管理し、リストにない相手への送金は自動的に拒否します。
さらに証券特有の事情として、相続・秘密鍵の紛失・裁判所命令などに備え、発行体が保有を強制的に移す「強制移転」という機能も必要になります。株式に「株主名簿の書き換え」があるのと同じ発想です。
補足: 実務では、こうした証券トークンの標準規格として ERC-1400 や ERC-3643 が知られています。これらはKYC・譲渡制限・パーティション(種類株)などを本格的に扱いますが、部品が多く初学者には重いので、今回はその本質だけを最小構成で、ERC20を土台に自作します。
3. コントラクトを書く
これらを踏まえて作成したコントラクトが以下です。ERC20 に Ownable(管理者権限)と Pausable(緊急停止)を組み合わせ、そこへホワイトリスト・保有者管理・保有割合・強制移転を加えています。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// 不動産などの現実資産(RWA)を裏付けとする「セキュリティトークン(証券トークン)」。
// 例: 100億円の不動産を 100万口 に分割し、1口 = 1トークンとして表現する。
//
// ステーブルコイン(JPYS)との決定的な違いは「譲渡制限」。
// ステーブルコインは誰でも自由に送金できたが、証券は法規制上、
// 本人確認(KYC/AML)を通過し許可された投資家(ホワイトリスト)の間でしか動かせない。
//
// - ERC20 : 残高管理・送金・イベントなど、トークンの基本機能
// - Ownable : 発行体(管理者)を1人定め、ホワイトリスト登録や強制移転を制限する
// - Pausable : 緊急時・規制対応で全移転を一時停止できるようにする
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Pausable.sol';
contract RealEstateSecurityToken is ERC20, Ownable, Pausable {
// KYC/AML を通過し、保有・受け取りを許可された投資家のホワイトリスト。
// これに載っていないアドレスは、送金元にも送金先にもなれない(= 譲渡制限)。
mapping(address => bool) public whitelisted;
// 現在トークンを保有しているアドレスの一覧(保有者管理)。
// 配当の分配や議決権の集計など、証券では「誰が保有者か」を把握する必要がある。
address[] private _holders;
// 1-based のインデックス。0 は「未登録(非保有者)」を意味する。
mapping(address => uint256) private _holderIndex;
event Whitelisted(address indexed account, bool status);
event ForcedTransfer(address indexed from, address indexed to, uint256 value);
// totalUnits = 発行する総口数(例: 100万口 = 1_000_000)。
// 発行体自身をホワイトリストに登録し、全口数をいったん発行体が保有する。
// ここから KYC を通過した投資家へ配分(transfer)していく。
constructor(uint256 totalUnits)
ERC20('Real Estate Security Token', 'REST')
Ownable(msg.sender)
{
_setWhitelisted(msg.sender, true);
_mint(msg.sender, totalUnits);
}
// 1口は分割できない証券なので decimals = 0(整数の口数で扱う)。
// 前回のステーブルコインは 6、その前の MyToken は 18 だった。
function decimals() public pure override returns (uint8) {
return 0;
}
// --- ホワイトリスト管理(管理者のみ) ---
// 投資家の KYC が完了したら true、資格を失ったら false にする。
function setWhitelisted(address account, bool status) external onlyOwner {
_setWhitelisted(account, status);
}
function _setWhitelisted(address account, bool status) internal {
whitelisted[account] = status;
emit Whitelisted(account, status);
}
// --- 保有割合の算出 ---
// 総口数に対する保有割合をベーシスポイント(bps)で返す。1% = 100 bps、100% = 10000 bps。
// 整数演算のため、割合は bps 単位で表現する(小数を避ける定番の手法)。
function ownershipBps(address account) external view returns (uint256) {
uint256 supply = totalSupply();
if (supply == 0) {
return 0;
}
return (balanceOf(account) * 10000) / supply;
}
// --- 保有者一覧 ---
function holderCount() external view returns (uint256) {
return _holders.length;
}
function holderAt(uint256 index) external view returns (address) {
return _holders[index];
}
// --- 管理者による強制移転 ---
// 相続・秘密鍵の紛失・裁判所命令・規制対応など、証券特有の事情で
// 発行体が保有を強制的に移すための機能。通常の transfer と違い allowance は不要。
// 内部で _update を通るので、譲渡制限(ホワイトリスト確認)と保有者同期も自動で効く。
function forceTransfer(address from, address to, uint256 value) external onlyOwner {
_update(from, to, value);
emit ForcedTransfer(from, to, value);
}
// --- 緊急停止 ---
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
// 譲渡制限の核心。ERC20 v5 では mint/transfer/burn/forceTransfer の
// すべての残高移動がこの _update を経由する。ここで一括してルールを効かせる。
function _update(address from, address to, uint256 value) internal override whenNotPaused {
// mint(from = 0) 以外では送信者がホワイトリスト済みであること、
// burn(to = 0) 以外では受信者がホワイトリスト済みであることを要求する。
if (from != address(0)) {
require(whitelisted[from], 'REST: sender not whitelisted');
}
if (to != address(0)) {
require(whitelisted[to], 'REST: recipient not whitelisted');
}
super._update(from, to, value);
// 残高の増減に応じて保有者一覧を最新化する。
_syncHolder(from);
_syncHolder(to);
}
// あるアドレスの残高状態に合わせて、保有者一覧への登録/除外を行う。
// 残高が 0 → 正 になったら追加、正 → 0 になったら swap-and-pop で除外する。
function _syncHolder(address account) internal {
if (account == address(0)) {
return;
}
bool hasBalance = balanceOf(account) > 0;
bool tracked = _holderIndex[account] != 0;
if (hasBalance && !tracked) {
// 新たに保有者になった → 末尾に追加(インデックスは 1-based)。
_holders.push(account);
_holderIndex[account] = _holders.length;
} else if (!hasBalance && tracked) {
// 残高が 0 になった → 末尾の要素を穴に詰めて pop(順序は保証しない)。
uint256 index = _holderIndex[account] - 1;
uint256 lastIndex = _holders.length - 1;
address lastHolder = _holders[lastIndex];
_holders[index] = lastHolder;
_holderIndex[lastHolder] = index + 1;
_holders.pop();
_holderIndex[account] = 0;
}
}
}
読みどころを補足します。
-
mapping(address => bool) public whitelisted-
ホワイトリスト(KYC) の本体です。ここに
trueで登録された投資家だけが、トークンを持ったり受け取ったりできます。
-
ホワイトリスト(KYC) の本体です。ここに
-
_update(...) whenNotPausedの中の2つのrequire-
譲渡制限の核心です。前回のステーブルコインでは
_updateにwhenNotPaused(緊急停止)だけを付けましたが、今回はさらに「送信者・受信者の双方がホワイトリスト済みか」を確認しています。ここを通らない移転はすべてrevert(失敗)します。 -
from == address(0)は発行(mint)、to == address(0)は**焼却(burn)**を意味します。発行は「無」から生まれるので送信者チェックを免除、焼却は「無」へ消えるので受信者チェックを免除、という書き分けです。
-
譲渡制限の核心です。前回のステーブルコインでは
-
_holders/_holderIndexと_syncHolder-
保有者管理の仕組みです。残高が
0 → 正になったアドレスを一覧に追加し、正 → 0になったら一覧から外します。除外は「末尾の要素を穴に詰めてpop()する」swap-and-pop という定番の手法で、配列に穴を空けずに削除できます。 - 証券では「配当を配る」「議決権を数える」ために、現在の保有者全員を把握できることが重要です。
-
保有者管理の仕組みです。残高が
-
ownershipBps(...)- 保有割合の算出です。Solidityは小数を扱えないので、割合をベーシスポイント(bps)——1% = 100、100% = 10000——という整数で返します。これは金融でよく使われる単位です。
-
forceTransfer(...) onlyOwner-
管理者による強制移転です。通常の送金と違い、相手の同意(allowance)なしに発行体が保有を移せます。内部で
_updateを呼んでいるので、移転先のホワイトリスト確認や保有者一覧の更新も自動で効きます。
-
管理者による強制移転です。通常の送金と違い、相手の同意(allowance)なしに発行体が保有を移せます。内部で
🧠 decimals を 0 にした理由
前回のステーブルコインは decimals = 6、その前の MyToken は 18 でした。今回はあえて 0 にしています。
理由はシンプルで、1口は分割できないからです。不動産の持分を「0.5口」に割ることは考えないので、口数はつねに整数で扱います。decimals = 0 にすると、100万口 はそのまま totalSupply = 1000000 という整数で表現され、parseUnits / formatUnits のような小数変換も不要になります。
4. デプロイする — 全口数を発行体が保有した状態を作る
デプロイ用スクリプト(scripts/deploy-security-token.js)では、総口数(100万口)を引数で渡します。
// 発行する総口数。100億円の不動産を 100万口 に分割するイメージ。
const TOTAL_UNITS = 1_000_000;
const RealEstateSecurityToken = await ethers.getContractFactory('RealEstateSecurityToken');
const token = await RealEstateSecurityToken.deploy(TOTAL_UNITS);
await token.deployed();
実行結果は次のとおりです。
$ npm run deploy:security
RealEstateSecurityToken deployed to: 0x610178dA211FEF7D417bC0e6FeD39F05609AD788
Owner (issuer): 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Name / Symbol: Real Estate Security Token / REST
Decimals: 0
Total supply: 1000000 口 (REST)
Transaction hash: 0xf0c0baa8a70fd9320dd325f54419569885a2b96d1e0675d0c13dc316c042b05c
Deployment info saved to: .../deployments/security-token-localhost.json
前回のステーブルコインは Total supply: 0.0(空の台帳)から始まりました。今回は違い、デプロイ時点で100万口すべてが発行体(Owner)の手元にある状態からスタートします。不動産という「すでに存在する資産」を丸ごとトークン化し、その持分を発行体がこれから投資家に配っていく、というモデルだからです。
5. KYC・配分・譲渡制限・強制移転を実演する
scripts/interact-security-token.js では、次の登場人物で一連の流れを再現します。
- Issuer(発行体 / 管理者) … デプロイ時にownerになったアカウント。不動産の管理会社に相当。
- Alice / Bob … KYCを通過する投資家。
- Carol … KYCを通過していない第三者(ホワイトリストに載らない)。
5-0. 初期状態 — 発行体が全口数を保有
[Initial]
Issuer: 1000000 口 (100.00%)
Alice : 0 口 (0.00%)
Bob : 0 口 (0.00%)
Carol : 0 口 (0.00%)
Total supply: 1000000 口
Holder count: 1
Holder count: 1 に注目してください。まだ発行体しか保有していないので、保有者は1人です。保有割合も発行体が100%です。
5-1. KYC登録 — ホワイトリストに載せる
投資家 Alice と Bob の本人確認が完了したとして、発行体がホワイトリストに登録します。Carol は未登録のままにします。
await token.connect(issuer).setWhitelisted(alice.address, true);
await token.connect(issuer).setWhitelisted(bob.address, true);
[1] KYC登録: Alice と Bob をホワイトリストに追加(Carol は未登録)
whitelisted(Alice): true
whitelisted(Bob): true
whitelisted(Carol): false
Gas used (1件): 48364
これで Alice と Bob は「トークンを保有できる資格」を得ました。Carol はまだ資格がありません。
5-2. 配分(transfer)— KYC済みの投資家へ持分を渡す
発行体が Alice へ30万口、Bob へ20万口を配分します。
[2] 配分: Issuer -> Alice 300,000口 / Issuer -> Bob 200,000口
Transfer(Alice) tx: 0xf4e392...351f26 gas: 111738
Transfer(Bob) tx: 0x111fe4...118c504 gas: 111726
[After 配分]
Issuer: 500000 口 (50.00%)
Alice : 300000 口 (30.00%)
Bob : 200000 口 (20.00%)
Carol : 0 口 (0.00%)
Total supply: 1000000 口
Holder count: 3
配分の結果、保有割合が 発行体50% / Alice 30% / Bob 20% になりました。これが ownershipBps() で算出した保有割合です(内部では 5000 / 3000 / 2000 bps)。Holder count も 1 → 3 に増えています。誰がどれだけ持っているかが、台帳から一目で分かります。
5-3. 譲渡制限 — 未KYCの相手には送れない
ここが今回の核心です。まず Alice が、KYCを通していない Carol へ送金を試みます。
[3] 譲渡制限を確認する
(a) Alice -> Carol へ 100口 送金(Carol は未KYC。拒否されるはず)
=> 期待どおり拒否: ... reverted with reason string 'REST: recipient not whitelisted'
REST: recipient not whitelisted(受信者がホワイトリストにない)というエラーで、しっかり拒否されました。Alice自身は保有者でも、渡す相手が未KYCなら送金は成立しない——これが譲渡制限です。
続いて、両者ともKYC済みの Alice → Bob なら成功します。
(b) Alice -> Bob へ 50,000口 送金(両者KYC済。成功するはず)
=> 成功。tx: 0x23181d...87b9cc gas: 47156
[After Alice->Bob]
Issuer: 500000 口 (50.00%)
Alice : 250000 口 (25.00%)
Bob : 250000 口 (25.00%)
Carol : 0 口 (0.00%)
Total supply: 1000000 口
Holder count: 3
同じ「送金」でも、相手が適格かどうかで結果が変わりました。🧠 もしこの譲渡制限がなければ、証券が誰の手にでも渡ってしまい、規制を満たせません。譲渡制限は、証券トークンを「合法な証券」たらしめる生命線なのです。
5-4. 強制移転(管理者承認)— 発行体が保有を移す
Bob が秘密鍵を紛失した、という想定です。通常のブロックチェーンでは、鍵を失ったら資産は永久に取り出せません。しかし証券では、発行体が「株主名簿を書き換える」ように保有を移せる必要があります。そこで発行体が forceTransfer で、Bob の全保有を Alice(本人の新しいアドレスと仮定)へ回収します。
const bobBalance = await token.balanceOf(bob.address);
await token.connect(issuer).forceTransfer(bob.address, alice.address, bobBalance);
[4] 強制移転: Bob が秘密鍵を紛失 → 発行体が Bob の全 250000 口を Alice へ回収
forceTransfer tx: 0x4a3f6c...853f47 gas: 51717
[After 強制移転]
Issuer: 500000 口 (50.00%)
Alice : 500000 口 (50.00%)
Bob : 0 口 (0.00%)
Carol : 0 口 (0.00%)
Total supply: 1000000 口
Holder count: 2
Bob の残高が0になり、その分がAliceへ移りました。ここで注目すべきは Holder count が 3 → 2 に減った点です。Bobの残高が0になったので、保有者一覧から自動的に外れました。_syncHolder の swap-and-pop が効いています。
☝ 通常の暗号資産では「自分の資産は自分だけのもの」で、他人が動かすことはできません。しかし証券トークンでは発行体に特別な権限があります。これは中央集権的に見えますが、相続・紛失・裁判所命令に対応するために証券には不可欠な機能です。株式の「名義書換」の電子版だと考えると分かりやすいです。
5-5. 緊急停止(Pausable)
最後に、前回同様の緊急停止も効くことを確認します。
[5] Issuer が pause() を実行(緊急停止)
paused(): true
Issuer -> Alice へ 10口 送金を試みる(止まっているはず)
=> 期待どおり停止中で拒否: ... reverted with custom error 'EnforcedPause()'
unpause() 後 paused(): false
pause() 中は、_update に付けた whenNotPaused によって、管理者による送金すら止まります(EnforcedPause() エラー)。ハッキングや規制対応など、緊急時の安全装置です。
6. gasを比較する — なぜ「初回の配分」は重いのか
今回の各操作のgas消費量を並べてみます。
| 操作 | 内容 | gasUsed | to |
|---|---|---|---|
| デプロイ | コントラクトを新規作成 | 1,952,397 | null |
| setWhitelisted | KYC登録(1件) | 48,364 | コントラクト |
| transfer(配分) | Issuer→Alice(新規保有者) | 111,738 | コントラクト |
| transfer(配分) | Issuer→Bob(新規保有者) | 111,726 | コントラクト |
| transfer | Alice→Bob(既存保有者どうし) | 47,156 | コントラクト |
| forceTransfer | Bob→Alice(強制移転) | 51,717 | コントラクト |
読み取れることがあります。
-
「配分」の送金(約111,000)が、通常の送金(約47,000)より2倍以上重い。 これは、配分先の Alice・Bob が新しい保有者だからです。新規保有者への送金では、(a) 残高を0から書き込む、(b) 保有者一覧(
_holders配列と_holderIndex)に追加する、というストレージへの新規書き込みが発生します。Ethereumでは「空だった領域に初めて書き込む」操作がとくに高コストなのです。 - Alice→Bob の送金(47,156)は安い。 両者とも既に保有者で、残高も0ではないため、新規のストレージ書き込みがなく、単なる数値の更新で済みます。
-
デプロイの
toはnull。 これまで同様、「新しいコントラクトを生成するトランザクション」であることを示しています。
🧠 保有者一覧を持つと便利な反面、その管理にはgas(コスト)がかかる、というトレードオフがここに表れています。実務では、保有者一覧をオンチェーンに持たず、イベントログから外部で集計する設計もよく採られます。
7. 強制移転を生データ(JSON-RPC)で確認する
強制移転が本当にブロックチェーン上で起きたことを、Hardhatを介さず、標準のJSON-RPCで直接確かめます。forceTransfer のトランザクションのレシートを取得します。
curl -s -X POST http://127.0.0.1:8545 \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt",
"params":["0x4a3f6c23f7b467253920c8e149691afc2c04e629cad343b2edb08b48fe853f47"]}'
返ってきたレシート(抜粋)には、2つのイベントログが含まれていました。
{
"result": {
"blockNumber": "0x13",
"gasUsed": "0xca05",
"status": "0x1",
"logs": [
{
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc",
"0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8"
],
"data": "0x000000000000000000000000000000000000000000000000000000000003d090"
},
{
"topics": [
"0x7b3ce50c216d2ccf9919c6b9f1bfb33f0350a33010f188750b4af38e00448138",
"0x0000000000000000000000003c44cdddb6a900fa2b585dd299e03d12fa4293bc",
"0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8"
],
"data": "0x000000000000000000000000000000000000000000000000000000000003d090"
}
]
}
}
この生データから、次のことが裏付けられます。
-
1つ目のログ
0xddf252ad…523b3ef… これはkeccak256("Transfer(address,address,uint256)")、つまり標準のERC20 Transferイベントの署名ハッシュです。前回のステーブルコインとまったく同じ値で、強制移転であっても内部的には通常の残高移動として記録されることが分かります。 -
2つ目のログ
0x7b3ce50c…448138… これは今回自作したkeccak256("ForcedTransfer(address,address,uint256)")です。**「これは通常の送金ではなく、管理者による強制移転だった」**という事実を、追加のイベントとして明示的に残しています。 -
topicsの2番目・3番目 …0x3c44…3bc(Bob)から0x7099…79c8(Alice)への移転であることを示します。interact の出力どおりです。 -
data: 0x…3d090… 16進数の0x3d090= 10進数の 250,000。移転した口数と完全に一致します。 -
blockNumber: "0x13"= 19、gasUsed: "0xca05"= 51,717 … gas比較表の値と一致します。
☝ ここが証券トークンの面白いところです。ウォレットや取引所は標準の Transfer イベントを見れば残高を追えますが、それとは別に ForcedTransfer を残すことで、監査する側は「この移転は誰の意思で起きたのか(本人の送金か、発行体の強制執行か)」まで区別できます。証券に求められる**トレーサビリティ(追跡可能性)**を、イベントの設計で実現しているわけです。
8. 全体の仕組みを振り返る
今回の流れをまとめると、次のようになります。
- デプロイ:不動産を100万口に分割し、全口数を発行体が保有した状態を作った。
- KYC登録:本人確認を通した投資家だけをホワイトリストに載せた。
- 配分(transfer):発行体からKYC済みの投資家へ持分を渡し、保有割合が確定した。
- 譲渡制限:未KYCの相手への送金は自動で拒否され、KYC済みどうしなら成功した。
- 強制移転(管理者承認):発行体が保有を強制的に移し、保有者一覧も自動更新された。
- 緊急停止(Pausable):緊急時に全移転を止められる安全装置を確認した。
前回のステーブルコインが「誰でも自由に送れる通貨」だったのに対し、今回は「適格な投資家しか持てない証券」へと踏み込みました。同じERC20を土台にしながら、_update に条件を足すだけで、まったく性質の違うトークンになる——ここにブロックチェーンの表現力があります。
そして、KYC・保有者管理・保有割合・強制移転といった仕組みは、まさに多くの金融機関がRWA(現実資産のトークン化)で目指している世界観そのものです。100億円の不動産を100万人が1口ずつ持てる未来は、こうした部品の積み重ねで現実になろうとしています。
9. まとめ
今回は、ERC20 を土台に不動産セキュリティトークン(REST)を作り、KYC・配分・譲渡制限・保有割合・強制移転・緊急停止までを実際に動かして確認しました。
- セキュリティトークンは「証券」なので、KYC済みの投資家間でしか移転できない(譲渡制限)
- 譲渡制限は、
_updateに「送信者・受信者がホワイトリスト済みか」の確認を足すだけで全操作に効く - 証券では保有者一覧と保有割合の把握が重要で、保有者管理は swap-and-pop で実装できる
- 相続・紛失に備えた強制移転は発行体だけが行え、
ForcedTransferイベントで追跡可能性を担保する - トークンの性質が変わっても、標準の
Transferイベントに従う限り、外部ツールとの互換性は保たれる
これで、Counter(状態の変更)→ ERC20(資産の記録)→ ステーブルコイン(現実通貨との連動)→ セキュリティトークン(現実資産のトークン化)と、ブロックチェーンでできることを一歩ずつ広げてこられました。
最後まで読んでいただき、ありがとうございました。
デジタルアセットを学ぶロードマップ