IncentivesButton.tsx
1 import { ProtocolAction } from '@aave/contract-helpers'; 2 import { valueToBigNumber } from '@aave/math-utils'; 3 import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives'; 4 import { DotsHorizontalIcon } from '@heroicons/react/solid'; 5 import { Box, SvgIcon, Typography } from '@mui/material'; 6 import { useState } from 'react'; 7 import { useEthenaIncentives } from 'src/hooks/useEthenaIncentives'; 8 import { useEtherfiIncentives } from 'src/hooks/useEtherfiIncentives'; 9 import { useMeritIncentives } from 'src/hooks/useMeritIncentives'; 10 import { useSonicIncentives } from 'src/hooks/useSonicIncentives'; 11 import { useZkSyncIgniteIncentives } from 'src/hooks/useZkSyncIgniteIncentives'; 12 import { useRootStore } from 'src/store/root'; 13 import { DASHBOARD } from 'src/utils/mixPanelEvents'; 14 15 import { ContentWithTooltip } from '../ContentWithTooltip'; 16 import { FormattedNumber } from '../primitives/FormattedNumber'; 17 import { TokenIcon } from '../primitives/TokenIcon'; 18 import { EthenaAirdropTooltipContent } from './EthenaIncentivesTooltipContent'; 19 import { EtherFiAirdropTooltipContent } from './EtherfiIncentivesTooltipContent'; 20 import { getSymbolMap, IncentivesTooltipContent } from './IncentivesTooltipContent'; 21 import { MeritIncentivesTooltipContent } from './MeritIncentivesTooltipContent'; 22 import { SonicAirdropTooltipContent } from './SonicIncentivesTooltipContent'; 23 import { ZkSyncIgniteIncentivesTooltipContent } from './ZkSyncIgniteIncentivesTooltipContent'; 24 25 interface IncentivesButtonProps { 26 symbol: string; 27 incentives?: ReserveIncentiveResponse[]; 28 displayBlank?: boolean; 29 } 30 31 const BlankIncentives = () => { 32 return ( 33 <Box 34 sx={{ 35 p: { xs: '0 4px', xsm: '3.625px 4px' }, 36 display: 'flex', 37 alignItems: 'center', 38 justifyContent: 'center', 39 }} 40 > 41 <Typography variant="main12" color="text.secondary"> 42 43 </Typography> 44 </Box> 45 ); 46 }; 47 48 export const MeritIncentivesButton = (params: { 49 symbol: string; 50 market: string; 51 protocolAction?: ProtocolAction; 52 }) => { 53 const [open, setOpen] = useState(false); 54 const { data: meritIncentives } = useMeritIncentives(params); 55 56 if (!meritIncentives) { 57 return null; 58 } 59 60 return ( 61 <ContentWithTooltip 62 tooltipContent={<MeritIncentivesTooltipContent meritIncentives={meritIncentives} />} 63 withoutHover 64 setOpen={setOpen} 65 open={open} 66 > 67 <Content incentives={[meritIncentives]} incentivesNetAPR={+meritIncentives.incentiveAPR} /> 68 </ContentWithTooltip> 69 ); 70 }; 71 72 export const ZkIgniteIncentivesButton = (params: { 73 market: string; 74 rewardedAsset?: string; 75 protocolAction?: ProtocolAction; 76 }) => { 77 const [open, setOpen] = useState(false); 78 const { data: zkSyncIgniteIncentives } = useZkSyncIgniteIncentives(params); 79 80 if (!zkSyncIgniteIncentives) { 81 return null; 82 } 83 84 return ( 85 <ContentWithTooltip 86 tooltipContent={ 87 <ZkSyncIgniteIncentivesTooltipContent zkSyncIgniteIncentives={zkSyncIgniteIncentives} /> 88 } 89 withoutHover 90 setOpen={setOpen} 91 open={open} 92 > 93 <Content 94 incentives={[zkSyncIgniteIncentives]} 95 incentivesNetAPR={+zkSyncIgniteIncentives.incentiveAPR} 96 /> 97 </ContentWithTooltip> 98 ); 99 }; 100 101 export const EthenaIncentivesButton = ({ rewardedAsset }: { rewardedAsset?: string }) => { 102 const [open, setOpen] = useState(false); 103 const points = useEthenaIncentives(rewardedAsset); 104 105 if (!points) { 106 return null; 107 } 108 109 return ( 110 <ContentWithTooltip 111 tooltipContent={<EthenaAirdropTooltipContent points={points} />} 112 withoutHover 113 setOpen={setOpen} 114 open={open} 115 > 116 <ContentEthenaButton points={points} /> 117 </ContentWithTooltip> 118 ); 119 }; 120 121 export const EtherfiIncentivesButton = (params: { 122 symbol: string; 123 market: string; 124 protocolAction?: ProtocolAction; 125 }) => { 126 const [open, setOpen] = useState(false); 127 const { market, protocolAction, symbol } = params; 128 const multiplier = useEtherfiIncentives(market, symbol, protocolAction); 129 130 if (!multiplier) { 131 return null; 132 } 133 134 return ( 135 <ContentWithTooltip 136 tooltipContent={<EtherFiAirdropTooltipContent multiplier={multiplier} />} 137 withoutHover 138 setOpen={setOpen} 139 open={open} 140 > 141 <ContentEtherfiButton multiplier={multiplier} /> 142 </ContentWithTooltip> 143 ); 144 }; 145 146 export const SonicIncentivesButton = ({ rewardedAsset }: { rewardedAsset?: string }) => { 147 const [open, setOpen] = useState(false); 148 const points = useSonicIncentives(rewardedAsset); 149 150 if (!points) { 151 return null; 152 } 153 154 return ( 155 <ContentWithTooltip 156 tooltipContent={<SonicAirdropTooltipContent points={points} />} 157 withoutHover 158 setOpen={setOpen} 159 open={open} 160 > 161 <ContentSonicButton points={points} /> 162 </ContentWithTooltip> 163 ); 164 }; 165 166 export const IncentivesButton = ({ incentives, symbol, displayBlank }: IncentivesButtonProps) => { 167 const [open, setOpen] = useState(false); 168 169 if (!(incentives && incentives.length > 0)) { 170 if (displayBlank) { 171 return <BlankIncentives />; 172 } else { 173 return null; 174 } 175 } 176 177 const isIncentivesInfinity = incentives.some( 178 (incentive) => incentive.incentiveAPR === 'Infinity' 179 ); 180 const incentivesAPRSum = isIncentivesInfinity 181 ? 'Infinity' 182 : incentives.reduce((aIncentive, bIncentive) => aIncentive + +bIncentive.incentiveAPR, 0); 183 184 const incentivesNetAPR = isIncentivesInfinity 185 ? 'Infinity' 186 : incentivesAPRSum !== 'Infinity' 187 ? valueToBigNumber(incentivesAPRSum || 0).toNumber() 188 : 'Infinity'; 189 190 return ( 191 <ContentWithTooltip 192 tooltipContent={ 193 <IncentivesTooltipContent 194 incentives={incentives} 195 incentivesNetAPR={incentivesNetAPR} 196 symbol={symbol} 197 /> 198 } 199 withoutHover 200 setOpen={setOpen} 201 open={open} 202 > 203 <Content 204 incentives={incentives} 205 incentivesNetAPR={incentivesNetAPR} 206 displayBlank={displayBlank} 207 /> 208 </ContentWithTooltip> 209 ); 210 }; 211 212 const Content = ({ 213 incentives, 214 incentivesNetAPR, 215 displayBlank, 216 plus, 217 }: { 218 incentives: ReserveIncentiveResponse[]; 219 incentivesNetAPR: number | 'Infinity'; 220 displayBlank?: boolean; 221 plus?: boolean; 222 }) => { 223 const [open, setOpen] = useState(false); 224 const trackEvent = useRootStore((store) => store.trackEvent); 225 226 if (!(incentives && incentives.length > 0)) { 227 if (displayBlank) { 228 return <BlankIncentives />; 229 } else { 230 return null; 231 } 232 } 233 234 if (incentivesNetAPR === 0) { 235 if (displayBlank) { 236 return <BlankIncentives />; 237 } else { 238 return null; 239 } 240 } 241 242 const incentivesButtonValue = () => { 243 if (incentivesNetAPR !== 'Infinity' && incentivesNetAPR < 10000) { 244 return ( 245 <FormattedNumber 246 value={incentivesNetAPR} 247 percent 248 variant="secondary12" 249 color="text.secondary" 250 /> 251 ); 252 } else if (incentivesNetAPR !== 'Infinity' && incentivesNetAPR > 9999) { 253 return ( 254 <FormattedNumber 255 value={incentivesNetAPR} 256 percent 257 compact 258 variant="secondary12" 259 color="text.secondary" 260 /> 261 ); 262 } else if (incentivesNetAPR === 'Infinity') { 263 return ( 264 <Typography variant="main12" color="text.secondary"> 265 ∞ 266 </Typography> 267 ); 268 } 269 }; 270 271 const iconSize = 12; 272 273 return ( 274 <Box 275 sx={(theme) => ({ 276 p: { xs: '0 4px', xsm: '2px 4px' }, 277 border: `1px solid ${open ? theme.palette.action.disabled : theme.palette.divider}`, 278 borderRadius: '4px', 279 cursor: 'pointer', 280 display: 'flex', 281 alignItems: 'center', 282 justifyContent: 'center', 283 transition: 'opacity 0.2s ease', 284 bgcolor: open ? 'action.hover' : 'transparent', 285 '&:hover': { 286 bgcolor: 'action.hover', 287 borderColor: 'action.disabled', 288 }, 289 })} 290 onClick={() => { 291 // TODO: How to handle this for event props? 292 trackEvent(DASHBOARD.VIEW_LM_DETAILS_DASHBOARD, {}); 293 setOpen(!open); 294 }} 295 > 296 <Box sx={{ mr: 2 }}> 297 {plus ? '+' : ''} {incentivesButtonValue()} 298 </Box> 299 <Box sx={{ display: 'inline-flex' }}> 300 <> 301 {incentives.length < 5 ? ( 302 <> 303 {incentives.map(getSymbolMap).map((incentive, index) => { 304 return ( 305 <TokenIcon 306 aToken={incentive.aToken} 307 symbol={incentive.tokenIconSymbol} 308 sx={{ fontSize: `${iconSize}px`, ml: -1 }} 309 key={index} 310 /> 311 ); 312 })} 313 </> 314 ) : ( 315 <> 316 {incentives 317 .slice(0, 3) 318 .map(getSymbolMap) 319 .map((incentive, index) => ( 320 <TokenIcon 321 symbol={incentive.tokenIconSymbol} 322 sx={{ fontSize: `${iconSize}px`, ml: -1 }} 323 key={index} 324 /> 325 ))} 326 <SvgIcon 327 sx={{ 328 fontSize: `${iconSize}px`, 329 borderRadius: '50%', 330 bgcolor: 'common.white', 331 color: 'common.black', 332 ml: -1, 333 zIndex: 5, 334 }} 335 > 336 <DotsHorizontalIcon /> 337 </SvgIcon> 338 </> 339 )} 340 </> 341 </Box> 342 </Box> 343 ); 344 }; 345 346 const ContentButton = ({ value, iconSrc }: { value: number; iconSrc: string }) => { 347 const [open, setOpen] = useState(false); 348 const trackEvent = useRootStore((store) => store.trackEvent); 349 350 return ( 351 <Box 352 sx={(theme) => ({ 353 p: { xs: '0 4px', xsm: '2px 4px' }, 354 border: `1px solid ${open ? theme.palette.action.disabled : theme.palette.divider}`, 355 borderRadius: '4px', 356 cursor: 'pointer', 357 display: 'flex', 358 alignItems: 'center', 359 justifyContent: 'center', 360 transition: 'opacity 0.2s ease', 361 bgcolor: open ? 'action.hover' : 'transparent', 362 '&:hover': { 363 bgcolor: 'action.hover', 364 borderColor: 'action.disabled', 365 }, 366 })} 367 onClick={() => { 368 trackEvent(DASHBOARD.VIEW_LM_DETAILS_DASHBOARD, {}); 369 setOpen(!open); 370 }} 371 > 372 <Box sx={{ mr: 2 }}> 373 <Typography component="span" variant="secondary12" color="text.secondary"> 374 {`${value}x`} 375 </Typography> 376 </Box> 377 <Box sx={{ display: 'inline-flex' }}> 378 <img src={iconSrc} width={12} height={12} alt="icon" /> 379 </Box> 380 </Box> 381 ); 382 }; 383 384 const ContentEthenaButton = ({ points }: { points: number }) => ( 385 <ContentButton value={points} iconSrc="/icons/other/ethena.svg" /> 386 ); 387 388 const ContentEtherfiButton = ({ multiplier }: { multiplier: number }) => ( 389 <ContentButton value={multiplier} iconSrc="/icons/other/ether.fi.svg" /> 390 ); 391 392 const ContentSonicButton = ({ points }: { points: number }) => ( 393 <ContentButton value={points} iconSrc="/icons/networks/sonic.svg" /> 394 );