feature: show pools apr
This commit is contained in:
222
app/javascript/src/abi/erc20.json
Normal file
222
app/javascript/src/abi/erc20.json
Normal 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"
|
||||
}
|
||||
]
|
||||
1
app/javascript/src/abi/pancake-router-v2.json
Normal file
1
app/javascript/src/abi/pancake-router-v2.json
Normal file
File diff suppressed because one or more lines are too long
98
app/javascript/src/components/Poo.tsx
Normal file
98
app/javascript/src/components/Poo.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
6
app/javascript/src/constants/index.ts
Normal file
6
app/javascript/src/constants/index.ts
Normal 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
|
||||
);
|
||||
49
app/javascript/src/contexts/BscProvider.tsx
Normal file
49
app/javascript/src/contexts/BscProvider.tsx
Normal 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>;
|
||||
};
|
||||
22
app/javascript/src/utils/apr.ts
Normal file
22
app/javascript/src/utils/apr.ts
Normal 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();
|
||||
};
|
||||
21
app/javascript/src/utils/getPrice.ts
Normal file
21
app/javascript/src/utils/getPrice.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
30
app/javascript/src/utils/getTotalStaked.ts
Normal file
30
app/javascript/src/utils/getTotalStaked.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user