From 92916908963513351e4cbfb23cb7b244ffc8376a Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 24 Oct 2024 16:30:39 +0100 Subject: [PATCH 01/29] feat: merkl reward list --- src/App.jsx | 2 + src/assets/merkl-powered-dark.svg | 30 ++++ src/assets/merkl-powered-light.svg | 30 ++++ src/assets/merkllogo-light.webp | Bin 0 -> 3552 bytes src/assets/merkllogo.webp | Bin 0 -> 3930 bytes src/assets/merklogo-dark.webp | Bin 0 -> 3022 bytes src/components/ui/TokenIcon.jsx | 40 +++-- src/components/vault/merkl/RewardList.jsx | 177 ++++++++++++++++++++ src/components/vault/yield/YieldParent.jsx | 4 +- src/pages/vault/Vault.jsx | 9 + src/pages/vault/VaultHistory.jsx | 2 +- src/pages/vault/VaultMerkl.jsx | 183 +++++++++++++++++++++ 12 files changed, 460 insertions(+), 17 deletions(-) create mode 100644 src/assets/merkl-powered-dark.svg create mode 100644 src/assets/merkl-powered-light.svg create mode 100644 src/assets/merkllogo-light.webp create mode 100644 src/assets/merkllogo.webp create mode 100644 src/assets/merklogo-dark.webp create mode 100644 src/components/vault/merkl/RewardList.jsx create mode 100644 src/pages/vault/VaultMerkl.jsx diff --git a/src/App.jsx b/src/App.jsx index fd891e8..43d154f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,6 +9,7 @@ import Home from './pages/Home'; import Vaults from './pages/vaults/Vaults'; import Vault from './pages/vault/Vault'; import VaultHistory from './pages/vault/VaultHistory'; +import VaultMerkl from './pages/vault/VaultMerkl'; import LiquidationPools from './pages/liquidation-pools/LiquidationPools'; import StakingPool from './pages/staking-pool/StakingPool'; import TermsOfUse from './pages/TermsOfUse'; @@ -30,6 +31,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/assets/merkl-powered-dark.svg b/src/assets/merkl-powered-dark.svg new file mode 100644 index 0000000..42779b2 --- /dev/null +++ b/src/assets/merkl-powered-dark.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/merkl-powered-light.svg b/src/assets/merkl-powered-light.svg new file mode 100644 index 0000000..fab4358 --- /dev/null +++ b/src/assets/merkl-powered-light.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/merkllogo-light.webp b/src/assets/merkllogo-light.webp new file mode 100644 index 0000000000000000000000000000000000000000..5ebf6803d08c98e686bfdf58f629176675261f4b GIT binary patch literal 3552 zcmWIYbaT7G%fJxs>J$(bU=hK^z`!5@#Lt;Q$k8Xjqmz+=fiXQ{Lhh3*`yF}n1O?eQ z#yb|}-d-iTGn?a9G53ShybS^zjSL*Gz8AKylD;ANSS}{b<-dAD$N!)6Nk;Yi?d-gZ zUKKxGqGoxx{?Bn3`fknf|Fb<5?Z3S~ds6ax58tB6|9|wu(EIdV2O}q~KN&em`n~nP z=k*};e|GM670C_Jv*dfDw9fXke(~yioxieu*5k?p3(V|AOG9SQ=dM_3Iq#GF|Bnb% z2~^h3IQP;Yk1Iu)bvg^;i(=}%KSFH(um9`rq?7}BnRO;vD^2XhE@)e>+rLyUtM55u z^Q}K2R|0eE^d2mIR&fgI^7=_HrpoX$TP^)LamCY1eFu!27%Qc(jxkOoMp^|WhLIZDSX5Pbczkq*?KN!DgKd@)8 zXE3wB7P%`*`NHOBJjLsHUv~fbzbxHZ|LH%MeOv50tAzh%+A`R@TfeVS^{eSere=lX zGiU6bkZo*Ot#B6_!4Ge_SvN6zfN$+p>8d=qb4ntqDZnKN-GOh=Usw*Wc5ZGF~c z?dvz{%GYoB^FBUt=kl%CQpD1Q*XO({Z9h1}P8(Y)(aYYqK1Eu1b0pRTqkrMdqHm55 zZmz+arlz0n_#V6a*xeIb-tL~5dAnTIpUs92>CdXh6l$T;o?J#!Aq29A$vRE=EvKSOBZ%EEA07{$R@c|QnB(ue%7;;8{KzbyiH46+X3^hwWfU3&f=KhEv#>v z_m|5BpE>$oa5^XqW^IENHwJH(>wMevmn$`S$KHtyx%uwd#-=%Y?=1;Po33gj@flq7 zq$c&ZKDZ`6nZw|$#!2q;%+=w>vfBFXqJJZ2{Q-v%Fb0e^dd3B0`g)yzk?ANVclbs% zf8rb4_8rnYJ~OUrxw2Np)p&2VM$neJgfsA@70DxM{d(VSDB8K#>2sup-`D8bP3FvJ z6p^G>Z@>CK{CQhNIs~H$O=@n0N+O~M!w=rjC3s$RTD_qMey#y_t zBjQ4mFW*$$y4PdDSv_z4wr}gwtis~Vie~+2`v8m_aDkrb-SK^f(SC1WG|SI@{YKSl z(O>1W2HCgEfhivBqN&p-tFy^KQ-b{5H%i~6U+;ZzecI!@$sXmW3*T*A;yC3q5gs0` zJBDK|8N$Hu@6i=tyO0Qw_x};wTDDtVhfn3cKPx=vz}K%;_u0=b`K$KmLYf2H7bmtt z!6P0FKlHh0B`{3x-g1D=zo6x=(FgVKtlMrvjc31o%Wkvd_WIB@%kH&x9lYY=k~MAC zt!=+7@(OE&MZaHpte_UMI9Kp=@b5pzW>-`$?(0`j^zPfW*Cg;>{HMQ1m6nS;b)SF1 zu>pQdfKQ)YkDoVNV?&t86|)9Mf7`HBngJyO4{CCb(6w50V( z_>F?@O>WVrWY^nIHoKl^_-|ssiV&dWRXNVR=5t;C{JHgAC_iW4^}cPXN@rF|G_@pg z33h0>DJ+y|YW<=z?@r0IfaWvCZ+AbN;B;%+hmU&Fng{#x<5LWqB7eD9^clLxVl}f}H>KYn}at98$mV z-*Q^azCH4uqvdat4GRO7-@iR!t{1mk12kTQ^EJx${PVdTeCYDs=4qiuEy4;jSx#uD z-}Ky{)SvMSBo*6o{FkfKLZJV(Ln_q1+ zEL^?kFaH0b-PHVi_nz%F-y(b~FSo+H1N9Yd$~D5%Z5L45wIVZ(fx**Li-7~kV`C6v zWM%+%i@;clkrhmb0r}EUb{vqc0ae2UWD7GP)MvJ{fYk#H1OpEs2{H>tvoSDCU}s5y3}rfSSPwg-AfQQAT8ARSa2#Op?c@E~H=;iGUzD5Tv*T z6{S)K%5dUjR1uL;K#M3#T!jB7Sh0Wg|6lT6&OP6E&O77YoA-Pv?(V)?00g-?Qv<2C z!Ri12Yj~bfd?_w2RDWeS1j_zAI$tuG003WrihVttk>HS3$kZO74rYSyz+$k3Mwf`3 zs8mlm`|HP>MxZ1|9%ALbzRvlkb{c~zp#z|#3K0lvTkI0FPwfE#cDHoyYd!K)3x0!x5{Bk&D-x$oJ)8wjB-9bQEs z4u}B*SLGXwygdjP2aI9@Y|$#HP72>|~D*s3}Ja`OO~5BXZFojj?_j!lLg zQYQ_LnG4Re47P;xQhvN)b@Fgw?%vRiNf}idz6R2x2mCzLC2K2P`pj0lop>@u(}>I< zMbRERqZQ4g4P(vQQq_=0Hf3-4k27zst{8YdvNR=&^72uA)8oo@Yq$#}rmrrk201=V z^?uB~La4ZVDOlII9`9^Pc(yP1RN?DV<2qDa6EewV+SQb4S*2Mu39sQJtB&P=)8t+bsqbf*qn zH{4H|OpQn{(>*1+uG9D%II}LUEgp0ewW?JPH26LNi5#} z_|3G9BMsH9$j~oM8LG&Rri^Q;5vikBQwtk3ry!xnfAJ}_c4`<>-<@#ueEP$S3y(AR zX5U`E74*dZYEpDqIdtiZ4Qo$YZLB!Bdde~Myv1?%TQl8`m#ut&t?XN9x2-WZV@CM% z47W1<)bJA<6EsZ?OVYMy#2PKQ*|uxmtSj8zJw|JVAyqRjI2fK#7PX}8zNmIeMoxX{ zZIXQ($-cDZk7s(-VlC~2*M?zRa@~8KQiJ=x4@)wreqdgmkubek&UUEo4Xj2LE_$Ie zgS2Nv&#bCNE3&HpvfY{1c&W?JH4zcbV0zk)QlqrtWzc`Ecslm5zglULL-E>PCst;ETfOuGXF{N1t-L zx`$PWJ6SS8{K-Og+U-|en!`QPxx3Ad)@;q3{bOx_Z#eCA*~w!avn;j>V(Lpzr`;gF zaaEyZ6)hnwT4I#3eb4DorhATIZc+2VK>VZH&N?lb>4Kj&S{?E5zJ1*zKUF$Z*IBl@ zc*h>kt+leIf!K?85#~UwEJwqv#Lkb~+rFiBKANR_JE0HzZo!2n7te;ku$~Q$_oz?8 zBD#&9-!IJByAz$to<3_0d+p*_np3`-=2h{_+KxwU<@qgYtuJ~bSKV4iT=i1ca${&+ z-xZ;54`JVv7Ge1;&vO9&Ln7=j*N* z;>fn5zT~72TdUh81tA_ghMs=NcEF8X+rIS5gN$hZrv{XiZ9M7V)Bd@~bo*A7Tau=0 zZ?C?-$tuF2)qBwMtzmc)SbXb-Wp87G=ll~c-qDG9wrzEe0jjc!k;M8^YwKH=79(q< z?5;{Z^Bx}6edZX2a-@2AT223;sRJY1DzuB&ZmKjCxQLBAyr`!Ayiy#o95v?<*S~)L zfct!oyLTm!{ZW~DcFwcABR}l-?+!HF;}>0Gf5Nxk`M?2h>@AtwueEQkY}06HQr5~0 zj<7v<$E7?2O!cFnD>jvQd$j%3BHY)LuDu(qz181;an_QUL%Bu8P0n}I*eXUYh!(3) zzad^6VYNb{&dXNV zEWOH(F;0#kscqQxoU3nCA03%m(-oE|*lFr;@>TkBs_czV{6{KlkL<<8vX!OB$xa^c zyJy+zy+n`N;?TcbA`MmqhZ?*m9mV;xovUc>HQ!`F#O}ARJf~x+8q(8WM zNe{b1JgTgJdMwPR+`QaFt3wt#Qgp)L;?|qz$(h6fZ!3Q3kzEVdyJ`Q~GAJC?JEpom zNRPKbDcZGLKvO#qP}^VGu!4A{zhLk~)rHU)-O(pAW-L!)%u5`T=h;(mWkLi=H`=R&a1quDXb?;_Lk3k&|j9*p_-*heXO-jylr>mGun5)3th>F_I&IS7^P#|^NaVpeUJi5y#Cgg31esD4h-vT4!LzH zxqH!KtG?P~53171ETY!^gx)-@m1aN<=M*Ak_8DMgCQgp#I-xYQXFmYJUQ}csd~&dN zj1|%7TqcTyG1(je8T0D*%NT^iAY+1vUIZ_Z3p1RvEMCm?i}&`Y$8+g+42+|Lx_vAu zmM`KnQ5q7<=LsaFSTcsj5QZ^HP?oFl7(_uqxnzv|RxUy@6CvV=I0D8&9kCZPSR`Lp z_fN)fM8<@psECBe$Hc_oVytjNF&l4TXJ>~eSmG@$v5fmAe@C;R>H; z#T*gJ5eg8wPg|0Nyeu)aZ;Bmbg{ z!iA_%5-t>dPT(K6_zeHc5SH)jCb2Zp*T`O8|C`R||04|v>J|+P@u{f)i0SVeNc`hO zOuR2sB8(E#nQqZc0cxtKguE^!Dkql73wGu3nF0x{E*WECOZYD|W zW(p&D?MM_pjm-?=FwpP`ZxetgQ^5WgB9D$2C2wzlfCKZfobVPrfsrwGHgICeo38K| z@>S?_FGz~`yo3xGo|VFC0+PS1iG?f<4`LGe8ydyU#m2(U#s+JN8&3r`js$HvtT{tM={3{MA2yyjV><|86y%48Bug5Ly;DAH;zh#EHs8DW;(H9lgSt- z7K;PR8HZ5ivlp?z*?r3Gvq4Na6FU2LYWPoq;pL0&@5t=&|BBsSLHJ^oLc4JtT>Wqx V!GGRI;NbI4#1z2oM-2BF@E^+P_DBE# literal 0 HcmV?d00001 diff --git a/src/assets/merklogo-dark.webp b/src/assets/merklogo-dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..9fb89e19c473cb70e0d0bf3a32e47ea68c5802bd GIT binary patch literal 3022 zcmWIYbaOk#&A<@u>J$(bU=hK^z`!5@#Lt;Q$k8Xjqmz+=fiXQ{Lhh3*`yF}n1O?eQ z#yb|}-d-iTGn?a9G53ShybS^zjSL*Gz8AKylD;ANSS}{b<-dAD$N!)6Nk;Yi?d-gZ zUKKxGqGoxx{?Bn3`fknf|Fb<5?Z3S~ds6ax58tB6|9|wu(EIdV2O}q~KN&em`n~nP z=k*};e|GM670C_Jv*dfDw9fXke(~yioxieu*5k?p3(V|AOG9SQ=dM_3Iq#GF|Bnb% z2~^h3IQP;Yk1Iu)bvg^;i(=}%KSFH(um9`rq?7}BnRO;vD^2XhE@)e>+rLyUtM55u z^Q}K2R|0eE^d2mIR&fgI^7=_HrpoX$TP^)LamCY1e8y>j z-&xggnc-#Xi8~+IKky&mzrcU~-hajk-{TQz}7B-w1x(ou** zD(|Z8KSiHqoE37d0NFwNGK1gdxcypXBoTLh?+K=)V(yEpm96;fWn0Hjc&K@ z3GGcdt8v2c86+=QF9ULf9j$=5Ba*y71-++cUj@s zKDm{KOw9_%XF_sNmGpf5tSpJ3SqEqNKR?&6pZS45WJaSSB9NJ99uqTmnHZPWb5SL{ z_YW`^&Q!|Oi27ZCaMJyf4B(RwaHnm(%BU(LXsbV z_8iJZN(3KwOwzjlBz)##6Hg(|tHAJCtZ;n#S)d2)FR{&Tv)Ol*E91+}r4l#pM15^G zsRsoQD6Eq2HI@3tx%>QVee}g`p|9L*lf}}~-EkYz`hkieF<*VbZ{_L}FXtQViR-wc zx%hJ0l;q^X>}xz1{{a<)1FXLaXkn$S@iX!6i&Cv7RXLY>a`JRRRs$7-%cza-fl;wu zd092z;cIdmzP7KiO?=HCBj5;1WsFyXl2}>yo}a!I?C4`VpLBgTN$XVFbLMZu%MU{N z;6Q%)OmqK<^~P=)le8~YcIAJ9B>XS)i7DK6{queV%p@Q)cW>#Hc!mhk)YT64-I9wu zzy>S7sdB5~GQ0MHL&f+(;CAjPBc4i-@n;SKGr*cfAC{UnE52bq7#yLRb>wV{w6S-S zQWP+GfHL07x4^W=Dh|pn{0VYgvkq#9sg)gnwE30DEoh3gzWVCQYEaSzrI@(Bb7n|c z$fb7P{{^bie;`h@JN)9<>|SvELX!3!g}K2I#$Hg@jF#lIFKY%*eR`FF;orYc2Y~H4 zT!8WWhnGlloc9l}V*hZh{(s$}+q2Y@3*)!6G2B_;KAX)?{jb9(RY~o(`?qI*f@olL z{wY%G&N9J8ki}8JMX-0*J?{@78D{;5xAh;c`Ty^4>o(sfapkw;4qti7C3W6vn_2JT zj9b6GN(*YU?A|(mc)$Md+7~P<96zKrfc2UGSNc3(QlMsk$P`gl$AAzqrs!$wrW^pu zvG6~maUz7x2sD9V0aV=3z<_Z9#Kiyq85o!rKumlARKkqJ1{n_k&1Jpj literal 0 HcmV?d00001 diff --git a/src/components/ui/TokenIcon.jsx b/src/components/ui/TokenIcon.jsx index adf0bb7..a6164d1 100644 --- a/src/components/ui/TokenIcon.jsx +++ b/src/components/ui/TokenIcon.jsx @@ -12,6 +12,7 @@ import sushilogo from "../../assets/sushilogo.svg"; import susdlogo from "../../assets/usdslogo.svg"; import usdclogo from "../../assets/usdclogo.svg"; import wethlogo from "../../assets/wethlogo.svg"; +import merkllogo from "../../assets/merkllogo.webp"; import Typography from "./Typography"; @@ -19,6 +20,7 @@ const TokenIcon = ({ symbol, style, className, + isMerkl, }) => { switch (symbol) { case 'ETH': @@ -26,7 +28,7 @@ const TokenIcon = ({ ETH logo ); @@ -35,7 +37,7 @@ const TokenIcon = ({ TST logo ); @@ -44,7 +46,7 @@ const TokenIcon = ({ EUROs logo ); @@ -53,7 +55,7 @@ const TokenIcon = ({ WBTC logo ); @@ -62,7 +64,7 @@ const TokenIcon = ({ LINK logo ); @@ -71,7 +73,7 @@ const TokenIcon = ({ ARB logo ); @@ -80,7 +82,7 @@ const TokenIcon = ({ PAXG logo ); @@ -89,7 +91,7 @@ const TokenIcon = ({ gmx logo ); @@ -98,7 +100,7 @@ const TokenIcon = ({ rdnt logo ); @@ -107,7 +109,7 @@ const TokenIcon = ({ sushi logo ); @@ -116,7 +118,7 @@ const TokenIcon = ({ USDs logo ); @@ -125,7 +127,7 @@ const TokenIcon = ({ USDC logo ); @@ -134,7 +136,7 @@ const TokenIcon = ({ WETH logo ); @@ -144,11 +146,21 @@ const TokenIcon = ({ logo ); default: + if (isMerkl) { + return ( + {`${symbol} + ) + } return (
{ + + let currencySymbol = ''; + if (vaultType === 'EUROs') { + currencySymbol = '€'; + } + if (vaultType === 'USDs') { + currencySymbol = '$'; + } + + const { vaultStore } = useVaultStore(); + + const [subRow, setSubRow] = useState('0sub'); + + const toggleSubRow = (index) => { + const useRow = index + 'sub'; + + if (subRow === useRow) { + setSubRow(''); + } else { + setSubRow(useRow); + } + } + + const vaultVersion = vaultStore?.status?.version || ''; + + const useSwapV4 = vaultVersion >= 4; + + return ( + <> + {/* +
*/} +
+ + + Merkl Reward Tokens + + + + + + + + + + {merklRewardsLoading ? (null) : ( + + {merklRewards.map(function(asset, index) { + const claimed = asset?.accumulated || ''; + const unclaimed = asset?.unclaimed || ''; + const symbol = asset.symbol || ''; + + return ( + + toggleSubRow(index)} + className={subRow === index + 'sub' ? ( + 'cursor-pointer hover active' + ) : ( + 'cursor-pointer hover' + )} + > + + + + + + + + + )} + )} + + )} +
AssetTokens 
+
+ + + +
{symbol}
+
+
+ + Accumulated: + +
+ {ethers.formatUnits(claimed, asset.decimals)} +
+ + Unclaimed: + +
+ {ethers.formatUnits(unclaimed, asset.decimals)} +
+ +
+ {merklRewardsLoading ? ( + + ) : (null)} +
+ {/*
+
*/} + + ); +}; + +export default RewardList; \ No newline at end of file diff --git a/src/components/vault/yield/YieldParent.jsx b/src/components/vault/yield/YieldParent.jsx index daff187..0ddba36 100644 --- a/src/components/vault/yield/YieldParent.jsx +++ b/src/components/vault/yield/YieldParent.jsx @@ -26,7 +26,7 @@ import Card from "../../ui/Card"; import Typography from "../../ui/Typography"; import Button from "../../ui/Button"; -const Vault = (props) => { +const YieldParent = (props) => { const { yieldEnabled } = props; const { vaultAddress } = useVaultAddressStore(); const { smartVaultABI } = useSmartVaultABIStore(); @@ -389,4 +389,4 @@ const Vault = (props) => { ) }; -export default Vault; \ No newline at end of file +export default YieldParent; \ No newline at end of file diff --git a/src/pages/vault/Vault.jsx b/src/pages/vault/Vault.jsx index ad90c11..70c8d15 100644 --- a/src/pages/vault/Vault.jsx +++ b/src/pages/vault/Vault.jsx @@ -141,6 +141,15 @@ const Vault = () => { All Vaults + {vaultType === 'USDs' ? ( + + ) : null} + +
+
+ Powered By Merkl +
+ + ) + }; + + useEffect(() => { + // fixes flashing "no vault found" on first load + setVaultsLoading(true); + setTimeout(() => { + setVaultsLoading(false); + }, 1000); + }, []); + + const vaultManagerAddress = chainId === arbitrumSepolia.id ? + arbitrumSepoliaContractAddress : + arbitrumContractAddress; + + const { data: vaultData, refetch } = useReadContract({ + address: vaultManagerAddress, + abi: vaultManagerAbi, + functionName: "vaultData", + args: [vaultId], + }); + + useWatchBlockNumber({ + onBlockNumber() { + refetch(); + }, + }) + + const currentVault = vaultData; + + useEffect(() => { + getMerklRewardsData(); + }, [currentVault, vaultsLoading]); + + const getMerklRewardsData = async () => { + try { + setMerklRewardsLoading(true); + const response = await axios.get( + `https://api.merkl.xyz/v3/userRewards?chainId=42161&proof=true&user=0xccD724B9af92A3A389150334556330EfD3f59487` + ); + + const useData = response?.data; + + const rewardsArray = []; + + Object.keys(useData).forEach(key => { + const value = useData[key]; + const rewardItem = { + tokenAddress: key, + ... value + } + rewardsArray.push(rewardItem); + }); + + setMerklRewards(rewardsArray); + setMerklRewardsLoading(false); + } catch (error) { + setMerklRewardsLoading(false); + console.log(error); + } + }; + + + if (vaultsLoading) { + return ( +
+ +
+ {vaultNav()} + +
+
+
+ ) + } + + if (!currentVault) { + return ( +
+ +
+ {vaultNav()} + Vault Not Found +
+
+
+ ); + } + + const { vaultAddress } = currentVault.status; + + return ( +
+ +
+ {vaultNav()} + + + +
+
+ +
+ ) +}; + +export default VaultMerkl; From 9e7706283a975d1c14ad5c8670e8602f5fc9a6a1 Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 25 Oct 2024 14:26:11 +0100 Subject: [PATCH 02/29] feat: merkl reward list --- src/abis/merkl.jsx | 32 +++ src/components/vault/merkl/RewardItem.jsx | 117 ++++++++++ src/components/vault/merkl/RewardList.jsx | 210 ++++++++--------- src/components/vault/merkl/TokenActions.jsx | 49 ++++ src/components/vault/merkl/WithdrawModal.jsx | 225 +++++++++++++++++++ src/pages/vault/VaultMerkl.jsx | 25 ++- src/store/Store.jsx | 15 ++ 7 files changed, 551 insertions(+), 122 deletions(-) create mode 100644 src/abis/merkl.jsx create mode 100644 src/components/vault/merkl/RewardItem.jsx create mode 100644 src/components/vault/merkl/TokenActions.jsx create mode 100644 src/components/vault/merkl/WithdrawModal.jsx diff --git a/src/abis/merkl.jsx b/src/abis/merkl.jsx new file mode 100644 index 0000000..c51e8ca --- /dev/null +++ b/src/abis/merkl.jsx @@ -0,0 +1,32 @@ +export const abi = [ + { + "inputs":[ + { + "internalType":"address[]", + "name":"users", + "type":"address[]" + }, + { + "internalType":"address[]", + "name":"tokens", + "type":"address[]" + }, + { + "internalType":"uint256[]", + "name":"amounts", + "type":"uint256[]" + }, + { + "internalType":"bytes32[][]", + "name":"proofs", + "type":"bytes32[][]" + } + ], + "name":"claim", + "outputs":[], + "stateMutability":"nonpayable", + "type":"function" + } +]; + +export default abi; diff --git a/src/components/vault/merkl/RewardItem.jsx b/src/components/vault/merkl/RewardItem.jsx new file mode 100644 index 0000000..161f07d --- /dev/null +++ b/src/components/vault/merkl/RewardItem.jsx @@ -0,0 +1,117 @@ +import { Fragment } from "react"; +import { ethers } from "ethers"; +import { + Tooltip, +} from 'react-daisyui'; + +import { + ChevronDownIcon, + ChevronUpIcon, +} from '@heroicons/react/24/outline'; + +import Button from "../../ui/Button"; +import TokenIcon from "../../ui/TokenIcon"; + +const RewardItem = ({ + vaultType, + index, + asset, + handleClick, + toggleSubRow, + subRow, +}) => { + + let currencySymbol = ''; + if (vaultType === 'EUROs') { + currencySymbol = '€'; + } + if (vaultType === 'USDs') { + currencySymbol = '$'; + } + + const claimed = asset?.accumulated; + const unclaimed = asset?.unclaimed; + const symbol = asset?.symbol; + const decimals = asset?.decimals; + const balance = asset?.balanceOf; + + return ( + + toggleSubRow(index)} + className={subRow === index + 'sub' ? ( + 'cursor-pointer hover active' + ) : ( + 'cursor-pointer hover' + )} + > + +
+ + + +
{symbol}
+
+ + + {ethers.formatUnits(balance, decimals)} + + + {ethers.formatUnits(unclaimed, decimals)} + + + + + + + + <> +
+ + +
+ + + +
+ ) +}; + +export default RewardItem; \ No newline at end of file diff --git a/src/components/vault/merkl/RewardList.jsx b/src/components/vault/merkl/RewardList.jsx index e00b686..9c478a1 100644 --- a/src/components/vault/merkl/RewardList.jsx +++ b/src/components/vault/merkl/RewardList.jsx @@ -4,6 +4,14 @@ import { Tooltip, } from 'react-daisyui'; +import { + useAccount, + useReadContracts, + useWriteContract, + useChainId, + useWatchBlockNumber, +} from "wagmi"; + import { ChevronDownIcon, ChevronUpIcon, @@ -12,19 +20,29 @@ import { import { useVaultStore, + useErc20AbiStore, + useVaultAddressStore, } from "../../../store/Store"; -import Card from "../../ui/Card"; import Button from "../../ui/Button"; import CenterLoader from "../../ui/CenterLoader"; import TokenIcon from "../../ui/TokenIcon"; import Typography from "../../ui/Typography"; +import TokenActions from "./TokenActions"; +import RewardItem from "./RewardItem"; const RewardList = ({ merklRewards, merklRewardsLoading, vaultType, }) => { + const { vaultStore } = useVaultStore(); + const { erc20Abi } = useErc20AbiStore(); + const { vaultAddress } = useVaultAddressStore(); + + const [actionType, setActionType] = useState(); + const [useAsset, setUseAsset] = useState(); + const [subRow, setSubRow] = useState('0sub'); let currencySymbol = ''; if (vaultType === 'EUROs') { @@ -34,9 +52,9 @@ const RewardList = ({ currencySymbol = '$'; } - const { vaultStore } = useVaultStore(); - - const [subRow, setSubRow] = useState('0sub'); + const closeAction = () => { + setActionType(''); + } const toggleSubRow = (index) => { const useRow = index + 'sub'; @@ -50,126 +68,78 @@ const RewardList = ({ const vaultVersion = vaultStore?.status?.version || ''; - const useSwapV4 = vaultVersion >= 4; + const { data: merklBalances, isLoading: merklBalancesLoading } = useReadContracts({ + contracts:merklRewards && merklRewards.length && merklRewards.map((item) =>({ + address: item.tokenAddress, + abi: erc20Abi, + functionName: "balanceOf", + args: [vaultAddress], + })) + }) + + const merklData = merklRewards && merklRewards.length && merklRewards.map((item, index) => { + let useBalance = 0n; + if (merklBalances) { + if (merklBalances[index]) { + if (merklBalances[index].result) { + useBalance = rewardDecimals[index].result; + } + return { + ...merklRewards[index], + balanceOf: useBalance + } + } + } + }); return ( <> - {/* -
*/} -
- - - Merkl Reward Tokens - - - - - - - - - - {merklRewardsLoading ? (null) : ( - - {merklRewards.map(function(asset, index) { - const claimed = asset?.accumulated || ''; - const unclaimed = asset?.unclaimed || ''; - const symbol = asset.symbol || ''; - - return ( - - toggleSubRow(index)} - className={subRow === index + 'sub' ? ( - 'cursor-pointer hover active' - ) : ( - 'cursor-pointer hover' - )} - > - - - - - - - - - )} - )} - +
+ + + Merkl Reward Tokens + +
AssetTokens 
-
- - - -
{symbol}
-
-
- - Accumulated: - -
- {ethers.formatUnits(claimed, asset.decimals)} -
- - Unclaimed: - -
- {ethers.formatUnits(unclaimed, asset.decimals)} -
- -
+ + + + + + + + + {merklRewardsLoading || merklBalancesLoading ? (null) : ( + + {merklData && merklData.length && merklData.map(function(asset, index) { + const handleClick = (type, asset) => { + setActionType(type); + setUseAsset(asset); + }; + + return ( + + )} )} -
AssetBalanceUnclaimed 
- {merklRewardsLoading ? ( - - ) : (null)} -
- {/*
-
*/} + + )} + + {merklRewardsLoading || merklBalancesLoading ? ( + + ) : (null)} + + ); }; diff --git a/src/components/vault/merkl/TokenActions.jsx b/src/components/vault/merkl/TokenActions.jsx new file mode 100644 index 0000000..63ceb01 --- /dev/null +++ b/src/components/vault/merkl/TokenActions.jsx @@ -0,0 +1,49 @@ +import React from "react"; +import { ethers } from "ethers"; +import WithdrawModal from "./WithdrawModal"; + +const TokenActions = ({ + actionType, + useAsset, + closeModal, + vaultType, +}) => { + let content; + + if (useAsset) { + const symbol = useAsset?.symbol; + const decimals = useAsset?.decimals; + const claimedRaw = useAsset?.accumulated; + const unclaimedRaw = useAsset?.unclaimed; + + const claimed = ethers.formatUnits(claimedRaw, decimals); + const unclaimed = ethers.formatUnits(unclaimedRaw, decimals); + + switch (actionType) { + case 'WITHDRAW': + content = ( + <> + + + ); + break; + default: + content = <>; + break; + } + + return <>{content}; + } + + return <>; +}; + +export default TokenActions; diff --git a/src/components/vault/merkl/WithdrawModal.jsx b/src/components/vault/merkl/WithdrawModal.jsx new file mode 100644 index 0000000..4bea66e --- /dev/null +++ b/src/components/vault/merkl/WithdrawModal.jsx @@ -0,0 +1,225 @@ +import { useEffect, useRef, useState } from "react"; +import { ethers } from "ethers"; +import { toast } from 'react-toastify'; +import { + useWriteContract, + useAccount, + useWaitForTransactionReceipt, +} from "wagmi"; +import { + ArrowDownCircleIcon, +} from '@heroicons/react/24/outline'; + +import { + useVaultAddressStore, + useSmartVaultABIStore, +} from "../../../store/Store"; + +import Modal from "../../ui/Modal"; +import Button from "../../ui/Button"; +import Typography from "../../ui/Typography"; +import Input from "../../ui/Input"; + +const WithdrawModal = (props) => { + const { + open, + closeModal, + symbol, + decimals, + claimed, + unclaimed, + vaultType, + } = props; + + const [amount, setAmount] = useState(0n); + + const { vaultAddress } = useVaultAddressStore(); + const { smartVaultABI } = useSmartVaultABIStore(); + + const [ txdata, setTxdata ] = useState(null); + + const { address } = useAccount(); + const inputRef = useRef(null); + + const handleAmount = (e) => { + setAmount(ethers.parseUnits(e.target.value.toString(), decimals)) + }; + + const { writeContract, isError, isPending, isSuccess } = useWriteContract(); + + const handleWithdrawCollateralNative = async () => { + try { + writeContract({ + abi: smartVaultABI, + address: vaultAddress, + functionName: "removeCollateralNative", + args: [ + amount, + address + ], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was an error'); + } + }; + + const handleWithdrawCollateral = async () => { + try { + writeContract({ + abi: smartVaultABI, + address: vaultAddress, + functionName: "removeCollateral", + args: [ + ethers.encodeBytes32String(symbol), + amount, + address + ], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was an error'); + } + }; + + const formatPrevTotal = claimed; + const formatAmount = ethers.formatUnits(amount); + const formatNewTotal = ethers.formatUnits(ethers.parseUnits(formatPrevTotal, decimals) - amount); + + useEffect(() => { + if (isPending) { + // + } else if (isSuccess) { + inputRef.current.value = ""; + inputRef.current.focus(); + setTxdata(txRcptData); + toast.success("Withdraw Successful"); + try { + plausible('CollateralWithdraw', { + props: { + CollateralWithdrawToken: symbol, + CollateralWithdrawAmount: formatAmount, + CollateralWithdrawPreviousTotal: formatPrevTotal, + CollateralWithdrawNewTotal: formatNewTotal, + } + }); + } catch (error) { + console.log(error); + } + } else if (isError) { + inputRef.current.value = ""; + inputRef.current.focus(); + } + }, [ + isPending, + isSuccess, + isError, + ]); + + const shortenAddress = (address) => { + const prefix = address?.slice(0, 6); + const suffix = address?.slice(-8); + return `${prefix}...${suffix}`; + }; + + const shortenedAddress = shortenAddress(address); + + const { + data: txRcptData, + } = useWaitForTransactionReceipt({ + hash: txdata, + }); + + const handleMaxBalance = async () => { + const formatted = claimed; + inputRef.current.value = formatted; + handleAmount({ target: { value: formatted } }); + }; + + return ( + <> + + <> + + + Withdraw {symbol} + + +
+ + Withdraw Amount + + + Available: {claimed || ''} + +
+
+ + + +
+
+ {symbol} to address "{shortenedAddress}" +
+ +
+ + +
+ +
+ + ); +}; + +export default WithdrawModal; \ No newline at end of file diff --git a/src/pages/vault/VaultMerkl.jsx b/src/pages/vault/VaultMerkl.jsx index aa5266b..9436218 100644 --- a/src/pages/vault/VaultMerkl.jsx +++ b/src/pages/vault/VaultMerkl.jsx @@ -3,7 +3,9 @@ import { useNavigate, useParams } from "react-router-dom"; import { useReadContract, useChainId, + useBlockNumber, useWatchBlockNumber, + useReadContracts, } from "wagmi"; import axios from "axios"; @@ -17,6 +19,8 @@ import { useCurrentTheme, useContractAddressStore, useVaultManagerAbiStore, + useVaultAddressStore, + useVaultStore, } from "../../store/Store"; import MerklPoweredDark from "../../assets/merkl-powered-dark.svg"; @@ -30,12 +34,20 @@ import Button from "../../components/ui/Button"; const VaultMerkl = () => { const chainId = useChainId(); - const { arbitrumSepoliaContractAddress, arbitrumContractAddress } = useContractAddressStore(); + const { + arbitrumSepoliaContractAddress, + arbitrumContractAddress + } = useContractAddressStore(); const { vaultManagerAbi } = useVaultManagerAbiStore(); + const { setVaultAddress } = useVaultAddressStore(); + const { vaultStore, setVaultStore } = useVaultStore(); const { vaultType, vaultId } = useParams(); const { currentTheme } = useCurrentTheme(); const navigate = useNavigate(); + const { data: blockNumber } = useBlockNumber(); + const [renderedBlock, setRenderedBlock] = useState(blockNumber); + const [vaultsLoading, setVaultsLoading] = useState(true); const [ merklRewards, setMerklRewards ] = useState({}); const [ merklRewardsLoading, setMerklRewardsLoading ] = useState(true); @@ -67,7 +79,7 @@ const VaultMerkl = () => { Powered By Merkl @@ -162,6 +174,15 @@ const VaultMerkl = () => { const { vaultAddress } = currentVault.status; + if ( + vaultStore.tokenId !== currentVault.tokenId || + blockNumber !== renderedBlock + ) { + setVaultStore(currentVault); + setVaultAddress(vaultAddress); + setRenderedBlock(blockNumber); + } + return (
diff --git a/src/store/Store.jsx b/src/store/Store.jsx index dae70f6..ffbc149 100644 --- a/src/store/Store.jsx +++ b/src/store/Store.jsx @@ -4,6 +4,7 @@ import erc20Abi from "../abis/erc20"; import chainlinkAbi from "../abis/priceFeeds/chainlink"; import smartVaultABI from "../abis/smartVault"; import smartVaultV4ABI from "../abis/smartVaultV4"; +import merklABI from "../abis/merkl"; import stakingAbi from "../abis/staking"; import liquidationPoolAbi from "../abis/liquidationPool"; import stakingPoolv2Abi from "../abis/stakingPoolV2"; @@ -130,6 +131,20 @@ export const useSmartVaultV4ABIStore = create() ( }) ); +export const useMerklABIStore = create() ( + () => ({ + merklABI, + }) +); + +export const useMerklAddressStore = create() ( + (set) => ({ + merklDistributorAddress: "0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae", + setContractAddress: (merklDistributorAddress) => + set(() => ({ merklDistributorAddress: merklDistributorAddress })), + }) +); + export const useContractAddressStore = create() ( (set) => ({ arbitrumContractAddress: "0xba169cceCCF7aC51dA223e04654Cf16ef41A68CC", From 08e9d769dbc641df226538f7827d36e023cecd9d Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 25 Oct 2024 14:37:04 +0100 Subject: [PATCH 03/29] feat: merkl reward withdraw --- src/components/vault/merkl/ClaimModal.jsx | 187 +++++++++++++++++++ src/components/vault/merkl/TokenActions.jsx | 7 +- src/components/vault/merkl/WithdrawModal.jsx | 67 ++----- 3 files changed, 208 insertions(+), 53 deletions(-) create mode 100644 src/components/vault/merkl/ClaimModal.jsx diff --git a/src/components/vault/merkl/ClaimModal.jsx b/src/components/vault/merkl/ClaimModal.jsx new file mode 100644 index 0000000..30860ad --- /dev/null +++ b/src/components/vault/merkl/ClaimModal.jsx @@ -0,0 +1,187 @@ +import { useEffect, useRef, useState } from "react"; +import { ethers } from "ethers"; +import { toast } from 'react-toastify'; +import { + useWriteContract, + useAccount, + useWaitForTransactionReceipt, +} from "wagmi"; +import { + ArrowDownCircleIcon, +} from '@heroicons/react/24/outline'; + +import { + useVaultAddressStore, + useSmartVaultABIStore, +} from "../../../store/Store"; + +import Modal from "../../ui/Modal"; +import Button from "../../ui/Button"; +import Typography from "../../ui/Typography"; +import Input from "../../ui/Input"; + +const ClaimModal = (props) => { + const { + open, + closeModal, + symbol, + decimals, + balance, + } = props; + + const [amount, setAmount] = useState(0n); + + const { vaultAddress } = useVaultAddressStore(); + const { smartVaultABI } = useSmartVaultABIStore(); + + const [ txdata, setTxdata ] = useState(null); + + const { address } = useAccount(); + const inputRef = useRef(null); + + const handleAmount = (e) => { + setAmount(ethers.parseUnits(e.target.value.toString(), decimals)) + }; + + const { writeContract, isError, isPending, isSuccess } = useWriteContract(); + + const handleWithdrawCollateral = async () => { + try { + writeContract({ + abi: smartVaultABI, + address: vaultAddress, + functionName: "removeCollateral", + args: [ + ethers.encodeBytes32String(symbol), + amount, + address + ], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was an error'); + } + }; + + const formatPrevTotal = balance; + const formatAmount = ethers.formatUnits(amount); + const formatNewTotal = ethers.formatUnits(ethers.parseUnits(formatPrevTotal, decimals) - amount); + + useEffect(() => { + if (isPending) { + // + } else if (isSuccess) { + inputRef.current.value = ""; + inputRef.current.focus(); + setTxdata(txRcptData); + toast.success("Withdraw Successful"); + } else if (isError) { + inputRef.current.value = ""; + inputRef.current.focus(); + } + }, [ + isPending, + isSuccess, + isError, + ]); + + const shortenAddress = (address) => { + const prefix = address?.slice(0, 6); + const suffix = address?.slice(-8); + return `${prefix}...${suffix}`; + }; + + const shortenedAddress = shortenAddress(address); + + const { + data: txRcptData, + } = useWaitForTransactionReceipt({ + hash: txdata, + }); + + const handleMaxBalance = async () => { + const formatted = balance; + inputRef.current.value = formatted; + handleAmount({ target: { value: formatted } }); + }; + + return ( + <> + + <> + + + Withdraw {symbol} + + +
+ + Withdraw Amount + + + Available: {balance || ''} + +
+
+ + + +
+
+ {symbol} to address "{shortenedAddress}" +
+ +
+ + +
+ +
+ + ); +}; + +export default ClaimModal; \ No newline at end of file diff --git a/src/components/vault/merkl/TokenActions.jsx b/src/components/vault/merkl/TokenActions.jsx index 63ceb01..932b711 100644 --- a/src/components/vault/merkl/TokenActions.jsx +++ b/src/components/vault/merkl/TokenActions.jsx @@ -13,9 +13,12 @@ const TokenActions = ({ if (useAsset) { const symbol = useAsset?.symbol; const decimals = useAsset?.decimals; + const balanceRaw = useAsset?.balanceOf; const claimedRaw = useAsset?.accumulated; const unclaimedRaw = useAsset?.unclaimed; + const tokenAddress = useAsset?.tokenAddress; + const balance = ethers.formatUnits(balanceRaw, decimals); const claimed = ethers.formatUnits(claimedRaw, decimals); const unclaimed = ethers.formatUnits(unclaimedRaw, decimals); @@ -28,8 +31,8 @@ const TokenActions = ({ closeModal={closeModal} symbol={symbol} decimals={decimals} - claimed={claimed} - unclaimed={unclaimed} + balance={balance} + tokenAddress={tokenAddress} vaultType={vaultType} /> diff --git a/src/components/vault/merkl/WithdrawModal.jsx b/src/components/vault/merkl/WithdrawModal.jsx index 4bea66e..4f0a1d2 100644 --- a/src/components/vault/merkl/WithdrawModal.jsx +++ b/src/components/vault/merkl/WithdrawModal.jsx @@ -26,9 +26,8 @@ const WithdrawModal = (props) => { closeModal, symbol, decimals, - claimed, - unclaimed, - vaultType, + balance, + tokenAddress, } = props; const [amount, setAmount] = useState(0n); @@ -47,15 +46,16 @@ const WithdrawModal = (props) => { const { writeContract, isError, isPending, isSuccess } = useWriteContract(); - const handleWithdrawCollateralNative = async () => { + const handleWithdrawToken = async () => { try { writeContract({ abi: smartVaultABI, address: vaultAddress, - functionName: "removeCollateralNative", + functionName: "removeAsset", args: [ + tokenAddress, amount, - address + address, ], }); } catch (error) { @@ -67,31 +67,6 @@ const WithdrawModal = (props) => { } }; - const handleWithdrawCollateral = async () => { - try { - writeContract({ - abi: smartVaultABI, - address: vaultAddress, - functionName: "removeCollateral", - args: [ - ethers.encodeBytes32String(symbol), - amount, - address - ], - }); - } catch (error) { - let errorMessage = ''; - if (error && error.shortMessage) { - errorMessage = error.shortMessage; - } - toast.error(errorMessage || 'There was an error'); - } - }; - - const formatPrevTotal = claimed; - const formatAmount = ethers.formatUnits(amount); - const formatNewTotal = ethers.formatUnits(ethers.parseUnits(formatPrevTotal, decimals) - amount); - useEffect(() => { if (isPending) { // @@ -100,18 +75,6 @@ const WithdrawModal = (props) => { inputRef.current.focus(); setTxdata(txRcptData); toast.success("Withdraw Successful"); - try { - plausible('CollateralWithdraw', { - props: { - CollateralWithdrawToken: symbol, - CollateralWithdrawAmount: formatAmount, - CollateralWithdrawPreviousTotal: formatPrevTotal, - CollateralWithdrawNewTotal: formatNewTotal, - } - }); - } catch (error) { - console.log(error); - } } else if (isError) { inputRef.current.value = ""; inputRef.current.focus(); @@ -137,7 +100,7 @@ const WithdrawModal = (props) => { }); const handleMaxBalance = async () => { - const formatted = claimed; + const formatted = balance; inputRef.current.value = formatted; handleAmount({ target: { value: formatted } }); }; @@ -153,7 +116,13 @@ const WithdrawModal = (props) => { Withdraw {symbol} - +
+ + This transaction may revert if the token you are withdrawing is being used as collateral and would leave your vault undercollateralised. + +
{ variant="p" className="text-right" > - Available: {claimed || ''} + Available: {balance || ''}
{ className="w-full lg:w-64" color="success" disabled={!amount || isPending} - onClick={ - symbol === "ETH" || symbol === "AGOR" - ? handleWithdrawCollateralNative - : handleWithdrawCollateral - } + onClick={handleWithdrawToken} loading={isPending} wide > From 7da59d126be0d4b0e693ae80ac5c35a326923463 Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 25 Oct 2024 17:09:58 +0100 Subject: [PATCH 04/29] feat: merkl withdraw and claim rewards --- src/components/vault/merkl/ClaimModal.jsx | 178 +++++++++----------- src/components/vault/merkl/RewardItem.jsx | 6 +- src/components/vault/merkl/RewardList.jsx | 48 ++++-- src/components/vault/merkl/TokenActions.jsx | 13 ++ 4 files changed, 131 insertions(+), 114 deletions(-) diff --git a/src/components/vault/merkl/ClaimModal.jsx b/src/components/vault/merkl/ClaimModal.jsx index 30860ad..ead788a 100644 --- a/src/components/vault/merkl/ClaimModal.jsx +++ b/src/components/vault/merkl/ClaimModal.jsx @@ -1,10 +1,8 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { ethers } from "ethers"; import { toast } from 'react-toastify'; import { useWriteContract, - useAccount, - useWaitForTransactionReceipt, } from "wagmi"; import { ArrowDownCircleIcon, @@ -12,49 +10,56 @@ import { import { useVaultAddressStore, - useSmartVaultABIStore, + useMerklAddressStore, + useMerklABIStore, } from "../../../store/Store"; import Modal from "../../ui/Modal"; import Button from "../../ui/Button"; import Typography from "../../ui/Typography"; -import Input from "../../ui/Input"; +import TokenIcon from "../../ui/TokenIcon"; +import CenterLoader from "../../ui/CenterLoader"; const ClaimModal = (props) => { const { open, closeModal, - symbol, - decimals, - balance, + useAssets, + parentLoading, } = props; - const [amount, setAmount] = useState(0n); - + const { merklDistributorAddress } = useMerklAddressStore(); + const { merklABI } = useMerklABIStore(); const { vaultAddress } = useVaultAddressStore(); - const { smartVaultABI } = useSmartVaultABIStore(); - - const [ txdata, setTxdata ] = useState(null); - - const { address } = useAccount(); - const inputRef = useRef(null); - const handleAmount = (e) => { - setAmount(ethers.parseUnits(e.target.value.toString(), decimals)) - }; + const [amount, setAmount] = useState(0n); const { writeContract, isError, isPending, isSuccess } = useWriteContract(); - const handleWithdrawCollateral = async () => { + const claimUsers = useAssets && useAssets.length && useAssets.map(function(asset, index) { + return (vaultAddress) + }); + const claimTokens = useAssets && useAssets.length && useAssets.map(function(asset, index) { + return (asset.tokenAddress) + }); + const claimAmounts = useAssets && useAssets.length && useAssets.map(function(asset, index) { + return (asset.accumulated) + }); + const claimProofs = useAssets && useAssets.length && useAssets.map(function(asset, index) { + return (asset.proof) + }); + + const handleClaimToken = async () => { try { writeContract({ - abi: smartVaultABI, - address: vaultAddress, - functionName: "removeCollateral", + abi: merklABI, + address: merklDistributorAddress, + functionName: "claim", args: [ - ethers.encodeBytes32String(symbol), - amount, - address + claimUsers, + claimTokens, + claimAmounts, + claimProofs ], }); } catch (error) { @@ -66,21 +71,13 @@ const ClaimModal = (props) => { } }; - const formatPrevTotal = balance; - const formatAmount = ethers.formatUnits(amount); - const formatNewTotal = ethers.formatUnits(ethers.parseUnits(formatPrevTotal, decimals) - amount); - useEffect(() => { if (isPending) { // } else if (isSuccess) { - inputRef.current.value = ""; - inputRef.current.focus(); - setTxdata(txRcptData); - toast.success("Withdraw Successful"); + toast.success("Claim Successful"); } else if (isError) { - inputRef.current.value = ""; - inputRef.current.focus(); + toast.error('There was an error'); } }, [ isPending, @@ -88,26 +85,6 @@ const ClaimModal = (props) => { isError, ]); - const shortenAddress = (address) => { - const prefix = address?.slice(0, 6); - const suffix = address?.slice(-8); - return `${prefix}...${suffix}`; - }; - - const shortenedAddress = shortenAddress(address); - - const { - data: txRcptData, - } = useWaitForTransactionReceipt({ - hash: txdata, - }); - - const handleMaxBalance = async () => { - const formatted = balance; - inputRef.current.value = formatted; - handleAmount({ target: { value: formatted } }); - }; - return ( <> { <> - Withdraw {symbol} + Claim Your Rewards -
- - Withdraw Amount - - - Available: {balance || ''} - -
-
- - + Please confirm that you wish to claim the following tokens: + -
-
- {symbol} to address "{shortenedAddress}" -
+ + + + + + + + {parentLoading ? (null) : ( + + {useAssets && useAssets.length && useAssets.map(function(asset, index) { + const symbol = asset?.symbol; + const decimals = asset?.decimals; + const unclaimedRaw = asset?.unclaimed; + + const unclaimed = ethers.formatUnits(unclaimedRaw, decimals); + + return ( + + + + + ) + })} + + )} +
TokenQuantity
+
+
+ +
+
+ {symbol} +
+
+
+ {unclaimed} +
+ {parentLoading ? ( + + ) : (null)}
diff --git a/src/components/vault/merkl/RewardList.jsx b/src/components/vault/merkl/RewardList.jsx index 9c478a1..8dfbd1b 100644 --- a/src/components/vault/merkl/RewardList.jsx +++ b/src/components/vault/merkl/RewardList.jsx @@ -1,45 +1,34 @@ -import { useState, Fragment } from "react"; -import { ethers } from "ethers"; -import { - Tooltip, -} from 'react-daisyui'; +import { useState } from "react"; import { - useAccount, useReadContracts, - useWriteContract, - useChainId, - useWatchBlockNumber, } from "wagmi"; import { - ChevronDownIcon, - ChevronUpIcon, QueueListIcon, } from '@heroicons/react/24/outline'; import { - useVaultStore, useErc20AbiStore, useVaultAddressStore, } from "../../../store/Store"; import Button from "../../ui/Button"; import CenterLoader from "../../ui/CenterLoader"; -import TokenIcon from "../../ui/TokenIcon"; import Typography from "../../ui/Typography"; import TokenActions from "./TokenActions"; import RewardItem from "./RewardItem"; +import ClaimModal from "./ClaimModal"; const RewardList = ({ merklRewards, merklRewardsLoading, vaultType, }) => { - const { vaultStore } = useVaultStore(); const { erc20Abi } = useErc20AbiStore(); const { vaultAddress } = useVaultAddressStore(); + const [claimAllOpen, setClaimAllOpen] = useState(false); const [actionType, setActionType] = useState(); const [useAsset, setUseAsset] = useState(); const [subRow, setSubRow] = useState('0sub'); @@ -66,8 +55,6 @@ const RewardList = ({ } } - const vaultVersion = vaultStore?.status?.version || ''; - const { data: merklBalances, isLoading: merklBalancesLoading } = useReadContracts({ contracts:merklRewards && merklRewards.length && merklRewards.map((item) =>({ address: item.tokenAddress, @@ -92,6 +79,8 @@ const RewardList = ({ } }); + const hasClaims = merklData.find(item => item.unclaimed > 0); + return ( <>
@@ -133,13 +122,38 @@ const RewardList = ({ {merklRewardsLoading || merklBalancesLoading ? ( ) : (null)} + +
+ +
+ setClaimAllOpen(false)} + useAssets={merklData} + vaultType={vaultType} + parentLoading={merklRewardsLoading || merklBalancesLoading} /> + ); }; diff --git a/src/components/vault/merkl/TokenActions.jsx b/src/components/vault/merkl/TokenActions.jsx index 932b711..ed668cf 100644 --- a/src/components/vault/merkl/TokenActions.jsx +++ b/src/components/vault/merkl/TokenActions.jsx @@ -1,6 +1,7 @@ import React from "react"; import { ethers } from "ethers"; import WithdrawModal from "./WithdrawModal"; +import ClaimModal from "./ClaimModal"; const TokenActions = ({ actionType, @@ -38,6 +39,18 @@ const TokenActions = ({ ); break; + case 'CLAIM': + content = ( + <> + + + ); + break; default: content = <>; break; From b644519de45e5914f5867db46e251af87999e06b Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 25 Oct 2024 17:11:37 +0100 Subject: [PATCH 05/29] chore: cleanup unused --- src/components/vault/merkl/ClaimModal.jsx | 4 +--- src/components/vault/merkl/TokenActions.jsx | 4 ---- src/pages/vault/VaultMerkl.jsx | 3 +-- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/components/vault/merkl/ClaimModal.jsx b/src/components/vault/merkl/ClaimModal.jsx index ead788a..f6ba858 100644 --- a/src/components/vault/merkl/ClaimModal.jsx +++ b/src/components/vault/merkl/ClaimModal.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { ethers } from "ethers"; import { toast } from 'react-toastify'; import { @@ -32,8 +32,6 @@ const ClaimModal = (props) => { const { merklABI } = useMerklABIStore(); const { vaultAddress } = useVaultAddressStore(); - const [amount, setAmount] = useState(0n); - const { writeContract, isError, isPending, isSuccess } = useWriteContract(); const claimUsers = useAssets && useAssets.length && useAssets.map(function(asset, index) { diff --git a/src/components/vault/merkl/TokenActions.jsx b/src/components/vault/merkl/TokenActions.jsx index ed668cf..e1b23b1 100644 --- a/src/components/vault/merkl/TokenActions.jsx +++ b/src/components/vault/merkl/TokenActions.jsx @@ -15,13 +15,9 @@ const TokenActions = ({ const symbol = useAsset?.symbol; const decimals = useAsset?.decimals; const balanceRaw = useAsset?.balanceOf; - const claimedRaw = useAsset?.accumulated; - const unclaimedRaw = useAsset?.unclaimed; const tokenAddress = useAsset?.tokenAddress; const balance = ethers.formatUnits(balanceRaw, decimals); - const claimed = ethers.formatUnits(claimedRaw, decimals); - const unclaimed = ethers.formatUnits(unclaimedRaw, decimals); switch (actionType) { case 'WITHDRAW': diff --git a/src/pages/vault/VaultMerkl.jsx b/src/pages/vault/VaultMerkl.jsx index 9436218..955a473 100644 --- a/src/pages/vault/VaultMerkl.jsx +++ b/src/pages/vault/VaultMerkl.jsx @@ -5,7 +5,6 @@ import { useChainId, useBlockNumber, useWatchBlockNumber, - useReadContracts, } from "wagmi"; import axios from "axios"; @@ -54,7 +53,7 @@ const VaultMerkl = () => { const isLight = currentTheme && currentTheme.includes('light'); - const vaultNav = (element) => { + const vaultNav = () => { return (
From 6d589a3169a9afd22f110006c4d9900303bcde0f Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 25 Oct 2024 17:29:56 +0100 Subject: [PATCH 06/29] feat: handle empty data --- src/components/vault/merkl/ClaimModal.jsx | 8 ++-- src/components/vault/merkl/RewardList.jsx | 57 +++++++++++++++-------- src/pages/vault/VaultMerkl.jsx | 5 +- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/components/vault/merkl/ClaimModal.jsx b/src/components/vault/merkl/ClaimModal.jsx index f6ba858..4a868e0 100644 --- a/src/components/vault/merkl/ClaimModal.jsx +++ b/src/components/vault/merkl/ClaimModal.jsx @@ -38,13 +38,13 @@ const ClaimModal = (props) => { return (vaultAddress) }); const claimTokens = useAssets && useAssets.length && useAssets.map(function(asset, index) { - return (asset.tokenAddress) + return (asset?.tokenAddress) }); const claimAmounts = useAssets && useAssets.length && useAssets.map(function(asset, index) { - return (asset.accumulated) + return (asset?.accumulated) }); const claimProofs = useAssets && useAssets.length && useAssets.map(function(asset, index) { - return (asset.proof) + return (asset?.proof) }); const handleClaimToken = async () => { @@ -118,7 +118,7 @@ const ClaimModal = (props) => { const unclaimed = ethers.formatUnits(unclaimedRaw, decimals); return ( - +
diff --git a/src/components/vault/merkl/RewardList.jsx b/src/components/vault/merkl/RewardList.jsx index 8dfbd1b..d961c01 100644 --- a/src/components/vault/merkl/RewardList.jsx +++ b/src/components/vault/merkl/RewardList.jsx @@ -56,15 +56,15 @@ const RewardList = ({ } const { data: merklBalances, isLoading: merklBalancesLoading } = useReadContracts({ - contracts:merklRewards && merklRewards.length && merklRewards.map((item) =>({ - address: item.tokenAddress, + contracts:merklRewards.map((item) =>({ + address: item?.tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [vaultAddress], })) }) - const merklData = merklRewards && merklRewards.length && merklRewards.map((item, index) => { + const merklData = merklRewards.map((item, index) => { let useBalance = 0n; if (merklBalances) { if (merklBalances[index]) { @@ -75,11 +75,13 @@ const RewardList = ({ ...merklRewards[index], balanceOf: useBalance } + } else { + return {}; } } }); - const hasClaims = merklData.find(item => item.unclaimed > 0); + const hasClaims = merklData.find(item => item?.unclaimed > 0); return ( <> @@ -99,22 +101,37 @@ const RewardList = ({ {merklRewardsLoading || merklBalancesLoading ? (null) : ( - {merklData && merklData.length && merklData.map(function(asset, index) { - const handleClick = (type, asset) => { - setActionType(type); - setUseAsset(asset); - }; - - return ( - - )} + {merklData && merklData.length ? ( + <> + {merklData.map(function(asset, index) { + const handleClick = (type, asset) => { + setActionType(type); + setUseAsset(asset); + }; + + return ( + + )} + )} + + ) : ( + <> + + + No Rewards Earned Yet. +
+ Start earning by placing your tokens into the yield pool. + + + )} )} diff --git a/src/pages/vault/VaultMerkl.jsx b/src/pages/vault/VaultMerkl.jsx index 955a473..6f527e0 100644 --- a/src/pages/vault/VaultMerkl.jsx +++ b/src/pages/vault/VaultMerkl.jsx @@ -117,10 +117,12 @@ const VaultMerkl = () => { }, [currentVault, vaultsLoading]); const getMerklRewardsData = async () => { + const { vaultAddress } = currentVault.status; + try { setMerklRewardsLoading(true); const response = await axios.get( - `https://api.merkl.xyz/v3/userRewards?chainId=42161&proof=true&user=0xccD724B9af92A3A389150334556330EfD3f59487` + `https://api.merkl.xyz/v3/userRewards?chainId=42161&proof=true&user=${vaultAddress}` ); const useData = response?.data; @@ -144,7 +146,6 @@ const VaultMerkl = () => { } }; - if (vaultsLoading) { return (
From 1203895bfbc66e386bf76cd09c91d6f69a04aebc Mon Sep 17 00:00:00 2001 From: Zak Date: Tue, 29 Oct 2024 13:43:31 +0000 Subject: [PATCH 07/29] feat: simple dark/light themes --- src/App.css | 94 +++++++++++++++++++++++++++++++ src/components/ui/ThemeToggle.jsx | 18 ++++++ tailwind.config.js | 14 +++++ 3 files changed, 126 insertions(+) diff --git a/src/App.css b/src/App.css index eb4e317..11169d4 100644 --- a/src/App.css +++ b/src/App.css @@ -136,6 +136,62 @@ --bg-image: url(/src/assets/bg-nebula-light.webp); } +*[data-theme='simple-light'], +*[data-theme='simple-dark'] { + --bg-image: none; + --glass-blur: none; + --glass-blur-sm: none; + --primary-color: 86, 23, 222; + --btn-outer-glow: 0 5px 15px 0px rgba(var(--primary-color), 0.0); + --btn-outer-glow-hover: 0 5px 15px 0px rgba(var(--primary-color), 0.4); + --btn-outline-outer-glow: 0 5px 15px 0px rgba(var(--primary-color), 0.0); + --btn-outline-outer-glow-hover: 0 5px 15px 3px rgba(var(--primary-color), 0.4); +} + +*[data-theme='simple-dark'] { + --glass-bg: none; + --glass-inset-color: 255, 255, 255, 0.0; + --glass-inset: none; + --glass-accent: 0, 0, 0; + --input-bg: rgba(var(--glass-accent), 0.4); + --input-bg-disabled: rgba(var(--glass-accent), 0.3); + --table-alt: rgba(var(--glass-accent), 0.2); + --glass-lines: rgba(var(--glass-accent), 0.2); + --alt-btn-txt-color: #ffffff; + + --btn-solid-outline-hover: inset 0px 0px 0px 1px rgba(255, 255, 255, 0.3); + --btn-inner-highlight: none; + --btn-inner-highlight-hover: inset 0px 0px 20px 1px rgba(255, 255, 255, 0.05); + + --btn-outline-inner-highlight: inset 0px 0px 20px 1px rgba(255, 255, 255, 0.05); + --btn-outline-inner-highlight-hover: inset 0px 0px 20px 1px rgba(255, 255, 255, 0.1); +} + +*[data-theme='simple-light'] { + --glass-bg: none; + --glass-inset-color: 255, 255, 255, 0.0; + --glass-inset: none; + --glass-accent: 0, 0, 0; + --input-bg: rgba(var(--glass-accent), 0.05); + --input-bg-disabled: rgba(var(--glass-accent), 0.3); + --table-alt: rgba(var(--glass-accent), 0.05); + --glass-lines: rgba(var(--glass-accent), 0.1); + --alt-btn-txt-color: #000000; + + --btn-edge-highlight-hover: + inset 1px 1px 0px 0px rgba(255, 255, 255, 0.1), + inset -1px -1px 0px 0px rgba(255, 255, 255, 0.1); + --btn-outer-shadow: + 0 4px 6px -1px rgb(0 0 0 / 0.3), + 0 2px 4px -2px rgb(0 0 0 / 0.3); + --btn-outer-shadow-hover: + 0 4px 6px -1px rgb(0 0 0 / 0.3), + 0 2px 4px -2px rgb(0 0 0 / 0.3); + --btn-outline-edge-highlight: + inset 1px 1px 0px 0px rgba(0, 0, 0, 0.1), + inset -1px -1px 0px 0px rgba(0, 0, 0, 0.1); +} + .glass-alt-bg { background: var(--table-alt); } @@ -152,6 +208,14 @@ overflow: auto; } +.tst-bg[data-theme='simple-dark'] { + background-color: rgb(25, 30, 36); +} + +.tst-bg[data-theme='simple-light'] { + background-color: rgb(242, 242, 242); +} + .tst-bg:before { content: ""; position: fixed; @@ -214,6 +278,13 @@ justify-content: end; } +*[data-theme='simple-dark'] .tst-topnav { + background-color: rgb(29, 35, 42); +} +*[data-theme='simple-light'] .tst-topnav { + background-color: rgb(255, 255, 255); +} + .tst-topnav::before { content: ""; position: absolute; @@ -252,6 +323,13 @@ position: relative; } +*[data-theme='simple-dark'] .tst-footer { + background-color: rgb(29, 35, 42); +} +*[data-theme='simple-light'] .tst-footer { + background-color: rgb(255, 255, 255); +} + .tst-footer::before { content: ""; position: absolute; @@ -285,6 +363,13 @@ position: relative; } +*[data-theme='simple-dark'] .tst-sidenav { + background-color: rgb(29, 35, 42); +} +*[data-theme='simple-light'] .tst-sidenav { + background-color: rgb(255, 255, 255); +} + .tst-sidenav::before { content: ""; position: absolute; @@ -410,6 +495,15 @@ position: relative; } +*[data-theme='simple-dark'] .tst-card { + background-color: rgb(29, 35, 42); + border-radius: var(--card-border-radius); +} +*[data-theme='simple-light'] .tst-card { + background-color: rgb(255, 255, 255); + border-radius: var(--card-border-radius); +} + .tst-card::before { content: ""; position: absolute; diff --git a/src/components/ui/ThemeToggle.jsx b/src/components/ui/ThemeToggle.jsx index 969b7d3..4adb54f 100644 --- a/src/components/ui/ThemeToggle.jsx +++ b/src/components/ui/ThemeToggle.jsx @@ -80,6 +80,24 @@ const ThemeToggle = (props) => { Nebula Light + +
diff --git a/tailwind.config.js b/tailwind.config.js index cff205b..b1c9823 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -45,6 +45,20 @@ export default { 'base-content' : 'rgba(255,255,255,0.8)', }, }, + { + 'simple-light': { + ...require("daisyui/src/theming/themes")["light"], + 'primary' : '#5617de', + 'base-content' : 'rgba(0,0,0,0.8)', + }, + }, + { + 'simple-dark': { + ...require("daisyui/src/theming/themes")["dark"], + 'primary' : '#5617de', + 'base-content' : 'rgba(255,255,255,0.8)', + }, + }, ], }, } \ No newline at end of file From ddfe52b1cf75b5ff8337efed9e37bfe1578ebca6 Mon Sep 17 00:00:00 2001 From: Zak Date: Tue, 29 Oct 2024 13:59:47 +0000 Subject: [PATCH 08/29] feat: improved error handling on deposit and withdraw --- src/components/vault/DepositModal.jsx | 7 +++++++ src/components/vault/WithdrawModal.jsx | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/src/components/vault/DepositModal.jsx b/src/components/vault/DepositModal.jsx index 92e735b..769e3e2 100644 --- a/src/components/vault/DepositModal.jsx +++ b/src/components/vault/DepositModal.jsx @@ -145,6 +145,7 @@ const DepositModal = (props) => { setTxdata(hash); inputRef.current.value = ""; inputRef.current.focus(); + setAmount(0n); setEthPending(false); setEthSuccess(true); handleDepositSuccess(); @@ -157,6 +158,9 @@ const DepositModal = (props) => { errorMessage = error.shortMessage; } toast.error(errorMessage || 'There was a problem'); + inputRef.current.value = ""; + inputRef.current.focus(); + setAmount(0n); } }; @@ -179,6 +183,7 @@ const DepositModal = (props) => { toast.error(errorMessage || 'There was a problem'); inputRef.current.value = ""; inputRef.current.focus(); + setAmount(0n); } } }; @@ -197,9 +202,11 @@ const DepositModal = (props) => { inputRef.current.focus(); handleDepositSuccess(); setTxdata(txRcptData); + setAmount(0n); } else if (isError) { inputRef.current.value = ""; inputRef.current.focus(); + setAmount(0n); } }, [ isPending, diff --git a/src/components/vault/WithdrawModal.jsx b/src/components/vault/WithdrawModal.jsx index 73764bb..ec1d026 100644 --- a/src/components/vault/WithdrawModal.jsx +++ b/src/components/vault/WithdrawModal.jsx @@ -98,6 +98,8 @@ const WithdrawModal = (props) => { inputRef.current.focus(); setTxdata(txRcptData); toast.success("Withdraw Successful"); + setAmount(0n); + closeModal(); try { plausible('CollateralWithdraw', { props: { @@ -113,6 +115,8 @@ const WithdrawModal = (props) => { } else if (isError) { inputRef.current.value = ""; inputRef.current.focus(); + toast.error('There was an error'); + setAmount(0n); } }, [ isPending, From 789e30ae8b3722d5d232c773116052e455908f25 Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 31 Oct 2024 13:34:45 +0000 Subject: [PATCH 09/29] feat: add more explanation to yield ratio explanation --- .../vault/yield/YieldDepositModal.jsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/vault/yield/YieldDepositModal.jsx b/src/components/vault/yield/YieldDepositModal.jsx index 26b1acb..f0322d6 100644 --- a/src/components/vault/yield/YieldDepositModal.jsx +++ b/src/components/vault/yield/YieldDepositModal.jsx @@ -161,18 +161,33 @@ const YieldDepositModal = (props) => { > - Choose Stable Ratio + Choose Your Allocation + + + + Balancing Stability & Growth Potential - Choose how much volatile collateral you want to use to earn a yield, and what percentage you would like in safer, correlated, stable asset yield strategies. -
- Recommended 10% stable. + Volatile pools (like {assetYield.pair[0]}/{assetYield.pair[1]}) can unlock high returns from UNISWAP trading fees, though short-term price shifts may temporarily impact returns. Over time, fees often outpace impermanent loss (IL) fluctuations, leading to positive yields.
+ + We require at least 10% of your collateral in the correlated stable pool (USDs/USDC). Stable pools provide consistent, lower-risk yields, shielding a portion of your assets from the effects of impermanent loss. You can increase this allocation for even more stability. + + + + By choosing your mix, you're setting up for both the potential rewards of volatility and the steady benefits of stability. +
From 4673d0e02308ca3060b11f24957d53e663bcfa5a Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 1 Nov 2024 13:59:25 +0000 Subject: [PATCH 10/29] feat: seperated modular theme and darkmods --- src/components/ThemeHandler.jsx | 85 +++++++++++++++--- src/components/ui/SideNav.jsx | 5 +- src/components/ui/ThemeSettings.jsx | 133 ++++++++++++++++++++++++++++ src/components/ui/TopNav.jsx | 4 +- src/store/Store.jsx | 19 ++++ 5 files changed, 230 insertions(+), 16 deletions(-) create mode 100644 src/components/ui/ThemeSettings.jsx diff --git a/src/components/ThemeHandler.jsx b/src/components/ThemeHandler.jsx index 081c2d9..3f40a02 100644 --- a/src/components/ThemeHandler.jsx +++ b/src/components/ThemeHandler.jsx @@ -5,32 +5,95 @@ import { import { useCurrentTheme, + useLocalThemeStore, + useLocalModeStore, + useLocalModePrefStore, } from "../store/Store"; const ThemeHandler = ({children}) => { const { currentTheme, setCurrentTheme } = useCurrentTheme(); + const { localThemeStore, setLocalThemeStore } = useLocalThemeStore(); + const { localModeStore, setLocalModeStore } = useLocalModeStore(); + const { localModePrefStore, setLocalModePrefStore } = useLocalModePrefStore(); + const localTheme = localStorage.getItem('theme'); + const localModePref = localStorage.getItem('modePref'); useEffect(() => { + let useTheme = 'deluxe'; if (localTheme) { - setCurrentTheme(localTheme); - } else { - if ( - window.matchMedia && - window.matchMedia('(prefers-color-scheme: dark)').matches - ) { - localStorage.setItem("theme", "deluxe-dark"); - setCurrentTheme("deluxe-dark"); + if (localTheme.includes('deluxe')) { + useTheme = 'deluxe'; + } else + if (localTheme.includes('nebula')) { + useTheme = 'nebula'; + } else + if (localTheme.includes('simple')) { + useTheme = 'simple'; } else { - localStorage.setItem("theme", "deluxe-light"); - setCurrentTheme("deluxe-light"); + useTheme = 'deluxe'; } } + setLocalThemeStore(useTheme); + + let useModePref = 'device'; + if (localModePref) { + useModePref = localModePref; + } + setLocalModePrefStore(useModePref); }, []) + useEffect(() => { + localStorage.setItem('theme', localThemeStore) + }, [localThemeStore]) + + useEffect(() => { + localStorage.setItem('modePref', localModePrefStore) + handleDarkMode(); + }, [localModePrefStore]) + + // const handleDarkMode = () => { + // if (localModePref) { + // hasLocalModePref(); + // } else { + // useDeviceAppearance(); + // } + // } + + const handleDarkMode = () => { + switch(localModePrefStore){ + case 'dark': + setLocalModeStore('dark'); + break; + case 'light': + setLocalModeStore('light'); + break; + case 'device': + useDeviceAppearance(); + break; + default: + useDeviceAppearance(); + break; + } + } + + const useDeviceAppearance = () => { + if (window.matchMedia) { + if(window.matchMedia('(prefers-color-scheme: dark)').matches){ + setLocalModeStore('dark'); + } else { + setLocalModeStore('light'); + } + } else { + setLocalModeStore('dark'); + } + } + + const useDaisyTheme = `${localThemeStore}-${localModeStore}`; + return ( - + {children} ) diff --git a/src/components/ui/SideNav.jsx b/src/components/ui/SideNav.jsx index a1b8043..2105efe 100644 --- a/src/components/ui/SideNav.jsx +++ b/src/components/ui/SideNav.jsx @@ -19,7 +19,7 @@ import StandardioLogoWhite from "../../assets/standardiologo-white.svg"; import StandardioLogoBlack from "../../assets/standardiologo-black.svg"; import Button from "./Button"; -import ThemeToggle from "./ThemeToggle"; +import ThemeSettings from "./ThemeSettings"; const SideNav = (props) => { const { toggleVisible } = props; @@ -43,7 +43,6 @@ const SideNav = (props) => { alt="TheStandard.io Logo" className="h-5" /> - {/* {import.meta.env.VITE_COMPANY_DAPP_NAME || ''} */}
- +
); diff --git a/src/components/ui/ThemeSettings.jsx b/src/components/ui/ThemeSettings.jsx new file mode 100644 index 0000000..81892e0 --- /dev/null +++ b/src/components/ui/ThemeSettings.jsx @@ -0,0 +1,133 @@ +import { useEffect, useRef, useState } from "react"; + +import { + useCurrentTheme, + useLocalThemeStore, + useLocalModeStore, + useLocalModePrefStore, +} from "../../store/Store"; + +import Modal from "./Modal"; +import Button from "./Button"; +import Typography from "./Typography"; +import Select from "./Select"; + +import { + SunIcon, + MoonIcon, +} from '@heroicons/react/24/solid'; + +const allThemes = [ + { + name: 'Deluxe', + value: 'deluxe', + }, + { + name: 'Nebula', + value: 'nebula', + }, + { + name: 'Simple', + value: 'simple', + }, +]; + +const allModes = [ + { + name: 'Use Device Settings', + value: 'device', + }, + { + name: 'Dark', + value: 'dark', + }, + { + name: 'Light', + value: 'light', + }, +]; + +const ThemeToggle = (props) => { + const [isOpen, setIsOpen] = useState(false); + const { setCurrentTheme } = useCurrentTheme(); + + const { localThemeStore, setLocalThemeStore } = useLocalThemeStore(); + const { localModeStore } = useLocalModeStore(); + const { localModePrefStore, setLocalModePrefStore } = useLocalModePrefStore(); + + const chosenTheme = localThemeStore; + + const chosenModePref = localModePrefStore; + + const isLight = localModeStore && localModeStore.includes('light'); + + const handleThemeChange = (e) => { + setLocalThemeStore(e.target.value) + } + const handleModePrefChange = (e) => { + setLocalModePrefStore(e.target.value) + } + + return ( + <> + + { + setIsOpen(false); + }} + > +
+ + Theme: + + +
+
+ + Dark/Light Mode: + + +
+
+ + ) + +}; + +export default ThemeToggle; \ No newline at end of file diff --git a/src/components/ui/TopNav.jsx b/src/components/ui/TopNav.jsx index afe6859..f01083c 100644 --- a/src/components/ui/TopNav.jsx +++ b/src/components/ui/TopNav.jsx @@ -10,7 +10,7 @@ import StandardioLogoWhite from "../../assets/standardiologo-white.svg"; import StandardioLogoBlack from "../../assets/standardiologo-black.svg"; import Button from "./Button"; -import ThemeToggle from "./ThemeToggle"; +import ThemeSettings from "./ThemeSettings"; import Notifications from "./Notifications"; import RainbowConnect from "../RainbowConnectButton"; @@ -48,7 +48,7 @@ const TopNav = (props) => {
- +
{/* */} diff --git a/src/store/Store.jsx b/src/store/Store.jsx index ffbc149..d7f2021 100644 --- a/src/store/Store.jsx +++ b/src/store/Store.jsx @@ -258,3 +258,22 @@ export const usesUSDVaultListPageStore = create( setCurrentsUSDPage: (currentsUSDPage) => set(() => ({ currentsUSDPage: currentsUSDPage })), }) ); + +export const useLocalThemeStore = create( + (set) => ({ + localThemeStore: 'deluxe', + setLocalThemeStore: (localThemeStore) => set(() => ({ localThemeStore: localThemeStore })), + }) +); +export const useLocalModeStore = create( + (set) => ({ + localModeStore: 'dark', + setLocalModeStore: (localModeStore) => set(() => ({ localModeStore: localModeStore })), + }) +); +export const useLocalModePrefStore = create( + (set) => ({ + localModePrefStore: 'device', + setLocalModePrefStore: (localModePrefStore) => set(() => ({ localModePrefStore: localModePrefStore })), + }) +); \ No newline at end of file From c21bde12c26ddf232134b7b8acb13f5ab42c56a2 Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 1 Nov 2024 14:08:57 +0000 Subject: [PATCH 11/29] feat: move modal to top layer --- src/components/ThemeHandler.jsx | 11 +-- src/components/ui/SideNav.jsx | 4 +- src/components/ui/ThemeButton.jsx | 39 +++++++++ src/components/ui/ThemeSettings.jsx | 37 ++------- src/components/ui/ThemeToggle.jsx | 122 ---------------------------- src/components/ui/TopNav.jsx | 6 +- src/store/Store.jsx | 6 ++ 7 files changed, 59 insertions(+), 166 deletions(-) create mode 100644 src/components/ui/ThemeButton.jsx delete mode 100644 src/components/ui/ThemeToggle.jsx diff --git a/src/components/ThemeHandler.jsx b/src/components/ThemeHandler.jsx index 3f40a02..83e8d22 100644 --- a/src/components/ThemeHandler.jsx +++ b/src/components/ThemeHandler.jsx @@ -10,6 +10,8 @@ import { useLocalModePrefStore, } from "../store/Store"; +import ThemeSettings from "./ui/ThemeSettings"; + const ThemeHandler = ({children}) => { const { currentTheme, setCurrentTheme } = useCurrentTheme(); @@ -53,14 +55,6 @@ const ThemeHandler = ({children}) => { handleDarkMode(); }, [localModePrefStore]) - // const handleDarkMode = () => { - // if (localModePref) { - // hasLocalModePref(); - // } else { - // useDeviceAppearance(); - // } - // } - const handleDarkMode = () => { switch(localModePrefStore){ case 'dark': @@ -95,6 +89,7 @@ const ThemeHandler = ({children}) => { return ( {children} + ) }; diff --git a/src/components/ui/SideNav.jsx b/src/components/ui/SideNav.jsx index 2105efe..1e10cdf 100644 --- a/src/components/ui/SideNav.jsx +++ b/src/components/ui/SideNav.jsx @@ -19,7 +19,7 @@ import StandardioLogoWhite from "../../assets/standardiologo-white.svg"; import StandardioLogoBlack from "../../assets/standardiologo-black.svg"; import Button from "./Button"; -import ThemeSettings from "./ThemeSettings"; +import ThemeButton from "./ThemeButton"; const SideNav = (props) => { const { toggleVisible } = props; @@ -193,7 +193,7 @@ const SideNav = (props) => {
- +
); diff --git a/src/components/ui/ThemeButton.jsx b/src/components/ui/ThemeButton.jsx new file mode 100644 index 0000000..f2a3546 --- /dev/null +++ b/src/components/ui/ThemeButton.jsx @@ -0,0 +1,39 @@ +import { + useThemeSettingsOpenStore, + useLocalModeStore, +} from "../../store/Store"; + +import Button from "./Button"; + +import { + SunIcon, + MoonIcon, +} from '@heroicons/react/24/solid'; + +const ThemeButton = () => { + const { setThemeSettingsOpenStore } = useThemeSettingsOpenStore(); + + const { localModeStore } = useLocalModeStore(); + + const isLight = localModeStore && localModeStore.includes('light'); + + return ( + <> + + + ) + +}; + +export default ThemeButton; \ No newline at end of file diff --git a/src/components/ui/ThemeSettings.jsx b/src/components/ui/ThemeSettings.jsx index 81892e0..8f745ee 100644 --- a/src/components/ui/ThemeSettings.jsx +++ b/src/components/ui/ThemeSettings.jsx @@ -1,22 +1,13 @@ -import { useEffect, useRef, useState } from "react"; - import { - useCurrentTheme, + useThemeSettingsOpenStore, useLocalThemeStore, - useLocalModeStore, useLocalModePrefStore, } from "../../store/Store"; import Modal from "./Modal"; -import Button from "./Button"; import Typography from "./Typography"; import Select from "./Select"; -import { - SunIcon, - MoonIcon, -} from '@heroicons/react/24/solid'; - const allThemes = [ { name: 'Deluxe', @@ -47,20 +38,16 @@ const allModes = [ }, ]; -const ThemeToggle = (props) => { - const [isOpen, setIsOpen] = useState(false); - const { setCurrentTheme } = useCurrentTheme(); +const ThemeSettings = () => { + const { themeSettingsOpenStore, setThemeSettingsOpenStore } = useThemeSettingsOpenStore(); const { localThemeStore, setLocalThemeStore } = useLocalThemeStore(); - const { localModeStore } = useLocalModeStore(); const { localModePrefStore, setLocalModePrefStore } = useLocalModePrefStore(); const chosenTheme = localThemeStore; const chosenModePref = localModePrefStore; - const isLight = localModeStore && localModeStore.includes('light'); - const handleThemeChange = (e) => { setLocalThemeStore(e.target.value) } @@ -70,22 +57,10 @@ const ThemeToggle = (props) => { return ( <> - { - setIsOpen(false); + setThemeSettingsOpenStore(false); }} >
@@ -130,4 +105,4 @@ const ThemeToggle = (props) => { }; -export default ThemeToggle; \ No newline at end of file +export default ThemeSettings; \ No newline at end of file diff --git a/src/components/ui/ThemeToggle.jsx b/src/components/ui/ThemeToggle.jsx deleted file mode 100644 index 4adb54f..0000000 --- a/src/components/ui/ThemeToggle.jsx +++ /dev/null @@ -1,122 +0,0 @@ -import { - useCurrentTheme, -} from "../../store/Store"; -import Button from "./Button"; - -import { - SunIcon, - MoonIcon, -} from '@heroicons/react/24/solid'; - -const ThemeToggle = (props) => { - const classes = props.className || ''; - const buttonType = props.buttonType || 'square'; - const { setCurrentTheme } = useCurrentTheme(); - - const chosenTheme = localStorage.getItem('theme'); - - const setTheme = (theme) => { - saveTheme(theme); - } - - const saveTheme = (newTheme) => { - localStorage.setItem("theme", newTheme); - setCurrentTheme(newTheme); - } - - const isLight = chosenTheme && chosenTheme.includes('light'); - - return ( -
- -
-
- - - - - - -
-
-
- ) - - // return ( - // - // ); -}; - -export default ThemeToggle; \ No newline at end of file diff --git a/src/components/ui/TopNav.jsx b/src/components/ui/TopNav.jsx index f01083c..19724e4 100644 --- a/src/components/ui/TopNav.jsx +++ b/src/components/ui/TopNav.jsx @@ -10,8 +10,8 @@ import StandardioLogoWhite from "../../assets/standardiologo-white.svg"; import StandardioLogoBlack from "../../assets/standardiologo-black.svg"; import Button from "./Button"; -import ThemeSettings from "./ThemeSettings"; -import Notifications from "./Notifications"; +import ThemeButton from "./ThemeButton"; +// import Notifications from "./Notifications"; import RainbowConnect from "../RainbowConnectButton"; @@ -48,7 +48,7 @@ const TopNav = (props) => {
- +
{/* */} diff --git a/src/store/Store.jsx b/src/store/Store.jsx index d7f2021..03c0364 100644 --- a/src/store/Store.jsx +++ b/src/store/Store.jsx @@ -259,6 +259,12 @@ export const usesUSDVaultListPageStore = create( }) ); +export const useThemeSettingsOpenStore = create( + (set) => ({ + themeSettingsOpenStore: false, + setThemeSettingsOpenStore: (themeSettingsOpenStore) => set(() => ({ themeSettingsOpenStore: themeSettingsOpenStore })), + }) +); export const useLocalThemeStore = create( (set) => ({ localThemeStore: 'deluxe', From 255c2336fd430872db90ec77e776819cdb72f4c8 Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 1 Nov 2024 14:11:47 +0000 Subject: [PATCH 12/29] feat: rename theme vars --- src/components/ThemeHandler.jsx | 31 +++++++++++++---------------- src/components/ui/ThemeButton.jsx | 6 +++--- src/components/ui/ThemeSettings.jsx | 8 ++++---- src/store/Store.jsx | 12 +++++------ 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/components/ThemeHandler.jsx b/src/components/ThemeHandler.jsx index 83e8d22..019dd0f 100644 --- a/src/components/ThemeHandler.jsx +++ b/src/components/ThemeHandler.jsx @@ -4,20 +4,17 @@ import { } from 'react-daisyui'; import { - useCurrentTheme, useLocalThemeStore, - useLocalModeStore, - useLocalModePrefStore, + useLocalThemeModeStore, + useLocalThemeModePrefStore, } from "../store/Store"; import ThemeSettings from "./ui/ThemeSettings"; const ThemeHandler = ({children}) => { - const { currentTheme, setCurrentTheme } = useCurrentTheme(); - const { localThemeStore, setLocalThemeStore } = useLocalThemeStore(); - const { localModeStore, setLocalModeStore } = useLocalModeStore(); - const { localModePrefStore, setLocalModePrefStore } = useLocalModePrefStore(); + const { localThemeModeStore, setLocalThemeModeStore } = useLocalThemeModeStore(); + const { localThemeModePrefStore, setLocalThemeModePrefStore } = useLocalThemeModePrefStore(); const localTheme = localStorage.getItem('theme'); const localModePref = localStorage.getItem('modePref'); @@ -43,7 +40,7 @@ const ThemeHandler = ({children}) => { if (localModePref) { useModePref = localModePref; } - setLocalModePrefStore(useModePref); + setLocalThemeModePrefStore(useModePref); }, []) useEffect(() => { @@ -51,17 +48,17 @@ const ThemeHandler = ({children}) => { }, [localThemeStore]) useEffect(() => { - localStorage.setItem('modePref', localModePrefStore) + localStorage.setItem('modePref', localThemeModePrefStore) handleDarkMode(); - }, [localModePrefStore]) + }, [localThemeModePrefStore]) const handleDarkMode = () => { - switch(localModePrefStore){ + switch(localThemeModePrefStore){ case 'dark': - setLocalModeStore('dark'); + setLocalThemeModeStore('dark'); break; case 'light': - setLocalModeStore('light'); + setLocalThemeModeStore('light'); break; case 'device': useDeviceAppearance(); @@ -75,16 +72,16 @@ const ThemeHandler = ({children}) => { const useDeviceAppearance = () => { if (window.matchMedia) { if(window.matchMedia('(prefers-color-scheme: dark)').matches){ - setLocalModeStore('dark'); + setLocalThemeModeStore('dark'); } else { - setLocalModeStore('light'); + setLocalThemeModeStore('light'); } } else { - setLocalModeStore('dark'); + setLocalThemeModeStore('dark'); } } - const useDaisyTheme = `${localThemeStore}-${localModeStore}`; + const useDaisyTheme = `${localThemeStore}-${localThemeModeStore}`; return ( diff --git a/src/components/ui/ThemeButton.jsx b/src/components/ui/ThemeButton.jsx index f2a3546..0fb3e6b 100644 --- a/src/components/ui/ThemeButton.jsx +++ b/src/components/ui/ThemeButton.jsx @@ -1,6 +1,6 @@ import { useThemeSettingsOpenStore, - useLocalModeStore, + useLocalThemeModeStore, } from "../../store/Store"; import Button from "./Button"; @@ -13,9 +13,9 @@ import { const ThemeButton = () => { const { setThemeSettingsOpenStore } = useThemeSettingsOpenStore(); - const { localModeStore } = useLocalModeStore(); + const { localThemeModeStore } = useLocalThemeModeStore(); - const isLight = localModeStore && localModeStore.includes('light'); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); return ( <> diff --git a/src/components/ui/ThemeSettings.jsx b/src/components/ui/ThemeSettings.jsx index 8f745ee..5070905 100644 --- a/src/components/ui/ThemeSettings.jsx +++ b/src/components/ui/ThemeSettings.jsx @@ -1,7 +1,7 @@ import { useThemeSettingsOpenStore, useLocalThemeStore, - useLocalModePrefStore, + useLocalThemeModePrefStore, } from "../../store/Store"; import Modal from "./Modal"; @@ -42,17 +42,17 @@ const ThemeSettings = () => { const { themeSettingsOpenStore, setThemeSettingsOpenStore } = useThemeSettingsOpenStore(); const { localThemeStore, setLocalThemeStore } = useLocalThemeStore(); - const { localModePrefStore, setLocalModePrefStore } = useLocalModePrefStore(); + const { localThemeModePrefStore, setLocalThemeModePrefStore } = useLocalThemeModePrefStore(); const chosenTheme = localThemeStore; - const chosenModePref = localModePrefStore; + const chosenModePref = localThemeModePrefStore; const handleThemeChange = (e) => { setLocalThemeStore(e.target.value) } const handleModePrefChange = (e) => { - setLocalModePrefStore(e.target.value) + setLocalThemeModePrefStore(e.target.value) } return ( diff --git a/src/store/Store.jsx b/src/store/Store.jsx index 03c0364..7e74d7c 100644 --- a/src/store/Store.jsx +++ b/src/store/Store.jsx @@ -271,15 +271,15 @@ export const useLocalThemeStore = create( setLocalThemeStore: (localThemeStore) => set(() => ({ localThemeStore: localThemeStore })), }) ); -export const useLocalModeStore = create( +export const useLocalThemeModeStore = create( (set) => ({ - localModeStore: 'dark', - setLocalModeStore: (localModeStore) => set(() => ({ localModeStore: localModeStore })), + localThemeModeStore: 'dark', + setLocalThemeModeStore: (localThemeModeStore) => set(() => ({ localThemeModeStore: localThemeModeStore })), }) ); -export const useLocalModePrefStore = create( +export const useLocalThemeModePrefStore = create( (set) => ({ - localModePrefStore: 'device', - setLocalModePrefStore: (localModePrefStore) => set(() => ({ localModePrefStore: localModePrefStore })), + localThemeModePrefStore: 'device', + setLocalThemeModePrefStore: (localThemeModePrefStore) => set(() => ({ localThemeModePrefStore: localThemeModePrefStore })), }) ); \ No newline at end of file From 486c4f04871f384d957e56f0001ca5ec52a1edf6 Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 1 Nov 2024 14:15:59 +0000 Subject: [PATCH 13/29] feat: replace old custom theme logic for dark/light tweaks --- src/components/WalletProvider.jsx | 6 +++--- src/components/ui/DashLayout.jsx | 6 +++--- src/components/ui/Footer.jsx | 6 +++--- src/components/ui/Notifications.jsx | 6 +++--- src/components/ui/SideNav.jsx | 6 +++--- src/components/ui/TopNav.jsx | 6 +++--- src/store/Store.jsx | 7 ------- 7 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/components/WalletProvider.jsx b/src/components/WalletProvider.jsx index 8de3726..c568449 100644 --- a/src/components/WalletProvider.jsx +++ b/src/components/WalletProvider.jsx @@ -10,7 +10,7 @@ import { import '@rainbow-me/rainbowkit/styles.css'; import { - useCurrentTheme, + useLocalThemeModeStore, useCurrentWagmiConfig } from "../store/Store"; @@ -19,9 +19,9 @@ import CenterLoader from "./ui/CenterLoader"; const projectId = import.meta.env.VITE_WALLETCONNECT_ID; const WalletProvider = ({ children }) => { - const { currentTheme } = useCurrentTheme(); + const { localThemeModeStore } = useLocalThemeModeStore(); const { setCurrentWagmiConfig } = useCurrentWagmiConfig(); - const isLight = currentTheme && currentTheme.includes('light'); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); const { chains, isLoading } = useAvailableChains(); diff --git a/src/components/ui/DashLayout.jsx b/src/components/ui/DashLayout.jsx index 4a9bcf7..423d727 100644 --- a/src/components/ui/DashLayout.jsx +++ b/src/components/ui/DashLayout.jsx @@ -5,7 +5,7 @@ import { } from 'react-daisyui'; import { - useCurrentTheme, + useLocalThemeModeStore, } from "../../store/Store"; import { @@ -32,8 +32,8 @@ const DashLayout = ({children}) => { setShowSideNav(visible => !visible); }, []); - const { currentTheme } = useCurrentTheme(); - const isLight = currentTheme && currentTheme.includes('light'); + const { localThemeModeStore } = useLocalThemeModeStore(); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); return (
diff --git a/src/components/ui/Footer.jsx b/src/components/ui/Footer.jsx index e94aced..fd8db12 100644 --- a/src/components/ui/Footer.jsx +++ b/src/components/ui/Footer.jsx @@ -1,5 +1,5 @@ import { - useCurrentTheme, + useLocalThemeModeStore, } from "../../store/Store"; import Button from "./Button"; @@ -53,8 +53,8 @@ const icons = [ ]; const Footer = (props) => { - const { currentTheme } = useCurrentTheme(); - const isLight = currentTheme && currentTheme.includes('light'); + const { localThemeModeStore } = useLocalThemeModeStore(); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); return (
diff --git a/src/components/ui/Notifications.jsx b/src/components/ui/Notifications.jsx index 86b7bff..c9948e1 100644 --- a/src/components/ui/Notifications.jsx +++ b/src/components/ui/Notifications.jsx @@ -2,7 +2,7 @@ import { useState } from "react"; import moment from 'moment'; import { - useCurrentTheme, + useLocalThemeModeStore, } from "../../store/Store"; import { @@ -20,8 +20,8 @@ const notifDate = '20240722'; const Notifications = (props) => { const [isOpen, setIsOpen] = useState(false); - const { currentTheme } = useCurrentTheme(); - const isLight = currentTheme && currentTheme.includes('light'); + const { localThemeModeStore } = useLocalThemeModeStore(); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); const notifRead = localStorage.getItem("notifRead"); diff --git a/src/components/ui/SideNav.jsx b/src/components/ui/SideNav.jsx index 1e10cdf..6151933 100644 --- a/src/components/ui/SideNav.jsx +++ b/src/components/ui/SideNav.jsx @@ -12,7 +12,7 @@ import { } from '@heroicons/react/24/outline'; import { - useCurrentTheme, + useLocalThemeModeStore, } from "../../store/Store"; import StandardioLogoWhite from "../../assets/standardiologo-white.svg"; @@ -25,9 +25,9 @@ const SideNav = (props) => { const { toggleVisible } = props; const location = useLocation(); const navigate = useNavigate(); - const { currentTheme } = useCurrentTheme(); + const { localThemeModeStore } = useLocalThemeModeStore(); - const isLight = currentTheme && currentTheme.includes('light'); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); return ( diff --git a/src/components/ui/TopNav.jsx b/src/components/ui/TopNav.jsx index 19724e4..0b5a64f 100644 --- a/src/components/ui/TopNav.jsx +++ b/src/components/ui/TopNav.jsx @@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom"; import { Bars3Icon } from '@heroicons/react/24/outline'; import { - useCurrentTheme, + useLocalThemeModeStore, } from "../../store/Store"; import StandardioLogoWhite from "../../assets/standardiologo-white.svg"; @@ -19,9 +19,9 @@ const TopNav = (props) => { const { toggleVisible } = props; const { address } = useAccount(); const navigate = useNavigate(); - const { currentTheme } = useCurrentTheme(); + const { localThemeModeStore } = useLocalThemeModeStore(); - const isLight = currentTheme && currentTheme.includes('light'); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); if (address) { return ( diff --git a/src/store/Store.jsx b/src/store/Store.jsx index 7e74d7c..6fd4cf4 100644 --- a/src/store/Store.jsx +++ b/src/store/Store.jsx @@ -16,13 +16,6 @@ export const useCurrentWagmiConfig = create( }) ); -export const useCurrentTheme = create( - (set) => ({ - currentTheme: 'deluxe-dark', - setCurrentTheme: (currentTheme) => set(() => ({ currentTheme: currentTheme })), - }) -); - export const useWideBorrowModal = create( (set) => ({ borrowWide: false, From 5db698db538f2e0b9c39f909fb6193abee3d27bb Mon Sep 17 00:00:00 2001 From: Zak Date: Fri, 1 Nov 2024 14:23:11 +0000 Subject: [PATCH 14/29] feat: finalise theme settings modal --- src/components/ui/ThemeSettings.jsx | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/components/ui/ThemeSettings.jsx b/src/components/ui/ThemeSettings.jsx index 5070905..cbccb3d 100644 --- a/src/components/ui/ThemeSettings.jsx +++ b/src/components/ui/ThemeSettings.jsx @@ -2,9 +2,16 @@ import { useThemeSettingsOpenStore, useLocalThemeStore, useLocalThemeModePrefStore, + useLocalThemeModeStore, } from "../../store/Store"; +import { + SunIcon, + MoonIcon, +} from '@heroicons/react/24/solid'; + import Modal from "./Modal"; +import Button from "./Button"; import Typography from "./Typography"; import Select from "./Select"; @@ -43,6 +50,7 @@ const ThemeSettings = () => { const { localThemeStore, setLocalThemeStore } = useLocalThemeStore(); const { localThemeModePrefStore, setLocalThemeModePrefStore } = useLocalThemeModePrefStore(); + const { localThemeModeStore } = useLocalThemeModeStore(); const chosenTheme = localThemeStore; @@ -55,6 +63,8 @@ const ThemeSettings = () => { setLocalThemeModePrefStore(e.target.value) } + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); + return ( <> { setThemeSettingsOpenStore(false); }} > + + {isLight ? ( + + ) : ( + + )} + Theme Settings + + + Select your preferred Theme, and Dark/Light mode settings below. +
{ >
+
+ +
) From 69a04f895040aac3e9f8b5ba0cd721a3ce434a83 Mon Sep 17 00:00:00 2001 From: Zak Date: Mon, 4 Nov 2024 10:16:40 +0000 Subject: [PATCH 15/29] chore: merge with main --- src/pages/vault/VaultMerkl.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/vault/VaultMerkl.jsx b/src/pages/vault/VaultMerkl.jsx index 6f527e0..e0495e0 100644 --- a/src/pages/vault/VaultMerkl.jsx +++ b/src/pages/vault/VaultMerkl.jsx @@ -15,7 +15,7 @@ import { } from '@heroicons/react/24/outline'; import { - useCurrentTheme, + useLocalThemeModeStore, useContractAddressStore, useVaultManagerAbiStore, useVaultAddressStore, @@ -41,7 +41,7 @@ const VaultMerkl = () => { const { setVaultAddress } = useVaultAddressStore(); const { vaultStore, setVaultStore } = useVaultStore(); const { vaultType, vaultId } = useParams(); - const { currentTheme } = useCurrentTheme(); + const { localThemeModeStore } = useLocalThemeModeStore(); const navigate = useNavigate(); const { data: blockNumber } = useBlockNumber(); @@ -51,7 +51,7 @@ const VaultMerkl = () => { const [ merklRewards, setMerklRewards ] = useState({}); const [ merklRewardsLoading, setMerklRewardsLoading ] = useState(true); - const isLight = currentTheme && currentTheme.includes('light'); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); const vaultNav = () => { return ( From 417d7fdcd9109927b821fe00ac8fbd58507d008f Mon Sep 17 00:00:00 2001 From: Zak Date: Mon, 4 Nov 2024 10:45:41 +0000 Subject: [PATCH 16/29] fix: fix wrong vault manager for merkl --- src/components/vault/merkl/ClaimModal.jsx | 4 +++- src/pages/vault/VaultMerkl.jsx | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/vault/merkl/ClaimModal.jsx b/src/components/vault/merkl/ClaimModal.jsx index 4a868e0..e2f4fb5 100644 --- a/src/components/vault/merkl/ClaimModal.jsx +++ b/src/components/vault/merkl/ClaimModal.jsx @@ -32,7 +32,7 @@ const ClaimModal = (props) => { const { merklABI } = useMerklABIStore(); const { vaultAddress } = useVaultAddressStore(); - const { writeContract, isError, isPending, isSuccess } = useWriteContract(); + const { writeContract, isError, isPending, isSuccess, error } = useWriteContract(); const claimUsers = useAssets && useAssets.length && useAssets.map(function(asset, index) { return (vaultAddress) @@ -75,12 +75,14 @@ const ClaimModal = (props) => { } else if (isSuccess) { toast.success("Claim Successful"); } else if (isError) { + console.error(error) toast.error('There was an error'); } }, [ isPending, isSuccess, isError, + error, ]); return ( diff --git a/src/pages/vault/VaultMerkl.jsx b/src/pages/vault/VaultMerkl.jsx index e0495e0..836c766 100644 --- a/src/pages/vault/VaultMerkl.jsx +++ b/src/pages/vault/VaultMerkl.jsx @@ -16,7 +16,7 @@ import { import { useLocalThemeModeStore, - useContractAddressStore, + usesUSDContractAddressStore, useVaultManagerAbiStore, useVaultAddressStore, useVaultStore, @@ -33,10 +33,12 @@ import Button from "../../components/ui/Button"; const VaultMerkl = () => { const chainId = useChainId(); + const { - arbitrumSepoliaContractAddress, - arbitrumContractAddress - } = useContractAddressStore(); + arbitrumsUSDSepoliaContractAddress, + arbitrumsUSDContractAddress + } = usesUSDContractAddressStore(); + const { vaultManagerAbi } = useVaultManagerAbiStore(); const { setVaultAddress } = useVaultAddressStore(); const { vaultStore, setVaultStore } = useVaultStore(); @@ -93,13 +95,14 @@ const VaultMerkl = () => { }, 1000); }, []); - const vaultManagerAddress = chainId === arbitrumSepolia.id ? - arbitrumSepoliaContractAddress : - arbitrumContractAddress; + const sUSDVaultManagerAddress = + chainId === arbitrumSepolia.id + ? arbitrumsUSDSepoliaContractAddress + : arbitrumsUSDContractAddress; const { data: vaultData, refetch } = useReadContract({ - address: vaultManagerAddress, abi: vaultManagerAbi, + address: sUSDVaultManagerAddress, functionName: "vaultData", args: [vaultId], }); From da4708cfb349885247ccccf5c759609d5582bc13 Mon Sep 17 00:00:00 2001 From: Zak Date: Mon, 4 Nov 2024 11:58:42 +0000 Subject: [PATCH 17/29] feat: add merkl logic for legacy vaults --- src/abis/smartVaultV4.jsx | 33 ++++++++++++ src/components/vault/merkl/ClaimModal.jsx | 64 +++++++++++++++++------ 2 files changed, 80 insertions(+), 17 deletions(-) diff --git a/src/abis/smartVaultV4.jsx b/src/abis/smartVaultV4.jsx index 8daf9a1..71fc717 100644 --- a/src/abis/smartVaultV4.jsx +++ b/src/abis/smartVaultV4.jsx @@ -62,6 +62,39 @@ export const abi = [ ], "stateMutability": "view", "type": "function" + }, + { + "type": "function", + "name": "merklClaim", + "inputs": [ + { + "name": "_distributor", + "type": "address", + "internalType": "address" + }, + { + "name": "users", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "tokens", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "amounts", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "proofs", + "type": "bytes32[][]", + "internalType": "bytes32[][]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" } ]; diff --git a/src/components/vault/merkl/ClaimModal.jsx b/src/components/vault/merkl/ClaimModal.jsx index e2f4fb5..85ac6ac 100644 --- a/src/components/vault/merkl/ClaimModal.jsx +++ b/src/components/vault/merkl/ClaimModal.jsx @@ -1,4 +1,5 @@ import { useEffect } from "react"; +import { useParams } from "react-router-dom"; import { ethers } from "ethers"; import { toast } from 'react-toastify'; import { @@ -12,6 +13,7 @@ import { useVaultAddressStore, useMerklAddressStore, useMerklABIStore, + useSmartVaultV4ABIStore, } from "../../../store/Store"; import Modal from "../../ui/Modal"; @@ -31,6 +33,9 @@ const ClaimModal = (props) => { const { merklDistributorAddress } = useMerklAddressStore(); const { merklABI } = useMerklABIStore(); const { vaultAddress } = useVaultAddressStore(); + const { smartVaultV4ABI } = useSmartVaultV4ABIStore(); + + const { vaultId } = useParams(); const { writeContract, isError, isPending, isSuccess, error } = useWriteContract(); @@ -48,24 +53,49 @@ const ClaimModal = (props) => { }); const handleClaimToken = async () => { - try { - writeContract({ - abi: merklABI, - address: merklDistributorAddress, - functionName: "claim", - args: [ - claimUsers, - claimTokens, - claimAmounts, - claimProofs - ], - }); - } catch (error) { - let errorMessage = ''; - if (error && error.shortMessage) { - errorMessage = error.shortMessage; + if (vaultId <= 49) { + // LEGACY CODE FOR OLD VAULTS + try { + writeContract({ + abi: merklABI, + address: merklDistributorAddress, + functionName: "claim", + args: [ + claimUsers, + claimTokens, + claimAmounts, + claimProofs + ], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was an error'); + } + } else { + // CURRENT CODE + try { + writeContract({ + abi: smartVaultV4ABI, + address: vaultAddress, + functionName: "merklClaim", + args: [ + merklDistributorAddress, + claimUsers, + claimTokens, + claimAmounts, + claimProofs + ], + }); + } catch (error) { + let errorMessage = ''; + if (error && error.shortMessage) { + errorMessage = error.shortMessage; + } + toast.error(errorMessage || 'There was an error'); } - toast.error(errorMessage || 'There was an error'); } }; From b3f94c9eaa91927485f5b3438e90cc787633a0f9 Mon Sep 17 00:00:00 2001 From: Zak Date: Tue, 12 Nov 2024 11:04:46 +0000 Subject: [PATCH 18/29] fix: wrong var name --- src/components/vault/merkl/RewardList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/vault/merkl/RewardList.jsx b/src/components/vault/merkl/RewardList.jsx index d961c01..2c7e25c 100644 --- a/src/components/vault/merkl/RewardList.jsx +++ b/src/components/vault/merkl/RewardList.jsx @@ -69,7 +69,7 @@ const RewardList = ({ if (merklBalances) { if (merklBalances[index]) { if (merklBalances[index].result) { - useBalance = rewardDecimals[index].result; + useBalance = merklBalances[index].result; } return { ...merklRewards[index], From ac2dd5285502a863fe5fb4cbba5348b9b7ba7013 Mon Sep 17 00:00:00 2001 From: Zak Date: Tue, 12 Nov 2024 11:06:36 +0000 Subject: [PATCH 19/29] feat: close claim modal after success claim --- src/components/vault/merkl/ClaimModal.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/vault/merkl/ClaimModal.jsx b/src/components/vault/merkl/ClaimModal.jsx index 85ac6ac..e6677c9 100644 --- a/src/components/vault/merkl/ClaimModal.jsx +++ b/src/components/vault/merkl/ClaimModal.jsx @@ -104,6 +104,7 @@ const ClaimModal = (props) => { // } else if (isSuccess) { toast.success("Claim Successful"); + closeModal(); } else if (isError) { console.error(error) toast.error('There was an error'); From 1c942132241dc25c33a0744b1a2a1124a6436b4f Mon Sep 17 00:00:00 2001 From: Zak Date: Tue, 12 Nov 2024 14:23:39 +0000 Subject: [PATCH 20/29] feat: add pool share to staking pool --- src/components/tst-staking/StakingRewards.jsx | 4 + src/components/tst-staking/StakingSummary.jsx | 75 +++++++------------ src/components/vault/merkl/RewardList.jsx | 2 + .../vault/yield/YieldPerformanceModal.jsx | 2 +- src/pages/tst-staking/TstStaking.jsx | 24 +++++- src/pages/vault/VaultMerkl.jsx | 2 + 6 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/components/tst-staking/StakingRewards.jsx b/src/components/tst-staking/StakingRewards.jsx index 37120af..b851750 100644 --- a/src/components/tst-staking/StakingRewards.jsx +++ b/src/components/tst-staking/StakingRewards.jsx @@ -19,6 +19,8 @@ const StakingRewards = ({ rawStakedSince, collatDaily, priceData, + tstGlobalBalance, + tstGlobalBalanceLoading, }) => { const { erc20Abi } = useErc20AbiStore(); @@ -102,6 +104,8 @@ const StakingRewards = ({ rawStakedSince={rawStakedSince} collatDaily={collatDaily} latestPrices={latestPrices} + tstGlobalBalance={tstGlobalBalance} + tstGlobalBalanceLoading={tstGlobalBalanceLoading} /> { const [open, setOpen] = useState(false); const [shareOpen, setShareOpen] = useState(false); @@ -175,7 +177,15 @@ const StakingSummary = ({ } // formatted amount - const stakedAmount = ethers.formatEther(tstAmount.toString()); + const stakedAmount = ethers.formatUnits(tstAmount.toString(), 18); + + let globalAmount = 0; + + if (tstGlobalBalance) { + globalAmount = ethers.formatUnits(tstGlobalBalance.toString(), 18); + } + + const poolShare = Number((stakedAmount / globalAmount) * 100).toFixed(8); const handleCloseModal = () => { setOpen(false) @@ -220,22 +230,6 @@ const StakingSummary = ({ return since; } - // const calculateSimpleAPY = (stakedAmount, totalRewardsValue, daysStaked) => { - // const dailyEarnings = Number(totalRewardsValue / daysStaked) || 0; - // const annualEarningsPerTST = (dailyEarnings * 365) / Number(stakedAmount) || 0; - // return annualEarningsPerTST * 100; - // } - const calculateSimpleAPY = (stakedAmount, totalRewardsValue, daysStaked) => { - const dailyEarnings = totalRewardsValue || 0; - const annualEarningsPerTST = (dailyEarnings * 365) / Number(stakedAmount) || 0; - return annualEarningsPerTST; - } - - // const calculateSimpleAPY = (totalValueUSD, dailyEarnings) => { - // const apy = Number(dailyEarnings * 365); - // return apy; - // } - const rewardsWithPrices = rewardsData.map(reward => { const useAmount = ethers.formatUnits(reward.amount, reward.decimals); const useRate = ethers.formatUnits(reward.dailyReward, reward.decimals); @@ -267,10 +261,6 @@ const StakingSummary = ({ if (nextTier?.minAmount) { progress = (stakedAmount / nextTier.minAmount) * 100; } - const apyDisplay = calculateSimpleAPY(stakedAmount, totalRateUSD, daysStaked) + '%' || '0%'; - // const apyDisplay = calculateSimpleAPY(totalValueUSD, dailyEarnings).toFixed(8) + '%' || '0%'; - - let stakeRatio = 'TODO.DOTO'; let progressPercentage = `${progress}%`; @@ -306,14 +296,14 @@ const StakingSummary = ({
- {/*
+
- Historical APY + Your Pool Share
-
*/} +
@@ -400,7 +390,7 @@ const StakingSummary = ({ Participate in protocol governance -
+
@@ -411,33 +401,14 @@ const StakingSummary = ({
- {/*
-
- - Historical APY - - - - - - {apyDisplay} - -
-
*/} - {/*
+
Your Pool Share - {stakeRatio} + {tstGlobalBalanceLoading ? ( + + ) : ( + <> + {poolShare || 0}% + + )}
-
*/} +
diff --git a/src/components/vault/merkl/RewardList.jsx b/src/components/vault/merkl/RewardList.jsx index 2c7e25c..0b5a03a 100644 --- a/src/components/vault/merkl/RewardList.jsx +++ b/src/components/vault/merkl/RewardList.jsx @@ -81,6 +81,8 @@ const RewardList = ({ } }); + console.log(123123, {merklBalances}, {merklRewards}) + const hasClaims = merklData.find(item => item?.unclaimed > 0); return ( diff --git a/src/components/vault/yield/YieldPerformanceModal.jsx b/src/components/vault/yield/YieldPerformanceModal.jsx index ec17703..705d3b2 100644 --- a/src/components/vault/yield/YieldPerformanceModal.jsx +++ b/src/components/vault/yield/YieldPerformanceModal.jsx @@ -190,7 +190,7 @@ const YieldPerformanceModal = ({ Current Value - ~${currentUSD?.toFixed(2) || ''} + ${currentUSD?.toFixed(2) || ''}
diff --git a/src/pages/tst-staking/TstStaking.jsx b/src/pages/tst-staking/TstStaking.jsx index 0e2d4c6..1e88577 100644 --- a/src/pages/tst-staking/TstStaking.jsx +++ b/src/pages/tst-staking/TstStaking.jsx @@ -14,6 +14,8 @@ import { } from '@heroicons/react/24/outline'; import { + useTstAddressStore, + useErc20AbiStore, useStakingPoolv3AbiStore, useStakingPoolv3AddressStore, } from "../../store/Store"; @@ -27,16 +29,26 @@ import Typography from "../../components/ui/Typography"; const TstStaking = (props) => { const { stakingPoolv3Abi } = useStakingPoolv3AbiStore(); - const navigate = useNavigate(); + const { erc20Abi } = useErc20AbiStore(); const { arbitrumSepoliaStakingPoolv3Address, arbitrumStakingPoolv3Address, } = useStakingPoolv3AddressStore(); + + const { + arbitrumTstAddress, + arbitrumSepoliaTstAddress, + } = useTstAddressStore(); + const navigate = useNavigate(); const { address } = useAccount(); const chainId = useChainId(); + const tstAddress = chainId === arbitrumSepolia.id ? + arbitrumSepoliaTstAddress : + arbitrumTstAddress; + const stakingPoolv3Address = chainId === arbitrumSepolia.id ? arbitrumSepoliaStakingPoolv3Address @@ -63,11 +75,19 @@ const TstStaking = (props) => { args: [], }); + const { data: tstGlobalBalance, isLoading: tstGlobalBalanceLoading, refetch: refetchTstGlobalBalance } = useReadContract({ + address: tstAddress, + abi: erc20Abi, + functionName: "balanceOf", + args: [stakingPoolv3Address], + }); + useWatchBlockNumber({ onBlockNumber() { refetchPositions(); refetchRewards(); refetchDailyReward(); + refetchTstGlobalBalance(); }, }) @@ -174,6 +194,8 @@ const TstStaking = (props) => { collatDaily={collatDaily} priceData={priceData} priceDataLoading={priceDataLoading} + tstGlobalBalance={tstGlobalBalance} + tstGlobalBalanceLoading={tstGlobalBalanceLoading} /> )} diff --git a/src/pages/vault/VaultMerkl.jsx b/src/pages/vault/VaultMerkl.jsx index 836c766..0ea5ad3 100644 --- a/src/pages/vault/VaultMerkl.jsx +++ b/src/pages/vault/VaultMerkl.jsx @@ -149,6 +149,8 @@ const VaultMerkl = () => { } }; + console.log(1231312313131233, {merklRewards}) + if (vaultsLoading) { return (
From 565f1ba80a3c45902d40f4445d87affbbb07b72e Mon Sep 17 00:00:00 2001 From: Zak Date: Tue, 12 Nov 2024 14:24:04 +0000 Subject: [PATCH 21/29] chore: remove console logs --- src/components/vault/merkl/RewardList.jsx | 2 -- src/pages/vault/VaultMerkl.jsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/components/vault/merkl/RewardList.jsx b/src/components/vault/merkl/RewardList.jsx index 0b5a03a..2c7e25c 100644 --- a/src/components/vault/merkl/RewardList.jsx +++ b/src/components/vault/merkl/RewardList.jsx @@ -81,8 +81,6 @@ const RewardList = ({ } }); - console.log(123123, {merklBalances}, {merklRewards}) - const hasClaims = merklData.find(item => item?.unclaimed > 0); return ( diff --git a/src/pages/vault/VaultMerkl.jsx b/src/pages/vault/VaultMerkl.jsx index 0ea5ad3..836c766 100644 --- a/src/pages/vault/VaultMerkl.jsx +++ b/src/pages/vault/VaultMerkl.jsx @@ -149,8 +149,6 @@ const VaultMerkl = () => { } }; - console.log(1231312313131233, {merklRewards}) - if (vaultsLoading) { return (
From b3a0ea84f56abbd36ab06eac59e68642d14b81fe Mon Sep 17 00:00:00 2001 From: Zak Date: Tue, 12 Nov 2024 15:00:43 +0000 Subject: [PATCH 22/29] feat: add merkl historical claim total --- src/components/vault/merkl/RewardItem.jsx | 6 +- src/components/vault/merkl/RewardList.jsx | 55 +++++++++++++++++-- .../vault/yield/YieldClaimModal.jsx | 6 +- .../vault/yield/YieldPerformanceModal.jsx | 2 +- src/components/vault/yield/YieldViewModal.jsx | 2 +- src/pages/vault/VaultHistory.jsx | 12 ++-- src/pages/vault/VaultMerkl.jsx | 8 +-- 7 files changed, 71 insertions(+), 20 deletions(-) diff --git a/src/components/vault/merkl/RewardItem.jsx b/src/components/vault/merkl/RewardItem.jsx index 0563bf7..d90affd 100644 --- a/src/components/vault/merkl/RewardItem.jsx +++ b/src/components/vault/merkl/RewardItem.jsx @@ -64,6 +64,10 @@ const RewardItem = ({ {ethers.formatUnits(balance, decimals)} + + {ethers.formatUnits(claimed, decimals)} + + {ethers.formatUnits(unclaimed, decimals)} @@ -87,7 +91,7 @@ const RewardItem = ({ 'glass-alt-bg w-full hidden h-0' )} > - + <>
) diff --git a/src/pages/vault/VaultMerkl.jsx b/src/pages/vault/VaultMerkl.jsx index 836c766..2793058 100644 --- a/src/pages/vault/VaultMerkl.jsx +++ b/src/pages/vault/VaultMerkl.jsx @@ -60,20 +60,20 @@ const VaultMerkl = () => {
From 00d99abe3122c5ad5822c9ed98d73e6e353b4a44 Mon Sep 17 00:00:00 2001 From: Zak Date: Wed, 13 Nov 2024 15:13:04 +0000 Subject: [PATCH 23/29] feat: add merkl usd val to yield summary --- src/components/vault/TokenList.jsx | 2 +- src/components/vault/yield/YieldParent.jsx | 36 +++++ src/components/vault/yield/YieldSummary.jsx | 67 ++++++--- .../vault/yield/YieldSummaryMerkl.jsx | 129 ++++++++++++++++++ src/store/Store.jsx | 9 +- 5 files changed, 219 insertions(+), 24 deletions(-) create mode 100644 src/components/vault/yield/YieldSummaryMerkl.jsx diff --git a/src/components/vault/TokenList.jsx b/src/components/vault/TokenList.jsx index 0d376fd..1ef86ac 100644 --- a/src/components/vault/TokenList.jsx +++ b/src/components/vault/TokenList.jsx @@ -241,7 +241,7 @@ const TokenList = ({ Place In Yield Pool {(amount <= 0 || !yieldEnabled || !tokenYield) ? (null) : ( - + )}
diff --git a/src/components/vault/yield/YieldParent.jsx b/src/components/vault/yield/YieldParent.jsx index c14630e..c75e604 100644 --- a/src/components/vault/yield/YieldParent.jsx +++ b/src/components/vault/yield/YieldParent.jsx @@ -32,6 +32,8 @@ const YieldParent = (props) => { const [ gammaReturnsLoading, setGammaReturnsLoading ] = useState(false); const [ gammaStats, setGammaStats ] = useState([]); const [ gammaStatsLoading, setGammaStatsLoading ] = useState(false); + const [ merklRewards, setMerklRewards ] = useState([]); + const [ merklRewardsLoading, setMerklRewardsLoading ] = useState(true); const [ userSummary, setUserSummary ] = useState([]); @@ -65,6 +67,38 @@ const YieldParent = (props) => { } }, [yieldData, isPending]); + useEffect(() => { + getMerklRewardsData(); + }, [vaultAddress]); + + const getMerklRewardsData = async () => { + try { + setMerklRewardsLoading(true); + const response = await axios.get( + `https://api.merkl.xyz/v3/userRewards?chainId=42161&proof=true&user=${vaultAddress}` + ); + + const useData = response?.data; + + const rewardsArray = []; + + Object.keys(useData).forEach(key => { + const value = useData[key]; + const rewardItem = { + tokenAddress: key, + ... value + } + rewardsArray.push(rewardItem); + }); + + setMerklRewards(rewardsArray); + setMerklRewardsLoading(false); + } catch (error) { + setMerklRewardsLoading(false); + console.log(error); + } + }; + const getGammaUserData = async () => { try { setGammaUserLoading(true); @@ -215,6 +249,8 @@ const YieldParent = (props) => { gammaUser={gammaUser} gammaUserLoading={gammaUserLoading} userSummary={userSummary} + merklRewards={merklRewards} + merklRewardsLoading={merklRewardsLoading} />
diff --git a/src/components/vault/yield/YieldSummary.jsx b/src/components/vault/yield/YieldSummary.jsx index b0a9d21..4a2672b 100644 --- a/src/components/vault/yield/YieldSummary.jsx +++ b/src/components/vault/yield/YieldSummary.jsx @@ -4,6 +4,12 @@ import { PresentationChartLineIcon } from '@heroicons/react/24/outline'; +import { + useMerklRewardsUSD, +} from "../../../store/Store"; + +import YieldSummaryMerkl from "./YieldSummaryMerkl"; + const formatUSD = (value) => { if (value) { return new Intl.NumberFormat('en-US', { @@ -23,9 +29,11 @@ const formatPercentage = (value) => { const YieldSummary = ({ gammaUserLoading, - userSummary + userSummary, + merklRewards, + merklRewardsLoading, }) => { - + const { merklRewardsUSD } = useMerklRewardsUSD(); let positions = []; @@ -54,6 +62,10 @@ const YieldSummary = ({ weightedMarketYieldSum += (parseFloat(position.netMarketReturnsPercentage) * weight); }); + if (merklRewardsUSD) { + totalYieldEarned += merklRewardsUSD + } + return { totalBalance, totalYieldEarned, @@ -141,7 +153,7 @@ const YieldSummary = ({ - Yield Pool Summary + Yield Summary {getPerformanceMessage(metrics.totalYieldEarned, metrics.totalMarketYield)} @@ -180,10 +192,15 @@ const YieldSummary = ({
- - Yield Generated - -
+
+ + Yield Generated + + {/* + APY without Merkl rewards + */} +
+
{gammaUserLoading ? ( <> @@ -206,21 +223,27 @@ const YieldSummary = ({ )}
-
- - Assets in Yield Pools - - {gammaUserLoading ? ( - <> - - - ) : ( - <> - - {formatUSD(metrics.totalBalance)} - - - )} +
+
+ + Assets in Yield Pools + + {gammaUserLoading ? ( + <> + + + ) : ( + <> + + {formatUSD(metrics.totalBalance)} + + + )} +
+
diff --git a/src/components/vault/yield/YieldSummaryMerkl.jsx b/src/components/vault/yield/YieldSummaryMerkl.jsx new file mode 100644 index 0000000..08e779c --- /dev/null +++ b/src/components/vault/yield/YieldSummaryMerkl.jsx @@ -0,0 +1,129 @@ +import { useEffect, useState } from "react"; +import { ethers } from "ethers"; +import axios from "axios"; +import { + useChainId, +} from "wagmi"; +import { arbitrumSepolia } from "wagmi/chains"; + +import { + PresentationChartLineIcon +} from '@heroicons/react/24/outline'; + +import { + useVaultAddressStore, + useMerklRewardsUSD, +} from "../../../store/Store"; + +import Typography from "../../ui/Typography"; + +const formatUSD = (value) => { + if (value) { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }).format(Math.abs(value)); + } +}; + +const formatPercentage = (value) => { + if (value) { + return `${value >= 0 ? '+' : ''}${value.toFixed(2)}%`; + } +}; + +const YieldSummaryMerkl = ({ + userSummary, + merklRewards, + merklRewardsLoading, +}) => { + const chainId = useChainId(); + const { merklRewardsUSD, setMerklRewardsUSD } = useMerklRewardsUSD(); + const [priceData, setPriceData] = useState(undefined); + const [priceDataLoading, setPriceDataLoading] = useState(true); + + const getPriceData = async () => { + try { + setPriceDataLoading(true); + const response = await axios.get( + "https://smart-vault-api.thestandard.io/asset_prices" + ); + const chainData = + chainId === arbitrumSepolia.id + ? response.data.arbitrum_sepolia + : response.data.arbitrum; + setPriceData(chainData); + setPriceDataLoading(false); + } catch (error) { + console.log(error); + setPriceDataLoading(false); + } + }; + + useEffect(() => { + getPriceData(); + }, []); + + const handleDailyPrices = () => { + const prices = {}; + if (priceData) { + for (const [token, tokenData] of Object.entries(priceData)) { + if (tokenData.prices && tokenData.prices.length > 0) { + const latestPrice = tokenData.prices[tokenData.prices.length - 1].price; + // convert to dollar value + prices[token] = parseFloat(latestPrice) / 100000000; + } + } + } + return prices; + } + + const latestPrices = handleDailyPrices(); + + const merklWithPrices = merklRewards.map(reward => { + const useAmount = ethers.formatUnits(reward.accumulated, reward.decimals); + const price = latestPrices[reward.symbol] || 1; // Default to 1 for stablecoins + const accumulatedUsd = parseFloat(useAmount) * price; + return { + ...reward, + accumulatedFormatted: useAmount, + accumulatedUsd: accumulatedUsd, + price: price + }; + }) + + let totalUSD = 0; + + merklWithPrices.forEach(reward => { + totalUSD += reward.accumulatedUsd; + }); + + useEffect(() => { + setMerklRewardsUSD(totalUSD); + }, [totalUSD]); + + return ( + <> +
+ + Lifetime Merkl Rewards + + {merklRewardsLoading ? ( + <> + + + ) : ( + <> + + {formatUSD(totalUSD)} + + + )} +
+ + ) +}; + +export default YieldSummaryMerkl; \ No newline at end of file diff --git a/src/store/Store.jsx b/src/store/Store.jsx index b3e5c98..532f891 100644 --- a/src/store/Store.jsx +++ b/src/store/Store.jsx @@ -293,4 +293,11 @@ export const useLocalThemeModePrefStore = create( localThemeModePrefStore: 'device', setLocalThemeModePrefStore: (localThemeModePrefStore) => set(() => ({ localThemeModePrefStore: localThemeModePrefStore })), }) -); \ No newline at end of file +); + +export const useMerklRewardsUSD = create( + (set) => ({ + merklRewardsUSD: 0, + setMerklRewardsUSD: (merklRewardsUSD) => set(() => ({ merklRewardsUSD: merklRewardsUSD })), + }) +); From e73424ba73662c2b42a12c83c34e8d8a5926546e Mon Sep 17 00:00:00 2001 From: Zak Date: Wed, 13 Nov 2024 15:21:37 +0000 Subject: [PATCH 24/29] feat: handle initial 0 values for yield --- src/components/vault/yield/YieldList.jsx | 2 +- src/components/vault/yield/YieldPerformanceModal.jsx | 8 ++++---- src/components/vault/yield/YieldSummary.jsx | 4 ++-- src/components/vault/yield/YieldSummaryMerkl.jsx | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/vault/yield/YieldList.jsx b/src/components/vault/yield/YieldList.jsx index 9b9362f..fe4ee67 100644 --- a/src/components/vault/yield/YieldList.jsx +++ b/src/components/vault/yield/YieldList.jsx @@ -164,7 +164,7 @@ const YieldList = (props) => { ) : ( <> - ${showBalance || ''} + ${showBalance || '0.00'} )} diff --git a/src/components/vault/yield/YieldPerformanceModal.jsx b/src/components/vault/yield/YieldPerformanceModal.jsx index 2683a49..6a0916b 100644 --- a/src/components/vault/yield/YieldPerformanceModal.jsx +++ b/src/components/vault/yield/YieldPerformanceModal.jsx @@ -44,9 +44,9 @@ const YieldPerformanceModal = ({ const initialTokenCurrentUSD = positionUser?.returns?.initialTokenCurrentUSD || 0; const currentUSD = positionUser?.returns?.currentUSD || 0; const hypervisorReturnsUSD = positionUser?.returns?.hypervisorReturnsUSD; - const hypervisorReturnsPercentage = positionUser?.returns?.hypervisorReturnsPercentage; - const netMarketReturnsUSD = positionUser?.returns?.netMarketReturnsUSD; - const netMarketReturnsPercentage = positionUser?.returns?.netMarketReturnsPercentage; + const hypervisorReturnsPercentage = positionUser?.returns?.hypervisorReturnsPercentage || 0; + const netMarketReturnsUSD = positionUser?.returns?.netMarketReturnsUSD || 0; + const netMarketReturnsPercentage = positionUser?.returns?.netMarketReturnsPercentage || 0; const tvlUSD = Number(positionStats?.tvlUSD) || 0; let showApy = '0'; @@ -55,7 +55,7 @@ const YieldPerformanceModal = ({ } const getMarketContext = () => { - const marketReturn = parseFloat(netMarketReturnsPercentage); + const marketReturn = parseFloat(netMarketReturnsPercentage) || 0; if (marketReturn > 0) { return { description: `Market movement has generated ${formatUSD(netMarketReturnsUSD)}`, diff --git a/src/components/vault/yield/YieldSummary.jsx b/src/components/vault/yield/YieldSummary.jsx index 4a2672b..95f2b38 100644 --- a/src/components/vault/yield/YieldSummary.jsx +++ b/src/components/vault/yield/YieldSummary.jsx @@ -135,7 +135,7 @@ const YieldSummary = ({ // Market is neutral/down, strategy building return ( - Your {formatUSD(totalBalance)} position is actively accumulating trading fees in TheStandard's yield pools. While the market impact is {formatUSD(totalMarketYield)} ({formatPercentage(weightedAverageMarketAPY)}), the strategy is building reserves through trading fees, currently at {formatUSD(totalYieldEarned)} ({formatPercentage(weightedAverageYieldAPY)}). This approach typically shows its strength over longer holding periods as fees accumulate. + Your {formatUSD(totalBalance)} position is actively accumulating trading fees in TheStandard's yield pools. While the market impact is {formatUSD(totalMarketYield)} ({formatPercentage(weightedAverageMarketAPY) || '0%'}), the strategy is building reserves through trading fees, currently at {formatUSD(totalYieldEarned)} ({formatPercentage(weightedAverageYieldAPY) || '0%'}). This approach typically shows its strength over longer holding periods as fees accumulate. ); }; @@ -217,7 +217,7 @@ const YieldSummary = ({ variant="p" className={`text-end ${getYieldColor(metrics.weightedAverageYieldAPY)}`} > - {formatPercentage(metrics.weightedAverageYieldAPY)} APY + {formatPercentage(metrics.weightedAverageYieldAPY) || '0%'} APY )} diff --git a/src/components/vault/yield/YieldSummaryMerkl.jsx b/src/components/vault/yield/YieldSummaryMerkl.jsx index 08e779c..9cbf28d 100644 --- a/src/components/vault/yield/YieldSummaryMerkl.jsx +++ b/src/components/vault/yield/YieldSummaryMerkl.jsx @@ -117,7 +117,7 @@ const YieldSummaryMerkl = ({ ) : ( <> - {formatUSD(totalUSD)} + {formatUSD(totalUSD) || '$0.00'} )} From 2ecea53833c8ccc676a8dc68b00de9caf578a639 Mon Sep 17 00:00:00 2001 From: Zak Date: Wed, 13 Nov 2024 15:22:58 +0000 Subject: [PATCH 25/29] style: correct colspan --- src/components/vault/merkl/RewardList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/vault/merkl/RewardList.jsx b/src/components/vault/merkl/RewardList.jsx index 65a17a5..9dd0643 100644 --- a/src/components/vault/merkl/RewardList.jsx +++ b/src/components/vault/merkl/RewardList.jsx @@ -172,7 +172,7 @@ const RewardList = ({ ) : ( <> - + No Rewards Earned Yet.
Start earning by placing your tokens into the yield pool. From baf8f7d393465aec68e75a8a6584d350242436cf Mon Sep 17 00:00:00 2001 From: Zak Date: Wed, 13 Nov 2024 15:24:30 +0000 Subject: [PATCH 26/29] chore: add clarifying subtext --- src/components/vault/yield/YieldSummary.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/vault/yield/YieldSummary.jsx b/src/components/vault/yield/YieldSummary.jsx index 95f2b38..c8a3a95 100644 --- a/src/components/vault/yield/YieldSummary.jsx +++ b/src/components/vault/yield/YieldSummary.jsx @@ -196,9 +196,9 @@ const YieldSummary = ({ Yield Generated - {/* - APY without Merkl rewards - */} + + Including Merkl rewards +
{gammaUserLoading ? ( From 409e6a6e1abe6fe3e67a5eea19a50b5ef56844a9 Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 14 Nov 2024 10:48:05 +0000 Subject: [PATCH 27/29] feat: cap total tst to 4 dec --- src/components/tst-staking/StakingSummary.jsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/tst-staking/StakingSummary.jsx b/src/components/tst-staking/StakingSummary.jsx index 1bdfe7a..3c85172 100644 --- a/src/components/tst-staking/StakingSummary.jsx +++ b/src/components/tst-staking/StakingSummary.jsx @@ -396,9 +396,18 @@ const StakingSummary = ({ Total TST Staked - - {stakedAmount} TST - + + + {stakedAmount ? ( + Number.parseFloat(Number(stakedAmount).toFixed(4)) + ) : ('0')} +  TST + +
From 1f29e00ffe5e1d2c55e9710f41272ad20804a113 Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 14 Nov 2024 10:54:20 +0000 Subject: [PATCH 28/29] style: make slider thumb more visible in lightmode --- src/components/vault/yield/YieldDepositModal.jsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/vault/yield/YieldDepositModal.jsx b/src/components/vault/yield/YieldDepositModal.jsx index f0322d6..27d3fdf 100644 --- a/src/components/vault/yield/YieldDepositModal.jsx +++ b/src/components/vault/yield/YieldDepositModal.jsx @@ -14,6 +14,7 @@ import { import { useVaultAddressStore, + useLocalThemeModeStore, } from "../../../store/Store"; import { @@ -36,6 +37,7 @@ const YieldDepositModal = (props) => { symbol, } = props; const { vaultAddress } = useVaultAddressStore(); + const { localThemeModeStore } = useLocalThemeModeStore(); const chainId = useChainId(); const [ yieldStage, setYieldStage ] = useState(''); const [ stableRatio, setStableRatio ] = useState(10); @@ -47,6 +49,8 @@ const YieldDepositModal = (props) => { const { writeContract, isError, error, isPending, isSuccess } = useWriteContract(); + const isLight = localThemeModeStore && localThemeModeStore.includes('light'); + const handleDepositYield = async () => { const now = Math.floor(Date.now() / 1000); const deadline = now + 60; @@ -210,7 +214,10 @@ const YieldDepositModal = (props) => { min={0} max="100" value={stableRatio} - className={`range [--range-shdw:unset] [&::-webkit-slider-thumb]:bg-white`} + className={isLight ? + `range [--range-shdw:unset] [&::-webkit-slider-thumb]:bg-primary` : + `range [--range-shdw:unset] [&::-webkit-slider-thumb]:bg-white` + } onChange={(e) => handleStableRatio(e.target.value)} />
From 78f08d74a67cea32e60a25510810fd6f5ea3f176 Mon Sep 17 00:00:00 2001 From: Zak Date: Thu, 14 Nov 2024 12:30:55 +0000 Subject: [PATCH 29/29] feat: add staking call to actions --- src/pages/vault/Vault.jsx | 31 +++++++++++++++++++++++++++++++ src/pages/vaults/Vaults.jsx | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/pages/vault/Vault.jsx b/src/pages/vault/Vault.jsx index 70c8d15..148431c 100644 --- a/src/pages/vault/Vault.jsx +++ b/src/pages/vault/Vault.jsx @@ -274,6 +274,37 @@ const Vault = () => { assetsLoading={!assets.length || assets.length === 0} yieldEnabled={yieldEnabled} /> + +
+
+
+ + Earn From Every Transaction | TST stakers receive: + + + 💰 1% of all yield pool deposits
+ 💸 Up to 5% of debt minting fees
+ 💎 1% of all collateral trades +
+
+
+ +
+ +
+
+
+
{ const { address: accountAddress } = useAccount(); const { vaultManagerAbi } = useVaultManagerAbiStore(); + const navigate = useNavigate(); + const { arbitrumSepoliaContractAddress, arbitrumContractAddress @@ -182,6 +188,36 @@ const Vaults = () => { return (
+ +
+
+
+ + Stake TST Today | Earn Protocol Fees + Early Governance | Join Before $10M TVL + + + 💰 1% of all yield pool deposits
+ 💸 Up to 5% of debt minting fees
+ 💎 1% of all collateral trades +
+
+
+ +
+ +
+
+