Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MYC/esMYC Staking to Earn page #236

Open
wants to merge 17 commits into
base: trs
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Addresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const CONTRACTS = {
FeeDistributorReader: "0x55ce0A81b697f1eff846e8a56D3f303A5BE490b1",
MerkleDistributor: "0xEC503757C71440A7c82B6BB2d689bC4d191bC75d",
MerkleDistributorReader: "0xbB097d322D793ecd03721538d906d0C450d3839C",

MYCStakingRewards: "0x32Ff08751299a13A4a47Aff8841f1cC6eE287b9F", // v2 staking
},
42161: {
// arbitrum mainnet
Expand Down Expand Up @@ -116,7 +118,7 @@ const CONTRACTS = {
MerkleDistributorReader: "0xbB097d322D793ecd03721538d906d0C450d3839C",

LentMYC: "0x9B225FF56C48671d4D04786De068Ed8b88b672d6",
MYCStakingRewards: "0xF9B003Ee160dA9677115Ad3c5bd6BB6dADcB2F93" // v2 staking
MYCStakingRewards: "0xF9B003Ee160dA9677115Ad3c5bd6BB6dADcB2F93", // v2 staking
},
};

Expand Down
171 changes: 150 additions & 21 deletions src/Api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
USD_DECIMALS,
ETH_DECIMALS,
ARBITRUM_GOERLI,
SECONDS_PER_YEAR
SECONDS_PER_YEAR,
} from "../Helpers";
import { getTokenBySymbol } from "../data/Tokens";

Expand Down Expand Up @@ -125,16 +125,22 @@ export function useSpreadCaptureVolume(chainId) {
const [res, setRes] = useState(undefined);

useEffect(() => {
getMycGraphClient(chainId).query({ query }).then((res) => {
const totalMMFees = res.data.volumeStats.reduce((sum, stat) => sum
.add(MM_FEE_MULTIPLIER.mul(stat.mint))
.add(MM_FEE_MULTIPLIER.mul(stat.burn))
.add(MM_FEE_MULTIPLIER.mul(stat.margin))
.add(MM_FEE_MULTIPLIER.mul(stat.liquidation))
.add(MM_SWAPS_FEE_MULTIPLIER.mul(stat.swap))
, bigNumberify(0));
setRes(totalMMFees.div(expandDecimals(1, FEE_MULTIPLIER_BASIS_POINTS)))
}).catch(console.warn);
getMycGraphClient(chainId)
.query({ query })
.then((res) => {
const totalMMFees = res.data.volumeStats.reduce(
(sum, stat) =>
sum
.add(MM_FEE_MULTIPLIER.mul(stat.mint))
.add(MM_FEE_MULTIPLIER.mul(stat.burn))
.add(MM_FEE_MULTIPLIER.mul(stat.margin))
.add(MM_FEE_MULTIPLIER.mul(stat.liquidation))
.add(MM_SWAPS_FEE_MULTIPLIER.mul(stat.swap)),
bigNumberify(0)
);
setRes(totalMMFees.div(expandDecimals(1, FEE_MULTIPLIER_BASIS_POINTS)));
})
.catch(console.warn);
rogue-hacker marked this conversation as resolved.
Show resolved Hide resolved
}, [setRes, query, chainId]);

return res;
Expand Down Expand Up @@ -1065,31 +1071,45 @@ export function useStakingApr(mycPrice, ethPrice) {

// apr is annualised rewards (USD value) / total staked (USD value) * 100
const { data: tokensPerInterval } = useSWR(
[`useStakingApr:tokensPerInterval:${ARBITRUM}`, ARBITRUM, getContract(ARBITRUM, "MYCStakingRewards"), "tokensPerInterval"],
[
`useStakingApr:tokensPerInterval:${ARBITRUM}`,
ARBITRUM,
getContract(ARBITRUM, "MYCStakingRewards"),
"tokensPerInterval",
],
{
fetcher: fetcher(undefined, RewardsTracker),
}
);

const mycTokenAddress = getContract(ARBITRUM, 'MYC');
const mycTokenAddress = getContract(ARBITRUM, "MYC");
const { data: mycDeposited } = useSWR(
[`useStakingApr:totalDepositSupply(MYC):${ARBITRUM}`, ARBITRUM, getContract(ARBITRUM, "MYCStakingRewards"), "totalDepositSupply"],
[
`useStakingApr:totalDepositSupply(MYC):${ARBITRUM}`,
ARBITRUM,
getContract(ARBITRUM, "MYCStakingRewards"),
"totalDepositSupply",
],
{
fetcher: fetcher(undefined, RewardsTracker, mycTokenAddress)
fetcher: fetcher(undefined, RewardsTracker, mycTokenAddress),
}
);

const esMycTokenAddress = getContract(ARBITRUM, 'ES_MYC');
const esMycTokenAddress = getContract(ARBITRUM, "ES_MYC");
const { data: esMycDeposited } = useSWR(
[`useStakingApr:totalDepositSupply(esMYC):${ARBITRUM}`, ARBITRUM, getContract(ARBITRUM, "MYCStakingRewards"), "totalDepositSupply"],
[
`useStakingApr:totalDepositSupply(esMYC):${ARBITRUM}`,
ARBITRUM,
getContract(ARBITRUM, "MYCStakingRewards"),
"totalDepositSupply",
],
{
fetcher: fetcher(undefined, RewardsTracker, esMycTokenAddress)
fetcher: fetcher(undefined, RewardsTracker, esMycTokenAddress),
}
);

useEffect(() => {
if(ethPrice?.gt(0) && mycPrice?.gt(0) && tokensPerInterval && mycDeposited && esMycDeposited) {

if (ethPrice?.gt(0) && mycPrice?.gt(0) && tokensPerInterval && mycDeposited && esMycDeposited) {
const tokensPerYear = tokensPerInterval.mul(SECONDS_PER_YEAR);
const annualRewardsUsd = tokensPerYear.mul(ethPrice);

Expand All @@ -1099,14 +1119,123 @@ export function useStakingApr(mycPrice, ethPrice) {
const aprPrecision = 10;
const apr = annualRewardsUsd.mul(expandDecimals(1, aprPrecision)).div(totalDepositsUsd);

const formattedApr = apr.toNumber() / (10 ** aprPrecision) * 100;
const formattedApr = (apr.toNumber() / 10 ** aprPrecision) * 100;
setStakingApr(formattedApr.toFixed(2));
}
}, [ethPrice, mycPrice, tokensPerInterval, mycDeposited, esMycDeposited]);

return stakingApr;
}

export function useUserStakingBalances(address, chainId) {
// Cannot update MYC and ES_MYC addresses in case of breaking functions on testnet elsewhere
const TESTNET_MYC_CONTRACT = "0x46873E80daf930265B7E5419BBC266cC2880ff8c";
const TESTNET_ES_MYC_CONTRACT = "0x4897Dca24BcB50014456bcBBc59A2D6530FadCeB";
const mycTokenAddress = chainId === ARBITRUM_GOERLI ? TESTNET_MYC_CONTRACT : getContract(chainId, "MYC");
const esMycTokenAddress = chainId === ARBITRUM_GOERLI ? TESTNET_ES_MYC_CONTRACT : getContract(chainId, "ES_MYC");
const stakingAddress = getContract(chainId, "MYCStakingRewards");

const { data: userMycBalance } = useSWR(
address ? [`useStakingBalances:balanceOf(MYC):${chainId}`, chainId, mycTokenAddress, "balanceOf", address] : null,
{
fetcher: fetcher(undefined, Token),
}
);

const { data: userEsMycBalance } = useSWR(
address
? [`useStakingBalances:balanceOf(esMYC):${chainId}`, chainId, esMycTokenAddress, "balanceOf", address]
: null,
{
fetcher: fetcher(undefined, Token),
}
);

const { data: userStakedMycBalance } = useSWR(
address
? [`useStakingBalances:depositBalances(MYC):${chainId}`, chainId, stakingAddress, "depositBalances", address]
: null,
{
fetcher: fetcher(undefined, RewardsTracker, mycTokenAddress),
}
);

const { data: userStakedEsMycBalance } = useSWR(
address
? [`useStakingBalances:depositBalances(esMYC):${chainId}`, chainId, stakingAddress, "depositBalances", address]
: null,
{
fetcher: fetcher(undefined, RewardsTracker, esMycTokenAddress),
}
);

const { data: rewardsEarned } = useSWR(
address ? [`useStakingBalances:claimable:${chainId}`, chainId, stakingAddress, "claimable", address] : null,
{
fetcher: fetcher(undefined, RewardsTracker),
}
);

const { data: cumulativeRewards } = useSWR(
address ? [`useStakingBalances:claimable:${chainId}`, chainId, stakingAddress, "cumulativeRewards", address] : null,
{
fetcher: fetcher(undefined, RewardsTracker),
}
);

return {
userMycBalance,
userEsMycBalance,
userStakedMycBalance,
userStakedEsMycBalance,
rewardsEarned,
cumulativeRewards,
};
}

export function useStakingValues(chainId) {
// const mycTokenAddress = getContract(chainId, "MYC");
// const esMycTokenAddress = getContract(chainId, "ES_MYC");
const stakingAddress = getContract(chainId, "MYCStakingRewards");

const { data: isPaused } = useSWR(
[`useStakingValues:inPrivateStakingMode:${chainId}`, chainId, stakingAddress, "inPrivateStakingMode"],
{
fetcher: fetcher(undefined, RewardsTracker),
}
);

const { data: totalStaked } = useSWR(
[`useStakingValues:totalSupply:${chainId}`, chainId, stakingAddress, "totalSupply"],
{
fetcher: fetcher(undefined, RewardsTracker),
}
);

const { data: depositCap } = useSWR(
[`useStakingValues:depositCap:${chainId}`, chainId, stakingAddress, "depositCap"],
{
fetcher: fetcher(undefined, RewardsTracker),
}
);

// const { data: totalMycDeposited } = useSWR(
// [`useStakingValues:totalDepositSupply(MYC):${chainId}`, chainId, stakingAddress, "totalDepositSupply"],
// {
// fetcher: fetcher(undefined, RewardsTracker, mycTokenAddress),
// }
// );

// const { data: totalEsMycDeposited } = useSWR(
// [`useStakingValues:totalDepositSupply(MYC):${chainId}`, chainId, stakingAddress, "totalDepositSupply"],
// {
// fetcher: fetcher(undefined, RewardsTracker, esMycTokenAddress),
// }
// );

return { isPaused, totalStaked, depositCap };
}

export function useTotalStaked() {
const [totalStakedMyc, setTotalStakedMyc] = useState(null);

Expand Down
7 changes: 7 additions & 0 deletions src/abis/RewardTracker.json

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions src/components/Stake/Sections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { BigNumber } from "ethers/lib/ethers";
import { FC } from "react";
import * as StakeV2Styled from "src/views/Stake/StakeV2Styles";
import { TokenIcon } from "src/components/Stake/TokenIcon";
import { CompatibleToken, TokenSize, TokenSizeEnum } from "src/components/Stake/types";
import { convertStringToFloat, parseBigNumberToString, formatNumberWithCommas } from "src/utils/common";
import { expandDecimals } from "src/Helpers";

interface WalletBalanceProps {
large?: boolean;
walletAmount: BigNumber;
tokenUsdPrice: BigNumber;
selectedToken: CompatibleToken;
tokenSize: TokenSize;
tokenDecimals?: number;
decimals?: number;
}

export const USD_PRICE_PRECISION = 30;
rogue-hacker marked this conversation as resolved.
Show resolved Hide resolved

export const WalletBalance: FC<WalletBalanceProps> = ({
large,
walletAmount,
tokenUsdPrice,
selectedToken,
tokenSize = TokenSizeEnum.lg,
decimals = 2,
}) => (
<>
<StakeV2Styled.FlexColEnd>
<TokenAmountRow
large={large}
tokenAmount={parseBigNumberToString(walletAmount, decimals)}
selectedToken={selectedToken}
tokenSize={tokenSize}
decimals={decimals}
/>
<StakeV2Styled.Subtitle>
$
{formatNumberWithCommas(
parseFloat(
parseBigNumberToString(tokenUsdPrice.mul(walletAmount).div(expandDecimals(1, USD_PRICE_PRECISION)), 2)
rogue-hacker marked this conversation as resolved.
Show resolved Hide resolved
)
)}
</StakeV2Styled.Subtitle>
</StakeV2Styled.FlexColEnd>
</>
);

interface TokenAmountRowProps {
large?: boolean;
tokenAmount: string;
selectedToken: CompatibleToken;
tokenSize: TokenSize;
decimals?: number;
}

export const TokenAmountRow: FC<TokenAmountRowProps> = ({
large,
tokenAmount,
selectedToken,
tokenSize,
decimals = 2,
}) => (
<>
{/* @ts-ignore-next-line */}
<StakeV2Styled.AmountRow large={large}>
<span>{convertStringToFloat(tokenAmount.toString(), decimals)}</span>
<TokenIcon token={selectedToken} size={tokenSize as TokenSize} />
<span>{selectedToken}</span>
</StakeV2Styled.AmountRow>
</>
);
13 changes: 13 additions & 0 deletions src/components/Stake/TokenIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import styled from "styled-components";
import { CompatibleToken, TokenSize, TokenSizeEnum } from "./types";
import { tokenAltText, tokenIcon } from "src/components/Stake/presets";

export const TokenIcon: React.FC<{ token: CompatibleToken; size: TokenSize }> = ({ token, size }) => (
<Token src={tokenIcon[token]} alt={tokenAltText[token]} size={size} />
);

const Token = styled.img<{ size: TokenSize }>`
margin-left: 8px;
margin-right: 4px;
width: ${({ size }) => (size === TokenSizeEnum.sm ? "16px" : "20px")};
`;
21 changes: 21 additions & 0 deletions src/components/Stake/presets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ethers } from 'ethers'
import { CompatibleToken } from 'src/components/Stake/types'
import mycTokenIcon from "../../img/earn/myc.svg"
import esMycTokenIcon from "../../img/earn/esmyc.svg"
import ethTokenIcon from "../../img/earn/eth.svg"

export const tokenIcon: Record<CompatibleToken, any> = {
MYC: mycTokenIcon,
esMYC: esMycTokenIcon,
WETH: ethTokenIcon,
ETH: ethTokenIcon,
}

export const tokenAltText: Record<CompatibleToken, any> = {
MYC: 'Mycelium token logo',
esMYC: 'Escrowed Mycelium token logo',
WETH: 'Wrapped Ether token logo',
ETH: 'Ether token logo',
}

export const ZERO_BN = ethers.BigNumber.from(0)
15 changes: 15 additions & 0 deletions src/components/Stake/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type CompatibleToken = 'MYC' | 'esMYC' | 'WETH' | 'ETH'

export enum CompatibleTokenEnum {
MYC = 'MYC',
esMYC = 'esMYC',
WETH = 'WETH',
ETH = 'ETH',
}

export type TokenSize = "sm" | "lg";

export enum TokenSizeEnum {
sm = "sm",
lg = "lg",
}
12 changes: 12 additions & 0 deletions src/img/earn/esmyc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/img/earn/eth.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/img/earn/myc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading