/ src / components / incentives / IncentivesButton.tsx
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          &nbsp;
 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">
265266          </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  );