Skip to content

Commit

Permalink
chore: start with usd estimation
Browse files Browse the repository at this point in the history
  • Loading branch information
icfor committed Jan 19, 2024
1 parent e5f9f29 commit 96ce80b
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 34 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@socialgouv/matomo-next": "^1.8.0",
"@tryghost/admin-api": "^1.13.11",
"@tryghost/content-api": "^1.11.20",
"bignumber.js": "^9.1.2",
"dotenv": "^16.3.1",
"dotenv-defaults": "^5.0.2",
"framer-motion": "^10.18.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { Coin } from "@cosmjs/stargate";
import { LinearProgress } from "@mui/material";
import BigNumber from "bignumber.js";
import useTranslation from "next-translate/useTranslation";
import type {
Dispatch,
MouseEventHandler,
ReactNode,
SetStateAction,
} from "react";
import { useContext, useEffect, useMemo, useState } from "react";
import { useContext, useEffect, useMemo, useRef, useState } from "react";

import CtaButton from "@src/components/cta-button";
import EmptyButton from "@src/components/empty-button";
Expand All @@ -18,22 +20,26 @@ import {
StakingContext,
getAccountsForNetwork,
getClaimableRewardsForNetwork,
getCoinPrice,
getNetworkStakingInfo,
getStakedDataForNetwork,
setSelectedAccount,
useStakingRef,
} from "@src/screens/staking/lib/staking_sdk/context";
import {
WalletId,
coinsDenoms,
networkKeyToNetworkId,
networksWithStaking,
} from "@src/screens/staking/lib/staking_sdk/core";
import type {
Account,
CoinDenom,
NetworkInfo,
} from "@src/screens/staking/lib/staking_sdk/core";
import { formatCoin } from "@src/screens/staking/lib/staking_sdk/formatters";
import { accountHasDelegations } from "@src/screens/staking/lib/staking_sdk/utils/accounts";
import { resolveCoin } from "@src/screens/staking/lib/staking_sdk/utils/coins";
import { convertToMoney } from "@src/utils/convert_to_money";
import type { Network, NetworkKey } from "@src/utils/network_info";

Expand All @@ -59,6 +65,8 @@ const PopOver = ({
}: PopOverProps) => {
const networkNetworkId = networkKeyToNetworkId[network.key as NetworkKey];
const stakingNetworkId = networkKeyToNetworkId[network.key as NetworkKey];
const [coinPrice, setCoinPrice] = useState<null | string>(null);
const requestingCoinPrice = useRef("");

const stakingRef = useStakingRef();

Expand Down Expand Up @@ -93,8 +101,8 @@ const PopOver = ({

const result = {
accounts: null as Account[] | null,
claimableRewards: "",
stakedData: "",
claimableRewards: null as Coin | null,
stakedData: null as Coin | null,
};

if (!!stakingNetworkId && !!wallet) {
Expand All @@ -104,30 +112,77 @@ const PopOver = ({
return result;
}

const stakedDataObj = getStakedDataForNetwork(
result.stakedData = getStakedDataForNetwork(
stakingRef.current.state,
stakingNetworkId,
);

if (stakedDataObj) {
result.stakedData = formatCoin(stakedDataObj);
}

const claimableRewardsObj = getClaimableRewardsForNetwork(
result.claimableRewards = getClaimableRewardsForNetwork(
stakingRef.current.state,
stakingNetworkId,
);

if (claimableRewardsObj) {
result.claimableRewards = formatCoin(claimableRewardsObj);
}
}

return result;
}, [stakingState, stakingNetworkId, stakingRef]);

// @TODO: move some logic to the sdk
useEffect(() => {
if (!claimableRewards?.denom) return;

const resolvedCoin = resolveCoin({
amount: "0",
denom: claimableRewards.denom,
});

const parsedDenom = resolvedCoin.denom?.toLowerCase() as CoinDenom;

if (
parsedDenom &&
coinsDenoms.has(parsedDenom) &&
requestingCoinPrice.current !== parsedDenom
) {
requestingCoinPrice.current = parsedDenom;

(async () => {
const coinPriceRespose = await getCoinPrice(
stakingRef.current.state,
stakingRef.current.setState,
parsedDenom,
);

setCoinPrice(coinPriceRespose ?? null);

requestingCoinPrice.current = "";
})();
}
}, [claimableRewards?.denom, stakingRef]);

const accountsWithDelegations = accounts?.filter(accountHasDelegations);

// @TODO: Move to sdk
const displayedRewards = (() => {
if (!claimableRewards) return null;

if (!coinPrice) return formatCoin(claimableRewards);

const coinNum = new BigNumber(claimableRewards.amount);
const coinValue = coinNum.multipliedBy(new BigNumber(coinPrice));

return `${coinValue.toFormat(5)} USD`;
})();

const displayedStaked = (() => {
if (!stakedData) return null;

if (!coinPrice) return [formatCoin(stakedData)];

const coinNum = new BigNumber(stakedData.amount);
const coinValue = coinNum.multipliedBy(new BigNumber(coinPrice));

return [formatCoin(stakedData), `≈ ${coinValue.toFormat(5)} USD`];
})();

return (
<div
className={styles.popover}
Expand All @@ -144,16 +199,20 @@ const PopOver = ({
{network.name && <div className={styles.name}>{network.name}</div>}
{(!!stakedData || !!claimableRewards) && (
<div className={styles.stakingData}>
{stakedData && (
{displayedStaked && (
<div className={styles.total}>
<div>{t("totalStaked")}</div>
<div>{stakedData}</div>
<div>
{displayedStaked.map((item, itemIdx) => (
<div key={itemIdx}>{item}</div>
))}
</div>
</div>
)}
{!!claimableRewards && (
<div className={styles.rewards}>
<div>{t("claimableRewards")}</div>
<div>{claimableRewards}</div>
<div>{displayedRewards}</div>
</div>
)}
</div>
Expand Down
24 changes: 24 additions & 0 deletions src/screens/staking/lib/staking_sdk/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import {
} from "./core";
import type {
Account,
CoinDenom,
NetworkInfo,
SetState,
StakingNetworkId,
State,
TStakingContext,
Wallet,
} from "./core";
import { geckoClient } from "./gecko_client";
import { stakingClient } from "./staking_client";
import { filterUniqueAddresses, sortAccounts } from "./utils/accounts";
import { getEmptyCoin, sumCoins } from "./utils/coins";
Expand Down Expand Up @@ -123,6 +125,28 @@ export const getNetworkStakingInfo = async (
return newInfo as NetworkInfo;
};

export const getCoinPrice = async (
state: State,
setState: SetState,
denom: CoinDenom,
) => {
if (state.coinsPrices[denom]) {
return state.coinsPrices[denom];
}

const price = await geckoClient.getCoinPrice(denom);

setState((prevState) => ({
...prevState,
coinsPrices: {
...prevState.coinsPrices,
[denom]: price,
},
}));

return price;
};

export const setUserWallet = (
state: State,
setState: SetState,
Expand Down
11 changes: 11 additions & 0 deletions src/screens/staking/lib/staking_sdk/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,17 @@ export type NetworkInfo = {
unbonding_period: number;
};

export enum CoinDenom {
AKT = "akt",
ATOM = "atom",
DYDX = "dydx",
TIA = "tia",
}

export const coinsDenoms = new Set([...Object.values(CoinDenom)]);

export type State = {
coinsPrices: { [key in CoinDenom]?: string };
hasInit: boolean;
networksInfo: { [key in StakingNetworkId]?: NetworkInfo };
selectedAccount: null | SelectedAccount;
Expand All @@ -111,6 +121,7 @@ export type TStakingContext = {
};

export const defaultState: State = {
coinsPrices: {},
hasInit: false,
networksInfo: {},
selectedAccount: null,
Expand Down
39 changes: 39 additions & 0 deletions src/screens/staking/lib/staking_sdk/gecko_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import BigNumber from "bignumber.js";

import { IS_E2E } from "@src/utils/e2e";

import { CoinDenom } from "./core";
import { adydxExp, uaktExp, uatomExp, utiaExp } from "./utils/coins";

const denomToEndpoint: Record<CoinDenom, string> = {
[CoinDenom.AKT]: "akash-network",
[CoinDenom.ATOM]: "cosmos",
[CoinDenom.DYDX]: "dydx",
[CoinDenom.TIA]: "celestia",
} as const;

const denomToMultiplier: Record<CoinDenom, number> = {
[CoinDenom.AKT]: uaktExp,
[CoinDenom.ATOM]: uatomExp,
[CoinDenom.DYDX]: adydxExp,
[CoinDenom.TIA]: utiaExp,
} as const;

export const geckoClient = {
getCoinPrice: async (denom: CoinDenom): Promise<string> =>
IS_E2E
? Promise.resolve("0.1")
: fetch(
`https://api.coingecko.com/api/v3/coins/${denomToEndpoint[denom]}`,
)
.then((res) => res.json())
.then((data) => {
const basePrice = new BigNumber(
data?.market_data?.current_price?.usd,
);

const multiplier = new BigNumber(10).pow(denomToMultiplier[denom]);

return basePrice.dividedBy(multiplier).toString();
}),
};
19 changes: 18 additions & 1 deletion src/screens/staking/lib/staking_sdk/staking_client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Coin } from "@cosmjs/stargate";
import BigNumber from "bignumber.js";

import type { StakingNetworkId } from "@src/screens/staking/lib/staking_sdk/core";

Expand All @@ -25,6 +26,20 @@ const fetchJson = <A = any>(uri: string, opts?: Options): Promise<A> =>
},
}).then((res) => res.json());

const rewardsDivisor = new BigNumber(10).pow(18);

const parseStakingRewards = async (res: GetRewardsResponse) =>
Array.isArray(res)
? res.map((coin) => {
const num = new BigNumber(coin.amount);

return {
amount: num.dividedBy(rewardsDivisor).toString(),
denom: coin.denom,
};
})
: res;

type StakeResponse = {
tx: {
authInfo: {
Expand Down Expand Up @@ -76,7 +91,9 @@ export const stakingClient = {
fetchJson<GetAddressInfoResponse>(`/api/address/${network}/${address}`),

getRewardsInfo: async (network: StakingNetworkId, address: string) =>
fetchJson<GetRewardsResponse>(`/api/rewards/${network}/${address}`),
fetchJson<GetRewardsResponse>(`/api/rewards/${network}/${address}`).then(
parseStakingRewards,
),
getStakingInfo: async (network: StakingNetworkId) =>
fetchJson<GetStakingInfoResponse>(`/api/staking_info/${network}`),
stake: async (network: StakingNetworkId, address: string, amount: string) =>
Expand Down
26 changes: 12 additions & 14 deletions src/screens/staking/lib/staking_sdk/utils/coins.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Coin } from "@cosmjs/stargate";
import BigNumber from "bignumber.js";

import { StakingNetworkId } from "../core";

Expand All @@ -13,45 +14,42 @@ const networkToResolvedDenom = {

type DenomToResolve = (typeof networkToResolvedDenom)[StakingNetworkId];

const uatomExp = 6;
const uaktExp = 6;
const utiaExp = 6;
const adydxExp = 18;
export const uatomExp = 6;
export const uaktExp = 6;
export const utiaExp = 6;
export const adydxExp = 18;

export const resolveCoin = (coin: Coin): Coin => {
const num = Number(coin.amount);

if (Number.isNaN(num)) {
return coin;
}

const compared = coin.denom?.toLowerCase() as DenomToResolve;

const parseNum = (exp: number) =>
new BigNumber(coin.amount).dividedBy(new BigNumber(10 ** exp)).toString();

switch (compared) {
case "uatom": {
return {
amount: (num / 10 ** uatomExp).toString(),
amount: parseNum(uatomExp),
denom: "ATOM",
};
}

case "utia": {
return {
amount: (num / 10 ** utiaExp).toString(),
amount: parseNum(utiaExp),
denom: "TIA",
};
}

case "adydx": {
return {
amount: (num / 10 ** adydxExp).toString(),
amount: parseNum(adydxExp),
denom: "DYDX",
};
}

case "uakt": {
return {
amount: (num / 10 ** uaktExp).toString(),
amount: parseNum(uaktExp),
denom: "AKT",
};
}
Expand Down
Loading

0 comments on commit 96ce80b

Please sign in to comment.