// SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.8.10; import '@openzeppelin/contracts/utils/Base64.sol'; import '@openzeppelin/contracts/utils/Strings.sol'; library ProfileTokenURILogic { uint8 internal constant DEFAULT_FONT_SIZE = 24; uint8 internal constant MAX_HANDLE_LENGTH_WITH_DEFAULT_FONT_SIZE = 17; /** * @notice Generates the token URI for the profile NFT. * * @dev The decoded token URI JSON metadata contains the following fields: name, description, image and attributes. * The image field contains a base64-encoded SVG. Both the JSON metadata and the image are generated fully on-chain. * * @param id The token ID of the profile. * @param followers The number of profile's followers. * @param owner The address which owns the profile. * @param handle The profile's handle. * @param imageURI The profile's picture URI. An empty string if has not been set. * * @return The profile's token URI as a base64-encoded JSON string. */ function getProfileTokenURI( uint256 id, uint256 followers, address owner, string memory handle, string memory imageURI ) external pure returns (string memory) { string memory handleWithAtSymbol = string(abi.encodePacked('@', handle)); return string( abi.encodePacked( 'data:application/json;base64,', Base64.encode( abi.encodePacked( '{"name":"', handleWithAtSymbol, '","description":"', handleWithAtSymbol, ' - Lens profile","image":"data:image/svg+xml;base64,', _getSVGImageBase64Encoded(handleWithAtSymbol, imageURI), '","attributes":[{"trait_type":"id","value":"#', Strings.toString(id), '"},{"trait_type":"followers","value":"', Strings.toString(followers), '"},{"trait_type":"owner","value":"', Strings.toHexString(uint160(owner)), '"},{"trait_type":"handle","value":"', handleWithAtSymbol, '"}]}' ) ) ) ); } /** * @notice Generates the token image. * * @dev If the image URI was set and meets URI format conditions, it will be embedded in the token image. * Otherwise, a default picture will be used. Handle font size is a function of handle length. * * @param handleWithAtSymbol The profile's handle beginning with "@" symbol. * @param imageURI The profile's picture URI. An empty string if has not been set. * * @return The profile token image as a base64-encoded SVG. */ function _getSVGImageBase64Encoded(string memory handleWithAtSymbol, string memory imageURI) internal pure returns (string memory) { return Base64.encode( abi.encodePacked( '', _getSVGProfilePicture(imageURI), '', handleWithAtSymbol, '' ) ); } /** * @notice Gets the fragment of the SVG correponding to the profile picture. * * @dev If the image URI was set and meets URI format conditions, this will return an image tag referencing it. * Otherwise, a group tag that renders the default picture will be returned. * * @param imageURI The profile's picture URI. An empty string if has not been set. * * @return The fragment of the SVG token's image correspondending to the profile picture. */ function _getSVGProfilePicture(string memory imageURI) internal pure returns (string memory) { if (_shouldUseCustomPicture(imageURI)) { return string( abi.encodePacked( '' ) ); } else { return ''; } } /** * @notice Maps the handle length to a font size. * * @dev Gives the font size as a function of handle length using the following formula: * * fontSize(handleLength) = 24 when handleLength <= 17 * fontSize(handleLength) = 24 - (handleLength - 12) / 2 when handleLength > 17 * * @param handleLength The profile's handle length. * @return The font size. */ function _handleLengthToFontSize(uint256 handleLength) internal pure returns (uint256) { return handleLength <= MAX_HANDLE_LENGTH_WITH_DEFAULT_FONT_SIZE ? DEFAULT_FONT_SIZE : DEFAULT_FONT_SIZE - (handleLength - 12) / 2; } /** * @notice Decides if Profile NFT should use user provided custom profile picture or the default one. * * @dev It checks if there is a custom imageURI set and makes sure it does not contain double-quotes to prevent * injection attacks through the generated SVG. * * @param imageURI The imageURI set by the profile owner. * * @return A boolean indicating whether custom profile picture should be used or not. */ function _shouldUseCustomPicture(string memory imageURI) internal pure returns (bool) { bytes memory imageURIBytes = bytes(imageURI); if (imageURIBytes.length == 0) { return false; } for (uint256 i = 0; i < imageURIBytes.length; i++) { if (imageURIBytes[i] == '"') { // Avoids embedding a user provided imageURI containing double-quotes to prevent injection attacks return false; } } return true; } }