// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.7.6; import '@openzeppelin/contracts/utils/Strings.sol'; import '@uniswap/v3-core/contracts/libraries/BitMath.sol'; import 'base64-sol/base64.sol'; /// @title NFTSVG /// @notice Provides a function for generating an SVG associated with a Uniswap NFT library NFTSVG { using Strings for uint256; string constant curve1 = 'M1 1C41 41 105 105 145 145'; string constant curve2 = 'M1 1C33 49 97 113 145 145'; string constant curve3 = 'M1 1C33 57 89 113 145 145'; string constant curve4 = 'M1 1C25 65 81 121 145 145'; string constant curve5 = 'M1 1C17 73 73 129 145 145'; string constant curve6 = 'M1 1C9 81 65 137 145 145'; string constant curve7 = 'M1 1C1 89 57.5 145 145 145'; string constant curve8 = 'M1 1C1 97 49 145 145 145'; struct SVGParams { string quoteToken; string baseToken; address poolAddress; string quoteTokenSymbol; string baseTokenSymbol; string feeTier; int24 tickLower; int24 tickUpper; int24 tickSpacing; int8 overRange; uint256 tokenId; string color0; string color1; string color2; string color3; string x1; string y1; string x2; string y2; string x3; string y3; } function generateSVG(SVGParams memory params) internal pure returns (string memory svg) { /* address: "0xe8ab59d3bcde16a29912de83a90eb39628cfc163", msg: "Forged in SVG for Uniswap in 2021 by 0xe8ab59d3bcde16a29912de83a90eb39628cfc163", sig: "0x2df0e99d9cbfec33a705d83f75666d98b22dea7c1af412c584f7d626d83f02875993df740dc87563b9c73378f8462426da572d7989de88079a382ad96c57b68d1b", version: "2" */ return string( abi.encodePacked( generateSVGDefs(params), generateSVGBorderText( params.quoteToken, params.baseToken, params.quoteTokenSymbol, params.baseTokenSymbol ), generateSVGCardMantle(params.quoteTokenSymbol, params.baseTokenSymbol, params.feeTier), generageSvgCurve(params.tickLower, params.tickUpper, params.tickSpacing, params.overRange), generateSVGPositionDataAndLocationCurve( params.tokenId.toString(), params.tickLower, params.tickUpper ), generateSVGRareSparkle(params.tokenId, params.poolAddress), '' ) ); } function generateSVGDefs(SVGParams memory params) private pure returns (string memory svg) { svg = string( abi.encodePacked( '", '', '" ) ) ), '"/>" ) ) ), '"/>" ) ) ), '" />', '" ) ) ), '" /> ', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ' ', '', '', '' ) ); } function generateSVGBorderText( string memory quoteToken, string memory baseToken, string memory quoteTokenSymbol, string memory baseTokenSymbol ) private pure returns (string memory svg) { svg = string( abi.encodePacked( '', '', baseToken, unicode' • ', baseTokenSymbol, ' ', ' ', baseToken, unicode' • ', baseTokenSymbol, ' ', '', quoteToken, unicode' • ', quoteTokenSymbol, ' ', quoteToken, unicode' • ', quoteTokenSymbol, ' ' ) ); } function generateSVGCardMantle( string memory quoteTokenSymbol, string memory baseTokenSymbol, string memory feeTier ) private pure returns (string memory svg) { svg = string( abi.encodePacked( ' ', quoteTokenSymbol, '/', baseTokenSymbol, '', feeTier, '', '' ) ); } function generageSvgCurve( int24 tickLower, int24 tickUpper, int24 tickSpacing, int8 overRange ) private pure returns (string memory svg) { string memory fade = overRange == 1 ? '#fade-up' : overRange == -1 ? '#fade-down' : '#none'; string memory curve = getCurve(tickLower, tickUpper, tickSpacing); svg = string( abi.encodePacked( '' '' '', '', '', '', generateSVGCurveCircle(overRange) ) ); } function getCurve( int24 tickLower, int24 tickUpper, int24 tickSpacing ) internal pure returns (string memory curve) { int24 tickRange = (tickUpper - tickLower) / tickSpacing; if (tickRange <= 4) { curve = curve1; } else if (tickRange <= 8) { curve = curve2; } else if (tickRange <= 16) { curve = curve3; } else if (tickRange <= 32) { curve = curve4; } else if (tickRange <= 64) { curve = curve5; } else if (tickRange <= 128) { curve = curve6; } else if (tickRange <= 256) { curve = curve7; } else { curve = curve8; } } function generateSVGCurveCircle(int8 overRange) internal pure returns (string memory svg) { string memory curvex1 = '73'; string memory curvey1 = '190'; string memory curvex2 = '217'; string memory curvey2 = '334'; if (overRange == 1 || overRange == -1) { svg = string( abi.encodePacked( '' ) ); } else { svg = string( abi.encodePacked( '', '' ) ); } } function generateSVGPositionDataAndLocationCurve( string memory tokenId, int24 tickLower, int24 tickUpper ) private pure returns (string memory svg) { string memory tickLowerStr = tickToString(tickLower); string memory tickUpperStr = tickToString(tickUpper); uint256 str1length = bytes(tokenId).length + 4; uint256 str2length = bytes(tickLowerStr).length + 10; uint256 str3length = bytes(tickUpperStr).length + 10; (string memory xCoord, string memory yCoord) = rangeLocation(tickLower, tickUpper); svg = string( abi.encodePacked( ' ', '', 'ID: ', tokenId, '', ' ', '', 'Min Tick: ', tickLowerStr, '', ' ', '', 'Max Tick: ', tickUpperStr, '' '', '', '', '' ) ); } function tickToString(int24 tick) private pure returns (string memory) { string memory sign = ''; if (tick < 0) { tick = tick * -1; sign = '-'; } return string(abi.encodePacked(sign, uint256(tick).toString())); } function rangeLocation(int24 tickLower, int24 tickUpper) internal pure returns (string memory, string memory) { int24 midPoint = (tickLower + tickUpper) / 2; if (midPoint < -125_000) { return ('8', '7'); } else if (midPoint < -75_000) { return ('8', '10.5'); } else if (midPoint < -25_000) { return ('8', '14.25'); } else if (midPoint < -5_000) { return ('10', '18'); } else if (midPoint < 0) { return ('11', '21'); } else if (midPoint < 5_000) { return ('13', '23'); } else if (midPoint < 25_000) { return ('15', '25'); } else if (midPoint < 75_000) { return ('18', '26'); } else if (midPoint < 125_000) { return ('21', '27'); } else { return ('24', '27'); } } function generateSVGRareSparkle(uint256 tokenId, address poolAddress) private pure returns (string memory svg) { if (isRare(tokenId, poolAddress)) { svg = string( abi.encodePacked( '', '', '' ) ); } else { svg = ''; } } function isRare(uint256 tokenId, address poolAddress) internal pure returns (bool) { bytes32 h = keccak256(abi.encodePacked(tokenId, poolAddress)); return uint256(h) < type(uint256).max / (1 + BitMath.mostSignificantBit(tokenId) * 2); } }