// 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);
}
}