Soulbound Token

#juneboku > topics > Soulbound Token
created: 2022-09-13 / updated: 2022-09-26

Non-FungibleかつNon-Transferableなトークン。運転免許証や卒業証書などの各種の証明書やPOAPのような参加証明などによくマッチしそうなインターフェイスのトークン。


WagumiのSoulbound Tokenの実装例
https://polygonscan.com/address/0xef756b67b90026f91d047d1b991f87d657309a42#code

/**
 *Submitted for verification at polygonscan.com on 2022-09-08
*/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

library  SbtLib {
    bytes32 constant SBT_STRUCT_POSITION = keccak256("xyz.wagumi.sbt");

    struct SbtStruct {
        address contractOwner;
        string name;
        string symbol;
        string baseURI;
        bytes32 validator;
        mapping(bytes4 => bool) interfaces;
        mapping(address => uint256) balances;
        mapping(uint256 => address) owners;
        mapping(uint256 => SbbStruct[]) sbbs;
        mapping(bytes32 => uint256) sbbIndex;
    }

    struct SbbStruct {
        uint256 chainId;
        address contractAddress;
        uint256 tokenId;
    }

  function sbtStorage()
    internal 
    pure 
    returns (SbtStruct storage sbtstruct) 
  {
    bytes32 position = SBT_STRUCT_POSITION;
    assembly {
      sbtstruct.slot := position
    }
  }


}
// File: diamond/Sbt.sol


pragma solidity ^0.8.16.0;


contract Sbt {
    function init(
        address _contractOwner,
        string calldata _name,
        string calldata _symbol,
        string calldata _baseURI,
        bytes32 _validator
    ) external {
        SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
        require(sbtstruct.contractOwner == address(0), "INITED ALREADY");
        sbtstruct.contractOwner = _contractOwner;
        sbtstruct.name = _name;
        sbtstruct.symbol = _symbol;
        sbtstruct.baseURI = _baseURI;
        sbtstruct.validator = _validator;
        sbtstruct.interfaces[(bytes4)(0x01ffc9a7)] = true; //ERC165
        sbtstruct.interfaces[(bytes4)(0x5b5e139f)] = true; //ERC721metadata
    }

    mapping(bytes4 => address) public implementations;

    function setImplementation(
        bytes4[] calldata _sigs,
        address[] calldata _impAddress
    ) external {
        SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
        require(msg.sender == sbtstruct.contractOwner, "OWNER ONLY");
        require(_sigs.length == _impAddress.length, "INVAILED LENGTH");
        for (uint256 i = 0; i < _sigs.length; i++) {
            unchecked {
                implementations[_sigs[i]] = _impAddress[i];
            }
        }
    }

    function contractOwner() external view returns (address) {
        SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
        return sbtstruct.contractOwner;
    }

    function supportsInterface(bytes4 _interfaceID)
        external
        view
        returns (bool)
    {
        SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
        return sbtstruct.interfaces[_interfaceID];
    }

    function name() external view returns (string memory) {
        SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
        return sbtstruct.name;
    }

    function symbol() external view returns (string memory) {
        SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
        return sbtstruct.symbol;
    }

    function tokenURI(uint256 _tokenId) external view returns (string memory) {
        SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
        return
            string(
                abi.encodePacked(sbtstruct.baseURI, toString(_tokenId), ".json")
            );
    }

    function ownerOf(uint256 _tokenId) external view returns (address) {
        SbtLib.SbtStruct storage sbtstruct = SbtLib.sbtStorage();
        return sbtstruct.owners[_tokenId];
    }

    function toString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            unchecked {
                digits++;
                temp /= 10;
            }
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            unchecked {
                digits -= 1;
                buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                value /= 10;
            }
        }
        return string(buffer);
    }

    fallback() external payable {
        address _imp = implementations[msg.sig];
        require(_imp != address(0), "Function does not exist");
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), _imp, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }

    receive() external payable {}
}

Mentioned in days

In 2022-09-29 Thu Read full day →

読書会のあとはWagumiのSoulbound Tokenも見に行ってみた。ソースコードの話よりは社会寄りの話のようだった。Discordのサーバからサーバに移動するだけでさまざまな会に参加できて便利だな、と思った。

@wagumiDAO: 和組Discordアイディア壁打ちVCにて、第5回SBT勉強会を開催します♪
https://pbs.twimg.com/media/FduWRDCaEAEf6m7.jpg

In 2022-09-25 Sun Read full day →

今日の取り組み

Soulbound Tokenについて理解を深めたくてソースコードを読んでいくつもりだったが、21:30の時点でPolygonScanがダウンしていたようで閲覧できなかった。
https://polygonscan.com/address/0xef756b67b90026f91d047d1b991f87d657309a42#code

仕方がないのでWagumiのSoulbound Tokenの周辺情報をごそごそしていた。NotionやGitHubをデータストアとして採用しているみたいで、現実的なWeb 2.5アーキテクチャって感じですねぇ。

「この人は、こう関わった」という記録の残し方は参考になるなあ。自分が今から新しくコミュニティをつくるとしたら?という観点で考えるとおもしろい。

In 2022-09-13 Tue Read full day →

今日のウェブログ

Soulbound Tokenのページをつくった。Non-Fungible TokenよりSoulbound Tokenの方がぼくはテンションが上がるかもなあ、と思う。けども、まだまだ体験できていないことだらけなので、体験を重ねながら見極めていかないとね。わかった気になると見誤るだろうなって思う。

Archives : 2,609 days