0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[ERC7518] 規制対応と資産管理を両立するセキュリティトークンの仕組みを理解しよう!

0
Posted at

はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、セミファンジブルなパーティション構造を使い、資産区分ごとに異なるコンプライアンス制御を行える仕組みを提案しているERC7518についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

ERC1155を拡張したセキュリティトークン標準は、実物資産(不動産やファンドなどの現実世界の資産)を、証券として適切に取り扱うためのトークン規格です。
もともとのERC1155は「マルチトークン規格」で、同じコントラクトの中で複数種類のトークンを扱える仕組みですが、セキュリティトークンとして使うには機能が足りませんでした。

ERC1155については以下の記事を参考にしてください。

ERC1155を拡張したこの標準では、「パーティション」という考え方を導入します。
ここでいうパーティションは、1つの tokenId が1つのパーティションを表し、そのパーティションごとに権利やルールを分けて管理できる単位のことです。
つまり、同じコントラクトの中でも、パーティションごとに異なる権利・制約・扱い方を設定できます。

この拡張規格では、特に「セミファンジブルトークン」を意識しています。
セミファンジブルトークンとは、ある場面では同じものとしてまとめて扱える一方で、別の場面では区別して扱いたいようなトークンのことです。
例えば、「ゲーム内の装備で剣と盾と鎧で区別できるが、各アイテムは複数ある」ようなケースです。

また、セキュリティトークンに必須となる次のような機能を標準インターフェースとして提供します。

  • トークンロック
    トークンロックは、一定期間トークンを移転できないようにする仕組みです。
    ベスティング(権利確定期間)やロックアップ(保有義務期間)のような「一定期間は売却禁止」といったルールを実装するために使います。

  • 強制送金(forceTransfer
    強制送金は、ユーザー側の任意ではなく、発行体や管理者側の判断でトークンを別アドレスへ移転する仕組みです。
    ウォレットの秘密鍵を失った場合の資産回復や、規制上の問題が発生したときの回収などに利用できます。

  • アドレス凍結
    アドレス凍結は、特定のウォレットアドレスに対して送金や受け取りを禁止する仕組みです。
    マネーロンダリング対策や制裁対象への送金禁止など、規制対応のために利用されます。

  • ペイアウト機構
    ペイアウトは、トークン保有者に対して配当・家賃収入・利息などの支払いを効率的に分配するための仕組みです。
    単体でも、複数アドレスまとめてでも配布できるようなインターフェースを想定しています。

    • 動的コンプライアンス管理
      コンプライアンスとは、投資家の適格性や規制条件を満たしているかをチェック・維持する仕組みです。
      オフチェーンバウチャーは、チェーン外のシステム(KYC/AMLサービスや証券会社のシステムなど)が発行する証明書のようなもので、これをもとにトークンの送受信可否を判断します。
      これにより、ルール変更にも柔軟に対応できます。

ERC1155を拡張したこの標準は、これらの機能を共通インターフェースとしてまとめることで、相互運用性が高く、コンプライアンス対応済みのセキュリティトークンエコシステムを構築しやすくすることを狙っています。

動機

セキュリティトークンと実世界資産のニーズ

近年、現実世界の資産(不動産、ファンド、債券など)をブロックチェーン上でトークン化する動きが加速しています。
このようなトークンは、多くの場合、法的には証券として扱われるため、単に「移転できるデジタル資産」であるだけでは不十分で、証券としてのルール(ロックアップ期間、投資家の属性制限、配当の支払いなど)に沿って管理する必要があります。

しかし、既存のトークン標準だけでは以下のような課題があります。

  • 1つのコントラクトの中で、性質の異なる複数の「クラス」や「区分」を柔軟に扱いづらい
  • 規制要件に基づく細かいコンプライアンスルール(投資家の属性、地域、規制区分ごとの制限など)を、標準的な方法で扱う手段が不足している

そこで、ERC1155をベースにしつつ、セキュリティトークン向けに必要な要素を拡張したのが、このセキュリティトークン標準です。

ERC1155拡張によるパーティションの導入

ERC1155拡張セキュリティトークン標準では、「パーティション」という概念を導入することで、1つのコントラクトの中で、半分ファンジブル・半分ノンファンジブルのような「セミファンジブル」な設計を実現します。

パーティションは、tokenIdごとに異なる単位として扱われます。
例えば、以下のような使い方ができます。

  • 同じ不動産の中でも「フロアごと」、「販売時期ごと」、「投資家区分ごと」にパーティションを分ける
  • 株式において「普通株」「優先株」「地域別クラス」など、異なる権利を持つ株式クラスごとにパーティションを分ける

それぞれのパーティションは、同じコントラクト内にありながら、別々のルール・別々の権利を持つ「区分」として管理されます。
これにより、複雑な実世界資産でも、コントラクトを分けずに一元的に扱えるようになります。

複雑なRWAへの対応

image.png
https://eips.ethereum.org/EIPS/eip-7518

ERC1155拡張セキュリティトークン標準は、特に以下のような資産を意識しています。

  • 不動産
    物件全体だけでなく、フロアやユニット単位で権利や価値が異なることが多くあります。
  • ファンドや金融商品
    シェアクラスごとに手数料や配当ポリシー、投資対象が異なる場合があります。

こうしたケースでは、「全部同じトークン」として扱うと現実の構造を十分に表現できません。
一方で、コントラクトを細かく分けすぎると管理が煩雑になります。
パーティションによる設計は、この両方の問題を解消し、1つの標準コントラクトの中で柔軟に表現できるようにするものです。

セキュリティトークンに求められる追加機能

ERC1155拡張セキュリティトークン標準では、単にパーティションを導入するだけでなく、セキュリティトークンとして現実に運用するために必要な機能も標準化します。

代表的なものは以下です。

  • トークンロック
    ベスティング期間やロックアップ期間のように、「一定期間は売却や譲渡ができない」状態を実現するための仕組みです。
    これにより、発行条件や契約上の制限をオンチェーンで表現できます。

  • 強制送金(forceTransfer
    秘密鍵紛失などで通常の手段では資産を動かせない場合や、規制違反が判明して資産を回収する必要がある場合に、発行体や管理者がトークンを別アドレスへ移すことができます。
    セキュリティトークンでは、投資家保護や法令遵守の観点から、このような回復手段が求められます。

  • アドレス凍結
    規制当局やコンプライアンス部門が、特定のアドレスを「ブラックリスト」として扱う必要がある場合に、そのアドレスからの送金・受け取りを禁止できます。
    これはマネーロンダリング対策や制裁リスト対応に直結する機能です。

  • ペイアウト機構
    トークン保有者に、家賃収入・配当・利息などを定期または不定期に分配するための仕組みです。
    個別アドレスだけでなく、複数アドレスへまとめて支払う操作も想定したインターフェースを含みます。

  • 動的コンプライアンス管理
    規制要件や投資家の属性は、時間とともに変わることがあります。
    オフチェーンバウチャーを使うことで、チェーン外のコンプライアンスシステムが「この投資家はこの条件で取引可能」といった証明を発行し、その内容に基づいてトークンの移転を許可・拒否できます。
    これにより、ルール変更や新しい規制への対応も、トークンコントラクトを作り直すことなく行いやすくなります。

標準化による相互運用性の向上

ERC1155拡張セキュリティトークン標準は、これらの機能を「標準のインターフェース」として定義することを目指しています。
これによって、次のようなメリットが期待されます。

  • さまざまな発行体・サービス事業者が、同じインターフェースを前提に開発できる
  • ウォレットやカストディサービス、取引所などが、一度この標準に対応すれば、多くのセキュリティトークンをまとめてサポートできる
  • コンプライアンスサービスやKYC/AMLプロバイダが、この標準に沿った形で接続しやすくなる

結果として、個別プロジェクトごとに独自実装を増やすのではなく、相互運用性の高いセキュリティトークンエコシステムを作りやすくすることが狙いです。

仕様

インターフェース

IERC7518.sol
pragma solidity ^0.8.0;

interface IERC7518 is IERC1155, IERC165{
  event TokensLocked(address indexed account, uint indexed id, uint256 amount, uint256 releaseTime);

  event TokenUnlocked(address indexed account, uint indexed id);

  event TokensForceTransferred(address indexed from, address indexed to, uint indexed id, uint256 amount);

  event AddressFrozen(address indexed account, bytes data);

  event AddressUnfrozen(address indexed account, bytes data);

  // Emitted when the transferability of tokens with a specific ID is restricted.
  event TransferRestricted(uint indexed id);

  // Emitted when the transferability restriction of tokens with a specific ID is removed.
  event TransferRestrictionRemoved(uint indexed id);

  event PayoutDelivered(address indexed from, address indexed to, uint256 amount);

  /**
  * @dev Retrieves the transferable balance of tokens for the specified account and ID.
  * @param account The address of the account.
  * @param id The token ID.
  * @return The transferable balance of tokens.
  */
  function transferableBalance(address account, uint id) external view returns (uint);

  /**
  * @dev Retrieves the locked balance of tokens for the specified account and ID.
  * @param account The address of the account.
  * @param id The token ID.
  * @return The locked balance of tokens.
  */
  function lockedBalanceOf(address account, uint256 id) external view returns (uint256);

  /**
  * @dev Restricts the transferability of tokens with the specified ID.
  * @param id The token ID.
  * @return A boolean value indicating whether the operation was successful.
  */
  function restrictTransfer(uint id) external returns (bool);

  /**
  * @dev Removes the transferability restriction of tokens with the specified ID.
  * @param id The token ID.
  * @return A boolean value indicating whether the operation was successful.
  */
  function removeRestriction(uint id) external returns (bool);

  /**
  * @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).
  * @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard).

  * After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard).  
  * @param _from    Source address
  * @param _to      Target address
  * @param _id      ID of the token type
  * @param _value   Transfer amount
  * @param _data    Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`
  */
  function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) override external;

  /**
  * @dev Checks if a transfer is allowed.
  * @param from The address to transfer tokens from.
  * @param to The address to transfer tokens to.
  * @param id The token ID.
  * @param amount The amount of tokens to transfer.
  * @param data Additional data related to the transfer.
  * @return status A boolean value indicating whether the transfer is allowed.
  */
  function canTransfer(address from, address to, uint id, uint amount, bytes calldata data) external view returns (bool status);

  /**
  * @dev lock token till a particular block time.
  * @param account The address of the account for which tokens will be locked.
  * @param id The token ID.
  * @param amount The amount of tokens to be locked for the account.
  * @param releaseTime The timestamp indicating when the locked tokens can be released.
  * @return bool Returns true if the tokens are successfully locked, otherwise false.
  */
  function lockTokens(address account, uint id, uint256 amount, uint256 releaseTime) external returns (bool);

  /**
  * @dev Unlocks tokens that have crossed the release time for a specific account and id.
  * @param account The address of the account to unlock tokens for.
  * @param id The token ID.
  */
  function unlockToken(address account, uint256 id) external;

  /**
  * @dev Force transfer in cases like recovery of tokens.
  * @param from The address to transfer tokens from.
  * @param to The address to transfer tokens to.
  * @param id The token ID.
  * @param amount The amount of tokens to transfer.
  * @param data Additional data related to the transfer.
  * @return A boolean value indicating whether the operation was successful.
  */
  function forceTransfer(address from, address to, uint256 id, uint256 amount, bytes memory data) external returns (bool);

  /**
  * @dev Freezes specified address.
  * @param account The address to be frozen.
  * @param data Additional data related to the freeze operation.
  * @return A boolean value indicating whether the operation was successful.
  */
  function freezeAddress(address account, bytes calldata data) external returns (bool);

  /**
  * @dev Unfreezes specified address.
  * @param account The address to be unfrozen.
  * @param data Additional data related to the unfreeze operation.
  * @return A boolean value indicating whether the operation was successful.
  */
  function unFreeze(address account, bytes memory data) external returns (bool);

  /**
  * @dev Sends payout to single address with corresponding amounts.
  * @param to address to send the payouts to.
  * @param amount amount representing the payouts to be sent.
  * @return A boolean indicating whether the batch payouts were successful.
  */* 
  function payout(address calldata to, uint256 calldata amount) public returns (bool);

  /**
  * @dev Sends batch payouts to multiple addresses with corresponding amounts.
  * @param to An array of addresses to send the payouts to.
  * @param amount An array of amounts representing the payouts to be sent.
  * @return A boolean indicating whether the batch payouts were successful.
  */
  function batchPayout(address[] calldata to, uint256[] calldata amount) public returns (bool);
}

イベント

TokensLocked

event TokensLocked(address indexed account, uint indexed id, uint256 amount, uint256 releaseTime);

トークンがロックされた時に発行されるイベント。
指定されたアカウントに対して、特定の id のトークンが一定量ロックされたことを示すイベントです。
ロックされたトークンは releaseTime を過ぎるまで移転できません。
ロック状態の可視化や、外部システムの追跡に役立ちます。

パラメータ

  • account
    ロックの対象となるアドレス。
  • id
    ロック対象のトークンID。
  • amount
    ロックされたトークン量。
  • releaseTime
    ロックが解除される時刻(UNIXタイム)。

TokenUnlocked

event TokenUnlocked(address indexed account, uint indexed id);

ロックされていたトークンが解除された時に発行されるイベント。
releaseTime を過ぎ、ロック状態のトークンが正常にアンロックされたことを通知します。
外部システムが状態変化を検知しやすくするためのイベントです。

パラメータ

  • account
    アンロック対象のアドレス。
  • id
    アンロックされたトークンID。

TokensForceTransferred

event TokensForceTransferred(address indexed from, address indexed to, uint indexed id, uint256 amount);

強制送金が実行された時に発行されるイベント。
通常の権限チェックや制限を無視して強制的にトークンが移転されたことを通知します。
秘密鍵紛失、コンプライアンス違反などの特別なケースで利用されるものです。

パラメータ

  • from
    送金元アドレス。
  • to
    送金先アドレス。
  • id
    トークンID。
  • amount
    送金されたトークン量。

AddressFrozen

event AddressFrozen(address indexed account, bytes data);

アドレスが凍結された時に発行されるイベント。
特定アドレスがコンプライアンス上の理由などで凍結されたことを知らせます。
凍結後はトークン移転やペイアウトを行えなくなります。

パラメータ

  • account
    凍結されたアドレス。
  • data
    凍結に関する追加情報。

AddressUnfrozen

event AddressUnfrozen(address indexed account, bytes data);

アドレスの凍結が解除された時に発行されるイベント。
凍結状態が解除されたことを示します。
凍結理由を外部システムが確認するため、付随情報が data に含まれます。

パラメータ

  • account
    解除対象のアドレス。
  • data
    解除時の追加情報。

TransferRestricted

event TransferRestricted(uint indexed id);

特定IDのトークンが移転制限された時に発行されるイベント。
指定されたトークンIDに対し、移転制限が設定されたことを示します。
証券規制の特別期間や、特定区分(パーティション)の制限ルールの発動時などに利用されます。

パラメータ

  • id
    移転制限が設定されたトークンID。

TransferRestrictionRemoved

event TransferRestrictionRemoved(uint indexed id);

特定IDのトークンの移転制限が解除された時に発行されるイベント。
解除される前に制限が存在していたトークンIDに対し、制限が取り消されたことを知らせます。
期間終了や規制緩和などが理由になることがあります。

パラメータ

  • id
    移転制限が解除されたトークンID。

PayoutDelivered

event PayoutDelivered(address indexed from, address indexed to, uint256 amount);

ペイアウトが実行された時に発行されるイベント。
発行体(または支払元)から対象アドレスへペイアウトが送られたことを通知するイベントです。
配当、利息、収益分配などに利用されます。

パラメータ

  • from
    支払元アドレス。
  • to
    受取アドレス。
  • amount
    支払われた金額。

関数

transferableBalance

function transferableBalance(address account, uint id) external view returns (uint);

アカウントが実際に移転可能なトークン残高を取得する関数。
balanceOf(account, id) からロックされている量 lockedBalanceOf(account, id) を差し引いた値を返します。
ロック期間中のトークンや制限中の数量を考慮した上で、移転可能な正しい数量を確認できます。

引数

  • account
    対象アドレス。
  • id
    トークンID。

戻り値

  • uint
    移転可能なトークン量。

lockedBalanceOf

function lockedBalanceOf(address account, uint256 id) external view returns (uint256);

指定アカウントのロックされているトークン残高を取得する関数。
ロック機能によって移転不可となっている数量を返します。
移転可否判定やUI表示、外部監査などで重要になります。

引数

  • account
    対象アドレス。
  • id
    トークンID。

戻り値

  • uint256
    ロックされているトークン量。

restrictTransfer

function restrictTransfer(uint id) external returns (bool);

特定IDのトークンに移転制限を設定する関数。
指定の id に対して移転を禁止します。規格上、成功時には TransferRestricted を発行することが推奨されています。
証券における規制期間や移転不可区分の管理に利用します。

引数

  • id
    移転制限を設定するトークンID。

戻り値

  • bool
    処理の成功状態。

removeRestriction

function removeRestriction(uint id) external returns (bool);

特定IDのトークンの移転制限を解除する関数。
指定された id に対する制限を解除します。
制限が存在しなかった場合は revert する必要があります。
解除時には TransferRestrictionRemoved の発行が推奨されています。

引数

  • id
    制限解除するトークンID。

戻り値

  • bool
    処理の成功状態。

safeTransferFrom

function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;

安全な方法でトークンを移転する関数。
ERC1155の安全転送ルールに従ってトークンを移転します。
移転前に canTransfer を呼び出して、凍結アドレスでないか、制限中のIDでないか、ロックされていないかなどの確認が行われます。
送信先がスマートコントラクトの場合は onERC1155Received を呼ぶ必要があります。

引数

  • _from
    送信元アドレス。
  • _to
    送信先アドレス。
  • _id
    トークンID。
  • _value
    移転量。
  • _data
    追加データ。

canTransfer

function canTransfer(address from, address to, uint id, uint amount, bytes calldata data) external view returns (bool status);

特定のトークン移転が許可されているかを判定する関数。
凍結状態の確認、移転制限の確認、ロック残高の確認などを行います。
外部のコンプライアンスコントラクトを呼び出して検証することも許されます。

引数

  • from
    移転元アドレス。
  • to
    移転先アドレス。
  • id
    トークンID。
  • amount
    移転量。
  • data
    追加データ。

戻り値

  • status
    移転が可能かどうか。

lockTokens

function lockTokens(address account, uint id, uint256 amount, uint256 releaseTime) external returns (bool);

トークンを指定時間までロックする関数。
対象アドレスのトークンを指定数量ロックし、releaseTime を過ぎるまで移転不可にします。
ロック数量が保有量を超える場合は revert します。
ロック成功時には TokensLocked を発行することが推奨されています。

引数

  • account
    ロック対象アドレス。
  • id
    トークンID。
  • amount
    ロックする量。
  • releaseTime
    ロック解除時刻。

戻り値

  • bool
    ロック処理の成功状態。

unlockToken

function unlockToken(address account, uint256 id) external;

ロック解除条件を満たしたトークンをアンロックする関数。
releaseTime <= block.timestamp の条件を満たすトークンを一括でアンロックします。
解除対象がない場合はガス節約のため revert することが推奨されています。
解除時には TokenUnlocked を発行します。

引数

  • account
    アンロック対象アドレス。
  • id
    トークンID。

forceTransfer

function forceTransfer(address from, address to, uint256 id, uint256 amount, bytes memory data) external returns (bool);

凍結アドレスから強制的にトークンを移転させる関数。
通常の移転制限をすべて無視して実行する特殊な移転処理です。
from が凍結されていなければ revert します。
権限を持つ管理者のみ実行できる必要があります。
実行後は TokensForceTransferred を発行することが推奨されています。

引数

  • from
    送金元アドレス。
  • to
    送金先アドレス。
  • id
    トークンID。
  • amount
    移転量。
  • data
    追加データ。

戻り値

  • bool
    処理の成功状態。

freezeAddress

function freezeAddress(address account, bytes calldata data) external returns (bool);

対象アドレスを凍結する関数。
凍結されたアドレスは移転やペイアウトが禁止されます。
実行には適切なアクセス制御が求められます。成功時には AddressFrozen を発行します。

引数

  • account
    凍結対象アドレス。
  • data
    凍結理由などの追加情報。

戻り値

  • bool
    処理の成功状態。

unFreeze

function unFreeze(address account, bytes memory data) external returns (bool);

凍結されたアドレスの凍結を解除する関数。
解除には管理権限が必要です。
解除状態を外部へ通知するため、AddressUnfrozen を発行します。

引数

  • account
    解除対象アドレス。
  • data
    付随情報。

戻り値

  • bool
    処理の成功状態。

payout

function payout(address to, uint256 amount) external returns (bool);

単一アドレスへペイアウトを行う関数。
配当や収益分配などの支払い処理を行います。
凍結されたアドレスが受取対象の場合処理は revert します。
さらに発行体アドレスに十分な残高があることが求められます。
成功時には PayoutDelivered を発行します。

引数

  • to
    受取アドレス。
  • amount
    支払い量。

戻り値

  • bool
    処理の成功状態。

batchPayout

function batchPayout(address[] calldata to, uint256[] calldata amount) external returns (bool);

複数アドレスへまとめてペイアウトを行う関数。
配当や報酬などを大量のアドレスへ効率よく支払うために使用します。
対象アドレスが凍結されている場合は処理が revert します。
発行体の残高チェックが推奨されています。

引数

  • to
    支払先アドレスの配列。
  • amount
    支払う量の配列。

戻り値

  • bool
    処理の成功状態。

Interoperability

ERC7518は、セキュリティトークンとしての機能を持ちながら、既存の ERC20 / ERC721 / ERC1155 といった多様なトークン規格と相互運用できるよう設計されています。
ここで紹介する Interoperability 仕様では、既存トークンを「包む(wrap)」ことで ERC7518 トークンへ変換し、必要に応じて「解く(unwrap)」ことで元のトークンに戻す仕組みを標準化しています。

この仕組みによって、既存資産を安全に ERC7518 ベースのパーティション化モデルへ統合でき、Ethereum 上の多様な資産に一貫した管理・コンプライアンス・機能拡張を行えるようになります。

IERC1155Wrapper.sol
interface IERC1155Wrapper is IERC7518 {

/**
@dev Emitted when a new wrapped token address is added to the set.
@param wrappedTokenAddress The address of the wrapped token that was added.
*/
event WrappedTokenAddressSet(address wrappedTokenAddress);

/**
@dev Emitted when tokens are wrapped.
@param The ERC-1155 token ID of the wrapped tokens.
@param amount The amount of tokens that were wrapped.
*/
event TokensWrapped(uint indexed id, uint256 amount);

/**
@dev Emitted when tokens are unwrapped.
@param wrappedTokenId Is the ERC-1155 token ID of the wrapped tokens.
@param amount The amount of tokens that were unwrapped.
*/
event TokensUnwrapped(uint indexed wrappedTokenId, uint256 amount);

/**
* @dev Sets the wrapped token address and logic for deciding partitions.
* @param wrappedTokenAddress The address of the wrapped token contract.
* @return A boolean value indicating whether the operation was successful.
*/
function setWrappedToken(address token) external returns (bool);

/**
* @dev Wraps the specified amount of tokens by depositing the original tokens and receiving new standard tokens.
* @param amount The amount of tokens to wrap.
* @param data Additional data for partition.
* @return A boolean value indicating whether the operation was successful.
*/
function wrapToken(uint256 amount, bytes calldata data) external returns (bool);

/**
* @notice Wraps a specified amount of tokens from a given partition into the main balance.
* @dev This function allows users to convert tokens from a specific partition back to the main balance,making them fungible with tokens from other partitions.
* @param partitionId The unique identifier of the partition from which tokens will be wrapped.
* @param id The unique identifier of the token.
* @param amount The amount of tokens to be wrapped from the specified partition.
* @param data Additional data that may be used to handle the wrap process (optional).
* @return success A boolean indicating whether the wrapping operation was successful or not.
*/

function wrapTokenFromPartition(bytes32 partitionId, uint256 id, uint256 amount, bytes calldata data) external returns (bool);
/**
* @dev Unwraps the specified amount of wrapped tokens by depositing the current tokens and receiving the original tokens.
* @param wrappedTokenId internal partition id.
* @param amount The amount of wrapped tokens to unwrap.
* @param data Additional data for partition.
* @return A boolean value indicating whether the operation was successful.
*/
function unwrapToken(uint256 wrappedTokenId, uint256 amount, bytes calldata data) external returns (bool);

/**
* @dev Retrieves the balance of wrapped tokens for the specified account and ID.
* @param account The address of the account.
* @param id The token ID.
* @param data Additional data for partition.
* @return The balance of wrapped tokens.
*/
function wrappedBalanceOf(address account, uint256 id, bytes calldata data) external view returns (uint256);

/**
* @dev Retrieves the balance of original tokens for the specified account and ID.
* @param account The address of the account.
* @param id The token ID.
* @param data Additional data for partition.
* @return The balance of original tokens.
*/
function originalBalanceOf(address account, uint256 id, bytes calldata data) external view returns (uint256);
}

イベント

WrappedTokenAddressSet

event WrappedTokenAddressSet(address wrappedTokenAddress);

新しいラップ対象トークンアドレスが登録された時に発行されるイベント。
ラッピング対象となる元のトークンコントラクトアドレスが設定されたことを示すイベントです。
ERC7518では複数の資産がラップされる可能性があるため、外部システムが対象の追加を確実に検知できるようにしています。

パラメータ

  • wrappedTokenAddress
    • 新しく登録されたラップ対象トークンのアドレス。

TokensWrapped

event TokensWrapped(uint indexed id, uint256 amount);

トークンがラップされた時に発行されるイベント。
ユーザーが元のトークンを預け、対応するERC7518トークンがミントされたことを示します。
ラップ後の数量管理や追跡に利用されます。

パラメータ

  • id
    • ラップされたERC1155形式のトークンID。
  • amount
    • ラップされたトークン量。

TokensUnwrapped

event TokensUnwrapped(uint indexed wrappedTokenId, uint256 amount);

トークンがアンラップされた時に発行されるイベント。
ERC7518トークンがバーンされ、元のトークンがユーザーへ戻されたことを示します。
アンラップ作業の透明性を高めるために利用されます。

パラメータ

  • wrappedTokenId
    • ラップトークンに対応する内部パーティションID。
  • amount
    • アンラップされたトークン量。

関数

setWrappedToken

function setWrappedToken(address token) external returns (bool);

ラップ対象のトークンアドレスを設定する関数。
ラッピング処理の対象となるトークンのコントラクトアドレスを登録します。
登録されたアドレスはラップ・アンラップの基準として扱われます。
ERC3643やその他のセキュリティトークン規格を対象にすることも可能です。

ERC3643については井岡の記事を参考にしてください。

引数

  • token
    ラップ対象トークンのアドレス。

戻り値

  • bool
    設定が成功したかどうか。

wrapToken

function wrapToken(uint256 amount, bytes calldata data) external returns (bool);

元トークンを預けてERC7518トークンへ変換する関数。
指定数量の元トークンをオンチェーンの保管庫(vault)にロックし、対応するERC7518トークンをミントします。
元トークンと ERC1155 の id の対応関係を適切に検証する必要があります。

引数

  • amount
    ラップする数量。
  • data
    パーティション識別などに使われる補足データ。

戻り値

  • bool
    ラップ処理の成功状態。

wrapTokenFromPartition

function wrapTokenFromPartition(bytes32 partitionId, uint256 id, uint256 amount, bytes calldata data) external returns (bool);

特定パーティションのトークンをラップして、メインバランスとして扱えるERC7518トークンに変換する関数。
パーティションに属するセミファンジブルトークンを指定数量ロックし、対応するERC7518トークンへ変換します。
元トークンとパーティションIDの対応関係を検証し、一対一でロックされるよう管理されます。

引数

  • partitionId
    元トークン側のパーティション識別子。
  • id
    トークンID。
  • amount
    ラップする数量。
  • data
    ラップ処理で使われる追加データ。

戻り値

  • success
    ラップ処理の成功状態。

unwrapToken

function unwrapToken(uint256 wrappedTokenId, uint256 amount, bytes calldata data) external returns (bool);

ラップされたERC7518トークンを元トークンへ戻すためのアンラップ関数。
アンラップでは、ERC7518トークンを指定数量バーンし、ロックされていた元トークンをユーザーに返却します。
ロック機能によって制限されているトークンはアンラップ不可となります。

引数

  • wrappedTokenId
    内部パーティションID。
  • amount
    アンラップする数量。
  • data
    補足データ。

戻り値

  • bool
    アンラップ処理の成功状態。

wrappedBalanceOf

function wrappedBalanceOf(address account, uint256 id, bytes calldata data) external view returns (uint256);

指定アドレスが保持するラップ済みERC7518トークン残高を取得する関数。
ユーザーがどれだけラップ済みトークンを保有しているか確認するための関数です。
パーティション情報を含む場合もあるため、data が参照されることがあります。

引数

  • account
    残高を確認するアドレス。
  • id
    トークンID。
  • data
    パーティション関連情報。

戻り値

  • uint256
    ラップ済みトークン残高。

originalBalanceOf

function originalBalanceOf(address account, uint256 id, bytes calldata data) external view returns (uint256);

指定アドレスが保持する元トークンの残高を取得する関数。
ラップ前のオリジナル資産の残高を確認するために使用します。
ラップ・アンラップの過程で整合性を確保するうえで重要な関数です。

引数

  • account
    残高確認するアドレス。
  • id
    トークンID。
  • data
    パーティションに関連する追加情報。

戻り値

  • uint256
    元トークンの残高。

パーティション管理

パーティションの基本概念

ERC7518は、ERC1155の特徴である複数の tokenId を活用し、資産を「パーティション」という単位で分割します。
このパーティションは、権利や規制、取扱ルールが異なる複数の証券区分を、1つのコントラクト内で整理するための仕組みです。

パーティションは以下のような性質を持ちます。

項目 説明
パーティションの生成方法 異なる tokenId をミントすることで新しいパーティションが作られる。
パーティション内部の性質 同じ tokenId のトークンは互いにファンジブル(同じ価値で交換可能)。
パーティション同士の関係 異なる tokenId 間では非ファンジブル(別の扱い・別の権利)。
用途の例 ファンドのクラス分け、異なる司法管轄区の投資家向け証券、別シリーズの債券、フロア単位の不動産証券化など。

このように、パーティションを使うことで、複雑な構造をもつ実世界資産を自然にブロックチェーン上で表現できます。

オフチェーン連携を含むコンプライアンス管理

ERC7518の特徴の1つが、高度なコンプライアンスチェックをオンチェーンの canTransfer 関数で一元管理できる点です。
規制要件は国・投資家属性・免除条件など、オフチェーンで判定されることが多く、スマートコントラクトだけでは判断しきれないケースがあります。

そこで ERC7518は、以下のように「オンチェーン + オフチェーン」のハイブリッドモデルを採用します。

コンプライアンス処理の流れ

image.png
https://eips.ethereum.org/EIPS/eip-7518

クライアントがトークンを送ろうとすると、以下のような流れが実行されます。

  1. クライアントがOracle / Complianceサービスへ転送リクエストを送信する。
    転送元・転送先・数量・パーティションなどの情報が含まれる。
  2. Oracle / Complianceサービスが KYC、司法管轄、規制免除の可否、独自ルールなどを確認する。
    各種データベースや外部システムと連携することもある。
  3. 条件を満たしている場合、Oracle / Complianceは署名付きの「バウチャー」を生成し、クライアントに返す。
  4. クライアントはこの署名付きバウチャーを safeTransferFromdata 引数としてコントラクトに渡す。
  5. コントラクト側の canTransfer 関数が、次の要素を検証する。
    • 署名の正当性
    • バウチャーに含まれる from/to/id/amount が実際の引数と一致しているか
    • 期限切れではないか
    • その他のチェーン上の状態(ロック、凍結、移転制限など)
  6. バウチャーが正当で、すべての条件を満たしている場合は true を返し、転送処理に進む。
  7. transfer が問題なく完了すると、クライアントへ status が返される。

バウチャーによる動的コンプライアンス

バウチャーは「署名付きの認可証明」のようなものです。
このアプローチのメリットは以下です。

項目 説明
柔軟性 規則変更があっても、スマートコントラクトを再デプロイせず、オフチェーンルールを更新するだけで対応できる。
プライバシー 投資家の属性(国籍・資格・ステータスなど)をチェーン上に公開せずに検証可能。
拡張性 司法管轄ごとの特別ルールや第三者チェックを追加しやすい。

実装では、バウチャーの形式や検証ポリシーはカスタマイズ可能となっており、アプリケーションごとに自由に設計できます。

トークンリカバリー

ウォレット紛失や不正アクセスなどが発生すると、投資家は資産を失う可能性があります。
証券として扱う以上、法的な観点や投資家保護の観点から、回復手段を用意しておく必要があります。

ERC7518はこのために forceTransfer を備えています。

強制送金には以下のような特徴があります。

項目 説明
通常の権限チェックを迂回 forceTransfer はロック、凍結、残高制限などの通常制御を無視できる。
発行体または回復エージェントのみが実行可能 不正利用を防ぐため、厳格なアクセス制御が求められる。
悪意のウォレットや失われた鍵から資産を安全に移転可能 証券として必要なリカバリ機構を提供する。

これにより、セキュリティトークンとして必要なリスク管理が可能になります。

ペイアウト管理

ERC7518は配当・利息・収益分配などのペイアウトを効率よく行うための仕組みを標準化しています。
ペイアウト処理は以下の2つの関数によって行われます。

関数 説明
payout 単一アドレスに対して支払いを行う。
batchPayout 複数アドレスへ一括で支払いを行う。

これにより、以下のようなメリットが生まれます。

項目 説明
事務作業の効率化 多数の投資家への分配を一度のトランザクションで実施できる。
手数料の削減 1件ずつ支払う場合に比べてガスコストを抑えられる。
統一された分配手順 どのアプリケーションでも同じインターフェースで配当機能をサポート可能。

また、凍結されたアドレスへの支払いは不可となっており、規制対応の観点からも適切な仕様になっています。

補足

コンプライアンス管理の強化

ERC7518は、トークン転送時のコンプライアンスチェックを中心に設計されています。
中核となるのが canTransfer で、この関数を通して「実行して良い transfer かどうか」を判定します。

canTransfer の実装方法は1パターンに固定されていません。
オンチェーンのストレージだけで完結させてもよく、オラクルを介して外部システムに問い合わせてもよく、完全にオフチェーンの認証基盤と連携してもかまいません。
これにより、既存のKYC/AMLや各国の証券規制に対応したコンプライアンスフレームワークと柔軟に統合できます。

コンプライアンスを支える周辺機能として、freezeAddressrestrictTransferlockTokensforceTransfer といった関数が用意されています。

  • freezeAddress はアドレス単位の凍結を行い、そのアドレスからの送金やペイアウトを止めます。
  • restrictTransfer は特定のトークンIDに対して移転制限をかけます。
  • lockTokens はベスティングやロックアップのように、一定期間は移転できない残高を設定します。
  • forceTransfer は例外的に、通常の制限を迂回して強制送金を行うための仕組みです。

これらの制御の結果としてロックされたトークンは、条件を満たした後に unlockToken によって解放されます。
unlockToken は、ロック解除のタイミングや解除対象を明示的に扱うことで、どのトークンがいつ解放されたのかを追跡しやすくし、運用上の透明性と責任の所在をはっきりさせます。

フラクショナリゼーション(分割所有)の扱い

ERC7518におけるフラクショナリゼーションは、追加の拡張を入れずに、ERC1155が持つ「残高ベース」のモデルをそのまま活用して実現されています。

1つのパーティションは tokenId で識別され、そのパーティションに属する残高の amount が、基礎となる証券の「何口分・何株分」に相当します。
例えば、ある不動産パーティションにおいて amount = 1000 を総発行数と決めた場合、そのうちの1単位は全体の 1/1000 を表すファンジブルな持分として扱えます。

挙動のイメージを表にまとめると以下のようになります。

要素 説明
フラクションの単位 tokenId ごとの amount が、そのパーティションに対する分割持分を表す。
発行と償却 発行体はミントとバーンによってフラクションの供給量を調整する。
転送 safeTransferFrom / safeBatchTransferFrom を使い、その前に必ず canTransfer を通す。
ロック・凍結 ロックや凍結は残高のうち「移転可能な部分」を削り、残高全体は保ちつつ移転を止める。
強制送金 forceTransfer はフラクション単位の残高に対して比例して作用する。
パーティションのマージ 新しい tokenId を発行し、旧パーティションのトークンを1:1または一定比率で預けることで統合する。
パーティションの分割 既存パーティションから新しい tokenId をミントし、定められた変換ルールに従って割り当てる。
コンプライアンスの適用 1つのパーティション内の全フラクションは同一ルールで扱われる。

重要なのは、パーティションの中にある全ての単位が同じルールで扱われる点です。
KYC/AML、ロックアップ、移転制限などのコンプライアンス要件は、そのパーティションに属する全てのフラクションに一貫して適用されます。
これにより、「小数点以下の持分」を許容しながらも、コンプライアンス上の管理単位をシンプルに保てます。

他標準との相互運用性

なぜERC1155ベースなのか

ERC7518ERC1155を土台にして設計されています。

ERC1155はもともと、複数トークンの同時管理、バッチ転送、残高ベースの会計といった機能を持ち、ゲーム・NFT・マルチアセット分野ですでに広く使われています。
この基盤を使うことで、既存ツールやウォレット、マーケットプレイスとの互換性を保ちながら、セキュリティトークン向けの機能を追加できます。

一方、検討対象となった他の標準との比較は以下の通りです。

規格 採用しなかった理由・位置付け
ERC3525 スロット(slot)と値(value)の抽象化は、ERC7518の「パーティション(tokenId)と残高(amount」とほぼ同じモデルを持つが、ERC721のように1ポジションごとに状態を持つため、大量発行や広範なコンプライアンスチェックの観点ではオーバーヘッドが大きい。
ERC6909 シンプルなマルチトークン標準だが、パーティションメタデータやコンプライアンスのためのフックがなく、規制資産には不向き。

ERC3525については以下の記事を参考にしてください。

ERC6909については以下の記事を参考にしてください。

ただし、スロットスタイルの互換性が必要な実装者向けに、ERC165を通して ERC3525 互換のインターフェースを任意で公開し、slot をパーティション、valueamount にマッピングすることは可能です。
この場合も、内部表現はあくまでERC1155的なパーティションモデルを維持できます。

ERC165については以下の記事を参考にしてください。

最終的にERC7518は、ERC1155tokenId をパーティションとして扱い、動的なミント・マージ・スプリットと、バウチャーベースの動的コンプライアンスを組み合わせる構成を採用しています。
これにより、再デプロイ不要でコンプライアンスロジックを進化させつつ、効率と拡張性を両立させています。

wrap / unwrap 関数の役割

相互運用性の観点から、wrapTokenwrapTokenFromPartitionunwrapToken は非常に重要な位置付けです。

関数名 役割
wrapToken 主にERC20互換のトークンを ERC7518 の世界へ取り込むための関数。元トークンをロックし、対応するERC7518トークンをミントする。
wrapTokenFromPartition NFTやその他のマルチトークン標準から、パーティション情報を持つ形でERC7518に変換するための関数。より複雑な資産構造に対応する。
unwrapToken ラップされたERC7518トークンを元の標準へ戻すための関数。元トークンはラップ中はロックされているため、アンラップによってロック解除され、元の標準に従って再び自由に扱えるようになる。

ERC20については以下の記事を参考にしてください。

unwrapToken によって、ユーザーは必要に応じて「ERC7518の世界」と「元の標準」の間を行き来できます。
これにより、既存のDeFiや取引所のインフラを維持しつつ、証券化・パーティション・コンプライアンス等の高度な機能を段階的に取り込むことができます。

ペイメント分配

支払い分配の設計は、運用目線での効率と透明性を重視しています。

単発の支払い、特定イベントに応じた分配、少数の投資家への特別な支払いなどには payout を使います。
これは、1つのアドレスに対して任意のタイミングで支払うための関数で、エアドロップやボーナス的な分配にも利用できます。

一方、定期的な配当や多人数への分配には batchPayout が適しています。
複数アドレスに対して一括で支払いを行うことで、ガスコストを削減しつつ、ブロックチェーン上でのオペレーションを楽にします。

関数 主な用途
payout 単発・少数のアドレスへの個別支払い。イベントトリガー型の分配。
batchPayout 多数のアドレスへ一度に支払う場合や、定期配当・利息支払いなど。

どちらの関数も、凍結アドレスへの支払いを拒否するといったコンプライアンスルールと組み合わせることで、規制対応と運用効率を両立させます。

互換性

ERC7518 は、ERC1155との互換性を壊さないように設計されています。

コアとなるトークンインターフェースはERC1155の仕様に従っており、追加された関数はすべて拡張インターフェースとして定義されています。
このため、既存のERC1155対応ウォレットやマーケットプレイスは、少なくとも「標準的なERC1155トークン」としてERC7518トークンを扱うことができます。

対象 互換性のポイント
ウォレット ERC1155対応ウォレットは、そのまま残高表示や送金などの基本機能を利用可能。
マーケットプレイス ERC1155ベースの取引ロジックを流用できる。追加のコンプライアンス機能は必要に応じて統合。
インフラ(インデクサなど) ERC1155イベントをそのまま処理できるため、既存のインデクシング基盤を再利用しやすい。

拡張部分(ロック、凍結、バウチャー検証など)は、対応したクライアントやサービスが順次サポートすればよく、非対応クライアントでも最低限の互換性は維持されます。

セキュリティ

最後に、ERC7518ではを安全に実装・運用するうえで重要となるセキュリティポイントを整理します。

アクセス制御

forceTransferfreezeAddresslockTokens のような関数は、残高や権利に大きな影響を与えます。
これらは発行体や特定の管理者ロールのみが実行できるよう、ロールベースの権限管理などで厳格に制御する必要があります。

パラメータ検証

safeTransferFromlockTokensforceTransfer などの関数では、アドレスがゼロアドレスでないか、残高が足りているか、権限を持つ送信者かどうかといった基本的なチェックが必須です。
これを怠ると、意図しない送金や残高の不整合、あるいは攻撃者による悪用につながります。

Reentrancy対策

外部コントラクトを呼び出す処理を含む関数(トークン送金や外部コールを伴うペイアウトなど)では、再入可能性攻撃に注意する必要があります。
再入可能性ガード(状態変更を先に行う、nonReentrant 的な仕組みを入れるなど)を適切に導入し、攻撃者が同じトランザクション中に複数回関数を呼び出せないようにします。

オーバーフロー/アンダーフロー対策

トークン残高やペイアウト額など、数値計算を行う部分ではオーバーフローとアンダーフローを防ぐ必要があります。
Solidity 0.8以降の組み込みチェックや、safeMath ライブラリを利用することで、このリスクを低減できます。

ペイアウトの安全性

payoutbatchPayout では、次の点に注意が必要です。

観点 説明
権限 誰でも任意のペイアウトを開始できると、コントラクトの残高が不正に流出する可能性があるため、適切なロールに限定する。
残高チェック 支払総額が発行体の利用可能残高を超えていないかを必ず確認する。
凍結アドレス 凍結対象への支払いは拒否し、コンプライアンス違反を防ぐ。

オフチェーンバウチャーの安全性

バウチャーを使ったコンプライアンス管理では、オフチェーン側のセキュリティも重要です。

  • バウチャーを発行するコンプライアンスサービスは、認証や署名鍵の管理を厳格に行う必要がある
  • 不正なバウチャー生成や改ざんを防ぐため、鍵管理・監査ログ・侵入検知などの仕組みが求められる
  • コントラクト側では、署名の検証、期限切れチェック、引数(from/to/id/amount)とバウチャー内容の完全一致確認を行う必要があります。

これらが適切に設計されていないと、コンプライアンスレイヤーがそのまま攻撃経路になり得るため、オンチェーン・オフチェーン双方での堅牢な設計が欠かせません。

アンフリーズ時の運用面の注意

unFreeze によってアドレスの凍結が解除されると、そのアドレスは再びトークンの送受信やペイアウトを受けられるようになります。

解除にあたっては、単に技術要件だけでなく、適用される規制や契約上の義務を満たしているかを運用側が確認する必要があります。
誤って制裁対象や未解決の問題があるアドレスを解除すると、法的なリスクやコンプライアンス違反につながる可能性があります。

引用

Abhinav (@abhinav-d3v) abhinav@zoniqx.com, Prithvish Baidya (@d4mr) pbaidya@zoniqx.com, Rajat Kumar (@rajatwasan) rwasan@zoniqx.com, Prasanth Kalangi pkalangi@zoniqx.com, "ERC-7518: Dynamic Compliant Interop Security Token [DRAFT]," Ethereum Improvement Proposals, no. 7518, September 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7518.

最後に

今回は「セミファンジブルなパーティション構造を使い、資産区分ごとに異なるコンプライアンス制御を行える仕組みを提案しているERC7518」についてまとめてきました!
いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

Twitter @cardene777

他の媒体でも情報発信しているのでぜひ他も見ていってください!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?