feature: show pools apr

This commit is contained in:
Claudio Ramos
2021-08-15 02:21:39 -03:00
parent c1129b9953
commit 9e96a3664b
17 changed files with 996 additions and 145 deletions

View File

@@ -0,0 +1,222 @@
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "owner",
"type": "address"
},
{
"indexed": true,
"name": "spender",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,98 @@
import React from "react";
import BigNumber from "bignumber.js";
import type { PoolConfig } from "../types";
import { useBsc } from "../contexts/BscProvider";
import { getPriceInBusd } from "../utils/getPrice";
import { getApr } from "../utils/apr";
import { getTotalStaked } from "../utils/getTotalStaked";
type PoolProps = {
pool: PoolConfig;
};
export const Pool = ({ pool }: PoolProps) => {
const {
provider,
pancake: { router },
} = useBsc();
const [apr, setApr] = React.useState<{
value: string | null;
loading: boolean;
}>({
value: null,
loading: true,
});
React.useEffect(() => {
(async () => {
const stakingPrice = await getPriceInBusd(router, pool.stakingToken);
const earningPrice = await getPriceInBusd(router, pool.earningToken);
const totalStaked = await getTotalStaked(provider, pool);
console.log(
`Total Staked for ${pool.stakingToken.symbol} - ${
pool.earningToken.symbol
}: ${JSON.stringify(totalStaked)}`
);
const aprValue = getApr(
stakingPrice,
earningPrice,
totalStaked,
parseFloat(pool.tokenPerBlock) / 1e-18
);
if (aprValue) {
setApr({
loading: false,
value: aprValue.toFixed(2),
});
}
})();
}, []);
return (
<div
key={pool.sousId}
id={pool.sousId.toString()}
className="flex items-center w-full h-auto bg-white px-16 p-4 rounded-xl shadow flex-col relative z-0 overflow-hidden hover:shadow-lg transition-all duration-300 cursor-pointer"
>
<div
className="box-border h-full w-full absolute left-0 top-0 rounded-xl opacity-20 filter blur-2xl bg-cover"
style={{
backgroundImage: `url('https://pancakeswap.finance/images/tokens/${pool.earningToken.address["56"]}.svg')`,
backgroundPositionX: "50%",
backgroundPositionY: "50%",
backgroundSize: "125%",
zIndex: -1,
}}
/>
<img
className="shadow-xl rounded-full w-24"
src={`https://pancakeswap.finance/images/tokens/${pool.earningToken.address["56"]}.svg`}
alt={`${pool.earningToken.symbol} icon`}
/>
<div className="mt-4 p-2">
<p>
<span className="font-medium">Investir:</span>{" "}
{pool.stakingToken.symbol}
</p>
<p>
<span className="font-medium">Receber:</span>{" "}
{pool.earningToken.symbol}
</p>
<div className="flex items-center">
<span className="font-medium mr-1">Rendimento:</span>
{apr.loading ? (
<div className="w-10 h-5 inline-block animate-pulse bg-gray-300 rounded" />
) : (
`${apr.value}%`
)}
</div>
</div>
</div>
);
};

View File

@@ -1,6 +1,7 @@
import React from "react";
import { pools } from "../constants/Pools";
import { Pool } from "./Poo";
export const PoolListing = () => {
return (
@@ -8,43 +9,7 @@ export const PoolListing = () => {
{pools
.filter((pool) => !pool.isFinished)
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
.map((pool) => (
<div
key={pool.sousId}
id={pool.sousId.toString()}
className="flex items-center w-full h-auto bg-white px-16 p-4 rounded-xl shadow flex-col relative z-0 overflow-hidden hover:shadow-lg transition-all duration-300 cursor-pointer"
>
<div
className="box-border h-full w-full absolute left-0 top-0 rounded-xl opacity-20 filter blur-2xl bg-cover"
style={{
backgroundImage: `url('https://pancakeswap.finance/images/tokens/${pool.earningToken.address["56"]}.svg')`,
backgroundPositionX: "50%",
backgroundPositionY: "50%",
backgroundSize: "125%",
zIndex: -1,
}}
/>
<img
className="shadow-xl rounded-full w-24"
src={`https://pancakeswap.finance/images/tokens/${pool.earningToken.address["56"]}.svg`}
alt={`${pool.earningToken.symbol} icon`}
/>
<div className="mt-4 p-2">
<p>
<span className="font-medium">Investir:</span>{" "}
{pool.stakingToken.symbol}
</p>
<p>
<span className="font-medium">Receber:</span>{" "}
{pool.earningToken.symbol}
</p>
<div className="flex items-center">
<span className="font-medium mr-1">Rendimento:</span>
<div className="w-10 h-5 inline-block animate-pulse bg-gray-300 rounded" />
</div>
</div>
</div>
))}
.map((pool) => <Pool key={pool.sousId} pool={pool} />)}
</div>
);
};

View File

@@ -0,0 +1,6 @@
import BigNumber from "bignumber.js";
export const BSC_BLOCK_TIME = 3;
export const BLOCKS_PER_YEAR = new BigNumber(
(60 / BSC_BLOCK_TIME) * 60 * 24 * 365
);

View File

@@ -0,0 +1,49 @@
import React, { useContext } from "react";
import { ethers } from "ethers";
import pancakeRouterV2 from "../abi/pancake-router-v2.json";
const provider = new ethers.providers.JsonRpcProvider(
"https://bsc-dataseed1.defibit.io/\n"
);
const router = new ethers.Contract(
"0x10ED43C718714eb63d5aA57B78B54704E256024E",
new ethers.utils.Interface(pancakeRouterV2),
provider
);
export type BscContext = {
provider: typeof provider;
pancake: {
router: InstanceType<typeof ethers.Contract>;
};
};
const Context = React.createContext<BscContext>({
provider,
pancake: {
router,
},
});
export const useBsc = () => {
const context = useContext(Context);
if (!context) {
throw new Error("You must wrap the component with <BscProvider />");
}
return context;
};
export const BscProvider = ({ children }: React.PropsWithChildren<any>) => {
const value: BscContext = {
provider,
pancake: {
router,
},
};
return <Context.Provider value={value}>{children}</Context.Provider>;
};

View File

@@ -0,0 +1,22 @@
import BigNumber from "bignumber.js";
import { BLOCKS_PER_YEAR } from "../constants";
export const getApr = (
stakingTokenPrice: number,
rewardTokenPrice: number,
totalStaked: number,
tokenPerBlock: number
) => {
const totalRewardPricePerYear = new BigNumber(rewardTokenPrice)
.times(tokenPerBlock)
.times(BLOCKS_PER_YEAR);
const totalStakingTokenInPool = new BigNumber(stakingTokenPrice).times(
totalStaked
);
const apr = totalRewardPricePerYear.div(totalStakingTokenInPool).times(100);
return apr.isNaN() || !apr.isFinite() ? null : apr.toNumber();
};

View File

@@ -0,0 +1,21 @@
import { ethers } from "ethers";
import { tokens } from "../constants/pancake/Tokens";
import type { Token } from "../constants/pancake/Tokens";
import type { BscContext } from "../contexts/BscProvider";
// 1 Wei = 1*10^18 Ether
const ONE_BUSD_IN_WEI = ethers.utils.parseUnits("1", 18);
export const getPriceInBusd = async (router: any, token: Token) => {
try {
const result = await router.getAmountsOut(ONE_BUSD_IN_WEI, [
token.address["56"],
tokens.busd.address["56"],
]);
return result[1].toString() / 1e18;
} catch {
return 0;
}
};

View File

@@ -0,0 +1,30 @@
import { ethers } from "ethers";
import BigNumber from "bignumber.js";
import erc20 from "../abi/erc20.json";
import { Token } from "../constants/pancake/Tokens";
import type { PoolConfig } from "../types";
export const getTotalStaked = async (
provider: ethers.providers.Provider,
pool: PoolConfig
) => {
if (pool.stakingToken.symbol === "BNB") {
// TODO: BNB
return 0;
}
const contract = new ethers.Contract(
pool.stakingToken.address["56"],
erc20,
provider
);
try {
const result = await contract.balanceOf(pool.contractAddress["56"]);
return new BigNumber(result.toJSON().hex).toNumber();
} catch {
return 0;
}
};