// File: contracts/Palmdeamon.sol



//SPDX-License-Identifier: Unlicense
import "@rari-capital/solmate/src/tokens/ERC721.sol";
import "./metadata.sol";

pragma solidity ^0.8.0;

contract Palmdeamon is ERC721, Metadata {
    address public admin;
    address public verificationcontract;

    mapping(uint256 => tdata) public tokendata;

    struct tdata {
        uint256 moisture;
        uint256 temperature;
        uint256 colorandlocation;
        string rtimestamp;
    }

    constructor(address _admin) ERC721("Seed Capital", "SDC") {
        admin = _admin;
    }

    function tokenURI(uint256 id) public view override returns (string memory) {
        return
            generatemetadata(
                id,
                tokendata[id].moisture,
                tokendata[id].temperature,
                tokendata[id].colorandlocation,
                tokendata[id].rtimestamp
            );
    }

    function setverificationcontract(address _verificationcontract) public {
        require(msg.sender == admin, "only admin can set verificationcontract");
        verificationcontract = _verificationcontract;
    }

    function setadmin(address newadmin) public {
        require(msg.sender == admin, "only admin can set admin");
        admin = newadmin;
    }

    function generatecolorprofile(
        uint256 profile,
        string memory firsthex,
        string memory secondhex,
        string memory venue,
        string memory plant,
        string memory curator 
    ) public {
        require(msg.sender == admin, "only admin can set colorprofile");
        _generatecolorprofile(
            profile,
            firsthex,
            secondhex,
            venue,
            plant,
            curator 
        );
    }

    function mintafterverification(
        uint256 value1,
        uint256 value2,
        uint256 colorpointer,
        uint256 tokenid,
        string memory rtimestamp
    ) public {
        require(
            msg.sender == verificationcontract,
            "minting can only be called from verification contract"
        );
        require(
            verificationcontract != address(0),
            "no verification contract set"
        );
        tokendata[tokenid].moisture = value1;
        tokendata[tokenid].temperature = value2;
        tokendata[tokenid].colorandlocation = colorpointer;
        tokendata[tokenid].rtimestamp = rtimestamp;
        _mint(tx.origin, tokenid);
    }
}

// File: @rari-capital/solmate/src/tokens/ERC721.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
/// @dev Note that balanceOf does not revert if passed the zero address, in defiance of the ERC.
abstract contract ERC721 {
    /*///////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 indexed id);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id);

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /*///////////////////////////////////////////////////////////////
                          METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id) public view virtual returns (string memory);

    /*///////////////////////////////////////////////////////////////
                            ERC721 STORAGE                        
    //////////////////////////////////////////////////////////////*/

    mapping(address => uint256) public balanceOf;

    mapping(uint256 => address) public ownerOf;

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*///////////////////////////////////////////////////////////////
                              CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*///////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) public virtual {
        address owner = ownerOf[id];

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        require(from == ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from || msg.sender == getApproved[id] || isApprovedForAll[from][msg.sender],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            balanceOf[from]--;

            balanceOf[to]++;
        }

        ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes memory data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*///////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(ownerOf[id] == address(0), "ALREADY_MINTED");

        // Counter overflow is incredibly unrealistic.
        unchecked {
            balanceOf[to]++;
        }

        ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) internal virtual {
        address owner = ownerOf[id];

        require(ownerOf[id] != address(0), "NOT_MINTED");

        // Ownership check above ensures no underflow.
        unchecked {
            balanceOf[owner]--;
        }

        delete ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL SAFE MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(
        address to,
        uint256 id,
        bytes memory data
    ) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
interface ERC721TokenReceiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 id,
        bytes calldata data
    ) external returns (bytes4);
}

// File: contracts/metadata.sol



pragma solidity ^0.8.0;
import "./generativesvg.sol";

contract Metadata is GenerativeSvg {
    function generatemetadata(
        uint256 id,
        uint256 moisture,
        uint256 temperature,
        uint256 locationcolor,
        string memory rtimestamp
    ) public view returns (string memory) {
        string memory name = generatename(id);
        string memory description = "Seed Capital - Certificates of Growth";
        string memory attributes = generateattributes(
            cschemes[locationcolor].venue,
            cschemes[locationcolor].curator
        );
        string memory image = getsvgbase64(
            moisture,
            temperature,
            locationcolor,
            rtimestamp,
            id
        );
        return
            string(
                abi.encodePacked(
                    "data:text/plain,"
                    '{"name":"',
                    name,
                    '", "description":"',
                    description,
                    '", "image": "',
                    image,
                    '",',
                    '"attributes": ',
                    attributes,
                    "}"
                )
            );
    }

    function generatename(uint256 tokenId)
        internal
        pure
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "Certificate of Growth ",
                    Strings.toString(tokenId)
                )
            );
    }

    function generateattributes(
        string memory venue,
        string memory curator
    ) internal pure returns (string memory) {
        return
            string(
                abi.encodePacked(
                    "[",
                    '{"trait_type":"Venue",',
                    '"value":"',
                    venue,
                    '"},',
                    '{"trait_type":"Curator",',
                    '"value":"',
                    curator,
                    '"}'
                    "]"
                )
            );
    }
}

// File: contracts/generativesvg.sol



pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Strings.sol";
import "base64-sol/base64.sol";

//mapfunction example moisture: 411,300,1000,3600,5600   mapfactor: 1000
// moisture: 1013, temperature: 20.125,

contract GenerativeSvg {
    using Strings for string;

    string internal header =
        "<?xml version='1.0' encoding='UTF-8'?><svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='480px' height='740px' viewBox='0 0 480 740' enable-background='new 0 0 480 740' xml:space='preserve'>";
    string internal background =
        "<rect y='140' fill='#141414' width='480' height='500'/>";

    uint256 mapfactor;
    mapping(uint256 => colorscheme) cschemes;

    struct colorscheme {
        string firsthex;
        string secondhex;
        string venue;
        string plant;
        string curator;
    }

    constructor() {
        cschemes[0].firsthex = "#f63f3c";
        cschemes[0].secondhex = "#13b9bc";
        cschemes[0].venue = "Art Dubai";
        cschemes[0].plant = "Dypsis lutescens";
        cschemes[0].curator = "Fingerprints DAO";

        cschemes[1].firsthex = "#ffea00";
        cschemes[1].secondhex = "#481249";

        cschemes[2].firsthex = "#167d5e";
        cschemes[2].secondhex = "#5e67b0";

        mapfactor = 100;
    }

    function mapvalue(
        uint256 value,
        uint256 leftMin,
        uint256 leftMax,
        uint256 rightMin,
        uint256 rightMax
    ) public view returns (uint256) {
        uint256 leftSpan = leftMax - leftMin;
        uint256 rightSpan = rightMax - rightMin;
        uint256 s = (value - leftMin) * mapfactor;
        uint256 valueScaled = s / uint256(leftSpan);
        return rightMin + (valueScaled * rightSpan) / mapfactor;
    }

    function _generatecolorprofile(
        uint256 profile,
        string memory firsthex,
        string memory secondhex,
        string memory venue,
        string memory plant,
        string memory curator
    ) internal {
        cschemes[profile].firsthex = firsthex;
        cschemes[profile].secondhex = secondhex;
        cschemes[profile].venue = venue;
        cschemes[profile].plant = plant;
        cschemes[profile].curator = curator;
    }

    function buildhsl(uint256 temp, uint256 moisture)
        internal
        view
        returns (string memory)
    {
        string memory h = Strings.toString(
            mapvalue(moisture, 70000, 80000, 0, 360)
        );
        string memory l = Strings.toString(
            mapvalue(temp, 19000, 23000, 50, 100)
        );
        return string(abi.encodePacked("hsl(", h, ",100%,", l, "%)"));
        //= 'hsl(0, 100%, 50%)';
    }

    function gradienty(uint256 temp) internal view returns (string memory) {
        return Strings.toString(mapvalue(temp, 19000, 23000, 300, 1000));
    }

    function gradientx(uint256 moisture) internal view returns (string memory) {
        return Strings.toString(mapvalue(moisture, 70000, 80000, 50, 700));
    }

    function gradientz(uint256 moisture) internal view returns (string memory) {
        return Strings.toString(mapvalue(moisture, 70000, 80000, 100, 500));
    }

    function lineargradienty(uint256 temp, uint256 locationcolor)
        internal
        view
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<linearGradient id='SVGID_1_' gradientUnits='userSpaceOnUse' x1='180.0005' y1='",
                    gradienty(temp),
                    "' x2='180.0005' y2='160.0005'> <stop  offset='0' style='stop-color:",
                    cschemes[locationcolor].secondhex,
                    ";stop-opacity:0'/><stop  offset='0.5' style='stop-color:",
                    cschemes[locationcolor].secondhex,
                    "'/> <stop  offset='1' style='stop-color:",
                    cschemes[locationcolor].secondhex,
                    ";stop-opacity:0'/></linearGradient> <rect y='160' fill='url(#SVGID_1_)' width='360' height='480'/>"
                )
            );
    }

    function lineargradientx(uint256 moisture, uint256 locationcolor)
        internal
        view
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<linearGradient id='SVGID_2_' gradientUnits='userSpaceOnUse' x1='0' y1='400' x2='",
                    gradientx(moisture),
                    "' y2='400'><stop  offset='0' style='stop-color:",
                    cschemes[locationcolor].firsthex,
                    ";stop-opacity:0'/><stop  offset='0.5' style='stop-color:",
                    cschemes[locationcolor].firsthex,
                    "'/> <stop  offset='1' style='stop-color:",
                    cschemes[locationcolor].firsthex,
                    ";stop-opacity:0'/></linearGradient> <rect y='160' fill='url(#SVGID_2_)' width='360' height='480'/>"
                )
            );
    }

    function lineargradientz(uint256 temp, uint256 moisture)
        internal
        view
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<linearGradient id='SVGID_3_' gradientUnits='userSpaceOnUse' x1='220.0225' y1='329.4453' x2='",
                    gradientz(moisture),
                    "' y2='219.445' gradientTransform='matrix(3.6 0 0 3.6 -684.0762 -606)'> <stop  offset='0' style='stop-color:",
                    buildhsl(temp, moisture),
                    ";stop-opacity:0'/> <stop  offset='1' style='stop-color:",
                    buildhsl(temp, moisture),
                    "'/> </linearGradient> <rect y='220' fill='url(#SVGID_3_)' width='360.001' height='360'/>"
                )
            );
    }

    function returnfixtext() internal pure returns (string memory) {
        return
            "<text transform='matrix(1 0 0 1 10 185.2061)' font-family='Arial' font-size='16'>Soil Moisture (x-axis)</text><text transform='matrix(1 0 0 1 10 141.4561)' font-family='Arial' font-size='16'>Time</text><text transform='matrix(1 0 0 1 10 605.2061)' font-family='Arial' font-size='16'>Plant</text><text transform='matrix(1 0 0 1 10 651.4561)' font-family='Arial' font-size='16'>Location</text><text transform='matrix(1 0 0 1 250 185.2061)' font-family='Arial' font-size='16'>Temperature (y-axis)</text><text transform='matrix(1 0 0 1 427.0029 241.4556)' fill='#EBEBEB' font-family='Arial' font-size='16'>terra0</text><text transform='matrix(1 0 0 1 9 80.7349)' fill='#141414' font-family='Times-Roman, Times' font-size='45'>Certificate of Growth </text>";
    }

    function buildpercentage(uint256 percentage)
        public
        pure
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    Strings.toString((percentage % 100000) / 1000),
                    ",",
                    Strings.toString((percentage % 1000) / 10)
                )
            );
    }



    function xypoint(uint256 moisture, uint256 temperature)
        internal
        view
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<text transform='matrix(1 0 0 1 ",
                    Strings.toString(mapvalue(moisture, 70000, 80000, 0, 335)),
                    " ",
                    Strings.toString(
                        580 -
                            (mapvalue(temperature, 19000, 23000, 245, 580) -
                                245)
                    ),
                    ")' fill='#EBEBEB' font-family='Courier, monospace' font-size='40'>+</text>"
                )
            );
    }

    function buildmoisture(uint256 moisture)
        internal
        pure
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<text transform='matrix(1 0 0 1 9 205.3359)' fill='#141414' font-family='Courier, monospace' font-size='24'>",
                    buildpercentage(moisture),
                    "%</text>"
                )
            );
    }

    function buildtemperature(uint256 temperature)
        internal
        pure
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<text transform='matrix(1 0 0 1 249 205.3364)' fill='#141414' font-family='Courier, monospace' font-size='24'>",
                    buildpercentage(temperature),
                    "C</text>"
                )
            );
    }

    function buildlocation(uint256 colorpointer)
        internal
        view
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<text transform='matrix(1 0 0 1 10 671.5859)'><tspan x='0' y='0' fill='#141414' font-family='Courier, monospace' font-size='24'>",
                    cschemes[colorpointer].venue,
                    "</tspan><tspan x='0' y='20' fill='#141414' font-family='Courier' font-size='24'>",
                    cschemes[colorpointer].curator,
                    "</tspan></text>"
                )
            );
    }

    function buildtokennumber(uint256 tokenid)
        internal
        pure
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<text transform='matrix(1 0 0 1 412.3906 80)' fill='#141414' font-family='Courier, monospace' font-size='24'>",
                    Strings.toString(tokenid),
                    "</text>"
                )
            );
    }

    function buildbars(uint256 moisture, uint256 temp)
        internal
        view
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "<text transform='matrix(1 0 0 1 360 ",
                    Strings.toString(
                        650 - mapvalue(moisture, 70000, 80000, 250, 390)
                    ),
                    ")' fill='#EBEBEB' font-family='Courier, monospace' font-size='49'>_</text>",
                    "<text transform='matrix(1 0 0 1 390 ",
                    Strings.toString(
                        650 - mapvalue(temp, 19000, 23000, 250, 390)
                    ),
                    ")' fill='#EBEBEB' font-family='Courier, monospace' font-size='49'>_</text>"
                )
            );
    }

    function returndynamictext(
        uint256 moisture,
        uint256 temp,
        uint256 locationcolor,
        string memory humantimestamp,
        uint256 tokenid
    ) internal view returns (string memory) {
        return
            string(
                abi.encodePacked(
                    buildtemperature(temp),
                    buildmoisture(moisture),
                    buildlocation(locationcolor),
                    "<text transform='matrix(1 0 0 1 10 625.3359)' fill='#141414' font-family='Courier, monospace' font-size='24'>",
                    cschemes[locationcolor].plant,
                    "</text>",
                    buildtokennumber(tokenid),
                    "<text transform='matrix(1 0 0 1 10 161.5859)' fill='#141414' font-family='Courier, monospace' font-size='24'>",
                    humantimestamp,
                    "</text>"
                )
            );
    }

    function generatebars() internal pure returns (string memory) {
        return
            string(
                abi.encodePacked(
                    "<linearGradient id='SVGID_4_' gradientUnits='userSpaceOnUse' x1='405' y1='580' x2='405' y2='220.1191'> <stop  offset='0' style='stop-color:#00FF00'/> <stop  offset='1' style='stop-color:#C2000B'/> </linearGradient> <rect x='390' y='220.119' fill='url(#SVGID_4_)' width='30' height='359.881'/><linearGradient id='SVGID_5_' gradientUnits='userSpaceOnUse' x1='375' y1='580' x2='375' y2='220.1191'> <stop  offset='0' style='stop-color:#141414'/> <stop  offset='1' style='stop-color:#00A0C6'/> </linearGradient>",
                    "<path opacity='0.25' fill='none' stroke='#EBEBEB' stroke-width='2' stroke-miterlimit='10' d='M300,220v360 M240,220v360 M180,220 v360 M120,220v360 M60,220v360 M360,520H0 M360,460H0 M360,400H0 M360,340H0 M360,280H0'/><rect x='360' y='260' opacity='0.25' fill='#EBEBEB' width='30' height='120'/><rect x='390' y='240' opacity='0.25' fill='#EBEBEB' width='30' height='160'/>",
                    "<rect x='360' y='220.119' fill='url(#SVGID_5_)' width='30' height='359.881'/>"
                )
            );
    }

    function generatesvg(
        uint256 moisture,
        uint256 temp,
        uint256 locationcolor,
        string memory humantimestamp,
        uint256 id
    ) public view returns (string memory) {
        return
            string(
                abi.encodePacked(
                    header,
                    background,
                    generatebars(),
                    lineargradienty(temp, locationcolor),
                    lineargradientx(moisture, locationcolor),
                    lineargradientz(temp, moisture),
                    "<path fill='#EBEBEB' d='M0,580v160h480V580H0z'/> <path fill='#EBEBEB' d='M0,0v220h480V0H0z'/>",
                    "<path opacity='0.25' fill='none' stroke='#EBEBEB' stroke-width='2' stroke-miterlimit='10' d='M300,220v360 M240,220v360 M180,220v360 M120,220v360 M60,220v360 M360,520H0 M360,460H0 M360,400H0 M360,340H0 M360,280H0'/>",
                    returnfixtext(),
                    returndynamictext(
                        moisture,
                        temp,
                        locationcolor,
                        humantimestamp,
                        id
                    ),
                    xypoint(moisture, temp),
                    buildbars(moisture, temp),
                    "</svg >"
                )
            );
    }

    function getsvgbase64(
        uint256 moisture,
        uint256 temperature,
        uint256 locationcolor,
        string memory humantimestamp,
        uint256 id
    ) public view returns (string memory) {
        return
            string(
                abi.encodePacked(
                    "data:image/svg+xml;base64,",
                    Base64.encode(
                        bytes(
                            generatesvg(
                                moisture,
                                temperature,
                                locationcolor,
                                humantimestamp,
                                id
                            )
                        )
                    )
                )
            );
    }
}

// File: @openzeppelin/contracts/utils/Strings.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)

pragma solidity ^0.8.0;

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

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

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }
}

// File: base64-sol/base64.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0;

/// @title Base64
/// @author Brecht Devos - <brecht@loopring.org>
/// @notice Provides functions for encoding/decoding base64
library Base64 {
    string internal constant TABLE_ENCODE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    bytes  internal constant TABLE_DECODE = hex"0000000000000000000000000000000000000000000000000000000000000000"
                                            hex"00000000000000000000003e0000003f3435363738393a3b3c3d000000000000"
                                            hex"00000102030405060708090a0b0c0d0e0f101112131415161718190000000000"
                                            hex"001a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132330000000000";

    function encode(bytes memory data) internal pure returns (string memory) {
        if (data.length == 0) return '';

        // load the table into memory
        string memory table = TABLE_ENCODE;

        // multiply by 4/3 rounded up
        uint256 encodedLen = 4 * ((data.length + 2) / 3);

        // add some extra buffer at the end required for the writing
        string memory result = new string(encodedLen + 32);

        assembly {
            // set the actual output length
            mstore(result, encodedLen)

            // prepare the lookup table
            let tablePtr := add(table, 1)

            // input ptr
            let dataPtr := data
            let endPtr := add(dataPtr, mload(data))

            // result ptr, jump over length
            let resultPtr := add(result, 32)

            // run over the input, 3 bytes at a time
            for {} lt(dataPtr, endPtr) {}
            {
                // read 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // write 4 characters
                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1)
                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1)
                mstore8(resultPtr, mload(add(tablePtr, and(shr( 6, input), 0x3F))))
                resultPtr := add(resultPtr, 1)
                mstore8(resultPtr, mload(add(tablePtr, and(        input,  0x3F))))
                resultPtr := add(resultPtr, 1)
            }

            // padding with '='
            switch mod(mload(data), 3)
            case 1 { mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) }
            case 2 { mstore(sub(resultPtr, 1), shl(248, 0x3d)) }
        }

        return result;
    }

    function decode(string memory _data) internal pure returns (bytes memory) {
        bytes memory data = bytes(_data);

        if (data.length == 0) return new bytes(0);
        require(data.length % 4 == 0, "invalid base64 decoder input");

        // load the table into memory
        bytes memory table = TABLE_DECODE;

        // every 4 characters represent 3 bytes
        uint256 decodedLen = (data.length / 4) * 3;

        // add some extra buffer at the end required for the writing
        bytes memory result = new bytes(decodedLen + 32);

        assembly {
            // padding with '='
            let lastBytes := mload(add(data, mload(data)))
            if eq(and(lastBytes, 0xFF), 0x3d) {
                decodedLen := sub(decodedLen, 1)
                if eq(and(lastBytes, 0xFFFF), 0x3d3d) {
                    decodedLen := sub(decodedLen, 1)
                }
            }

            // set the actual output length
            mstore(result, decodedLen)

            // prepare the lookup table
            let tablePtr := add(table, 1)

            // input ptr
            let dataPtr := data
            let endPtr := add(dataPtr, mload(data))

            // result ptr, jump over length
            let resultPtr := add(result, 32)

            // run over the input, 4 characters at a time
            for {} lt(dataPtr, endPtr) {}
            {
               // read 4 characters
               dataPtr := add(dataPtr, 4)
               let input := mload(dataPtr)

               // write 3 bytes
               let output := add(
                   add(
                       shl(18, and(mload(add(tablePtr, and(shr(24, input), 0xFF))), 0xFF)),
                       shl(12, and(mload(add(tablePtr, and(shr(16, input), 0xFF))), 0xFF))),
                   add(
                       shl( 6, and(mload(add(tablePtr, and(shr( 8, input), 0xFF))), 0xFF)),
                               and(mload(add(tablePtr, and(        input , 0xFF))), 0xFF)
                    )
                )
                mstore(resultPtr, shl(232, output))
                resultPtr := add(resultPtr, 3)
            }
        }

        return result;
    }
}