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;
}
},
};
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)