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 (
+
+ )
+}
+
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