diff --git a/packages/contracts/src/abis/evergreenFarming.ts b/packages/contracts/src/abis/evergreenFarming.ts new file mode 100644 index 00000000000..782323724aa --- /dev/null +++ b/packages/contracts/src/abis/evergreenFarming.ts @@ -0,0 +1,652 @@ +export const EVERGREEN_FARMING_ABI = [ + { + inputs: [ + { + internalType: 'address', + name: '_owner', + type: 'address', + }, + { + internalType: 'address', + name: '_rewardsDistribution', + type: 'address', + }, + { + internalType: 'address', + name: '_rewardsToken', + type: 'address', + }, + { + internalType: 'address', + name: '_stakingToken', + type: 'address', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'oldOwner', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnerChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnerNominated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'isPaused', + type: 'bool', + }, + ], + name: 'PauseChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Recovered', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + ], + name: 'RewardAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'user', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + ], + name: 'RewardPaid', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'newDuration', + type: 'uint256', + }, + ], + name: 'RewardsDurationUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'user', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Staked', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'user', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Withdrawn', + type: 'event', + }, + { + constant: false, + inputs: [], + name: 'acceptOwnership', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'earned', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [], + name: 'exit', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [], + name: 'getReward', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'getRewardForDuration', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'lastPauseTime', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'lastTimeRewardApplicable', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'lastUpdateTime', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: '_owner', + type: 'address', + }, + ], + name: 'nominateNewOwner', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'nominatedOwner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: 'reward', + type: 'uint256', + }, + ], + name: 'notifyRewardAmount', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'paused', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'periodFinish', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenAmount', + type: 'uint256', + }, + ], + name: 'recoverERC20', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'rewardPerToken', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'rewardPerTokenStored', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'rewardRate', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'rewards', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'rewardsDistribution', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'rewardsDuration', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'rewardsToken', + outputs: [ + { + internalType: 'contract IERC20', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'bool', + name: '_paused', + type: 'bool', + }, + ], + name: 'setPaused', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'address', + name: '_rewardsDistribution', + type: 'address', + }, + ], + name: 'setRewardsDistribution', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: '_rewardsDuration', + type: 'uint256', + }, + ], + name: 'setRewardsDuration', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'stake', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'stakingToken', + outputs: [ + { + internalType: 'contract IERC20', + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'userRewardPerTokenPaid', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'withdraw', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, +] as const diff --git a/packages/contracts/src/abis/index.ts b/packages/contracts/src/abis/index.ts index 1ee11c1e594..8131b7d8d4d 100644 --- a/packages/contracts/src/abis/index.ts +++ b/packages/contracts/src/abis/index.ts @@ -4,6 +4,7 @@ export * from './arbRetryableTx' export * from './arbSys' export * from './iBep20' export * from './farming' +export * from './evergreenFarming' export * from './foxy' export * from './foxyStaking' export * from './l1ArbitrumGateway' diff --git a/packages/contracts/src/constants.ts b/packages/contracts/src/constants.ts index e9cf958a62e..4f19e01c186 100644 --- a/packages/contracts/src/constants.ts +++ b/packages/contracts/src/constants.ts @@ -1,5 +1,6 @@ import { type Address, erc20Abi } from 'viem' +import { EVERGREEN_FARMING_ABI } from './abis/evergreenFarming' import { FARMING_ABI } from './abis/farming' import { I_UNISWAP_V2_PAIR_ABI } from './abis/iUniswapV2Pair' import { THORCHAIN_ROUTER_ABI } from './abis/thorchainRouter' @@ -19,6 +20,8 @@ export const ETH_FOX_STAKING_V6_CONTRACT = '0xebb1761ad43034fd7faa64d84e5bbd8cb5 export const ETH_FOX_STAKING_V7_CONTRACT = '0x5939783dbf3e9f453a69bc9ddc1e492efac1fbcb' as const export const ETH_FOX_STAKING_V8_CONTRACT = '0x662da6c777a258382f08b979d9489c3fbbbd8ac3' as const export const ETH_FOX_STAKING_V9_CONTRACT = '0x721720784b76265aa3e34c1c7ba02a6027bcd3e5' as const +export const ETH_FOX_STAKING_EVERGREEN_CONTRACT = + '0xe7e16e2b05440c2e484c5c41ac3e5a4d15da2744' as const export const foxEthStakingContractAddresses = [ ETH_FOX_STAKING_V9_CONTRACT, @@ -30,6 +33,7 @@ export const foxEthStakingContractAddresses = [ ETH_FOX_STAKING_V3_CONTRACT, ETH_FOX_STAKING_V2_CONTRACT, ETH_FOX_STAKING_V1_CONTRACT, + ETH_FOX_STAKING_EVERGREEN_CONTRACT, ] as const // Exported as a string literal for contract address discrimination purposes @@ -67,6 +71,7 @@ export const CONTRACT_ADDRESS_TO_ABI = { [ETH_FOX_STAKING_V7_CONTRACT]: FARMING_ABI, [ETH_FOX_STAKING_V8_CONTRACT]: FARMING_ABI, [ETH_FOX_STAKING_V9_CONTRACT]: FARMING_ABI, + [ETH_FOX_STAKING_EVERGREEN_CONTRACT]: EVERGREEN_FARMING_ABI, [FOX_TOKEN_CONTRACT]: erc20Abi, [UNISWAP_V2_ROUTER_02_CONTRACT_MAINNET]: UNISWAP_V2_ROUTER_02_ABI, // THOR Router Mainnet diff --git a/packages/contracts/src/types.ts b/packages/contracts/src/types.ts index 79873675198..cb39b6df5db 100644 --- a/packages/contracts/src/types.ts +++ b/packages/contracts/src/types.ts @@ -1,5 +1,6 @@ -import type { GetContractReturnType, PublicClient } from 'viem' +import type { Address, GetContractReturnType, PublicClient } from 'viem' +import type { EVERGREEN_FARMING_ABI, FARMING_ABI } from './abis' import type { CONTRACT_ADDRESS_TO_ABI, CONTRACT_TYPE_TO_ABI, @@ -46,3 +47,7 @@ export type DefinedContract = { } export type FoxEthStakingContractAddress = (typeof foxEthStakingContractAddresses)[number] +export type FoxEthStakingContractAbi = typeof FARMING_ABI | typeof EVERGREEN_FARMING_ABI +export type FoxEthStakingContract = T extends typeof FARMING_ABI + ? GetContractReturnType + : GetContractReturnType diff --git a/src/pages/Defi/components/EligibleCarousel.tsx b/src/pages/Defi/components/EligibleCarousel.tsx index 828ec5d0fcc..f4a0f8f6814 100644 --- a/src/pages/Defi/components/EligibleCarousel.tsx +++ b/src/pages/Defi/components/EligibleCarousel.tsx @@ -1,6 +1,6 @@ import type { CardProps } from '@chakra-ui/react' import { Button, Card, Flex, Heading } from '@chakra-ui/react' -import { ETH_FOX_STAKING_V9_CONTRACT } from '@shapeshiftoss/contracts' +import { ETH_FOX_STAKING_EVERGREEN_CONTRACT } from '@shapeshiftoss/contracts' import { uniqBy } from 'lodash' import { useCallback, useMemo } from 'react' import { useTranslate } from 'react-polyglot' @@ -50,29 +50,30 @@ export const EligibleCarousel: React.FC = props => { ) .slice(0, 5) - const foxFarmingV9 = eligibleOpportunities.find( - eligibleOpportunity => eligibleOpportunity.contractAddress === ETH_FOX_STAKING_V9_CONTRACT, + const foxFarmingEvergreen = eligibleOpportunities.find( + eligibleOpportunity => + eligibleOpportunity.contractAddress === ETH_FOX_STAKING_EVERGREEN_CONTRACT, ) const rfoxOpportunity = eligibleOpportunities.find( eligibleOpportunity => eligibleOpportunity.provider === DefiProvider.rFOX, ) - if (!foxFarmingV9 && !rfoxOpportunity) { + if (!foxFarmingEvergreen && !rfoxOpportunity) { return filteredEligibleOpportunities } - const filteredEligibleOpportunitiesWithFoxFarmingV9 = uniqBy( + const filteredEligibleOpportunitiesWithFoxFarmingEvergreen = uniqBy( [ rfoxOpportunity, filteredEligibleOpportunities[0], - foxFarmingV9, + foxFarmingEvergreen, ...filteredEligibleOpportunities.slice(1), ].filter(isSome), 'contractAddress', ).slice(0, 5) - return filteredEligibleOpportunitiesWithFoxFarmingV9 + return filteredEligibleOpportunitiesWithFoxFarmingEvergreen }, [eligibleOpportunities]) const renderEligibleCards = useMemo(() => { diff --git a/src/state/slices/opportunitiesSlice/constants.ts b/src/state/slices/opportunitiesSlice/constants.ts index d06d44f7de4..d3a161dc629 100644 --- a/src/state/slices/opportunitiesSlice/constants.ts +++ b/src/state/slices/opportunitiesSlice/constants.ts @@ -1,7 +1,10 @@ import type { AssetId } from '@shapeshiftoss/caip' import { ethAssetId, foxAssetId, foxOnArbitrumOneAssetId } from '@shapeshiftoss/caip' import type { FoxEthStakingContractAddress } from '@shapeshiftoss/contracts' -import { foxEthStakingContractAddresses } from '@shapeshiftoss/contracts' +import { + ETH_FOX_STAKING_EVERGREEN_CONTRACT, + foxEthStakingContractAddresses, +} from '@shapeshiftoss/contracts' import { getTypeGuardAssertion } from 'lib/utils' import type { DefiProviderMetadata, LpId, StakingId } from './types' @@ -45,6 +48,7 @@ export const foxEthStakingAssetIdV8: AssetId = 'eip155:1/erc20:0x662da6c777a258382f08b979d9489c3fbbbd8ac3' export const foxEthStakingAssetIdV9: AssetId = 'eip155:1/erc20:0x721720784b76265aa3e34c1c7ba02a6027bcd3e5' +export const foxEthStakingAssetIdEvergreen: AssetId = `eip155:1/erc20:${ETH_FOX_STAKING_EVERGREEN_CONTRACT}` // Tuple of all staking contracts as AssetIds, to iterate over and dispatch RTK queries for export const foxEthAssetIds = [ @@ -57,6 +61,7 @@ export const foxEthAssetIds = [ foxEthStakingAssetIdV7, foxEthStakingAssetIdV8, foxEthStakingAssetIdV9, + // foxEthStakingAssetIdEvergreen, // TODO: Uncomment when Evergreen staking is ready for go-live ] as const export const foxEthStakingIds = foxEthAssetIds as readonly StakingId[] @@ -70,6 +75,7 @@ export const STAKING_ID_TO_VERSION = { [foxEthStakingAssetIdV7]: 'V7', [foxEthStakingAssetIdV8]: 'V8', [foxEthStakingAssetIdV9]: 'V9', + [foxEthStakingAssetIdEvergreen]: 'Evergreen', } export const rFOXStakingIds = [foxOnArbitrumOneAssetId] as readonly StakingId[] diff --git a/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/index.ts b/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/index.ts index 9e53c815dd0..2ad38f87ba3 100644 --- a/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/index.ts +++ b/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/index.ts @@ -1,4 +1,5 @@ import { foxAssetId, fromAccountId, fromAssetId } from '@shapeshiftoss/caip' +import type { FoxEthStakingContract, FoxEthStakingContractAbi } from '@shapeshiftoss/contracts' import { ETH_FOX_POOL_CONTRACT, fetchUniV2PairData, @@ -53,7 +54,9 @@ export const ethFoxStakingMetadataResolver = async ({ } assertIsFoxEthStakingContractAddress(contractAddress) - const foxFarmingContract = getOrCreateContractByAddress(contractAddress) + const foxFarmingContract = getOrCreateContractByAddress( + contractAddress, + ) as FoxEthStakingContract const uniV2LPContract = getOrCreateContractByAddress(ETH_FOX_POOL_CONTRACT) // tvl diff --git a/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/utils.ts b/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/utils.ts index e6528738252..b780fa6492c 100644 --- a/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/utils.ts +++ b/src/state/slices/opportunitiesSlice/resolvers/ethFoxStaking/utils.ts @@ -1,6 +1,5 @@ -import type { FARMING_ABI } from '@shapeshiftoss/contracts' +import type { FoxEthStakingContract, FoxEthStakingContractAbi } from '@shapeshiftoss/contracts' import { memoize } from 'lodash' -import type { Address, GetContractReturnType, PublicClient } from 'viem' import { bnOrZero } from 'lib/bignumber/bignumber' export const makeTotalLpApr = (foxRewardRatePerToken: string, foxEquivalentPerLPToken: string) => @@ -15,15 +14,12 @@ export const makeTotalLpApr = (foxRewardRatePerToken: string, foxEquivalentPerLP // Rate of FOX given per second for all staked addresses) const getRewardsRate = memoize( - async ( - farmingRewardsContract: GetContractReturnType, - ) => await farmingRewardsContract.read.rewardRate(), + async (farmingRewardsContract: FoxEthStakingContract) => + await farmingRewardsContract.read.rewardRate(), ) const getTotalLpSupply = memoize( - async ( - farmingRewardsContract: GetContractReturnType, - ) => { + async (farmingRewardsContract: FoxEthStakingContract) => { try { const totalSupply = await farmingRewardsContract.read.totalSupply() return bnOrZero(totalSupply.toString()) @@ -36,9 +32,7 @@ const getTotalLpSupply = memoize( ) export const rewardRatePerToken = memoize( - async ( - farmingRewardsContract: GetContractReturnType, - ) => { + async (farmingRewardsContract: FoxEthStakingContract) => { try { const rewardRate = await getRewardsRate(farmingRewardsContract) const totalSupply = await getTotalLpSupply(farmingRewardsContract)