Skip to content

Commit

Permalink
Fix Liquidity Pool average APY
Browse files Browse the repository at this point in the history
  • Loading branch information
sarah-thong committed Mar 14, 2022
1 parent 5ab83ac commit 20d4408
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 62 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@types/react-router-dom": "^5.1.7",
"@types/redux-logger": "^3.0.8",
"bignumber.js": "^9.0.1",
"carbon-js-sdk": "^0.1.47",
"carbon-js-sdk": "^0.1.57",
"chart.js": "^3.2.0",
"eslint": "^7.23.0",
"gsap": "^3.6.1",
Expand Down
136 changes: 136 additions & 0 deletions src/@demex-info/utils/liquidityPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import BigNumber from "bignumber.js";
import { CarbonSDK, Models, WSModels } from "carbon-js-sdk";
import { TokenClient } from "carbon-js-sdk/lib/clients";

// Constant amount of liquidity (to estimate APY)
export const constantLP = new BigNumber(1000);

export interface Pool {
denom: string;
rewardsWeight: BigNumber;
totalCommitment: BigNumber;
sharesAmount: BigNumber;

denomA: string;
amountA: BigNumber;
Expand Down Expand Up @@ -37,6 +41,8 @@ export const parseLiquidityPools = (data: WSPools, tokenClient: TokenClient): Po
const amtB = parseNumber(pool?.amount_b, BN_ZERO)!;
const adjustedAmtA = tokenClient.toHuman(pool?.denom_a ?? "", amtA);
const adjustedAmtB = tokenClient.toHuman(pool?.denom_b ?? "", amtB);
const sharesAmt = parseNumber(pool?.shares_amount, BN_ZERO)!;
const adjustedShares = tokenClient.toHuman(pool?.denom, sharesAmt);
const totalCommitBN = parseNumber(total_commitment, BN_ZERO)!;
const adjustedCommit = tokenClient.toHuman(pool?.denom ?? "", totalCommitBN);
return {
Expand All @@ -45,6 +51,7 @@ export const parseLiquidityPools = (data: WSPools, tokenClient: TokenClient): Po
amountA: adjustedAmtA,
denomB: pool?.denom_b ?? "",
amountB: adjustedAmtB,
sharesAmount: adjustedShares,
rewardsWeight: parseNumber(rewards_weight, BN_ZERO)!,
totalCommitment: adjustedCommit,
};
Expand Down Expand Up @@ -149,3 +156,132 @@ export function getBreakdownToken(

return apy.isFinite() && !apy.isZero() ? apy : BN_ZERO;
}

/**
*
* return weekly rewards for liquidity pool
* @param rewards total weekly pools rewards (in BigNumber form)
* @param rewardsWeight rewards weight for liquidity pool
* @param totalWeight total rewards weight for all pools
*/
export function getWeeklyRewards(
rewards: BigNumber = BN_ZERO,
rewardsWeight: BigNumber = BN_ZERO,
totalWeight: BigNumber = BN_ZERO,
): BigNumber {
const weeklyRewards = !totalWeight.isZero() ? rewards.times(rewardsWeight).div(totalWeight) : BN_ZERO;
return weeklyRewards;
}

interface LPRewardsParams {
sdk: CarbonSDK | undefined;
pool: Pool;
poolsRewards: BigNumber;
totalWeight: BigNumber;
amountLP?: BigNumber;
boostFactor?: BigNumber;
commitPower?: BigNumber;
}
/**
*
* Returns swth lp rewards in usd (amount depends on pool weight and user commitment power)
*
* @param sdk carbon sdk instance (for retrieving usd values, etc.)
* @param pool pool data
* @param poolsRewards total swth LP rewards
* @param totalWeight total rewards weight for all pools
* @param amountLP total LP tokens staked (used only if commitPower is undefined)
* @param boostFactor boost factor of staked tokens (used only if commitPower is undefined)
* @param commitPower user's commitment power (if not provided, take amountLP * boostFactor to get commitPower)
*/
export function getLPRewards(rewardsParams: LPRewardsParams) {
const { sdk, pool, poolsRewards, totalWeight, amountLP = BN_ZERO, boostFactor = new BigNumber(1) } = rewardsParams;
const swthUSD = sdk?.token.getUSDValue("swth") ?? BN_ZERO;
const userCommit = rewardsParams.commitPower
? rewardsParams.commitPower
: amountLP.times(boostFactor);

// swthRewardsPoolUsd = poolsRewards * (rewardsWeight / totalWeight) * swthUsd
const swthRewardsPoolUsd = getWeeklyRewards(poolsRewards, pool.rewardsWeight, totalWeight).times(swthUSD);
const adjustedTotalCommit = pool.totalCommitment.plus(userCommit); // adjust totalCommit based on user's individual power
// swthRewardsUserUsd = swthRewardsPoolUsd * (userCommitPower / adjustedTotalCommit)
return adjustedTotalCommit.isZero() ? BN_ZERO : swthRewardsPoolUsd.times(userCommit.div(adjustedTotalCommit));
}

interface ApyParams extends LPRewardsParams {
notionalLp?: BigNumber
}

/**
*
* return estimated APY (for detailed calculation, please refer to the following resources: https://www.notion.so/switcheo/Calculating-LP-APY-Pseudocode-f20f51dda7164e779c4680479eb628f2)
* @param sdk carbon sdk instance (for retrieving usd values, etc.)
* @param pool pool data
* @param poolsRewards total swth LP rewards
* @param totalWeight total rewards weight for all pools
* @param amountLP total LP tokens staked
* @param boostFactor boost factor of staked tokens
* @param commitPower user's commitment power
* @param notionalLp user's pool liquidity (in usd)
*/
export function estimateAPY(apyParams: ApyParams) {
const {
sdk, pool, poolsRewards, totalWeight, amountLP = BN_ZERO, boostFactor = new BigNumber(1), commitPower = undefined, notionalLp = BN_ZERO,
} = apyParams;
const swthRewards = getLPRewards({
sdk,
pool,
poolsRewards,
totalWeight,
amountLP,
boostFactor,
commitPower,
});
return notionalLp.isZero() ? BN_ZERO : swthRewards.div(notionalLp).times(52).shiftedBy(2);
}

/**
*
* Return estimated APY if user's pool liquidity (in usd) is provided (i.e. no staked tokens info).
* Used for calculating APY given constant value (e.g. $1000 of staked LP tokens)
* @param sdk carbon sdk instance (for retrieving usd values, etc.)
* @param pool pool data
* @param poolsRewards total swth LP rewards
* @param totalWeight total rewards weight for all pools
* @param boostFactor boost factor of staked tokens
* @param notionalLp user's pool liquidity (in usd)
*/
export function estimateApyUSD(apyUSDParams: ApyParams) {
const {
sdk, pool, poolsRewards, totalWeight, boostFactor = new BigNumber(1), notionalLp = BN_ZERO,
} = apyUSDParams;
const poolTotal = getTotalUSDPrice(sdk, pool);
const amountLP = poolTotal.isZero() ? BN_ZERO : notionalLp.div(poolTotal).times(pool.sharesAmount);
return estimateAPY({
sdk, pool, poolsRewards, totalWeight, amountLP, boostFactor, notionalLp,
});
}

/**
*
* return total USD amount for tokenA and tokenB of liquidity pool from pool data
* @param sdk carbon sdk instance (for retrieving token usd value, etc.)
* @param pool liquidity pool data
*/
export function getTotalUSDPrice(
sdk: CarbonSDK | undefined,
pool: Pool,
): BigNumber {
const {
amountA: amountABN = BN_ZERO, amountB: amountBBN = BN_ZERO, denomA, denomB,
} = pool;

const usdValueA = sdk?.token.getUSDValue(denomA) ?? BN_ZERO;
const usdValueB = sdk?.token.getUSDValue(denomB) ?? BN_ZERO;

const amountAUSD = usdValueA.times(amountABN);
const amountBUSD = usdValueB.times(amountBBN);

const totalValueUSD = amountAUSD.plus(amountBUSD);
return totalValueUSD;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useAsyncTask, useWebsocket } from "@demex-info/hooks";
import { RootState } from "@demex-info/store/types";
import { BN_HUNDRED, BN_ZERO, calculateAPY, getBreakdownToken, Pool, parseLiquidityPools } from "@demex-info/utils";
import { BN_HUNDRED, BN_ZERO, constantLP, estimateApyUSD, getBreakdownToken, Pool, parseLiquidityPools, parseNumber } from "@demex-info/utils";
import { Hidden } from "@material-ui/core";
import BigNumber from "bignumber.js";
import { WSConnectorTypes, WSModels, WSResult } from "carbon-js-sdk";
import { Models, WSConnectorTypes, WSModels, WSResult } from "carbon-js-sdk";
import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import LiquidityPoolSection from "./LiquidityPoolSection";
Expand All @@ -22,6 +22,7 @@ const LiquidityPool: React.FC<Props> = (props: Props) => {

const [pools, setPools] = React.useState<Pool[]>([]);
const [weeklyRewards, setWeeklyRewards] = React.useState<BigNumber>(BN_ZERO);
const [commitCurve, setCommitCurve] = React.useState<Models.CommitmentCurve | undefined>(undefined);

const sdk = useSelector((store: RootState) => store.app.sdk);
const tokenClient = sdk?.token;
Expand All @@ -36,6 +37,9 @@ const LiquidityPool: React.FC<Props> = (props: Props) => {

const poolsRewards = await sdk!.lp.getWeeklyRewards();

const curveResponse = await sdk.query.liquiditypool.CommitmentCurve({});
setCommitCurve(curveResponse.commitmentCurve);

setPools(poolsData);
setWeeklyRewards(poolsRewards ?? BN_ZERO);
} catch (err) {
Expand Down Expand Up @@ -86,16 +90,24 @@ const LiquidityPool: React.FC<Props> = (props: Props) => {
const avgApy = React.useMemo((): BigNumber => {
let weightTotal: BigNumber = BN_ZERO;
let cumApy: BigNumber = BN_ZERO;
const maxBoostBN = parseNumber(commitCurve?.maxRewardMultiplier, BN_ZERO)!.dividedBy(100);

pools.forEach((p: Pool) => {
weightTotal = weightTotal.plus(p.rewardsWeight ?? BN_ZERO);
});
pools.forEach((pool: Pool) => {
const indivApy = calculateAPY(sdk, pool, weeklyRewards, weightTotal);
const indivApy = estimateApyUSD({
sdk,
pool,
poolsRewards: weeklyRewards,
totalWeight: weightTotal,
boostFactor: maxBoostBN,
notionalLp: constantLP,
});
cumApy = cumApy.plus(indivApy);
});
return weightTotal.isZero() ? BN_ZERO : cumApy.dividedBy(pools.length);
}, [pools, weeklyRewards, sdk]);
}, [pools, weeklyRewards, sdk, commitCurve]);

return (
<React.Fragment>
Expand Down
78 changes: 21 additions & 57 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1170,16 +1170,15 @@
isomorphic-ws "4.0.1"
ws "7.4.5"

"@cityofzion/neon-api@^5.0.0-next.17":
version "5.0.0-next.17"
resolved "https://registry.yarnpkg.com/@cityofzion/neon-api/-/neon-api-5.0.0-next.17.tgz#81d6765101c90a49564bddd967656ff290979bd4"
integrity sha512-QQiwopbDLmli9cyGvvmdhrpEyss2/9g+zJ4DJ2aP9T0Clep37Mv2Yfv10/XIXwrGPXdB/cXht58GevKa8P1cLQ==
"@cityofzion/neon-api@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@cityofzion/neon-api/-/neon-api-5.0.0.tgz#7f8e68b14923dfb8c1292836c276b178f5184372"
integrity sha512-nvslHi77gv1Vc44rZb2QamQyWhWGhooXDnVCIA6BZUpiArMWZ3TNYaDCXLWQTSvz14UdcYN8WIIt9v/vcD/wqg==

"@cityofzion/neon-core-next@npm:@cityofzion/neon-core@^5.0.0-next.17", "@cityofzion/neon-core@^5.0.0-next.17":
name "@cityofzion/neon-core-next"
version "5.0.0-next.17"
resolved "https://registry.yarnpkg.com/@cityofzion/neon-core/-/neon-core-5.0.0-next.17.tgz#fe666b9432eabc7743be926882499d01a33c4f05"
integrity sha512-+yb9J+gJSvmOEP6raKOLJdg88GsVP6I6ITFAkdxeWe32PzgtMl1NxZc3QrWBOJ8PQ9t5CGN+KW2l+fwgMqfavQ==
"@cityofzion/neon-core-next@npm:@cityofzion/neon-core@^5.0.0", "@cityofzion/neon-core@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@cityofzion/neon-core/-/neon-core-5.0.0.tgz#6b242782404df474285ad7760224871444cc1d35"
integrity sha512-OJPZsgWxXFCSSFo6WiGmxn+/2XP0w0JXT8oYOqYxKfFYJXNh6/ENuglMs7w5jMpFsYb/Hp6Hy8qdXf/Q3Es9jg==
dependencies:
abort-controller "3.0.0"
bignumber.js "7.2.1"
Expand Down Expand Up @@ -1216,13 +1215,13 @@
secure-random "1.1.2"
wif "2.0.6"

"@cityofzion/neon-js-next@npm:@cityofzion/neon-js@^5.0.0-next.17":
version "5.0.0-next.17"
resolved "https://registry.yarnpkg.com/@cityofzion/neon-js/-/neon-js-5.0.0-next.17.tgz#ba09d3404f799bbc381e85f18664c5e6c996923e"
integrity sha512-36Gzh1wrDYvHKoCn7tBenbAOP2ke/rvpkzMwRILxGc3PmY4xJATzGKlpbw9koNFR4fs8uLKZCnyC4aT4XWWDKQ==
"@cityofzion/neon-js-next@npm:@cityofzion/neon-js@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@cityofzion/neon-js/-/neon-js-5.0.0.tgz#018df01d691f0736f5e604f2b87d01ef102bb5cf"
integrity sha512-c1c1wkGEn3vGds+T61Ki9Dm5uycrIUmC9UgtwrztkAhuZXxL86QgaAOCARLnJol+/qsHLSUwUmHo7cIrcMVWJA==
dependencies:
"@cityofzion/neon-api" "^5.0.0-next.17"
"@cityofzion/neon-core" "^5.0.0-next.17"
"@cityofzion/neon-api" "^5.0.0"
"@cityofzion/neon-core" "^5.0.0"

"@cityofzion/neon-js@^4.9.0":
version "4.9.0"
Expand All @@ -1233,16 +1232,6 @@
"@cityofzion/neon-core" "^4.9.0"
"@cityofzion/neon-nep5" "^4.9.0"

"@cityofzion/neon-ledger-next@npm:@cityofzion/neon-ledger@^5.0.0-next.17":
version "5.0.0-next.17"
resolved "https://registry.yarnpkg.com/@cityofzion/neon-ledger/-/neon-ledger-5.0.0-next.17.tgz#0bf7195a9e037a0fd4c651204e3dc8c51f2556cc"
integrity sha512-Yt05Dbz9eN1S20Bhow3CWC3tzAE3nfxSKAy0cKhE3C02mF8I5+tX0ys6WG3ROjG7FzmOrNZ4jDu78l3XwzVkCw==
dependencies:
"@ledgerhq/hw-transport" "5.51.1"
"@types/ledgerhq__hw-transport" "4.21.3"
"@types/lodash-es" "^4.17.4"
lodash-es "4.17.15"

"@cityofzion/neon-nep5@^4.9.0":
version "4.9.0"
resolved "https://registry.yarnpkg.com/@cityofzion/neon-nep5/-/neon-nep5-4.9.0.tgz#aa170bfa83c5ef75270d10951e02f87e7faf4f47"
Expand Down Expand Up @@ -2330,7 +2319,7 @@
"@ledgerhq/hw-transport" "^6.11.2"
"@ledgerhq/logs" "^6.10.0"

"@ledgerhq/hw-transport@5.51.1", "@ledgerhq/hw-transport@^5.25.0":
"@ledgerhq/hw-transport@^5.25.0":
version "5.51.1"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.51.1.tgz#8dd14a8e58cbee4df0c29eaeef983a79f5f22578"
integrity sha512-6wDYdbWrw9VwHIcoDnqWBaDFyviyjZWv6H9vz9Vyhe4Qd7TIFmbTl/eWs6hZvtZBza9K8y7zD8ChHwRI4s9tSw==
Expand Down Expand Up @@ -2910,32 +2899,13 @@
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=

"@types/[email protected]":
version "4.21.3"
resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport/-/ledgerhq__hw-transport-4.21.3.tgz#1e658da6b5d01ffab92f9660cf57121aecfa7e2c"
integrity sha512-6QveiZLsFLq9WZDk8HWAZhivoGzyz5S8WV36hpUe7KrVDaTR1fDdB+syorrNRhYbyjraAuUJrIdJR5p/7doq8g==
dependencies:
"@types/node" "*"

"@types/loadable__component@^5.13.4":
version "5.13.4"
resolved "https://registry.yarnpkg.com/@types/loadable__component/-/loadable__component-5.13.4.tgz#a4646b2406b1283efac1a9d9485824a905b33d4a"
integrity sha512-YhoCCxyuvP2XeZNbHbi8Wb9EMaUJuA2VGHxJffcQYrJKIKSkymJrhbzsf9y4zpTmr5pExAAEh5hbF628PAZ8Dg==
dependencies:
"@types/react" "*"

"@types/lodash-es@^4.17.4":
version "4.17.6"
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.6.tgz#c2ed4c8320ffa6f11b43eb89e9eaeec65966a0a0"
integrity sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==
dependencies:
"@types/lodash" "*"

"@types/lodash@*":
version "4.14.179"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.179.tgz#490ec3288088c91295780237d2497a3aa9dfb5c5"
integrity sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==

"@types/long@^4.0.0", "@types/long@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
Expand Down Expand Up @@ -4778,16 +4748,15 @@ capture-exit@^2.0.0:
dependencies:
rsvp "^4.8.4"

carbon-js-sdk@^0.1.47:
version "0.1.47"
resolved "https://registry.yarnpkg.com/carbon-js-sdk/-/carbon-js-sdk-0.1.47.tgz#a6653dc85e5ae79c1b7919f9e9eb768bf10e8bea"
integrity sha512-2+uC3C4K+nRxn0k4sZ/mESJX1IbFrntj/Obi4fikalasV14XzZ5eYlcvktOU0OtGxhnK8XbbgOwxOy9CPhROtw==
carbon-js-sdk@^0.1.57:
version "0.1.57"
resolved "https://registry.yarnpkg.com/carbon-js-sdk/-/carbon-js-sdk-0.1.57.tgz#661bb1203252400a004304114dc96bcb07c24930"
integrity sha512-RQiKoQY/PwADS6I4mkUlDUTp9KTIHvzhDCL6HtTm91Qh3E9JUyKuSzsSenVmHuX/ISldhrmT9CxWgYagX0KyOw==
dependencies:
"@cityofzion/neon-core" "^4.9.0"
"@cityofzion/neon-core-next" "npm:@cityofzion/neon-core@^5.0.0-next.17"
"@cityofzion/neon-core-next" "npm:@cityofzion/neon-core@^5.0.0"
"@cityofzion/neon-js" "^4.9.0"
"@cityofzion/neon-js-next" "npm:@cityofzion/neon-js@^5.0.0-next.17"
"@cityofzion/neon-ledger-next" "npm:@cityofzion/neon-ledger@^5.0.0-next.17"
"@cityofzion/neon-js-next" "npm:@cityofzion/neon-js@^5.0.0"
"@cosmjs/proto-signing" "^0.26.6"
"@cosmjs/stargate" "^0.26.6"
"@grpc/grpc-js" "^1.3.2"
Expand Down Expand Up @@ -9399,11 +9368,6 @@ [email protected]:
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.14.tgz#12a95a963cc5955683cee3b74e85458954f37ecc"
integrity sha512-7zchRrGa8UZXjD/4ivUWP1867jDkhzTG2c/uj739utSd7O/pFFdxspCemIFKEEjErbcqRzn8nKnGsi7mvTgRPA==

[email protected]:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==

lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz"
Expand Down

0 comments on commit 20d4408

Please sign in to comment.