const StyledContainer = styled.div` padding-top: 18px; width: 478px; border: 1px solid rgba(55, 58, 83, 1); border-radius: 16px; margin: 50px auto 0; padding: 20px 0 0px; position: relative; `; const Content = styled.div` padding: 20px 15px; `; const Wrapper = styled.div``; const BlurWrap = styled.div` position: relative; `; const WithdrawWrap = styled.div` padding: 20px 15px; min-height: 300px; .withdraw-title { font-size: 16px; color: white; margin-bottom: 20px; } .withdraw-item { display: flex; align-items: center; justify-content: space-between; color: white; margin-bottom: 10px; } .empty { color: white; text-align: center; } .empty-title { font-size: 16px; font-weight: 600; padding: 20px 0; } .empty-txt { font-size: 16px; } `; const Blur = styled.div` position: absolute; left: 0; right: 0; top: 0; bottom: 0; backdrop-filter: blur(4px); `; const Summary = styled.div` display: flex; padding: 0 20px 20px; border-bottom: 1px solid rgba(55, 58, 83, 1); align-items: center; justify-content: space-between; `; const SummaryItem = styled.div` font-size: 14px; font-weight: 400; line-height: 16.8px; .title { color: rgba(151, 154, 190, 1); } .amount { margin-top: 5px; color: rgba(255, 255, 255, 1); } `; const Panel = styled.div` height: 100px; border-radius: 12px; border: 1px solid rgba(55, 58, 83, 1); background-color: rgba(46, 49, 66, 1); padding: 15px; margin-bottom: 20px; .title { font-size: 14px; font-weight: 400; line-height: 16.8px; color: rgba(151, 154, 190, 1); } .body { display: flex; justify-content: space-between; align-items: center; gap: 20px; } .foot { margin-top: 10px; display: flex; justify-content: center; justify-content: space-between; font-size: 12px; font-weight: 400; line-height: 14.4px; color: rgba(151, 154, 190, 1); } `; const Input = styled.input` color: #fff; font-size: 20px; font-weight: 500; border: none; height: 24px; width: 200px; outline: none; background-color: transparent; padding: 0; &:focus { color: #fff; background-color: transparent; border-color: transparent; outline: none; box-shadow: none; } `; const List = styled.div` display: flex; align-items: center; justify-content: space-between; font-size: 15px; .keys { color: rgba(151, 154, 190, 1); } .values { color: #fff; } `; const BtnWrap = styled.div` display: flex; justify-content: space-around; margin-top: 20px; `; const Btn = styled.button` background-color: var(--switch-color); color: var(--button-text-color); display: block; width: 100%; /* width: 130px; height: 40px; */ height: 56px; font-size: 16px; font-weight: 600; color: white; background-color: #075a5a; border-radius: 6px; cursor: pointer; transition: 0.5s; &:hover { opacity: 0.8; } &:disabled { opacity: 0.5; } `; const ClaimBtn = styled.button` background-color: var(--button-color); color: var(--button-text-color); display: block; font-size: 14px; font-weight: 600; height: 40px; border-radius: 6px; cursor: pointer; transition: 0.5s; &:hover { opacity: 0.8; } &:disabled { background-color: var(--button-disabled-color); cursor: not-allowed; } `; const WithdrawTitle = styled.div` font-size: 18px; font-weight: 600; padding: 20px 0; color: white; `; const VotingPowerABI = [ { inputs: [{ internalType: "address", name: "who", type: "address" }], name: "totalNFTStaked", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [ { internalType: "address", name: "", type: "address" }, { internalType: "uint256", name: "", type: "uint256" }, ], name: "lockedTokenIdNfts", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, ]; const LockedZeroTokensABI = [ { inputs: [{ internalType: "uint256", name: "", type: "uint256" }], name: "locked", outputs: [ { internalType: "uint256", name: "amount", type: "uint256" }, { internalType: "uint256", name: "end", type: "uint256" }, { internalType: "uint256", name: "start", type: "uint256" }, { internalType: "uint256", name: "power", type: "uint256" }, ], stateMutability: "view", type: "function", }, { inputs: [{ internalType: "uint256", name: "_tokenId", type: "uint256" }], name: "withdraw", outputs: [], stateMutability: "nonpayable", type: "function", }, ]; const { dexConfig, wethAddress, multicallAddress, WithdrawalContract, chainIdNotSupport, multicall, prices, account, addAction, toast, chainId, nativeCurrency, tab, StakeTokens, ExchangeToken, onChange, selectTab, supportChainId, ZeroContract, LockedZeroTokens, ZeroVotingPower, } = props; const { parseUnits, formatUnits } = ethers.utils; // console.log("Content--", props); const { tokenPairs } = dexConfig; function getData(n) { let now = new Date(n), y = now.getFullYear(), m = now.getMonth() + 1, d = now.getDate(); return y + "-" + (m < 10 ? "0" + m : m) + "-" + (d < 10 ? "0" + d : d); } State.init({ tvl: 0, stakeAmount: "", lockDuration: 7776000, options: [ { text: "3 months", value: 7776000, }, { text: "6 months", value: 15552000, }, { text: "1 year", value: 31536000, }, { text: "2 years", value: 63072000, }, { text: "4 years", value: 126144000, }, ], tokenBal: 0, unlockDate: "", withdrawList: [], updater: "", votingPower: 0, nftIds: 0, locksList: [], }); const bonus = { 7776000: "-", 15552000: "-", 31536000: "+5% bonus", 63072000: "+10% bonus", 126144000: "+20% bonus", }; const votingMap = { 7776000: { a: 0.0625, b: 0, }, 15552000: { a: 0.125, b: 0 }, 31536000: { a: 0.25, b: 0.05 }, 63072000: { a: 0.5, b: 0.1 }, 126144000: { a: 1, b: 0.2 }, }; useEffect(() => { const _time = (new Date().getTime() / 1000 + state.lockDuration) * 1000; const _unloackDate = getData(_time); const _votingPower = Big(state.stakeAmount || 0) .times(votingMap[state.lockDuration].a || 0) .plus( Big(state.stakeAmount || 0).times(votingMap[state.lockDuration].b || 0) ) .toFixed(7); State.update({ unlockDate: _unloackDate, votingPower: _votingPower, }); }, [state.lockDuration, state.stakeAmount]); useEffect(() => { State.update({ loading: !chainIdNotSupport, }); }, []); const clickBalance = (_bal) => { State.update({ stakeAmount: Big(_bal || 0).toFixed(4, 0), }); }; function fetchData(url) { return asyncFetch(url); } function getLocksList() { const calls = new Array(state.nftIds).fill("").map((item, _index) => ({ address: ZeroVotingPower, name: "lockedTokenIdNfts", params: [account, _index], })); multicall({ abi: VotingPowerABI, calls, options: {}, multicallAddress, provider: Ethers.provider(), }) .then((res) => { const _ids = res.map((item) => item[0]?.toNumber()); return _ids; }) .then((ids) => { const calls = ids.map((id) => ({ address: LockedZeroTokens, name: "locked", params: [id], })); multicall({ abi: LockedZeroTokensABI, calls, options: {}, multicallAddress, provider: Ethers.provider(), }).then((data) => { const formated = data.map((item, index) => { const [amount, end, start, power] = item; return { nftId: ids[index], amount: amount.toString(), end: end.toString(), start: start.toString(), power: power.toString(), }; }); State.update({ locksList: formated, }); }); }) .catch((err) => { console.log("catch-getLocksList-error:", err); }); } useEffect(() => { if (state.nftIds > 0) { getLocksList(); } }, [state.nftIds]); function handleClaim(_nftId) { State.update({ claimLoading: true, }); const contract = new ethers.Contract( LockedZeroTokens, LockedZeroTokensABI, Ethers.provider().getSigner() ); contract .withdraw(_nftId) .then((tx) => { tx.wait() .then((res) => { const { status, transactionHash } = res; if (status !== 1) throw new Error(""); State.update({ claimLoading: false, updater: Date.now(), }); toast?.success({ title: "Claim Successfully!", // text: `Approve ${Big(amount).toFixed(2)} ${tokenSymbol}`, tx: transactionHash, chainId, }); }) .catch((err) => { State.update({ claimLoading: false, }); }); }) .catch((err) => { console.log("handleClaim-error--", err); State.update({ claimLoading: false, }); }); } function getTokenBalance() { const contract = new ethers.Contract( StakeTokens[0].address, [ { inputs: [{ internalType: "address", name: "account", type: "address" }], name: "balanceOf", outputs: [{ internalType: "uint256", name: "value", type: "uint256" }], stateMutability: "view", type: "function", }, ], Ethers.provider().getSigner() ); contract .balanceOf(account) .then((_balance) => { const _bal = formatUnits(_balance, StakeTokens[0].decimals); State.update({ tokenBal: _bal, }); }) .catch((err) => { console.log("Catch-getTokenBalance-error--", err); }); } const setNumKMB = (num, digital) => { if (!num && num !== 0 && num !== "") return num; if (typeof num !== "number" && typeof num !== "string") return num; const _num = Number(num); let result = "", unit = ""; // K M B if (_num <= 999) { result = _num; } else if (_num <= 999999) { result = _num / 1000; unit = "K"; } else if (_num <= 999999999) { result = _num / 1000000; unit = "M"; } else if (_num <= 999999999999) { result = _num / 1000000000; unit = "B"; } else { result = _num / 1000000000000; unit = "T"; return result.toExponential(2) + unit; } let _result; if (digital !== null) _result = result.toFixed(digital) + unit; else _result = result + unit; return _result; }; function getTvl() { const contract = new ethers.Contract( ZeroContract, [ { inputs: [{ internalType: "address", name: "account", type: "address" }], name: "balanceOf", outputs: [{ internalType: "uint256", name: "value", type: "uint256" }], stateMutability: "view", type: "function", }, ], Ethers.provider().getSigner() ); contract .balanceOf(LockedZeroTokens) .then((_balance) => { const _tvl = setNumKMB(formatUnits(_balance, 18), 2); State.update({ tvl: _tvl, }); }) .catch((err) => { console.log("Catch-getTvl-error--", err); }); } function getTotalNFTStaked() { const contract = new ethers.Contract( ZeroVotingPower, VotingPowerABI, Ethers.provider() ); contract .totalNFTStaked(account) .then((_amount) => { const length = _amount.toNumber(); State.update({ nftIds: length, }); }) .catch((err) => { console.log("Catch-getTotalNFTStaked-error--", err); }); } useEffect(() => { getTokenBalance(); getTvl(); getTotalNFTStaked(); }, []); // console.log("STATE--", state); return ( <div> <StyledContainer> <Wrapper> <Summary> <SummaryItem> <div className="title">Total locked</div> <div className="amount">{state.tvl}</div> </SummaryItem> <SummaryItem> <div className="title">APR</div> <div className="amount">{0}%</div> </SummaryItem> </Summary> <Content> <BlurWrap> {chainId !== supportChainId ? <Blur></Blur> : null} <Panel> <div className="title">Stake</div> <div className="body"> <Input type="text" placeholder="0" value={state.stakeAmount} onChange={(ev) => { if (isNaN(Number(ev.target.value))) return; let amount = ev.target.value.replace(/\s+/g, ""); if (Big(amount || 0).gt(Big(state.tokenBal || 0))) { amount = Big(state.tokenBal || 0).toFixed(4, 0); } State.update({ stakeAmount: amount, }); }} /> <Widget src="bluebiu.near/widget/UI.Select.Index" props={{ options: state.options, value: state.options.find( (obj) => obj.value === state.lockDuration ), onChange: (option) => { // console.log("onchange--", option); State.update({ lockDuration: option.value, }); }, }} /> </div> <div className="foot"> <div class="prices"> $ {Big(state.stakeAmount || 0) .times(Big(prices[StakeTokens[0].symbol] || 1)) .toFixed(2, 0)} </div> <div class="balance"> Balance: <Widget src="bluebiu.near/widget/AAVE.Stake.Balance" props={{ value: state.tokenBal, digit: 4, onClick: clickBalance, symbol: StakeTokens[0].symbol, }} /> </div> </div> </Panel> <List> <span className="keys">Staking Bonus</span> <span className="values">{bonus[state.lockDuration]}</span> </List> <List> <span className="keys">Voting Power</span> <span className="values"> ~ {parseFloat(state.votingPower)} veZERO </span> </List> <List> <span className="keys">APR</span> <span className="values">0%</span> </List> <List> <span className="keys">Lock Until</span> <span className="values">{state.unlockDate}</span> </List> </BlurWrap> <Widget src="bluebiu.near/widget/AAVE.Stake.Button" props={{ ...props, actionText: "Stake", amount: state.stakeAmount, // lockDuration: state.lockDuration, stakeToken: StakeTokens[0], lockDuration: state.lockDuration, onSuccess: () => { State.update({ loading: true, stakeAmount: "" }); }, }} /> </Content> <WithdrawWrap> <BlurWrap> {chainId !== supportChainId ? <Blur></Blur> : null} <WithdrawTitle>Your Locks</WithdrawTitle> {state.locksList.length ? ( <> {/* <div className="withdraw-title">AMOUNT</div> */} <div className="withdraw-list"> {state.locksList.map((item) => ( <div className="withdraw-item"> <span className="withdraw-amount"> {getData(item.end * 1000)} </span> <span>{Number(formatUnits(item.amount)).toFixed(2)}</span> <span> {Number(formatUnits(item.power)).toFixed(2)} veZERO </span> <ClaimBtn onClick={(e) => { handleClaim(item.nftId); }} disabled={new Date().getTime() < item.end * 1000} > {state.claimLoading ? ( <Widget src="bluebiu.near/widget/0vix.LendingLoadingIcon" props={{ size: 16, }} /> ) : ( "Withdraw" )} </ClaimBtn> </div> ))} </div> </> ) : ( <div className="empty"> <div className="empty-title">No locks found</div> <div className="empty-txt"> Your staking positions will be visible as NFTs here. You can withdraw your staked tokens after the lock period is over. </div> </div> )} </BlurWrap> {chainId !== supportChainId ? ( <Widget src="bluebiu.near/widget/AAVE.Stake.SwitchBtn" props={{ ...props, actionText: selectTab, }} /> ) : null} </WithdrawWrap> </Wrapper> </StyledContainer> </div> );