DEV Community

Swatantra goswami
Swatantra goswami

Posted on

Everystake.one Staking

import {ethers, formatUnits, parseUnits} from 'ethers';
import erc20 from '../../abis/erc20.json';

// Contract addresses extracted from @everstake/wallet-sdk-hysp bundle (eth_mainnet)
const HYSP = {
  issuanceVault: '0x5455222CCDd32F85C1998f57DC6CF613B4498C2a',
  redemptionVault: '0x9C3743582e8b2d7cCb5e08caF3c9C33780ac446f',
  oracle: '0x6f51d8aF5bE2cF3517B8d6Cd07361bE382E83be6',
  mToken: '0x548857309BEfb6Fb6F20a9C5A56c9023D892785B',
};

const ZERO_REFERRER = ethers.ZeroHash;
const MEVUSD_DECIMALS = 18;
const SECONDS_IN_DAY = 86400;
const DAYS_IN_YEAR = 365;

const issuanceVaultAbi = [
  'function depositInstant(address tokenIn, uint256 amountToken, uint256 minReceiveAmount, bytes32 referrerId) external',
];

const redemptionVaultAbi = [
  'function redeemInstant(address tokenOut, uint256 amountMTokenIn, uint256 minReceiveAmount) external',
  'function redeemRequest(address tokenOut, uint256 amountMTokenIn) external returns (uint256)',
];

const oracleAbi = [
  'function lastAnswer() external view returns (int256)',
  'function decimals() external view returns (uint8)',
  'function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',
  'function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',
];

const fetchHYSPAPY = async evmProvider => {
  try {
    const oracle = new ethers.Contract(HYSP.oracle, oracleAbi, evmProvider);
    const latestRound = await oracle.latestRoundData();
    const cutoff = Number(latestRound.updatedAt) - 6 * SECONDS_IN_DAY;

    let roundId = latestRound.roundId;
    let oldRound = null;

    for (let i = 0; i < 30 && roundId > 0n; i++) {
      roundId = roundId - 1n;
      const r = await oracle.getRoundData(roundId);
      if (Number(r.updatedAt) <= cutoff) {
        oldRound = r;
        break;
      }
    }

    if (!oldRound) return '0.00';

    const daysElapsed =
      (Number(latestRound.updatedAt) - Number(oldRound.updatedAt)) /
      SECONDS_IN_DAY;
    if (daysElapsed < 1 || Number(oldRound.answer) === 0) return '0.00';

    const growth = Number(latestRound.answer) / Number(oldRound.answer);
    const apy = growth ** (DAYS_IN_YEAR / daysElapsed) - 1;
    return (apy * 100).toFixed(2);
  } catch (e) {
    console.warn('[everstakeProvider] APY fetch error:', e?.message);
    return '0.00';
  }
};

export const everstakeProvider = {
  icon: 'https://everstake.one/apple-touch-icon.png',
  name: 'Everstake',
  apy: '0% APY',
  stakedAmount: '0',
  stakedAmountRaw: null,

  createStaking: async (
    {from, amount, privateKey, contractAddress, decimals, evmProvider},
    provider,
  ) => {
    try {
      const wallet = new ethers.Wallet(privateKey);
      const walletSigner = wallet.connect(evmProvider);
      const amountInWei = parseUnits(amount.toString(), decimals);

      const tokenContract = new ethers.Contract(
        contractAddress,
        erc20,
        walletSigner,
      );
      const vault = new ethers.Contract(
        HYSP.issuanceVault,
        issuanceVaultAbi,
        walletSigner,
      );

      // USDT requires resetting allowance to 0 before setting a new value
      const allowance = await tokenContract.allowance(from, HYSP.issuanceVault);
      if (allowance > 0n) {
        const resetTx = await tokenContract.approve(HYSP.issuanceVault, 0n);
        await resetTx.wait();
      }
      const approveTx = await tokenContract.approve(
        HYSP.issuanceVault,
        amountInWei,
      );
      await approveTx.wait();

      const tx = await vault.depositInstant(
        contractAddress,
        amountInWei,
        0,
        ZERO_REFERRER,
      );
      const receipt = await tx.wait();
      return receipt.hash;
    } catch (error) {
      console.log(error);
      throw error;
    }
  },

  getEstimateFeeForStaking: async (
    {from, amount, privateKey, contractAddress, decimals, evmProvider},
    provider,
  ) => {
    const wallet = new ethers.Wallet(privateKey);
    const walletSigner = wallet.connect(evmProvider);
    const amountInWei = parseUnits(amount.toString(), decimals);

    const tokenContract = new ethers.Contract(
      contractAddress,
      erc20,
      walletSigner,
    );
    const vault = new ethers.Contract(
      HYSP.issuanceVault,
      issuanceVaultAbi,
      walletSigner,
    );

    let estimateGas;
    try {
      estimateGas = await vault.depositInstant.estimateGas(
        contractAddress,
        amountInWei,
        0,
        ZERO_REFERRER,
      );
    } catch {
      const approveGas = await tokenContract.approve.estimateGas(
        HYSP.issuanceVault,
        amountInWei,
      );
      estimateGas = approveGas + 300000n;
    }
    return {estimateGas};
  },

  unStaking: async (
    {from, privateKey, contractAddress, evmProvider},
    provider,
  ) => {
    try {
      const wallet = new ethers.Wallet(privateKey);
      const walletSigner = wallet.connect(evmProvider);

      const mTokenContract = new ethers.Contract(
        HYSP.mToken,
        erc20,
        walletSigner,
      );
      const redemptionVault = new ethers.Contract(
        HYSP.redemptionVault,
        redemptionVaultAbi,
        walletSigner,
      );

      const mTokenBalance = await mTokenContract.balanceOf(from);
      if (!mTokenBalance || mTokenBalance === 0n) {
        throw new Error('No Everstake (mEVUSD) balance to unstake');
      }

      const approveTx = await mTokenContract.approve(
        HYSP.redemptionVault,
        mTokenBalance,
      );
      await approveTx.wait();

      // Try instant redeem first; fallback to queued request if insufficient liquidity
      let receipt;
      try {
        const tx = await redemptionVault.redeemInstant(
          contractAddress,
          mTokenBalance,
          0,
        );
        receipt = await tx.wait();
      } catch {
        const tx = await redemptionVault.redeemRequest(
          contractAddress,
          mTokenBalance,
        );
        receipt = await tx.wait();
      }

      return receipt.hash;
    } catch (error) {
      console.log(error);
      throw error;
    }
  },

  getEstimateFeeForDeactivateStaking: async ({
    privateKey,
    contractAddress,
    evmProvider,
  }) => {
    try {
      debugger;
      const wallet = new ethers.Wallet(privateKey);
      const walletSigner = wallet.connect(evmProvider);

      const mTokenContract = new ethers.Contract(
        HYSP.mToken,
        erc20,
        walletSigner,
      );
      const redemptionVault = new ethers.Contract(
        HYSP.redemptionVault,
        redemptionVaultAbi,
        walletSigner,
      );

      const walletAddress = new ethers.Wallet(privateKey).address;
      const mTokenBalance = await mTokenContract.balanceOf(walletAddress);
      if (!mTokenBalance || mTokenBalance === 0n) {
        return {estimateGas: 300000n};
      }

      let estimateGas;
      try {
        estimateGas = await redemptionVault.redeemInstant.estimateGas(
          contractAddress,
          mTokenBalance,
          0,
        );
      } catch {
        const approveGas = await mTokenContract.approve.estimateGas(
          HYSP.redemptionVault,
          mTokenBalance,
        );
        estimateGas = approveGas + 300000n;
      }
      return {estimateGas};
    } catch (e) {
      console.error(
        '[everstakeProvider getEstimateFeeForDeactivateStaking]',
        e,
      );
      throw e;
    }
  },

  getStakingBalance: async (
    {evmProvider, address, contractAddress},
    provider,
  ) => {
    try {
      const mTokenContract = new ethers.Contract(
        HYSP.mToken,
        erc20,
        evmProvider,
      );
      const mTokenBalance = await mTokenContract.balanceOf(address);
      return {
        stakingBalance: mTokenBalance?.toString() || '0',
        energyBalance: '0',
        bandwidthBalance: '0',
      };
    } catch (error) {
      console.error(
        '[everstakeProvider getStakingBalance] error:',
        error?.message,
      );
      throw error;
    }
  },

  fetchData: async (
    {evmProvider, contractAddress, walletAddress, tokenDecimals},
    provider,
  ) => {
    try {
      const mTokenContract = new ethers.Contract(
        HYSP.mToken,
        erc20,
        evmProvider,
      );
      const oracle = new ethers.Contract(HYSP.oracle, oracleAbi, evmProvider);

      const [apy, mTokenBalance, oracleDecimals] = await Promise.all([
        fetchHYSPAPY(evmProvider),
        walletAddress
          ? mTokenContract.balanceOf(walletAddress)
          : Promise.resolve(null),
        oracle.decimals(),
      ]);

      let stakedAmount = null;
      let stakedAmountRaw = null;

      if (mTokenBalance !== null && mTokenBalance > 0n) {
        try {
          const price = await oracle.lastAnswer();
          const mTokenFloat = parseFloat(
            formatUnits(mTokenBalance, MEVUSD_DECIMALS),
          );
          const priceFloat = parseFloat(formatUnits(price, oracleDecimals));
          const underlyingValue = (mTokenFloat * priceFloat).toFixed(
            tokenDecimals,
          );
          stakedAmount = underlyingValue;
          stakedAmountRaw = parseUnits(
            underlyingValue,
            tokenDecimals,
          ).toString();
        } catch {
          stakedAmount = formatUnits(mTokenBalance, MEVUSD_DECIMALS);
          stakedAmountRaw = mTokenBalance.toString();
        }
      }

      return {apy, stakedAmount, stakedAmountRaw, totalStaked: null};
    } catch (error) {
      console.warn('[everstakeProvider] fetchData error:', error);
      return null;
    }
  },
};

Enter fullscreen mode Exit fullscreen mode

Top comments (0)