はじめに

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

今回は、トークンに紐づく画像をオンチェーン上にSVG形式で保存する仕組みを提案しているERC2569についてまとめていきます!

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

他にも様々なEIPについてまとめています。

概要

ERC2569で提案されているインターフェースセットは、スマートコントラクトがEthereum上にSVG画像を保存し、後からその画像を取得できるようにする仕組みを提案しています。 対応するトークンは以下のように幅広く想定されています。

  • ファンジブルトークン(代替可能トークン、例:ERC20)
  • ノンファンジブルトークン(代替不可能トークン、例:ERC721)
  • 将来登場するその他のトークン標準

ERC2569で提案されている仕様は以下の2つのインターフェースから構成されています。

  • SVG画像をEthereumに保存するためのインターフェース
  • EthereumからSVG画像を取得するためのインターフェース

主な用途としては、以下のような画像の保存と表示が挙げられます。

  • ファンジブルトークンのアイコン
  • ノンファンジブルトークン(NFT)のアイコン
  • DAOのレピュテーショントークンのロゴ

動機

現在、ERC721はEthereum上で非代替トークン(NFT)を定義するための広く普及した規格です。 NFTは、クリプトギフトやクリプトメダル、コレクティブルなどに利用されており、有名な事例としてはCryptoKittiesがあります。

このようなNFTには画像が紐づけられていますが、トークンのコードはEthereum上に永続的に保存される一方で、画像データは多くの場合中央集権的なサーバーに保存されています。 アプリケーションは、Ethereumからトークン情報を取得し、その情報を元に中央サーバー上の画像を表示しています。

しかし、この方法では画像データが中央サーバーに依存しており、サーバーがダウンしたり画像が破損・改ざんされるリスクがあります。 これにより、NFTが持つ本来の不変性・耐改ざん性が損なわれる恐れがあります。

そこで、画像データ自体もEthereumに保存することによって、永続的かつ耐改ざん性の高い形でトークンと画像を結びつけるためのインターフェースを提案しています。 このアプローチにより、完全に分散化されたNFTアーキテクチャが実現可能になります。

仕様

ERC2569では、Ethereum上にSVG画像を保存し、トークンに紐づいた画像として取得できるようにするためのインターフェースが定義されています。 対象となるのは、ERC721、ERC1155、将来の標準トークンです。 ファンジブルトークン(ERC20など)にも対応しています。

メソッド

EIP2569互換コントラクトは以下の2つのメソッドを持つ必要があります。

  • getTokenImageSvg(uint256 tokenId) external view returns (string memory)
  • setTokenImageSvg(uint256 tokenId, string memory imageSvg) internal

これらは、SVG画像をEthereumに保存する処理と、Ethereumから画像を取得する処理を定義しています。

getTokenImageSvg

SVG画像を取得するための関数です。 トークンIDを引数に取り、そのIDに対応するSVG画像の内容を返します。

ERC-721, ERC-1155など、トークンIDを持つトークンコントラクトに保存されたSVG画像の文字列を取得します。 ファンジブルトークン(ERC20など)の場合、IDは必要なく画像データを格納している文字列変数から直接取得します。

引数
  • tokenId
    • トークンの識別ID。
    • NFT(ERC721/ERC1155など)の場合は画像とIDが紐づけられ、IDを使って画像を取得します。
    • ファンジブルトークンの場合はID不要です。

setTokenImageSvg

SVG画像を設定するための関数。 内部関数(internal)として定義されており、トークンIDと画像データ(文字列)を引数に取りコントラクトに保存します。

ERC721、ERC1155などのトークンIDに対応するSVG画像をコントラクトの文字列変数に保存します。 ファンジブルトークンの場合は、IDではなく直接文字列変数に保存します。

引数
  • tokenId
    • 画像を紐づけるトークンのID。
    • NFTであればIDと画像が1対1で対応します。
  • imageSvg
    • SVG画像の内容を文字列として渡します。
    • 最低限<svg>タグ内にname属性とdesc(説明)属性が含まれている必要があります。

画像保存の手順

  1. 画像保持用の変数定義 SVG画像の内容を格納するためのstring型変数、または複数画像に対応するためのstring[]型変数を定義します。

  2. 画像設定用関数の定義 SVG画像データ(文字列)を、上記の変数に保存するための関数を定義します。

画像取得の手順

  1. トークンIDのある場合 ERC721やERC1155などのIDを持つトークンでは、そのIDを引数にしてgetTokenImageSvgを呼び出し、画像を取得します。

  2. トークンIDのない場合 ERC20などIDが存在しないトークンでは、画像を保持している文字列変数から直接取得するため、IDを使わずに画像を取得できます。

この仕様により、トークンに紐づく画像を完全にEthereum上で管理できるようになり、中央集権的な外部ストレージに依存せず、安全かつ永続的な画像管理が実現されます。

補足

ブロックチェーン技術が誕生して以降、情報を永続的かつ改ざん不可能な形で保存する方法として、トランザクションにテキストデータを埋め込む技術を活用してきました。 しかし、これまで画像データを同様の方法で保存する手段は存在しませんでした。

その理由の1つは、画像ファイルのサイズがテキストファイルに比べて非常に大きいため、Ethereumに直接保存しようとするとガスコストが膨大になり、ブロックのガスリミットを超えてしまうという技術的な制約があったからです。

しかし、SVG(Scalable Vector Graphics)という仕様がW3Cによって1999年に策定されたことにより、この制約は大きく変わりました。

SVGの利点とEthereumでの画像保存

SVGは、以下のようなラスター画像(JPEGやPNGなど)にはないいくつかの利点を持ちます。

  • ファイルサイズがコンパクト ピクセルベースの画像は高品質を保つために大きなサイズで保存されがちですが、SVGはベクター形式であるため、拡大縮小しても品質が劣化せず最小限のサイズで保存できます。

この「コンパクトなファイルサイズ」は、Ethereumのようなオンチェーン環境における画像保存における最大の課題を解決する手段となります。 そのため、SVG形式で画像を保存することは、Ethereum上で画像を永続的かつ改ざん耐性のある形で管理する有効な方法だと考えられます。

トークンに画像を表示する必要性

現在のDAppでは、ERC721などのNFTに画像を表示することが一般的です。 一方で、ERC20などのファンジブルトークンには画像がないケースがほとんどです。

しかし、将来的にはERC20を含むすべてのトークンに画像を持たせるニーズが高まると考えられます。 DAppのユーザビリティ向上やブランド認知の観点からも、トークンにアイコンやロゴを持たせることは有用です。

中央集権的ストレージの限界とSVGによる代替

現在、画像は中央集権的なサーバーに保存されていることが多く、そのサーバーがダウンしたり、悪意ある変更を受けるリスクがあります。 これは本質的に分散性・信頼性を重視するブロックチェーンの理念に反しています。

SVG形式で画像を変換し、Ethereumに直接保存することでこうしたリスクを排除できます。 このアプローチは、ERC721、ERC1155、ERC20といった既存のトークン標準に限らず、今後登場する新しい標準にも対応可能な柔軟性を持っています。

このように、SVGを利用することで、より安全かつ永続的なトークン画像の管理が実現でき、今後のDApp開発において広く活用されることが期待されます。

参考実装

js
// ----- IERC721GetImageSvg.sol -------------------------

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional retrieving SVG image extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
contract IERC721GetImageSvg is IERC721 {
    function getTokenImageSvg(uint256 tokenId) external view returns (string memory);
}


// ----- ERC721GetImageSvg.sol -------------------------

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/GSN/Context.sol";
import "@openzeppelin/contracts/token/ERC721/./ERC721.sol";
import "@openzeppelin/contracts/introspection/ERC165.sol";
import "./IERC721GetImageSvg.sol";

contract ERC721GetImageSvg is Context, ERC165, ERC721, IERC721GetImageSvg {
    // Mapping for token Images
    mapping(uint256 => string) private _tokenImageSvgs;

    /*
     *     bytes4(keccak256('getTokenImageSvg(uint256)')) == 0x87d2f48c
     *
     *     => 0x87d2f48c == 0x87d2f48c
     */
    bytes4 private constant _INTERFACE_ID_ERC721_GET_TOKEN_IMAGE_SVG = 0x87d2f48c;

    /**
     * @dev Constructor function
     */
    constructor () public {
        // register the supported interfaces to conform to ERC721 via ERC165
        _registerInterface(_INTERFACE_ID_ERC721_GET_TOKEN_IMAGE_SVG);
    }

    /**
     * @dev Returns an SVG Image for a given token ID.
     * Throws if the token ID does not exist. May return an empty string.
     * @param tokenId uint256 ID of the token to query
     */
    function getTokenImageSvg(uint256 tokenId) external view returns (string memory) {
        require(_exists(tokenId), "ERC721GetImageSvg: SVG Image query for nonexistent token");
        return _tokenImageSvgs[tokenId];
    }

    /**
     * @dev Internal function to set the token SVG image for a given token.
     * Reverts if the token ID does not exist.
     * @param tokenId uint256 ID of the token to set its SVG image
     * @param imagesvg string SVG  to assign
     */
    function setTokenImageSvg(uint256 tokenId, string memory imagesvg) internal {
        require(_exists(tokenId), "ERC721GetImageSvg: SVG image set of nonexistent token");
        _tokenImageSvgs[tokenId] = imagesvg;
    }

}


// ----- ERC721ImageSvgMintable.sol -------------------------

pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC721/ERC721Metadata.sol";
import "@openzeppelin/contracts/access/roles/MinterRole.sol";
import "./ERC721GetImageSvg.sol";

/**
 * @title ERC721ImageSvgMintable
 * @dev ERC721 minting logic with imagesvg.
 */
contract ERC721ImageSvgMintable is ERC721, ERC721Metadata, ERC721GetImageSvg, MinterRole {
    /**
     * @dev Function to mint tokens.
     * @param to The address that will receive the minted tokens.
     * @param tokenId The token id to mint.
     * @param tokenImageSvg The token SVG image of the minted token.
     * @return A boolean that indicates if the operation was successful.
     */
    function mintWithTokenImageSvg(address to, uint256 tokenId, string memory tokenImageSvg) public onlyMinter returns (bool) {
        _mint(to, tokenId);
        setTokenImageSvg(tokenId, tokenImageSvg);
        return true;
    }
}


We propose to add three sol files in the existing ERC-1155 implementation.
Here are the details for the proposed sol files.

// ----- IERC1155GetImageSvg.sol -------------------------

pragma solidity ^0.5.0;

import "./IERC1155.sol";

/**
 * @title ERC-1155 Multi Token Standard, retrieving SVG image for a token
 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md
 */
contract IERC1155GetImageSvg is IERC1155 {
    function getTokenImageSvg(uint256 tokenId) external view returns (string memory);
}


// ----- ERC1155GetImageSvg.sol -------------------------

pragma solidity ^0.5.0;

import "./ERC1155.sol";
import "./IERC1155GetImageSvg.sol";

contract ERC1155GetImageSvg is ERC165, ERC1155, IERC1155GetImageSvg {
    // Mapping for token Images
    mapping(uint256 => string) private _tokenImageSvgs;

    /*
     *     bytes4(keccak256('getTokenImageSvg(uint256)')) == 0x87d2f48c
     *
     *     => 0x87d2f48c == 0x87d2f48c
     */
    bytes4 private constant _INTERFACE_ID_ERC1155_GET_TOKEN_IMAGE_SVG = 0x87d2f48c;

    /**
     * @dev Constructor function
     */
    constructor () public {
        // register the supported interfaces to conform to ERC1155 via ERC165
        _registerInterface(_INTERFACE_ID_ERC1155_GET_TOKEN_IMAGE_SVG);
    }


    /**
     * @dev Returns an SVG Image for a given token ID.
     * Throws if the token ID does not exist. May return an empty string.
     * @param tokenId uint256 ID of the token to query
     */
    function getTokenImageSvg(uint256 tokenId) external view returns (string memory) {
        require(_exists(tokenId), "ERC1155GetImageSvg: SVG Image query for nonexistent token");
        return _tokenImageSvgs[tokenId];
    }

    /**
     * @dev Internal function to set the token SVG image for a given token.
     * Reverts if the token ID does not exist.
     * @param tokenId uint256 ID of the token to set its SVG image
     * @param imagesvg string SVG  to assign
     */
    function setTokenImageSvg(uint256 tokenId, string memory imagesvg) internal {
        require(_exists(tokenId), "ERC1155GetImageSvg: SVG image set of nonexistent token");
        _tokenImageSvgs[tokenId] = imagesvg;
    }

}



// ----- ERC1155MixedFungibleWithSvgMintable.sol -------------------------

pragma solidity ^0.5.0;

import "./ERC1155MixedFungibleMintable.sol";
import "./ERC1155GetImageSvg.sol";

/**
    @dev Mintable form of ERC1155 with SVG images
    Shows how easy it is to mint new items with SVG images
*/

contract ERC1155MixedFungibleWithSvgMintable is ERC1155, ERC1155MixedFungibleMintable, ERC1155GetImageSvg {
    /**
     * @dev Function to mint non-fungible tokens.
     * @param _to The address that will receive the minted tokens.
     * @param _type The token type to mint.
     * @param tokenImageSvg The token SVG image of the minted token.
     */
    function mintNonFungibleWithImageSvg(uint256 _type, address[] calldata _to, string memory tokenImageSvg) external creatorOnly(_type) {
        mintNonFungible(_type, _to);
        setTokenImageSvg(_type, tokenImageSvg);
    }


    /**
     * @dev Function to mint fungible tokens.
     * @param _to The address that will receive the minted tokens.
     * @param _id The token type to mint.
     * @param _quantities The number of tokens for a type to mint.
     * @param tokenImageSvg The token SVG image of the minted token.
     */
    function mintFungibleWithImageSvg(uint256 _id, address[] calldata _to, uint256[] calldata _quantities, string memory tokenImageSvg) external creatorOnly(_id) {
        mintFungible(_id, _to, _quantities, tokenImageSvg)  {
        setTokenImageSvg(_id, tokenImageSvg);
    }
}



We propose to add three sol files in the existing ERC-20 implementation.
Here are the details for the proposed sol files.


// ----- IERC20GetImageSvg.sol -------------------------

pragma solidity ^0.5.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title ERC-20 Fungible Token Standard, retrieving SVG image for a token
 * @dev See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
 */
contract IERC20GetImageSvg is IERC20 {
    function getTokenImageSvg() external view returns (string memory);
}


// ----- ERC20GetImageSvg.sol -------------------------

pragma solidity ^0.5.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./IERC20GetImageSvg.sol";

contract ERC20GetImageSvg is ERC20, IERC20GetImageSvg {
    string private _tokenImageSvg;
//将图片实现写在构造器中
    constructor(string calldata svgCode) public {
_tokenImageSvg = svgCode
}

    /**
     * @dev Returns an SVG Image.
     */
    function getTokenImageSvg() external view returns (string memory) {
        return _tokenImageSvg;
    }

}

引用

Hua Zhang (@dgczhh), Yuefei Tan (@whtyfhas), Derek Zhou (@zhous), Ran Xing (@lemontreeran), "ERC-2569: Saving and Displaying Image Onchain for Universal Tokens [DRAFT]," Ethereum Improvement Proposals, no. 2569, March 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2569.

最後に

今回は「トークンに紐づく画像をオンチェーン上にSVG形式で保存する仕組みを提案しているERC2569」についてまとめてきました! いかがだったでしょうか?

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

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