diff --git a/.openzeppelin/mainnet.json b/.openzeppelin/mainnet.json index 15f70095..aff28ba7 100644 --- a/.openzeppelin/mainnet.json +++ b/.openzeppelin/mainnet.json @@ -226,6 +226,232 @@ } } } + }, + "f5994764c094d27dccc784eac5674d3f8721c6d83bed328d38defdf246f3e7d1": { + "address": "0x49c3D3735084f5F69D4E214Fe2680A0c870Bbfe3", + "txHash": "0xb7296e77121ef7e86ec989139197e0509cf172b90c671a0c1544d9439e28b7b9", + "layout": { + "storage": [ + { + "contract": "Powered", + "label": "_powerSwitch", + "type": "t_address", + "src": "contracts/PowerSwitch/Powered.sol:24" + }, + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_bool", + "src": "@openzeppelin/contracts/proxy/Initializable.sol:25" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts/proxy/Initializable.sol:30" + }, + { + "contract": "ContextUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:31" + }, + { + "contract": "OwnableUpgradeable", + "label": "_owner", + "type": "t_address", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:20" + }, + { + "contract": "OwnableUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:74" + }, + { + "contract": "Geyser", + "label": "_geyser", + "type": "t_struct(GeyserData)5791_storage", + "src": "contracts/Geyser.sol:222" + }, + { + "contract": "Geyser", + "label": "_vaults", + "type": "t_mapping(t_address,t_struct(VaultData)5804_storage)", + "src": "contracts/Geyser.sol:223" + }, + { + "contract": "Geyser", + "label": "_bonusTokenSet", + "type": "t_struct(AddressSet)4188_storage", + "src": "contracts/Geyser.sol:224" + }, + { + "contract": "Geyser", + "label": "_vaultFactorySet", + "type": "t_struct(AddressSet)4188_storage", + "src": "contracts/Geyser.sol:225" + } + ], + "types": { + "t_struct(GeyserData)5791_storage": { + "label": "struct IGeyser.GeyserData", + "members": [ + { + "label": "stakingToken", + "type": "t_address" + }, + { + "label": "rewardToken", + "type": "t_address" + }, + { + "label": "rewardPool", + "type": "t_address" + }, + { + "label": "rewardScaling", + "type": "t_struct(RewardScaling)5816_storage" + }, + { + "label": "rewardSharesOutstanding", + "type": "t_uint256" + }, + { + "label": "totalStake", + "type": "t_uint256" + }, + { + "label": "totalStakeUnits", + "type": "t_uint256" + }, + { + "label": "lastUpdate", + "type": "t_uint256" + }, + { + "label": "rewardSchedules", + "type": "t_array(t_struct(RewardSchedule)5798_storage)dyn_storage" + } + ] + }, + "t_address": { + "label": "address" + }, + "t_struct(RewardScaling)5816_storage": { + "label": "struct IGeyser.RewardScaling", + "members": [ + { + "label": "floor", + "type": "t_uint256" + }, + { + "label": "ceiling", + "type": "t_uint256" + }, + { + "label": "time", + "type": "t_uint256" + } + ] + }, + "t_uint256": { + "label": "uint256" + }, + "t_array(t_struct(RewardSchedule)5798_storage)dyn_storage": { + "label": "struct IGeyser.RewardSchedule[]" + }, + "t_struct(RewardSchedule)5798_storage": { + "label": "struct IGeyser.RewardSchedule", + "members": [ + { + "label": "duration", + "type": "t_uint256" + }, + { + "label": "start", + "type": "t_uint256" + }, + { + "label": "shares", + "type": "t_uint256" + } + ] + }, + "t_mapping(t_address,t_struct(VaultData)5804_storage)": { + "label": "mapping(address => struct IGeyser.VaultData)" + }, + "t_struct(VaultData)5804_storage": { + "label": "struct IGeyser.VaultData", + "members": [ + { + "label": "totalStake", + "type": "t_uint256" + }, + { + "label": "stakes", + "type": "t_array(t_struct(StakeData)5809_storage)dyn_storage" + } + ] + }, + "t_array(t_struct(StakeData)5809_storage)dyn_storage": { + "label": "struct IGeyser.StakeData[]" + }, + "t_struct(StakeData)5809_storage": { + "label": "struct IGeyser.StakeData", + "members": [ + { + "label": "amount", + "type": "t_uint256" + }, + { + "label": "timestamp", + "type": "t_uint256" + } + ] + }, + "t_struct(AddressSet)4188_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)3923_storage" + } + ] + }, + "t_struct(Set)3923_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]" + }, + "t_bytes32": { + "label": "bytes32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]" + }, + "t_bool": { + "label": "bool" + } + } + } } }, "admin": { diff --git a/README.md b/README.md index 6b3b9e21..2a485743 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,23 @@ ethereum: proxyAdmin: 0xc70F5bc82ccb3de00400814ff8bD406C271db3c4 geyserRegistry: 0xFc43803F203e3821213bE687120aD44C8a21A7e7 geysers: - - poolRef: "CHARM-WAMPL-WETH (Crystal V1)" - deployment: 0xEac308Fa45A9b64cfb6965e8d1237B39016862e3 + - poolRef: "CHARM-SPOT-USDC (Bootstrap V1)" + deployment: 0x569f042C54B094A10E6fe1b52515eEC507D8da06 - - poolRef: "BB-SPOT-USDC (Steamboat V1)" - deployment: 0xF0a45FA4fBec33A2A51E08058bEA92761c08D7D5 + - poolRef: "CHARM-WAMPL-WETH (Crystal V2)" + deployment: 0x59d177f718e902e59CF3Cbd19519194bcC437FeF - - poolRef: "CHARM-SPOT-USDC (Great geyser V1)" - deployment: 0x7B2e9353D3Bf71d9f9246B1291eE29DFB11B32C7 + - poolRef: "STAMPL (Riverside V1)" + deployment: 0xa19604b951592170DDa857CBE46609B85AB00Dee + + # - poolRef: "CHARM-WAMPL-WETH (Crystal V1)" + # deployment: 0xEac308Fa45A9b64cfb6965e8d1237B39016862e3 + + # - poolRef: "BB-SPOT-USDC (Steamboat V1)" + # deployment: 0xF0a45FA4fBec33A2A51E08058bEA92761c08D7D5 + + # - poolRef: "CHARM-SPOT-USDC (Great geyser V1)" + # deployment: 0x7B2e9353D3Bf71d9f9246B1291eE29DFB11B32C7 # - poolRef: "UNI-ETH-AMPL-V2 (Beehive V7)" # deployment: 0x5Ec6f02D0b657E4a56d6020Bc21F19f2Ca13EcA9 diff --git a/frontend/README.md b/frontend/README.md index c4fcce34..d1096ee4 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -282,4 +282,5 @@ To learn React, check out the [React documentation](https://reactjs.org/). ``` ./scripts/deploy-s3.sh +./scripts/flush-cache-prod.sh DIST.cloudfront.net ``` diff --git a/frontend/scripts/flush-cache-prod.sh b/frontend/scripts/flush-cache-prod.sh new file mode 100755 index 00000000..8e0c75e1 --- /dev/null +++ b/frontend/scripts/flush-cache-prod.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# +# Name: flush-cache-prod.sh +# +# Usage: ./flush-cache-prod.sh "" +# +# Description: +# 1. Looks up a CloudFront distribution by matching the input domain name +# (e.g., "d123abcxyz.cloudfront.net") to the distribution's DomainName field. +# 2. Issues a cache invalidation for all files ("/*"). +# 3. Monitors the invalidation until it is completed. +# +# Requirements: +# - AWS CLI installed and configured +# - jq installed (for JSON parsing) +# - A CloudFront distribution whose DomainName matches the supplied input + +set -euo pipefail + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 \"\"" + echo "Example: $0 \"d123abcxyz.cloudfront.net\"" + exit 1 +fi + +DISTRIBUTION_DOMAIN="$1" + +echo "Looking up the distribution by domain name: \"$DISTRIBUTION_DOMAIN\" ..." + +# Step 1: Query the distribution list, matching the DomainName to DISTRIBUTION_DOMAIN +DISTRIBUTION_ID=$( + aws cloudfront list-distributions --output json \ + | jq -r --arg DOMAIN "$DISTRIBUTION_DOMAIN" ' + .DistributionList.Items[] + | select(.DomainName == $DOMAIN) + | .Id + ' +) + +if [ -z "$DISTRIBUTION_ID" ]; then + echo "Error: No distribution found with domain name: \"$DISTRIBUTION_DOMAIN\"" + exit 1 +fi + +echo "Found distribution ID: $DISTRIBUTION_ID" + +# Step 2: Issue a cache invalidation for all files +echo "Creating invalidation for all paths (/*) ..." +INVALIDATION_JSON=$( + aws cloudfront create-invalidation \ + --distribution-id "$DISTRIBUTION_ID" \ + --paths "/*" +) + +# Extract the invalidation ID from the result +INVALIDATION_ID=$(echo "$INVALIDATION_JSON" | jq -r '.Invalidation.Id') + +echo "Invalidation created. ID: $INVALIDATION_ID" + +# Step 3: Monitor the invalidation until it completes +echo "Monitoring invalidation status..." + +while true; do + STATUS=$( + aws cloudfront get-invalidation \ + --distribution-id "$DISTRIBUTION_ID" \ + --id "$INVALIDATION_ID" \ + --output json \ + | jq -r '.Invalidation.Status' + ) + + echo "Current status: $STATUS" + + if [ "$STATUS" == "Completed" ]; then + echo "Invalidation $INVALIDATION_ID has completed!" + break + fi + + # Sleep for a few seconds before checking again (avoid spamming AWS) + sleep 5 +done + +echo "Cache flush complete." diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d13a03f1..0d299ca4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -25,7 +25,7 @@ function App() { } /> diff --git a/frontend/src/components/GeyserFirst/GeyserFirstContainer.tsx b/frontend/src/components/GeyserFirst/GeyserFirstContainer.tsx index 6a1f3168..9f3df305 100644 --- a/frontend/src/components/GeyserFirst/GeyserFirstContainer.tsx +++ b/frontend/src/components/GeyserFirst/GeyserFirstContainer.tsx @@ -13,13 +13,13 @@ import { GeyserStakeView } from './GeyserStakeView' import { GeyserStatsView } from './GeyserStatsView' export const GeyserFirstContainer = () => { - const { ref } = useParams() + const { slug } = useParams() const { ready, validNetwork } = useContext(Web3Context) const { geyserAction, updateGeyserAction, selectedGeyserInfo: { isWrapped }, - selectGeyserByRef, + selectGeyserBySlug, geysers, loading, } = useContext(GeyserContext) @@ -29,13 +29,13 @@ export const GeyserFirstContainer = () => { const [geyserNotFound, setGeyserNotFound] = useState(false) useEffect(() => { const fetchGeyser = async () => { - if (ref && geysers.length > 0) { - const found = await selectGeyserByRef(ref) + if (slug && geysers.length > 0) { + const found = await selectGeyserBySlug(slug) setGeyserNotFound(!found) } } fetchGeyser() - }, [ref, geysers, selectGeyserByRef]) + }, [slug, geysers, selectGeyserBySlug]) if (loading) return diff --git a/frontend/src/components/GeyserFirst/GeyserStakeView.tsx b/frontend/src/components/GeyserFirst/GeyserStakeView.tsx index be4126eb..af015e17 100644 --- a/frontend/src/components/GeyserFirst/GeyserStakeView.tsx +++ b/frontend/src/components/GeyserFirst/GeyserStakeView.tsx @@ -44,7 +44,7 @@ export const GeyserStakeView = () => { } = useContext(GeyserContext) const { decimals: stakingTokenDecimals, symbol: stakingTokenSymbol, address: stakingTokenAddress } = stakingTokenInfo const { symbol: rewardTokenSymbol, address: rewardTokenAddress } = rewardTokenInfo - const { selectedVault, currentLock, withdrawUnlockedFromVault, rewardAmountClaimedOnUnstake } = + const { selectedVault, currentLock, withdrawUnlockedFromVault, rewardAmountClaimedOnUnstake, otherActiveLock } = useContext(VaultContext) const { stakingTokenBalance, underlyingTokenBalance, refreshWalletBalances } = useContext(WalletContext) const { @@ -69,8 +69,10 @@ export const GeyserStakeView = () => { const setDefaultInputAmount = () => { if (stakingTokenInfo.price > 0) { const initialStakeAmountUSD = 1000 - const stakeAmt = Math.max(initialStakeAmountUSD / stakingTokenInfo.price, 0.000001) - const stakeAmtFP = parseUnits(stakeAmt.toFixed(stakingTokenInfo.decimals), stakingTokenInfo.decimals) + const stakeAmt = Math.max(initialStakeAmountUSD / stakingTokenInfo.price, 0.0000001).toFixed( + stakingTokenInfo.decimals, + ) + const stakeAmtFP = parseUnits(stakeAmt, stakingTokenInfo.decimals) setUserInput(stakeAmt) setParsedUserInput(BigNumber.from(stakeAmtFP)) } @@ -194,10 +196,15 @@ export const GeyserStakeView = () => { {!ready && connectWallet()} />} {ready && parsedUserInput.gt(0) && ( - + )} diff --git a/frontend/src/components/GeyserFirst/MyStats.tsx b/frontend/src/components/GeyserFirst/MyStats.tsx index 3c92b98e..8178a0f6 100644 --- a/frontend/src/components/GeyserFirst/MyStats.tsx +++ b/frontend/src/components/GeyserFirst/MyStats.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useContext } from 'react' import styled from 'styled-components/macro' import tw from 'twin.macro' -import Web3Context from 'context/Web3Context' +// import Web3Context from 'context/Web3Context' import { GeyserContext } from 'context/GeyserContext' import { StatsContext } from 'context/StatsContext' import { safeNumeral } from 'utils/numeral' @@ -10,11 +10,11 @@ import { GeyserStatsBox } from './GeyserStatsBox' import { GET_APY_STAKE_MSG, GET_APY_WARN_MSG, GET_REWARD_MULTIPLIER_MSG, CURRENT_REWARDS_MSG } from '../../constants' export const MyStats = () => { - const { ready } = useContext(Web3Context) + // const { ready } = useContext(Web3Context) const { userStats: { apy, currentMultiplier, maxMultiplier, currentReward }, vaultStats: { currentStake }, - geyserStats: { calcPeriodInDays }, + geyserStats: { calcPeriodInDays, duration }, } = useContext(StatsContext) const { selectedGeyserInfo: { @@ -35,12 +35,17 @@ export const MyStats = () => { } const config = getGeyserConfig(selectedGeyser.id) const lpAPYNew = (stakeAPYs.lp && stakeAPYs.lp[config.lpRef]) || 0 - const geyserAPYGlobal = stakeAPYs.geysers && stakeAPYs.geysers[config.ref] - const geyserAPYNew = ready ? apy : geyserAPYGlobal || apy + const geyserAPYGlobal = stakeAPYs.geysers && stakeAPYs.geysers[config.slug] + // const geyserAPYNew = ready ? apy : geyserAPYGlobal || apy + // NOTE: just showing the global APY as a guideline for most users. + let geyserAPYNew = geyserAPYGlobal || apy + if (duration === 0) { + geyserAPYNew = 0 + } setLPAPY(lpAPYNew) setGeyserAPY(geyserAPYNew) setFinalAPY(Math.min(geyserAPYNew + lpAPYNew, 100000)) - }, [selectedGeyser, apy]) + }, [selectedGeyser, apy, duration]) // TODO: handle bonus tokens const baseRewards = currentReward * rewardTokenPrice @@ -62,7 +67,7 @@ export const MyStats = () => { {GET_APY_STAKE_MSG()} = ({ link, balance, staked }) => { - if (balance.gte(0)) { - return <> - } - - return ( +export const StakeWarning: React.FC = ({ poolAddress, balance, staked, otherActiveLock }) => { + const renderStakeWarning = (message: string, buttonLabel: string, url: string, newTab: bool) => ( - Insufficient balance + {message} - + ) -} -const Content = styled.div` - ${tw`flex flex-row flex-grow text-white bg-secondary font-bold`} -` + if (otherActiveLock) { + return renderStakeWarning('Your tokens are staked elsewhere', 'Unstake', '/', false) + } + + if (balance.gte(0)) { + return null + } + + const buttonLabel = staked.lte(0) ? 'Get LP' : 'Get more' + return renderStakeWarning('Insufficient balance', buttonLabel, poolAddress, true) +} const StakeWarningContainer = styled.div` ${tw`h-80px mt-1 mb-5 border border-lightGray flex flex-row tracking-wider`} @@ -41,6 +45,14 @@ const ColoredDiv = styled.div` ${tw`h-full w-2 bg-secondaryDark`} ` +const Content = styled.div` + ${tw`flex flex-row flex-grow text-white bg-secondary font-bold`} +` + +const MessageContainer = styled.div` + ${tw`flex flex-row flex-grow w-8/12`} +` + const Message = styled.span` ${tw`ml-5 my-auto`} ` @@ -54,7 +66,3 @@ const Button = styled.button` ${tw`sm:text-sm`} ${tw`hover:border hover:border-white cursor-pointer`} ` - -const MessageContainer = styled.div` - ${tw`flex flex-row flex-grow w-8/12`} -` diff --git a/frontend/src/components/GeysersList.tsx b/frontend/src/components/GeysersList.tsx index 1dd3a34e..4b9a490d 100644 --- a/frontend/src/components/GeysersList.tsx +++ b/frontend/src/components/GeysersList.tsx @@ -10,36 +10,38 @@ export const GeysersList = () => { const navigate = useNavigate() const { geysers, - getGeyserRefByName, + getGeyserSlugByName, selectedGeyserInfo: { geyser: selectedGeyser }, getGeyserName, } = useContext(GeyserContext) const { selectedVault } = useContext(VaultContext) const handleGeyserChange = async (geyserName: string) => { - navigate(`/geysers/${getGeyserRefByName(geyserName)}`) + navigate(`/geysers/${getGeyserSlugByName(geyserName)}`) } const optgroups = (() => { const stakedGeysers = selectedVault ? selectedVault.locks.map((l) => l.geyser) : [] - const geysersToShow = geysers.filter((g) => g.active || stakedGeysers.find((s) => s.id === g.id)) + let geysersToShow = geysers.filter((g) => g.active || stakedGeysers.find((s) => s.id === g.id)) + if (geysersToShow.length === 0) { + geysersToShow = geysers.slice(0, 3) + } + const activeGeysers = geysersToShow.filter((g) => g.active === true).map(({ id }) => getGeyserName(id)) const inactiveGeysers = geysersToShow.filter((g) => !(g.active === true)).map(({ id }) => getGeyserName(id)) - - const options = [ - { + const options = [] + if (activeGeysers.length > 0) { + options.push({ group: 'Active Geysers', options: activeGeysers, - }, - ] - + }) + } if (inactiveGeysers.length > 0) { options.push({ group: 'Inactive Geysers', options: inactiveGeysers, }) } - return options })() diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 04bb45f7..fc1fa7e1 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -27,7 +27,7 @@ export const HeaderTab: React.FC = () => { if (index === 0) { navigate('/') } else if (index === 1) { - navigate(`/geysers/${selectedGeyserConfig?.ref || defaultGeyser?.ref}`) + navigate(`/geysers/${selectedGeyserConfig?.slug || defaultGeyser?.slug}`) } else if (index === 2) { navigate('/vault') } diff --git a/frontend/src/components/Home.tsx b/frontend/src/components/Home.tsx index edbf2f74..e80b7d27 100644 --- a/frontend/src/components/Home.tsx +++ b/frontend/src/components/Home.tsx @@ -31,7 +31,7 @@ export const Home = () => { const tvl = stakingTokenInfo ? getGeyserTotalDeposit(g, stakingTokenInfo) : 0 const stakingTokens = (stakingTokenInfo && stakingTokenInfo.composition.map((t) => t.symbol)) || [] const lpAPY = (stakeAPYs.lp && stakeAPYs.lp[config.lpRef]) || 0 - const geyserAPY = (stakeAPYs.geysers && stakeAPYs.geysers[config.ref]) || 0 + const geyserAPY = (stakeAPYs.geysers && stakeAPYs.geysers[config.slug]) || 0 const apy = lpAPY + geyserAPY const programName = extractProgramName(config.name) const platform = getPlatformConfig(config) @@ -46,7 +46,7 @@ export const Home = () => { rewards, apy, name: programName, - ref: config.ref, + slug: config.slug, poolAddress: config.poolAddress, poolType, platform, @@ -56,7 +56,10 @@ export const Home = () => { const totalTVL = geyserData.reduce((s, g) => g.tvl + s, 0) const totalRewards = geyserData.filter((g) => g.active).reduce((s, g) => g.rewards + s, 0) - const geysersToShow = geyserData.filter((g) => g.active || stakedGeysers.find((s) => s.id === g.id)) + let geysersToShow = geyserData.filter((g) => g.active || stakedGeysers.find((s) => s.id === g.id)) + if (geysersToShow.length === 0) { + geysersToShow = geyserData.slice(0, 3) + } return ( @@ -75,7 +78,7 @@ export const Home = () => { {geysersToShow.map((g) => ( - + @@ -88,7 +91,7 @@ export const Home = () => { - navigate(`/geysers/${g.ref}`)}>{g.name} + navigate(`/geysers/${g.slug}`)}>{g.name} {g.poolType} @@ -133,7 +136,7 @@ const BodyRow = styled.tr<{ $inactive?: boolean }>` ` const TableCell = styled.td` - ${tw`p-4 text-center`} + ${tw`px-2 py-4 text-center`} ` const DataCell = styled.td` diff --git a/frontend/src/components/TokenIcons.tsx b/frontend/src/components/TokenIcons.tsx index dedd1982..509e1070 100644 --- a/frontend/src/components/TokenIcons.tsx +++ b/frontend/src/components/TokenIcons.tsx @@ -30,15 +30,15 @@ function getTokenIcon(token) { } const TokenIcons = ({ tokens }) => ( -
+
{tokens.map((token, index) => (
0 ? '-ml-3' : ''} - `} + rounded-full bg-white flex items-center justify-center w-9 h-9 + border border-gray + ${index > 0 ? '-ml-3' : ''} + `} > {token}
diff --git a/frontend/src/config/app.ts b/frontend/src/config/app.ts index 925fb41c..1d373b26 100644 --- a/frontend/src/config/app.ts +++ b/frontend/src/config/app.ts @@ -82,171 +82,211 @@ export const platformsList: PlatformsList = { const geyserList: AppGeysersList = { [Network.Mainnet]: [ + // { + // name: 'Bootstrap I (Charm USDC/SPOT)', + // address: '0x569f042C54B094A10E6fe1b52515eEC507D8da06', + // stakingToken: StakingToken.CHARM_V1, + // rewardToken: RewardToken.AMPL, + // isWrapped: false, + // poolAddress: 'https://alpha.charm.fi/ethereum/vault/0x2dcaff0f75765d7867887fc402b71c841b3a4bfb', + // slug: 'bootstrap-v1', + // lpRef: 'charmUsdcSpot', + // platform: Platform.Charm, + // exclusive: true, + // // staked pool address: 0x898adc9aa0c23dce3fed6456c34dbe2b57784325 + // }, + + { + name: 'Riverside I (STAMPL)', + address: '0xa19604b951592170DDa857CBE46609B85AB00Dee', + stakingToken: StakingToken.STAMPL, + rewardToken: RewardToken.FORTH, + isWrapped: false, + poolAddress: 'https://app.spot.cash/vault', + slug: 'riverside-v1', + lpRef: 'stampl', + platform: Platform.Ampleforth, + // staked pool address: 0xa19604b951592170DDa857CBE46609B85AB00Dee + }, + + { + name: 'Crystal II (Charm WETH/WAMPL)', + address: '0x59d177f718e902e59CF3Cbd19519194bcC437FeF', + stakingToken: StakingToken.CHARM_V1, + rewardToken: RewardToken.FORTH, + isWrapped: false, + poolAddress: 'https://alpha.charm.fi/ethereum/vault/0x9658b5bdcad59dd0b7b936d955e5df81ea2b4dcb', + slug: 'crystal-geyser-v2', + lpRef: 'charmWethWampl', + platform: Platform.Charm, + // staked pool address: 0x0c2b6bf7322a3cceb47c7ba74f2c75a19f530f11 + }, + { - name: 'Crystal V1 (Charm WETH/WAMPL)', + name: 'Crystal I (Charm WETH/WAMPL)', address: '0xEac308Fa45A9b64cfb6965e8d1237B39016862e3', stakingToken: StakingToken.CHARM_V1, rewardToken: RewardToken.FORTH, isWrapped: false, poolAddress: 'https://alpha.charm.fi/ethereum/vault/0x9658b5bdcad59dd0b7b936d955e5df81ea2b4dcb', - ref: 'crystal-geyser-v1', - lpRef: 'charm-weth-wampl', + slug: 'crystal-geyser-v1', + lpRef: 'charmWethWampl', platform: Platform.Charm, // staked pool address: 0x0c2b6bf7322a3cceb47c7ba74f2c75a19f530f11 }, { - name: 'Steamboat V1 (BillBroker USDC/SPOT)', + name: 'Steamboat I (BillBroker USDC/SPOT)', address: '0xF0a45FA4fBec33A2A51E08058bEA92761c08D7D5', stakingToken: StakingToken.BILL_BROKER, rewardToken: RewardToken.FORTH, isWrapped: false, poolAddress: 'http://app.spot.cash/broker', - ref: 'steamboat-v1', - lpRef: 'bill-broker', + slug: 'steamboat-v1', + lpRef: 'billBroker', platform: Platform.Ampleforth, // staked pool address: 0xA088Aef966CAD7fE0B38e28c2E07590127Ab4ccB }, { - name: 'Great Geyser V1 (Charm USDC/SPOT)', + name: 'Great Geyser I (Charm USDC/SPOT)', address: '0x7B2e9353D3Bf71d9f9246B1291eE29DFB11B32C7', stakingToken: StakingToken.CHARM_V1, rewardToken: RewardToken.FORTH, isWrapped: false, poolAddress: 'https://alpha.charm.fi/ethereum/vault/0x2dcaff0f75765d7867887fc402b71c841b3a4bfb', - ref: 'great-geyser-v1', - lpRef: 'charm-usdc-spot', + slug: 'great-geyser-v1', + lpRef: 'charmUsdcSpot', platform: Platform.Charm, // staked pool address: 0x898adc9aa0c23dce3fed6456c34dbe2b57784325 }, { - name: 'Beehive V7 (UniswapV2 ETH-AMPL)', + name: 'Beehive VII (UniswapV2 ETH-AMPL)', address: '0x5Ec6f02D0b657E4a56d6020Bc21F19f2Ca13EcA9', stakingToken: StakingToken.UNISWAP_V2, rewardToken: RewardToken.FORTH, isWrapped: false, poolAddress: 'https://app.uniswap.org/#/add/v2/0xD46bA6D942050d489DBd938a2C909A5d5039A161/ETH', - ref: 'beehive-v7', - lpRef: 'univ2-ampl-eth', + slug: 'beehive-v7', + lpRef: 'uniswapV2AmplEth', platform: Platform.Uniswap, // staked pool address: 0xc5be99A02C6857f9Eac67BbCE58DF5572498F40c }, { - name: 'Fly V2 (ArrakisV1 USDC/SPOT)', + name: 'Fly II (ArrakisV1 USDC/SPOT)', address: '0x392b58F407Efe1681a2EBB470600Bc2146D231a2', stakingToken: StakingToken.ARRAKIS_V1, rewardToken: RewardToken.FORTH, isWrapped: false, poolAddress: 'https://beta.arrakis.finance/vaults/1/0xDF367477C5E596af88E8797c3CDe8E28854cb79c', - ref: 'fly-v2', + slug: 'fly-v2', platform: Platform.Arrakis, // staked pool address: 0x7E0C73AF898E1ad50a8eFd7D3A678C23cD90b74C }, { - name: 'Beehive V6 (UniswapV2 ETH-AMPL)', + name: 'Beehive VI (UniswapV2 ETH-AMPL)', address: '0xfa3A1B55f77D0cEd6706283c16296F8317c70e52', stakingToken: StakingToken.UNISWAP_V2, rewardToken: RewardToken.SPOT, isWrapped: false, poolAddress: 'https://app.uniswap.org/#/add/v2/0xD46bA6D942050d489DBd938a2C909A5d5039A161/ETH', - ref: 'beehive-v6', - lpRef: 'univ2-ampl-eth', + slug: 'beehive-v6', + lpRef: 'uniswapV2AmplEth', platform: Platform.Uniswap, // staked pool address: 0xc5be99A02C6857f9Eac67BbCE58DF5572498F40c }, { - name: 'Fly V1 (ArrakisV1 USDC/SPOT)', + name: 'Fly I (ArrakisV1 USDC/SPOT)', address: '0xAA17f42C2F28ba8eF1De171C5E8e4EBd3cd5F2Ec', stakingToken: StakingToken.ARRAKIS_V1, rewardToken: RewardToken.SPOT, isWrapped: false, poolAddress: 'https://beta.arrakis.finance/vaults/1/0xDF367477C5E596af88E8797c3CDe8E28854cb79c', - ref: 'fly-v1', + slug: 'fly-v1', platform: Platform.Arrakis, // staked pool address: 0x7E0C73AF898E1ad50a8eFd7D3A678C23cD90b74C }, { - name: 'Beehive V5 (UniswapV2 ETH-AMPL)', + name: 'Beehive V (UniswapV2 ETH-AMPL)', address: '0x5Bc95edc2a05247235dd5D6d1773B8cCB95D083B', stakingToken: StakingToken.UNISWAP_V2, rewardToken: RewardToken.AMPL, isWrapped: false, poolAddress: 'https://app.uniswap.org/#/add/v2/0xD46bA6D942050d489DBd938a2C909A5d5039A161/ETH', - ref: 'beehive-v5', - lpRef: 'univ2-ampl-eth', + slug: 'beehive-v5', + lpRef: 'uniswapV2AmplEth', platform: Platform.Uniswap, // staked pool address: 0xc5be99A02C6857f9Eac67BbCE58DF5572498F40c }, { - name: 'Trinity V3 (BalancerV2 WBTC-WETH-WAMPL)', + name: 'Trinity III (BalancerV2 WBTC-WETH-WAMPL)', address: '0x13ED22A00576E41B64B686857B484987a3Ad1A3B', stakingToken: StakingToken.BALANCER_WEIGHTED_POOL_V2, rewardToken: RewardToken.AMPL, isWrapped: false, poolAddress: 'https://app.balancer.fi/#/pool/0xd4e2af4507b6b89333441c0c398edffb40f86f4d0001000000000000000002ab', - ref: 'trinity-v5', + slug: 'trinity-v5', platform: Platform.Balancer, // staked pool address: 0xd4E2af4507B6B89333441C0c398edfFB40f86f4D // poolID:0xd4e2af4507b6b89333441c0c398edffb40f86f4d0001000000000000000002ab // vault: 0xba12222222228d8ba445958a75a0704d566bf2c8 }, { - name: 'Splendid Pilot (AAVEV2 aAMPL)', + name: 'Splendid I (AAVEV2 aAMPL)', address: '0x1Fee4745E70509fBDc718beDf5050F471298c1CE', stakingToken: StakingToken.AAVE_V2_AMPL, rewardToken: RewardToken.AMPL, isWrapped: true, poolAddress: 'https://app.aave.com/reserve-overview/?underlyingAsset=0xd46ba6d942050d489dbd938a2c909a5d5039a161&marketName=proto_mainnet', - ref: 'splendid-v5', + slug: 'splendid-v5', platform: Platform.Aave, // staked pool address: 0x1e6bb68acec8fefbd87d192be09bb274170a0548 }, { - name: 'Beehive V4 (UniswapV2 ETH-AMPL)', + name: 'Beehive IV (UniswapV2 ETH-AMPL)', address: '0x88F12aE68315A89B885A2f1b0610fE2A9E1720B9', stakingToken: StakingToken.UNISWAP_V2, rewardToken: RewardToken.AMPL, isWrapped: false, poolAddress: 'https://app.uniswap.org/#/add/v2/0xD46bA6D942050d489DBd938a2C909A5d5039A161/ETH', - ref: 'beehive-v4', - lpRef: 'univ2-ampl-eth', + slug: 'beehive-v4', + lpRef: 'uniswapV2AmplEth', platform: Platform.Uniswap, // staked pool address: 0xc5be99A02C6857f9Eac67BbCE58DF5572498F40c }, { - name: 'Trinity V2 (BalancerV1 BTC-ETH-AMPL)', + name: 'Trinity II (BalancerV1 BTC-ETH-AMPL)', address: '0x0ec93391752ef1A06AA2b83D15c3a5814651C891', stakingToken: StakingToken.BALANCER_V1, rewardToken: RewardToken.AMPL, isWrapped: false, - ref: 'trinity-v2', + slug: 'trinity-v2', platform: Platform.Balancer, // staked pool address: 0xa751A143f8fe0a108800Bfb915585E4255C2FE80 }, { - name: 'Old Faithful V2 (BalancerV1 AMPL-USDC)', + name: 'Old Faithful II (BalancerV1 AMPL-USDC)', address: '0x914A766578C2397da969b3ca088e3e757249A435', stakingToken: StakingToken.BALANCER_SMART_POOL_V1, rewardToken: RewardToken.AMPL, isWrapped: false, - ref: 'old-faithful-v2', + slug: 'old-faithful-v2', platform: Platform.Balancer, // staked pool address: 0x49F2befF98cE62999792Ec98D0eE4Ad790E7786F }, { - name: 'Pescadero V2 (Sushiswap ETH-AMPL)', + name: 'Pescadero II (Sushiswap ETH-AMPL)', address: '0x56eD0272f99eBD903043399A51794f966D72E526', stakingToken: StakingToken.SUSHISWAP, rewardToken: RewardToken.AMPL, isWrapped: false, - ref: 'pescadero-v2', + slug: 'pescadero-v2', platform: Platform.Sushiswap, // staked pool address: 0xCb2286d9471cc185281c4f763d34A962ED212962 }, diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index 07830f71..e116e7d3 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -58,6 +58,7 @@ export enum StakingToken { ARRAKIS_V1, CHARM_V1, BILL_BROKER, + STAMPL, } // Reward tokens @@ -98,7 +99,7 @@ export const GET_APY_STAKE_MSG = () => 'The aggregate staking APY is an estimate based on two components: fees from liquidity provisioning and rewards from geyser emissions. This figure does not account for potential gains or losses associated with holding liquidity tokens.' export const GET_APY_WARN_MSG = () => - '1) The LP APY is estimated by annualizing the yield from swap fees generated over the past 30 days. 2) The geyser drip rate assumes that you have reached the max multiplier.' + `1) The LP APY is estimated by annualizing the yield from swap fees generated over the past 30 days. 2) The geyser drip rate assumes that you are staking at-least 1000$ worth of tokens and that you reach the max multiplier.` export const GET_REWARD_MULTIPLIER_MSG = ({ days = '30', multiplier = '3.0' }) => `Stake at-least ${days} days to achieve a ${multiplier}x reward multiplier.` diff --git a/frontend/src/context/GeyserContext.tsx b/frontend/src/context/GeyserContext.tsx index f0c90fc2..5a3304a6 100644 --- a/frontend/src/context/GeyserContext.tsx +++ b/frontend/src/context/GeyserContext.tsx @@ -23,8 +23,8 @@ export const GeyserContext = createContext<{ selectedGeyserInfo: GeyserInfo selectGeyser: (geyser: Geyser) => void selectGeyserById: (id: string) => void - getGeyserRefByName: (name: string) => string - selectGeyserByRef: (name: string) => bool + getGeyserSlugByName: (name: string) => string + selectGeyserBySlug: (name: string) => bool geyserAction: GeyserAction updateGeyserAction: (a: GeyserAction) => void handleStakeUnstake: (arg0: Vault | null, arg1: BigNumber) => Promise @@ -54,8 +54,8 @@ export const GeyserContext = createContext<{ }, selectGeyser: () => {}, selectGeyserById: () => {}, - getGeyserRefByName: () => '', - selectGeyserByRef: () => false, + getGeyserSlugByName: () => '', + selectGeyserBySlug: () => false, geyserAction: GeyserAction.STAKE, updateGeyserAction: () => {}, handleStakeUnstake: async () => undefined, @@ -266,19 +266,19 @@ export const GeyserContextProvider: React.FC = ({ children }) => { if (geyser) await selectGeyser(geyser) } - const getGeyserRefByName = (name: string): string => { + const getGeyserSlugByName = (name: string): string => { const geyserConfigs = getGeysersConfigList(networkId) const geyser = geyserConfigs.find((g) => g.name === name) if (geyser) { - return geyser.ref + return geyser.slug } else { - return geyserConfigs[0].ref + return geyserConfigs[0].slug } } - const selectGeyserByRef = async (ref: string): bool => { + const selectGeyserBySlug = async (slug: string): bool => { const geyserConfigs = getGeysersConfigList(networkId) - const geyser = geyserConfigs.find((g) => g.ref === ref) + const geyser = geyserConfigs.find((g) => g.slug === slug) if (!geyser) { return false } @@ -331,8 +331,8 @@ export const GeyserContextProvider: React.FC = ({ children }) => { selectedGeyserInfo, selectGeyser, selectGeyserById, - getGeyserRefByName, - selectGeyserByRef, + getGeyserSlugByName, + selectGeyserBySlug, geyserAction, updateGeyserAction, handleStakeUnstake, diff --git a/frontend/src/context/VaultContext.tsx b/frontend/src/context/VaultContext.tsx index 1dfb18da..45387d4b 100644 --- a/frontend/src/context/VaultContext.tsx +++ b/frontend/src/context/VaultContext.tsx @@ -25,6 +25,7 @@ export const VaultContext = createContext<{ | null rewardAmountClaimedOnUnstake: ((receipt: TransactionReceipt) => Promise) | null loading: bool + otherActiveLock: bool }>({ vaults: [], selectedVault: null, @@ -36,12 +37,14 @@ export const VaultContext = createContext<{ withdrawUnlockedFromVault: null, rewardAmountClaimedOnUnstake: null, loading: false, + otherActiveLock: false, }) export const VaultContextProvider: React.FC = ({ children }) => { const { address, signer, ready, networkId } = useContext(Web3Context) const { selectedGeyserInfo: { geyser: selectedGeyser }, + selectedGeyserConfig, loading: geyserLoading, geysers, } = useContext(GeyserContext) @@ -52,6 +55,7 @@ export const VaultContextProvider: React.FC = ({ children }) => { const [vaults, setVaults] = useState([]) const [selectedVault, setSelectedVault] = useState(null) const [currentLock, setCurrentLock] = useState(null) + const [otherActiveLock, setOtherActiveLock] = useState(false) const selectVault = (vault: Vault) => setSelectedVault(vault) const selectVaultById = (id: string) => setSelectedVault(vaults.find((vault) => vault.id === id) || selectedVault) @@ -83,6 +87,7 @@ export const VaultContextProvider: React.FC = ({ children }) => { setVaults([]) setSelectedVault(null) setCurrentLock(null) + setOtherActiveLock(false) if (ready) { getVaults({ variables: { id: address.toLowerCase() } }) } @@ -103,12 +108,19 @@ export const VaultContextProvider: React.FC = ({ children }) => { }, [vaultData, selectedVault, geyserLoading]) useEffect(() => { - if (address && selectedVault && geysers.length > 0 && selectedGeyser) { + if (address && selectedVault && geysers.length > 0 && selectedGeyser && selectedGeyserConfig) { const { stakingToken } = selectedGeyser const lockId = `${selectedVault.id}-${selectedGeyser.id}-${stakingToken}` setCurrentLock(selectedVault.locks.find((lock) => lock.id === lockId) || null) + setOtherActiveLock( + !!selectedGeyserConfig.exclusive && + !!selectedVault.locks.find( + (lock) => lock.token === selectedGeyser.stakingToken && lock.geyser.id !== selectedGeyser.id, + ), + ) } else { setCurrentLock(null) + setOtherActiveLock(false) } }, [address, selectedVault, selectedGeyser, geyserLoading]) @@ -125,6 +137,7 @@ export const VaultContextProvider: React.FC = ({ children }) => { withdrawUnlockedFromVault, rewardAmountClaimedOnUnstake, loading: vaultLoading || geyserLoading, + otherActiveLock, }} > {children} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 8e7715cf..0e9b62ea 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -152,8 +152,11 @@ export type GeyserConfig = { address: string stakingToken: StakingToken rewardToken: RewardToken - isWrapped: boolean + isWrapped?: boolean poolAddress?: string + exclusive?: boolean + slug: string + platform: Platform } export type AdditionalTokenConfig = { diff --git a/frontend/src/utils/abis/Stampl.ts b/frontend/src/utils/abis/Stampl.ts new file mode 100644 index 00000000..5767c184 --- /dev/null +++ b/frontend/src/utils/abis/Stampl.ts @@ -0,0 +1,519 @@ +export const STAMPL_ABI = [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { inputs: [], name: 'DeployedCountOverLimit', type: 'error' }, + { inputs: [], name: 'InsufficientDeployment', type: 'error' }, + { inputs: [], name: 'InsufficientLiquidity', type: 'error' }, + { inputs: [], name: 'OutOfBounds', type: 'error' }, + { inputs: [], name: 'UnacceptableSwap', type: 'error' }, + { inputs: [], name: 'UnacceptableTrancheLength', type: 'error' }, + { inputs: [], name: 'UnauthorizedCall', type: 'error' }, + { inputs: [], name: 'UnauthorizedTransferOut', type: 'error' }, + { inputs: [], name: 'UnexpectedAsset', type: 'error' }, + { inputs: [], name: 'UnexpectedDecimals', type: 'error' }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, + { indexed: true, internalType: 'address', name: 'spender', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'balance', type: 'uint256' }, + ], + name: 'AssetSynced', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'uint8', name: 'version', type: 'uint8' }], + name: 'Initialized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'previousOwner', type: 'address' }, + { indexed: true, internalType: 'address', name: 'newOwner', type: 'address' }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'address', name: 'account', type: 'address' }], + name: 'Paused', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [{ indexed: false, internalType: 'address', name: 'account', type: 'address' }], + name: 'Unpaused', + type: 'event', + }, + { + inputs: [], + name: 'MAX_DEPLOYED_COUNT', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'ONE', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'PERC_DECIMALS', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'TRANCHE_DUST_AMT', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'address', name: 'spender', type: 'address' }, + ], + name: 'allowance', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'i', type: 'uint256' }], + name: 'assetAt', + outputs: [{ internalType: 'contract IERC20Upgradeable', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'assetCount', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + name: 'burn', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'account', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'burnFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'underlyingAmtIn', type: 'uint256' }], + name: 'computeMintAmt', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'perpAmtIn', type: 'uint256' }], + name: 'computePerpToUnderlyingSwapAmt', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { + components: [ + { internalType: 'uint256', name: 'perpTVL', type: 'uint256' }, + { internalType: 'uint256', name: 'vaultTVL', type: 'uint256' }, + { internalType: 'uint256', name: 'seniorTR', type: 'uint256' }, + ], + internalType: 'struct SubscriptionParams', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'noteAmtBurnt', type: 'uint256' }], + name: 'computeRedemptionAmts', + outputs: [ + { + components: [ + { internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + internalType: 'struct TokenAmount[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'underlyingAmtIn', type: 'uint256' }], + name: 'computeUnderlyingToPerpSwapAmt', + outputs: [ + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { + components: [ + { internalType: 'uint256', name: 'perpTVL', type: 'uint256' }, + { internalType: 'uint256', name: 'vaultTVL', type: 'uint256' }, + { internalType: 'uint256', name: 'seniorTR', type: 'uint256' }, + ], + internalType: 'struct SubscriptionParams', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'decimals', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'subtractedValue', type: 'uint256' }, + ], + name: 'decreaseAllowance', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { inputs: [], name: 'deploy', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [{ internalType: 'uint256', name: 'underlyingAmtIn', type: 'uint256' }], + name: 'deposit', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'deviationRatio', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'feePolicy', + outputs: [{ internalType: 'contract IFeePolicy', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTVL', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }], + name: 'getVaultAssetValue', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'spender', type: 'address' }, + { internalType: 'uint256', name: 'addedValue', type: 'uint256' }, + ], + name: 'increaseAllowance', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: 'name', type: 'string' }, + { internalType: 'string', name: 'symbol', type: 'string' }, + { internalType: 'contract IPerpetualTranche', name: 'perp_', type: 'address' }, + { internalType: 'contract IFeePolicy', name: 'feePolicy_', type: 'address' }, + ], + name: 'init', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }], + name: 'isVaultAsset', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'keeper', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'minDeploymentAmt', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { inputs: [], name: 'pause', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [], + name: 'paused', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'perp', + outputs: [{ internalType: 'contract IPerpetualTranche', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }], + name: 'recover', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { inputs: [], name: 'recover', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [{ internalType: 'uint256', name: 'notes', type: 'uint256' }], + name: 'recoverAndRedeem', + outputs: [ + { + components: [ + { internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + internalType: 'struct TokenAmount[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { inputs: [], name: 'recoverAndRedeploy', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [{ internalType: 'uint256', name: 'notes', type: 'uint256' }], + name: 'redeem', + outputs: [ + { + components: [ + { internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + internalType: 'struct TokenAmount[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { inputs: [], name: 'renounceOwnership', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [], + name: 'reservedSubscriptionPerc', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'reservedUnderlyingBal', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'perpAmtIn', type: 'uint256' }], + name: 'swapPerpsForUnderlying', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'underlyingAmtIn', type: 'uint256' }], + name: 'swapUnderlyingForPerps', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'transfer', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'transferERC20', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'underlying', + outputs: [{ internalType: 'contract IERC20Upgradeable', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { inputs: [], name: 'unpause', outputs: [], stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [{ internalType: 'contract IFeePolicy', name: 'feePolicy_', type: 'address' }], + name: 'updateFeePolicy', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'keeper_', type: 'address' }], + name: 'updateKeeper', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'minDeploymentAmt_', type: 'uint256' }], + name: 'updateMinDeploymentAmt', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'reservedSubscriptionPerc_', type: 'uint256' }], + name: 'updateReservedSubscriptionPerc', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'reservedUnderlyingBal_', type: 'uint256' }], + name: 'updateReservedUnderlyingBal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'contract IERC20Upgradeable', name: 'token', type: 'address' }], + name: 'vaultAssetBalance', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +] diff --git a/frontend/src/utils/stakingToken.ts b/frontend/src/utils/stakingToken.ts index cdc1d71e..f5b1b578 100644 --- a/frontend/src/utils/stakingToken.ts +++ b/frontend/src/utils/stakingToken.ts @@ -16,6 +16,7 @@ import { ARRAKIS_V1_ABI } from './abis/ArrakisV1' import { CHARM_V1_ABI } from './abis/CharmV1' import { UNISWAP_V3_POOL_ABI } from './abis/UniswapV3Pool' import { BILL_BROKER_ABI } from './abis/BillBroker' +import { STAMPL_ABI } from './abis/Stampl' import { SPOT_APPRAISER_ABI } from './abis/SpotAppraiser' import { getCurrentPrice } from './price' import { defaultTokenInfo, getTokenInfo } from './token' @@ -66,6 +67,8 @@ export const getStakingTokenInfo = async ( return getCharmV1(tokenAddress, signerOrProvider) case StakingToken.BILL_BROKER: return getBillBroker(tokenAddress, signerOrProvider) + case StakingToken.STAMPL: + return getSTAMPL(tokenAddress, signerOrProvider) default: throw new Error(`Handler for ${token} not found`) } @@ -494,3 +497,35 @@ const getBillBroker = async (tokenAddress: string, signerOrProvider: SignerOrPro wrappedToken: null, } } + +const getSTAMPL = async (tokenAddress: string, signerOrProvider: SignerOrProvider): Promise => { + const address = toChecksumAddress(tokenAddress) + const { name, symbol, decimals } = await getTokenInfo(address, signerOrProvider) + + const contract = new Contract(address, STAMPL_ABI, signerOrProvider) + const ampl = await contract.underlying() + const amplTokenInfo = await getTokenInfo(ampl, signerOrProvider) + const amplPrice = await getCurrentPrice(amplTokenInfo.symbol) + + const tvl = parseFloat(formatUnits(await contract.callStatic.getTVL(), amplTokenInfo.decimals)) + const totalSupply = parseFloat(formatUnits(await contract.totalSupply(), decimals)) + const tokenCompositions = [ + { + address: ampl.address, + ...amplTokenInfo, + balance: tvl, + value: amplPrice * tvl, + weight: 1.0, + }, + ] + const marketCap = tokenCompositions[0].value + return { + address, + decimals, + name, + symbol, + price: marketCap / totalSupply, + composition: tokenCompositions, + wrappedToken: null, + } +} diff --git a/frontend/src/utils/stats.ts b/frontend/src/utils/stats.ts index 34359cb7..f8306cc4 100644 --- a/frontend/src/utils/stats.ts +++ b/frontend/src/utils/stats.ts @@ -179,6 +179,7 @@ export const getUserDrip = async ( const lockStakeUnitsAfterDuration = getLockStakeUnits(lock, afterDuration).add(stakeUnitsFromAdditionalStake) if (totalStakeUnitsAfterDuration.isZero()) return 0 const futureUserDrip = poolDrip.mul(lockStakeUnitsAfterDuration).div(totalStakeUnitsAfterDuration) + if (futureUserDrip.sub(currentUserDrip).lt('0')) return 0 const userDrip = futureUserDrip.sub(currentUserDrip) return parseInt(userDrip.toString(), 10) } diff --git a/hardhat.config.ts b/hardhat.config.ts index 1109dd5c..9d1fe47d 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -86,7 +86,7 @@ task('deploy', 'deploy full set of factory contracts') UniversalVault.address, ]) const GeyserRegistry = await deployContract('GeyserRegistry', ethers.getContractFactory, signer) - const RouterV1 = await deployContract('RouterV1', ethers.getContractFactory, signer) + const GeyserRouter = await deployContract('GeyserRouter', ethers.getContractFactory, signer) if (mock) { const totalSupply = parseUnits('10') @@ -129,9 +129,9 @@ task('deploy', 'deploy full set of factory contracts') GeyserTemplate: { abi: (await ethers.getContractAt('Geyser', ethers.constants.AddressZero)).interface.format(), }, - RouterV1: { - address: RouterV1.address, - abi: RouterV1.interface.format(), + GeyserRouter: { + address: GeyserRouter.address, + abi: GeyserRouter.interface.format(), }, }) @@ -201,10 +201,11 @@ task('create-geyser', 'deploy an instance of Geyser') .addParam('ceiling', 'the ceiling of reward scaling') .addParam('time', 'the time of reward scaling in seconds') .addOptionalParam('finalOwner', 'the address of the final owner', '0x') + .addOptionalParam('instanceType', 'the type of geyser to be deployed', 'Geyser') .addOptionalParam('factoryVersion', 'the factory version', 'latest') .setAction( async ( - { factoryVersion, stakingToken, rewardToken, floor, ceiling, time, finalOwner }, + { factoryVersion, stakingToken, rewardToken, floor, ceiling, time, finalOwner, instanceType }, { ethers, run, upgrades, network }, ) => { await run('compile') @@ -216,7 +217,7 @@ task('create-geyser', 'deploy an instance of Geyser') readFileSync(`${SDK_PATH}/deployments/${network.name}/factories-${factoryVersion}.json`).toString(), ) - const factory = await ethers.getContractFactory('Geyser', signer) + const factory = await ethers.getContractFactory(instanceType, signer) const geyser = await upgrades.deployProxy(factory, undefined, { initializer: false, }) @@ -234,10 +235,7 @@ task('create-geyser', 'deploy an instance of Geyser') console.log(' reward ceiling', ceiling) // CRITICAL: The ordering of the following transaction can't change for the subgraph to be indexed - - // Note: geyser registry is owned by the ecofund multisig - // this script will fail here - // the following need to be executed manually + // NOTE: geyser registry is currently owned by the ampeforth deploy wallet. console.log('Register Geyser Instance') const geyserRegistry = await ethers.getContractAt('GeyserRegistry', GeyserRegistry.address, signer) await (await geyserRegistry.register(geyser.address)).wait(1) @@ -269,7 +267,11 @@ task('create-geyser', 'deploy an instance of Geyser') ) await (await powerSwitch.transferOwnership(finalOwner)).wait(1) await (await geyser.transferOwnership(finalOwner)).wait(1) - await (await proxyAdmin.transferOwnership(finalOwner)).wait(1) + try { + await (await proxyAdmin.transferOwnership(finalOwner)).wait(1) + } catch (e) { + console.log('Proxy admin not owned by deployer') + } } }, ) diff --git a/subgraph/src/geyser.ts b/subgraph/src/geyser.ts index 3b9bd2a8..4093150c 100644 --- a/subgraph/src/geyser.ts +++ b/subgraph/src/geyser.ts @@ -18,11 +18,7 @@ import { Staked, Unstaked, } from '../generated/templates/GeyserTemplate/GeyserContract' -import { - EmergencyShutdown, - PowerOff, - PowerOn, -} from '../generated/templates/PowerSwitchTemplate/PowerSwitchContract' +import { EmergencyShutdown, PowerOff, PowerOn } from '../generated/templates/PowerSwitchTemplate/PowerSwitchContract' import { ERC20 } from '../generated/templates/GeyserTemplate/ERC20' import { RebasingERC20 } from '../generated/templates' @@ -199,11 +195,7 @@ function updateVaultStake(geyserAddress: Address, vaultAddress: Address, timesta let geyserContract = GeyserContract.bind(geyserAddress) let lock = new Lock( - vaultAddress.toHex() + - '-' + - geyserAddress.toHex() + - '-' + - geyserContract.getGeyserData().stakingToken.toHex(), + vaultAddress.toHex() + '-' + geyserAddress.toHex() + '-' + geyserContract.getGeyserData().stakingToken.toHex(), ) lock.geyser = geyserAddress.toHex() lock.vault = vaultAddress.toHex() diff --git a/subgraph/src/vault.ts b/subgraph/src/vault.ts index d04935fa..c1cc76a7 100644 --- a/subgraph/src/vault.ts +++ b/subgraph/src/vault.ts @@ -10,12 +10,7 @@ import { Lock, LockedBalance, User, Vault } from '../generated/schema' import { Transfer } from '../generated/UniversalVaultNFT/ERC721' // entity imports -import { - Locked, - RageQuit, - Unlocked, - VaultContract, -} from '../generated/templates/VaultTemplate/VaultContract' +import { Locked, RageQuit, Unlocked, VaultContract } from '../generated/templates/VaultTemplate/VaultContract' // template instantiation function updateVault(vaultAddress: Address): void { @@ -40,18 +35,11 @@ export function handleNewVault(event: InstanceAdded): void { } // event handlers -function updateLock( - vaultAddress: Address, - geyserAddress: Address, - tokenAddress: Address, - timestamp: BigInt, -): void { +function updateLock(vaultAddress: Address, geyserAddress: Address, tokenAddress: Address, timestamp: BigInt): void { updateVault(vaultAddress) let vaultContract = VaultContract.bind(vaultAddress) - let lock = new Lock( - vaultAddress.toHex() + '-' + geyserAddress.toHex() + '-' + tokenAddress.toHex(), - ) + let lock = new Lock(vaultAddress.toHex() + '-' + geyserAddress.toHex() + '-' + tokenAddress.toHex()) let lockedBalance = new LockedBalance(vaultAddress.toHex() + '-' + tokenAddress.toHex()) lock.amount = vaultContract.getBalanceDelegated(tokenAddress, geyserAddress) @@ -82,12 +70,7 @@ export function handleRageQuit(event: RageQuit): void { } function bigIntToAddress(value: BigInt): Address { - return Address.fromString( - value - .toHex() - .slice(2) - .padStart(40, '0'), - ) + return Address.fromString(value.toHex().slice(2).padStart(40, '0')) } export function handleTransfer(event: Transfer): void {