Skip to content

Instantly share code, notes, and snippets.

@KashifCh-eth
Forked from BlockmanCodes/app.css
Last active April 20, 2023 10:29
Show Gist options
  • Save KashifCh-eth/b208d554d594dab9060ae8c52b40c408 to your computer and use it in GitHub Desktop.
Save KashifCh-eth/b208d554d594dab9060ae8c52b40c408 to your computer and use it in GitHub Desktop.
.App {
background-color: #2B2D3C;
text-align: center;
min-height: 100vh;
padding-top: 25px;
padding-bottom: 25px
}
/*////////////////////////////////////////////////////////////////////////////*/
/* Nav Bar */
.connectButton {
float: right;
margin-right: 24px;
background-color: #1168f1;
border-radius: 4px;
padding: 5px 15px;
color: #f1f1f3;
font-size: 24px;
cursor: pointer;
}
.connectButton:hover {
background-color: #f90;
}
/*////////////////////////////////////////////////////////////////////////////*/
/* Market */
.marketContainer {
background-color: #fff;
width: 620px;
margin: 0 auto;
border-radius: 24px;
padding: 8px;
}
.subContainer {
margin-bottom: 20px;
}
.marketOption {
display: flex;
margin-left: 22px;
}
.optionData {
margin-left: 10px;
color: #fff;
font-weight: 700;
}
.optionPercent {
display: block;
font-size: 24px;
font-weight: 1000;
color: #f90;
}
.logoImg {
background-color: #fff;
border-radius: 100px;
margin-bottom: 14px;
margin-right: 14px;
width: 28px;
height: 28px;
}
.marketHeader {
color: #1168f1;
font-size: 32px;
font-weight: 800;
}
.stakedTokensHeader {
color: #fff;
}
.hoverButton:hover {
background: #f90;
cursor: pointer;
}
.hoverButton:active {
/* box-shadow: 0 0 0 white; */
box-shadow: inset;
}
/*////////////////////////////////////////////////////////////////////////////*/
/* Assets */
.assetContainer {
background-color: #1168f1;
padding: 8px 0 10px 28px;
width: 620px;
min-height: 225px;
margin: 0 auto;
top: -25px;
border-radius: 24px;
margin-top: 50px; /* space between sections */
color: #fff;
font-weight: 500;
position: relative;
}
.columnHeaders {
font-weight: 800;
color: #f90;
}
/*////////////////////////////////////////////////////////////////////////////*/
/* Assets */
.stakedLogoImg {
background-color: #fff;
border-radius: 100px;
margin-bottom: 14px;
margin-right: 14px;
width: 18px;
height: 18px;
}
/*////////////////////////////////////////////////////////////////////////////*/
/* Glyphs */
.glyphContainer {
background-color: #fff;
display: inline-block;
width: 60px;
height: 60px;
border-radius: 12px;
}
.glyph {
font-size: 34px;
}
/******************************************************************************/
/* Stake Modal */
.modal-class {
background-color: rgba(0,0,0,0.5);
position: fixed;
left: 0;
top: -400px;
right: 0px;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
color: rgb(86, 90, 105);
}
.modal-content {
background-color: #fff;
width: 300px !important;
padding: 10px 25px;
background-color: rgb(237, 238, 242) !important;
border: 1px solid rgb(206, 208, 217) !important;
border-radius: 12px !important;
}
.fieldContainer {
padding-left: 0px !important;
}
.inputField {
padding-left: 10px;
border-radius: 36px;
border: 1px solid #65cdee;
height: 2rem;
margin: 10px 0;
width: 100%;
}
.inputFieldUnitsContainer {
padding-left: 0px !important;
padding-top: 14px;
}
.pinkButton {
width: 100%;
height: 45px;
line-height: 45px;
font-size: 24px;
color: #fff;
border-radius: 20px;
background-color: #dd2f81;
cursor: pointer
}
.orangeMiniButton {
width: 100%;
height: 25px;
line-height: 25px;
font-size: 12px;
color: #000;
border-radius: 20px;
background-color: #fff;
cursor: pointer
}
.orangeMiniButton:hover {
color: #fff;
background-color: #f90;;
}
import { useEffect, useState } from 'react';
import { ethers } from 'ethers';
import artifact from './artifacts/contracts/Staking.sol/Staking.json'
import linkArtifact from './artifacts/contracts/Chainlink.sol/Chainlink.json'
import usdtArtifact from './artifacts/contracts/Tether.sol/Tether.json'
import usdcArtifact from './artifacts/contracts/UsdCoin.sol/UsdCoin.json'
import wbtcArtifact from './artifacts/contracts/WrappedBitcoin.sol/WrappedBitcoin.json'
import wethArtifact from './artifacts/contracts/WrappedEther.sol/WrappedEther.json'
import './App.css';
import StakeModal from './components/StakeModal'
const CONTRACT_ADDRESS = '0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1'
const LINK_ADDRESS = '0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44'
const USDT_ADDRESS = '0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f'
const USDC_ADDRESS = '0x4A679253410272dd5232B3Ff7cF5dbB88f295319'
const WBTC_ADDRESS = '0x7a2088a1bFc9d81c55368AE168C2C02570cB814F'
const WETH_ADDRESS = '0x09635F643e140090A9A8Dcd712eD6285858ceBef'
function App() {
const [provider, setProvider] = useState(undefined);
const [signer, setSigner] = useState(undefined);
const [contract, setContract] = useState(undefined);
const toEther = wei => Number(ethers.utils.formatEther(String(wei))).toFixed(2);
const [tokenSymbols, setTokenSymbols] = useState([])
const [tokens, setTokens] = useState({})
const [stakedTokens, setStakedTokens] = useState({})
const [assetIds, setAssetIds] = useState([]);
const [assets, setAssets] = useState([]);
const [showStakeModal, setShowStakeModal] = useState(false)
const [stakeTokenSymbol, setStakeTokenSymbol] = useState(undefined)
const [stakeTokenQuantity, setStakeTokenQuantity] = useState(undefined)
const [tokenContracts, setTokenContracts] = useState({})
useEffect(() => {
const onLoad = async () => {
const provider = await new ethers.providers.Web3Provider(window.ethereum)
setProvider(provider)
const contract = await new ethers.Contract(CONTRACT_ADDRESS, artifact.abi, provider)
setContract(contract)
const linkContract = await new ethers.Contract(LINK_ADDRESS, linkArtifact.abi, provider)
const usdtContract = await new ethers.Contract(USDT_ADDRESS, usdtArtifact.abi, provider)
const usdcContract = await new ethers.Contract(USDC_ADDRESS, usdcArtifact.abi, provider)
const wbtcContract = await new ethers.Contract(WBTC_ADDRESS, wbtcArtifact.abi, provider)
const wethContract = await new ethers.Contract(WETH_ADDRESS, wethArtifact.abi, provider)
setTokenContracts(prev => ({...prev, ['LINK']:linkContract}))
setTokenContracts(prev => ({...prev, ['USDT']:usdtContract}))
setTokenContracts(prev => ({...prev, ['USDC']:usdcContract}))
setTokenContracts(prev => ({...prev, ['WBTC']:wbtcContract}))
setTokenContracts(prev => ({...prev, ['WETH']:wethContract}))
const tokenSymbols = await contract.getTokenSymbols()
setTokenSymbols(tokenSymbols)
tokenSymbols.map(async symbol => {
const token = await contract.getToken(symbol)
setTokens(prev => ({...prev, [symbol]:token}))
const stakedAmount = await contract.stakedTokens(symbol)
setStakedTokens(prev => ({...prev, [symbol]:toEther(stakedAmount)}))
})
}
onLoad();
}, [])
const isConnected = () => signer !== undefined
const getSigner = async () => {
const signer = provider.getSigner();
setSigner(signer)
return signer
}
const connectAndLoad = async () => {
const signer = await getSigner(provider)
setSigner(signer)
const assetIdsHex = await contract.connect(signer).getPositionIdsForAddress()
const assetIds = assetIdsHex.map(id => Number(id))
setAssetIds(assetIds)
const queriedAssets = await Promise.all(
assetIds.map(id => contract.connect(signer).getPositionById(Number(id)) )
)
queriedAssets.map(async asset => {
const tokensStaked = toEther(asset.tokenQuantity)
const ethAccruedInterestWei = await calcAccruedInterest(asset.apy, asset.ethValue, asset.createdDate)
const ethAccruedInterest = toEther(ethAccruedInterestWei)
const usdAccruedInterest = ((ethAccruedInterest * tokens['WETH'].usdPrice) / 100).toFixed(2)
const parsedAsset = {
positionId: Number(asset.positionId),
tokenName: asset.name,
tokenSymbol: asset.symbol,
createdDate: asset.createdDate,
apy: asset.apy / 100,
tokensStaked: tokensStaked,
usdValue: toEther(asset.usdValue) / 100,
usdAccruedInterest: usdAccruedInterest,
ethAccruedInterest: ethAccruedInterest,
open: asset.open,
}
setAssets(prev => [...prev, parsedAsset])
})
}
const calcAccruedInterest = async (apy, value, createdDate) => {
const numberOfDays = await contract.calculateNumberDays(createdDate)
const accruedInterest = await contract.calculateInterest(apy, value, numberOfDays)
return Number(accruedInterest)
}
const openStakingModal = (tokenSymbol) => {
setShowStakeModal(true)
setStakeTokenSymbol(tokenSymbol)
}
const stakeTokens = async () => {
const stakeTokenQuantityWei = ethers.utils.parseEther(stakeTokenQuantity);
await tokenContracts[stakeTokenSymbol].connect(signer).approve(contract.address, stakeTokenQuantityWei);
contract.connect(signer).stakeTokens(stakeTokenSymbol, stakeTokenQuantityWei);
}
const withdraw = (positionId) => {
contract.connect(signer).closePosition(positionId)
}
const tokenRow = (tokenSymbol) => {
const token = tokens[tokenSymbol]
const amountStaked = Number(stakedTokens[tokenSymbol])
return (
<div className="row">
<div className="col-md-2">
{displayLogo(token?.symbol)}
</div>
<div className="col-md-2">
{token?.symbol}
</div>
<div className="col-md-2">
{(Number(token?.usdPrice) / 100).toFixed(0)}
</div>
<div className="col-md-2">
{amountStaked}
</div>
<div className="col-md-2">
{(Number(token?.apy) / 100).toFixed(0)}%
</div>
<div className="col-md-2">
{isConnected() && (
<div
className="orangeMiniButton"
onClick={() => openStakingModal(tokenSymbol, '12%')}>
Stake
</div>
)}
</div>
</div>
)
}
const displayLogo = symbol => {
if (symbol === 'LINK') {
return (<><img className="logoImg" src="link.webp"/></>)
} else if (symbol === 'USDT') {
return (<><img className="logoImg" src="usdt.webp"/></>)
} else if (symbol === 'USDC') {
return (<><img className="logoImg" src="usdc.webp"/></>)
} else if (symbol === 'WBTC') {
return (<><img className="logoImg" src="wbtc.webp"/></>)
} else if (symbol === 'WETH') {
return (<><img className="logoImg" src="weth.webp"/></>)
}
}
return (
<div className="App">
<div className="marketContainer">
<div className="subContainer">
<span>
<img className="logoImg" src="eth-logo.webp"/>
</span>
<span className="marketHeader">Ethereum Market</span>
</div>
<div>
<div className="row columnHeaders">
<div className="col-md-2">Asset</div>
<div className="col-md-2">Symbol</div>
<div className="col-md-2">Price (USD)</div>
<div className="col-md-2">Total Supplied</div>
<div className="col-md-2">APY</div>
<div className="col-md-2"></div>
</div>
</div>
<div>
{tokenSymbols.length > 0 && Object.keys(tokens).length > 0 && tokenSymbols.map((a,idx) => (
<div>
{tokenRow(a)}
</div>
))}
</div>
</div>
<div className="assetContainer">
{isConnected() ? (
<>
<div className="subContainer">
<span className="marketHeader stakedTokensHeader">Staked Assets</span>
</div>
<div>
<div>
<div className="row columnHeaders">
<div className="col-md-1">Asset</div>
<div className="col-md-2">Tokens Staked</div>
<div className="col-md-2">Market Value (USD)</div>
<div className="col-md-2">Accrued Interest (USD)</div>
<div className="col-md-2">Accrued Interest (ETH)</div>
<div className="col-md-2"></div>
</div>
</div>
<br />
{assets.length > 0 && assets.map((a,idx) => (
<div className="row">
<div className="col-md-1">
{displayLogo(a.tokenSymbol)}
</div>
<div className="col-md-2">
{a.tokensStaked}
</div>
<div className="col-md-2">
{a.usdValue}
</div>
<div className="col-md-2">
{a.usdAccruedInterest}
</div>
<div className="col-md-2">
{a.ethAccruedInterest}
</div>
<div className="col-md-2">
{a.open ? (
<div onClick={() => withdraw(a.positionId)} className="orangeMiniButton">Withdraw</div>
) : (
<span>closed</span>
)}
</div>
</div>
))}
</div>
</>
) : (
<div
onClick={() => connectAndLoad()}
className="connectButton">
Connect Wallet
</div>
)}
</div>
{showStakeModal && (
<StakeModal
onClose={() => setShowStakeModal(false)}
stakeTokenSymbol={stakeTokenSymbol}
setStakeTokenQuantity={setStakeTokenQuantity}
stakeTokens={stakeTokens}
/>
)}
</div>
);
}
export default App;
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.2.0",
"ethers": "^5.6.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"styled-components": "^5.3.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
require("@nomiclabs/hardhat-waffle");
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
version: "0.8.0",
},
paths: {
artifacts: "./client/src/artifacts",
}
};
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
{
"name": "stake-erc20-tokens",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@nomiclabs/hardhat-ethers": "^2.1.0",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@openzeppelin/contracts": "^4.7.3",
"chai": "^4.3.6",
"ethereum-waffle": "^3.4.4",
"ethers": "^5.6.9",
"hardhat": "^2.10.1"
}
}
async function main() {
[owner] = await ethers.getSigners();
const Staking = await ethers.getContractFactory('Staking', owner);
const staking = await Staking.deploy(
187848,
{
value: ethers.utils.parseEther('100')
}
);
const Chainlink = await ethers.getContractFactory('Chainlink', owner); chainlink = await Chainlink.deploy();
const Tether = await ethers.getContractFactory('Tether', owner); tether = await Tether.deploy();
const UsdCoin = await ethers.getContractFactory('UsdCoin', owner); usdCoin = await UsdCoin.deploy();
const WrappedBitcoin = await ethers.getContractFactory('WrappedBitcoin', owner); wrappedBitcoin = await WrappedBitcoin.deploy();
const WrappedEther = await ethers.getContractFactory('WrappedEther', owner); wrappedEther = await WrappedEther.deploy();
await staking.connect(owner).addToken('Chainlink', 'LINK', chainlink.address, 867, 1500);
await staking.connect(owner).addToken('Tether', 'USDT', tether.address, 100, 200);
await staking.connect(owner).addToken('UsdCoin', 'USDC', usdCoin.address, 100, 200, );
await staking.connect(owner).addToken('WrappedBitcoin','WBTC', wrappedBitcoin.address, 2382096, 500);
await staking.connect(owner).addToken('WrappedEther', 'WETH', wrappedEther.address, 187848, 1000);
console.log("Staking:", staking.address);
console.log("Chainlink:", chainlink.address);
console.log("Tether:", tether.address);
console.log("UsdCoin:", usdCoin.address);
console.log("WrappedBitcoin:", wrappedBitcoin.address);
console.log("WrappedEther:", wrappedEther.address);
await chainlink.connect(owner).approve(staking.address, ethers.utils.parseEther('100'));
await staking.connect(owner).stakeTokens('LINK',ethers.utils.parseEther('100'))
await wrappedBitcoin.connect(owner).approve(staking.address, ethers.utils.parseEther('2'));
await staking.connect(owner).stakeTokens('WBTC',ethers.utils.parseEther('2'))
await wrappedBitcoin.connect(owner).approve(staking.address, ethers.utils.parseEther('10'));
await staking.connect(owner).stakeTokens('WBTC',ethers.utils.parseEther('10'))
await wrappedEther.connect(owner).approve(staking.address, ethers.utils.parseEther('10'));
await staking.connect(owner).stakeTokens('WETH',ethers.utils.parseEther('10'))
const provider = waffle.provider;
const block = await provider.getBlock()
const newCreatedDate = block.timestamp - (86400 * 365)
await staking.connect(owner).modifyCreatedDate(1, newCreatedDate)
await staking.connect(owner).modifyCreatedDate(2, newCreatedDate)
await staking.connect(owner).modifyCreatedDate(3, newCreatedDate)
}
// npx hardhat run --network localhost scripts/deploy.js
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
import React, { useState } from 'react';
const StakeModal = props => {
const {
onClose,
stakeTokenSymbol,
setStakeTokenQuantity,
stakeTokens,
} = props
return (
<>
<div className="modal-class" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<div className="modal-body">
<h2 className="titleHeader">Stake</h2>
<div className="row">
<div className="col-md-9 fieldContainer">
<input
className="inputField"
placeholder="0.0"
onChange={e => props.setStakeTokenQuantity(e.target.value)}
/>
</div>
<div className="col-md-3 inputFieldUnitsContainer">
<span>{stakeTokenSymbol}</span>
</div>
</div>
<div className="row">
<div
onClick={() => {stakeTokens(); onClose()}}
className="pinkButton hoverButton">
Stake
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default StakeModal;
const { expect } = require("chai");
describe('Staking', () => {
beforeEach(async () => {
[owner, signer2] = await ethers.getSigners();
Staking = await ethers.getContractFactory('Staking', owner);
staking = await Staking.deploy(
187848,
{
value: ethers.utils.parseEther('100')
}
);
Chainlink = await ethers.getContractFactory('Chainlink', signer2);
chainlink = await Chainlink.deploy();
staking.connect(owner).addToken(
'Chainlink',
'LINK',
chainlink.address,
867,
1500
)
await chainlink.connect(signer2).approve(
staking.address,
ethers.utils.parseEther('100')
);
staking.connect(signer2).stakeTokens(
'LINK',
ethers.utils.parseEther('100')
)
});
describe('addToken', () => {
it('adds a token symbol', async () => {
const tokenSymbols = await staking.getTokenSymbols()
expect(tokenSymbols).to.eql(['LINK'])
})
it('adds token information', async () => {
const token = await staking.getToken('LINK')
expect(token.tokenId).to.equal(1)
expect(token.name).to.equal('Chainlink')
expect(token.symbol).to.equal('LINK')
expect(token.tokenAddress).to.equal(chainlink.address)
expect(token.usdPrice).to.equal(867)
expect(token.ethPrice).to.equal(0)
expect(token.apy).to.equal(1500)
})
it('increments currentTokenId', async () => {
expect( await staking.currentTokenId() ).to.equal(2)
})
})
describe('stakeToken', () => {
it('transfers tokens', async () => {
const signerBalance = await chainlink.balanceOf(signer2.address)
expect(signerBalance).to.equal( ethers.utils.parseEther('4900') )
const contractBalance = await chainlink.balanceOf(staking.address)
expect(contractBalance).to.equal( ethers.utils.parseEther('100') )
})
it('creates a position', async () => {
const positionIds = await staking.connect(signer2).getPositionIdsForAddress()
expect(positionIds.length).to.equal(1)
const position = await staking.connect(signer2).getPositionById(positionIds[0])
expect(position.positionId).to.equal(1)
expect(position.walletAddress).to.equal(signer2.address)
expect(position.name).to.equal('Chainlink')
expect(position.symbol).to.equal('LINK')
expect(position.apy).to.equal(1500)
expect(position.tokenQuantity).to.equal( ethers.utils.parseEther('100') )
expect(position.open).to.equal(true)
})
it('increments positionId', async () => {
expect(await staking.currentPositionId()).to.equal(2)
})
it('increases total amount of staked token', async () => {
expect(await staking.stakedToken('LINK')).to.equal( ethers.utils.parseEther('100') )
})
})
describe('calculateInterest', () => {
it('returns interest accrued to a position', async () => {
const apy = 1500
const value = ethers.utils.parseEther('100')
const days = 365
const interestRate = await staking.calculateInterest(apy, value, days)
expect( String(interestRate) ).to.equal( String(ethers.utils.parseEther('15')) )
})
})
describe('calculateNumberDays', () => {
it('returns the number of days since createdDate', async () => {
const provider = waffle.provider;
const block = await provider.getBlock()
const oneYearAgo = block.timestamp - (86400 * 101)
const days = await staking.connect(owner).calculateNumberDays(oneYearAgo)
expect(days).to.be.equal(101)
})
})
describe('closePosition', () => {
beforeEach(async () => {
provider = waffle.provider;
contractEthbalanceBefore = await provider.getBalance(staking.address)
signerEthBalanceBefore = await provider.getBalance(signer2.address)
const block = await provider.getBlock()
const newCreatedDate = block.timestamp - (86400 * 365)
await staking.connect(owner).modifyCreatedDate(1, newCreatedDate)
await staking.connect(signer2).closePosition(1)
})
it('returns tokens to wallet', async () => {
const signerBalance = await chainlink.balanceOf(signer2.address)
expect(signerBalance).to.equal( ethers.utils.parseEther('5000') )
const contractBalance = await chainlink.balanceOf(staking.address)
expect(contractBalance).to.equal( ethers.utils.parseEther('0') )
})
it('sends ether interest to wallet', async () => {
const contractEthBalanceAfter = await provider.getBalance(staking.address)
const signerEthBalanceAfter = await provider.getBalance(signer2.address)
expect(contractEthBalanceAfter).to.be.below(contractEthbalanceBefore)
expect(signerEthBalanceAfter).to.be.above(signerEthBalanceBefore)
})
it('closes position', async () => {
const position = await staking.connect(signer2).getPositionById(1)
expect(position.open).to.equal(false)
})
})
})
@KashifCh-eth
Copy link
Author

position

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment