diff --git a/solidity/dashboard/package-lock.json b/solidity/dashboard/package-lock.json index c3ddd3bcf6..f96b911c5a 100644 --- a/solidity/dashboard/package-lock.json +++ b/solidity/dashboard/package-lock.json @@ -2458,9 +2458,9 @@ } }, "@keep-network/keep-ecdsa": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@keep-network/keep-ecdsa/-/keep-ecdsa-1.5.0.tgz", - "integrity": "sha512-fFzlyQd1dCm10sdrMDEFcu1gVs7Liycd35AgmeTYLbGGXjwnaZtokkYNT570iZ1mE2pVFPUNYAM52+pPUB7fCg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@keep-network/keep-ecdsa/-/keep-ecdsa-1.6.0.tgz", + "integrity": "sha512-di/o4SGTlBUDbC0XnedDiE2XmvNCRfamsm+9jtO79jLN171bf+c9qr4iq/lxMteW5wZGwd1fziNJiwczXf7YcQ==", "requires": { "@keep-network/keep-core": "1.6.0", "@keep-network/sortition-pools": "1.2.0-pre.3", diff --git a/solidity/dashboard/package.json b/solidity/dashboard/package.json index 5dfc80a0e6..a87eae20fe 100644 --- a/solidity/dashboard/package.json +++ b/solidity/dashboard/package.json @@ -6,7 +6,7 @@ "dependencies": { "@0x/subproviders": "^6.0.8", "@keep-network/keep-core": "1.4.1", - "@keep-network/keep-ecdsa": "1.5.0", + "@keep-network/keep-ecdsa": "1.6.0", "@keep-network/tbtc": "1.1.0", "@ledgerhq/hw-app-eth": "^5.13.0", "@ledgerhq/hw-transport-u2f": "^5.13.0", diff --git a/solidity/dashboard/src/actions/web3.js b/solidity/dashboard/src/actions/web3.js index 17eaf4f3dc..808b370817 100644 --- a/solidity/dashboard/src/actions/web3.js +++ b/solidity/dashboard/src/actions/web3.js @@ -195,6 +195,7 @@ export const addMoreLpTokens = ( amount, address, liquidityPairContractName, + pool, meta ) => { return { @@ -203,6 +204,7 @@ export const addMoreLpTokens = ( contractName: liquidityPairContractName, amount, address, + pool, }, meta, } diff --git a/solidity/dashboard/src/components/Icons.jsx b/solidity/dashboard/src/components/Icons.jsx index 74338dd1b3..56da2c824b 100644 --- a/solidity/dashboard/src/components/Icons.jsx +++ b/solidity/dashboard/src/components/Icons.jsx @@ -321,6 +321,24 @@ const Coinbase = () => ( ) +const Saddle = ({ className }) => { + return ( + Saddle Logo + ) +} + export { Badge, Cross, @@ -371,4 +389,5 @@ export { Time, KeepDashboardLogo, NetworkStatusIndicator, + Saddle, } diff --git a/solidity/dashboard/src/components/LiquidityRewardCard.jsx b/solidity/dashboard/src/components/LiquidityRewardCard.jsx index 6c25491e23..55df2a563b 100644 --- a/solidity/dashboard/src/components/LiquidityRewardCard.jsx +++ b/solidity/dashboard/src/components/LiquidityRewardCard.jsx @@ -11,6 +11,7 @@ import Banner from "./Banner" import { toTokenUnit } from "../utils/token.utils" import { gt } from "../utils/arithmetics.utils" import { formatPercentage } from "../utils/general.utils" +import { LIQUIDITY_REWARD_PAIRS } from "../constants/constants" const LiquidityRewardCard = ({ title, @@ -32,6 +33,7 @@ const LiquidityRewardCard = ({ addLpTokens, withdrawLiquidityRewards, isAPYFetching, + pool, }) => { const formattedApy = useMemo(() => { const bn = new BigNumber(apy).multipliedBy(100) @@ -94,7 +96,9 @@ const LiquidityRewardCard = ({ href={viewPoolLink} className="text-white text-link" > - Uniswap pool + {title === LIQUIDITY_REWARD_PAIRS.TBTC_SADDLE.label + ? "Saddle pool" + : "Uniswap pool"} @@ -114,7 +118,10 @@ const LiquidityRewardCard = ({

{title}

- Uniswap Pool  + {title === LIQUIDITY_REWARD_PAIRS.TBTC_SADDLE.label + ? "Saddle Pool" + : "Uniswap Pool"} +   Uniswap subgraph API -  to fetch the the total pool value and KEEP token in USD. +  to fetch the total pool value and KEEP token in USD. {isAPYFetching ? ( @@ -228,6 +235,7 @@ const LiquidityRewardCard = ({ addLpTokens( wrappedTokenBalance, liquidityPairContractName, + pool, awaitingPromise ) } diff --git a/solidity/dashboard/src/constants/constants.js b/solidity/dashboard/src/constants/constants.js index ea931e8aa7..ddf5b563d3 100644 --- a/solidity/dashboard/src/constants/constants.js +++ b/solidity/dashboard/src/constants/constants.js @@ -14,6 +14,7 @@ export const TBTC_SYSTEM_CONTRACT_NAME = "tbtcSystemContract" export const TOKEN_STAKING_ESCROW_CONTRACT_NAME = "tokenStakingEscrow" export const OLD_TOKEN_STAKING_CONTRACT_NAME = "oldTokenStakingContract" export const STAKING_PORT_BACKER_CONTRACT_NAME = "stakingPortBackerContract" +export const LP_REWARDS_TBTC_SADDLE_CONTRACT_NAME = "LPRewardsTBTCSaddle" export const LP_REWARDS_KEEP_ETH_CONTRACT_NAME = "LPRewardsKEEPETHContract" export const LP_REWARDS_TBTC_ETH_CONTRACT_NAME = "LPRewardsTBTCETHContract" export const LP_REWARDS_KEEP_TBTC_CONTRACT_NAME = "LPRewardsKEEPTBTCContract" @@ -41,12 +42,19 @@ export const SIGNING_GROUP_STATUS = { } export const LIQUIDITY_REWARD_PAIRS = { + TBTC_SADDLE: { + contractName: LP_REWARDS_TBTC_SADDLE_CONTRACT_NAME, + label: "TBTC + SADDLE", + viewPoolLink: "https://saddle.exchange/#/deposit", + pool: "SADDLE", + }, KEEP_ETH: { contractName: LP_REWARDS_KEEP_ETH_CONTRACT_NAME, label: "KEEP + ETH", viewPoolLink: "https://info.uniswap.org/pair/0xe6f19dab7d43317344282f803f8e8d240708174a", address: "0xe6f19dab7d43317344282f803f8e8d240708174a", + pool: "UNISWAP", }, KEEP_TBTC: { contractName: LP_REWARDS_KEEP_TBTC_CONTRACT_NAME, @@ -54,6 +62,7 @@ export const LIQUIDITY_REWARD_PAIRS = { viewPoolLink: "https://info.uniswap.org/pair/0x38c8ffee49f286f25d25bad919ff7552e5daf081", address: "0x38c8ffee49f286f25d25bad919ff7552e5daf081", + pool: "UNISWAP", }, TBTC_ETH: { contractName: LP_REWARDS_TBTC_ETH_CONTRACT_NAME, @@ -61,5 +70,6 @@ export const LIQUIDITY_REWARD_PAIRS = { viewPoolLink: "https://info.uniswap.org/pair/0x854056fd40c1b52037166285b2e54fee774d33f6", address: "0x854056fd40c1b52037166285b2e54fee774d33f6", + pool: "UNISWAP", }, } diff --git a/solidity/dashboard/src/contracts-artifacts/SaddleSwap.json b/solidity/dashboard/src/contracts-artifacts/SaddleSwap.json new file mode 100644 index 0000000000..4cd0bce88a --- /dev/null +++ b/solidity/dashboard/src/contracts-artifacts/SaddleSwap.json @@ -0,0 +1,2 @@ +{ "abi": [{"inputs":[{"internalType":"contract IERC20[]","name":"_pooledTokens","type":"address[]"},{"internalType":"uint8[]","name":"decimals","type":"uint8[]"},{"internalType":"string","name":"lpTokenName","type":"string"},{"internalType":"string","name":"lpTokenSymbol","type":"string"},{"internalType":"uint256","name":"_a","type":"uint256"},{"internalType":"uint256","name":"_fee","type":"uint256"},{"internalType":"uint256","name":"_adminFee","type":"uint256"},{"internalType":"uint256","name":"_withdrawFee","type":"uint256"},{"internalType":"contract IAllowlist","name":"_allowlist","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"fees","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"invariant","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpTokenSupply","type":"uint256"}],"name":"AddLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newAdminFee","type":"uint256"}],"name":"NewAdminFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newSwapFee","type":"uint256"}],"name":"NewSwapFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newWithdrawFee","type":"uint256"}],"name":"NewWithdrawFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldA","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newA","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"initialTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"futureTime","type":"uint256"}],"name":"RampA","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"lpTokenSupply","type":"uint256"}],"name":"RemoveLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokenAmounts","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"fees","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"invariant","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpTokenSupply","type":"uint256"}],"name":"RemoveLiquidityImbalance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"provider","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpTokenAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpTokenSupply","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"boughtId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokensBought","type":"uint256"}],"name":"RemoveLiquidityOne","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"currentA","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"time","type":"uint256"}],"name":"StopRampA","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokensSold","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tokensBought","type":"uint256"},{"indexed":false,"internalType":"uint128","name":"soldId","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"boughtId","type":"uint128"}],"name":"TokenSwap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"minToMint","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"calculateCurrentWithdrawFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"calculateRemoveLiquidity","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint8","name":"tokenIndex","type":"uint8"}],"name":"calculateRemoveLiquidityOneToken","outputs":[{"internalType":"uint256","name":"availableTokenAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"tokenIndexFrom","type":"uint8"},{"internalType":"uint8","name":"tokenIndexTo","type":"uint8"},{"internalType":"uint256","name":"dx","type":"uint256"}],"name":"calculateSwap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bool","name":"deposit","type":"bool"}],"name":"calculateTokenAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableGuard","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getA","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAPrecise","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getAdminBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllowlist","outputs":[{"internalType":"contract IAllowlist","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getDepositTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"index","type":"uint8"}],"name":"getToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"index","type":"uint8"}],"name":"getTokenBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"getTokenIndex","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVirtualPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isGuarded","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"futureA","type":"uint256"},{"internalType":"uint256","name":"futureTime","type":"uint256"}],"name":"rampA","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256[]","name":"minAmounts","type":"uint256[]"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256","name":"maxBurnAmount","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityImbalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint8","name":"tokenIndex","type":"uint8"},{"internalType":"uint256","name":"minAmount","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityOneToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newAdminFee","type":"uint256"}],"name":"setAdminFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newWithdrawFee","type":"uint256"}],"name":"setDefaultWithdrawFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newSwapFee","type":"uint256"}],"name":"setSwapFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stopRampA","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"tokenIndexFrom","type":"uint8"},{"internalType":"uint8","name":"tokenIndexTo","type":"uint8"},{"internalType":"uint256","name":"dx","type":"uint256"},{"internalType":"uint256","name":"minDy","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swapStorage","outputs":[{"internalType":"uint256","name":"initialA","type":"uint256"},{"internalType":"uint256","name":"futureA","type":"uint256"},{"internalType":"uint256","name":"initialATime","type":"uint256"},{"internalType":"uint256","name":"futureATime","type":"uint256"},{"internalType":"uint256","name":"swapFee","type":"uint256"},{"internalType":"uint256","name":"adminFee","type":"uint256"},{"internalType":"uint256","name":"defaultWithdrawFee","type":"uint256"},{"internalType":"contract LPToken","name":"lpToken","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"transferAmount","type":"uint256"}],"name":"updateUserWithdrawFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawAdminFees","outputs":[],"stateMutability":"nonpayable","type":"function"}] +} \ No newline at end of file diff --git a/solidity/dashboard/src/contracts.js b/solidity/dashboard/src/contracts.js index 3a4898c1ad..537b944f98 100644 --- a/solidity/dashboard/src/contracts.js +++ b/solidity/dashboard/src/contracts.js @@ -20,7 +20,9 @@ import ECDSARewardsDistributor from "@keep-network/keep-ecdsa/artifacts/ECDSARew import LPRewardsKEEPETH from "@keep-network/keep-ecdsa/artifacts/LPRewardsKEEPETH.json" import LPRewardsTBTCETH from "@keep-network/keep-ecdsa/artifacts/LPRewardsTBTCETH.json" import LPRewardsKEEPTBTC from "@keep-network/keep-ecdsa/artifacts/LPRewardsKEEPTBTC.json" +import LPRewardsTBTCSaddle from "@keep-network/keep-ecdsa/artifacts/LPRewardsTBTCSaddle.json" import IERC20 from "@keep-network/keep-core/artifacts/IERC20.json" +import SaddleSwap from "./contracts-artifacts/SaddleSwap.json" import Web3 from "web3" import { @@ -41,6 +43,7 @@ import { LP_REWARDS_KEEP_ETH_CONTRACT_NAME, LP_REWARDS_TBTC_ETH_CONTRACT_NAME, LP_REWARDS_KEEP_TBTC_CONTRACT_NAME, + LP_REWARDS_TBTC_SADDLE_CONTRACT_NAME, } from "./constants/constants" export const CONTRACT_DEPLOY_BLOCK_NUMBER = { @@ -118,6 +121,10 @@ const contracts = { artifact: LPRewardsKEEPTBTC, withDeployBlock: true, }, + [LP_REWARDS_TBTC_SADDLE_CONTRACT_NAME]: { + artifact: LPRewardsTBTCSaddle, + withDeployBlock: true, + }, } export async function getKeepTokenContractDeployerAddress(web3) { @@ -317,3 +324,11 @@ export const createLPRewardsContract = async (web3, contractName) => { const { artifact } = contracts[contractName] return await getContract(web3, artifact, {}) } + +export const createSaddleSwapContract = (web3) => { + return createWeb3ContractInstance( + web3, + SaddleSwap.abi, + "0x4f6A43Ad7cba042606dECaCA730d4CE0A57ac62e" + ) +} diff --git a/solidity/dashboard/src/css/card-container.less b/solidity/dashboard/src/css/card-container.less index 66e04bb917..082fedc770 100644 --- a/solidity/dashboard/src/css/card-container.less +++ b/solidity/dashboard/src/css/card-container.less @@ -2,4 +2,10 @@ display: flex; justify-content: center; flex-wrap: wrap; + margin: 0 2.2rem; + + @media screen and (min-width: 1600px) { + justify-content: start; + margin: 0 2.2rem; + } } \ No newline at end of file diff --git a/solidity/dashboard/src/css/liquidity-page.less b/solidity/dashboard/src/css/liquidity-page.less index 5bf261feeb..8fea003dbd 100644 --- a/solidity/dashboard/src/css/liquidity-page.less +++ b/solidity/dashboard/src/css/liquidity-page.less @@ -27,7 +27,7 @@ .liquidity__double-icon-container { margin-right: 0.5rem; - .tbtc-eth& { + .tbtc-eth&, .tbtc-saddle& { .main-icon { circle { fill: @white; diff --git a/solidity/dashboard/src/pages/liquidity/LiquidityPage.jsx b/solidity/dashboard/src/pages/liquidity/LiquidityPage.jsx index 1e89a05bda..61ff8ddbb5 100644 --- a/solidity/dashboard/src/pages/liquidity/LiquidityPage.jsx +++ b/solidity/dashboard/src/pages/liquidity/LiquidityPage.jsx @@ -22,7 +22,7 @@ const LiquidityPage = ({ headerTitle }) => { const { isConnected } = useWeb3Context() const keepTokenBalance = useSelector((state) => state.keepTokenBalance) - const { KEEP_ETH, TBTC_ETH, KEEP_TBTC } = useSelector( + const { TBTC_SADDLE, KEEP_ETH, TBTC_ETH, KEEP_TBTC } = useSelector( (state) => state.liquidityRewards ) const dispatch = useDispatch() @@ -50,6 +50,7 @@ const LiquidityPage = ({ headerTitle }) => { const addLpTokens = ( wrappedTokenBalance, liquidityPairContractName, + pool, awaitingPromise ) => { dispatch( @@ -57,6 +58,7 @@ const LiquidityPage = ({ headerTitle }) => { wrappedTokenBalance, address, liquidityPairContractName, + pool, awaitingPromise ) ) @@ -110,6 +112,26 @@ const LiquidityPage = ({ headerTitle }) => { )} + { addLpTokens={addLpTokens} withdrawLiquidityRewards={withdrawLiquidityRewards} isAPYFetching={KEEP_ETH.isAPYFetching} + pool={LIQUIDITY_REWARD_PAIRS.KEEP_ETH.pool} /> { addLpTokens={addLpTokens} withdrawLiquidityRewards={withdrawLiquidityRewards} isAPYFetching={KEEP_TBTC.isAPYFetching} + pool={LIQUIDITY_REWARD_PAIRS.KEEP_TBTC.pool} /> { addLpTokens={addLpTokens} withdrawLiquidityRewards={withdrawLiquidityRewards} isAPYFetching={TBTC_ETH.isAPYFetching} + pool={LIQUIDITY_REWARD_PAIRS.TBTC_ETH.pool} /> diff --git a/solidity/dashboard/src/reducers/liquidity-rewards.js b/solidity/dashboard/src/reducers/liquidity-rewards.js index 599956e655..4eef6a843c 100644 --- a/solidity/dashboard/src/reducers/liquidity-rewards.js +++ b/solidity/dashboard/src/reducers/liquidity-rewards.js @@ -12,6 +12,7 @@ const liquidityPairInitialData = { } const initialState = { + TBTC_SADDLE: { ...liquidityPairInitialData }, KEEP_ETH: { ...liquidityPairInitialData }, TBTC_ETH: { ...liquidityPairInitialData }, KEEP_TBTC: { ...liquidityPairInitialData }, diff --git a/solidity/dashboard/src/sagas/liquidity-rewards.js b/solidity/dashboard/src/sagas/liquidity-rewards.js index 2db273b291..bedbf0cb9a 100644 --- a/solidity/dashboard/src/sagas/liquidity-rewards.js +++ b/solidity/dashboard/src/sagas/liquidity-rewards.js @@ -1,18 +1,12 @@ import { takeLatest, takeEvery, fork, call, put } from "redux-saga/effects" -import { getContractsContext, submitButtonHelper, logError } from "./utils" +import { submitButtonHelper, logError, getLPRewardsWrapper } from "./utils" import { sendTransaction } from "./web3" -import { - fetchStakedBalance, - fetchWrappedTokenBalance, - fetchLPRewardsTotalSupply, - fetchRewardBalance, - getWrappedTokenConctract, - calculateAPY, -} from "../services/liquidity-rewards" +import { LiquidityRewardsFactory } from "../services/liquidity-rewards" import { gt, percentageOf, eq } from "../utils/arithmetics.utils" import { LIQUIDITY_REWARD_PAIRS } from "../constants/constants" import { getWsUrl } from "../connectors/utils" import { initializeWeb3, createLPRewardsContract } from "../contracts" +/** @typedef { import("../services/liquidity-rewards").LiquidityRewards} LiquidityRewards */ function* fetchAllLiquidtyRewardsData(action) { const { address } = action.payload @@ -23,9 +17,9 @@ function* fetchAllLiquidtyRewardsData(action) { } function* fetchLiquidityRewardsData(liquidityRewardPair, address) { - const contracts = yield getContractsContext() + /** @type LiquidityRewards */ + const LiquidityRewards = yield getLPRewardsWrapper(liquidityRewardPair) - const LPRewardsContract = contracts[liquidityRewardPair.contractName] try { yield put({ type: `liquidity_rewards/${liquidityRewardPair.name}_fetch_data_start`, @@ -34,22 +28,25 @@ function* fetchLiquidityRewardsData(liquidityRewardPair, address) { // Fetching balance of liquidity token for a given uniswap pair deposited in // the `LPRewards` contract. - const lpBalance = yield call(fetchStakedBalance, address, LPRewardsContract) + const lpBalance = yield call( + [LiquidityRewards, LiquidityRewards.stakedBalance], + address + ) // Fetching balance of liquidity token for a given uniswap pair. const wrappedTokenBalance = yield call( - fetchWrappedTokenBalance, - address, - LPRewardsContract + [LiquidityRewards, LiquidityRewards.wrappedTokenBalance], + address ) let apy = Infinity // Fetching total deposited liqidity tokens in the `LPRewards` contract. - const totalSupply = yield call(fetchLPRewardsTotalSupply, LPRewardsContract) + const totalSupply = yield call([ + LiquidityRewards, + LiquidityRewards.totalSupply, + ]) if (gt(totalSupply, 0)) { apy = yield call( - calculateAPY, - totalSupply, - liquidityRewardPair.name, - LPRewardsContract + [LiquidityRewards, LiquidityRewards.calculateAPY], + totalSupply ) } @@ -57,7 +54,10 @@ function* fetchLiquidityRewardsData(liquidityRewardPair, address) { let shareOfPoolInPercent = 0 if (gt(lpBalance, 0)) { // Fetching available reward balance from `LPRewards` contract. - reward = yield call(fetchRewardBalance, address, LPRewardsContract) + reward = yield call( + [LiquidityRewards, LiquidityRewards.rewardBalance], + address + ) // % of total pool in the `LPRewards` contract. shareOfPoolInPercent = percentageOf(lpBalance, totalSupply).toString() } @@ -90,35 +90,30 @@ export function* watchFetchLiquidityRewardsData() { } function* stakeTokens(action) { - const { contractName, address, amount } = action.payload + const { contractName, address, amount, pool } = action.payload - const contracts = yield getContractsContext() - const LPRewardsContract = contracts[contractName] - const lpRewardsContractAddress = LPRewardsContract.options.address - - const WrappedTokenContract = yield call( - getWrappedTokenConctract, - LPRewardsContract - ) + /** @type LiquidityRewards */ + const LiquidityRewards = yield getLPRewardsWrapper({ contractName, pool }) const approvedAmount = yield call( - WrappedTokenContract.methods.allowance(address, lpRewardsContractAddress) - .call + [LiquidityRewards, LiquidityRewards.wrappedTokenAllowance], + address, + LiquidityRewards.LPRewardsContractAddress ) if (!eq(amount, approvedAmount)) { yield call(sendTransaction, { payload: { - contract: WrappedTokenContract, + contract: LiquidityRewards.wrappedToken, methodName: "approve", - args: [lpRewardsContractAddress, amount], + args: [LiquidityRewards.LPRewardsContractAddress, amount], }, }) } yield call(sendTransaction, { payload: { - contract: LPRewardsContract, + contract: LiquidityRewards.LPRewardsContract, methodName: "stake", args: [amount], }, @@ -145,7 +140,6 @@ function* fetchLiquidityRewardsAPY(liquidityRewardPair) { type: `liquidity_rewards/${liquidityRewardPair.name}_fetch_apy_start`, payload: { liquidityRewardPairName: liquidityRewardPair.name }, }) - const web3 = initializeWeb3(getWsUrl()) const LPRewardsContract = yield call( createLPRewardsContract, @@ -153,14 +147,23 @@ function* fetchLiquidityRewardsAPY(liquidityRewardPair) { liquidityRewardPair.contractName ) + /** @type LiquidityRewards */ + const LiquidityRewards = yield call( + [LiquidityRewardsFactory, LiquidityRewardsFactory.initialize], + liquidityRewardPair.pool, + LPRewardsContract, + web3 + ) + let apy = Infinity - const totalSupply = yield call(fetchLPRewardsTotalSupply, LPRewardsContract) + const totalSupply = yield call([ + LiquidityRewards, + LiquidityRewards.totalSupply, + ]) if (gt(totalSupply, 0)) { apy = yield call( - calculateAPY, - totalSupply, - liquidityRewardPair.name, - LPRewardsContract + [LiquidityRewards, LiquidityRewards.calculateAPY], + totalSupply ) } diff --git a/solidity/dashboard/src/sagas/subscriptions.js b/solidity/dashboard/src/sagas/subscriptions.js index 17bf590e6a..4975093ad9 100644 --- a/solidity/dashboard/src/sagas/subscriptions.js +++ b/solidity/dashboard/src/sagas/subscriptions.js @@ -1,18 +1,17 @@ import { fork, take, call, put, select } from "redux-saga/effects" import moment from "moment" import { createSubcribeToContractEventChannel } from "./web3" -import { getContractsContext, getWeb3Context } from "./utils" +import { + getContractsContext, + getWeb3Context, + getLPRewardsWrapper, +} from "./utils" import { createManagedGrantContractInstance } from "../contracts" import { add, sub } from "../utils/arithmetics.utils" import { isSameEthAddress } from "../utils/general.utils" import { getEventsFromTransaction, ZERO_ADDRESS } from "../utils/ethereum.utils" import { LIQUIDITY_REWARD_PAIRS } from "../constants/constants" -import { - fetchRewardBalance, - fetchLPRewardsTotalSupply, - calculateAPY, - getWrappedTokenConctract, -} from "../services/liquidity-rewards" +/** @typedef { import("../services/liquidity-rewards").LiquidityRewards} LiquidityRewards */ export function* subscribeToKeepTokenTransferEvent() { yield take("keep-token/balance_request_success") @@ -606,13 +605,13 @@ export function* subsribeToECDSARewardsClaimedEvent() { } function* observeLiquidityTokenStakedEvent(liquidityRewardPair) { - const contracts = yield getContractsContext() - const LPRewardsContract = contracts[liquidityRewardPair.contractName] + /** @type LiquidityRewards */ + const LiquidityRewards = yield getLPRewardsWrapper(liquidityRewardPair) // Create subscription channel. const contractEventCahnnel = yield call( createSubcribeToContractEventChannel, - LPRewardsContract, + LiquidityRewards.LPRewardsContract, "Staked" ) @@ -622,8 +621,8 @@ function* observeLiquidityTokenStakedEvent(liquidityRewardPair) { yield* lpTokensStakedOrWithdrawn( eventData.returnValues, + LiquidityRewards, liquidityRewardPair.name, - LPRewardsContract, `liquidity_rewards/${liquidityRewardPair.name}_staked` ) } catch (error) { @@ -634,13 +633,13 @@ function* observeLiquidityTokenStakedEvent(liquidityRewardPair) { } function* observeLiquidityTokenWithdrawnEvent(liquidityRewardPair) { - const contracts = yield getContractsContext() - const LPRewardsContract = contracts[liquidityRewardPair.contractName] + /** @type LiquidityRewards */ + const LiquidityRewards = yield getLPRewardsWrapper(liquidityRewardPair) // Create subscription channel. const contractEventCahnnel = yield call( createSubcribeToContractEventChannel, - LPRewardsContract, + LiquidityRewards.LPRewardsContract, "Withdrawn" ) @@ -649,8 +648,8 @@ function* observeLiquidityTokenWithdrawnEvent(liquidityRewardPair) { const eventData = yield take(contractEventCahnnel) yield* lpTokensStakedOrWithdrawn( eventData.returnValues, + LiquidityRewards, liquidityRewardPair.name, - LPRewardsContract, `liquidity_rewards/${liquidityRewardPair.name}_withdrawn` ) } catch (error) { @@ -662,8 +661,9 @@ function* observeLiquidityTokenWithdrawnEvent(liquidityRewardPair) { function* lpTokensStakedOrWithdrawn( eventValues, + /** @type LiquidityRewards */ + LiquidityRewards, liquidityRewardPairName, - LPRewardsContract, actionType ) { const { @@ -671,19 +671,19 @@ function* lpTokensStakedOrWithdrawn( } = yield getWeb3Context() const { user, amount } = eventValues - const totalSupply = yield call(fetchLPRewardsTotalSupply, LPRewardsContract) + const totalSupply = yield call([ + LiquidityRewards, + LiquidityRewards.totalSupply, + ]) const apy = yield call( - calculateAPY, - totalSupply, - liquidityRewardPairName, - LPRewardsContract + [LiquidityRewards, LiquidityRewards.calculateAPY], + totalSupply ) const reward = yield call( - fetchRewardBalance, - defaultAccount, - LPRewardsContract + [LiquidityRewards, LiquidityRewards.rewardBalance], + defaultAccount ) // If the `Withdrawn` or `Staked` event was emitted the total pool of the LPRewards, @@ -702,16 +702,16 @@ function* lpTokensStakedOrWithdrawn( } function* observeLiquidityRewardPaidEvent(liquidityRewardPair) { - const contracts = yield getContractsContext() + /** @type LiquidityRewards */ + const LiquidityRewards = yield getLPRewardsWrapper(liquidityRewardPair) const { eth: { defaultAccount }, } = yield getWeb3Context() - const LPRewardsContract = contracts[liquidityRewardPair.contractName] // Create subscription channel. const contractEventCahnnel = yield call( createSubcribeToContractEventChannel, - LPRewardsContract, + LiquidityRewards.LPRewardsContract, "RewardPaid" ) @@ -738,16 +738,12 @@ function* observeLiquidityRewardPaidEvent(liquidityRewardPair) { } function* observeWrappedTokenMintAndBurnTx(liquidityRewardPair) { - const contracts = yield getContractsContext() - const LPRewardsContract = contracts[liquidityRewardPair.contractName] - const WrappedTokenContract = yield call( - getWrappedTokenConctract, - LPRewardsContract - ) + /** @type LiquidityRewards */ + const LiquidityRewards = yield getLPRewardsWrapper(liquidityRewardPair) const contractEventCahnnel = yield call( createSubcribeToContractEventChannel, - WrappedTokenContract, + LiquidityRewards.wrappedToken, "Transfer" ) @@ -762,7 +758,7 @@ function* observeWrappedTokenMintAndBurnTx(liquidityRewardPair) { // these casese we need to update APY value because the tootal pool value // of the wrapped token has been increased / decresed. if (from === ZERO_ADDRESS || to === ZERO_ADDRESS) { - yield* updateAPY(LPRewardsContract, liquidityRewardPair.name) + yield* updateAPY(LiquidityRewards, liquidityRewardPair.name) } } catch (error) { console.error(`Failed subscribing to Transfer event`, error) @@ -771,10 +767,20 @@ function* observeWrappedTokenMintAndBurnTx(liquidityRewardPair) { } } -function* updateAPY(LPRewardsContract, liquidityRewardPairName) { - const totalSupply = yield call(fetchLPRewardsTotalSupply, LPRewardsContract) +function* updateAPY( + /** @type LiquidityRewards */ + LiquidityRewards, + liquidityRewardPairName +) { + const totalSupply = yield call([ + LiquidityRewards, + LiquidityRewards.totalSupply, + ]) - const apy = yield call(calculateAPY, totalSupply, liquidityRewardPairName) + const apy = yield call( + [LiquidityRewards, LiquidityRewards.calculateAPY], + totalSupply + ) yield put({ type: `liquidity_rewards/${liquidityRewardPairName}_apy_updated`, payload: { diff --git a/solidity/dashboard/src/sagas/utils.js b/solidity/dashboard/src/sagas/utils.js index 4dc7bfed15..795b817d3e 100644 --- a/solidity/dashboard/src/sagas/utils.js +++ b/solidity/dashboard/src/sagas/utils.js @@ -1,5 +1,7 @@ import { call, put } from "redux-saga/effects" import { Web3Loaded, ContractsLoaded } from "../contracts" +import { LiquidityRewardsFactory } from "../services/liquidity-rewards" +/** @typedef { import("../services/liquidity-rewards").LiquidityRewards} LiquidityRewards */ export function* getWeb3Context() { return yield Web3Loaded @@ -37,3 +39,26 @@ export function* logError(errorActionType, error, payload = {}) { error, }) } + +/** + * + * @param {Object} liquidityRewardPair - Liquidity reward data. + * @param {string} liquidityRewardPair.pool - The type of pool. + * @param {string} liquidityRewardPair.contractName - The LPRewards contract + * name for a given liquidity pair. + * @return {LiquidityRewards} Liquidity rewards wrapper. + */ +export function* getLPRewardsWrapper(liquidityRewardPair) { + const contracts = yield getContractsContext() + const web3 = yield getWeb3Context() + + const LPRewardsContract = contracts[liquidityRewardPair.contractName] + const LiquidityRewards = yield call( + [LiquidityRewardsFactory, LiquidityRewardsFactory.initialize], + liquidityRewardPair.pool, + LPRewardsContract, + web3 + ) + + return LiquidityRewards +} diff --git a/solidity/dashboard/src/services/liquidity-rewards.js b/solidity/dashboard/src/services/liquidity-rewards.js index c5263d788b..517c1bbde7 100644 --- a/solidity/dashboard/src/services/liquidity-rewards.js +++ b/solidity/dashboard/src/services/liquidity-rewards.js @@ -1,91 +1,226 @@ import web3Utils from "web3-utils" -import { Web3Loaded, createERC20Contract } from "../contracts" +import { createERC20Contract, createSaddleSwapContract } from "../contracts" import BigNumber from "bignumber.js" -import { LIQUIDITY_REWARD_PAIRS } from "../constants/constants" import { toTokenUnit } from "../utils/token.utils" -import { getPairData, getKeepTokenPriceInUSD } from "./uniswap-api" +import { + getPairData, + getKeepTokenPriceInUSD, + getBTCPriceInUSD, +} from "./uniswap-api" import moment from "moment" +import { add } from "../utils/arithmetics.utils" +/** @typedef {import("web3").default} Web3 */ +/** @typedef {LiquidityRewards} LiquidityRewards */ // lp contract address -> wrapped ERC20 token as web3 contract instance const LPRewardsToWrappedTokenCache = {} const WEEKS_IN_YEAR = 52 -export const fetchWrappedTokenBalance = async (address, LPrewardsContract) => { - const ERC20Contract = await getWrappedTokenConctract(LPrewardsContract) +class LiquidityRewards { + constructor(_wrappedTokenContract, _LPRewardsContract, _web3) { + this.wrappedToken = _wrappedTokenContract + this.LPRewardsContract = _LPRewardsContract + this.web3 = _web3 + } - return await ERC20Contract.methods.balanceOf(address).call() -} + get wrappedTokenAddress() { + return this.wrappedToken.options.address + } -export const getWrappedTokenConctract = async (LPRewardsContract) => { - const web3 = await Web3Loaded - const lpRewardsContractAddress = web3Utils.toChecksumAddress( - LPRewardsContract.options.address - ) + get LPRewardsContractAddress() { + return this.LPRewardsContract.options.address + } - if (!LPRewardsToWrappedTokenCache.hasOwnProperty(lpRewardsContractAddress)) { - const wrappedTokenAddress = await LPRewardsContract.methods - .wrappedToken() - .call() - LPRewardsToWrappedTokenCache[ - lpRewardsContractAddress - ] = createERC20Contract(web3, wrappedTokenAddress) + wrappedTokenBalance = async (address) => { + return await this.wrappedToken.methods.balanceOf(address).call() } - return LPRewardsToWrappedTokenCache[lpRewardsContractAddress] -} + wrappedTokenTotalSupply = async () => { + return await this.wrappedToken.methods.totalSupply().call() + } -export const fetchStakedBalance = async (address, LPrewardsContract) => { - return await LPrewardsContract.methods.balanceOf(address).call() -} + wrappedTokenAllowance = async (owner, spender) => { + return await this.wrappedToken.methods.allowance(owner, spender).call() + } -export const fetchTotalLPTokensCreatedInUniswap = async (LPrewardsContract) => { - const ERC20Contract = await getWrappedTokenConctract(LPrewardsContract) - return await ERC20Contract.methods.totalSupply().call() -} + stakedBalance = async (address) => { + return await this.LPRewardsContract.methods.balanceOf(address).call() + } -export const fetchLPRewardsTotalSupply = async (LPrewardsContract) => { - return await LPrewardsContract.methods.totalSupply().call() -} -export const fetchRewardBalance = async (address, LPrewardsContract) => { - return await LPrewardsContract.methods.earned(address).call() + totalSupply = async () => { + return await this.LPRewardsContract.methods.totalSupply().call() + } + + rewardBalance = async (address) => { + return await this.LPRewardsContract.methods.earned(address).call() + } + + rewardRate = async () => { + return await this.LPRewardsContract.methods.rewardRate().call() + } + + rewardPoolPerWeek = async () => { + const rewardRate = await this.rewardRate() + return toTokenUnit(rewardRate).multipliedBy( + moment.duration(7, "days").asSeconds() + ) + } + + _calculateR = ( + keepTokenInUSD, + rewardPoolPerInterval, + totalLPTokensInLPRewardsInUSD + ) => { + return keepTokenInUSD + .multipliedBy(rewardPoolPerInterval) + .div(totalLPTokensInLPRewardsInUSD) + } + + /** + * Calculates the APY. + * + * @param {BigNumber} r Period rate. + * @param {number | string | BigNumber} n Number of compounding periods. + * @return {BigNumber} APY value. + */ + _calculateAPY = (r, n = WEEKS_IN_YEAR) => { + return r.plus(1).pow(n).minus(1) + } + + calculateAPY = async (totalSupplyOfLPRewards) => { + throw new Error("First, implement the `calculateAPY` function") + } } -export const fetchRewardRate = async (LPRewardsContract) => { - return await LPRewardsContract.methods.rewardRate().call() +class UniswapLPRewards extends LiquidityRewards { + calculateAPY = async (totalSupplyOfLPRewards) => { + totalSupplyOfLPRewards = toTokenUnit(totalSupplyOfLPRewards) + + const pairData = await getPairData(this.wrappedTokenAddress.toLowerCase()) + const rewardPoolPerWeek = await this.rewardPoolPerWeek() + + const lpRewardsPoolInUSD = totalSupplyOfLPRewards + .multipliedBy(pairData.reserveUSD) + .div(pairData.totalSupply) + + const ethPrice = new BigNumber(pairData.reserveUSD).div(pairData.reserveETH) + + let keepTokenInUSD = 0 + if (pairData.token0.symbol === "KEEP") { + keepTokenInUSD = ethPrice.multipliedBy(pairData.token0.derivedETH) + } else if (pairData.token1.symbol === "KEEP") { + keepTokenInUSD = ethPrice.multipliedBy(pairData.token1.derivedETH) + } else { + keepTokenInUSD = await getKeepTokenPriceInUSD() + } + + const r = this._calculateR( + keepTokenInUSD, + rewardPoolPerWeek, + lpRewardsPoolInUSD + ) + + return this._calculateAPY(r, WEEKS_IN_YEAR) + } } -export const calculateAPY = async ( - totalSupplyOfLPRewards, - pairSymbol, - LPRewardsContract -) => { - totalSupplyOfLPRewards = toTokenUnit(totalSupplyOfLPRewards) +class SaddleLPRewards extends LiquidityRewards { + BTC_POOL_TOKENS = [ + { name: "TBTC", decimals: 18 }, + { name: "WBTC", decimals: 8 }, + { name: "RENBTC", decimals: 8 }, + { name: "SBTC", decimals: 18 }, + ] + + constructor(_wrappedTokenContract, _LPRewardsContract, _web3) { + super(_wrappedTokenContract, _LPRewardsContract, _web3) + this.swapContract = createSaddleSwapContract(this.web3) + } + + swapContract = null - const pairData = await getPairData(LIQUIDITY_REWARD_PAIRS[pairSymbol].address) - const rewardRate = await fetchRewardRate(LPRewardsContract) + calculateAPY = async (totalSupplyOfLPRewards) => { + totalSupplyOfLPRewards = toTokenUnit(totalSupplyOfLPRewards) - const rewardPoolPerWeek = toTokenUnit(rewardRate).multipliedBy( - moment.duration(7, "days").asSeconds() - ) + const wrappedTokenTotalSupply = toTokenUnit( + await this.wrappedTokenTotalSupply() + ) - const totalLPTokensInLPRewardsInUSD = totalSupplyOfLPRewards - .multipliedBy(pairData.reserveUSD) - .div(pairData.totalSupply) + const BTCInPool = await this._getBTCInPool() + const BTCPriceInUSD = await getBTCPriceInUSD() - const ethPrice = new BigNumber(pairData.reserveUSD).div(pairData.reserveETH) + const wrappedTokenPoolInUSD = BTCPriceInUSD.multipliedBy( + toTokenUnit(BTCInPool) + ) - let keepTokenInUSD = 0 - if (pairData.token0.symbol === "KEEP") { - keepTokenInUSD = ethPrice.multipliedBy(pairData.token0.derivedETH) - } else if (pairData.token1.symbol === "KEEP") { - keepTokenInUSD = ethPrice.multipliedBy(pairData.token1.derivedETH) - } else { - keepTokenInUSD = await getKeepTokenPriceInUSD() + const keepTokenInUSD = await getKeepTokenPriceInUSD() + + const rewardPoolPerWeek = 125000 // await this.rewardPoolPerWeek() + + const lpRewardsPoolInUSD = totalSupplyOfLPRewards + .multipliedBy(wrappedTokenPoolInUSD) + .div(wrappedTokenTotalSupply) + + const r = this._calculateR( + keepTokenInUSD, + rewardPoolPerWeek, + lpRewardsPoolInUSD + ) + + return this._calculateAPY(r, WEEKS_IN_YEAR) } - const r = keepTokenInUSD - .multipliedBy(rewardPoolPerWeek) - .div(totalLPTokensInLPRewardsInUSD) + _getBTCInPool = async () => { + return ( + await Promise.all( + this.BTC_POOL_TOKENS.map(async (token, i) => { + const balance = await this._getTokenBalance(i) + return new BigNumber(10) + .pow(18 - token.decimals) // cast all to 18 decimals + .multipliedBy(balance) + }) + ) + ).reduce(add, 0) + } - return r.plus(1).pow(WEEKS_IN_YEAR).minus(1) + _getTokenBalance = async (index) => { + return await this.swapContract.methods.getTokenBalance(index).call() + } +} + +const LiquidityRewardsPoolStrategy = { + UNISWAP: UniswapLPRewards, + SADDLE: SaddleLPRewards, +} + +export class LiquidityRewardsFactory { + /** + * + * @param {('UNISWAP' | 'SADDLE')} pool - The supported type of pools. + * @param {Object} LPRewardsContract - The LPRewardsContract as web3 contract instance. + * @param {Web3} web3 - web3 + * @return {LiquidityRewards} - The Liquidity Rewards Wrapper + */ + static async initialize(pool, LPRewardsContract, web3) { + const lpRewardsContractAddress = web3Utils.toChecksumAddress( + LPRewardsContract.options.address + ) + + if ( + !LPRewardsToWrappedTokenCache.hasOwnProperty(lpRewardsContractAddress) + ) { + const wrappedTokenAddress = await LPRewardsContract.methods + .wrappedToken() + .call() + LPRewardsToWrappedTokenCache[ + lpRewardsContractAddress + ] = createERC20Contract(web3, wrappedTokenAddress) + } + + const wrappedTokenContract = + LPRewardsToWrappedTokenCache[lpRewardsContractAddress] + const PoolStrategy = LiquidityRewardsPoolStrategy[pool] + + return new PoolStrategy(wrappedTokenContract, LPRewardsContract, web3) + } } diff --git a/solidity/dashboard/src/services/uniswap-api.js b/solidity/dashboard/src/services/uniswap-api.js index 1b17415bc6..0ac675e751 100644 --- a/solidity/dashboard/src/services/uniswap-api.js +++ b/solidity/dashboard/src/services/uniswap-api.js @@ -39,14 +39,27 @@ export const getPairData = async (pairId) => { return response.data.data.pair } +const getTokenPriceInUSD = async (address) => { + const pairData = await getPairData(address) + const ethPrice = new BigNumber(pairData.reserveUSD).div(pairData.reserveETH) + + return ethPrice.multipliedBy(pairData.token0.derivedETH) +} + /** * Returns the current KEEP token price in USD based on the Uniswap pool. * * @return {BigNumber} KEEP token price in USD. */ export const getKeepTokenPriceInUSD = async () => { - const pairData = await getPairData(LIQUIDITY_REWARD_PAIRS.KEEP_ETH.address) - const ethPrice = new BigNumber(pairData.reserveUSD).div(pairData.reserveETH) + return await getTokenPriceInUSD(LIQUIDITY_REWARD_PAIRS.KEEP_ETH.address) +} - return ethPrice.multipliedBy(pairData.token0.derivedETH) +/** + * Returns the current BTC price in USD based on the TBTC/ETH Uniswap pool. + * + * @return {BigNumber} BTC price in USD. + */ +export const getBTCPriceInUSD = async () => { + return await getTokenPriceInUSD(LIQUIDITY_REWARD_PAIRS.TBTC_ETH.address) } diff --git a/solidity/dashboard/src/static/svg/Saddle_logomark_blue.png b/solidity/dashboard/src/static/svg/Saddle_logomark_blue.png new file mode 100644 index 0000000000..320d61f282 Binary files /dev/null and b/solidity/dashboard/src/static/svg/Saddle_logomark_blue.png differ diff --git a/solidity/dashboard/src/static/svg/Saddle_logomark_transparent_white.png b/solidity/dashboard/src/static/svg/Saddle_logomark_transparent_white.png new file mode 100644 index 0000000000..6aecff3e45 Binary files /dev/null and b/solidity/dashboard/src/static/svg/Saddle_logomark_transparent_white.png differ diff --git a/solidity/dashboard/src/static/svg/Saddle_logomark_white.png b/solidity/dashboard/src/static/svg/Saddle_logomark_white.png new file mode 100644 index 0000000000..cb6705c763 Binary files /dev/null and b/solidity/dashboard/src/static/svg/Saddle_logomark_white.png differ