antoncoding/monarch

View on GitHub
app/rewards/components/UniformProgram.tsx

Summary

Maintainability
A
0 mins
Test Coverage
'use client';

import { useMemo } from 'react';
import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@nextui-org/table';
import Image from 'next/image';
import { toast } from 'react-toastify';
import { Address } from 'viem';
import { useAccount, useSwitchChain } from 'wagmi';
import { DistributionResponseType } from '@/hooks/useRewards';
import { useTransactionWithToast } from '@/hooks/useTransactionWithToast';
import { formatReadable, formatBalance } from '@/utils/balance';
import { getNetworkImg } from '@/utils/networks';
import { findToken } from '@/utils/tokens';
import { UniformRewardType } from '@/utils/types';

type UniformProgramProps = {
  account: string;
  uniformRewards: UniformRewardType[];
  distributions: DistributionResponseType[];
};

export default function UniformProgram({
  account,
  uniformRewards,
  distributions,
}: UniformProgramProps) {
  const { chainId } = useAccount();
  const { switchChain } = useSwitchChain();

  const { sendTransaction } = useTransactionWithToast({
    toastId: 'claim-uniform',
    pendingText: 'Claiming Uniform Reward...',
    successText: 'Uniform Reward Claimed!',
    errorText: 'Failed to claim uniform rewards',
    chainId,
    pendingDescription: `Claiming uniform rewards`,
    successDescription: `Successfully claimed uniform rewards`,
  });

  const rewardsData = useMemo(
    () =>
      uniformRewards.map((reward) => {
        const token = findToken(reward.asset.address, reward.asset.chain_id);
        const distribution = distributions.find(
          (d) => d.asset.address.toLowerCase() === reward.asset.address.toLowerCase(),
        );
        return {
          ...reward,
          token,
          distribution,
          claimable: BigInt(reward.amount.claimable_now ?? '0'),
          pending: BigInt(reward.amount.claimable_next ?? '0'),
          total: BigInt(reward.amount.total ?? '0'),
          claimed: BigInt(reward.amount.claimed ?? '0'),
        };
      }),
    [uniformRewards, distributions],
  );

  return (
    <div className="mt-4 gap-8">
      <div className="px-4 py-2 font-zen text-xl">Uniform Program Rewards</div>
      <p className="px-4 pb-8 text-sm text-gray-500">
        The Uniform Program is a new reward system that applies to all users who supply to Morpho,
        regardless of the specific market. It provides a consistent reward rate for each dollar
        supplied across eligible markets, promoting broader participation in the Morpho ecosystem.
        For more details, check the{' '}
        <a
          href="https://forum.morpho.org/t/mip65-new-scalable-rewards-model/617"
          target="_blank"
          rel="noopener noreferrer"
          className="text-blue-500 hover:underline"
        >
          forum post here
        </a>
        .
      </p>

      <div className="mb-6 mt-2 bg-secondary">
        <Table
          aria-label="Uniform Program Rewards Table"
          classNames={{
            th: 'bg-secondary text-center',
            td: 'text-center',
            wrapper: 'rounded-none shadow-none bg-secondary',
          }}
        >
          <TableHeader>
            <TableColumn align="center">Asset</TableColumn>
            <TableColumn align="center">Chain</TableColumn>
            <TableColumn align="center">Claimable</TableColumn>
            <TableColumn align="center">Pending</TableColumn>
            <TableColumn align="center">Claimed</TableColumn>
            <TableColumn align="center">Total</TableColumn>
            <TableColumn align="end">Action</TableColumn>
          </TableHeader>
          <TableBody>
            {rewardsData.map((reward, index) => (
              <TableRow key={index}>
                <TableCell>
                  <div className="flex items-center justify-center gap-2">
                    <p>{reward.token?.symbol}</p>
                    {reward.token?.img && (
                      <Image src={reward.token.img} alt="token icon" width="20" height="20" />
                    )}
                  </div>
                </TableCell>
                <TableCell>
                  <div className="flex items-center justify-center">
                    <Image
                      src={getNetworkImg(reward.asset.chain_id) ?? ''}
                      alt={`Chain ${reward.asset.chain_id}`}
                      width={20}
                      height={20}
                    />
                  </div>
                </TableCell>
                <TableCell>
                  <div className="flex items-center justify-center gap-1">
                    <p>
                      {formatReadable(
                        formatBalance(reward.claimable, reward.token?.decimals ?? 18),
                      )}
                    </p>
                    {reward.token?.img && (
                      <Image src={reward.token.img} alt="token icon" width="16" height="16" />
                    )}
                  </div>
                </TableCell>
                <TableCell>
                  <div className="flex items-center justify-center gap-1">
                    <p>
                      {formatReadable(formatBalance(reward.pending, reward.token?.decimals ?? 18))}
                    </p>
                    {reward.token?.img && (
                      <Image src={reward.token.img} alt="token icon" width="16" height="16" />
                    )}
                  </div>
                </TableCell>
                <TableCell>
                  <div className="flex items-center justify-center gap-1">
                    <p>
                      {formatReadable(formatBalance(reward.claimed, reward.token?.decimals ?? 18))}
                    </p>
                    {reward.token?.img && (
                      <Image src={reward.token.img} alt="token icon" width="16" height="16" />
                    )}
                  </div>
                </TableCell>
                <TableCell>
                  <div className="flex items-center justify-center gap-1">
                    <p>
                      {formatReadable(formatBalance(reward.total, reward.token?.decimals ?? 18))}
                    </p>
                    {reward.token?.img && (
                      <Image src={reward.token.img} alt="token icon" width="16" height="16" />
                    )}
                  </div>
                </TableCell>
                <TableCell>
                  <div className="flex justify-end">
                    <button
                      type="button"
                      className={`bg-hovered items-center justify-between rounded-sm p-2 text-xs duration-300 ease-in-out ${
                        reward.claimable === BigInt(0) || !reward.distribution
                          ? 'cursor-not-allowed opacity-50'
                          : 'hover:scale-110 hover:bg-orange-500'
                      }`}
                      disabled={reward.claimable === BigInt(0) || !reward.distribution}
                      onClick={(e) => {
                        e.stopPropagation();
                        if (!account) {
                          toast.error('Connect wallet');
                          return;
                        }
                        if (!reward.distribution) {
                          toast.error('No claim data');
                          return;
                        }
                        if (chainId !== reward.asset.chain_id) {
                          switchChain({ chainId: reward.asset.chain_id });
                          toast('Click on claim again after switching network');
                          return;
                        }
                        sendTransaction({
                          account: account as Address,
                          to: reward.distribution.distributor.address as Address,
                          data: reward.distribution.tx_data as `0x${string}`,
                          chainId: reward.distribution.distributor.chain_id,
                        });
                      }}
                    >
                      Claim
                    </button>
                  </div>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </div>
    </div>
  );
}