diff --git a/README.md b/README.md
index 72af0ad7..65d51d06 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,18 @@
-# Lyra Interface
+# Kwenta Options Interface
An open source interface for the Lyra Protocol, a decentralized options exchange built on Optimistic Ethereum.
-## Accessing the Lyra Interface
+## Accessing the Kwenta Options Interface
-To access the Lyra Interface, use an IPFS gateway link from the [latest release](https://github.com/lyra-finance/interface/releases/latest) or visit [app.lyra.finance](https://app.lyra.finance).
+To access the Kwenta Options Interface, use an IPFS gateway link from the [latest release](https://github.com/Kwenta/lyra-interface/releases/latest) or visit [options.kwenta.eth.limo](https://options.kwenta.eth.limo).
## Contributions
-The Lyra Interface is open source, permissionless software that can be developed and hosted by anyone. Thanks in advance for your contributions!
+The Kwenta Options Interface is open source, permissionless software that can be developed and hosted by anyone. Thanks in advance for your contributions! Contributors should fork this repository and submit pull requests against the `kwenta/lyra-interface:dev` branch.
+
+The `master` branch will be regularly synchronized in line with changes incoming from the `lyra-finance/interface:master` branch by the devDAO PM.
+
+Once changes in the `dev` branch have been thoroughly tested and approved, new releases to the Kwenta-hosted Lyra frontend will be pushed to the `deploy` branch.
### Setup
@@ -26,4 +30,4 @@ yarn start
```
yarn build
-```
+```
\ No newline at end of file
diff --git a/app/.env.production b/app/.env.production
index b7d66b10..c52f2a2a 100644
--- a/app/.env.production
+++ b/app/.env.production
@@ -10,6 +10,8 @@ REACT_APP_ENABLE_TERMS_OF_USE=true
# Node Providers
REACT_APP_INFURA_PROJECT_ID=0454e8da3dd7418c8cc8bf0961417068
REACT_APP_ALCHEMY_PROJECT_ID=PAYPVgUFFCJE6uKwBWzZI_PjrSF79-0I
+REACT_APP_ALCHEMY_ARBITRUM_PROJECT_ID=zHh7If3eVmL8WETSWzpqFV61YqUNARwe
+REACT_APP_ALCHEMY_ETHEREUM_PROJECT_ID=7CT7Q2obESWD4Kfvf8tUo2PynUzaZh1N
# Analytics
REACT_APP_POST_HOG_API_KEY=phc_1duTJdHbYFcVN268FV4CI3vDf3dgM0AqKsBkM2nasBt
diff --git a/app/craco.config.cjs b/app/craco.config.cjs
index be66714a..399c2624 100644
--- a/app/craco.config.cjs
+++ b/app/craco.config.cjs
@@ -25,6 +25,14 @@ module.exports = {
new BundleAnalyzerPlugin({ analyzerMode: process.env.REACT_APP_INTERACTIVE_ANALYZE ? 'server' : 'json' })
)
}
+ if (process.env.NODE_ENV !== 'production') {
+ webpackConfig.devtool = 'eval-cheap-module-source-map'
+ webpackConfig.ignoreWarnings = [
+ {
+ message: /Failed to parse source map/,
+ },
+ ]
+ }
webpackConfig.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
diff --git a/app/public/favicon/android-chrome-192x192.png b/app/public/favicon/android-chrome-192x192.png
index 248dae15..f9f2059c 100644
Binary files a/app/public/favicon/android-chrome-192x192.png and b/app/public/favicon/android-chrome-192x192.png differ
diff --git a/app/public/favicon/android-chrome-512x512.png b/app/public/favicon/android-chrome-512x512.png
index 42377d31..6dc4ffce 100644
Binary files a/app/public/favicon/android-chrome-512x512.png and b/app/public/favicon/android-chrome-512x512.png differ
diff --git a/app/public/favicon/apple-touch-icon.png b/app/public/favicon/apple-touch-icon.png
index 8364b5ba..b658b2f6 100644
Binary files a/app/public/favicon/apple-touch-icon.png and b/app/public/favicon/apple-touch-icon.png differ
diff --git a/app/public/favicon/favicon-16x16.png b/app/public/favicon/favicon-16x16.png
index 79185e4c..c7d35dc1 100644
Binary files a/app/public/favicon/favicon-16x16.png and b/app/public/favicon/favicon-16x16.png differ
diff --git a/app/public/favicon/favicon-32x32.png b/app/public/favicon/favicon-32x32.png
index 8370bb9e..e3175c55 100644
Binary files a/app/public/favicon/favicon-32x32.png and b/app/public/favicon/favicon-32x32.png differ
diff --git a/app/public/favicon/favicon.ico b/app/public/favicon/favicon.ico
index e2e81b7f..fe267276 100644
Binary files a/app/public/favicon/favicon.ico and b/app/public/favicon/favicon.ico differ
diff --git a/app/public/favicon/mstile-150x150.png b/app/public/favicon/mstile-150x150.png
index e9b8debe..35ed595e 100644
Binary files a/app/public/favicon/mstile-150x150.png and b/app/public/favicon/mstile-150x150.png differ
diff --git a/app/public/favicon/safari-pinned-tab.svg b/app/public/favicon/safari-pinned-tab.svg
index c64df8e2..23e2e8ec 100644
--- a/app/public/favicon/safari-pinned-tab.svg
+++ b/app/public/favicon/safari-pinned-tab.svg
@@ -2,42 +2,29 @@
Created by potrace 1.14, written by Peter Selinger 2001-2017
-
-
-
+
diff --git a/app/public/images/logo-yellow.svg b/app/public/images/logo-yellow.svg
new file mode 100644
index 00000000..a3929a3f
--- /dev/null
+++ b/app/public/images/logo-yellow.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/public/images/logo.svg b/app/public/images/logo.svg
new file mode 100644
index 00000000..96f2633d
--- /dev/null
+++ b/app/public/images/logo.svg
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/public/index.html b/app/public/index.html
index b151b530..42044b2c 100644
--- a/app/public/index.html
+++ b/app/public/index.html
@@ -7,21 +7,21 @@
-
Lyra Interface | Decentralized Options Liquidity
-
+ Kwenta Options | Decentralized Options Liquidity
+
-
+
-
+
diff --git a/app/src/App.tsx b/app/src/App.tsx
index 593217d8..f1e67ff9 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -16,8 +16,11 @@ import AdminMarketPage from './pages/AdminPage'
import NotFoundPage from './pages/NotFoundPage'
import PortfolioPage from './pages/PortfolioPage'
import PositionPage from './pages/PositionPage'
-import RewardsHistoryPage from './pages/RewardsHistoryPage'
-import RewardsPage from './pages/RewardsPage'
+import RewardsEthLyraLPPage from './pages/RewardsEthLyraLPPage'
+import RewardsIndexPage from './pages/RewardsIndexPage'
+import RewardsShortsPage from './pages/RewardsShortsPage'
+import RewardsTradingPage from './pages/RewardsTradingPage'
+import RewardsVaultsPage from './pages/RewardsVaultsPage'
import StoryBookPage from './pages/StoryBookPage'
import TradePage from './pages/TradePage'
import VaultsHistoryPage from './pages/VaultsHistoryPage'
@@ -126,8 +129,12 @@ function App(): JSX.Element {
} />
} />
} />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
} />
{typeof label === 'string' ? (
-
+
{label}
) : (
diff --git a/app/src/components/dev/TableDemoCard/index.tsx b/app/src/components/dev/TableDemoCard/index.tsx
index 5ab05183..b7ddd3b1 100644
--- a/app/src/components/dev/TableDemoCard/index.tsx
+++ b/app/src/components/dev/TableDemoCard/index.tsx
@@ -1,5 +1,6 @@
import Card, { CardElement } from '@lyra/ui/components/Card'
import CardSection from '@lyra/ui/components/Card/CardSection'
+import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
import Table, { TableCellProps, TableColumn, TableData } from '@lyra/ui/components/Table'
import Text from '@lyra/ui/components/Text'
import { MarginProps } from '@lyra/ui/types'
@@ -62,6 +63,7 @@ export default function TableDemoCard({ ...marginProps }: MarginProps): CardElem
+
diff --git a/app/src/components/position/PositionCard/index.tsx b/app/src/components/position/PositionCard/index.tsx
index f466095e..38a62e3b 100644
--- a/app/src/components/position/PositionCard/index.tsx
+++ b/app/src/components/position/PositionCard/index.tsx
@@ -12,6 +12,7 @@ import React, { useState } from 'react'
import PositionStatusText from '@/app/components/common/PositionStatusText'
import { UNIT, ZERO_BN } from '@/app/constants/bn'
import ShortYieldValue from '@/app/containers/common/ShortYieldValue'
+import TradeCollateralFormModal from '@/app/containers/trade/TradeCollateralFormModal'
import TradeFormModal from '@/app/containers/trade/TradeFormModal'
import useWallet from '@/app/hooks/account/useWallet'
@@ -31,7 +32,8 @@ const PositionCard = ({ position, option }: Props): JSX.Element | null => {
const equity = position.isLong ? currentPrice.mul(size).div(UNIT) : position?.collateral?.value ?? ZERO_BN
const [isBuy, setIsBuy] = useState(false)
- const [isOpen, setIsOpen] = useState(false)
+ const [isTradeFormOpen, setIsTradeFormOpen] = useState(false)
+ const [isCollateralFormOpen, setIsCollateralFormOpen] = useState(false)
const { account, isOverride } = useWallet()
const isOwner = account === position.owner && !isOverride
@@ -85,30 +87,43 @@ const PositionCard = ({ position, option }: Props): JSX.Element | null => {
label="Open Position"
onClick={() => {
setIsBuy(position.isLong)
- setIsOpen(true)
+ setIsTradeFormOpen(true)
}}
/>
{
setIsBuy(!position.isLong)
- setIsOpen(true)
+ setIsTradeFormOpen(true)
}}
/>
+ setIsCollateralFormOpen(true)}
+ />
) : null}
setIsOpen(false)}
- onTrade={() => setIsOpen(false)}
+ isOpen={isTradeFormOpen}
+ onClose={() => setIsTradeFormOpen(false)}
+ onTrade={() => setIsTradeFormOpen(false)}
isBuy={isBuy}
position={position}
option={option}
/>
+ setIsCollateralFormOpen(false)}
+ onTrade={() => setIsCollateralFormOpen(false)}
+ position={position}
+ option={option}
+ />
)
}
diff --git a/app/src/components/rewards/RewardTokenAmounts/index.tsx b/app/src/components/rewards/RewardTokenAmounts/index.tsx
new file mode 100644
index 00000000..ff4ea9d0
--- /dev/null
+++ b/app/src/components/rewards/RewardTokenAmounts/index.tsx
@@ -0,0 +1,51 @@
+import Flex from '@lyra/ui/components/Flex'
+import Text, { TextVariant } from '@lyra/ui/components/Text'
+import { MarginProps } from '@lyra/ui/types'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import { RewardEpochTokenAmount } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useMemo } from 'react'
+
+import TokenImageStack from '../../common/TokenImageStack'
+
+type Props = {
+ tokenAmounts: RewardEpochTokenAmount[]
+ size?: number
+ variant?: TextVariant
+ showDash?: boolean
+ hideZeroAmount?: boolean
+ hideTokenImages?: boolean
+} & MarginProps
+
+export default function RewardTokenAmounts({
+ tokenAmounts,
+ size = 24,
+ variant = 'body',
+ showDash = true,
+ hideTokenImages = false,
+ hideZeroAmount = false,
+ ...styleProps
+}: Props) {
+ const rewardsText = useMemo(
+ () => tokenAmounts.map(token => `${formatNumber(token.amount)} ${token.symbol}`).join(', '),
+ [tokenAmounts]
+ )
+ const isRewardAmountZero = tokenAmounts.reduce((totalAmount, tokenAmount) => totalAmount + tokenAmount.amount, 0) <= 0
+
+ if (showDash && (tokenAmounts.length === 0 || (hideZeroAmount && isRewardAmountZero))) {
+ return (
+
+ -
+
+ )
+ }
+
+ return (
+
+ {!hideTokenImages ? (
+ t.symbol)} />
+ ) : null}
+ {rewardsText}
+
+ )
+}
diff --git a/app/src/components/rewards/TradingFeeRebateTable/index.tsx b/app/src/components/rewards/TradingFeeRebateTable/index.tsx
new file mode 100644
index 00000000..24f69e79
--- /dev/null
+++ b/app/src/components/rewards/TradingFeeRebateTable/index.tsx
@@ -0,0 +1,54 @@
+import Flex from '@lyra/ui/components/Flex'
+import Text from '@lyra/ui/components/Text'
+import { LayoutProps, MarginProps } from '@lyra/ui/types'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import formatPercentage from '@lyra/ui/utils/formatPercentage'
+import { GlobalRewardEpochTradingFeeRebateTier } from '@lyrafinance/lyra-js'
+import React from 'react'
+
+import RowItem from '../../common/RowItem'
+
+type Props = {
+ feeRebateTiers: GlobalRewardEpochTradingFeeRebateTier[]
+ effectiveRebate?: number
+} & MarginProps &
+ LayoutProps
+
+export function TradingFeeRebateTable({ effectiveRebate, feeRebateTiers, ...styleProps }: Props) {
+ return (
+
+
+ Staked LYRA
+
+ }
+ value={
+
+ Rebate
+
+ }
+ />
+
+ {feeRebateTiers.map(feeRebateTier => (
+
+ {formatNumber(feeRebateTier.stakedLyraCutoff)}
+
+ }
+ value={
+
+ {formatPercentage(feeRebateTier.feeRebate, true)}
+
+ }
+ />
+ ))}
+
+
+ )
+}
diff --git a/app/src/components/vaults_index/VaultsIndexTable/index.tsx b/app/src/components/vaults_index/VaultsIndexTable/index.tsx
index e8758ef4..11b4151a 100644
--- a/app/src/components/vaults_index/VaultsIndexTable/index.tsx
+++ b/app/src/components/vaults_index/VaultsIndexTable/index.tsx
@@ -90,7 +90,7 @@ const VaultsIndexTable = ({ vaults, ...styleProps }: VaultsIndexTableProps): Tab
vault: { market, globalRewardEpoch },
} = props.row.original
- const isNew = globalRewardEpoch && globalRewardEpoch.id <= 1
+ const isNew = globalRewardEpoch?.startTimestamp === 1676419200 && market.baseToken.symbol == 'WBTC'
return (
diff --git a/app/src/constants/fetch.ts b/app/src/constants/fetch.ts
index 2a244584..8d962739 100644
--- a/app/src/constants/fetch.ts
+++ b/app/src/constants/fetch.ts
@@ -6,8 +6,9 @@ export enum FetchId {
// accounts
AccountENS = 'AccountENS',
AccountEthBalance = 'AccountEthBalance',
- AccountBalance = 'AccountBalance',
AccountScreenTransaction = 'AccountScreenTransaction',
+ AccountBalances = 'AccountBalances',
+ AccountLyraBalances = 'AccountLyraBalances',
// portfolio
PortfolioPageData = 'PortfolioPageData',
@@ -17,7 +18,6 @@ export enum FetchId {
// trade
TradePageData = 'TradePageData',
- TradeBalances = 'TradeBalances',
// position
PositionPageData = 'PositionPageData',
@@ -33,18 +33,23 @@ export enum FetchId {
// rewards
// TODO: @dappbeast simplify account hooks
+ RewardsPageData = 'RewardsPageData',
+ RewardsIndexPageData = 'RewardsIndexPageData',
+ RewardsShortsPageData = 'RewardsShortsPageData',
+ RewardsEthLyraLPPageData = 'RewardsEthLyraLPPageData',
Markets = 'Markets',
AccountRewardEpochs = 'AccountRewardEpochs',
LatestRewardEpoch = 'LatestRewardEpoch',
- Stake = 'Stake',
- Unstake = 'Unstake',
LyraStaking = 'LyraStaking',
- LyraAccountStaking = 'LyraAccountStaking',
+ LyraStakingAccount = 'LyraStakingAccount',
WethLyraStaking = 'WethLyraStaking',
- AccountWethLyraStakingL2 = 'AccountWethLyraStakingL2',
- AccountWethLyraStaking = 'AccountWethLyraStaking',
+ WethLyraStakingL2Account = 'WethLyraStakingL2Account',
+ WethLyraStakingAccount = 'WethLyraStakingAccount',
ClaimableBalanceL2 = 'ClaimableBalanceL2',
- ClaimableBalanceL1 = 'ClaimableBalanceL1',
+ ClaimableStakingRewards = 'ClaimableStakingRewards',
+ ClaimableWethLyraRewards = 'ClaimableWethLyraRewards',
+ TokenSupply = 'TokenSupply',
+ NetworkTradingVolume = 'NetworkTradingVolume',
// shared
PositionHistory = 'PositionHistory',
diff --git a/app/src/constants/layout.ts b/app/src/constants/layout.ts
index e09cb6c6..7c47da28 100644
--- a/app/src/constants/layout.ts
+++ b/app/src/constants/layout.ts
@@ -22,3 +22,6 @@ export const MIN_TRADE_POSITION_CARD_HEIGHT = 140
export const TRADE_SPOT_LINE_CHART_HEIGHT = [120, 140]
export const TRADE_SPOT_CANDLE_CHART_HEIGHT = [160, 240]
export const ADMIN_TRANSACTIONS_CARD_WIDTH = 560
+
+export const REWARDS_CARD_GRID_COLUMN_TEMPLATE = ['1fr 36px', '1.1fr 0.9fr 1.1fr 0.9fr 36px']
+export const REWARDS_HISTORY_GRID_COLUMN_TEMPLATE = ['1fr 1fr 1fr', '0.5fr 0.5fr 1fr']
diff --git a/app/src/constants/links.ts b/app/src/constants/links.ts
index 7c143d1e..9284bfae 100644
--- a/app/src/constants/links.ts
+++ b/app/src/constants/links.ts
@@ -21,6 +21,10 @@ export const UNISWAP_URL = 'https://app.uniswap.org'
export const ONE_INCH_URL = 'https://app.1inch.io'
export const SOCKET_URL = 'https://www.bungee.exchange/'
export const TWITTER_URL = 'https://twitter.com'
+export const KWENTA_MARKETS_URL = 'https://kwenta.eth.limo/market/?asset=sETH&accountType=isolated_margin'
+export const KWENTA_DASHBOARD_URL = 'https://kwenta.eth.limo/dashboard'
+export const KWENTA_EXCHANGE_URL = 'https://kwenta.eth.limo/exchange'
+export const KWENTA_LEADERBOARD_URL = 'https://kwenta.eth.limo/leaderboard'
export const TERMS_OF_USE_URL = 'https://www.lyra.finance/terms-of-use'
@@ -44,7 +48,7 @@ export const SHORT_COLLATERAL_REWARDS_DOC_URL =
'https://docs.lyra.finance/governance/incentives#short-collateral-rewards'
export const TRADING_CUTOFF_DOC_URL = 'https://docs.lyra.finance/overview/how-does-lyra-work/trading#trading-cutoffs'
-export const SWAP_SYNTH_1INCH_URL = 'https://app.1inch.io/#/10/unified/swap/ETH/'
+export const SWAP_TOKEN_1INCH_URL = 'https://app.1inch.io/#/10/simple/swap/ETH'
export const WETH_LYRA_L2_LIQUIDITY_URL =
'https://beta.arrakis.finance/vaults/10/0x70535C46ce04181adf749f34B65B6365164d6B6E'
diff --git a/app/src/constants/networks.ts b/app/src/constants/networks.ts
index 1f16399f..23276c8e 100644
--- a/app/src/constants/networks.ts
+++ b/app/src/constants/networks.ts
@@ -26,6 +26,7 @@ const INFURA_PROJECT_ID = nullthrows(
'Missing REACT_APP_INFURA_PROJECT_ID in environment variables'
)
const ALCHEMY_PROJECT_ID = process.env.REACT_APP_ALCHEMY_PROJECT_ID
+const REACT_APP_ALCHEMY_ARBITRUM_PROJECT_ID = process.env.REACT_APP_ALCHEMY_ARBITRUM_PROJECT_ID
export const NETWORK_CONFIGS: Record = {
[Chain.Optimism]: {
@@ -57,7 +58,12 @@ export const NETWORK_CONFIGS: Record = {
chainId: 42161,
network: Network.Arbitrum,
walletRpcUrl: 'https://arb1.arbitrum.io/rpc',
- readRpcUrls: [`https://arbitrum-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`],
+ readRpcUrls: filterNulls([
+ `https://arbitrum-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`,
+ REACT_APP_ALCHEMY_ARBITRUM_PROJECT_ID
+ ? `https://arb-mainnet.g.alchemy.com/v2/${REACT_APP_ALCHEMY_ARBITRUM_PROJECT_ID}`
+ : null,
+ ]),
blockExplorerUrl: 'https://arbiscan.io/',
iconUrls: ['https://optimism.io/images/metamask_icon.svg', 'https://optimism.io/images/metamask_icon.png'],
},
diff --git a/app/src/constants/pages.ts b/app/src/constants/pages.ts
index 57550a82..94959253 100644
--- a/app/src/constants/pages.ts
+++ b/app/src/constants/pages.ts
@@ -9,8 +9,11 @@ export enum PageId {
Vaults = 'Vaults',
VaultsIndex = 'VaultsIndex',
VaultsHistory = 'VaultsHistory',
- Rewards = 'Rewards',
- RewardsHistory = 'RewardsHistory',
+ RewardsIndex = 'RewardsIndex',
+ RewardsTrading = 'RewardsTrading',
+ RewardsShorts = 'RewardsShorts',
+ RewardsVaults = 'RewardsVaults',
+ RewardsEthLyraLp = 'RewardsEthLyraLp',
Storybook = 'Storybook',
}
@@ -42,8 +45,18 @@ export type PageArgsMap = {
network: string
marketAddressOrName: string
}
- [PageId.Rewards]: undefined
- [PageId.RewardsHistory]: undefined
+ [PageId.RewardsIndex]: undefined
+ [PageId.RewardsTrading]: {
+ network: string
+ }
+ [PageId.RewardsShorts]: {
+ network: string
+ }
+ [PageId.RewardsVaults]: {
+ network: string
+ marketAddressOrName: string
+ }
+ [PageId.RewardsEthLyraLp]: undefined
[PageId.Storybook]: undefined
}
diff --git a/app/src/constants/rewards.ts b/app/src/constants/rewards.ts
new file mode 100644
index 00000000..52ebe8d0
--- /dev/null
+++ b/app/src/constants/rewards.ts
@@ -0,0 +1,4 @@
+import { SECONDS_IN_HOUR } from './time'
+
+// 4 hour delay on epoch end to rewards being claimable
+export const CLAIMABLE_REWARDS_DELAY = 4 * SECONDS_IN_HOUR
diff --git a/app/src/constants/token.ts b/app/src/constants/token.ts
index ae105b56..d5d34dc8 100644
--- a/app/src/constants/token.ts
+++ b/app/src/constants/token.ts
@@ -1 +1,4 @@
export const SOCKET_NATIVE_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
+
+export const LYRA_REWARD_TOKENS = ['lyra', 'stklyra']
+export const L2_REWARD_TOKENS = ['op']
diff --git a/app/src/containers/common/FeeRebateModal/index.tsx b/app/src/containers/common/FeeRebateModal/index.tsx
deleted file mode 100644
index 81beea52..00000000
--- a/app/src/containers/common/FeeRebateModal/index.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Button from '@lyra/ui/components/Button'
-import Card from '@lyra/ui/components/Card'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Center from '@lyra/ui/components/Center'
-import Flex from '@lyra/ui/components/Flex'
-import { IconType } from '@lyra/ui/components/Icon'
-import List from '@lyra/ui/components/List'
-import ListItem from '@lyra/ui/components/List/ListItem'
-import Modal from '@lyra/ui/components/Modal'
-import Spinner from '@lyra/ui/components/Spinner'
-import Text from '@lyra/ui/components/Text'
-import formatBalance from '@lyra/ui/utils/formatBalance'
-import formatNumber from '@lyra/ui/utils/formatNumber'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import { Network } from '@lyrafinance/lyra-js'
-import React from 'react'
-
-import { PageId } from '@/app/constants/pages'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useLatestRewardEpoch from '@/app/hooks/rewards/useLatestRewardEpoch'
-import getPagePath from '@/app/utils/getPagePath'
-
-type Props = {
- isOpen: boolean
- onClose: () => void
- network?: Network
-}
-
-const FeeRebateModalBody = withSuspense(
- ({ onClose, network }: Props) => {
- const walletNetwork = useNetwork()
- const epochs = useLatestRewardEpoch(network ?? walletNetwork)
- const global = epochs?.global
- const account = epochs?.account
- const feeRebate = account?.tradingFeeRebate ?? global?.minTradingFeeRebate ?? 0
- const feeTiers = global?.tradingFeeRebateTiers ?? []
- const stkLyra = account?.stakedLyraBalance ?? 0
- return (
- <>
-
-
- 0 ? 'primaryText' : 'secondaryText'} variant="title">
- {formatBalance(stkLyra, 'stkLYRA')}
-
-
-
-
- The Lyra Protocol's trading rewards program allows traders to earn back part of their trading fees as Staked
- LYRA {walletNetwork === Network.Optimism ? 'and OP' : ''} tokens every 2 weeks. Traders can stake LYRA to
- unlock a higher fee rebate.
-
-
-
-
-
-
-
- Staked LYRA
-
-
- Rebate
-
-
-
- {/* TODO: @dappbeast fix spacing issues */}
-
- {feeTiers.map(tier => (
-
- {formatNumber(tier.stakedLyraCutoff, { dps: 0 })}
-
- }
- rightContent={
-
- {formatPercentage(tier.feeRebate, true)}
-
- }
- />
- ))}
-
-
-
-
-
- >
- )
- },
- () => (
-
-
-
-
-
- )
-)
-
-export default function FeeRebateModal({ isOpen, onClose, network }: Props) {
- return (
-
-
-
- )
-}
diff --git a/app/src/containers/common/RewardsLastUpdatedAlert/index.tsx b/app/src/containers/common/RewardsLastUpdatedAlert/index.tsx
index 606276d6..3ead0ca5 100644
--- a/app/src/containers/common/RewardsLastUpdatedAlert/index.tsx
+++ b/app/src/containers/common/RewardsLastUpdatedAlert/index.tsx
@@ -6,29 +6,31 @@ import React from 'react'
import { SECONDS_IN_HOUR } from '@/app/constants/time'
import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useLatestRewardEpoch from '@/app/hooks/rewards/useLatestRewardEpoch'
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
-const RewardsLastUpdatedAlert = withSuspense(() => {
+type Props = {
+ latestRewardEpochs: LatestRewardEpoch[]
+}
+
+const RewardsLastUpdatedAlert = ({ latestRewardEpochs }: Props) => {
const network = useNetwork()
- const epochs = useLatestRewardEpoch(network)
- const currentTimestamp = epochs?.global.blockTimestamp ?? 0
- const lastUpdatedTimestamp = epochs?.global.lastUpdatedTimestamp ?? 0
+ const latestRewardEpoch = latestRewardEpochs.find(epochs => epochs.global.lyra.network === network)
+ const currentTimestamp = latestRewardEpoch?.global.blockTimestamp ?? 0
+ const lastUpdatedTimestamp = latestRewardEpoch?.global.lastUpdatedTimestamp ?? 0
const lastUpdatedDuration = currentTimestamp - lastUpdatedTimestamp
- if (lastUpdatedDuration > SECONDS_IN_HOUR) {
- return (
-
-
-
- )
- } else {
+ if (lastUpdatedDuration <= SECONDS_IN_HOUR) {
return null
}
-})
+ return (
+
+
+
+ )
+}
export default RewardsLastUpdatedAlert
diff --git a/app/src/containers/portfolio/PortfolioChartCard/PortfolioOnboardingCard.tsx b/app/src/containers/portfolio/PortfolioChartCard/PortfolioOnboardingCard.tsx
index 332e98c4..97ce821b 100644
--- a/app/src/containers/portfolio/PortfolioChartCard/PortfolioOnboardingCard.tsx
+++ b/app/src/containers/portfolio/PortfolioChartCard/PortfolioOnboardingCard.tsx
@@ -19,9 +19,14 @@ function PortfolioOnboardingCard() {
return (
-
-
- Welcome to Lyra
+
+
+ Welcome to Kwenta
+
+
+ Powered by
+
+ Lyra
Trade, provide liquidity and earn rewards with the Lyra Protocol
diff --git a/app/src/containers/portfolio/PortfolioOpenPositionsCard/index.tsx b/app/src/containers/portfolio/PortfolioOpenPositionsCard/index.tsx
index be72d874..801bbe52 100644
--- a/app/src/containers/portfolio/PortfolioOpenPositionsCard/index.tsx
+++ b/app/src/containers/portfolio/PortfolioOpenPositionsCard/index.tsx
@@ -58,6 +58,7 @@ const PortfolioOpenPositionsCard = ({ openPositions }: Props): CardElement => {
{
- const owner = useWalletAccount()
- const account = lyraOptimism.account(owner ?? '')
+ const account = useWalletAccount()
+ const network = useNetwork()
const execute = useTransaction(Network.Optimism)
const mutateClaimableBalance = useMutateClaimableBalances()
const claimableBalances = useClaimableBalances()
const isSelectedBalanceZero = ZERO_BN.add(claimableBalances.oldStkLyra).isZero()
const handleDistributorClaim = async () => {
- const tx = await account.claim(['0xdE48b1B5853cc63B1D05e507414D3E02831722F8'])
+ if (!account) {
+ return
+ }
+ const tx = await getLyraSDK(network).claimRewards(account, ['0xdE48b1B5853cc63B1D05e507414D3E02831722F8'])
await execute(tx, {
onComplete: () => {
mutateClaimableBalance()
diff --git a/app/src/containers/rewards/ClaimAndMigrateModal/MigrateStkLyraButton.tsx b/app/src/containers/rewards/ClaimAndMigrateModal/MigrateStkLyraButton.tsx
index 4e483b64..e453bc12 100644
--- a/app/src/containers/rewards/ClaimAndMigrateModal/MigrateStkLyraButton.tsx
+++ b/app/src/containers/rewards/ClaimAndMigrateModal/MigrateStkLyraButton.tsx
@@ -1,17 +1,19 @@
import Box from '@lyra/ui/components/Box'
import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
import { MarginProps } from '@lyra/ui/types'
-import { Network } from '@lyrafinance/lyra-js'
+import { LyraStaking, Network } from '@lyrafinance/lyra-js'
import React, { useCallback, useMemo } from 'react'
import { ZERO_BN } from '@/app/constants/bn'
import { LogEvent } from '@/app/constants/logEvents'
import { TransactionType } from '@/app/constants/screen'
+import useAccountLyraBalances, { useMutateAccountLyraBalances } from '@/app/hooks/account/useAccountLyraBalances'
import useTransaction from '@/app/hooks/account/useTransaction'
+import useWalletAccount from '@/app/hooks/account/useWalletAccount'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useAccount from '@/app/hooks/market/useAccount'
-import useLyraAccountStaking, { useMutateAccountStaking } from '@/app/hooks/rewards/useLyraAccountStaking'
+import { useMutateAccountStaking } from '@/app/hooks/rewards/useLyraAccountStaking'
import logEvent from '@/app/utils/logEvent'
+import { lyraOptimism } from '@/app/utils/lyra'
import TransactionButton from '../../common/TransactionButton'
@@ -19,37 +21,34 @@ type Props = MarginProps
const MigrateStkLyraButton = withSuspense(
({ ...styleProps }: Props) => {
- const lyraAccountStaking = useLyraAccountStaking()
- const amount = lyraAccountStaking?.lyraBalances.optimismOldStkLyra ?? ZERO_BN
- const account = useAccount(Network.Optimism)
+ const lyraBalances = useAccountLyraBalances()
+ const amount = lyraBalances.optimismOldStkLyra
+ const account = useWalletAccount()
const execute = useTransaction(Network.Optimism)
- const { insufficientAllowance, insufficientBalance } = useMemo(() => {
- const migrationAllowance = lyraAccountStaking?.lyraAllowances.migrationAllowance ?? ZERO_BN
- const optimismOldStkLyra = lyraAccountStaking?.lyraBalances.optimismOldStkLyra ?? ZERO_BN
- const insufficientAllowance = migrationAllowance.lte(ZERO_BN)
- const insufficientBalance = optimismOldStkLyra.lte(ZERO_BN)
- return {
- insufficientAllowance,
- insufficientBalance,
- }
- }, [lyraAccountStaking])
+ const { insufficientAllowance, insufficientBalance } = useMemo(
+ () => ({
+ insufficientAllowance: lyraBalances.migrationAllowance.lte(ZERO_BN),
+ insufficientBalance: lyraBalances.optimismOldStkLyra.lte(ZERO_BN),
+ }),
+ [lyraBalances]
+ )
const mutateAccountStaking = useMutateAccountStaking()
-
+ const mutateAccountLyraBalances = useMutateAccountLyraBalances()
const handleClickApprove = useCallback(async () => {
if (!account) {
console.warn('Account does not exist')
return null
}
logEvent(LogEvent.MigrateStakeLyraApproveSubmit)
- const tx = await account.approveMigrateStakedLyra()
+ const tx = await LyraStaking.approveMigrate(lyraOptimism, account)
await execute(tx, {
onComplete: async () => {
logEvent(LogEvent.MigrateStakeLyraApproveSuccess)
- await mutateAccountStaking()
+ await Promise.all([mutateAccountLyraBalances(), mutateAccountStaking()])
},
onError: error => logEvent(LogEvent.MigrateStakeLyraApproveError, { error: error?.message }),
})
- }, [account, execute, mutateAccountStaking])
+ }, [account, execute, mutateAccountStaking, mutateAccountLyraBalances])
const handleClickMigrate = useCallback(async () => {
if (!account) {
@@ -57,15 +56,15 @@ const MigrateStkLyraButton = withSuspense(
return
}
logEvent(LogEvent.MigrateStakeLyraSubmit)
- const tx = await account.migrateStakedLyra()
+ const tx = await LyraStaking.migrateStakedLyra(lyraOptimism, account)
await execute(tx, {
onComplete: async () => {
logEvent(LogEvent.MigrateStakeLyraSuccess)
- await mutateAccountStaking()
+ await Promise.all([mutateAccountStaking(), mutateAccountLyraBalances()])
},
onError: error => logEvent(LogEvent.MigrateStakeLyraError, { error: error?.message }),
})
- }, [account, execute, mutateAccountStaking])
+ }, [account, execute, mutateAccountStaking, mutateAccountLyraBalances])
const migrateButton = (
{
const claimableBalances = useClaimableBalances()
- const lyraAccountStaking = useLyraAccountStaking()
- const oldStkLyraBalance = lyraAccountStaking?.lyraBalances.optimismOldStkLyra ?? ZERO_BN
+ const lyraBalances = useAccountLyraBalances()
+ const oldStkLyraBalance = lyraBalances.optimismOldStkLyra
const hasClaimableOldStkLyra = claimableBalances.oldStkLyra.gt(0)
const hasOldStkLyra = oldStkLyraBalance.gt(0)
diff --git a/app/src/containers/rewards/ClaimModal/ClaimButton.tsx b/app/src/containers/rewards/ClaimModal/ClaimButton.tsx
deleted file mode 100644
index c7b98c7e..00000000
--- a/app/src/containers/rewards/ClaimModal/ClaimButton.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
-import React from 'react'
-
-import { ZERO_BN } from '@/app/constants/bn'
-import { TransactionType } from '@/app/constants/screen'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import useTransaction from '@/app/hooks/account/useTransaction'
-import useNetworkToken from '@/app/hooks/data/useNetworkToken'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useAccount from '@/app/hooks/market/useAccount'
-import useAccountWethLyraStakingL2, {
- useMutateAccountWethLyraStakingL2,
-} from '@/app/hooks/rewards/useAccountWethLyraStakingL2'
-import useClaimableBalances, { useMutateClaimableBalances } from '@/app/hooks/rewards/useClaimableBalance'
-import filterNulls from '@/app/utils/filterNulls'
-
-import TransactionButton from '../../common/TransactionButton'
-
-type Props = {
- isOpChecked: boolean
- isOldStkLyraChecked: boolean
- isNewStkLyraChecked: boolean
- isWethLyraChecked: boolean
- onClaim?: () => void
-}
-
-const ClaimButton = withSuspense(
- ({ isOpChecked, isOldStkLyraChecked, isNewStkLyraChecked, isWethLyraChecked, onClaim }: Props) => {
- const network = useNetwork()
- const account = useAccount(network)
- const stkLyra = useNetworkToken(network, 'stkLyra')
- const op = useNetworkToken(network, 'op')
- const execute = useTransaction(network)
- const mutateClaimableBalance = useMutateClaimableBalances()
- const claimableBalances = useClaimableBalances()
- const wethLyraAccount = useAccountWethLyraStakingL2()
- const mutateWethLyraAccount = useMutateAccountWethLyraStakingL2()
- const isSelectedBalanceZero = ZERO_BN.add(isOpChecked ? claimableBalances.op : ZERO_BN)
- .add(isNewStkLyraChecked ? claimableBalances.newStkLyra : ZERO_BN)
- .add(isOldStkLyraChecked ? claimableBalances.oldStkLyra : ZERO_BN)
- .add(isWethLyraChecked && wethLyraAccount?.rewards.gt(0) ? wethLyraAccount?.rewards : ZERO_BN)
- .isZero()
-
- const handleWethLyraClaim = async () => {
- const tx = await account?.claimWethLyraRewardsL2()
- if (tx) {
- await execute(tx, {
- onComplete: () => {
- mutateWethLyraAccount()
- if (onClaim) {
- onClaim()
- }
- },
- })
- }
- }
-
- const handleDistributorClaim = async () => {
- const tokens = filterNulls([
- isOpChecked ? op?.address : null,
- isNewStkLyraChecked ? stkLyra?.address : null,
- isOldStkLyraChecked ? '0xdE48b1B5853cc63B1D05e507414D3E02831722F8' : null,
- ])
- const tx = await account?.claim(tokens)
- if (tx) {
- await execute(tx, {
- onComplete: () => {
- mutateClaimableBalance()
- if (onClaim) {
- onClaim()
- }
- },
- })
- }
- }
-
- return (
- {
- if (isWethLyraChecked) {
- await handleWethLyraClaim()
- return
- }
- await handleDistributorClaim()
- }}
- />
- )
- },
- () =>
-)
-
-export default ClaimButton
diff --git a/app/src/containers/rewards/ClaimModal/ClaimModalContent.tsx b/app/src/containers/rewards/ClaimModal/ClaimModalContent.tsx
deleted file mode 100644
index 418e2df8..00000000
--- a/app/src/containers/rewards/ClaimModal/ClaimModalContent.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Card from '@lyra/ui/components/Card'
-import CardBody from '@lyra/ui/components/Card/CardBody'
-import Flex from '@lyra/ui/components/Flex'
-import Checkbox from '@lyra/ui/components/Input/Checkbox'
-import Spinner from '@lyra/ui/components/Spinner'
-import Text from '@lyra/ui/components/Text'
-import { Network } from '@lyrafinance/lyra-js'
-import React from 'react'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useAccountWethLyraStakingL2 from '@/app/hooks/rewards/useAccountWethLyraStakingL2'
-import useClaimableBalances from '@/app/hooks/rewards/useClaimableBalance'
-
-type Props = {
- isOpChecked: boolean
- isOldStkLyraChecked: boolean
- isNewStkLyraChecked: boolean
- isWethLyraChecked: boolean
- onClickOp: () => void
- onClickNewStkLyra: () => void
- onClickWethLyra: () => void
- onClickOldStkLyra: () => void
-}
-
-const ClaimModalContent = withSuspense(
- ({
- isOpChecked,
- isOldStkLyraChecked,
- isNewStkLyraChecked,
- isWethLyraChecked,
- onClickNewStkLyra,
- onClickOp,
- onClickOldStkLyra,
- onClickWethLyra,
- }: Props) => {
- const network = useNetwork()
- const claimableBalances = useClaimableBalances()
- const wethLyraAccount = useAccountWethLyraStakingL2()
- const isDistributorRewardsDisabled = isWethLyraChecked
- const isWethLyraDisabled = isOpChecked || isNewStkLyraChecked || isOldStkLyraChecked
- return (
-
- {network === Network.Optimism && claimableBalances.oldStkLyra.gt(0) ? (
-
-
-
-
-
- Old stkLyra Rewards
-
-
-
-
-
-
-
- ) : null}
- {claimableBalances.newStkLyra.gt(0) ? (
-
-
-
-
-
- New stkLyra Rewards
-
-
-
-
-
-
-
- ) : null}
- {network === Network.Optimism && claimableBalances.op.gt(0) ? (
-
-
-
-
-
- OP Rewards
-
-
-
-
-
-
-
- ) : null}
- {network === Network.Optimism && wethLyraAccount?.rewards.gt(0) ? (
-
-
-
-
-
- WETH-LYRA Rewards
-
-
-
-
-
-
-
- ) : null}
-
- )
- },
- () =>
-)
-
-export default ClaimModalContent
diff --git a/app/src/containers/rewards/ClaimModal/index.tsx b/app/src/containers/rewards/ClaimModal/index.tsx
index 5b9fe6e6..2197b066 100644
--- a/app/src/containers/rewards/ClaimModal/index.tsx
+++ b/app/src/containers/rewards/ClaimModal/index.tsx
@@ -1,65 +1,129 @@
+import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
+import Collapsible from '@lyra/ui/components/Collapsible'
+import Flex from '@lyra/ui/components/Flex'
+import Icon, { IconType } from '@lyra/ui/components/Icon'
import Modal from '@lyra/ui/components/Modal'
-import ModalBody from '@lyra/ui/components/Modal/ModalBody'
-import React, { useState } from 'react'
+import ModalSection from '@lyra/ui/components/Modal/ModalSection'
+import Text from '@lyra/ui/components/Text'
+import { AccountRewardEpoch } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useState } from 'react'
-import withSuspense from '@/app/hooks/data/withSuspense'
+import RowItem from '@/app/components/common/RowItem'
+import { TransactionType } from '@/app/constants/screen'
+import useTransaction from '@/app/hooks/account/useTransaction'
+import { useMutateRewardsPageData } from '@/app/hooks/rewards/useRewardsPageData'
+import formatRewardTokenAmounts from '@/app/utils/formatRewardTokenAmounts'
+import formatTokenName from '@/app/utils/formatTokenName'
+import getLyraSDK from '@/app/utils/getLyraSDK'
+import getNetworkDisplayName from '@/app/utils/getNetworkDisplayName'
-import ClaimButton from './ClaimButton'
-import ClaimModalContent from './ClaimModalContent'
+import TransactionButton from '../../common/TransactionButton'
type Props = {
isOpen: boolean
onClose: () => void
+ accountRewardEpoch: AccountRewardEpoch
}
-const ClaimModal = withSuspense(({ isOpen, onClose }: Props): JSX.Element => {
- const [isNewStkLyraChecked, setIsNewStkLyraChecked] = useState(false)
- const [isOldStkLyraChecked, setIsOldStkLyraChecked] = useState(false)
- const [isOpChecked, setIsOpChecked] = useState(false)
- const [isWethLyraChecked, setIsWethLyraChecked] = useState(false)
-
- const handleClickNewStkLyra = () => {
- setIsNewStkLyraChecked(!isNewStkLyraChecked)
- setIsWethLyraChecked(false)
- }
- const handleClickOp = () => {
- setIsOpChecked(!isOpChecked)
- setIsWethLyraChecked(false)
- }
- const handleClickOldStkLyra = () => {
- setIsOldStkLyraChecked(!isOldStkLyraChecked)
- setIsWethLyraChecked(false)
- }
-
- const handleClickWethLyra = () => {
- setIsWethLyraChecked(!isWethLyraChecked)
- setIsOpChecked(false)
- setIsNewStkLyraChecked(false)
- }
+export default function ClaimModal({ accountRewardEpoch, isOpen, onClose }: Props) {
+ const [isVaultExpanded, setIsVaultExpanded] = useState(false)
+ const execute = useTransaction(accountRewardEpoch.lyra.network)
+ const mutateRewardsPageData = useMutateRewardsPageData()
+ const { tradingRewards, totalRewards } = accountRewardEpoch.claimableRewards
+ const emptyVaultRewards = accountRewardEpoch.globalEpoch
+ .totalVaultRewards(accountRewardEpoch.globalEpoch.markets[0].address)
+ .map(t => ({ ...t, amount: 0 }))
+ const emptyTradingRewards = accountRewardEpoch.globalEpoch.tradingRewards(0, 0)
return (
-
-
-
-
+
+
+
+ Your rewards for Vaults, Trading and Shorts are claimable all at once, which saves on transaction fees. Here
+ is the breakdown of your claimable rewards for each program you are participating in.
+
+
+ setIsVaultExpanded(!isVaultExpanded)}
+ header={
+
+
+ {formatRewardTokenAmounts(
+ accountRewardEpoch.totalClaimableVaultRewards.length
+ ? accountRewardEpoch.totalClaimableVaultRewards
+ : emptyVaultRewards
+ )}
+
+
+
+ }
+ />
+ }
+ >
+ {accountRewardEpoch.globalEpoch.markets
+ .filter(market => market.baseToken.symbol !== 'sSOL')
+ .map(market => {
+ const vaultRewards = accountRewardEpoch.claimableVaultRewards(market.address)
+ return (
+ ({ ...t, amount: 0 }))
+ )}
+ />
+ )
+ })}
+
+
-
+
+
+
+
+ Total
+ {formatRewardTokenAmounts(totalRewards)}
+
+
+ reward.amount === 0)}
+ onClick={async () => {
+ const tx = await getLyraSDK(accountRewardEpoch.lyra.network).claimRewards(
+ accountRewardEpoch.account,
+ totalRewards.map(token => token.address)
+ )
+ await execute(tx, {
+ onComplete: () => {
+ mutateRewardsPageData()
+ onClose()
+ },
+ })
+ }}
+ />
)
-})
-
-export default ClaimModal
+}
diff --git a/app/src/containers/rewards/ClaimModalButton/index.tsx b/app/src/containers/rewards/ClaimModalButton/index.tsx
new file mode 100644
index 00000000..0223a01a
--- /dev/null
+++ b/app/src/containers/rewards/ClaimModalButton/index.tsx
@@ -0,0 +1,28 @@
+import Button from '@lyra/ui/components/Button'
+import { LayoutProps, MarginProps } from '@lyra/ui/types'
+import { AccountRewardEpoch } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useMemo } from 'react'
+
+type Props = {
+ accountRewardEpoch?: AccountRewardEpoch | null
+ onClick: () => void
+} & MarginProps &
+ LayoutProps
+
+export default function ClaimModalButton({ onClick, accountRewardEpoch, ...styleProps }: Props) {
+ const hasClaimableRewards = useMemo(
+ () => accountRewardEpoch?.claimableRewards.totalRewards.some(token => token.amount > 0),
+ [accountRewardEpoch]
+ )
+ return (
+
+ )
+}
diff --git a/app/src/containers/rewards/ClaimStakingRewardsModal/ClaimStakingRewardsButton.tsx b/app/src/containers/rewards/ClaimStakingRewardsModal/ClaimStakingRewardsButton.tsx
index f2a2b00d..f18af858 100644
--- a/app/src/containers/rewards/ClaimStakingRewardsModal/ClaimStakingRewardsButton.tsx
+++ b/app/src/containers/rewards/ClaimStakingRewardsModal/ClaimStakingRewardsButton.tsx
@@ -5,7 +5,9 @@ import { TransactionType } from '@/app/constants/screen'
import useTransaction from '@/app/hooks/account/useTransaction'
import useWalletAccount from '@/app/hooks/account/useWalletAccount'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useClaimableBalancesL1, { useMutateClaimableBalancesL1 } from '@/app/hooks/rewards/useClaimableBalanceL1'
+import useClaimableStakingRewards, {
+ useMutateClaimableStakingRewards,
+} from '@/app/hooks/rewards/useClaimableStakingRewards'
import { lyraOptimism } from '@/app/utils/lyra'
import TransactionButton from '../../common/TransactionButton'
@@ -16,14 +18,16 @@ type Props = {
const ClaimButton = withSuspense(
({ onClaim }: Props) => {
- const owner = useWalletAccount()
- const account = lyraOptimism.account(owner ?? '')
+ const account = useWalletAccount()
const execute = useTransaction('ethereum')
- const mutateClaimableBalance = useMutateClaimableBalancesL1()
- const claimableBalances = useClaimableBalancesL1()
+ const mutateClaimableBalance = useMutateClaimableStakingRewards()
+ const claimableBalances = useClaimableStakingRewards()
const handleStkLyraClaim = async () => {
- const tx = await account.claimStakedLyraRewards()
+ if (!account) {
+ return
+ }
+ const tx = await lyraOptimism.claimStakingRewards(account)
await execute(tx, {
onComplete: () => {
mutateClaimableBalance()
@@ -42,7 +46,7 @@ const ClaimButton = withSuspense(
transactionType={TransactionType.ClaimStakedLyraRewards}
network="ethereum"
label="Claim"
- isDisabled={claimableBalances.newStkLyra.isZero()}
+ isDisabled={claimableBalances.isZero()}
onClick={async () => await handleStkLyraClaim()}
/>
)
diff --git a/app/src/containers/rewards/ClaimStakingRewardsModal/ClaimStakingRewardsModalContent.tsx b/app/src/containers/rewards/ClaimStakingRewardsModal/ClaimStakingRewardsModalContent.tsx
deleted file mode 100644
index de93221f..00000000
--- a/app/src/containers/rewards/ClaimStakingRewardsModal/ClaimStakingRewardsModalContent.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Card from '@lyra/ui/components/Card'
-import CardBody from '@lyra/ui/components/Card/CardBody'
-import Spinner from '@lyra/ui/components/Spinner'
-import { MarginProps } from '@lyra/ui/types'
-import React from 'react'
-
-import RowItem from '@/app/components/common/RowItem'
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useClaimableBalancesL1 from '@/app/hooks/rewards/useClaimableBalanceL1'
-
-type Props = MarginProps
-
-const ClaimStakingRewardsModalContent = withSuspense(
- ({ ...styleProps }: Props) => {
- const claimableBalances = useClaimableBalancesL1()
- return (
-
- {claimableBalances.newStkLyra.gt(0) ? (
-
-
- }
- />
-
-
- ) : null}
-
- )
- },
- () =>
-)
-
-export default ClaimStakingRewardsModalContent
diff --git a/app/src/containers/rewards/ClaimStakingRewardsModal/index.tsx b/app/src/containers/rewards/ClaimStakingRewardsModal/index.tsx
index cd49bd3c..3d6518c7 100644
--- a/app/src/containers/rewards/ClaimStakingRewardsModal/index.tsx
+++ b/app/src/containers/rewards/ClaimStakingRewardsModal/index.tsx
@@ -1,22 +1,38 @@
import Modal from '@lyra/ui/components/Modal'
import ModalBody from '@lyra/ui/components/Modal/ModalBody'
+import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
+import Text from '@lyra/ui/components/Text'
import React from 'react'
+import RowItem from '@/app/components/common/RowItem'
+import TokenAmountText from '@/app/components/common/TokenAmountText'
import withSuspense from '@/app/hooks/data/withSuspense'
+import useClaimableStakingRewards from '@/app/hooks/rewards/useClaimableStakingRewards'
import ClaimButton from './ClaimStakingRewardsButton'
-import ClaimStakingRewardsModalContent from './ClaimStakingRewardsModalContent'
type Props = {
isOpen: boolean
onClose: () => void
}
+const ClaimableBalanceText = withSuspense(
+ () => {
+ const claimableBalance = useClaimableStakingRewards()
+ return
+ },
+ () =>
+)
+
const ClaimStakingRewardsModal = withSuspense(({ isOpen, onClose }: Props): JSX.Element => {
return (
-
+
-
+
+ When you claim stkLYRA, it gets added to your stkLYRA balance. This increases your rewards and contributes to
+ your vault boosts and trading rewards. Unstaking LYRA has a 14 day cooldown period.
+
+ } />
diff --git a/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/ClaimWethLyraStakingRewardsButton.tsx b/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/ClaimWethLyraStakingRewardsButton.tsx
index 67816e0b..34019b43 100644
--- a/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/ClaimWethLyraStakingRewardsButton.tsx
+++ b/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/ClaimWethLyraStakingRewardsButton.tsx
@@ -1,12 +1,12 @@
import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
import React from 'react'
-import { ZERO_BN } from '@/app/constants/bn'
import { TransactionType } from '@/app/constants/screen'
import useTransaction from '@/app/hooks/account/useTransaction'
import useWalletAccount from '@/app/hooks/account/useWalletAccount'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useClaimableBalancesL1, { useMutateClaimableBalancesL1 } from '@/app/hooks/rewards/useClaimableBalanceL1'
+import { useMutateClaimableStakingRewards } from '@/app/hooks/rewards/useClaimableStakingRewards'
+import useClaimableWethLyraRewards from '@/app/hooks/rewards/useClaimableWethLyraRewards'
import { lyraOptimism } from '@/app/utils/lyra'
import TransactionButton from '../../common/TransactionButton'
@@ -17,15 +17,16 @@ type Props = {
const ClaimWethLyraStakingRewardsButton = withSuspense(
({ onClaim }: Props) => {
- const owner = useWalletAccount()
- const account = lyraOptimism.account(owner ?? '')
+ const account = useWalletAccount()
const execute = useTransaction('ethereum')
- const mutateClaimableBalance = useMutateClaimableBalancesL1()
- const claimableBalances = useClaimableBalancesL1()
- const isSelectedBalanceZero = ZERO_BN.add(claimableBalances.lyra).isZero()
+ const mutateClaimableBalance = useMutateClaimableStakingRewards()
+ const claimableBalance = useClaimableWethLyraRewards()
const handleLyraClaim = async () => {
- const tx = await account.claimWethLyraRewards()
+ if (!account) {
+ return
+ }
+ const tx = await lyraOptimism.claimWethLyraRewards(account)
await execute(tx, {
onComplete: () => {
mutateClaimableBalance()
@@ -44,12 +45,12 @@ const ClaimWethLyraStakingRewardsButton = withSuspense(
transactionType={TransactionType.ClaimWethLyraRewards}
network="ethereum"
label="Claim"
- isDisabled={isSelectedBalanceZero}
+ isDisabled={claimableBalance.isZero()}
onClick={async () => await handleLyraClaim()}
/>
)
},
- () =>
+ () =>
)
export default ClaimWethLyraStakingRewardsButton
diff --git a/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/ClaimWethLyraStakingRewardsModalContent.tsx b/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/ClaimWethLyraStakingRewardsModalContent.tsx
deleted file mode 100644
index a28ed187..00000000
--- a/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/ClaimWethLyraStakingRewardsModalContent.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Card from '@lyra/ui/components/Card'
-import CardBody from '@lyra/ui/components/Card/CardBody'
-import Spinner from '@lyra/ui/components/Spinner'
-import { MarginProps } from '@lyra/ui/types'
-import React from 'react'
-
-import RowItem from '@/app/components/common/RowItem'
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useClaimableBalancesL1 from '@/app/hooks/rewards/useClaimableBalanceL1'
-
-type Props = MarginProps
-
-const ClaimWethLyraStakingRewardsModalContent = withSuspense(
- ({ ...styleProps }: Props) => {
- const claimableBalances = useClaimableBalancesL1()
- return (
-
- {claimableBalances.lyra.gt(0) ? (
-
-
- }
- />
-
-
- ) : null}
-
- )
- },
- () =>
-)
-
-export default ClaimWethLyraStakingRewardsModalContent
diff --git a/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/index.tsx b/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/index.tsx
index 51a547da..62ef2602 100644
--- a/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/index.tsx
+++ b/app/src/containers/rewards/ClaimWethLyraStakingRewardsModal/index.tsx
@@ -2,25 +2,36 @@ import Modal from '@lyra/ui/components/Modal'
import ModalBody from '@lyra/ui/components/Modal/ModalBody'
import React from 'react'
+import RowItem from '@/app/components/common/RowItem'
+import TokenAmountText from '@/app/components/common/TokenAmountText'
+import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
import withSuspense from '@/app/hooks/data/withSuspense'
+import useClaimableWethLyraRewards from '@/app/hooks/rewards/useClaimableWethLyraRewards'
import ClaimWethLyraStakingRewardsButton from './ClaimWethLyraStakingRewardsButton'
-import ClaimWethLyraStakingRewardsModalContent from './ClaimWethLyraStakingRewardsModalContent'
type Props = {
isOpen: boolean
onClose: () => void
}
-const ClaimWethLyraStakingRewardsModal = withSuspense(({ isOpen, onClose }: Props): JSX.Element => {
+const ClaimableBalanceText = withSuspense(
+ () => {
+ const claimableBalance = useClaimableWethLyraRewards()
+ return
+ },
+ () =>
+)
+
+const ClaimWethLyraStakingRewardsModal = ({ isOpen, onClose }: Props): JSX.Element => {
return (
-
+
-
+ } />
)
-})
+}
export default ClaimWethLyraStakingRewardsModal
diff --git a/app/src/containers/rewards/RewardsBreakdownCard/ClaimableRewardsCardSection.tsx b/app/src/containers/rewards/RewardsBreakdownCard/ClaimableRewardsCardSection.tsx
deleted file mode 100644
index b733ea23..00000000
--- a/app/src/containers/rewards/RewardsBreakdownCard/ClaimableRewardsCardSection.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Button from '@lyra/ui/components/Button'
-import { CardElement } from '@lyra/ui/components/Card'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Grid from '@lyra/ui/components/Grid'
-import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
-import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
-import Text from '@lyra/ui/components/Text'
-import useIsMobile from '@lyra/ui/hooks/useIsMobile'
-import { MarginProps } from '@lyra/ui/types'
-import formatNumber from '@lyra/ui/utils/formatNumber'
-import { Network } from '@lyrafinance/lyra-js'
-import React, { useState } from 'react'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
-import { ZERO_BN } from '@/app/constants/bn'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useAccountWethLyraStakingL2 from '@/app/hooks/rewards/useAccountWethLyraStakingL2'
-import useClaimableBalances from '@/app/hooks/rewards/useClaimableBalance'
-
-import ClaimModal from '../ClaimModal'
-
-type Props = MarginProps
-
-const ClaimableStakedLyraText = withSuspense(
- (): CardElement => {
- const claimableBalance = useClaimableBalances()
- return (
-
- {formatNumber(claimableBalance.newStkLyra)} stkLYRA
-
- )
- },
- () =>
-)
-
-const ClaimableRewardsText = withSuspense(
- () => {
- const network = useNetwork()
- const claimableBalance = useClaimableBalances()
- const wethLyraAccount = useAccountWethLyraStakingL2()
- const claimableStkLyra = claimableBalance.newStkLyra.add(wethLyraAccount?.rewards ?? ZERO_BN)
- return (
- <>
-
-
- Claimable stkLYRA
-
-
-
- {network === Network.Optimism ? (
-
-
- Claimable OP
-
-
-
- ) : null}
- >
- )
- },
- () => (
-
-
-
-
- )
-)
-
-const OpenClaimModalButton = withSuspense(
- ({ onClick }: { onClick: () => void }) => {
- const claimableBalance = useClaimableBalances()
- const wethLyraAccount = useAccountWethLyraStakingL2()
- return (
-
- )
- },
- () =>
-)
-
-const ClaimableRewardsCardSection = ({ ...marginProps }: Props): CardElement => {
- const isMobile = useIsMobile()
- const [isClaimOpen, setIsClaimOpen] = useState(false)
- return (
- <>
-
-
-
- Claimable
-
-
-
-
-
-
-
- setIsClaimOpen(!isClaimOpen)} />
-
- setIsClaimOpen(false)} />
-
- >
- )
-}
-
-export default ClaimableRewardsCardSection
diff --git a/app/src/containers/rewards/RewardsBreakdownCard/PendingRewardsCardSection.tsx b/app/src/containers/rewards/RewardsBreakdownCard/PendingRewardsCardSection.tsx
deleted file mode 100644
index f08e9969..00000000
--- a/app/src/containers/rewards/RewardsBreakdownCard/PendingRewardsCardSection.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Button from '@lyra/ui/components/Button'
-import { CardElement } from '@lyra/ui/components/Card'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Grid from '@lyra/ui/components/Grid'
-import { IconType } from '@lyra/ui/components/Icon'
-import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
-import Text from '@lyra/ui/components/Text'
-import Countdown from '@lyra/ui/components/Text/CountdownText'
-import useIsMobile from '@lyra/ui/hooks/useIsMobile'
-import { MarginProps } from '@lyra/ui/types'
-import formatNumber from '@lyra/ui/utils/formatNumber'
-import React from 'react'
-import { useNavigate } from 'react-router-dom'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
-import { PageId } from '@/app/constants/pages'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useLatestRewardEpoch from '@/app/hooks/rewards/useLatestRewardEpoch'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-import getPagePath from '@/app/utils/getPagePath'
-
-type Props = MarginProps
-
-const PendingRewardsCardGridItems = withSuspense(
- () => {
- const network = useNetwork()
- const epochs = useLatestRewardEpoch(network)
- const account = epochs?.account
- const global = epochs?.global
- const epochEndTimestamp = global?.endTimestamp ?? 0
- // TODO - @dillon refactor later with better solution
- const opStakingRewards = findOpRewardEpochToken(account?.stakingRewards ?? [])
- const opVaultRewards = findOpRewardEpochToken(account?.totalVaultRewards ?? [])
- const opTradingRewards = findOpRewardEpochToken(account?.tradingRewards ?? [])
- const opShortCollatRewards = findOpRewardEpochToken(account?.shortCollateralRewards ?? [])
- const wethLyraStakingL2Rewards = findOpRewardEpochToken(account?.wethLyraStakingL2.rewards ?? [])
- const opRewards =
- opStakingRewards + opVaultRewards + opTradingRewards + opShortCollatRewards + wethLyraStakingL2Rewards
- const stkLyraVaultRewards = findLyraRewardEpochToken(account?.totalVaultRewards ?? [])
- const stkLyraTradingRewards = findLyraRewardEpochToken(account?.tradingRewards ?? [])
- const stkLyraShortCollatRewards = findLyraRewardEpochToken(account?.shortCollateralRewards ?? [])
- const stkLyraRewards = stkLyraVaultRewards + stkLyraTradingRewards + stkLyraShortCollatRewards
-
- // TODO: @dillon remove flags later
- const isDepositPeriod = global?.isDepositPeriod
- return (
- <>
-
-
- Pending Rewards
-
- {opRewards > 0 ? (
-
- ) : null}
-
-
-
-
- Countdown
-
-
-
- >
- )
- },
- () => {
- return (
- <>
-
-
-
-
- >
- )
- }
-)
-
-const PendingStakedLyraText = withSuspense(
- () => {
- const network = useNetwork()
- const epochs = useLatestRewardEpoch(network)
- const account = epochs?.account
- // TODO: @dillon remove next epoch
- const isDepositPeriod = epochs?.global.isDepositPeriod
- const lyraVaultRewards = findLyraRewardEpochToken(account?.totalVaultRewards ?? [])
- const lyraTradingRewards = findLyraRewardEpochToken(account?.tradingRewards ?? [])
- const lyraShortCollatRewards = findLyraRewardEpochToken(account?.shortCollateralRewards ?? [])
- const lyraRewards = lyraVaultRewards + lyraTradingRewards + lyraShortCollatRewards
- return (
-
- {formatNumber(isDepositPeriod ? 0 : lyraRewards)} stkLYRA
-
- )
- },
- () => {
- return
- }
-)
-
-const PendingRewardsCardSection = ({ ...marginProps }: Props): CardElement => {
- const isMobile = useIsMobile()
- const navigate = useNavigate()
- return (
-
-
-
- Pending
-
-
-
-
-
-
-
- navigate(getPagePath({ page: PageId.RewardsHistory }))}
- target="_blank"
- />
-
-
- )
-}
-
-export default PendingRewardsCardSection
diff --git a/app/src/containers/rewards/RewardsBreakdownCard/ShortCollateralRewardsSection.tsx b/app/src/containers/rewards/RewardsBreakdownCard/ShortCollateralRewardsSection.tsx
deleted file mode 100644
index 947158d6..00000000
--- a/app/src/containers/rewards/RewardsBreakdownCard/ShortCollateralRewardsSection.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import { CardElement } from '@lyra/ui/components/Card'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Flex from '@lyra/ui/components/Flex'
-import Grid from '@lyra/ui/components/Grid'
-import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps } from '@lyra/ui/types'
-import formatUSD from '@lyra/ui/utils/formatUSD'
-import { Network } from '@lyrafinance/lyra-js'
-import React, { useMemo } from 'react'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
-import { ZERO_BN } from '@/app/constants/bn'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import usePositionHistory from '@/app/hooks/position/usePositionHistory'
-import useLatestRewardEpoch from '@/app/hooks/rewards/useLatestRewardEpoch'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-import fromBigNumber from '@/app/utils/fromBigNumber'
-
-type Props = MarginProps
-
-const ShortCollateralRewardsCardGrid = withSuspense(
- ({ ...styleProps }: MarginProps) => {
- const network = useNetwork()
- const epochs = useLatestRewardEpoch(network)
- const positions = usePositionHistory(true)
- const globalRewardEpoch = epochs?.global
- const accountRewardEpoch = epochs?.account
- // @ TODO - DIllon to to loop through later
- const lyraRewardsCap = findLyraRewardEpochToken(globalRewardEpoch?.tradingRewardsCap ?? [])
- const opRewardsCap = findOpRewardEpochToken(globalRewardEpoch?.tradingRewardsCap ?? [])
- const lyraRewards = findLyraRewardEpochToken(accountRewardEpoch?.shortCollateralRewards ?? [])
- const opRewards = findOpRewardEpochToken(accountRewardEpoch?.shortCollateralRewards ?? [])
-
- const shortPositions = useMemo(() => {
- if (!globalRewardEpoch) {
- return []
- }
- return (
- positions
- // Consider open positions or positions closed this epoch
- .filter(p => !p.isLong && (!p.closeTimestamp || p.closeTimestamp > globalRewardEpoch.startTimestamp))
- )
- }, [globalRewardEpoch, positions])
-
- const shortCollateralValue = useMemo(() => {
- return shortPositions.reduce((sum, position) => {
- return sum.add(position.collateral?.value ?? ZERO_BN)
- }, ZERO_BN)
- }, [shortPositions])
-
- const { lyra: lyraYieldPerDay, op: opYieldPerDay } = useMemo(() => {
- if (!globalRewardEpoch) {
- return { lyra: 0, op: 0 }
- }
- return shortPositions.reduce(
- (sum, position) => {
- const shortCollateralYields = globalRewardEpoch.shortCollateralYieldPerDay(
- fromBigNumber(position.size),
- fromBigNumber(position.delta),
- position.expiryTimestamp,
- position.market().baseToken.symbol
- )
- // TODO @dillon - come back later
- const lyra = findLyraRewardEpochToken(shortCollateralYields)
- const op = findOpRewardEpochToken(shortCollateralYields)
- return { lyra: sum.lyra + lyra, op: sum.op + op }
- },
- { lyra: 0, op: 0 }
- )
- }, [shortPositions, globalRewardEpoch])
-
- // TODO: @dillon remove next epoch
- const isDepositPeriod = globalRewardEpoch?.isDepositPeriod
-
- return (
-
-
-
- Short Collateral
-
-
- {formatUSD(shortCollateralValue)}
-
-
-
-
- Short Yield / Day
-
-
-
- {opRewardsCap > 0 ? (
- <>
-
- ·
-
-
- >
- ) : null}
-
-
- {lyraRewardsCap > 0 ? (
-
-
- Pending stkLYRA
-
-
-
- ) : null}
- {opRewardsCap > 0 ? (
-
-
- Pending OP
-
-
-
- ) : null}
-
- )
- },
- ({ ...styleProps }: MarginProps) => {
- return (
-
-
-
-
- )
- }
-)
-
-const ShortCollateralRewardsCardSection = ({ ...marginProps }: Props): CardElement => {
- const walletNetwork = useNetwork()
- return (
-
-
- Short Rewards
-
-
-
- Lyra's shorting program rewards traders for selling calls and puts with Staked LYRA{' '}
- {walletNetwork === Network.Optimism ? 'and OP' : ''} tokens every 2 weeks. This program is not subject to
- boosts.
-
-
- )
-}
-
-export default ShortCollateralRewardsCardSection
diff --git a/app/src/containers/rewards/RewardsBreakdownCard/TradingRewardsCardSection.tsx b/app/src/containers/rewards/RewardsBreakdownCard/TradingRewardsCardSection.tsx
deleted file mode 100644
index 30f90ae7..00000000
--- a/app/src/containers/rewards/RewardsBreakdownCard/TradingRewardsCardSection.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Button from '@lyra/ui/components/Button'
-import { CardElement } from '@lyra/ui/components/Card'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Flex from '@lyra/ui/components/Flex'
-import Grid from '@lyra/ui/components/Grid'
-import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps } from '@lyra/ui/types'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import formatUSD from '@lyra/ui/utils/formatUSD'
-import { Network } from '@lyrafinance/lyra-js'
-import React, { useState } from 'react'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useLatestRewardEpoch from '@/app/hooks/rewards/useLatestRewardEpoch'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-
-import FeeRebateModal from '../../common/FeeRebateModal'
-
-type Props = MarginProps
-
-const TradingRewardsCardGrid = withSuspense(
- ({ ...styleProps }: Props) => {
- const network = useNetwork()
- const epochs = useLatestRewardEpoch(network)
- const globalRewardEpoch = epochs?.global
- const accountRewardEpoch = epochs?.account
- const tradingFees = accountRewardEpoch?.tradingFees ?? 0
- const minTradingFeeRebate = globalRewardEpoch?.minTradingFeeRebate ?? 0
- const tradingFeeRebate = accountRewardEpoch?.tradingFeeRebate ?? minTradingFeeRebate
- const lyraRewardsCap = findLyraRewardEpochToken(globalRewardEpoch?.tradingRewardsCap ?? [])
- const opRewardsCap = findOpRewardEpochToken(globalRewardEpoch?.tradingRewardsCap ?? [])
- const lyraRewards = findLyraRewardEpochToken(accountRewardEpoch?.tradingRewards ?? [])
- const opRewards = findOpRewardEpochToken(accountRewardEpoch?.tradingRewards ?? [])
-
- // TODO: @dillon remove next epoch
- const isDepositPeriod = globalRewardEpoch?.isDepositPeriod
-
- return (
-
-
-
- Total Fees
-
-
- {formatUSD(tradingFees)}
-
-
-
-
- Fee Rebate
-
-
- {formatPercentage(tradingFeeRebate, true)}
-
-
- {lyraRewardsCap > 0 ? (
-
-
- Pending stkLYRA
-
-
-
- ) : null}
- {opRewardsCap > 0 ? (
-
-
- Pending OP
-
-
-
- ) : null}
-
- )
- },
- ({ ...styleProps }: MarginProps) => {
- return (
-
-
-
-
- )
- }
-)
-
-const TradingRewardsCardSection = ({ ...marginProps }: MarginProps): CardElement => {
- const [isOpen, setIsOpen] = useState(false)
- const walletNetwork = useNetwork()
- return (
-
-
- Trading Rewards
-
-
-
- setIsOpen(true)} />
-
-
- Lyra's fee rebate program allows traders to earn back part of their fees as Staked LYRA{' '}
- {walletNetwork === Network.Optimism ? 'and OP' : ''} tokens every 2 weeks. Traders can stake LYRA to unlock a
- higher fee rebate tier.
-
- setIsOpen(false)} />
-
- )
-}
-
-export default TradingRewardsCardSection
diff --git a/app/src/containers/rewards/RewardsBreakdownCard/VaultsRewardsCardSection.tsx b/app/src/containers/rewards/RewardsBreakdownCard/VaultsRewardsCardSection.tsx
deleted file mode 100644
index c7d5351f..00000000
--- a/app/src/containers/rewards/RewardsBreakdownCard/VaultsRewardsCardSection.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import { CardElement } from '@lyra/ui/components/Card'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Flex from '@lyra/ui/components/Flex'
-import Grid from '@lyra/ui/components/Grid'
-import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps } from '@lyra/ui/types'
-import formatBalance from '@lyra/ui/utils/formatBalance'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import { AccountRewardEpoch, GlobalRewardEpoch, Market, Network } from '@lyrafinance/lyra-js'
-import React, { useMemo } from 'react'
-
-import MarketImage from '@/app/components/common/MarketImage'
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
-import TokenAPYRangeText from '@/app/components/common/TokenAPYRangeText'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useTradeBalances from '@/app/hooks/market/useTradeBalances'
-import useLatestRewardEpoch from '@/app/hooks/rewards/useLatestRewardEpoch'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-import formatTokenName from '@/app/utils/formatTokenName'
-
-type Props = MarginProps
-
-type RowProps = {
- accountRewardEpoch?: AccountRewardEpoch | null
- globalRewardEpoch: GlobalRewardEpoch
- market: Market
-}
-
-const VaultRewardsMyLiquidity = withSuspense(
- ({ market }: { market: Market }) => {
- const balances = useTradeBalances(market)
- // TODO: @dappbeast change to liquidity $$
- return (
- {formatBalance(balances.liquidityToken.balance, balances.liquidityToken.symbol)}
- )
- },
- () =>
-)
-
-const VaultRewardsMarketRow = ({ accountRewardEpoch, globalRewardEpoch, market }: RowProps) => {
- const marketAddress = market.address
- const opRewards = findOpRewardEpochToken(accountRewardEpoch?.vaultRewards(marketAddress) ?? [])
- const lyraRewards = findLyraRewardEpochToken(accountRewardEpoch?.vaultRewards(marketAddress) ?? [])
- const opApy = findOpRewardEpochToken(
- accountRewardEpoch?.vaultApy(marketAddress) ?? globalRewardEpoch.minVaultApy(marketAddress)
- )
- const lyraApy = findLyraRewardEpochToken(
- accountRewardEpoch?.vaultApy(marketAddress) ?? globalRewardEpoch.minVaultApy(marketAddress)
- )
- const maxApy = globalRewardEpoch.maxVaultApy(marketAddress).reduce((sum, tokens) => sum + tokens.amount, 0)
- const tokenNameOrAddress = market.lyra.network === Network.Optimism ? ['stkLyra', 'OP'] : ['stkLyra']
-
- // TODO: @dillon remove next epoch
- const isDepositPeriod = globalRewardEpoch.isDepositPeriod
-
- return (
-
-
-
- Vault
-
-
-
-
- {formatTokenName(market.baseToken)}
-
-
-
-
-
- Your Liquidity
-
-
-
-
-
- APY
-
-
-
- {lyraApy > 0 ? (
-
-
- Pending stkLYRA
-
-
-
- ) : null}
- {opApy > 0 ? (
-
-
- Pending OP
-
-
-
- ) : null}
-
- )
-}
-
-const VaultRewardsMarketRows = withSuspense(
- () => {
- const network = useNetwork()
- const epochs = useLatestRewardEpoch(network)
- const globalRewardEpoch = epochs?.global
- const accountRewardEpoch = epochs?.account
- const markets = useMemo(() => {
- return globalRewardEpoch
- ? globalRewardEpoch.markets.filter(market => {
- const lyra = findLyraRewardEpochToken(globalRewardEpoch.totalVaultRewards(market.address) ?? [])
- const op = findOpRewardEpochToken(globalRewardEpoch.totalVaultRewards(market.address) ?? [])
- return lyra > 0 || op > 0
- })
- : []
- }, [globalRewardEpoch])
- return (
- <>
- {globalRewardEpoch
- ? markets.map(market => {
- if (market.baseToken.symbol.toLowerCase() === 'ssol') {
- return null
- }
- return (
-
- )
- })
- : null}
- >
- )
- },
- () => {
- return (
-
-
-
-
- )
- }
-)
-
-const VaultsRewardsCardSection = ({ ...marginProps }: Props): CardElement => {
- const walletNetwork = useNetwork()
- return (
-
-
- Vault Rewards
-
-
-
- Lyra's vault program rewards liquidity providers with Staked{' '}
- {walletNetwork === Network.Optimism ? 'and OP' : ''} tokens every 2 weeks. Liquidity providers can stake LYRA to
- boost their rewards.
-
-
- )
-}
-
-export default VaultsRewardsCardSection
diff --git a/app/src/containers/rewards/RewardsBreakdownCard/WethLyraStakingRewardsCardSection.tsx b/app/src/containers/rewards/RewardsBreakdownCard/WethLyraStakingRewardsCardSection.tsx
deleted file mode 100644
index e79a36c1..00000000
--- a/app/src/containers/rewards/RewardsBreakdownCard/WethLyraStakingRewardsCardSection.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Button from '@lyra/ui/components/Button'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Flex from '@lyra/ui/components/Flex'
-import Grid from '@lyra/ui/components/Grid'
-import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
-import Text from '@lyra/ui/components/Text'
-import useIsMobile from '@lyra/ui/hooks/useIsMobile'
-import formatNumber from '@lyra/ui/utils/formatNumber'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import formatTruncatedUSD from '@lyra/ui/utils/formatTruncatedUSD'
-import formatUSD from '@lyra/ui/utils/formatUSD'
-import React, { useState } from 'react'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
-import { ZERO_BN } from '@/app/constants/bn'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useAccountWethLyraStaking from '@/app/hooks/rewards/useAccountWethLyraStaking'
-import useAccountWethLyraStakingL2 from '@/app/hooks/rewards/useAccountWethLyraStakingL2'
-import useWethLyraStaking from '@/app/hooks/rewards/useWethLyraStaking'
-import fromBigNumber from '@/app/utils/fromBigNumber'
-
-import ClaimWethLyraStakingRewardsModal from '../ClaimWethLyraStakingRewardsModal'
-import WethLyraL2UnstakeModal from '../WethLyraL2UnstakeModal'
-import WethLyraStakeModal from '../WethLyraStakeModal'
-import WethLyraUnstakeModal from '../WethLyraUnstakeModal'
-
-type ClaimButtonProps = {
- onOpenModal: () => void
-}
-
-type UnstakeOldButtonProps = {
- onOpenModal: () => void
-}
-
-const WethLyraRewardsGridItems = withSuspense(
- () => {
- const accountWethLyraStaking = useAccountWethLyraStaking()
- const wethLyraStaking = useWethLyraStaking()
- return (
- <>
-
-
- Your Staked LP Tokens
-
-
- {formatNumber(accountWethLyraStaking?.stakedLPTokenBalance ?? 0)}
- {accountWethLyraStaking?.stakedLPTokenBalance.gt(0)
- ? ` (${formatUSD(
- fromBigNumber(accountWethLyraStaking?.stakedLPTokenBalance ?? ZERO_BN) *
- (wethLyraStaking?.lpTokenValue ?? 0)
- )})`
- : null}
-
-
-
-
- Total Staked
-
- {formatTruncatedUSD(wethLyraStaking?.stakedTVL ?? ZERO_BN)}
-
-
-
- APY
-
- {formatPercentage(wethLyraStaking?.apy ?? 0, true)}
-
-
-
- LYRA Rewards
-
-
-
- >
- )
- },
- () => (
-
-
-
-
- )
-)
-
-const WethLyraStakingRewardsL1ClaimButton = withSuspense(({ onOpenModal }: ClaimButtonProps) => {
- const accountWethLyraStaking = useAccountWethLyraStaking()
- const rewards = accountWethLyraStaking?.rewards ?? ZERO_BN
- return rewards.gt(ZERO_BN) ? : null
-})
-
-const UnstakeOldWethLyraButton = withSuspense(({ onOpenModal }: UnstakeOldButtonProps) => {
- const wethLyraStakingL2 = useAccountWethLyraStakingL2()
- const oldStakedAmount = wethLyraStakingL2?.stakedLPTokenBalance ?? ZERO_BN
- return oldStakedAmount.gt(ZERO_BN) ? (
-
- ) : null
-})
-
-export default function WethLyraStakingRewardsCardSection() {
- const isMobile = useIsMobile()
- const [isStakeModalOpen, setIsStakeModalOpen] = useState(false)
- const [isUnstakeModalOpen, setIsUnstakeModalOpen] = useState(false)
- const [isClaimModalOpen, setIsClaimModalOpen] = useState(false)
- const [isOldUnstakeModalOpen, setIsOldUnstakeModalOpen] = useState(false)
- return (
-
-
-
- NEW
-
-
- WETH/LYRA Rewards
-
-
-
-
-
-
-
- Lyra's WETH/LYRA program rewards WETH/LYRA liquidity providers on the Uniswap v3 pool via Arrakis Finance.
- Liquidity providers earn LYRA tokens.
-
-
- setIsStakeModalOpen(!isStakeModalOpen)}
- />
- setIsUnstakeModalOpen(!isUnstakeModalOpen)} />
- setIsClaimModalOpen(!isClaimModalOpen)} />
- setIsOldUnstakeModalOpen(!isOldUnstakeModalOpen)} />
-
- setIsStakeModalOpen(false)} />
- setIsUnstakeModalOpen(false)} />
- setIsClaimModalOpen(false)} />
- setIsOldUnstakeModalOpen(false)} />
-
- )
-}
diff --git a/app/src/containers/rewards/RewardsBreakdownCard/index.tsx b/app/src/containers/rewards/RewardsBreakdownCard/index.tsx
deleted file mode 100644
index 543aeb4e..00000000
--- a/app/src/containers/rewards/RewardsBreakdownCard/index.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import Card, { CardElement } from '@lyra/ui/components/Card'
-import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
-import Flex from '@lyra/ui/components/Flex'
-import useIsMobile from '@lyra/ui/hooks/useIsMobile'
-import { MarginProps } from '@lyra/ui/types'
-import React from 'react'
-
-import ClaimableRewardsCardSection from './ClaimableRewardsCardSection'
-import PendingRewardsCardSection from './PendingRewardsCardSection'
-import ShortCollateralRewardsCardSection from './ShortCollateralRewardsSection'
-import TradingRewardsCardSection from './TradingRewardsCardSection'
-import VaultsRewardsCardSection from './VaultsRewardsCardSection'
-import WethLyraStakingRewardsCardSection from './WethLyraStakingRewardsCardSection'
-
-type Props = MarginProps
-
-const RewardsBreakdownCard = ({ ...marginProps }: Props): CardElement => {
- const isMobile = useIsMobile()
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-export default RewardsBreakdownCard
diff --git a/app/src/containers/rewards/RewardsHistoryCard/RewardsHistoryCardSection.tsx b/app/src/containers/rewards/RewardsHistoryCard/RewardsHistoryCardSection.tsx
deleted file mode 100644
index 1912bf71..00000000
--- a/app/src/containers/rewards/RewardsHistoryCard/RewardsHistoryCardSection.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps } from '@lyra/ui/types'
-import formatDate from '@lyra/ui/utils/formatDate'
-import { AccountRewardEpoch } from '@lyrafinance/lyra-js'
-import React from 'react'
-
-import ShortCollateralRewardsHistoryGrid from './ShortCollateralRewardsHistoryGrid'
-import StakingRewardsHistoryGrid from './StakingRewardsHistoryGrid'
-import TradingRewardsHistoryGrid from './TradingRewardsHistoryGrid'
-import VaultRewardsHistoryGrid from './VaultRewardsHistoryGrid'
-
-type Props = {
- epochNumber: number
- accountRewardEpoch: AccountRewardEpoch
-} & MarginProps
-
-const RewardsHistoryCardSection = ({ accountRewardEpoch, epochNumber, ...marginProps }: Props) => {
- return (
-
-
- Epoch {epochNumber}
-
- {formatDate(accountRewardEpoch.globalEpoch.startTimestamp, true)} -{' '}
- {formatDate(accountRewardEpoch.globalEpoch.endTimestamp, true)}
-
-
-
-
-
-
-
- )
-}
-
-export default RewardsHistoryCardSection
diff --git a/app/src/containers/rewards/RewardsHistoryCard/ShortCollateralRewardsHistoryGrid.tsx b/app/src/containers/rewards/RewardsHistoryCard/ShortCollateralRewardsHistoryGrid.tsx
deleted file mode 100644
index 1ea8e82c..00000000
--- a/app/src/containers/rewards/RewardsHistoryCard/ShortCollateralRewardsHistoryGrid.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Flex from '@lyra/ui/components/Flex'
-import Grid from '@lyra/ui/components/Grid'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps, PaddingProps } from '@lyra/ui/types'
-import { AccountRewardEpoch } from '@lyrafinance/lyra-js'
-import { Network } from '@lyrafinance/lyra-js'
-import React from 'react'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-
-type Props = {
- accountRewardEpoch: AccountRewardEpoch
-} & MarginProps &
- PaddingProps
-
-const ShortCollateralRewardsHistoryGrid = ({ accountRewardEpoch, ...marginProps }: Props) => {
- const network = useNetwork()
- const lyraRewards = findLyraRewardEpochToken(accountRewardEpoch.shortCollateralRewards ?? [])
- const opRewards = findOpRewardEpochToken(accountRewardEpoch.shortCollateralRewards ?? [])
- return (
-
-
- Short Collateral Rewards
-
-
-
-
- stkLYRA Rewards
-
-
-
- {network === Network.Optimism ? (
-
-
- OP Rewards
-
-
-
- ) : null}
-
-
- )
-}
-
-export default ShortCollateralRewardsHistoryGrid
diff --git a/app/src/containers/rewards/RewardsHistoryCard/StakingRewardsHistoryGrid.tsx b/app/src/containers/rewards/RewardsHistoryCard/StakingRewardsHistoryGrid.tsx
deleted file mode 100644
index 3b9a8acd..00000000
--- a/app/src/containers/rewards/RewardsHistoryCard/StakingRewardsHistoryGrid.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Flex from '@lyra/ui/components/Flex'
-import Grid from '@lyra/ui/components/Grid'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps, PaddingProps } from '@lyra/ui/types'
-import formatDate from '@lyra/ui/utils/formatDate'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import { AccountRewardEpoch, Network } from '@lyrafinance/lyra-js'
-import React from 'react'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-
-type Props = {
- accountRewardEpoch: AccountRewardEpoch
-} & MarginProps &
- PaddingProps
-
-const StakingRewardsHistoryGrid = ({ accountRewardEpoch, ...marginProps }: Props) => {
- const network = useNetwork()
- const lyraRewards = findLyraRewardEpochToken(accountRewardEpoch.stakingRewards)
- const opRewards = findOpRewardEpochToken(accountRewardEpoch.stakingRewards)
- const lyraUnlockTimestamp = findLyraRewardEpochToken(accountRewardEpoch?.stakingRewardsUnlockTimestamp ?? [])
- const showStakingRewards = lyraRewards > 0 || opRewards > 0
- const stakingApy = accountRewardEpoch.globalEpoch.stakingApy.reduce((total, apy) => total + apy.amount, 0)
- if (!showStakingRewards) {
- return null
- }
- return (
-
-
- Staking Rewards
-
-
-
-
- stkLYRA Rewards (Locked)
-
-
-
- {network === Network.Optimism ? (
-
-
- OP Rewards
-
-
-
- ) : null}
-
-
- LYRA Unlock
-
- {formatDate(lyraUnlockTimestamp)}
-
-
-
- Avg. APY
-
- {formatPercentage(stakingApy, true)}
-
-
-
- )
-}
-
-export default StakingRewardsHistoryGrid
diff --git a/app/src/containers/rewards/RewardsHistoryCard/TradingRewardsHistoryGrid.tsx b/app/src/containers/rewards/RewardsHistoryCard/TradingRewardsHistoryGrid.tsx
deleted file mode 100644
index 242116ae..00000000
--- a/app/src/containers/rewards/RewardsHistoryCard/TradingRewardsHistoryGrid.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Flex from '@lyra/ui/components/Flex'
-import Grid from '@lyra/ui/components/Grid'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps, PaddingProps } from '@lyra/ui/types'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import formatUSD from '@lyra/ui/utils/formatUSD'
-import { AccountRewardEpoch } from '@lyrafinance/lyra-js'
-import React from 'react'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-
-type Props = {
- accountRewardEpoch: AccountRewardEpoch
-} & MarginProps &
- PaddingProps
-
-const TradingRewardsHistoryGrid = ({ accountRewardEpoch, ...marginProps }: Props) => {
- const lyraRewards = findLyraRewardEpochToken(accountRewardEpoch.tradingRewards)
- const opRewards = findOpRewardEpochToken(accountRewardEpoch.tradingRewards)
- const { tradingFees, tradingFeeRebate } = accountRewardEpoch
- if (!opRewards || !lyraRewards) {
- return null
- }
- return (
-
-
- Trading Rewards
-
-
-
-
- stkLYRA Rewards
-
-
-
-
-
- OP Rewards
-
-
-
-
-
- Total Fees
-
- {formatUSD(tradingFees)}
-
-
-
- Fee Rebate
-
- {formatPercentage(tradingFeeRebate, true)}
-
-
-
- )
-}
-
-export default TradingRewardsHistoryGrid
diff --git a/app/src/containers/rewards/RewardsHistoryCard/VaultRewardsHistoryGrid.tsx b/app/src/containers/rewards/RewardsHistoryCard/VaultRewardsHistoryGrid.tsx
deleted file mode 100644
index c8c06699..00000000
--- a/app/src/containers/rewards/RewardsHistoryCard/VaultRewardsHistoryGrid.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Flex from '@lyra/ui/components/Flex'
-import Grid from '@lyra/ui/components/Grid'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps } from '@lyra/ui/types'
-import formatNumber from '@lyra/ui/utils/formatNumber'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import { AccountRewardEpoch, Market } from '@lyrafinance/lyra-js'
-import React, { useMemo } from 'react'
-
-import MarketImage from '@/app/components/common/MarketImage'
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-import formatTokenName from '@/app/utils/formatTokenName'
-
-type Props = {
- accountRewardEpoch: AccountRewardEpoch
-} & MarginProps
-
-type RowProps = {
- accountRewardEpoch: AccountRewardEpoch
- market: Market
-}
-
-const VaultRewardsHistoryMarketRow = ({ accountRewardEpoch, market }: RowProps) => {
- const marketAddress = market.address
- const vaultTokens = accountRewardEpoch ? accountRewardEpoch.vaultTokenBalance(marketAddress) : 0
- const opRewards = findOpRewardEpochToken(accountRewardEpoch.vaultRewards(marketAddress))
- const lyraRewards = findLyraRewardEpochToken(accountRewardEpoch.vaultRewards(marketAddress))
- const opApy = findOpRewardEpochToken(accountRewardEpoch.vaultApy(marketAddress))
- const lyraApy = findOpRewardEpochToken(accountRewardEpoch.vaultApy(marketAddress))
- const apyMultiplier = accountRewardEpoch.vaultApyMultiplier(marketAddress)
- const stakedLyraBalance = accountRewardEpoch.stakedLyraBalance
-
- return (
-
-
-
- Vault
-
-
-
-
- {formatTokenName(market.baseToken)}
-
-
-
- {lyraApy > 0 ? (
-
-
- stkLYRA Rewards
-
-
-
- ) : null}
- {opApy > 0 ? (
-
-
- OP Rewards
-
-
-
- ) : null}
-
-
- Avg. LP Tokens
-
- {formatNumber(vaultTokens)}
-
-
-
- Avg. APY
-
-
- {formatPercentage(opApy + lyraApy, true)} {apyMultiplier > 1 ? `(${formatNumber(apyMultiplier)}x)` : ''}
-
-
-
- )
-}
-
-const VaultRewardsHistoryGrid = ({ accountRewardEpoch, ...marginProps }: Props) => {
- const markets = accountRewardEpoch.globalEpoch.markets
- const marketsWithRewards = useMemo(() => {
- return markets.filter(market => {
- const vaultRewards = accountRewardEpoch.vaultRewards(market.address)
- const lyraVaultRewards = findLyraRewardEpochToken(vaultRewards)
- const opVaultRewards = findOpRewardEpochToken(vaultRewards)
- return lyraVaultRewards > 0 || opVaultRewards > 0
- })
- }, [accountRewardEpoch, markets])
- if (marketsWithRewards.length === 0) {
- return null
- }
- return (
-
-
- Vault Rewards
-
- {marketsWithRewards.map(market => (
-
- ))}
-
- )
-}
-
-export default VaultRewardsHistoryGrid
diff --git a/app/src/containers/rewards/RewardsHistoryCard/index.tsx b/app/src/containers/rewards/RewardsHistoryCard/index.tsx
deleted file mode 100644
index ee10008b..00000000
--- a/app/src/containers/rewards/RewardsHistoryCard/index.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Card from '@lyra/ui/components/Card'
-import CardBody from '@lyra/ui/components/Card/CardBody'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
-import Center from '@lyra/ui/components/Center'
-import Spinner from '@lyra/ui/components/Spinner'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps } from '@lyra/ui/types'
-import React, { useMemo, useState } from 'react'
-
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useAccountRewardEpochs from '@/app/hooks/rewards/useAccountRewardEpochs'
-
-import RewardsHistoryCardSection from './RewardsHistoryCardSection'
-
-export const REWARDS_HISTORY_DEFAULT_EPOCH_LIMIT = 3
-
-type Props = MarginProps
-
-const RewardsHistoryCard = withSuspense(
- ({ ...marginProps }: Props) => {
- const [showAll, setShowAll] = useState(false)
- const accountRewardEpochs = useAccountRewardEpochs()
- const epochs = useMemo(() => {
- const rewardEpochs = accountRewardEpochs
- .sort((a, b) => b.globalEpoch.endTimestamp - a.globalEpoch.endTimestamp)
- .slice(0, showAll ? undefined : REWARDS_HISTORY_DEFAULT_EPOCH_LIMIT)
- return rewardEpochs
- }, [accountRewardEpochs, showAll])
-
- return (
-
- {epochs.length === 0 ? (
-
- You have no rewards history
-
- ) : null}
- {epochs.map((accountRewardEpoch, idx) => {
- const epochNumber = accountRewardEpochs.length - idx
- return (
-
- {idx > 0 ? : null}
-
-
- )
- })}
- {!showAll && epochs.length >= REWARDS_HISTORY_DEFAULT_EPOCH_LIMIT ? (
- setShowAll(true)}
- sx={{
- alignItems: 'center',
- cursor: 'pointer',
- ':hover': { bg: 'buttonHover' },
- }}
- >
-
- Show More
-
-
- ) : null}
-
- )
- },
- (accountRewardEpochs, ...marginProps) => {
- return (
-
-
-
-
-
-
-
- )
- }
-)
-
-export default RewardsHistoryCard
diff --git a/app/src/containers/rewards/RewardsPageHeader/index.tsx b/app/src/containers/rewards/RewardsPageHeader/index.tsx
new file mode 100644
index 00000000..7e473d0a
--- /dev/null
+++ b/app/src/containers/rewards/RewardsPageHeader/index.tsx
@@ -0,0 +1,38 @@
+import Box from '@lyra/ui/components/Box'
+import IconButton from '@lyra/ui/components/Button/IconButton'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import { IconType } from '@lyra/ui/components/Icon'
+import Text from '@lyra/ui/components/Text'
+import React from 'react'
+
+import { PageId } from '@/app/constants/pages'
+import getPagePath from '@/app/utils/getPagePath'
+
+import RewardsTokenSupplyCard from '../RewardsTokenSupplyCard'
+
+type Props = {
+ showBackButton?: boolean
+}
+export default function RewardPageHeader({ showBackButton = true }: Props) {
+ return (
+
+
+
+
+ Rewards
+
+
+ Stake and Earn
+
+
+
+
+ {showBackButton ? (
+
+
+
+ ) : null}
+
+ )
+}
diff --git a/app/src/containers/rewards/RewardsStakingCard/StakedCardSection.tsx b/app/src/containers/rewards/RewardsStakingCard/StakedCardSection.tsx
deleted file mode 100644
index 4180d27e..00000000
--- a/app/src/containers/rewards/RewardsStakingCard/StakedCardSection.tsx
+++ /dev/null
@@ -1,247 +0,0 @@
-import Box from '@lyra/ui/components/Box'
-import Button from '@lyra/ui/components/Button'
-import { CardElement } from '@lyra/ui/components/Card'
-import CardSection from '@lyra/ui/components/Card/CardSection'
-import Grid from '@lyra/ui/components/Grid'
-import Icon, { IconType } from '@lyra/ui/components/Icon'
-import Link from '@lyra/ui/components/Link'
-import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
-import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
-import Text from '@lyra/ui/components/Text'
-import Tooltip from '@lyra/ui/components/Tooltip'
-import useIsMobile from '@lyra/ui/hooks/useIsMobile'
-import { MarginProps } from '@lyra/ui/types'
-import formatBalance from '@lyra/ui/utils/formatBalance'
-import formatNumber from '@lyra/ui/utils/formatNumber'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import React, { useMemo, useState } from 'react'
-
-import RowItem from '@/app/components/common/RowItem'
-import StakeAPYTooltip from '@/app/components/common/StakeAPYTooltip'
-import { ZERO_BN } from '@/app/constants/bn'
-import UnstakeModal from '@/app/containers/rewards/UnstakeModal'
-import useNetwork from '@/app/hooks/account/useNetwork'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useClaimableBalances from '@/app/hooks/rewards/useClaimableBalance'
-import useClaimableBalancesL1 from '@/app/hooks/rewards/useClaimableBalanceL1'
-import useLatestRewardEpoch from '@/app/hooks/rewards/useLatestRewardEpoch'
-import useLyraAccountStaking from '@/app/hooks/rewards/useLyraAccountStaking'
-import { findLyraRewardEpochToken } from '@/app/utils/findRewardToken'
-
-import ClaimAndMigrateModal from '../ClaimAndMigrateModal'
-import ClaimStakingRewardsModal from '../ClaimStakingRewardsModal'
-import StakeModal from '../StakeModal'
-
-type Props = MarginProps
-
-type StakedCardSectionButtonProps = {
- onStakeOpen: () => void
- onUnstakeOpen: () => void
- onClaimL1Open: () => void
- onClaimAndMigrateOpen: () => void
-}
-
-const StakedLyraBalanceText = withSuspense(
- (): CardElement => {
- const lyraAccountStaking = useLyraAccountStaking()
- const { optimismOldStkLyra, optimismStkLyra, ethereumStkLyra } = useMemo(() => {
- const optimismOldStkLyra = lyraAccountStaking?.lyraBalances.optimismOldStkLyra ?? ZERO_BN
- const ethereumStkLyra = lyraAccountStaking?.lyraBalances.ethereumStkLyra ?? ZERO_BN
- const optimismStkLyra = lyraAccountStaking?.lyraBalances.optimismStkLyra ?? ZERO_BN
- return {
- optimismOldStkLyra,
- optimismStkLyra,
- ethereumStkLyra,
- }
- }, [lyraAccountStaking])
- const balance = optimismOldStkLyra.add(optimismStkLyra).add(ethereumStkLyra)
- return (
-
-
- {optimismStkLyra.gt(0) ? (
-
- ) : null}
- {optimismOldStkLyra.gt(0) ? (
-
- ) : null}
- {optimismOldStkLyra.gt(0) ? (
-
- Migrate your staked LYRA on Optimism to a new version of staked LYRA that is native to Ethereum mainnet.
- You'll need to migrate to continue to earn boosts on your vault and trading rewards.{' '}
-
- Learn more
-
-
- ) : optimismStkLyra.gt(0) ? (
-
- Bridge your stkLYRA from Optimism to Ethereum mainnet via the{' '}
-
- Optimism Gateway
- {' '}
- to earn staking rewards.
-
- ) : null}
-
- }
- alignItems="center"
- flexDirection="row"
- >
-
- {formatBalance(balance, 'stkLYRA')}
-
-
-
- )
- },
- () => {
- return
- }
-)
-
-const StakedLyraAPYText = withSuspense(
- () => {
- const network = useNetwork()
- const globalEpoch = useLatestRewardEpoch(network)?.global
- const stakingApyLyra = findLyraRewardEpochToken(globalEpoch?.stakingApy ?? [])
- const stakingApyTotal = globalEpoch?.stakingApy.reduce((total, apy) => total + apy.amount, 0) ?? 0
- return (
-
-
- {formatPercentage(stakingApyTotal, true)}
-
-
- )
- },
- () =>
-)
-
-const StakingRewardsText = withSuspense(
- (): CardElement => {
- const claimableBalances = useClaimableBalances()
- const claimableBalancesL1 = useClaimableBalancesL1()
- let claimableBalance = ZERO_BN
- if (claimableBalances.oldStkLyra.gt(ZERO_BN)) {
- claimableBalance = claimableBalances.oldStkLyra
- } else if (claimableBalancesL1.newStkLyra) {
- claimableBalance = claimableBalancesL1.newStkLyra
- }
- return (
-
- {formatNumber(claimableBalance)} stkLYRA
-
- )
- },
- () =>
-)
-
-const StakedCardSectionButton = withSuspense(
- ({ onStakeOpen, onUnstakeOpen, onClaimAndMigrateOpen, onClaimL1Open }: StakedCardSectionButtonProps) => {
- const claimableBalances = useClaimableBalances()
- const claimableBalancesL1 = useClaimableBalancesL1()
- const lyraAccountStaking = useLyraAccountStaking()
- const optimismOldStkLyra = lyraAccountStaking?.lyraBalances.optimismOldStkLyra ?? ZERO_BN
- const hasClaimableOldStkLyra = claimableBalances.oldStkLyra.gt(0)
- const hasOldStkLyra = optimismOldStkLyra.gt(0)
- if (hasClaimableOldStkLyra || hasOldStkLyra) {
- return (
-
-
-
- )
- }
- return (
-
-
-
- {claimableBalancesL1.newStkLyra.gt(ZERO_BN) ? (
-
- ) : null}
-
- )
- },
- () => {
- return (
-
-
-
-
- )
- }
-)
-
-const StakedCardSection = ({ ...marginProps }: Props): CardElement => {
- const [isUnstakeOpen, setIsUnstakeOpen] = useState(false)
- const [isStakeOpen, setIsStakeOpen] = useState(false)
- const [isL1ClaimOpen, setIsClaimL1Open] = useState(false)
- const [isClaimAndMigrateOpen, setIsClaimAndMigrateOpen] = useState(false)
- const isMobile = useIsMobile()
-
- return (
-
-
- Staked
-
-
-
-
-
- Claimable Rewards
-
-
-
-
-
- APY
-
-
-
-
- setIsStakeOpen(true)}
- onUnstakeOpen={() => setIsUnstakeOpen(true)}
- onClaimAndMigrateOpen={() => setIsClaimAndMigrateOpen(true)}
- onClaimL1Open={() => setIsClaimL1Open(true)}
- />
- setIsUnstakeOpen(false)} />
- setIsStakeOpen(false)} />
- setIsClaimAndMigrateOpen(false)} />
- setIsClaimL1Open(false)} />
-
- )
-}
-
-export default StakedCardSection
diff --git a/app/src/containers/rewards/RewardsStakingCard/index.tsx b/app/src/containers/rewards/RewardsStakingCard/index.tsx
deleted file mode 100644
index 647a405b..00000000
--- a/app/src/containers/rewards/RewardsStakingCard/index.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import Card, { CardElement } from '@lyra/ui/components/Card'
-import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
-import useIsMobile from '@lyra/ui/hooks/useIsMobile'
-import { MarginProps } from '@lyra/ui/types'
-import React from 'react'
-
-import NotStakedCardSection from './NotStakedCardSection'
-import StakedCardSection from './StakedCardSection'
-
-type Props = MarginProps
-
-const RewardsStakingCard = ({ ...marginProps }: Props): CardElement => {
- const isMobile = useIsMobile()
- return (
-
-
-
-
-
- )
-}
-
-export default RewardsStakingCard
diff --git a/app/src/containers/rewards/RewardsTokenSupplyCard/index.tsx b/app/src/containers/rewards/RewardsTokenSupplyCard/index.tsx
new file mode 100644
index 00000000..a62eab50
--- /dev/null
+++ b/app/src/containers/rewards/RewardsTokenSupplyCard/index.tsx
@@ -0,0 +1,87 @@
+import Box from '@lyra/ui/components/Box'
+import Card from '@lyra/ui/components/Card'
+import CardBody from '@lyra/ui/components/Card/CardBody'
+import Flex from '@lyra/ui/components/Flex'
+import LinearProgress from '@lyra/ui/components/LinearProgress'
+import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
+import Text from '@lyra/ui/components/Text'
+import Tooltip from '@lyra/ui/components/Tooltip'
+import { MarginProps } from '@lyra/ui/types'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import formatPercentage from '@lyra/ui/utils/formatPercentage'
+import formatTruncatedNumber from '@lyra/ui/utils/formatTruncatedNumber'
+import React from 'react'
+
+import RowItem from '@/app/components/common/RowItem'
+import withSuspense from '@/app/hooks/data/withSuspense'
+import useTokenSupply from '@/app/hooks/rewards/useTokenSupply'
+
+const RewardsTokenSupplyCard = withSuspense(
+ ({ ...styleProps }: MarginProps) => {
+ const supply = useTokenSupply()
+ const circulatingSupplyPct = supply ? supply.totalCirculatingSupply / supply.totalSupply : 0
+ return supply ? (
+
+
+
+ Circulating Supply
+
+
+
+
+
+
+
+ }
+ >
+ {formatNumber(supply.totalCirculatingSupply)}
+
+
+ {formatNumber(supply.totalSupply)}
+
+
+
+ {formatPercentage(circulatingSupplyPct, true)}
+
+
+ ) : null
+ },
+ () => (
+
+
+
+ Circulating Supply
+
+
+
+ {formatNumber(1_000_000_000)}
+
+
+
+
+
+ )
+)
+
+export default RewardsTokenSupplyCard
diff --git a/app/src/containers/rewards/ShortRewardsSection/ShortRewardsCards.tsx b/app/src/containers/rewards/ShortRewardsSection/ShortRewardsCards.tsx
new file mode 100644
index 00000000..fa69b7c6
--- /dev/null
+++ b/app/src/containers/rewards/ShortRewardsSection/ShortRewardsCards.tsx
@@ -0,0 +1,168 @@
+import IconButton from '@lyra/ui/components/Button/IconButton'
+import Card from '@lyra/ui/components/Card'
+import CardBody from '@lyra/ui/components/Card/CardBody'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import { IconType } from '@lyra/ui/components/Icon'
+import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
+import Text from '@lyra/ui/components/Text'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import { MarginProps } from '@lyra/ui/types'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import formatTruncatedUSD from '@lyra/ui/utils/formatTruncatedUSD'
+import formatUSD from '@lyra/ui/utils/formatUSD'
+import { GlobalRewardEpoch, Network, RewardEpochTokenAmount } from '@lyrafinance/lyra-js'
+import React, { useMemo } from 'react'
+import { useNavigate } from 'react-router-dom'
+
+import NetworkImage from '@/app/components/common/NetworkImage'
+import { ZERO_BN } from '@/app/constants/bn'
+import { REWARDS_CARD_GRID_COLUMN_TEMPLATE } from '@/app/constants/layout'
+import { PageId } from '@/app/constants/pages'
+import withSuspense from '@/app/hooks/data/withSuspense'
+import usePositionHistory from '@/app/hooks/position/usePositionHistory'
+import useNetworkTradingVolume from '@/app/hooks/rewards/useNetworkTradingVolume'
+import fromBigNumber from '@/app/utils/fromBigNumber'
+import getNetworkDisplayName from '@/app/utils/getNetworkDisplayName'
+import getPagePath from '@/app/utils/getPagePath'
+
+type Props = {
+ globalRewardEpoch: GlobalRewardEpoch
+} & MarginProps
+
+const ShortCollateralValueText = withSuspense(
+ ({ globalRewardEpoch }: { globalRewardEpoch: GlobalRewardEpoch }) => {
+ const positions = usePositionHistory(true)
+ const shortPositions = useMemo(
+ () =>
+ positions
+ // Consider open positions or positions closed this epoch
+ .filter(p => !p.isLong && (!p.closeTimestamp || p.closeTimestamp > globalRewardEpoch.startTimestamp)),
+
+ [globalRewardEpoch, positions]
+ )
+ const shortCollateralValue = useMemo(
+ () => shortPositions.reduce((sum, position) => sum.add(position.collateral?.value ?? ZERO_BN), ZERO_BN),
+ [shortPositions]
+ )
+ return (
+
+ {formatUSD(shortCollateralValue, { maxDps: 2 })}
+
+ )
+ },
+ () =>
+)
+
+const NetworkTVLText = withSuspense(
+ ({ network }: { network: Network }) => {
+ const { totalShortOpenInterestUSD } = useNetworkTradingVolume(network)
+ return (
+
+ {formatTruncatedUSD(totalShortOpenInterestUSD)}
+
+ )
+ },
+ () =>
+)
+
+const ShortCollateralYieldText = withSuspense(({ globalRewardEpoch }: { globalRewardEpoch: GlobalRewardEpoch }) => {
+ const positions = usePositionHistory(true)
+ const shortPositions = useMemo(
+ () =>
+ // Consider open positions or positions closed this epoch
+ positions.filter(p => !p.isLong && (!p.closeTimestamp || p.closeTimestamp > globalRewardEpoch.startTimestamp)),
+ [globalRewardEpoch, positions]
+ )
+ const shortCollateralYields = useMemo(() => {
+ const rewardTokens = Object.values(
+ shortPositions.reduce((sum, position) => {
+ globalRewardEpoch
+ .shortCollateralYieldPerDay(
+ fromBigNumber(position.size),
+ fromBigNumber(position.delta),
+ position.expiryTimestamp,
+ position.market().baseToken.symbol
+ )
+ .forEach(rewardToken => {
+ if (!sum[rewardToken.symbol]) {
+ sum[rewardToken.symbol] = rewardToken
+ return
+ }
+ sum[rewardToken.symbol].amount += rewardToken.amount
+ })
+ return sum
+ }, {} as Record)
+ )
+ return rewardTokens.length ? rewardTokens : globalRewardEpoch.tradingRewards(0, 0)
+ }, [shortPositions, globalRewardEpoch])
+ const displayYieldToken = shortCollateralYields.reduce(
+ (max, yieldToken) => (yieldToken.amount > max.amount ? yieldToken : max),
+ shortCollateralYields[0]
+ )
+ return (
+
+ {formatNumber(displayYieldToken.amount, { maxDps: 2 })} {displayYieldToken.symbol}
+
+ )
+})
+
+const ShortRewardsCards = ({ globalRewardEpoch, ...styleProps }: Props) => {
+ const navigate = useNavigate()
+ const isMobile = useIsMobile()
+ return (
+ navigate(getPagePath({ page: PageId.RewardsShorts, network: globalRewardEpoch.lyra.network }))}
+ {...styleProps}
+ >
+
+
+
+
+
+ Shorts · {globalRewardEpoch ? getNetworkDisplayName(globalRewardEpoch.lyra.network) : null}
+
+
+ {!isMobile ? (
+ <>
+
+
+ TVL
+
+
+
+
+
+ Your Collateral
+
+
+
+
+
+ Yield / Day
+
+
+
+ >
+ ) : null}
+
+
+
+
+
+
+
+ )
+}
+
+export default ShortRewardsCards
diff --git a/app/src/containers/rewards/ShortRewardsSection/index.tsx b/app/src/containers/rewards/ShortRewardsSection/index.tsx
new file mode 100644
index 00000000..68608b58
--- /dev/null
+++ b/app/src/containers/rewards/ShortRewardsSection/index.tsx
@@ -0,0 +1,37 @@
+import Box from '@lyra/ui/components/Box'
+import { CardElement } from '@lyra/ui/components/Card'
+import Flex from '@lyra/ui/components/Flex'
+import Text from '@lyra/ui/components/Text'
+import { MarginProps } from '@lyra/ui/types'
+import React from 'react'
+
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+
+import ShortRewardsCards from './ShortRewardsCards'
+
+type Props = {
+ latestRewardEpochs: LatestRewardEpoch[]
+} & MarginProps
+const ShortRewardsSection = ({ latestRewardEpochs, ...marginProps }: Props): CardElement => {
+ return (
+
+
+
+ Short Collateral
+
+ Earn rewards by selling options.
+
+
+ {latestRewardEpochs.map((latestRewardEpoch, i) => (
+
+ ))}
+
+
+ )
+}
+
+export default ShortRewardsSection
diff --git a/app/src/containers/rewards/StakeCardBody/StakeCardBodyBottomSection.tsx b/app/src/containers/rewards/StakeCardBody/StakeCardBodyBottomSection.tsx
deleted file mode 100644
index f83c0d40..00000000
--- a/app/src/containers/rewards/StakeCardBody/StakeCardBodyBottomSection.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { BigNumber } from '@ethersproject/bignumber'
-import ModalSection from '@lyra/ui/components/Modal/ModalSection'
-import Shimmer from '@lyra/ui/components/Shimmer'
-import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps } from '@lyra/ui/types'
-import { LayoutProps } from '@lyra/ui/types'
-import formatPercentage from '@lyra/ui/utils/formatPercentage'
-import { Market } from '@lyrafinance/lyra-js'
-import React, { useEffect } from 'react'
-import { Flex } from 'rebass'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
-import { ZERO_BN } from '@/app/constants/bn'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useMarkets from '@/app/hooks/rewards/useMarkets'
-import useStake from '@/app/hooks/rewards/useStake'
-import { findLyraRewardEpochToken } from '@/app/utils/findRewardToken'
-
-import StakeFormButton from './StakeCardBodyButton'
-
-type StakeCardBodyBottomSectionProps = {
- amount: BigNumber
- vault: Market | null
- setVault: (vault: Market) => void
- onClose: () => void
-} & LayoutProps &
- MarginProps
-
-const StakeCardBodyBottomSection = withSuspense(
- ({ vault, amount, setVault, onClose, ...styleProps }: StakeCardBodyBottomSectionProps) => {
- const markets = useMarkets()
- useEffect(() => {
- if (!vault) {
- setVault(markets[0])
- }
- }, [markets, vault, setVault])
- const stake = useStake(amount)
- const newLyraStakingYieldPerDay = findLyraRewardEpochToken(stake?.newStakingYieldPerDay ?? [])
- const newTradingFeeRebate = stake ? stake.newTradingFeeRebate : 0
- const newStakedLyraBalanceDelta = (stake?.newStakedLyraBalance ?? ZERO_BN).sub(stake?.stakedLyraBalance ?? ZERO_BN)
- return (
-
-
- Staking Yield / Day
-
-
-
-
-
- Fee Rebate
-
- {formatPercentage(newTradingFeeRebate, true)}
-
-
-
-
- )
- },
- ({ vault, amount, setVault, onClose, ...styleProps }: StakeCardBodyBottomSectionProps) => {
- return (
-
-
-
- Staking Yield / Day
-
-
-
-
-
- Fee Rebate
-
-
-
-
-
-
-
-
-
- )
- }
-)
-
-export default StakeCardBodyBottomSection
diff --git a/app/src/containers/rewards/StakeCardBody/StakeCardBodyTopSection.tsx b/app/src/containers/rewards/StakeCardBody/StakeCardBodyTopSection.tsx
deleted file mode 100644
index 82a3620c..00000000
--- a/app/src/containers/rewards/StakeCardBody/StakeCardBodyTopSection.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { BigNumber } from '@ethersproject/bignumber'
-import ModalSection from '@lyra/ui/components/Modal/ModalSection'
-import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
-import Text from '@lyra/ui/components/Text'
-import { MarginProps } from '@lyra/ui/types'
-import { LayoutProps } from '@lyra/ui/types'
-import React from 'react'
-import { Flex } from 'rebass'
-
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
-import { ZERO_BN } from '@/app/constants/bn'
-import withSuspense from '@/app/hooks/data/withSuspense'
-import useLyraAccountStaking from '@/app/hooks/rewards/useLyraAccountStaking'
-
-import StakeFormAmountInput from './StakeCardBodyAmountInput'
-
-type StakeCardBodyTopSectionProps = {
- amount: BigNumber
- onChangeAmount: (amount: BigNumber) => void
-} & LayoutProps &
- MarginProps
-
-const StakeCardBodyTopSectionBalance = withSuspense(
- () => {
- const lyraAccountStaking = useLyraAccountStaking()
- return
- },
- () => {
- return
- }
-)
-
-const StakeCardBodyTopSectionInput = withSuspense(
- ({ amount, onChangeAmount }: StakeCardBodyTopSectionProps) => {
- const lyraAccountStaking = useLyraAccountStaking()
- const maxStakeBalance = lyraAccountStaking?.lyraBalances.ethereumLyra ?? ZERO_BN
- return (
-
- )
- },
- () => {
- return
- }
-)
-
-const StakeCardBodyTopSection = ({ amount, onChangeAmount, ...styleProps }: StakeCardBodyTopSectionProps) => {
- return (
-
-
- By staking LYRA you earn stkLYRA rewards and you receive boosts on your vault and trading rewards. Staked LYRA
- has a 14 day unstaking period.
-
-
-
- Stakeable Balance
-
-
-
-
-
- Amount to Stake
-
-
-
-
- )
-}
-
-export default StakeCardBodyTopSection
diff --git a/app/src/containers/rewards/StakeCardBody/index.tsx b/app/src/containers/rewards/StakeCardBody/index.tsx
deleted file mode 100644
index 496bb6e3..00000000
--- a/app/src/containers/rewards/StakeCardBody/index.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { BigNumber } from '@ethersproject/bignumber'
-import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
-import { Market } from '@lyrafinance/lyra-js'
-import React, { useState } from 'react'
-
-import { ZERO_BN } from '@/app/constants/bn'
-
-import StakeCardBodyBottomSection from './StakeCardBodyBottomSection'
-import StakeCardBodyTopSection from './StakeCardBodyTopSection'
-
-type Props = {
- onClose: () => void
-}
-
-const StakeCardBody = ({ onClose }: Props) => {
- const [vault, setVault] = useState(null)
- const [amount, setAmount] = useState(ZERO_BN)
- return (
- <>
-
-
-
- >
- )
-}
-
-export default StakeCardBody
diff --git a/app/src/containers/rewards/StakeCardBody/StakeCardBodyAmountInput.tsx b/app/src/containers/rewards/StakeModal/StakeFormAmountInput.tsx
similarity index 100%
rename from app/src/containers/rewards/StakeCardBody/StakeCardBodyAmountInput.tsx
rename to app/src/containers/rewards/StakeModal/StakeFormAmountInput.tsx
diff --git a/app/src/containers/rewards/StakeCardBody/StakeCardBodyButton.tsx b/app/src/containers/rewards/StakeModal/StakeFormButton.tsx
similarity index 62%
rename from app/src/containers/rewards/StakeCardBody/StakeCardBodyButton.tsx
rename to app/src/containers/rewards/StakeModal/StakeFormButton.tsx
index 49f16f4a..dbb11d7f 100644
--- a/app/src/containers/rewards/StakeCardBody/StakeCardBodyButton.tsx
+++ b/app/src/containers/rewards/StakeModal/StakeFormButton.tsx
@@ -2,17 +2,18 @@ import { BigNumber } from '@ethersproject/bignumber'
import Box from '@lyra/ui/components/Box'
import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
import { MarginProps } from '@lyra/ui/types'
-import { Network, StakeDisabledReason } from '@lyrafinance/lyra-js'
-import React, { useCallback, useMemo } from 'react'
+import React, { useCallback } from 'react'
import { LogEvent } from '@/app/constants/logEvents'
import { TransactionType } from '@/app/constants/screen'
+import useAccountLyraBalances, { useMutateAccountLyraBalances } from '@/app/hooks/account/useAccountLyraBalances'
import useTransaction from '@/app/hooks/account/useTransaction'
+import useWalletAccount from '@/app/hooks/account/useWalletAccount'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useAccount from '@/app/hooks/market/useAccount'
import { useMutateAccountStaking } from '@/app/hooks/rewards/useLyraAccountStaking'
-import useStake from '@/app/hooks/rewards/useStake'
+import { useMutateLyraStaking } from '@/app/hooks/rewards/useLyraStaking'
import logEvent from '@/app/utils/logEvent'
+import { lyraOptimism } from '@/app/utils/lyra'
import TransactionButton from '../../common/TransactionButton'
@@ -23,54 +24,51 @@ type Props = {
const StakeFormButton = withSuspense(
({ amount, onClose, ...styleProps }: Props) => {
- const stake = useStake(amount)
- const account = useAccount(Network.Optimism)
+ const lyraBalances = useAccountLyraBalances()
+ const account = useWalletAccount()
const execute = useTransaction('ethereum')
- const { insufficientAllowance, insufficientBalance } = useMemo(() => {
- const insufficientAllowance = stake?.disabledReason === StakeDisabledReason.InsufficientAllowance
- const insufficientBalance = stake?.disabledReason === StakeDisabledReason.InsufficientBalance
- return {
- insufficientAllowance,
- insufficientBalance,
- }
- }, [stake])
+ const insufficientBalance = lyraBalances.ethereumLyra.lt(amount)
+ const insufficientAllowance = lyraBalances.stakingAllowance.lt(amount)
const mutateAccountStaking = useMutateAccountStaking()
+ const mutateLyraStaking = useMutateLyraStaking()
+ const mutateLyraBalances = useMutateAccountLyraBalances()
const handleClickApprove = useCallback(async () => {
if (!account) {
console.warn('Account does not exist')
return null
}
logEvent(LogEvent.StakeLyraApproveSubmit)
- const tx = await account.approveStake()
+ const tx = await lyraOptimism.approveStaking(account)
await execute(tx, {
onComplete: async () => {
logEvent(LogEvent.StakeLyraApproveSuccess)
- await Promise.all([mutateAccountStaking()])
+ await Promise.all([mutateLyraBalances()])
},
onError: error => logEvent(LogEvent.StakeLyraApproveError, { error: error?.message }),
})
- }, [account, execute, mutateAccountStaking])
+ }, [account, execute, mutateLyraBalances])
const handleClickStake = useCallback(async () => {
- if (!account || !stake) {
+ if (!account) {
console.warn('Account or stake does not exist')
return
}
- logEvent(LogEvent.StakeLyraSubmit, { stakeAmount: stake.amount })
- if (stake.tx) {
- await execute(stake.tx, {
+ logEvent(LogEvent.StakeLyraSubmit, { stakeAmount: amount })
+ const tx = await lyraOptimism.stake(account, amount)
+ if (tx) {
+ await execute(tx, {
onComplete: async () => {
- logEvent(LogEvent.StakeLyraSuccess, { stakeAmount: stake.amount })
- await Promise.all([mutateAccountStaking()])
+ logEvent(LogEvent.StakeLyraSuccess, { stakeAmount: amount })
+ await Promise.all([mutateAccountStaking(), mutateLyraStaking()])
onClose()
},
onError: error => {
- logEvent(LogEvent.StakeLyraError, { stakeAmount: stake.amount, error: error?.message })
+ logEvent(LogEvent.StakeLyraError, { stakeAmount: amount, error: error?.message })
onClose()
},
})
}
- }, [account, execute, stake, onClose, mutateAccountStaking])
+ }, [account, amount, execute, onClose, mutateLyraStaking, mutateAccountStaking])
const stakeButton = (
void
}
-export default function StakeModal(props: Props) {
- const { isOpen, onClose } = props
+type StakeCardBodyTopSectionProps = {
+ amount: BigNumber
+ onChangeAmount: (amount: BigNumber) => void
+}
+const StakeCardBalanceText = withSuspense(
+ () => {
+ const lyraBalances = useAccountLyraBalances()
+ return {formatNumber(lyraBalances.ethereumLyra)} LYRA
+ },
+ () =>
+)
+
+const StakeCardInput = withSuspense(
+ ({ amount, onChangeAmount }: StakeCardBodyTopSectionProps) => {
+ const lyraBalances = useAccountLyraBalances()
+ const maxStakeBalance = lyraBalances.ethereumLyra
+ return (
+
+ )
+ },
+ () => {
+ return
+ }
+)
+
+export default function StakeModal({ isOpen, onClose, globalRewardEpoch }: Props) {
+ const [amount, setAmount] = useState(ZERO_BN)
return (
-
-
+
+
+
+ By staking LYRA you earn stkLYRA rewards and you receive boosts on your vault and trading rewards. Staked LYRA
+ has a 14 day unstaking period.
+
+ setAmount(amount)} />}
+ />
+ } />
+ {formatPercentage(globalRewardEpoch.stakingApy, true)}}
+ />
+
+
)
}
diff --git a/app/src/containers/rewards/RewardsStakingCard/NotStakedCardSection.tsx b/app/src/containers/rewards/StakingRewardsCard/NotStakedCardSection.tsx
similarity index 71%
rename from app/src/containers/rewards/RewardsStakingCard/NotStakedCardSection.tsx
rename to app/src/containers/rewards/StakingRewardsCard/NotStakedCardSection.tsx
index 8c27c849..172058f7 100644
--- a/app/src/containers/rewards/RewardsStakingCard/NotStakedCardSection.tsx
+++ b/app/src/containers/rewards/StakingRewardsCard/NotStakedCardSection.tsx
@@ -1,4 +1,3 @@
-import Box from '@lyra/ui/components/Box'
import Button from '@lyra/ui/components/Button'
import { CardElement } from '@lyra/ui/components/Card'
import CardSection from '@lyra/ui/components/Card/CardSection'
@@ -11,19 +10,16 @@ import { MarginProps } from '@lyra/ui/types'
import formatTruncatedNumber from '@lyra/ui/utils/formatTruncatedNumber'
import React, { useMemo } from 'react'
-import { ONE_INCH_URL } from '@/app/constants/links'
+import { SWAP_TOKEN_1INCH_URL } from '@/app/constants/links'
+import useAccountLyraBalances from '@/app/hooks/account/useAccountLyraBalances'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useLyraAccountStaking from '@/app/hooks/rewards/useLyraAccountStaking'
type Props = MarginProps
const LyraBalanceText = withSuspense(
(): CardElement => {
- const lyraAccountStaking = useLyraAccountStaking()
- const lyraBalance = useMemo(
- () => lyraAccountStaking?.lyraBalances.ethereumLyra.add(lyraAccountStaking?.lyraBalances.optimismLyra) ?? 0,
- [lyraAccountStaking]
- )
+ const lyraBalances = useAccountLyraBalances()
+ const lyraBalance = useMemo(() => lyraBalances.ethereumLyra.add(lyraBalances.optimismLyra), [lyraBalances])
return (
{formatTruncatedNumber(lyraBalance)} LYRA
@@ -44,13 +40,11 @@ const NotStakedCardSection = ({ ...marginProps }: Props): CardElement => {
width={!isMobile ? `50%` : undefined}
{...marginProps}
>
-
-
- Not Staked
-
-
-
-
+
+ Not Staked
+
+
+
By staking LYRA you earn stkLYRA rewards and receive boosts on your vault and trading rewards. Staked LYRA has a
14 day unstaking period.
@@ -59,7 +53,7 @@ const NotStakedCardSection = ({ ...marginProps }: Props): CardElement => {
size="lg"
label="LYRA on 1inch"
rightIcon={IconType.ArrowUpRight}
- href={`${ONE_INCH_URL}/#/10/unified/swap/ETH/LYRA`}
+ href={`${SWAP_TOKEN_1INCH_URL}/LYRA`}
target="_blank"
/>
diff --git a/app/src/containers/rewards/StakingRewardsCard/StakedCardSection.tsx b/app/src/containers/rewards/StakingRewardsCard/StakedCardSection.tsx
new file mode 100644
index 00000000..8a994fac
--- /dev/null
+++ b/app/src/containers/rewards/StakingRewardsCard/StakedCardSection.tsx
@@ -0,0 +1,89 @@
+import { CardElement } from '@lyra/ui/components/Card'
+import CardSection from '@lyra/ui/components/Card/CardSection'
+import Grid from '@lyra/ui/components/Grid'
+import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
+import Text from '@lyra/ui/components/Text'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import { MarginProps } from '@lyra/ui/types'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import formatPercentage from '@lyra/ui/utils/formatPercentage'
+import React, { useState } from 'react'
+
+import LabelItem from '@/app/components/common/LabelItem'
+import { ZERO_BN } from '@/app/constants/bn'
+import UnstakeModal from '@/app/containers/rewards/UnstakeModal'
+import withSuspense from '@/app/hooks/data/withSuspense'
+import useClaimableBalances from '@/app/hooks/rewards/useClaimableBalance'
+import useClaimableStakingRewards from '@/app/hooks/rewards/useClaimableStakingRewards'
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+
+import ClaimAndMigrateModal from '../ClaimAndMigrateModal'
+import ClaimStakingRewardsModal from '../ClaimStakingRewardsModal'
+import StakeModal from '../StakeModal'
+import StakedCardSectionButton from './StakedCardSectionButton'
+import StakedLyraBalanceText from './StakedLyraBalanceText'
+
+type Props = {
+ latestRewardEpochs: LatestRewardEpoch[]
+} & MarginProps
+
+const StakingRewardsText = withSuspense(
+ (): CardElement => {
+ const claimableBalances = useClaimableBalances()
+ const claimableStakingRewards = useClaimableStakingRewards()
+ let claimableBalance = ZERO_BN
+ if (claimableBalances.oldStkLyra.gt(ZERO_BN)) {
+ claimableBalance = claimableBalances.oldStkLyra
+ } else if (claimableStakingRewards) {
+ claimableBalance = claimableStakingRewards
+ }
+ return {formatNumber(claimableBalance)} stkLYRA
+ },
+ () =>
+)
+
+const StakedCardSection = ({ latestRewardEpochs, ...marginProps }: Props): CardElement => {
+ const [isUnstakeOpen, setIsUnstakeOpen] = useState(false)
+ const [isStakeOpen, setIsStakeOpen] = useState(false)
+ const [isL1ClaimOpen, setIsClaimL1Open] = useState(false)
+ const [isClaimAndMigrateOpen, setIsClaimAndMigrateOpen] = useState(false)
+ const isMobile = useIsMobile()
+
+ return (
+
+
+ Staked
+
+
+
+ } />
+
+
+ setIsStakeOpen(true)}
+ onUnstakeOpen={() => setIsUnstakeOpen(true)}
+ onClaimAndMigrateOpen={() => setIsClaimAndMigrateOpen(true)}
+ onClaimL1Open={() => setIsClaimL1Open(true)}
+ />
+ setIsUnstakeOpen(false)} />
+ setIsStakeOpen(false)}
+ />
+ setIsClaimAndMigrateOpen(false)} />
+ setIsClaimL1Open(false)} />
+
+ )
+}
+
+export default StakedCardSection
diff --git a/app/src/containers/rewards/StakingRewardsCard/StakedCardSectionButton.tsx b/app/src/containers/rewards/StakingRewardsCard/StakedCardSectionButton.tsx
new file mode 100644
index 00000000..0504284e
--- /dev/null
+++ b/app/src/containers/rewards/StakingRewardsCard/StakedCardSectionButton.tsx
@@ -0,0 +1,70 @@
+import Button from '@lyra/ui/components/Button'
+import Grid from '@lyra/ui/components/Grid'
+import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
+import React from 'react'
+
+import { ZERO_BN } from '@/app/constants/bn'
+import useAccountLyraBalances from '@/app/hooks/account/useAccountLyraBalances'
+import withSuspense from '@/app/hooks/data/withSuspense'
+import useClaimableBalances from '@/app/hooks/rewards/useClaimableBalance'
+import useClaimableStakingRewards from '@/app/hooks/rewards/useClaimableStakingRewards'
+
+type Props = {
+ onStakeOpen: () => void
+ onUnstakeOpen: () => void
+ onClaimL1Open: () => void
+ onClaimAndMigrateOpen: () => void
+}
+
+const StakedCardSectionButton = withSuspense(
+ ({ onStakeOpen, onUnstakeOpen, onClaimAndMigrateOpen, onClaimL1Open }: Props) => {
+ const claimableBalances = useClaimableBalances()
+ const claimableStakingRewards = useClaimableStakingRewards()
+ const lyraBalances = useAccountLyraBalances()
+ const optimismOldStkLyra = lyraBalances.optimismOldStkLyra
+ const hasClaimableOldStkLyra = claimableBalances.oldStkLyra.gt(0)
+ const hasOldStkLyra = optimismOldStkLyra.gt(0)
+ if (hasClaimableOldStkLyra || hasOldStkLyra) {
+ return (
+
+
+
+ )
+ }
+ return (
+
+ {claimableStakingRewards.gt(ZERO_BN) ? (
+
+ ) : null}
+
+
+
+ )
+ },
+ () => {
+ return (
+
+
+
+
+ )
+ }
+)
+
+export default StakedCardSectionButton
diff --git a/app/src/containers/rewards/StakingRewardsCard/StakedLyraBalanceText.tsx b/app/src/containers/rewards/StakingRewardsCard/StakedLyraBalanceText.tsx
new file mode 100644
index 00000000..92c8cd6e
--- /dev/null
+++ b/app/src/containers/rewards/StakingRewardsCard/StakedLyraBalanceText.tsx
@@ -0,0 +1,29 @@
+import { CardElement } from '@lyra/ui/components/Card'
+import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
+import Text from '@lyra/ui/components/Text'
+import formatBalance from '@lyra/ui/utils/formatBalance'
+import React from 'react'
+
+import useAccountLyraBalances from '@/app/hooks/account/useAccountLyraBalances'
+import withSuspense from '@/app/hooks/data/withSuspense'
+
+const StakedLyraBalanceText = withSuspense(
+ (): CardElement => {
+ const lyraBalances = useAccountLyraBalances()
+ const optimismOldStkLyra = lyraBalances.optimismOldStkLyra
+ const ethereumStkLyra = lyraBalances.ethereumStkLyra
+ const optimismStkLyra = lyraBalances.optimismStkLyra
+ const arbitrumStkLyra = lyraBalances.arbitrumStkLyra
+ const balance = optimismOldStkLyra.add(optimismStkLyra).add(ethereumStkLyra).add(arbitrumStkLyra)
+ return (
+
+ {formatBalance(balance, 'stkLYRA')}
+
+ )
+ },
+ () => {
+ return
+ }
+)
+
+export default StakedLyraBalanceText
diff --git a/app/src/containers/rewards/StakingRewardsCard/index.tsx b/app/src/containers/rewards/StakingRewardsCard/index.tsx
new file mode 100644
index 00000000..3f30f77e
--- /dev/null
+++ b/app/src/containers/rewards/StakingRewardsCard/index.tsx
@@ -0,0 +1,40 @@
+import Box from '@lyra/ui/components/Box'
+import Card, { CardElement } from '@lyra/ui/components/Card'
+import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
+import Flex from '@lyra/ui/components/Flex'
+import Text from '@lyra/ui/components/Text'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import { MarginProps } from '@lyra/ui/types'
+import React from 'react'
+
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+
+import NotStakedCardSection from './NotStakedCardSection'
+import StakedCardSection from './StakedCardSection'
+
+type Props = {
+ latestRewardEpochs: LatestRewardEpoch[]
+} & MarginProps
+
+const StakingRewardsCard = ({ latestRewardEpochs, ...marginProps }: Props): CardElement => {
+ const isMobile = useIsMobile()
+ return (
+
+
+
+ Staking
+
+
+ Earn rewards by staking Lyra and get boosted rewards for trading and liquidity mining.
+
+
+
+
+
+
+
+
+ )
+}
+
+export default StakingRewardsCard
diff --git a/app/src/containers/rewards/TradingRebateBoostModal/index.tsx b/app/src/containers/rewards/TradingRebateBoostModal/index.tsx
new file mode 100644
index 00000000..cc13875d
--- /dev/null
+++ b/app/src/containers/rewards/TradingRebateBoostModal/index.tsx
@@ -0,0 +1,83 @@
+import Modal from '@lyra/ui/components/Modal'
+import ModalBody from '@lyra/ui/components/Modal/ModalBody'
+import Text from '@lyra/ui/components/Text'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import formatPercentage from '@lyra/ui/utils/formatPercentage'
+import { AccountRewardEpoch, GlobalRewardEpoch } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useState } from 'react'
+import { useMemo } from 'react'
+
+import RowItem from '@/app/components/common/RowItem'
+import { TradingFeeRebateTable } from '@/app/components/rewards/TradingFeeRebateTable'
+import { ZERO_BN } from '@/app/constants/bn'
+
+import StakeFormAmountInput from '../StakeModal/StakeFormAmountInput'
+import StakeFormButton from '../StakeModal/StakeFormButton'
+
+type Props = {
+ isOpen: boolean
+ onClose: () => void
+ globalRewardEpoch: GlobalRewardEpoch
+ accountRewardEpoch?: AccountRewardEpoch | null
+}
+
+export default function TradingRebateBoostModal({ isOpen, onClose, globalRewardEpoch, accountRewardEpoch }: Props) {
+ const [amount, setAmount] = useState(ZERO_BN)
+ const effectiveRebate = accountRewardEpoch?.tradingFeeRebate ?? globalRewardEpoch.tradingFeeRebateTiers[0].feeRebate
+ const nextEffectiveRebateTierIdx = useMemo(
+ () =>
+ Math.min(
+ globalRewardEpoch.tradingFeeRebateTiers.findIndex(rebateTier => rebateTier.feeRebate === effectiveRebate) + 1,
+ globalRewardEpoch.tradingFeeRebateTiers.length - 1
+ ) ?? 1,
+ [globalRewardEpoch, effectiveRebate]
+ )
+ const isMaxFeeRebate = nextEffectiveRebateTierIdx === globalRewardEpoch.tradingFeeRebateTiers.length - 1
+ const nextEffectiveRebateTier = globalRewardEpoch.tradingFeeRebateTiers[nextEffectiveRebateTierIdx]
+ const stkLyraDiff = nextEffectiveRebateTier.stakedLyraCutoff - (accountRewardEpoch?.stakedLyraBalance ?? 0)
+ const lyraBalance = accountRewardEpoch?.lyraBalances.ethereumLyra ?? ZERO_BN
+ return (
+
+ Boost Your Fee Rebate
+
+ }
+ >
+
+
+ Your are currently receiving a {formatPercentage(effectiveRebate, true)} rebate on your trading fees.{' '}
+ {!isMaxFeeRebate
+ ? `Stake ${formatNumber(stkLyraDiff)} more LYRA to earn a ${formatPercentage(
+ nextEffectiveRebateTier.feeRebate,
+ true
+ )} rebate.`
+ : ''}
+
+ setAmount(newAmount)}
+ max={lyraBalance}
+ />
+ }
+ />
+
+
+
+
+
+ )
+}
diff --git a/app/src/containers/rewards/TradingRewardsSection/TradingRewardsCard.tsx b/app/src/containers/rewards/TradingRewardsSection/TradingRewardsCard.tsx
new file mode 100644
index 00000000..a2e3cb35
--- /dev/null
+++ b/app/src/containers/rewards/TradingRewardsSection/TradingRewardsCard.tsx
@@ -0,0 +1,110 @@
+import IconButton from '@lyra/ui/components/Button/IconButton'
+import Card from '@lyra/ui/components/Card'
+import CardBody from '@lyra/ui/components/Card/CardBody'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import { IconType } from '@lyra/ui/components/Icon'
+import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
+import Text from '@lyra/ui/components/Text'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import { MarginProps } from '@lyra/ui/types'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import formatTruncatedUSD from '@lyra/ui/utils/formatTruncatedUSD'
+import formatUSD from '@lyra/ui/utils/formatUSD'
+import { AccountRewardEpoch, GlobalRewardEpoch, Network } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+
+import NetworkImage from '@/app/components/common/NetworkImage'
+import { REWARDS_CARD_GRID_COLUMN_TEMPLATE } from '@/app/constants/layout'
+import { PageId } from '@/app/constants/pages'
+import withSuspense from '@/app/hooks/data/withSuspense'
+import useNetworkTradingVolume from '@/app/hooks/rewards/useNetworkTradingVolume'
+import getNetworkDisplayName from '@/app/utils/getNetworkDisplayName'
+import getPagePath from '@/app/utils/getPagePath'
+
+type Props = {
+ globalRewardEpoch: GlobalRewardEpoch
+ accountRewardEpoch?: AccountRewardEpoch | null
+} & MarginProps
+
+const TradingVolumeText = withSuspense(
+ ({ network }: { network: Network }) => {
+ const { totalNotionalVolume } = useNetworkTradingVolume(network)
+ return (
+
+ {formatTruncatedUSD(totalNotionalVolume)}
+
+ )
+ },
+ () =>
+)
+
+const TradingRewardsCard = ({ globalRewardEpoch, accountRewardEpoch, ...styleProps }: Props) => {
+ const navigate = useNavigate()
+ const isMobile = useIsMobile()
+ const tradingFees = accountRewardEpoch?.tradingFees ?? 0
+ const tradingRewards = accountRewardEpoch?.tradingRewards ?? globalRewardEpoch.tradingRewards(0, 0)
+ const displayRewardToken = tradingRewards.reduce(
+ (max, yieldToken) => (yieldToken.amount > max.amount ? yieldToken : max),
+ tradingRewards[0]
+ )
+ return (
+ navigate(getPagePath({ page: PageId.RewardsTrading, network: globalRewardEpoch.lyra.network }))}
+ {...styleProps}
+ >
+
+
+
+
+
+ Trading · {globalRewardEpoch ? getNetworkDisplayName(globalRewardEpoch?.lyra.network) : ''}
+
+
+ {!isMobile ? (
+ <>
+
+
+ Volume
+
+
+
+
+
+ Your Fees
+
+
+ {formatUSD(tradingFees, { maxDps: 2 })}
+
+
+
+
+ Your Rebate
+
+
+ {formatNumber(displayRewardToken.amount, { maxDps: 2 })} {displayRewardToken.symbol}
+
+
+ >
+ ) : null}
+
+
+
+
+
+
+ )
+}
+
+export default TradingRewardsCard
diff --git a/app/src/containers/rewards/TradingRewardsSection/index.tsx b/app/src/containers/rewards/TradingRewardsSection/index.tsx
new file mode 100644
index 00000000..896c57b1
--- /dev/null
+++ b/app/src/containers/rewards/TradingRewardsSection/index.tsx
@@ -0,0 +1,42 @@
+import Box from '@lyra/ui/components/Box'
+import { CardElement } from '@lyra/ui/components/Card'
+import Flex from '@lyra/ui/components/Flex'
+import Text from '@lyra/ui/components/Text'
+import { MarginProps } from '@lyra/ui/types'
+import React from 'react'
+
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+
+import TradingRewardsCard from './TradingRewardsCard'
+
+type Props = {
+ latestRewardEpochs: LatestRewardEpoch[]
+} & MarginProps
+
+const TradingRewardsSection = ({ latestRewardEpochs, ...marginProps }: Props): CardElement => {
+ const allEpochs = Object.values(latestRewardEpochs)
+ return (
+
+
+
+ Trading
+
+ Earn rewards through trading fee rebates.
+
+
+ {allEpochs.map((latestEpochs, idx) =>
+ latestEpochs ? (
+
+ ) : null
+ )}
+
+
+ )
+}
+
+export default TradingRewardsSection
diff --git a/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyBottomSection.tsx b/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyBottomSection.tsx
index b5fbd835..d320133c 100644
--- a/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyBottomSection.tsx
+++ b/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyBottomSection.tsx
@@ -7,85 +7,30 @@ import Text from '@lyra/ui/components/Text'
import Countdown from '@lyra/ui/components/Text/CountdownText'
import { MarginProps } from '@lyra/ui/types'
import { LayoutProps } from '@lyra/ui/types'
-import { Market } from '@lyrafinance/lyra-js'
-import React, { useEffect, useMemo } from 'react'
+import React, { useMemo } from 'react'
import { Flex } from 'rebass'
-import AmountUpdateText from '@/app/components/common/AmountUpdateText'
-import { ZERO_BN } from '@/app/constants/bn'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useLyraAccountStaking from '@/app/hooks/rewards/useLyraAccountStaking'
+import useLyraStakingAccount from '@/app/hooks/rewards/useLyraAccountStaking'
import useMarkets from '@/app/hooks/rewards/useMarkets'
-import useUnstake from '@/app/hooks/rewards/useUnstake'
-import { findLyraRewardEpochToken, findOpRewardEpochToken } from '@/app/utils/findRewardToken'
-import toBigNumber from '@/app/utils/toBigNumber'
-import TokenImage from '../../common/TokenImage'
import UnstakeCardBodyButton from './UnstakeCardBodyButton'
type UnstakeCardBodyBottomSectionProps = {
amount: BigNumber
- vault: Market | null
- setVault: (vault: Market) => void
onClose: () => void
} & LayoutProps &
MarginProps
const UnstakeCardBodyBottomSection = withSuspense(
- ({ vault, amount, setVault, onClose, ...styleProps }: UnstakeCardBodyBottomSectionProps) => {
+ ({ amount, onClose, ...styleProps }: UnstakeCardBodyBottomSectionProps) => {
const markets = useMarkets()
- useEffect(() => {
- if (!vault) {
- setVault(markets[0])
- }
- }, [markets, vault, setVault])
- const unstake = useUnstake(amount)
- const lyraAccountStaking = useLyraAccountStaking()
+ const lyraAccountStaking = useLyraStakingAccount()
const currentTimestamp = useMemo(() => markets[0].block.timestamp, [markets])
const unstakeWindowEndTimestamp = lyraAccountStaking?.unstakeWindowEndTimestamp ?? 0
- const { lyraStakingYieldPerDay, opStakingYieldPerDay, newLyraStakingYieldPerDay, newOpStakingYieldPerDay } =
- useMemo(() => {
- const lyraStakingYieldPerDay = findLyraRewardEpochToken(unstake?.stakingYieldPerDay ?? [])
- const opStakingYieldPerDay = findOpRewardEpochToken(unstake?.stakingYieldPerDay ?? [])
- const newLyraStakingYieldPerDay = findLyraRewardEpochToken(unstake?.newStakingYieldPerDay ?? [])
- const newOpStakingYieldPerDay = findOpRewardEpochToken(unstake?.newStakingYieldPerDay ?? [])
- return {
- lyraStakingYieldPerDay,
- opStakingYieldPerDay,
- newLyraStakingYieldPerDay,
- newOpStakingYieldPerDay,
- }
- }, [unstake])
return (
-
-
- Staking Yield / Day
-
-
-
-
-
-
-
-
-
-
-
-
{unstakeWindowEndTimestamp > currentTimestamp ? (
@@ -98,18 +43,9 @@ const UnstakeCardBodyBottomSection = withSuspense(
)
},
- ({ vault, amount, setVault, onClose, ...styleProps }: UnstakeCardBodyBottomSectionProps) => {
+ ({ amount, onClose, ...styleProps }: UnstakeCardBodyBottomSectionProps) => {
return (
-
-
- Staking Yield / Day
-
-
-
-
-
-
diff --git a/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyButton.tsx b/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyButton.tsx
index 8d61ce5c..d70c30ba 100644
--- a/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyButton.tsx
+++ b/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyButton.tsx
@@ -2,18 +2,20 @@ import { BigNumber } from '@ethersproject/bignumber'
import Box from '@lyra/ui/components/Box'
import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
import { MarginProps } from '@lyra/ui/types'
-import { Network, UnstakeDisabledReason } from '@lyrafinance/lyra-js'
-import React, { useCallback, useMemo } from 'react'
+import { Network } from '@lyrafinance/lyra-js'
+import React, { useCallback } from 'react'
import { ZERO_BN } from '@/app/constants/bn'
import { LogEvent } from '@/app/constants/logEvents'
import { TransactionType } from '@/app/constants/screen'
+import useAccountLyraBalances from '@/app/hooks/account/useAccountLyraBalances'
import useTransaction from '@/app/hooks/account/useTransaction'
+import useWalletAccount from '@/app/hooks/account/useWalletAccount'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useAccount from '@/app/hooks/market/useAccount'
-import useLyraAccountStaking, { useMutateAccountStaking } from '@/app/hooks/rewards/useLyraAccountStaking'
-import useUnstake from '@/app/hooks/rewards/useUnstake'
+import useLyraStakingAccount, { useMutateAccountStaking } from '@/app/hooks/rewards/useLyraAccountStaking'
+import { useMutateLyraStaking } from '@/app/hooks/rewards/useLyraStaking'
import logEvent from '@/app/utils/logEvent'
+import { lyraOptimism } from '@/app/utils/lyra'
import TransactionButton from '../../common/TransactionButton'
@@ -24,52 +26,49 @@ type Props = {
const UnstakeCardBodyButton = withSuspense(
({ amount, onClose, ...styleProps }: Props) => {
- const lyraAccountStaking = useLyraAccountStaking()
- const unstake = useUnstake(amount ?? ZERO_BN)
- const account = useAccount(Network.Optimism)
+ const lyraAccountStaking = useLyraStakingAccount()
+ const lyraBalances = useAccountLyraBalances()
+ const account = useWalletAccount()
const execute = useTransaction(Network.Optimism)
- const { insufficientBalance } = useMemo(() => {
- const insufficientBalance = unstake?.disabledReason === UnstakeDisabledReason.InsufficientBalance
- const notInUnstakeWindow = unstake?.disabledReason === UnstakeDisabledReason.NotInUnstakeWindow
- return {
- insufficientBalance,
- notInUnstakeWindow,
- }
- }, [unstake])
-
const mutateAccountStaking = useMutateAccountStaking()
+ const mutateLyraStaking = useMutateLyraStaking()
+
const handleClickRequestUnstake = useCallback(async () => {
- if (!account || !unstake) {
+ if (!account) {
console.warn('Account or unstake does not exist')
return
}
- logEvent(LogEvent.UnstakeLyraSubmit, { unstakeAmount: unstake.amount })
- const tx = await account.requestUnstake()
+ logEvent(LogEvent.UnstakeLyraSubmit, { unstakeAmount: amount })
+ const tx = await lyraOptimism.requestUnstake(account)
await execute(tx, {
onComplete: async () => {
- logEvent(LogEvent.UnstakeLyraSuccess, { unstakeAmount: unstake.amount })
+ logEvent(LogEvent.UnstakeLyraSuccess, { unstakeAmount: amount })
await Promise.all([mutateAccountStaking()])
},
onError: error => {
- logEvent(LogEvent.UnstakeLyraError, { unstakeAmount: unstake.amount, error: error?.message })
+ logEvent(LogEvent.UnstakeLyraError, { unstakeAmount: amount, error: error?.message })
},
})
- }, [account, execute, mutateAccountStaking, unstake])
+ }, [amount, account, execute, mutateAccountStaking])
const handleClickUnstake = useCallback(async () => {
if (!account) {
console.warn('Account does not exist')
return
}
- if (unstake?.tx) {
- await execute(unstake.tx, {
- onComplete: async () => await Promise.all([mutateAccountStaking()]),
+ const tx = await lyraOptimism.unstake(account, amount ?? ZERO_BN)
+ if (tx) {
+ await execute(tx, {
+ onComplete: async () => await Promise.all([mutateAccountStaking(), mutateLyraStaking()]),
})
}
onClose && onClose()
- }, [account, execute, unstake, onClose, mutateAccountStaking])
+ }, [account, amount, execute, onClose, mutateAccountStaking, mutateLyraStaking])
+
+ const insufficientBalance = amount ? lyraBalances.ethereumStkLyra.lte(amount) : false
const isCooldown = !!lyraAccountStaking?.isInCooldown
- const hasUnstakeableBalance = (lyraAccountStaking?.lyraBalances.ethereumStkLyra ?? 0) <= 0
+ const hasUnstakeableBalance = lyraBalances.ethereumStkLyra.lte(0)
+
return (
{!lyraAccountStaking?.isInUnstakeWindow ? (
diff --git a/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyRequestSection.tsx b/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyRequestSection.tsx
index ccbdde3b..00eaf4dc 100644
--- a/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyRequestSection.tsx
+++ b/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyRequestSection.tsx
@@ -6,13 +6,13 @@ import Text from '@lyra/ui/components/Text'
import Countdown from '@lyra/ui/components/Text/CountdownText'
import { MarginProps } from '@lyra/ui/types'
import { LayoutProps } from '@lyra/ui/types'
+import formatNumber from '@lyra/ui/utils/formatNumber'
import React from 'react'
-import { Flex } from 'rebass'
-import TokenAmountText from '@/app/components/common/TokenAmountText'
-import TokenAmountTextShimmer from '@/app/components/common/TokenAmountText/TokenAmountTextShimmer'
+import RowItem from '@/app/components/common/RowItem'
+import useAccountLyraBalances from '@/app/hooks/account/useAccountLyraBalances'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useLyraAccountStaking from '@/app/hooks/rewards/useLyraAccountStaking'
+import useLyraStakingAccount from '@/app/hooks/rewards/useLyraAccountStaking'
import UnstakeCardBodyButton from './UnstakeCardBodyButton'
@@ -20,44 +20,38 @@ type Props = LayoutProps & MarginProps
const UnstakeCardBodyRequestUnstakeSectionBalance = withSuspense(
() => {
- const lyraAccountStaking = useLyraAccountStaking()
- const amount = lyraAccountStaking?.lyraBalances.ethereumStkLyra.add(
- lyraAccountStaking?.lyraBalances.optimismStkLyra
- )
- return
+ const lyraBalances = useAccountLyraBalances()
+ return {formatNumber(lyraBalances.ethereumStkLyra)} stkLYRA
},
- () =>
+ () =>
)
const UnstakeCardBodyRequestUnstakeSectionCountdown = withSuspense(
() => {
- const lyraAccountStaking = useLyraAccountStaking()
+ const lyraAccountStaking = useLyraStakingAccount()
return
},
- () => {
- return
- }
+ () =>
)
const UnstakeCardBodyRequestUnstakeSection = ({ ...styleProps }: Props) => {
return (
-
- Staked LYRA has a 14 day unstaking period. During this period you will not receive any rewards. Boosts for vault
- and trading rewards will also be disabled.
+
+ Staked LYRA has a 14 day unstaking period. Boosts for vault and trading rewards will also be disabled.
-
-
- Unstakeable Balance
-
-
-
-
-
- Unstakeable In
-
-
-
+ }
+ />
+ }
+ />
{
- const lyraAccountStaking = useLyraAccountStaking()
- return (
-
-
-
- {formatNumber(lyraAccountStaking?.lyraBalances.ethereumStkLyra ?? 0)}
-
-
- )
+ const lyraBalances = useAccountLyraBalances()
+ return {formatNumber(lyraBalances.ethereumStkLyra)} stkLYRA
},
- () => {
- return
- }
+ () =>
)
const UnstakeCardBodyTopSectionInput = withSuspense(
({ amount, onChangeAmount, ...styleProps }: UnstakeCardBodyTopSectionProps) => {
- const lyraAccountStaking = useLyraAccountStaking()
- const maxStakeBalance = lyraAccountStaking?.lyraBalances.ethereumStkLyra ?? ZERO_BN
+ const lyraBalances = useAccountLyraBalances()
+ const maxStakeBalance = lyraBalances.ethereumStkLyra
return (
{
return (
-
-
+
+
Your staked LYRA can now be unstaked. If you do not unstake within 2 days, the staking period resets and you'll
have to wait 14 days.
-
-
- Unstakeable Balance
-
-
-
-
-
- Amount to Unstake
-
-
-
-
+ }
+ />
+ } />
+
)
}
diff --git a/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyUnstakeSection.tsx b/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyUnstakeSection.tsx
index 7c3b57d9..4956bcf3 100644
--- a/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyUnstakeSection.tsx
+++ b/app/src/containers/rewards/UnstakeCardBody/UnstakeCardBodyUnstakeSection.tsx
@@ -1,6 +1,4 @@
import { BigNumber } from '@ethersproject/bignumber'
-import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
-import { Market } from '@lyrafinance/lyra-js'
import React, { useState } from 'react'
import { ZERO_BN } from '@/app/constants/bn'
@@ -13,13 +11,11 @@ type Props = {
}
const UnstakeCardBodyUnstakeSection = ({ onClose }: Props) => {
- const [vault, setVault] = useState(null)
const [amount, setAmount] = useState(ZERO_BN)
return (
<>
-
-
+
>
)
}
diff --git a/app/src/containers/rewards/UnstakeCardBody/index.tsx b/app/src/containers/rewards/UnstakeCardBody/index.tsx
index 3e0e55b1..7d239057 100644
--- a/app/src/containers/rewards/UnstakeCardBody/index.tsx
+++ b/app/src/containers/rewards/UnstakeCardBody/index.tsx
@@ -4,7 +4,7 @@ import Spinner from '@lyra/ui/components/Spinner'
import React from 'react'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useLyraAccountStaking from '@/app/hooks/rewards/useLyraAccountStaking'
+import useLyraStakingAccount from '@/app/hooks/rewards/useLyraAccountStaking'
import UnstakeCardBodyRequestUnstakeSection from './UnstakeCardBodyRequestSection'
import UnstakeCardBodyUnstakeSection from './UnstakeCardBodyUnstakeSection'
@@ -15,7 +15,7 @@ type Props = {
const UnstakeCardBody = withSuspense(
({ onClose }: Props) => {
- const lyraAccountStaking = useLyraAccountStaking()
+ const lyraAccountStaking = useLyraStakingAccount()
return (
<>
{lyraAccountStaking?.isInUnstakeWindow ? (
@@ -28,7 +28,7 @@ const UnstakeCardBody = withSuspense(
},
() => {
return (
-
+
diff --git a/app/src/containers/rewards/VaultsRewardsSection/VaultRewardsMarketCard.tsx b/app/src/containers/rewards/VaultsRewardsSection/VaultRewardsMarketCard.tsx
new file mode 100644
index 00000000..9ca960b6
--- /dev/null
+++ b/app/src/containers/rewards/VaultsRewardsSection/VaultRewardsMarketCard.tsx
@@ -0,0 +1,170 @@
+import IconButton from '@lyra/ui/components/Button/IconButton'
+import Card from '@lyra/ui/components/Card'
+import CardBody from '@lyra/ui/components/Card/CardBody'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import { IconType } from '@lyra/ui/components/Icon'
+import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
+import Text from '@lyra/ui/components/Text'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import formatTruncatedUSD from '@lyra/ui/utils/formatTruncatedUSD'
+import formatUSD from '@lyra/ui/utils/formatUSD'
+import { AccountRewardEpoch, GlobalRewardEpoch, Market } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useMemo } from 'react'
+import { useNavigate } from 'react-router-dom'
+
+import MarketImage from '@/app/components/common/MarketImage'
+import { ONE_BN, UNIT, ZERO_BN } from '@/app/constants/bn'
+import { ChartInterval } from '@/app/constants/chart'
+import { REWARDS_CARD_GRID_COLUMN_TEMPLATE } from '@/app/constants/layout'
+import { PageId } from '@/app/constants/pages'
+import useAccountBalances from '@/app/hooks/account/useAccountBalances'
+import withSuspense from '@/app/hooks/data/withSuspense'
+import useVaultStats from '@/app/hooks/vaults/useVaultStats'
+import formatAPY from '@/app/utils/formatAPY'
+import formatAPYRange from '@/app/utils/formatAPYRange'
+import formatTokenName from '@/app/utils/formatTokenName'
+import getChartIntervalSeconds from '@/app/utils/getChartIntervalSeconds'
+import getNetworkDisplayName from '@/app/utils/getNetworkDisplayName'
+import getPagePath from '@/app/utils/getPagePath'
+
+type Props = {
+ accountRewardEpoch?: AccountRewardEpoch | null
+ globalRewardEpoch: GlobalRewardEpoch
+ market: Market
+}
+
+const VaultYourLiquidityText = withSuspense(
+ ({ market }: { market: Market }) => {
+ const balances = useAccountBalances(market)
+ const vault = useVaultStats(market, getChartIntervalSeconds(ChartInterval.OneDay))
+ const liquidityValue = useMemo(
+ () => balances.liquidityToken.balance.mul(vault?.liquidity.tokenPrice ?? ONE_BN).div(UNIT),
+ [balances, vault]
+ )
+ return (
+
+ {formatUSD(liquidityValue, { minDps: 0 })}
+
+ )
+ },
+ () =>
+)
+
+const VaultAPYText = withSuspense(
+ ({ globalRewardEpoch, accountRewardEpoch, market }: Props) => {
+ const balances = useAccountBalances(market)
+ const minApy = globalRewardEpoch.minVaultApy(market.address)
+ const userApy = accountRewardEpoch?.vaultApy(market.address) ?? minApy
+ return balances.liquidityToken.balance.isZero() ? (
+
+ {formatAPYRange(minApy, globalRewardEpoch.maxVaultApy(market.address), { showSymbol: false })}
+
+ ) : (
+
+ {formatAPY(userApy, { showSymbol: false })}
+
+ )
+ },
+ () =>
+)
+
+const VaultTVLText = withSuspense(
+ ({ market }: { market: Market }) => {
+ const vault = useVaultStats(market, getChartIntervalSeconds(ChartInterval.OneDay))
+ return (
+
+ {formatTruncatedUSD(vault?.tvl ?? ZERO_BN)}
+
+ )
+ },
+ () =>
+)
+
+const VaultRewardsMarketCard = ({ accountRewardEpoch, globalRewardEpoch, market }: Props) => {
+ const navigate = useNavigate()
+ const isMobile = useIsMobile()
+ return (
+
+ navigate(
+ getPagePath({
+ page: PageId.RewardsVaults,
+ network: market.lyra.network,
+ marketAddressOrName: market.name,
+ })
+ )
+ }
+ sx={{
+ ':hover': {
+ bg: 'cardNestedHover',
+ cursor: 'pointer',
+ },
+ ':active': {
+ bg: 'active',
+ cursor: 'pointer',
+ },
+ }}
+ >
+
+
+
+
+
+ {formatTokenName(market.baseToken)} · {getNetworkDisplayName(market.lyra.network)}
+
+
+ {!isMobile ? (
+ <>
+
+
+ TVL
+
+
+
+
+
+ Your Liquidity
+
+
+
+
+
+ APY
+
+
+
+ >
+ ) : null}
+
+
+
+
+
+
+
+ )
+}
+
+export default VaultRewardsMarketCard
diff --git a/app/src/containers/rewards/VaultsRewardsSection/index.tsx b/app/src/containers/rewards/VaultsRewardsSection/index.tsx
new file mode 100644
index 00000000..f714d387
--- /dev/null
+++ b/app/src/containers/rewards/VaultsRewardsSection/index.tsx
@@ -0,0 +1,56 @@
+import Box from '@lyra/ui/components/Box'
+import { CardElement } from '@lyra/ui/components/Card'
+import Flex from '@lyra/ui/components/Flex'
+import Text from '@lyra/ui/components/Text'
+import { MarginProps } from '@lyra/ui/types'
+import React from 'react'
+import { useMemo } from 'react'
+
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+
+import VaultRewardsMarketCard from './VaultRewardsMarketCard'
+
+type Props = {
+ latestRewardEpochs: LatestRewardEpoch[]
+} & MarginProps
+
+const VaultsRewardsSection = ({ latestRewardEpochs: latestRewardEpochs, ...marginProps }: Props): CardElement => {
+ const vaultsRewardsCards: JSX.Element[] = useMemo(
+ () =>
+ latestRewardEpochs.reduce(
+ (marketCards, latestRewardEpoch) =>
+ latestRewardEpoch
+ ? [
+ ...marketCards,
+ ...latestRewardEpoch.global.markets
+ .filter(market => market.baseToken.symbol !== 'sSOL')
+ .map(market => (
+
+ )),
+ ]
+ : marketCards,
+ [] as JSX.Element[]
+ ),
+ [latestRewardEpochs]
+ )
+ return (
+
+
+
+ Vaults
+
+ Earn rewards by providing liquidity to a variety of pools.
+
+
+ {vaultsRewardsCards}
+
+
+ )
+}
+
+export default VaultsRewardsSection
diff --git a/app/src/containers/rewards/WethLyraL2UnstakeModal/WethLyraL2UnstakeModalContent.tsx b/app/src/containers/rewards/WethLyraL2UnstakeModal/WethLyraL2UnstakeModalContent.tsx
index 69b5e670..7bba8e07 100644
--- a/app/src/containers/rewards/WethLyraL2UnstakeModal/WethLyraL2UnstakeModalContent.tsx
+++ b/app/src/containers/rewards/WethLyraL2UnstakeModal/WethLyraL2UnstakeModalContent.tsx
@@ -6,7 +6,7 @@ import ButtonShimmer from '@lyra/ui/components/Shimmer/ButtonShimmer'
import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
import Text from '@lyra/ui/components/Text'
import formatNumber from '@lyra/ui/utils/formatNumber'
-import { Network } from '@lyrafinance/lyra-js'
+import { Network, WethLyraStaking } from '@lyrafinance/lyra-js'
import React, { useCallback } from 'react'
import { Flex } from 'rebass'
@@ -38,7 +38,7 @@ const WethLyraL2UnstakeModalContent = withSuspense(
const handleClickUnstake = useCallback(async () => {
if (account) {
- const tx = await lyraOptimism.account(account).unstakeWethLyraL2(amount)
+ const tx = await WethLyraStaking.unstakeL2(lyraOptimism, account, amount)
execute(tx, {
onComplete: () => {
mutateAccountWethLyraStakingL2()
diff --git a/app/src/containers/rewards/WethLyraLPRewardsSection/WethLyraLPRewardsCard.tsx b/app/src/containers/rewards/WethLyraLPRewardsSection/WethLyraLPRewardsCard.tsx
new file mode 100644
index 00000000..30ef66b6
--- /dev/null
+++ b/app/src/containers/rewards/WethLyraLPRewardsSection/WethLyraLPRewardsCard.tsx
@@ -0,0 +1,96 @@
+import IconButton from '@lyra/ui/components/Button/IconButton'
+import Card from '@lyra/ui/components/Card'
+import CardBody from '@lyra/ui/components/Card/CardBody'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import { IconType } from '@lyra/ui/components/Icon'
+import TextShimmer from '@lyra/ui/components/Shimmer/TextShimmer'
+import Text from '@lyra/ui/components/Text'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import formatPercentage from '@lyra/ui/utils/formatPercentage'
+import formatTruncatedUSD from '@lyra/ui/utils/formatTruncatedUSD'
+import { WethLyraStaking } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+
+import TokenImageStack from '@/app/components/common/TokenImageStack'
+import { ZERO_BN } from '@/app/constants/bn'
+import { PageId } from '@/app/constants/pages'
+import withSuspense from '@/app/hooks/data/withSuspense'
+import useAccountWethLyraStaking from '@/app/hooks/rewards/useAccountWethLyraStaking'
+import useWethLyraStaking from '@/app/hooks/rewards/useWethLyraStaking'
+import fromBigNumber from '@/app/utils/fromBigNumber'
+import getPagePath from '@/app/utils/getPagePath'
+
+type Props = {
+ wethLyraStaking: WethLyraStaking | null
+}
+
+const WethLyraYourLiquidityText = withSuspense(
+ () => {
+ const accountWethLyraStaking = useAccountWethLyraStaking()
+ const wethLyraStaking = useWethLyraStaking()
+ const liquidityValue =
+ fromBigNumber(accountWethLyraStaking?.stakedLPTokenBalance ?? ZERO_BN) * (wethLyraStaking?.lpTokenValue ?? 0)
+ return (
+
+ {formatTruncatedUSD(liquidityValue)}
+
+ )
+ },
+ () =>
+)
+
+export default function WethLyraLPRewardsCard({ wethLyraStaking }: Props) {
+ const navigate = useNavigate()
+ const isMobile = useIsMobile()
+ return (
+ navigate(getPagePath({ page: PageId.RewardsEthLyraLp }))}>
+
+
+
+
+
+ ETH-LYRA LP · Ethereum
+
+
+ {!isMobile ? (
+ <>
+
+
+ TVL
+
+
+ {formatTruncatedUSD(wethLyraStaking?.stakedTVL ?? ZERO_BN)}
+
+
+
+
+ Your Liquidity
+
+
+
+
+
+ APY
+
+
+ {formatPercentage(wethLyraStaking?.apy ?? 0, true)}
+
+
+ >
+ ) : null}
+
+
+
+
+
+
+ )
+}
diff --git a/app/src/containers/rewards/WethLyraLPRewardsSection/index.tsx b/app/src/containers/rewards/WethLyraLPRewardsSection/index.tsx
new file mode 100644
index 00000000..9ed0a571
--- /dev/null
+++ b/app/src/containers/rewards/WethLyraLPRewardsSection/index.tsx
@@ -0,0 +1,31 @@
+import Box from '@lyra/ui/components/Box'
+import { CardElement } from '@lyra/ui/components/Card'
+import Flex from '@lyra/ui/components/Flex'
+import Text from '@lyra/ui/components/Text'
+import { MarginProps } from '@lyra/ui/types'
+import { WethLyraStaking } from '@lyrafinance/lyra-js'
+import React from 'react'
+
+import WethLyraLPRewardsCard from './WethLyraLPRewardsCard'
+
+type Props = {
+ wethLyraStaking: WethLyraStaking | null
+} & MarginProps
+
+const WethLyraLPRewardsSection = ({ wethLyraStaking, ...marginProps }: Props): CardElement => {
+ return (
+
+
+
+ ETH-LYRA LP
+
+ Earn rewards on the Uniswap v3 pool via Arrakis Finance and Camelot DEX.
+
+
+
+
+
+ )
+}
+
+export default WethLyraLPRewardsSection
diff --git a/app/src/containers/rewards/WethLyraStakeModal/WethLyraStakeModalContent.tsx b/app/src/containers/rewards/WethLyraStakeModal/WethLyraStakeModalContent.tsx
index dc36a78b..75f43a73 100644
--- a/app/src/containers/rewards/WethLyraStakeModal/WethLyraStakeModalContent.tsx
+++ b/app/src/containers/rewards/WethLyraStakeModal/WethLyraStakeModalContent.tsx
@@ -38,7 +38,7 @@ const WethLyraStakeModalContent = withSuspense(
const isDisabled = amount.lte(0) || accountWethLyraStaking?.unstakedLPTokenBalance.eq(0)
const handleClickStake = useCallback(async () => {
if (account) {
- const tx = await lyraOptimism.account(account).stakeWethLyra(amount)
+ const tx = await lyraOptimism.stakeWethLyra(account, amount)
await execute(tx, {
onComplete: async () => {
await mutateAccountWethLyraStaking()
@@ -50,7 +50,7 @@ const WethLyraStakeModalContent = withSuspense(
const handleClickApprove = useCallback(async () => {
if (account) {
- const tx = await lyraOptimism.account(account).approveWethLyraTokens()
+ const tx = await lyraOptimism.approveWethLyraStaking(account)
execute(tx, {
onComplete: async () => {
await mutateAccountWethLyraStaking()
diff --git a/app/src/containers/rewards/WethLyraUnstakeModal/WethLyraUnstakeModalContent.tsx b/app/src/containers/rewards/WethLyraUnstakeModal/WethLyraUnstakeModalContent.tsx
index 6e59e67e..18d69f25 100644
--- a/app/src/containers/rewards/WethLyraUnstakeModal/WethLyraUnstakeModalContent.tsx
+++ b/app/src/containers/rewards/WethLyraUnstakeModal/WethLyraUnstakeModalContent.tsx
@@ -37,7 +37,7 @@ const WethLyraUnstakeModalContent = withSuspense(
const handleClickUnstake = useCallback(async () => {
if (account) {
- const tx = await lyraOptimism.account(account).unstakeWethLyra(amount)
+ const tx = await lyraOptimism.unstakeWethLyra(account, amount)
execute(tx, {
onComplete: () => {
mutateAccountWethLyraStaking()
diff --git a/app/src/containers/trade/TradeCollateralFormModal/index.tsx b/app/src/containers/trade/TradeCollateralFormModal/index.tsx
new file mode 100644
index 00000000..2b058565
--- /dev/null
+++ b/app/src/containers/trade/TradeCollateralFormModal/index.tsx
@@ -0,0 +1,161 @@
+import { BigNumber } from '@ethersproject/bignumber'
+import Button from '@lyra/ui/components/Button'
+import CardSection from '@lyra/ui/components/Card/CardSection'
+import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
+import Center from '@lyra/ui/components/Center'
+import Modal from '@lyra/ui/components/Modal'
+import Spinner from '@lyra/ui/components/Spinner'
+import Text from '@lyra/ui/components/Text'
+import formatBalance from '@lyra/ui/utils/formatBalance'
+import { Position } from '@lyrafinance/lyra-js'
+import { Market, Option } from '@lyrafinance/lyra-js'
+import React, { useCallback, useState } from 'react'
+
+import AmountUpdateText from '@/app/components/common/AmountUpdateText'
+import RowItem from '@/app/components/common/RowItem'
+import { ZERO_BN } from '@/app/constants/bn'
+import { MIN_TRADE_CARD_HEIGHT } from '@/app/constants/layout'
+import useAccountBalances from '@/app/hooks/account/useAccountBalances'
+import withSuspense from '@/app/hooks/data/withSuspense'
+import useTradeSync from '@/app/hooks/market/useTradeSync'
+import fromBigNumber from '@/app/utils/fromBigNumber'
+import getSoftMaxCollateral from '@/app/utils/getSoftMaxCollateral'
+import getSoftMinCollateral from '@/app/utils/getSoftMinCollateral'
+
+import TradeFormButton from '../TradeForm/TradeFormButton'
+import TradeFormCollateralSection from '../TradeForm/TradeFormCollateralSection'
+
+// TODO: @dappbeast make slippage configurable
+const SLIPPAGE = 0.5 / 100 // 0.5%
+
+type Props = {
+ isOpen: boolean
+ onClose: () => void
+ onTrade?: (market: Market, positionId: number) => void
+ option: Option
+ position: Position
+}
+
+const TradeCollateralFormModal = withSuspense(({ isOpen, onClose, onTrade, option, position }: Props) => {
+ const market = option.market()
+ // TODO: @dappbeast parallelize requests
+ const balances = useAccountBalances(market)
+ const quoteBalance = balances.quoteAsset
+ const baseBalance = balances.baseAsset
+ const isBaseCollateral = position.collateral?.isBase
+ const defaultCollateralAmount = position.collateral?.amount ?? ZERO_BN
+ const [collateralAmount, setCollateralAmount] = useState(defaultCollateralAmount)
+
+ // Reset to default collateral
+ const resetCollateralAmount = useCallback(() => {
+ setCollateralAmount(defaultCollateralAmount) // Triggers default fallback
+ }, [defaultCollateralAmount])
+
+ const handleTrade = useCallback(
+ (market: Market, positionId: number) => {
+ resetCollateralAmount()
+ if (onTrade) {
+ onTrade(market, positionId)
+ }
+ },
+ [onTrade, resetCollateralAmount]
+ )
+
+ const trade = useTradeSync({
+ option,
+ position,
+ balances,
+ isBuy: true,
+ size: ZERO_BN,
+ setToCollateral: collateralAmount,
+ isBaseCollateral,
+ slippage: SLIPPAGE,
+ })
+
+ if (!trade.collateral) {
+ return null
+ }
+
+ const max = getSoftMaxCollateral(trade, trade.collateral)
+ const min = getSoftMinCollateral(trade.collateral)
+ const isRange = !min.gte(max)
+
+ return (
+
+ {isRange ? (
+ <>
+
+
+
+
+ }
+ valueColor="text"
+ textVariant="secondary"
+ />
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+
+
+ This position is too small and has fixed collateral requirements that can't be adjusted.
+
+ onClose()} />
+
+ >
+ )}
+
+ )
+})
+;() => (
+
+
+
+
+
+)
+
+export default TradeCollateralFormModal
diff --git a/app/src/containers/trade/TradeForm/TradeFormButton.tsx b/app/src/containers/trade/TradeForm/TradeFormButton.tsx
index 4875cc7a..6b9fa094 100644
--- a/app/src/containers/trade/TradeForm/TradeFormButton.tsx
+++ b/app/src/containers/trade/TradeForm/TradeFormButton.tsx
@@ -8,8 +8,8 @@ import { MAX_BN } from '@/app/constants/bn'
import { ITERATIONS } from '@/app/constants/contracts'
import { LogEvent } from '@/app/constants/logEvents'
import { TransactionType } from '@/app/constants/screen'
+import useAccount from '@/app/hooks/account/useAccount'
import useTransaction from '@/app/hooks/account/useTransaction'
-import useAccount from '@/app/hooks/market/useAccount'
import useMutateTrade from '@/app/hooks/mutations/useMutateTrade'
import useMutateTradeApprove from '@/app/hooks/mutations/useMutateTradeApprove'
import getLyraSDK from '@/app/utils/getLyraSDK'
diff --git a/app/src/containers/trade/TradeForm/TradeFormCollateralSection.tsx b/app/src/containers/trade/TradeForm/TradeFormCollateralSection.tsx
index 0aa84765..233d34ba 100644
--- a/app/src/containers/trade/TradeForm/TradeFormCollateralSection.tsx
+++ b/app/src/containers/trade/TradeForm/TradeFormCollateralSection.tsx
@@ -33,7 +33,7 @@ type Props = {
collateral: TradeCollateral
collateralAmount: BigNumber
onChangeCollateralAmount: (size: BigNumber) => void
- onToggleCoveredCall: (isCoveredCall: boolean) => void
+ onToggleCoveredCall?: (isCoveredCall: boolean) => void
} & MarginProps
const NUM_STEPS = 200
@@ -85,7 +85,9 @@ const TradeFormCollateralSection = ({
const onOpen = useCallback(() => setIsOpen(true), [])
const onSelectCollateral = useCallback(
(isCoveredCall: boolean) => {
- onToggleCoveredCall(isCoveredCall)
+ if (onToggleCoveredCall) {
+ onToggleCoveredCall(isCoveredCall)
+ }
onClose()
},
[onClose, onToggleCoveredCall]
diff --git a/app/src/containers/trade/TradeForm/index.tsx b/app/src/containers/trade/TradeForm/index.tsx
index 328b2985..cbb94eb9 100644
--- a/app/src/containers/trade/TradeForm/index.tsx
+++ b/app/src/containers/trade/TradeForm/index.tsx
@@ -14,8 +14,8 @@ import RowItem from '@/app/components/common/RowItem'
import { ZERO_BN } from '@/app/constants/bn'
import { MIN_TRADE_CARD_HEIGHT } from '@/app/constants/layout'
import TradeFormSizeInput from '@/app/containers/trade/TradeForm/TradeFormSizeInput'
+import useAccountBalances from '@/app/hooks/account/useAccountBalances'
import withSuspense from '@/app/hooks/data/withSuspense'
-import useTradeBalances from '@/app/hooks/market/useTradeBalances'
import useTradeSync from '@/app/hooks/market/useTradeSync'
import formatTokenName from '@/app/utils/formatTokenName'
import fromBigNumber from '@/app/utils/fromBigNumber'
@@ -41,7 +41,7 @@ const TradeForm = withSuspense(
const market = option.market()
// TODO: @dappbeast parallelize requests
- const balances = useTradeBalances(market)
+ const balances = useAccountBalances(market)
const isLong = position ? position.isLong : isBuy
diff --git a/app/src/containers/vaults/VaultsBoostFormModal/index.tsx b/app/src/containers/vaults/VaultsBoostFormModal/index.tsx
index 572bd965..305dbdd3 100644
--- a/app/src/containers/vaults/VaultsBoostFormModal/index.tsx
+++ b/app/src/containers/vaults/VaultsBoostFormModal/index.tsx
@@ -9,12 +9,12 @@ import React, { useState } from 'react'
import AmountUpdateText from '@/app/components/common/AmountUpdateText'
import { ZERO_BN } from '@/app/constants/bn'
import { Vault } from '@/app/constants/vault'
-import StakeFormButton from '@/app/containers/rewards/StakeCardBody/StakeCardBodyButton'
import formatAPY from '@/app/utils/formatAPY'
import formatAPYRange from '@/app/utils/formatAPYRange'
import fromBigNumber from '@/app/utils/fromBigNumber'
import RowItem from '../../../components/common/RowItem'
+import StakeFormButton from '../../rewards/StakeModal/StakeFormButton'
type Props = {
isOpen: boolean
diff --git a/app/src/containers/vaults/VaultsDepositForm/VaultsDepositFormButton.tsx b/app/src/containers/vaults/VaultsDepositForm/VaultsDepositFormButton.tsx
index 715f88c6..1902b4a5 100644
--- a/app/src/containers/vaults/VaultsDepositForm/VaultsDepositFormButton.tsx
+++ b/app/src/containers/vaults/VaultsDepositForm/VaultsDepositFormButton.tsx
@@ -2,12 +2,11 @@ import { BigNumber } from '@ethersproject/bignumber'
import { MarginProps } from '@lyra/ui/types'
import React, { useCallback } from 'react'
-import { MAX_BN } from '@/app/constants/bn'
import { LogEvent } from '@/app/constants/logEvents'
import { TransactionType } from '@/app/constants/screen'
import { Vault } from '@/app/constants/vault'
import useTransaction from '@/app/hooks/account/useTransaction'
-import useAccount from '@/app/hooks/market/useAccount'
+import useWalletAccount from '@/app/hooks/account/useWalletAccount'
import useMutateVaultDepositAndWithdraw from '@/app/hooks/mutations/useMutateVaultDepositAndWithdraw'
import logEvent from '@/app/utils/logEvent'
@@ -22,7 +21,7 @@ type Props = {
const VaultsDepositFormButton = ({ vault, amount, onDeposit, ...styleProps }: Props) => {
const { market, marketBalances } = vault
const quoteAsset = marketBalances.quoteAsset
- const account = useAccount(market.lyra.network)
+ const account = useWalletAccount()
const mutateDeposit = useMutateVaultDepositAndWithdraw()
@@ -32,7 +31,7 @@ const VaultsDepositFormButton = ({ vault, amount, onDeposit, ...styleProps }: Pr
console.warn('Account does not exist')
return
}
- const tx = await account.approveDeposit(market.address, MAX_BN)
+ const tx = await market.approveDeposit(account)
await execute(tx, {
onComplete: async () => {
logEvent(LogEvent.VaultDepositApproveSuccess)
@@ -40,14 +39,14 @@ const VaultsDepositFormButton = ({ vault, amount, onDeposit, ...styleProps }: Pr
},
onError: error => logEvent(LogEvent.VaultDepositApproveError, { error: error?.message }),
})
- }, [account, market.address, execute, mutateDeposit])
+ }, [account, market, execute, mutateDeposit])
const handleClickDeposit = useCallback(async () => {
if (!account || !market) {
console.warn('Account does not exist')
return
}
- await execute(market.deposit(account.address, amount), {
+ await execute(market.deposit(account, amount), {
onComplete: async () => {
logEvent(LogEvent.VaultDepositSuccess)
await mutateDeposit()
diff --git a/app/src/containers/vaults/VaultsMyLiquidityCard/index.tsx b/app/src/containers/vaults/VaultsMyLiquidityCard/index.tsx
index cee65eb9..85a23dfe 100644
--- a/app/src/containers/vaults/VaultsMyLiquidityCard/index.tsx
+++ b/app/src/containers/vaults/VaultsMyLiquidityCard/index.tsx
@@ -44,8 +44,9 @@ const VaultsMyLiquidityCard = ({ vault }: Props) => {
const { market, globalRewardEpoch, apyMultiplier } = vault
- const isNew = globalRewardEpoch && globalRewardEpoch.id <= 1
- const isStartEarningInFuture = market.block.timestamp < (globalRewardEpoch?.startEarningTimestamp ?? 0)
+ const isNew = globalRewardEpoch?.startTimestamp === 1676419200 && market.baseToken.symbol == 'WBTC'
+ const isStartEarningInFuture =
+ market.block.timestamp < (globalRewardEpoch?.startEarningTimestamp ?? 0) && market.baseToken.symbol === 'WBTC'
return (
diff --git a/app/src/containers/vaults/VaultsWithdrawForm/VaultsWithdrawFormButton.tsx b/app/src/containers/vaults/VaultsWithdrawForm/VaultsWithdrawFormButton.tsx
index c91d5304..c9ed7577 100644
--- a/app/src/containers/vaults/VaultsWithdrawForm/VaultsWithdrawFormButton.tsx
+++ b/app/src/containers/vaults/VaultsWithdrawForm/VaultsWithdrawFormButton.tsx
@@ -5,8 +5,8 @@ import React, { useCallback } from 'react'
import { LogEvent } from '@/app/constants/logEvents'
import { TransactionType } from '@/app/constants/screen'
import { Vault } from '@/app/constants/vault'
+import useAccount from '@/app/hooks/account/useAccount'
import useTransaction from '@/app/hooks/account/useTransaction'
-import useAccount from '@/app/hooks/market/useAccount'
import useMutateVaultDepositAndWithdraw from '@/app/hooks/mutations/useMutateVaultDepositAndWithdraw'
import logEvent from '@/app/utils/logEvent'
diff --git a/app/src/hooks/market/useAccount.ts b/app/src/hooks/account/useAccount.ts
similarity index 82%
rename from app/src/hooks/market/useAccount.ts
rename to app/src/hooks/account/useAccount.ts
index 9aae1ceb..84a09fb9 100644
--- a/app/src/hooks/market/useAccount.ts
+++ b/app/src/hooks/account/useAccount.ts
@@ -2,7 +2,7 @@ import { Account, Network } from '@lyrafinance/lyra-js'
import getLyraSDK from '@/app/utils/getLyraSDK'
-import useWalletAccount from '../account/useWalletAccount'
+import useWalletAccount from './useWalletAccount'
export default function useAccount(network: Network): Account | null {
const address = useWalletAccount()
diff --git a/app/src/hooks/market/useTradeBalances.ts b/app/src/hooks/account/useAccountBalances.ts
similarity index 73%
rename from app/src/hooks/market/useTradeBalances.ts
rename to app/src/hooks/account/useAccountBalances.ts
index 60cdfde4..683ae7e0 100644
--- a/app/src/hooks/market/useTradeBalances.ts
+++ b/app/src/hooks/account/useAccountBalances.ts
@@ -9,8 +9,14 @@ import getLyraSDK from '@/app/utils/getLyraSDK'
import useWalletAccount from '../account/useWalletAccount'
import useFetch, { useMutate } from '../data/useFetch'
-const fetcher = async (network: Network, marketAddress: string, account: string): Promise => {
- return await getLyraSDK(network).account(account).marketBalances(marketAddress)
+export const fetchMarketBalances = async (
+ network: Network,
+ marketAddress: string,
+ account: string
+): Promise => {
+ const balances = await getLyraSDK(network).account(account).balances()
+ const marketBalance = balances.find(balance => balance.marketAddress === marketAddress)
+ return marketBalance ?? null
}
const getEmpty = (owner: string, market: Market): AccountBalances => ({
@@ -35,19 +41,19 @@ const getEmpty = (owner: string, market: Market): AccountBalances => ({
},
})
-export default function useTradeBalances(market: Market): AccountBalances {
+export default function useAccountBalances(market: Market): AccountBalances {
const account = useWalletAccount()
const [balances] = useFetch(
- FetchId.TradeBalances,
+ FetchId.AccountBalances,
account ? [market.lyra.network, market.address, account] : null,
- fetcher,
+ fetchMarketBalances,
{ refreshInterval: 10 * 1000 } // 10 seconds
)
return useMemo(() => balances ?? getEmpty(ZERO_ADDRESS, market), [balances, market])
}
export const useMutateTradeBalances = (market: Market): (() => Promise) => {
- const mutate = useMutate(FetchId.TradeBalances, fetcher)
+ const mutate = useMutate(FetchId.AccountBalances, fetchMarketBalances)
const account = useWalletAccount()
return useCallback(async () => {
if (account) {
diff --git a/app/src/hooks/account/useAccountLyraBalances.ts b/app/src/hooks/account/useAccountLyraBalances.ts
new file mode 100644
index 00000000..e2937759
--- /dev/null
+++ b/app/src/hooks/account/useAccountLyraBalances.ts
@@ -0,0 +1,50 @@
+import { AccountLyraBalances, Network } from '@lyrafinance/lyra-js'
+import { useCallback } from 'react'
+
+import { ZERO_BN } from '@/app/constants/bn'
+import { FetchId } from '@/app/constants/fetch'
+import getLyraSDK from '@/app/utils/getLyraSDK'
+
+import useWalletAccount from '../account/useWalletAccount'
+import useFetch, { useMutate } from '../data/useFetch'
+import useNetwork from './useNetwork'
+
+const fetcher = async (network: Network, account: string): Promise =>
+ await getLyraSDK(network).account(account).lyraBalances()
+
+export const EMPTY_LYRA_BALANCES: AccountLyraBalances = {
+ ethereumLyra: ZERO_BN,
+ optimismLyra: ZERO_BN,
+ arbitrumLyra: ZERO_BN,
+ optimismOldStkLyra: ZERO_BN,
+ ethereumStkLyra: ZERO_BN,
+ optimismStkLyra: ZERO_BN,
+ arbitrumStkLyra: ZERO_BN,
+ migrationAllowance: ZERO_BN,
+ stakingAllowance: ZERO_BN,
+}
+
+export default function useAccountLyraBalances(): AccountLyraBalances {
+ const account = useWalletAccount()
+ const network = useNetwork()
+ const [balances] = useFetch(
+ FetchId.AccountLyraBalances,
+ account ? [network, account] : null,
+ fetcher,
+ { refreshInterval: 10 * 1000 } // 10 seconds
+ )
+ return balances ?? EMPTY_LYRA_BALANCES
+}
+
+export const useMutateAccountLyraBalances = (): (() => Promise) => {
+ const mutate = useMutate(FetchId.AccountLyraBalances, fetcher)
+ const account = useWalletAccount()
+ const network = useNetwork()
+ return useCallback(async () => {
+ if (account) {
+ return await mutate(network, account)
+ } else {
+ return null
+ }
+ }, [mutate, account, network])
+}
diff --git a/app/src/hooks/admin/useAdminPageData.ts b/app/src/hooks/admin/useAdminPageData.ts
index 06b32c64..a761dc6c 100644
--- a/app/src/hooks/admin/useAdminPageData.ts
+++ b/app/src/hooks/admin/useAdminPageData.ts
@@ -1,7 +1,7 @@
-import { AdminMarketGlobalCache, Market } from '@lyrafinance/lyra-js'
+import { AdminMarketGlobalCache, Market, Network } from '@lyrafinance/lyra-js'
import { FetchId } from '@/app/constants/fetch'
-import fetchMarkets from '@/app/utils/fetchMarkets'
+import getLyraSDK from '@/app/utils/getLyraSDK'
import useFetch, { useMutate } from '../data/useFetch'
@@ -13,7 +13,8 @@ type AdminRoot = {
}
const fetcher = async (): Promise => {
- const markets = await fetchMarkets()
+ const markets = (await Promise.all(Object.values(Network).map(network => getLyraSDK(network).markets()))).flat()
+
const marketsWithGlobalCaches = (
await Promise.all(markets.map(m => m.lyra.admin().getMarketGlobalCache(m.address)))
).map((globalCache, idx) => ({
diff --git a/app/src/hooks/mutations/useMutateTrade.ts b/app/src/hooks/mutations/useMutateTrade.ts
index e397bc38..4b5bbec9 100644
--- a/app/src/hooks/mutations/useMutateTrade.ts
+++ b/app/src/hooks/mutations/useMutateTrade.ts
@@ -1,7 +1,7 @@
import { Trade } from '@lyrafinance/lyra-js'
import { useCallback } from 'react'
-import { useMutateTradeBalances } from '../market/useTradeBalances'
+import { useMutateTradeBalances } from '../account/useAccountBalances'
import { useMutateTradePageData } from '../market/useTradePageData'
import { useMutatePositionPageData } from '../position/usePositionPageData'
diff --git a/app/src/hooks/mutations/useMutateTradeApprove.ts b/app/src/hooks/mutations/useMutateTradeApprove.ts
index f69c9b87..0d113efb 100644
--- a/app/src/hooks/mutations/useMutateTradeApprove.ts
+++ b/app/src/hooks/mutations/useMutateTradeApprove.ts
@@ -1,7 +1,7 @@
import { Trade } from '@lyrafinance/lyra-js'
import { useCallback } from 'react'
-import { useMutateTradeBalances } from '../market/useTradeBalances'
+import { useMutateTradeBalances } from '../account/useAccountBalances'
export default function useMutateTradeApprove(trade: Trade) {
const mutateTradeBalances = useMutateTradeBalances(trade.market())
diff --git a/app/src/hooks/rewards/useAccountWethLyraStaking.ts b/app/src/hooks/rewards/useAccountWethLyraStaking.ts
index 4a14e371..c3408720 100644
--- a/app/src/hooks/rewards/useAccountWethLyraStaking.ts
+++ b/app/src/hooks/rewards/useAccountWethLyraStaking.ts
@@ -8,13 +8,13 @@ import useWalletAccount from '../account/useWalletAccount'
import useFetch, { useMutate } from '../data/useFetch'
const fetchAccountWethLyraStaking = async (account: string): Promise => {
- return await lyraOptimism.account(account).wethLyraStaking()
+ return await lyraOptimism.wethLyraStakingAccount(account)
}
export default function useAccountWethLyraStaking(): AccountWethLyraStaking | null {
const account = useWalletAccount()
const [accountStaking] = useFetch(
- FetchId.AccountWethLyraStaking,
+ FetchId.WethLyraStakingAccount,
account ? [account] : null,
fetchAccountWethLyraStaking
)
@@ -22,7 +22,7 @@ export default function useAccountWethLyraStaking(): AccountWethLyraStaking | nu
}
export const useMutateAccountWethLyraStaking = (): (() => Promise) => {
- const mutate = useMutate(FetchId.AccountWethLyraStaking, fetchAccountWethLyraStaking)
+ const mutate = useMutate(FetchId.WethLyraStakingAccount, fetchAccountWethLyraStaking)
const account = useWalletAccount()
return useCallback(async () => (account ? await mutate(account) : null), [mutate, account])
}
diff --git a/app/src/hooks/rewards/useAccountWethLyraStakingL2.ts b/app/src/hooks/rewards/useAccountWethLyraStakingL2.ts
index f7041973..33eb2f09 100644
--- a/app/src/hooks/rewards/useAccountWethLyraStakingL2.ts
+++ b/app/src/hooks/rewards/useAccountWethLyraStakingL2.ts
@@ -1,4 +1,4 @@
-import { AccountWethLyraStakingL2 } from '@lyrafinance/lyra-js'
+import { AccountWethLyraStakingL2, WethLyraStaking } from '@lyrafinance/lyra-js'
import { useCallback } from 'react'
import { FetchId } from '@/app/constants/fetch'
@@ -8,13 +8,13 @@ import useWalletAccount from '../account/useWalletAccount'
import useFetch, { useMutate } from '../data/useFetch'
const fetchAccountWethLyraStakingL2 = async (account: string): Promise => {
- return await lyraOptimism.account(account).wethLyraStakingL2()
+ return await WethLyraStaking.getByOwnerL2(lyraOptimism, account)
}
export default function useAccountWethLyraStakingL2(): AccountWethLyraStakingL2 | null {
const account = useWalletAccount()
const [accountStaking] = useFetch(
- FetchId.AccountWethLyraStakingL2,
+ FetchId.WethLyraStakingL2Account,
account ? [account] : null,
fetchAccountWethLyraStakingL2
)
@@ -22,7 +22,7 @@ export default function useAccountWethLyraStakingL2(): AccountWethLyraStakingL2
}
export const useMutateAccountWethLyraStakingL2 = (): (() => Promise) => {
- const mutate = useMutate(FetchId.AccountWethLyraStakingL2, fetchAccountWethLyraStakingL2)
+ const mutate = useMutate(FetchId.WethLyraStakingL2Account, fetchAccountWethLyraStakingL2)
const account = useWalletAccount()
return useCallback(async () => (account ? await mutate(account) : null), [mutate, account])
}
diff --git a/app/src/hooks/rewards/useClaimableBalance.ts b/app/src/hooks/rewards/useClaimableBalance.ts
index 9d70550e..9d4a0d45 100644
--- a/app/src/hooks/rewards/useClaimableBalance.ts
+++ b/app/src/hooks/rewards/useClaimableBalance.ts
@@ -3,7 +3,7 @@ import { useCallback } from 'react'
import { ZERO_BN } from '@/app/constants/bn'
import { FetchId } from '@/app/constants/fetch'
-import { lyraArbitrum, lyraOptimism } from '@/app/utils/lyra'
+import getLyraSDK from '@/app/utils/getLyraSDK'
import useNetwork from '../account/useNetwork'
import useWalletAccount from '../account/useWalletAccount'
@@ -13,15 +13,11 @@ const EMPTY: ClaimableBalanceL2 = {
oldStkLyra: ZERO_BN,
newStkLyra: ZERO_BN,
op: ZERO_BN,
+ lyra: ZERO_BN,
}
const fetchClaimableBalance = async (network: Network, account: string): Promise => {
- switch (network) {
- case Network.Arbitrum:
- return await lyraArbitrum.account(account).claimableRewardsL2()
- case Network.Optimism:
- return await lyraOptimism.account(account).claimableRewardsL2()
- }
+ return await getLyraSDK(network).claimableRewards(account)
}
export default function useClaimableBalances(): ClaimableBalanceL2 {
diff --git a/app/src/hooks/rewards/useClaimableBalanceL1.ts b/app/src/hooks/rewards/useClaimableBalanceL1.ts
deleted file mode 100644
index 21fc3da9..00000000
--- a/app/src/hooks/rewards/useClaimableBalanceL1.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { ClaimableBalanceL1 } from '@lyrafinance/lyra-js'
-import { useCallback } from 'react'
-
-import { ZERO_BN } from '@/app/constants/bn'
-import { FetchId } from '@/app/constants/fetch'
-import { lyraOptimism } from '@/app/utils/lyra'
-
-import useWalletAccount from '../account/useWalletAccount'
-import useFetch, { useMutate } from '../data/useFetch'
-
-const EMPTY: ClaimableBalanceL1 = {
- newStkLyra: ZERO_BN,
- lyra: ZERO_BN,
-}
-
-const fetchClaimableBalanceL1 = async (account: string): Promise =>
- await lyraOptimism.account(account).claimableRewardsL1()
-
-export default function useClaimableBalancesL1(): ClaimableBalanceL1 {
- const account = useWalletAccount()
- const [claimableBalance] = useFetch(FetchId.ClaimableBalanceL1, account ? [account] : null, fetchClaimableBalanceL1)
- return claimableBalance ?? EMPTY
-}
-
-export const useMutateClaimableBalancesL1 = (): (() => Promise) => {
- const mutate = useMutate(FetchId.ClaimableBalanceL1, fetchClaimableBalanceL1)
- const account = useWalletAccount()
- return useCallback(async () => (account ? await mutate(account) : null), [mutate, account])
-}
diff --git a/app/src/hooks/rewards/useClaimableStakingRewards.ts b/app/src/hooks/rewards/useClaimableStakingRewards.ts
new file mode 100644
index 00000000..8a24f83b
--- /dev/null
+++ b/app/src/hooks/rewards/useClaimableStakingRewards.ts
@@ -0,0 +1,28 @@
+import { BigNumber } from '@ethersproject/bignumber'
+import { useCallback } from 'react'
+
+import { ZERO_BN } from '@/app/constants/bn'
+import { FetchId } from '@/app/constants/fetch'
+import { lyraOptimism } from '@/app/utils/lyra'
+
+import useWalletAccount from '../account/useWalletAccount'
+import useFetch, { useMutate } from '../data/useFetch'
+
+const fetchClaimableStakingRewards = async (account: string): Promise =>
+ await lyraOptimism.claimableStakingRewards(account)
+
+export default function useClaimableStakingRewards(): BigNumber {
+ const account = useWalletAccount()
+ const [claimableBalance] = useFetch(
+ FetchId.ClaimableStakingRewards,
+ account ? [account] : null,
+ fetchClaimableStakingRewards
+ )
+ return claimableBalance ?? ZERO_BN
+}
+
+export const useMutateClaimableStakingRewards = (): (() => Promise) => {
+ const mutate = useMutate(FetchId.ClaimableStakingRewards, fetchClaimableStakingRewards)
+ const account = useWalletAccount()
+ return useCallback(async () => (account ? await mutate(account) : null), [mutate, account])
+}
diff --git a/app/src/hooks/rewards/useClaimableWethLyraRewards.ts b/app/src/hooks/rewards/useClaimableWethLyraRewards.ts
new file mode 100644
index 00000000..56384a25
--- /dev/null
+++ b/app/src/hooks/rewards/useClaimableWethLyraRewards.ts
@@ -0,0 +1,28 @@
+import { BigNumber } from '@ethersproject/bignumber'
+import { useCallback } from 'react'
+
+import { ZERO_BN } from '@/app/constants/bn'
+import { FetchId } from '@/app/constants/fetch'
+import { lyraOptimism } from '@/app/utils/lyra'
+
+import useWalletAccount from '../account/useWalletAccount'
+import useFetch, { useMutate } from '../data/useFetch'
+
+const fetchClaimableWethLyraRewards = async (account: string): Promise =>
+ await lyraOptimism.claimableWethLyraRewards(account)
+
+export default function useClaimableWethLyraRewards(): BigNumber {
+ const account = useWalletAccount()
+ const [claimableBalance] = useFetch(
+ FetchId.ClaimableWethLyraRewards,
+ account ? [account] : null,
+ fetchClaimableWethLyraRewards
+ )
+ return claimableBalance ?? ZERO_BN
+}
+
+export const useMutateClaimableStakingRewards = (): (() => Promise) => {
+ const mutate = useMutate(FetchId.ClaimableWethLyraRewards, fetchClaimableWethLyraRewards)
+ const account = useWalletAccount()
+ return useCallback(async () => (account ? await mutate(account) : null), [mutate, account])
+}
diff --git a/app/src/hooks/rewards/useLatestRewardEpoch.ts b/app/src/hooks/rewards/useLatestRewardEpoch.ts
index b5d6acc4..de95ec32 100644
--- a/app/src/hooks/rewards/useLatestRewardEpoch.ts
+++ b/app/src/hooks/rewards/useLatestRewardEpoch.ts
@@ -7,15 +7,14 @@ import useWalletAccount from '../account/useWalletAccount'
import useFetch from '../data/useFetch'
import { fetchAccountRewardEpochs } from './useAccountRewardEpochs'
-type LatestRewardEpoch = {
+export type LatestRewardEpoch = {
global: GlobalRewardEpoch
account?: AccountRewardEpoch | null
}
export const fetchLatestRewardEpoch = async (
network: Network,
- address: string | null,
- sortByAscending?: boolean
+ address: string | null
): Promise => {
const sdk = getLyraSDK(network)
const [globalRewardEpochs, accountRewardEpochs] = await Promise.all([
@@ -29,15 +28,13 @@ export const fetchLatestRewardEpoch = async (
accountRewardEpochs.find(epoch => epoch.globalEpoch.startTimestamp === global.startTimestamp) ?? null
return { account, global }
})
- .sort((a, b) =>
- sortByAscending ? a.global.endTimestamp - b.global.endTimestamp : b.global.endTimestamp - a.global.endTimestamp
- )
+ .sort((a, b) => a.global.endTimestamp - b.global.endTimestamp)
.find(({ account, global }) => account?.isPendingRewards || global.isCurrent) ?? null
)
}
-export default function useLatestRewardEpoch(network: Network, sortByAscending?: boolean): LatestRewardEpoch | null {
+export default function useLatestRewardEpoch(network: Network): LatestRewardEpoch | null {
const account = useWalletAccount()
- const [data] = useFetch(FetchId.LatestRewardEpoch, [network, account, sortByAscending], fetchLatestRewardEpoch)
+ const [data] = useFetch(FetchId.LatestRewardEpoch, [network, account], fetchLatestRewardEpoch)
return data ?? null
}
diff --git a/app/src/hooks/rewards/useLyraAccountStaking.ts b/app/src/hooks/rewards/useLyraAccountStaking.ts
index 82d9067d..1da3a1db 100644
--- a/app/src/hooks/rewards/useLyraAccountStaking.ts
+++ b/app/src/hooks/rewards/useLyraAccountStaking.ts
@@ -8,17 +8,17 @@ import useWalletAccount from '../account/useWalletAccount'
import useFetch, { useMutate } from '../data/useFetch'
const fetchLyraAccountStaking = async (account: string): Promise => {
- return await lyraOptimism.account(account).lyraStaking()
+ return await lyraOptimism.lyraStakingAccount(account)
}
-export default function useLyraAccountStaking(): AccountLyraStaking | null {
+export default function useLyraStakingAccount(): AccountLyraStaking | null {
const account = useWalletAccount()
- const [accountStaking] = useFetch(FetchId.LyraAccountStaking, account ? [account] : null, fetchLyraAccountStaking)
+ const [accountStaking] = useFetch(FetchId.LyraStakingAccount, account ? [account] : null, fetchLyraAccountStaking)
return accountStaking
}
export const useMutateAccountStaking = (): (() => Promise) => {
- const mutate = useMutate(FetchId.LyraAccountStaking, fetchLyraAccountStaking)
+ const mutate = useMutate(FetchId.LyraStakingAccount, fetchLyraAccountStaking)
const account = useWalletAccount()
return useCallback(async () => {
if (account) {
diff --git a/app/src/hooks/rewards/useLyraStaking.ts b/app/src/hooks/rewards/useLyraStaking.ts
index ade2dff3..eee42607 100644
--- a/app/src/hooks/rewards/useLyraStaking.ts
+++ b/app/src/hooks/rewards/useLyraStaking.ts
@@ -3,11 +3,18 @@ import { LyraStaking } from '@lyrafinance/lyra-js'
import { FetchId } from '@/app/constants/fetch'
import { lyraOptimism } from '@/app/utils/lyra'
-import useFetch from '../data/useFetch'
+import useFetch, { useMutate } from '../data/useFetch'
-const fetchGlobalStaking = async (): Promise => await lyraOptimism.lyraStaking()
+export const fetchLyraStaking = async (): Promise => {
+ return await lyraOptimism.lyraStaking()
+}
export default function useLyraStaking(): LyraStaking | null {
- const [lyraStaking] = useFetch(FetchId.LyraStaking, [], fetchGlobalStaking)
- return lyraStaking
+ const [stake] = useFetch(FetchId.LyraStaking, [], fetchLyraStaking)
+ return stake
+}
+
+export const useMutateLyraStaking = (): (() => Promise) => {
+ const mutate = useMutate(FetchId.LyraStaking, fetchLyraStaking)
+ return mutate
}
diff --git a/app/src/hooks/rewards/useNetworkTradingVolume.ts b/app/src/hooks/rewards/useNetworkTradingVolume.ts
new file mode 100644
index 00000000..aa2c2e4a
--- /dev/null
+++ b/app/src/hooks/rewards/useNetworkTradingVolume.ts
@@ -0,0 +1,39 @@
+import { Network } from '@lyrafinance/lyra-js'
+
+import { FetchId } from '@/app/constants/fetch'
+import fromBigNumber from '@/app/utils/fromBigNumber'
+import getLyraSDK from '@/app/utils/getLyraSDK'
+
+import useFetch from '../data/useFetch'
+
+type NetworkTradingVolumeData = {
+ totalShortOpenInterestUSD: number
+ totalNotionalVolume: number
+}
+
+const EMPTY: NetworkTradingVolumeData = {
+ totalShortOpenInterestUSD: 0,
+ totalNotionalVolume: 0,
+}
+
+const fetcher = async (network: Network): Promise => {
+ const markets = await getLyraSDK(network).markets()
+ const volumes = await Promise.all(markets.map(market => market.tradingVolumeHistory()))
+ const totalShortOpenInterestUSD = volumes.reduce(
+ (totalShortOI, volume) => totalShortOI + fromBigNumber(volume[volume.length - 1].totalShortOpenInterestUSD),
+ 0
+ )
+ const totalNotionalVolume = volumes.reduce(
+ (totalNetworkVolume, volume) => totalNetworkVolume + fromBigNumber(volume[volume.length - 1].totalNotionalVolume),
+ 0
+ )
+ return {
+ totalShortOpenInterestUSD,
+ totalNotionalVolume,
+ }
+}
+
+export default function useNetworkTradingVolume(network: Network) {
+ const [data] = useFetch(FetchId.NetworkTradingVolume, [network], fetcher)
+ return data ?? EMPTY
+}
diff --git a/app/src/hooks/rewards/useRewardsEthLyraLPPageData.ts b/app/src/hooks/rewards/useRewardsEthLyraLPPageData.ts
new file mode 100644
index 00000000..d64214ee
--- /dev/null
+++ b/app/src/hooks/rewards/useRewardsEthLyraLPPageData.ts
@@ -0,0 +1,44 @@
+import { AccountWethLyraStaking, WethLyraStaking } from '@lyrafinance/lyra-js'
+import { BigNumber } from 'ethers'
+
+import { ZERO_BN } from '@/app/constants/bn'
+import { FetchId } from '@/app/constants/fetch'
+import { lyraOptimism } from '@/app/utils/lyra'
+
+import useWalletAccount from '../account/useWalletAccount'
+import useFetch from '../data/useFetch'
+
+type RewardsEthLyraLPPageData = {
+ wethLyraStaking: WethLyraStaking | null
+ accountWethLyraStaking: AccountWethLyraStaking | null
+ accountWethLyraStakingL2: AccountWethLyraStaking | null
+ claimableBalance: BigNumber
+}
+
+const EMPTY: RewardsEthLyraLPPageData = {
+ wethLyraStaking: null,
+ accountWethLyraStaking: null,
+ accountWethLyraStakingL2: null,
+ claimableBalance: ZERO_BN,
+}
+
+const fetcher = async (address: string | null): Promise => {
+ const [wethLyraStaking, accountWethLyraStaking, claimableBalance, accountWethLyraStakingL2] = await Promise.all([
+ lyraOptimism.wethLyraStaking(),
+ address ? lyraOptimism.wethLyraStakingAccount(address) : null,
+ address ? lyraOptimism.claimableWethLyraRewards(address) : ZERO_BN,
+ address ? WethLyraStaking.getByOwnerL2(lyraOptimism, address) : null,
+ ])
+ return {
+ wethLyraStaking,
+ accountWethLyraStaking,
+ accountWethLyraStakingL2,
+ claimableBalance,
+ }
+}
+
+export default function useRewardsEthLyraLPPageData(): RewardsEthLyraLPPageData {
+ const account = useWalletAccount()
+ const [data] = useFetch(FetchId.RewardsEthLyraLPPageData, [account], fetcher)
+ return data ?? EMPTY
+}
diff --git a/app/src/hooks/rewards/useRewardsPageData.ts b/app/src/hooks/rewards/useRewardsPageData.ts
new file mode 100644
index 00000000..4bd3fba9
--- /dev/null
+++ b/app/src/hooks/rewards/useRewardsPageData.ts
@@ -0,0 +1,80 @@
+import { AccountRewardEpoch, GlobalRewardEpoch, Network, WethLyraStaking } from '@lyrafinance/lyra-js'
+import { useCallback } from 'react'
+
+import { FetchId } from '@/app/constants/fetch'
+import getLyraSDK from '@/app/utils/getLyraSDK'
+import { lyraOptimism } from '@/app/utils/lyra'
+
+import useWallet from '../account/useWallet'
+import useWalletAccount from '../account/useWalletAccount'
+import useFetch, { useMutate } from '../data/useFetch'
+import { LatestRewardEpoch } from './useLatestRewardEpoch'
+
+type NetworkRewardsData = {
+ globalRewardEpochs: GlobalRewardEpoch[]
+ accountRewardEpochs: AccountRewardEpoch[]
+ latestRewardEpoch: LatestRewardEpoch
+}
+
+export type RewardsPageData = {
+ wethLyraStaking: WethLyraStaking | null
+ epochs: {
+ [network in Network]?: NetworkRewardsData
+ }
+}
+
+const EMPTY: RewardsPageData = {
+ wethLyraStaking: null,
+ epochs: {},
+}
+
+export const fetchRewardsPageData = async (walletAddress: string | null): Promise => {
+ const [globalRewardEpochs, accountRewardEpochs, wethLyraStaking] = await Promise.all([
+ Promise.all(Object.values(Network).map(network => getLyraSDK(network).globalRewardEpochs())),
+ walletAddress
+ ? Promise.all(Object.values(Network).map(network => getLyraSDK(network).accountRewardEpochs(walletAddress)))
+ : [],
+ lyraOptimism.wethLyraStaking(),
+ ])
+
+ const networkEpochsMap = Object.values(Network).reduce((map, network, idx) => {
+ const networkGlobalEpochs = globalRewardEpochs[idx]
+ const networkAccountRewardEpochs = accountRewardEpochs[idx]
+ const matchedRewardEpochs = networkGlobalEpochs
+ .map(globalEpoch => ({
+ account:
+ networkAccountRewardEpochs?.find(
+ accountEpoch => accountEpoch.globalEpoch.startTimestamp === globalEpoch.startTimestamp
+ ) ?? null,
+ global: globalEpoch,
+ }))
+ .sort((a, b) => b.global.endTimestamp - a.global.endTimestamp)
+ return {
+ ...map,
+ [network]: {
+ globalRewardEpochs: networkGlobalEpochs,
+ accountRewardEpochs: networkAccountRewardEpochs,
+ latestRewardEpoch: matchedRewardEpochs[0],
+ },
+ }
+ }, {})
+
+ return {
+ epochs: networkEpochsMap,
+ wethLyraStaking,
+ }
+}
+
+export default function useRewardsPageData(): RewardsPageData {
+ const account = useWalletAccount()
+ const [rewardsPageData] = useFetch(FetchId.RewardsPageData, [account], fetchRewardsPageData, {
+ refreshInterval: 30 * 1000,
+ })
+ return rewardsPageData ?? EMPTY
+}
+
+export function useMutateRewardsPageData() {
+ const { account } = useWallet()
+ const mutate = useMutate(FetchId.RewardsPageData, fetchRewardsPageData)
+ return useCallback(() => (account ? mutate(account) : null), [mutate, account])
+}
diff --git a/app/src/hooks/rewards/useRewardsShortsPageData.ts b/app/src/hooks/rewards/useRewardsShortsPageData.ts
new file mode 100644
index 00000000..1809c8a7
--- /dev/null
+++ b/app/src/hooks/rewards/useRewardsShortsPageData.ts
@@ -0,0 +1,38 @@
+import { Network } from '@lyrafinance/lyra-js'
+import { BigNumber } from 'ethers'
+
+import { ZERO_ADDRESS, ZERO_BN } from '@/app/constants/bn'
+import { FetchId } from '@/app/constants/fetch'
+import getLyraSDK from '@/app/utils/getLyraSDK'
+
+import useWalletAccount from '../account/useWalletAccount'
+import useFetch from '../data/useFetch'
+import { fetchRewardsPageData, RewardsPageData } from './useRewardsPageData'
+
+const EMPTY = {
+ epochs: {},
+ wethLyraStaking: null,
+ collateral: ZERO_BN,
+}
+
+type RewardsShortPageData = RewardsPageData & {
+ collateral: BigNumber
+}
+
+const fetcher = async (account: string, network: Network): Promise => {
+ const lyra = getLyraSDK(network)
+ const [rewardPageData, openPositions] = await Promise.all([
+ fetchRewardsPageData(account),
+ lyra.openPositions(account),
+ ])
+ return {
+ ...rewardPageData,
+ collateral: openPositions.reduce((sum, position) => sum.add(position.collateral?.value ?? ZERO_BN), ZERO_BN),
+ }
+}
+
+export default function useRewardsShortsPageData(network: Network | null): RewardsShortPageData {
+ const account = useWalletAccount()
+ const [data] = useFetch(FetchId.RewardsShortsPageData, network ? [account ?? ZERO_ADDRESS, network] : null, fetcher)
+ return data ?? EMPTY
+}
diff --git a/app/src/hooks/rewards/useStake.ts b/app/src/hooks/rewards/useStake.ts
deleted file mode 100644
index fdbfe2ab..00000000
--- a/app/src/hooks/rewards/useStake.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { BigNumber } from '@ethersproject/bignumber'
-import { LyraStake } from '@lyrafinance/lyra-js'
-
-import { FetchId } from '@/app/constants/fetch'
-import { lyraOptimism } from '@/app/utils/lyra'
-
-import useWalletAccount from '../account/useWalletAccount'
-import useFetch from '../data/useFetch'
-
-export const fetchStake = async (account: string, amountStr: string): Promise => {
- return await lyraOptimism.stake(account, BigNumber.from(amountStr))
-}
-
-export default function useStake(amount: BigNumber): LyraStake | null {
- const account = useWalletAccount()
- const [stake] = useFetch(FetchId.Stake, account ? [account, amount.toString()] : null, fetchStake)
- return stake
-}
diff --git a/app/src/hooks/rewards/useTokenSupply.ts b/app/src/hooks/rewards/useTokenSupply.ts
new file mode 100644
index 00000000..48f49576
--- /dev/null
+++ b/app/src/hooks/rewards/useTokenSupply.ts
@@ -0,0 +1,22 @@
+import { FetchId } from '@/app/constants/fetch'
+
+import useFetch from '../data/useFetch'
+
+type TokenSupplyResponse = {
+ mainnetCirculatingSupply: number
+ optimismCirculatingSupply: number
+ arbitrumCirculatingSupply: number
+ totalCirculatingSupply: number
+ totalSupply: number
+}
+
+async function fetcher() {
+ const res = await fetch(`${process.env.REACT_APP_API_URL}/supply`)
+ const data: TokenSupplyResponse = await res.json()
+ return data
+}
+
+export default function useTokenSupply() {
+ const [data] = useFetch(FetchId.TokenSupply, [], fetcher)
+ return data
+}
diff --git a/app/src/hooks/rewards/useUnstake.ts b/app/src/hooks/rewards/useUnstake.ts
deleted file mode 100644
index 2f8c49f5..00000000
--- a/app/src/hooks/rewards/useUnstake.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { BigNumber } from '@ethersproject/bignumber'
-import { LyraUnstake } from '@lyrafinance/lyra-js'
-
-import { FetchId } from '@/app/constants/fetch'
-import { lyraOptimism } from '@/app/utils/lyra'
-
-import useWalletAccount from '../account/useWalletAccount'
-import useFetch from '../data/useFetch'
-
-export const fetchUnstake = async (account: string, amountStr: string): Promise => {
- return await lyraOptimism.unstake(account, BigNumber.from(amountStr))
-}
-
-export default function useUnstake(amount: BigNumber): LyraUnstake | null {
- const account = useWalletAccount()
- const [stake] = useFetch(FetchId.Unstake, account ? [account, amount.toString()] : null, fetchUnstake)
- return stake
-}
diff --git a/app/src/hooks/vaults/useVaultsPageData.ts b/app/src/hooks/vaults/useVaultsPageData.ts
index ac4edd34..055b92c1 100644
--- a/app/src/hooks/vaults/useVaultsPageData.ts
+++ b/app/src/hooks/vaults/useVaultsPageData.ts
@@ -14,8 +14,8 @@ const fetcher = async (walletAddress?: string): Promise => {
const vaults = await Promise.all(
Object.values(Network).map(async network => {
const lyra = getLyraSDK(network)
- const marketAddresses = await lyra.marketAddresses()
- return await Promise.all(marketAddresses.map(marketAddress => fetchVault(network, marketAddress, walletAddress)))
+ const markets = await lyra.markets()
+ return await Promise.all(markets.map(market => fetchVault(network, market, walletAddress)))
})
)
return vaults
diff --git a/app/src/page_helpers/RewardsEthLyraLPPageHelper/index.tsx b/app/src/page_helpers/RewardsEthLyraLPPageHelper/index.tsx
new file mode 100644
index 00000000..496cedcd
--- /dev/null
+++ b/app/src/page_helpers/RewardsEthLyraLPPageHelper/index.tsx
@@ -0,0 +1,176 @@
+import Button from '@lyra/ui/components/Button'
+import Card from '@lyra/ui/components/Card'
+import CardSection from '@lyra/ui/components/Card/CardSection'
+import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import { IconType } from '@lyra/ui/components/Icon'
+import Text from '@lyra/ui/components/Text'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import formatPercentage from '@lyra/ui/utils/formatPercentage'
+import formatTruncatedUSD from '@lyra/ui/utils/formatTruncatedUSD'
+import formatUSD from '@lyra/ui/utils/formatUSD'
+import { AccountWethLyraStaking, WethLyraStaking } from '@lyrafinance/lyra-js'
+import { BigNumber } from 'ethers'
+import React, { useState } from 'react'
+
+import LabelItem from '@/app/components/common/LabelItem'
+import TokenAmountText from '@/app/components/common/TokenAmountText'
+import TokenImageStack from '@/app/components/common/TokenImageStack'
+import { ZERO_BN } from '@/app/constants/bn'
+import { WETH_LYRA_L1_LIQUIDITY_URL } from '@/app/constants/links'
+import ConnectWalletButton from '@/app/containers/common/ConnectWalletButton'
+import ClaimWethLyraStakingRewardsModal from '@/app/containers/rewards/ClaimWethLyraStakingRewardsModal'
+import RewardPageHeader from '@/app/containers/rewards/RewardsPageHeader'
+import WethLyraL2UnstakeModal from '@/app/containers/rewards/WethLyraL2UnstakeModal'
+import WethLyraStakeModal from '@/app/containers/rewards/WethLyraStakeModal'
+import WethLyraUnstakeModal from '@/app/containers/rewards/WethLyraUnstakeModal'
+import useWalletAccount from '@/app/hooks/account/useWalletAccount'
+import fromBigNumber from '@/app/utils/fromBigNumber'
+
+import Page from '../common/Page'
+import PageGrid from '../common/Page/PageGrid'
+
+const CTA_BUTTON_WIDTH = 160
+
+type Props = {
+ claimableBalance: BigNumber
+ wethLyraStakingAccount: AccountWethLyraStaking | null
+ wethLyraStakingAccountL2: AccountWethLyraStaking | null
+ wethLyraStaking: WethLyraStaking
+}
+
+const RewardsEthLyraLPPageHelper = ({
+ claimableBalance,
+ wethLyraStaking,
+ wethLyraStakingAccount,
+ wethLyraStakingAccountL2,
+}: Props) => {
+ const isMobile = useIsMobile()
+ const account = useWalletAccount()
+ const [isOpen, setIsOpen] = useState(false)
+ const [isStakeOpen, setIsStakeOpen] = useState(false)
+ const [isUnstakeOpen, setIsUnstakeOpen] = useState(false)
+ const [isL2UnstakeOpen, setIsL2UnstakeOpen] = useState(false)
+ const stakedLPTokenBalance = fromBigNumber(wethLyraStakingAccount?.stakedLPTokenBalance ?? ZERO_BN)
+ const poolLyraBalance = stakedLPTokenBalance * wethLyraStaking.lyraPerToken
+ const poolWethBalance = stakedLPTokenBalance * wethLyraStaking.wethPerToken
+ return (
+ : null} noHeaderPadding>
+
+ {isMobile ? : null}
+
+ LYRA-ETH LP Rewards
+
+ · Ethereum
+
+
+
+
+ Overview
+
+ This program rewards WETH / LYRA liquidity providers on the Uniswap v3 pool via Arrakis Finance. LPs earn
+ LYRA tokens which can be claimed at any time.
+
+
+
+
+
+
+
+
+
+ Your Rewards
+
+ {account ? (
+ <>
+
+
+
+
+ {formatNumber(poolWethBalance)} ETH, {formatNumber(poolLyraBalance)} LYRA
+
+
+ }
+ />
+
+ }
+ valueColor="primaryText"
+ />
+
+
+ setIsOpen(true)}
+ minWidth={CTA_BUTTON_WIDTH}
+ />
+ setIsStakeOpen(true)}
+ minWidth={CTA_BUTTON_WIDTH}
+ />
+ {wethLyraStakingAccount?.stakedLPTokenBalance.gt(0) ? (
+ setIsUnstakeOpen(true)}
+ minWidth={CTA_BUTTON_WIDTH}
+ />
+ ) : null}
+ {wethLyraStakingAccountL2?.stakedLPTokenBalance.gt(0) ? (
+ setIsL2UnstakeOpen(true)}
+ minWidth={CTA_BUTTON_WIDTH}
+ />
+ ) : null}
+
+
+ >
+ ) : (
+
+
+
+ )}
+
+
+
+ setIsOpen(false)} />
+ setIsStakeOpen(false)} />
+ setIsUnstakeOpen(false)} />
+ setIsUnstakeOpen(false)} />
+ setIsL2UnstakeOpen(false)} />
+
+ )
+}
+
+export default RewardsEthLyraLPPageHelper
diff --git a/app/src/page_helpers/RewardsHistoryPageHelper/index.tsx b/app/src/page_helpers/RewardsHistoryPageHelper/index.tsx
deleted file mode 100644
index 7b075c21..00000000
--- a/app/src/page_helpers/RewardsHistoryPageHelper/index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react'
-
-import { PageId } from '@/app/constants/pages'
-import RewardsHistoryCard from '@/app/containers/rewards/RewardsHistoryCard'
-import getPagePath from '@/app/utils/getPagePath'
-
-import Page from '../common/Page'
-import PageGrid from '../common/Page/PageGrid'
-
-const RewardsHistoryPageHelper = () => {
- return (
-
-
-
-
-
- )
-}
-
-export default RewardsHistoryPageHelper
diff --git a/app/src/page_helpers/RewardsIndexPageHelper/index.tsx b/app/src/page_helpers/RewardsIndexPageHelper/index.tsx
new file mode 100644
index 00000000..3a0dabd6
--- /dev/null
+++ b/app/src/page_helpers/RewardsIndexPageHelper/index.tsx
@@ -0,0 +1,55 @@
+import Box from '@lyra/ui/components/Box'
+import Grid from '@lyra/ui/components/Grid'
+import Text from '@lyra/ui/components/Text'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import { WethLyraStaking } from '@lyrafinance/lyra-js'
+import React from 'react'
+
+import RewardsLastUpdatedAlert from '@/app/containers/common/RewardsLastUpdatedAlert'
+import RewardsTokenSupplyCard from '@/app/containers/rewards/RewardsTokenSupplyCard'
+import ShortRewardsSection from '@/app/containers/rewards/ShortRewardsSection'
+import StakingRewardsCard from '@/app/containers/rewards/StakingRewardsCard'
+import TradingRewardsSection from '@/app/containers/rewards/TradingRewardsSection'
+import VaultsRewardsSection from '@/app/containers/rewards/VaultsRewardsSection'
+import WethLyraLPRewardsSection from '@/app/containers/rewards/WethLyraLPRewardsSection'
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+
+import Page from '../common/Page'
+import PageGrid from '../common/Page/PageGrid'
+
+type Props = {
+ latestRewardEpochs: LatestRewardEpoch[]
+ wethLyraStaking: WethLyraStaking | null
+}
+
+const RewardsIndexPageHelper = ({ latestRewardEpochs, wethLyraStaking }: Props) => {
+ const isMobile = useIsMobile()
+ const pageHeader = (
+
+
+
+ Rewards
+
+
+ Stake and Earn
+
+
+
+
+ )
+ return (
+
+
+ {isMobile ? pageHeader : null}
+
+
+
+
+
+
+
+
+ )
+}
+
+export default RewardsIndexPageHelper
diff --git a/app/src/page_helpers/RewardsPageHelper/index.tsx b/app/src/page_helpers/RewardsPageHelper/index.tsx
deleted file mode 100644
index 154fe135..00000000
--- a/app/src/page_helpers/RewardsPageHelper/index.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react'
-
-import RewardsLastUpdatedAlert from '@/app/containers/common/RewardsLastUpdatedAlert'
-import RewardsBreakdownCard from '@/app/containers/rewards/RewardsBreakdownCard'
-import RewardsStakingCard from '@/app/containers/rewards/RewardsStakingCard'
-
-import Page from '../common/Page'
-import PageGrid from '../common/Page/PageGrid'
-
-const RewardsPageHelper = () => {
- return (
-
-
-
-
-
-
-
- )
-}
-
-export default RewardsPageHelper
diff --git a/app/src/page_helpers/RewardsShortsPageHelper/index.tsx b/app/src/page_helpers/RewardsShortsPageHelper/index.tsx
new file mode 100644
index 00000000..a0b4b0d6
--- /dev/null
+++ b/app/src/page_helpers/RewardsShortsPageHelper/index.tsx
@@ -0,0 +1,167 @@
+import Card from '@lyra/ui/components/Card'
+import CardBody from '@lyra/ui/components/Card/CardBody'
+import CardSection from '@lyra/ui/components/Card/CardSection'
+import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import Text from '@lyra/ui/components/Text'
+import Countdown from '@lyra/ui/components/Text/CountdownText'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import formatDate from '@lyra/ui/utils/formatDate'
+import formatUSD from '@lyra/ui/utils/formatUSD'
+import { AccountRewardEpoch, GlobalRewardEpoch, Network } from '@lyrafinance/lyra-js'
+import { BigNumber } from 'ethers'
+import React, { useMemo, useState } from 'react'
+
+import LabelItem from '@/app/components/common/LabelItem'
+import RewardTokenAmounts from '@/app/components/rewards/RewardTokenAmounts'
+import { CLAIMABLE_REWARDS_DELAY } from '@/app/constants/rewards'
+import ConnectWalletButton from '@/app/containers/common/ConnectWalletButton'
+import ClaimModal from '@/app/containers/rewards/ClaimModal'
+import ClaimModalButton from '@/app/containers/rewards/ClaimModalButton'
+import RewardPageHeader from '@/app/containers/rewards/RewardsPageHeader'
+import useNetwork from '@/app/hooks/account/useNetwork'
+import useWalletAccount from '@/app/hooks/account/useWalletAccount'
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+import getNetworkDisplayName from '@/app/utils/getNetworkDisplayName'
+
+import Page from '../common/Page'
+import PageGrid from '../common/Page/PageGrid'
+
+const CTA_BUTTON_WIDTH = 160
+
+type Props = {
+ collateral: BigNumber
+ latestRewardEpoch: LatestRewardEpoch
+ accountRewardEpochs: AccountRewardEpoch[]
+ globalRewardEpochs: GlobalRewardEpoch[]
+}
+
+const RewardsShortsPageHelper = ({ latestRewardEpoch, accountRewardEpochs, globalRewardEpochs, collateral }: Props) => {
+ const [isOpen, setIsOpen] = useState(false)
+ const isMobile = useIsMobile()
+ const account = useWalletAccount()
+ const network = useNetwork()
+ const { global: latestGlobalRewardEpoch, account: latestAccountRewardEpoch } = latestRewardEpoch
+ const pendingRewards = latestAccountRewardEpoch?.shortCollateralRewards.length
+ ? latestAccountRewardEpoch?.shortCollateralRewards
+ : latestGlobalRewardEpoch.shortCollateralRewards(0)
+ const globalRewardEpochsSorted = useMemo(
+ () => globalRewardEpochs.sort((a, b) => b.startTimestamp - a.startTimestamp),
+ [globalRewardEpochs]
+ )
+ const claimableRewards = latestAccountRewardEpoch?.claimableRewards.tradingRewards.length
+ ? latestAccountRewardEpoch?.claimableRewards.tradingRewards
+ : latestGlobalRewardEpoch.shortCollateralRewards(0)
+ return (
+ : null} noHeaderPadding>
+
+ {isMobile ? : null}
+
+ Short Rewards
+
+ · {getNetworkDisplayName(latestGlobalRewardEpoch.lyra.network)}
+
+
+
+
+ Overview
+
+ This program rewards traders for selling calls and puts with LYRA{' '}
+ {network === Network.Optimism ? 'and OP' : ''} tokens every two weeks. This program is not subject to
+ boosts.
+
+
+
+
+
+ Your Rewards
+
+ · {formatDate(latestRewardEpoch.global.startTimestamp, true)} -{' '}
+ {formatDate(latestRewardEpoch.global.endTimestamp, true)}
+
+
+ {account ? (
+ <>
+
+
+ }
+ />
+ }
+ valueColor="primaryText"
+ />
+
+ }
+ />
+
+ setIsOpen(true)}
+ minWidth={CTA_BUTTON_WIDTH}
+ />
+
+ >
+ ) : (
+
+
+
+ )}
+
+
+ {account ? (
+
+
+
+ History
+
+
+ Epoch
+
+ Your Rewards
+
+
+ {globalRewardEpochsSorted.map(globalRewardEpoch => {
+ const accountEpoch = accountRewardEpochs.find(
+ accountRewardEpoch => accountRewardEpoch.globalEpoch.id === globalRewardEpoch.id
+ )
+ return (
+
+
+ {formatDate(globalRewardEpoch.startTimestamp, true)} -{' '}
+ {formatDate(globalRewardEpoch.endTimestamp, true)}
+
+
+
+ )
+ })}
+
+
+ ) : null}
+ {latestAccountRewardEpoch ? (
+ setIsOpen(false)} />
+ ) : null}
+
+
+ )
+}
+
+export default RewardsShortsPageHelper
diff --git a/app/src/page_helpers/RewardsTradingPageHelper/index.tsx b/app/src/page_helpers/RewardsTradingPageHelper/index.tsx
new file mode 100644
index 00000000..7c19ec7d
--- /dev/null
+++ b/app/src/page_helpers/RewardsTradingPageHelper/index.tsx
@@ -0,0 +1,226 @@
+import Button from '@lyra/ui/components/Button'
+import Card from '@lyra/ui/components/Card'
+import CardBody from '@lyra/ui/components/Card/CardBody'
+import CardSection from '@lyra/ui/components/Card/CardSection'
+import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import Text from '@lyra/ui/components/Text'
+import Countdown from '@lyra/ui/components/Text/CountdownText'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import formatDate from '@lyra/ui/utils/formatDate'
+import formatPercentage from '@lyra/ui/utils/formatPercentage'
+import formatUSD from '@lyra/ui/utils/formatUSD'
+import { AccountRewardEpoch, GlobalRewardEpoch, Network } from '@lyrafinance/lyra-js'
+import React, { useMemo, useState } from 'react'
+import { useEffect } from 'react'
+
+import LabelItem from '@/app/components/common/LabelItem'
+import RewardTokenAmounts from '@/app/components/rewards/RewardTokenAmounts'
+import { TradingFeeRebateTable } from '@/app/components/rewards/TradingFeeRebateTable'
+import { CLAIMABLE_REWARDS_DELAY } from '@/app/constants/rewards'
+import ConnectWalletButton from '@/app/containers/common/ConnectWalletButton'
+import ClaimModal from '@/app/containers/rewards/ClaimModal'
+import ClaimModalButton from '@/app/containers/rewards/ClaimModalButton'
+import RewardPageHeader from '@/app/containers/rewards/RewardsPageHeader'
+import TradingRebateBoostModal from '@/app/containers/rewards/TradingRebateBoostModal'
+import useNetwork from '@/app/hooks/account/useNetwork'
+import useWalletAccount from '@/app/hooks/account/useWalletAccount'
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+import getNetworkDisplayName from '@/app/utils/getNetworkDisplayName'
+
+import Page from '../common/Page'
+import PageGrid from '../common/Page/PageGrid'
+
+const CTA_BUTTON_WIDTH = 160
+
+type Props = {
+ latestRewardEpoch: LatestRewardEpoch
+ accountRewardEpochs: AccountRewardEpoch[]
+ globalRewardEpochs: GlobalRewardEpoch[]
+}
+
+const RewardsTradingPageHelper = ({ latestRewardEpoch, accountRewardEpochs, globalRewardEpochs }: Props) => {
+ const isMobile = useIsMobile()
+ const network = useNetwork()
+ const [isClaimModalOpen, setIsClaimModalOpen] = useState(false)
+ const [isStakeModalOpen, setIsStakeModalOpen] = useState(false)
+ const account = useWalletAccount()
+ const { global: latestGlobalRewardEpoch, account: latestAccountRewardEpoch } = latestRewardEpoch
+ const effectiveRebate =
+ latestAccountRewardEpoch?.tradingFeeRebate ?? latestGlobalRewardEpoch.tradingFeeRebateTiers[0].feeRebate
+ const pendingRewards = latestAccountRewardEpoch?.tradingRewards.length
+ ? latestAccountRewardEpoch.tradingRewards
+ : latestGlobalRewardEpoch.tradingRewards(0, 0)
+ const globalRewardEpochsSorted = useMemo(
+ () => globalRewardEpochs.sort((a, b) => b.startTimestamp - a.startTimestamp),
+ [globalRewardEpochs]
+ )
+ const claimableRewards = latestAccountRewardEpoch?.claimableRewards.tradingRewards.length
+ ? latestAccountRewardEpoch?.claimableRewards.tradingRewards
+ : latestGlobalRewardEpoch.tradingRewards(0, 0)
+
+ useEffect(() => {
+ window.scrollTo(0, 0)
+ }, [])
+
+ return (
+ : null} noHeaderPadding>
+
+ {isMobile ? : null}
+
+ Trading Rewards
+
+ · {getNetworkDisplayName(latestGlobalRewardEpoch.lyra.network)}
+
+
+
+
+ Overview
+
+ This program allows traders to earn back part of their fees as LYRA
+ {network === Network.Optimism ? ' and OP' : ''} tokens every two weeks. Traders can stake LYRA to boost
+ fee rebates.
+
+
+
+
+
+
+ Your Rewards
+
+ · {formatDate(latestRewardEpoch.global.startTimestamp, true)} -{' '}
+ {formatDate(latestRewardEpoch.global.endTimestamp, true)}
+
+
+ {account ? (
+ <>
+
+
+
+
+
+ }
+ />
+ }
+ valueColor="primaryText"
+ />
+
+ }
+ />
+
+ setIsClaimModalOpen(true)}
+ accountRewardEpoch={latestAccountRewardEpoch}
+ minWidth={CTA_BUTTON_WIDTH}
+ />
+
+ >
+ ) : (
+
+
+
+ )}
+
+
+
+
+ Fee Tiers
+
+
+
+ setIsStakeModalOpen(true)}
+ />
+
+
+
+
+ {account ? (
+
+
+
+ History
+
+
+ Epoch
+ Your Fees
+
+ Your Rewards
+
+
+ {globalRewardEpochsSorted.map(globalRewardEpoch => {
+ const accountEpoch = accountRewardEpochs.find(
+ accountRewardEpoch => accountRewardEpoch.globalEpoch.id === globalRewardEpoch.id
+ )
+ return (
+
+
+ {formatDate(globalRewardEpoch.startTimestamp, true)} -{' '}
+ {formatDate(globalRewardEpoch.endTimestamp, true)}
+
+ {accountEpoch ? formatUSD(accountEpoch.tradingFees) : formatUSD(0)}
+
+
+ )
+ })}
+
+
+ ) : null}
+
+ {latestAccountRewardEpoch ? (
+ setIsClaimModalOpen(false)}
+ />
+ ) : null}
+ setIsStakeModalOpen(false)}
+ globalRewardEpoch={latestGlobalRewardEpoch}
+ accountRewardEpoch={latestAccountRewardEpoch}
+ />
+
+ )
+}
+
+export default RewardsTradingPageHelper
diff --git a/app/src/page_helpers/RewardsVaultsPageHelper/index.tsx b/app/src/page_helpers/RewardsVaultsPageHelper/index.tsx
new file mode 100644
index 00000000..e81e63ed
--- /dev/null
+++ b/app/src/page_helpers/RewardsVaultsPageHelper/index.tsx
@@ -0,0 +1,245 @@
+import Button from '@lyra/ui/components/Button'
+import Card from '@lyra/ui/components/Card'
+import CardBody from '@lyra/ui/components/Card/CardBody'
+import CardSection from '@lyra/ui/components/Card/CardSection'
+import CardSeparator from '@lyra/ui/components/Card/CardSeparator'
+import Flex from '@lyra/ui/components/Flex'
+import Grid from '@lyra/ui/components/Grid'
+import { IconType } from '@lyra/ui/components/Icon'
+import Text from '@lyra/ui/components/Text'
+import Countdown from '@lyra/ui/components/Text/CountdownText'
+import useIsMobile from '@lyra/ui/hooks/useIsMobile'
+import formatDate from '@lyra/ui/utils/formatDate'
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import formatTruncatedUSD from '@lyra/ui/utils/formatTruncatedUSD'
+import formatUSD from '@lyra/ui/utils/formatUSD'
+import { AccountRewardEpoch, GlobalRewardEpoch, Market } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useMemo } from 'react'
+import { useState } from 'react'
+import { useEffect } from 'react'
+
+import LabelItem from '@/app/components/common/LabelItem'
+import RewardTokenAmounts from '@/app/components/rewards/RewardTokenAmounts'
+import { UNIT, ZERO_BN } from '@/app/constants/bn'
+import { REWARDS_HISTORY_GRID_COLUMN_TEMPLATE } from '@/app/constants/layout'
+import { PageId } from '@/app/constants/pages'
+import { CLAIMABLE_REWARDS_DELAY } from '@/app/constants/rewards'
+import ConnectWalletButton from '@/app/containers/common/ConnectWalletButton'
+import ClaimModal from '@/app/containers/rewards/ClaimModal'
+import ClaimModalButton from '@/app/containers/rewards/ClaimModalButton'
+import RewardPageHeader from '@/app/containers/rewards/RewardsPageHeader'
+import useWalletAccount from '@/app/hooks/account/useWalletAccount'
+import { LatestRewardEpoch } from '@/app/hooks/rewards/useLatestRewardEpoch'
+import formatAPY from '@/app/utils/formatAPY'
+import formatAPYRange from '@/app/utils/formatAPYRange'
+import formatTokenName from '@/app/utils/formatTokenName'
+import getNetworkDisplayName from '@/app/utils/getNetworkDisplayName'
+import getPagePath from '@/app/utils/getPagePath'
+
+import Page from '../common/Page'
+import PageGrid from '../common/Page/PageGrid'
+
+const CTA_BUTTON_WIDTH = 160
+
+type Props = {
+ market: Market
+ latestRewardEpoch: LatestRewardEpoch
+ accountRewardEpochs: AccountRewardEpoch[]
+ globalRewardEpochs: GlobalRewardEpoch[]
+}
+
+const RewardsVaultsPageHelper = ({ market, latestRewardEpoch, accountRewardEpochs, globalRewardEpochs }: Props) => {
+ const isMobile = useIsMobile()
+ const account = useWalletAccount()
+ const [isOpen, setIsOpen] = useState(false)
+ const { global: latestGlobalRewardEpoch, account: latestAccountRewardEpoch } = latestRewardEpoch
+ const vaultApy = latestAccountRewardEpoch?.vaultApy(market.address) ?? []
+ const minApy = latestGlobalRewardEpoch.minVaultApy(market.address)
+ const maxApy = latestGlobalRewardEpoch.maxVaultApy(market.address)
+ const vaultApyMultiplier = latestAccountRewardEpoch?.vaultApyMultiplier(market.address)
+
+ const emptyVaultRewards = useMemo(
+ () => latestGlobalRewardEpoch.totalVaultRewards(market.address).map(t => ({ ...t, amount: 0 })),
+ [latestGlobalRewardEpoch, market]
+ )
+ const pendingRewards = latestAccountRewardEpoch?.vaultRewards(market.address) ?? emptyVaultRewards
+
+ const { claimableVaultRewards, tvl, liquidityTokenBalanceValue } = useMemo(() => {
+ const marketIndex = latestGlobalRewardEpoch.markets.findIndex(m => market.isEqual(m.address))
+ const marketLiquidity = marketIndex >= 0 ? latestGlobalRewardEpoch.marketsLiquidity[marketIndex] : null
+ const liquidityTokenBalance =
+ latestAccountRewardEpoch?.vaultTokenBalances[market.baseToken.symbol].balance ?? ZERO_BN
+ const liquidityTokenBalanceValue = marketLiquidity?.tokenPrice.mul(liquidityTokenBalance).div(UNIT) ?? ZERO_BN
+ return {
+ tvl: marketLiquidity?.tvl ?? ZERO_BN,
+ liquidityTokenBalanceValue,
+ claimableVaultRewards: latestAccountRewardEpoch?.claimableVaultRewards(market.address) ?? emptyVaultRewards,
+ }
+ }, [latestGlobalRewardEpoch, market, latestAccountRewardEpoch, emptyVaultRewards])
+
+ const globalRewardEpochsSorted = useMemo(
+ () => globalRewardEpochs.sort((a, b) => b.startTimestamp - a.startTimestamp),
+ [globalRewardEpochs]
+ )
+
+ useEffect(() => {
+ window.scrollTo(0, 0)
+ }, [])
+
+ return (
+ : null} noHeaderPadding>
+
+ {isMobile ? : null}
+
+ {formatTokenName(market.baseToken)} Vault
+
+ · {getNetworkDisplayName(latestGlobalRewardEpoch.lyra.network)}
+
+
+
+
+ Overview
+
+ This program rewards {formatTokenName(market.baseToken)} vault liquidity providers. Liquidity providers
+ can stake LYRA to boost their rewards.
+
+
+
+
+
+
+
+
+
+ Your Rewards
+
+ · {formatDate(latestRewardEpoch.global.startTimestamp, true)} -{' '}
+ {formatDate(latestRewardEpoch.global.endTimestamp, true)}
+
+
+ {account ? (
+ <>
+
+
+ 1.01 ? `(${formatNumber(vaultApyMultiplier)}x)` : ''
+ }`}
+ valueColor={vaultApy.reduce((total, t) => total + t.amount, 0) > 0 ? 'primaryText' : 'text'}
+ />
+
+ }
+ />
+ }
+ valueColor="primaryText"
+ />
+
+ }
+ />
+
+ setIsOpen(true)}
+ minWidth={CTA_BUTTON_WIDTH}
+ />
+
+
+ >
+ ) : (
+
+
+
+ )}
+
+
+ {account ? (
+
+
+
+ History
+
+
+ Epoch
+ Your Liquidity
+
+ Your Rewards
+
+
+ {globalRewardEpochsSorted.map(globalRewardEpoch => {
+ const accountEpoch = accountRewardEpochs.find(
+ accountRewardEpoch => accountRewardEpoch.globalEpoch.id === globalRewardEpoch.id
+ )
+ const vaultRewards = accountEpoch?.vaultRewards(market.address) ?? []
+ const epochEmptyVaultRewards = globalRewardEpoch
+ .totalVaultRewards(market.address)
+ .map(t => ({ ...t, amount: 0 }))
+ return (
+
+
+ {formatDate(globalRewardEpoch.startTimestamp, true)} -{' '}
+ {formatDate(globalRewardEpoch.endTimestamp, true)}
+
+ {formatUSD(accountEpoch?.vaultTokenBalance(market.address) ?? 0)}
+
+
+ )
+ })}
+
+
+ ) : null}
+ {latestAccountRewardEpoch ? (
+ setIsOpen(false)} />
+ ) : null}
+
+
+ )
+}
+
+export default RewardsVaultsPageHelper
diff --git a/app/src/page_helpers/common/Layout/LayoutDesktopNav.tsx b/app/src/page_helpers/common/Layout/LayoutDesktopNav.tsx
index e80edb73..4c6e0e47 100644
--- a/app/src/page_helpers/common/Layout/LayoutDesktopNav.tsx
+++ b/app/src/page_helpers/common/Layout/LayoutDesktopNav.tsx
@@ -1,3 +1,5 @@
+import DropdownButton from '@lyra/ui/components/Button/DropdownButton'
+import DropdownButtonListItem from '@lyra/ui/components/Button/DropdownButtonListItem'
import DropdownIconButton from '@lyra/ui/components/Button/DropdownIconButton'
import Flex from '@lyra/ui/components/Flex'
import { IconType } from '@lyra/ui/components/Icon'
@@ -6,33 +8,38 @@ import Link from '@lyra/ui/components/Link'
import BaseLink from '@lyra/ui/components/Link/BaseLink'
import useIsDarkMode from '@lyra/ui/hooks/useIsDarkMode'
import React, { useCallback, useState } from 'react'
-import { useLocation } from 'react-router-dom'
import { DESKTOP_HEADER_NAV_HEIGHT, DESKTOP_LAYOUT_LARGE_WIDTH } from '@/app/constants/layout'
+import {
+ KWENTA_DASHBOARD_URL,
+ KWENTA_EXCHANGE_URL,
+ KWENTA_LEADERBOARD_URL,
+ KWENTA_MARKETS_URL,
+} from '@/app/constants/links'
import { LogEvent } from '@/app/constants/logEvents'
import { PageId } from '@/app/constants/pages'
import AccountButton from '@/app/containers/common/AccountButton'
import useNetwork from '@/app/hooks/account/useNetwork'
import getAssetSrc from '@/app/utils/getAssetSrc'
import { getDefaultMarket } from '@/app/utils/getDefaultMarket'
-import { getNavPageFromPath } from '@/app/utils/getNavPageFromPath'
import getPagePath from '@/app/utils/getPagePath'
import logEvent from '@/app/utils/logEvent'
import LayoutMoreDropdownListItems from './LayoutMoreDropdownListItems'
import LayoutPrivacyModal from './LayoutPrivacyModal'
-const SIDE_WIDTH = 420
+const SIDE_WIDTH = 320
+const LOGO_SIDE_WIDTH = 160
export default function LayoutDesktopNav(): JSX.Element {
const [isDarkMode] = useIsDarkMode()
- const { pathname } = useLocation()
- const page = getNavPageFromPath(pathname)
const network = useNetwork()
+ const [isKwentaOpen, setKwentaOpen] = useState(false)
const [isMoreOpen, setIsMoreOpen] = useState(false)
const [isPrivacyOpen, setIsPrivacyOpen] = useState(false)
const onMoreClose = useCallback(() => setIsMoreOpen(false), [])
+ const onKwentaClose = useCallback(() => setKwentaOpen(false), [])
return (
-
-
-
+
+
+
-
- logEvent(LogEvent.NavPortfolioTabClick)}
- >
- Portfolio
+
+
+ Dashboard
logEvent(LogEvent.NavTradeTabClick)}
+ mx={6}
+ mr={8}
+ href={KWENTA_MARKETS_URL}
textVariant="bodyMedium"
variant="secondary"
- color={page !== PageId.Trade ? 'secondaryText' : 'text'}
+ sx={{ display: 'flex', alignItems: 'center' }}
>
- Trade
+ Futures
+
- logEvent(LogEvent.NavVaultsTabClick)}
- color={page !== PageId.Vaults ? 'secondaryText' : 'text'}
+ setKwentaOpen(true)}
+ label="Options"
+ sx={{ width: '100px' }}
>
- Vaults
+ logEvent(LogEvent.NavPortfolioTabClick)}
+ />
+ logEvent(LogEvent.NavTradeTabClick)}
+ />
+ logEvent(LogEvent.NavVaultsTabClick)}
+ />
+ logEvent(LogEvent.NavStakeTabClick)}
+ />
+
+
+ Exchange
- logEvent(LogEvent.NavStakeTabClick)}
- color={page !== PageId.Rewards ? 'secondaryText' : 'text'}
- >
- Rewards
+
+ Leaderboard
diff --git a/app/src/page_helpers/common/Layout/LayoutMobileBottomNav.tsx b/app/src/page_helpers/common/Layout/LayoutMobileBottomNav.tsx
index ef60f903..dcfa3e66 100644
--- a/app/src/page_helpers/common/Layout/LayoutMobileBottomNav.tsx
+++ b/app/src/page_helpers/common/Layout/LayoutMobileBottomNav.tsx
@@ -2,6 +2,7 @@ import DropdownButtonListItem from '@lyra/ui/components/Button/DropdownButtonLis
import Flex from '@lyra/ui/components/Flex'
import Icon, { IconType } from '@lyra/ui/components/Icon'
import Image from '@lyra/ui/components/Image'
+import BaseLink from '@lyra/ui/components/Link/BaseLink'
import List from '@lyra/ui/components/List'
import Modal from '@lyra/ui/components/Modal'
import Token from '@lyra/ui/components/Token'
@@ -10,6 +11,12 @@ import React, { useCallback, useContext, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { MOBILE_FOOTER_HEIGHT } from '@/app/constants/layout'
+import {
+ KWENTA_DASHBOARD_URL,
+ KWENTA_EXCHANGE_URL,
+ KWENTA_LEADERBOARD_URL,
+ KWENTA_MARKETS_URL,
+} from '@/app/constants/links'
import { PageId } from '@/app/constants/pages'
import AccountButton from '@/app/containers/common/AccountButton'
import useNetwork from '@/app/hooks/account/useNetwork'
@@ -25,6 +32,7 @@ export default function LayoutMobileBottomNav(): JSX.Element {
const navigate = useNavigate()
const network = useNetwork()
const [isOpen, setIsOpen] = useState(false)
+ const [isKwentaOpen, setKwentaOpen] = useState(false)
const [isMoreOpen, setIsMoreOpen] = useState(false)
const [isPrivacyOpen, setIsPrivacyOpen] = useState(false)
const onClose = useCallback(() => setIsOpen(false), [])
@@ -82,15 +90,12 @@ export default function LayoutMobileBottomNav(): JSX.Element {
-
+
+
+
{!isMainnet() ? : null}
-
+
{
navigate(getPagePath({ page: PageId.Portfolio }))
@@ -114,11 +119,16 @@ export default function LayoutMobileBottomNav(): JSX.Element {
/>
{
- navigate(getPagePath({ page: PageId.Rewards }))
+ navigate(getPagePath({ page: PageId.RewardsIndex }))
onClose()
}}
label="Rewards"
/>
+ setKwentaOpen(!isKwentaOpen)}
+ label="Futures"
+ rightIcon={ }
+ />
setIsMoreOpen(!isMoreOpen)}
label="More"
@@ -127,6 +137,16 @@ export default function LayoutMobileBottomNav(): JSX.Element {
+ setKwentaOpen(false)}>
+
+
+
+
+
+
+
+
+
setIsMoreOpen(false)}>
diff --git a/app/src/page_helpers/common/Layout/LayoutMoreDropdownListItems.tsx b/app/src/page_helpers/common/Layout/LayoutMoreDropdownListItems.tsx
index c0ab833a..94a05b96 100644
--- a/app/src/page_helpers/common/Layout/LayoutMoreDropdownListItems.tsx
+++ b/app/src/page_helpers/common/Layout/LayoutMoreDropdownListItems.tsx
@@ -1,13 +1,10 @@
import DropdownButtonListItem from '@lyra/ui/components/Button/DropdownButtonListItem'
import { IconType } from '@lyra/ui/components/Icon'
-import useIsDarkMode from '@lyra/ui/hooks/useIsDarkMode'
import React from 'react'
-import { DISCORD_URL, DOCS_URL, GITHUB_URL, STATS_URL, V1_DAPP_URL } from '@/app/constants/links'
+import { DISCORD_URL, DOCS_URL, GITHUB_URL, STATS_URL } from '@/app/constants/links'
import { LogEvent } from '@/app/constants/logEvents'
-import isMainnet from '@/app/utils/isMainnet'
import logEvent from '@/app/utils/logEvent'
-import setIsMainnet from '@/app/utils/setIsTestnet'
type Props = {
onClose: () => void
@@ -15,7 +12,6 @@ type Props = {
}
const LayoutMoreDropdownListItems = ({ onClose, onClickPrivacy }: Props): JSX.Element => {
- const [isDarkMode, setIsDarkMode] = useIsDarkMode()
return (
<>
- {
- logEvent(LogEvent.NavV1Click)
- onClose()
- }}
- target="_blank"
- label="V1"
- icon={IconType.ExternalLink}
- />
- {
- const newIsMainnet = !isMainnet()
- logEvent(LogEvent.NavNetworkToggle, {
- isMainnet: newIsMainnet,
- })
- setIsMainnet(newIsMainnet)
- onClose()
- }}
- icon={isMainnet() ? IconType.ToggleLeft : IconType.ToggleRight}
- />
- {
- logEvent(LogEvent.NavLightModeToggle, {
- type: !isDarkMode ? 'light' : 'dark',
- })
- setIsDarkMode(!isDarkMode)
- onClose()
- }}
- label={isDarkMode ? 'Light Mode' : 'Dark Mode'}
- icon={isDarkMode ? IconType.Sun : IconType.Moon}
- />
{process.env.REACT_APP_RELEASE_TAG ? (
+
{showBackButton ? (
{
+const PageError = ({ errorCode = '404', error }: Props): JSX.Element => {
return (
- 404
+ {errorCode}
{typeof error === 'string' ? {error} : error}
diff --git a/app/src/page_helpers/common/Page/index.tsx b/app/src/page_helpers/common/Page/index.tsx
index df2eba8e..8c3de74b 100644
--- a/app/src/page_helpers/common/Page/index.tsx
+++ b/app/src/page_helpers/common/Page/index.tsx
@@ -14,6 +14,7 @@ type Props = {
desktopHeader?: React.ReactNode
mobileHeader?: React.ReactNode
mobileCollapsedHeader?: string | TextElement | (TextElement | null)[] | null
+ noHeaderPadding?: boolean
}
export default function Page({
@@ -25,6 +26,7 @@ export default function Page({
desktopHeader,
mobileHeader,
mobileCollapsedHeader,
+ noHeaderPadding = false,
}: Props): JSX.Element {
const isMobile = useIsMobile()
return isMobile ? (
@@ -42,6 +44,7 @@ export default function Page({
showBackButton={showBackButton}
header={desktopHeader ?? header}
rightColumn={desktopRightColumn}
+ noHeaderPadding={noHeaderPadding}
>
{children}
diff --git a/app/src/pages/RewardsEthLyraLPPage.tsx b/app/src/pages/RewardsEthLyraLPPage.tsx
new file mode 100644
index 00000000..d1b68219
--- /dev/null
+++ b/app/src/pages/RewardsEthLyraLPPage.tsx
@@ -0,0 +1,29 @@
+import React from 'react'
+
+import withSuspense from '../hooks/data/withSuspense'
+import useRewardsEthLyraLPPageData from '../hooks/rewards/useRewardsEthLyraLPPageData'
+import PageError from '../page_helpers/common/Page/PageError'
+import PageLoading from '../page_helpers/common/Page/PageLoading'
+import RewardsEthLyraLPPageHelper from '../page_helpers/RewardsEthLyraLPPageHelper'
+
+// /rewards
+const RewardsEthLyraLPPage = withSuspense(
+ (): JSX.Element => {
+ const { wethLyraStaking, accountWethLyraStaking, claimableBalance, accountWethLyraStakingL2 } =
+ useRewardsEthLyraLPPageData()
+ if (!wethLyraStaking) {
+ return
+ }
+ return (
+
+ )
+ },
+ () =>
+)
+
+export default RewardsEthLyraLPPage
diff --git a/app/src/pages/RewardsHistoryPage.tsx b/app/src/pages/RewardsHistoryPage.tsx
deleted file mode 100644
index 98fac2f2..00000000
--- a/app/src/pages/RewardsHistoryPage.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import React from 'react'
-
-import RewardsHistoryPageHelper from '@/app/page_helpers/RewardsHistoryPageHelper'
-
-// /rewards/history
-export default function RewardsHistoryPage(): JSX.Element {
- return
-}
diff --git a/app/src/pages/RewardsIndexPage.tsx b/app/src/pages/RewardsIndexPage.tsx
new file mode 100644
index 00000000..c8c1acf2
--- /dev/null
+++ b/app/src/pages/RewardsIndexPage.tsx
@@ -0,0 +1,20 @@
+import React from 'react'
+
+import RewardsIndexPageHelper from '@/app/page_helpers/RewardsIndexPageHelper'
+
+import withSuspense from '../hooks/data/withSuspense'
+import useRewardsPageData from '../hooks/rewards/useRewardsPageData'
+import PageLoading from '../page_helpers/common/Page/PageLoading'
+import filterNulls from '../utils/filterNulls'
+
+// /rewards
+const RewardsIndexPage = withSuspense(
+ (): JSX.Element => {
+ const { epochs, wethLyraStaking } = useRewardsPageData()
+ const latestRewardEpochs = filterNulls(Object.values(epochs).map(epoch => epoch.latestRewardEpoch))
+ return
+ },
+ () =>
+)
+
+export default RewardsIndexPage
diff --git a/app/src/pages/RewardsPage.tsx b/app/src/pages/RewardsPage.tsx
deleted file mode 100644
index 470d0d57..00000000
--- a/app/src/pages/RewardsPage.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import React from 'react'
-
-import RewardsPageHelper from '@/app/page_helpers/RewardsPageHelper'
-
-// /rewards
-export default function RewardsPage(): JSX.Element {
- return
-}
diff --git a/app/src/pages/RewardsShortsPage.tsx b/app/src/pages/RewardsShortsPage.tsx
new file mode 100644
index 00000000..41d0354b
--- /dev/null
+++ b/app/src/pages/RewardsShortsPage.tsx
@@ -0,0 +1,39 @@
+import { Network } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useParams } from 'react-router-dom'
+
+import withSuspense from '../hooks/data/withSuspense'
+import useRewardsShortsPageData from '../hooks/rewards/useRewardsShortsPageData'
+import PageError from '../page_helpers/common/Page/PageError'
+import PageLoading from '../page_helpers/common/Page/PageLoading'
+import RewardsShortsPageHelper from '../page_helpers/RewardsShortsPageHelper'
+import coerce from '../utils/coerce'
+
+// /rewards
+const RewardsShortsPage = withSuspense(
+ (): JSX.Element => {
+ const { network: networkStr } = useParams()
+ const network = coerce(Network, networkStr) ?? null
+
+ const { epochs, collateral } = useRewardsShortsPageData(network)
+ if (!network) {
+ return
+ }
+ const epochData = epochs[network]
+ if (!epochData) {
+ return
+ }
+
+ return (
+
+ )
+ },
+ () =>
+)
+
+export default RewardsShortsPage
diff --git a/app/src/pages/RewardsTradingPage.tsx b/app/src/pages/RewardsTradingPage.tsx
new file mode 100644
index 00000000..53de3a50
--- /dev/null
+++ b/app/src/pages/RewardsTradingPage.tsx
@@ -0,0 +1,37 @@
+import { Network } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useParams } from 'react-router-dom'
+
+import withSuspense from '../hooks/data/withSuspense'
+import useRewardsPageData from '../hooks/rewards/useRewardsPageData'
+import PageError from '../page_helpers/common/Page/PageError'
+import PageLoading from '../page_helpers/common/Page/PageLoading'
+import RewardsTradingPageHelper from '../page_helpers/RewardsTradingPageHelper'
+import coerce from '../utils/coerce'
+
+// /rewards
+const RewardsTradingPage = withSuspense(
+ (): JSX.Element => {
+ const { network: networkStr } = useParams()
+ const network = coerce(Network, networkStr) ?? null
+
+ const { epochs } = useRewardsPageData()
+ const epochData = network ? epochs[network] : null
+ if (!epochData) {
+ return
+ } else if (!network) {
+ return
+ }
+
+ return (
+
+ )
+ },
+ () =>
+)
+
+export default RewardsTradingPage
diff --git a/app/src/pages/RewardsVaultsPage.tsx b/app/src/pages/RewardsVaultsPage.tsx
new file mode 100644
index 00000000..09d4581a
--- /dev/null
+++ b/app/src/pages/RewardsVaultsPage.tsx
@@ -0,0 +1,41 @@
+import { Network } from '@lyrafinance/lyra-js'
+import React from 'react'
+import { useParams } from 'react-router-dom'
+
+import withSuspense from '../hooks/data/withSuspense'
+import useFindMarket from '../hooks/market/useFindMarket'
+import useRewardsPageData from '../hooks/rewards/useRewardsPageData'
+import PageError from '../page_helpers/common/Page/PageError'
+import PageLoading from '../page_helpers/common/Page/PageLoading'
+import RewardsVaultsPageHelper from '../page_helpers/RewardsVaultsPageHelper'
+import coerce from '../utils/coerce'
+
+// /rewards
+const RewardsVaultsPage = withSuspense(
+ (): JSX.Element => {
+ const { network: networkStr, marketAddressOrName = null } = useParams()
+ const network = coerce(Network, networkStr) ?? null
+ const { epochs } = useRewardsPageData()
+
+ const epochData = network ? epochs[network] : null
+
+ const market = useFindMarket(epochData?.latestRewardEpoch?.global.markets ?? [], network, marketAddressOrName)
+ if (!epochData) {
+ return
+ } else if (!market || !network) {
+ return
+ }
+
+ return (
+
+ )
+ },
+ () =>
+)
+
+export default RewardsVaultsPage
diff --git a/app/src/providers/WalletProvider.tsx b/app/src/providers/WalletProvider.tsx
index 22b0b638..e181da85 100644
--- a/app/src/providers/WalletProvider.tsx
+++ b/app/src/providers/WalletProvider.tsx
@@ -127,7 +127,7 @@ export const WalletSeeContext = createContext<{ seeAddress: string | null; remov
// Store selected walletType in local browser storage
export function WalletProvider({ children }: { children: React.ReactNode }): JSX.Element {
- const lyraThemeLight = getThemePreset(false, true)
+ const lyraThemeLight = getThemePreset(false, false)
const text = lyraThemeLight.colors?.text
const secondaryText = lyraThemeLight.colors?.secondaryText
const primaryText = lyraThemeLight.colors?.primaryText
diff --git a/app/src/utils/CachedStaticJsonRpcProvider.ts b/app/src/utils/CachedStaticJsonRpcProvider.ts
index 2557221b..7a85d2c4 100644
--- a/app/src/utils/CachedStaticJsonRpcProvider.ts
+++ b/app/src/utils/CachedStaticJsonRpcProvider.ts
@@ -2,6 +2,8 @@ import { FilterByBlockHash } from '@ethersproject/abstract-provider'
import { Block, BlockTag, Filter, Log, StaticJsonRpcProvider, TransactionRequest } from '@ethersproject/providers'
import { deepCopy, Deferrable, fetchJson } from 'ethers/lib/utils'
+import logError from './logError'
+
function getResult(payload: { error?: { code?: number; data?: any; message?: string }; result?: any }): any {
if (payload.error) {
const error: any = new Error(payload.error.message)
@@ -77,6 +79,7 @@ export default class CachedStaticJsonRpcProvider extends StaticJsonRpcProvider {
return result
} catch (error) {
console.error(error)
+ logError(error, { url, request })
if (i === this.urls.length - 1) {
// Throw on last request failure
this.emit('debug', {
diff --git a/app/src/utils/fetchVault.ts b/app/src/utils/fetchVault.ts
index 4285a2f0..b43e0230 100644
--- a/app/src/utils/fetchVault.ts
+++ b/app/src/utils/fetchVault.ts
@@ -1,39 +1,32 @@
-import { AccountLyraBalances, AccountRewardEpoch, Network, RewardEpochTokenAmount } from '@lyrafinance/lyra-js'
+import { AccountRewardEpoch, Market, Network, RewardEpochTokenAmount } from '@lyrafinance/lyra-js'
-import { ZERO_ADDRESS, ZERO_BN } from '../constants/bn'
+import { ZERO_ADDRESS } from '../constants/bn'
import { Vault } from '../constants/vault'
+import { EMPTY_LYRA_BALANCES } from '../hooks/account/useAccountLyraBalances'
import fromBigNumber from './fromBigNumber'
import getEmptyMarketBalances from './getEmpyMarketBalances'
import getLyraSDK from './getLyraSDK'
+import isMarketEqual from './isMarketEqual'
const EMPTY_APY: RewardEpochTokenAmount[] = []
-const EMPTY_LYRA_BALANCE: AccountLyraBalances = {
- ethereumLyra: ZERO_BN,
- optimismLyra: ZERO_BN,
- optimismOldStkLyra: ZERO_BN,
- ethereumStkLyra: ZERO_BN,
- optimismStkLyra: ZERO_BN,
-}
-
-const fetchVault = async (network: Network, marketAddressOrName: string, walletAddress?: string): Promise => {
+const fetchVault = async (network: Network, market: Market, walletAddress?: string): Promise => {
const lyra = getLyraSDK(network)
- const market = await lyra.market(marketAddressOrName)
const account = lyra.account(walletAddress ?? ZERO_ADDRESS)
- const fetchAccountBalances = async () =>
- account ? account.marketBalances(marketAddressOrName) : getEmptyMarketBalances(ZERO_ADDRESS, market)
-
- const fetchLyraBalances = async () => (account ? account.lyraBalances() : EMPTY_LYRA_BALANCE)
+ const fetchLyraBalances = async () => (account ? account.lyraBalances() : EMPTY_LYRA_BALANCES)
- const [marketLiquidity, globalRewardEpoch, marketBalances, lyraBalances, deposits, withdrawals] = await Promise.all([
+ const [marketLiquidity, globalRewardEpoch, balances, lyraBalances, deposits, withdrawals] = await Promise.all([
market.liquidity(),
lyra.latestGlobalRewardEpoch(),
- fetchAccountBalances(),
+ account.balances(),
fetchLyraBalances(),
- account.liquidityDeposits(marketAddressOrName),
- account.liquidityWithdrawals(marketAddressOrName),
+ lyra.liquidityDeposits(market.address, account.address),
+ lyra.liquidityWithdrawals(market.address, account.address),
])
+ const marketBalances =
+ balances.find(balance => isMarketEqual(balance.market, market.address)) ??
+ getEmptyMarketBalances(ZERO_ADDRESS, market)
const pendingDeposits = deposits.filter(d => d.isPending)
const pendingWithdrawals = withdrawals.filter(w => w.isPending)
@@ -43,10 +36,10 @@ const fetchVault = async (network: Network, marketAddressOrName: string, walletA
accountRewardEpoch = await globalRewardEpoch.accountRewardEpoch(walletAddress)
}
- const minApy = globalRewardEpoch?.minVaultApy(marketAddressOrName) ?? EMPTY_APY
- const maxApy = globalRewardEpoch?.maxVaultApy(marketAddressOrName) ?? EMPTY_APY
- const apy = accountRewardEpoch?.vaultApy(marketAddressOrName) ?? minApy
- const apyMultiplier = accountRewardEpoch?.vaultApyMultiplier(marketAddressOrName) ?? 1
+ const minApy = globalRewardEpoch?.minVaultApy(market.address) ?? EMPTY_APY
+ const maxApy = globalRewardEpoch?.maxVaultApy(market.address) ?? EMPTY_APY
+ const apy = accountRewardEpoch?.vaultApy(market.address) ?? minApy
+ const apyMultiplier = accountRewardEpoch?.vaultApyMultiplier(market.address) ?? 1
const liquidityToken = marketBalances.liquidityToken
diff --git a/app/src/utils/formatAPYRange.ts b/app/src/utils/formatAPYRange.ts
index 220b64ea..9dcdcb06 100644
--- a/app/src/utils/formatAPYRange.ts
+++ b/app/src/utils/formatAPYRange.ts
@@ -5,6 +5,7 @@ import formatTokenName from './formatTokenName'
type FormatAPYRangeOptions = {
showEmptyDash?: boolean
+ showSymbol?: boolean
}
export default function formatAPYRange(
@@ -25,6 +26,12 @@ export default function formatAPYRange(
return options?.showEmptyDash ? '-' : ''
}
+ const showSymbol = options?.showSymbol ?? true
+
+ if (!showSymbol) {
+ return `${formatPercentage(minTotalApy, true)} - ${formatPercentage(maxTotalApy, true)}`
+ }
+
return `${formatPercentage(minTotalApy, true)} - ${formatPercentage(maxTotalApy, true)} ${minApys
.filter(({ amount }) => amount > 0)
.map(token => formatTokenName(token))
diff --git a/app/src/utils/formatRewardTokenAmounts.ts b/app/src/utils/formatRewardTokenAmounts.ts
new file mode 100644
index 00000000..9fd110e6
--- /dev/null
+++ b/app/src/utils/formatRewardTokenAmounts.ts
@@ -0,0 +1,10 @@
+import formatNumber from '@lyra/ui/utils/formatNumber'
+import { RewardEpochTokenAmount } from '@lyrafinance/lyra-js'
+
+const formatRewardTokenAmounts = (rewardTokenAmounts: RewardEpochTokenAmount[]) => {
+ return rewardTokenAmounts
+ .map(rewardTokenAmount => `${formatNumber(rewardTokenAmount.amount)} ${rewardTokenAmount.symbol}`)
+ .join(', ')
+}
+
+export default formatRewardTokenAmounts
diff --git a/app/src/utils/getNavPageFromPath.ts b/app/src/utils/getNavPageFromPath.ts
index 2c0c7cbb..8b2f56fe 100644
--- a/app/src/utils/getNavPageFromPath.ts
+++ b/app/src/utils/getNavPageFromPath.ts
@@ -17,7 +17,7 @@ export const getNavPageFromPath = (path: string): PageId | null => {
} else if (rootPath === 'vaults') {
return PageId.Vaults
} else if (rootPath === 'rewards') {
- return PageId.Rewards
+ return PageId.RewardsIndex
} else {
return null
}
diff --git a/app/src/utils/getPagePath.ts b/app/src/utils/getPagePath.ts
index 52eacb51..45e56855 100644
--- a/app/src/utils/getPagePath.ts
+++ b/app/src/utils/getPagePath.ts
@@ -21,6 +21,18 @@ const getPositionPath = (args: PageArgsMap[PageId.Position]): string => {
return `/position/${args.network}/${args.marketAddressOrName}/${args.positionId}`.toLowerCase()
}
+const getRewardsTradingPath = (args: PageArgsMap[PageId.RewardsTrading]): string => {
+ return `/rewards/trading/${args.network}`
+}
+
+const getRewardsVaultsPath = (args: PageArgsMap[PageId.RewardsVaults]): string => {
+ return `/rewards/vaults/${args.network}/${args.marketAddressOrName}`
+}
+
+const getRewardsShortsPath = (args: PageArgsMap[PageId.RewardsShorts]): string => {
+ return `/rewards/shorts/${args.network}`
+}
+
// TODO: @dappbeast Fix page path typescript in switch statement
export const getRelativePagePath = (args: PagePathArgs): string => {
const page = args.page as PageId
@@ -45,10 +57,16 @@ export const getRelativePagePath = (args: PagePathA
return '/vaults/history'
case PageId.Vaults:
return getVaultPath(args as PageArgsMap[PageId.Vaults])
- case PageId.Rewards:
+ case PageId.RewardsIndex:
return '/rewards'
- case PageId.RewardsHistory:
- return '/rewards/history'
+ case PageId.RewardsTrading:
+ return getRewardsTradingPath(args as PageArgsMap[PageId.RewardsTrading])
+ case PageId.RewardsVaults:
+ return getRewardsVaultsPath(args as PageArgsMap[PageId.RewardsVaults])
+ case PageId.RewardsShorts:
+ return getRewardsShortsPath(args as PageArgsMap[PageId.RewardsShorts])
+ case PageId.RewardsEthLyraLp:
+ return '/rewards/eth-lyra'
case PageId.NotFound:
return '/404'
}
diff --git a/app/src/utils/getStakingYieldPerDay.ts b/app/src/utils/getStakingYieldPerDay.ts
new file mode 100644
index 00000000..d7b5f891
--- /dev/null
+++ b/app/src/utils/getStakingYieldPerDay.ts
@@ -0,0 +1,29 @@
+import { BigNumber } from '@ethersproject/bignumber'
+import { GlobalRewardEpoch, RewardEpochTokenAmount } from '@lyrafinance/lyra-js'
+
+import { UNIT } from '../constants/bn'
+import { SECONDS_IN_DAY } from '../constants/time'
+import fromBigNumber from './fromBigNumber'
+
+type RewardTokenYield = RewardEpochTokenAmount & {
+ yield: number
+}
+
+export default function getStakingYieldPerDay(
+ totalStkLyraSupply: BigNumber,
+ stkLyraBalance: BigNumber,
+ globalRewardEpoch: GlobalRewardEpoch
+): RewardTokenYield[] {
+ const stakedLyraPctShare = totalStkLyraSupply.gt(0)
+ ? fromBigNumber(stkLyraBalance.mul(UNIT).div(totalStkLyraSupply))
+ : 0
+ return globalRewardEpoch.totalStakingRewards.map(rewardToken => {
+ // Token emission per day
+ const totalTokensPerDay =
+ globalRewardEpoch.duration > 0 ? (rewardToken.amount / globalRewardEpoch.duration) * SECONDS_IN_DAY : 0
+ return {
+ ...rewardToken,
+ yield: totalTokensPerDay * stakedLyraPctShare,
+ }
+ })
+}
diff --git a/app/src/utils/isMarketEqual.ts b/app/src/utils/isMarketEqual.ts
new file mode 100644
index 00000000..0a422083
--- /dev/null
+++ b/app/src/utils/isMarketEqual.ts
@@ -0,0 +1,10 @@
+import { getAddress, isAddress } from '@ethersproject/address'
+import { Market } from '@lyrafinance/lyra-js'
+
+export default function isMarketEqual(market: Market, marketAddressOrName: string): boolean {
+ if (isAddress(marketAddressOrName)) {
+ return market.address === getAddress(marketAddressOrName)
+ } else {
+ return market.baseToken.symbol.toLowerCase() === marketAddressOrName.toLowerCase()
+ }
+}
diff --git a/app/src/utils/lyra.ts b/app/src/utils/lyra.ts
index 9bcc1b1d..e7a50550 100644
--- a/app/src/utils/lyra.ts
+++ b/app/src/utils/lyra.ts
@@ -1,4 +1,4 @@
-import Lyra, { Version } from '@lyrafinance/lyra-js'
+import Lyra from '@lyrafinance/lyra-js'
import { NETWORK_CONFIGS } from '../constants/networks'
import CachedStaticJsonRpcProvider from './CachedStaticJsonRpcProvider'
@@ -27,7 +27,8 @@ export const lyraOptimism = new Lyra({
ethereumProvider: mainnetProvider,
})
-export const lyraArbitrum = new Lyra(
- { provider: arbitrumProvider, optimismProvider: optimismProvider, ethereumProvider: mainnetProvider },
- Version.Newport
-)
+export const lyraArbitrum = new Lyra({
+ provider: arbitrumProvider,
+ optimismProvider: optimismProvider,
+ ethereumProvider: mainnetProvider,
+})
diff --git a/app/src/utils/mainnetProvider.ts b/app/src/utils/mainnetProvider.ts
index a415047a..f120515f 100644
--- a/app/src/utils/mainnetProvider.ts
+++ b/app/src/utils/mainnetProvider.ts
@@ -1,6 +1,9 @@
import nullthrows from 'nullthrows'
import CachedStaticJsonRpcProvider from './CachedStaticJsonRpcProvider'
+import filterNulls from './filterNulls'
+
+const REACT_APP_ALCHEMY_ETHEREUM_PROJECT_ID = process.env.REACT_APP_ALCHEMY_ETHEREUM_PROJECT_ID
const INFURA_PROJECT_ID = nullthrows(
process.env.REACT_APP_INFURA_PROJECT_ID,
@@ -13,7 +16,12 @@ export const MAINNET_NETWORK_CONFIG = {
chainId: 1,
network: 'ethereum',
walletRpcUrl: `https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`,
- readRpcUrls: [`https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`],
+ readRpcUrls: filterNulls([
+ `https://mainnet.infura.io/v3/${INFURA_PROJECT_ID}`,
+ REACT_APP_ALCHEMY_ETHEREUM_PROJECT_ID
+ ? `https://eth-mainnet.g.alchemy.com/v2/${REACT_APP_ALCHEMY_ETHEREUM_PROJECT_ID}`
+ : null,
+ ]),
blockExplorerUrl: 'https://etherscan.io/',
iconUrls: [],
}
diff --git a/package.json b/package.json
index 8a08a214..4ff8c8cf 100644
--- a/package.json
+++ b/package.json
@@ -29,5 +29,8 @@
"hooks": {
"pre-commit": "lint-staged"
}
+ },
+ "dependencies": {
+ "concurrently": "^7.6.0"
}
}
diff --git a/sdk/package.json b/sdk/package.json
index 211febbe..b105ec5a 100644
--- a/sdk/package.json
+++ b/sdk/package.json
@@ -26,6 +26,7 @@
"format": "prettier --write \"src/**/*.ts\"",
"lint": "eslint '**/*.ts'",
"clean": "rimraf node_modules dist",
+ "ts-prune": "ts-prune -p tsconfig.esm.json -i 'src/contracts/.*/typechain/|src/index.ts'",
"prepare": "yarn build",
"preversion": "yarn lint",
"version": "yarn format && git add -A src",
@@ -55,6 +56,7 @@
"prettier": "^2.7.0",
"rimraf": "^3.0.2",
"ts-node": "^10.5.0",
+ "ts-prune": "^0.10.3",
"tslib": "^2.4.0",
"typechain": "^8.1.1",
"typescript": "^4.7.3",
diff --git a/sdk/scripts/.env b/sdk/scripts/.env
index dc05ba07..00473828 100644
--- a/sdk/scripts/.env
+++ b/sdk/scripts/.env
@@ -1,6 +1,5 @@
# Copy to .env.local
PRIVATE_KEY=
-API_URL=https://api.lyra.finance
-CHAIN=optimism
-VERSION=avalon
+RPC_URL=
+CHAIN=
diff --git a/sdk/scripts/staking.ts b/sdk/scripts/staking.ts
deleted file mode 100644
index 244e0c62..00000000
--- a/sdk/scripts/staking.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import getLyra from './utils/getLyra'
-
-export default async function staking() {
- const lyra = getLyra()
- const globalStakingData = await lyra.lyraStaking()
- console.log(globalStakingData)
-}
diff --git a/sdk/scripts/utils/getLyra.ts b/sdk/scripts/utils/getLyra.ts
index 67c056d1..48458579 100644
--- a/sdk/scripts/utils/getLyra.ts
+++ b/sdk/scripts/utils/getLyra.ts
@@ -1,25 +1,21 @@
import { StaticJsonRpcProvider } from '@ethersproject/providers'
import { Chain } from '../../src/constants/chain'
-import Lyra, { Version } from '../../src/lyra'
+import Lyra from '../../src/lyra'
import getLyraDeploymentChainId from '../../src/utils/getLyraDeploymentChainId'
import getLyraDeploymentRPCURL from '../../src/utils/getLyraDeploymentRPCURL'
import coerce from './coerce'
export default function getLyra(): Lyra {
const chain = coerce(Chain, process.env.CHAIN ?? '', Chain.Optimism)
- const version = coerce(Version, process.env.VERSION ?? '', Version.Avalon)
const chainId = getLyraDeploymentChainId(chain)
const rpcUrl = process.env.RPC_URL ?? getLyraDeploymentRPCURL(chain)
- const lyra = new Lyra(
- {
- provider: new StaticJsonRpcProvider(rpcUrl, chainId),
- optimismProvider: new StaticJsonRpcProvider(
- getLyraDeploymentRPCURL(Chain.Optimism),
- getLyraDeploymentChainId(Chain.Optimism)
- ),
- },
- version
- )
+ const lyra = new Lyra({
+ provider: new StaticJsonRpcProvider(rpcUrl, chainId),
+ optimismProvider: new StaticJsonRpcProvider(
+ getLyraDeploymentRPCURL(Chain.Optimism),
+ getLyraDeploymentChainId(Chain.Optimism)
+ ),
+ })
return lyra
}
diff --git a/sdk/src/account/fetchAccountWethLyraStaking.ts b/sdk/src/account/fetchAccountWethLyraStaking.ts
new file mode 100644
index 00000000..a42ea46d
--- /dev/null
+++ b/sdk/src/account/fetchAccountWethLyraStaking.ts
@@ -0,0 +1,116 @@
+import { LyraGlobalContractId } from '../constants/contracts'
+import { LyraGlobalContractMap } from '../constants/mappings'
+import Lyra from '../lyra'
+import getGlobalContract from '../utils/getGlobalContract'
+import multicall, { MulticallRequest } from '../utils/multicall'
+import toBigNumber from '../utils/toBigNumber'
+import { AccountWethLyraStaking, AccountWethLyraStakingL2 } from '../weth_lyra_staking'
+
+export async function fetchAccountWethLyraStaking(lyra: Lyra, address: string): Promise {
+ const arrakisPoolL1Contract = getGlobalContract(lyra, LyraGlobalContractId.ArrakisPoolL1, lyra.ethereumProvider)
+ const wethLyraStakingL1RewardsContract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.WethLyraStakingRewardsL1,
+ lyra.ethereumProvider
+ )
+ const {
+ returnData: [unstakedLPTokenBalance, allowance, stakedLPTokenBalance, rewards],
+ } = await multicall<
+ [
+ MulticallRequest,
+ MulticallRequest,
+ MulticallRequest,
+ MulticallRequest
+ ]
+ >(
+ lyra,
+ [
+ {
+ contract: arrakisPoolL1Contract,
+ function: 'balanceOf',
+ args: [address],
+ },
+ {
+ contract: arrakisPoolL1Contract,
+ function: 'allowance',
+ args: [address, wethLyraStakingL1RewardsContract.address],
+ },
+ {
+ contract: wethLyraStakingL1RewardsContract,
+ function: 'balanceOf',
+ args: [address],
+ },
+ {
+ contract: wethLyraStakingL1RewardsContract,
+ function: 'earned',
+ args: [address],
+ },
+ ],
+ lyra.ethereumProvider
+ )
+ return {
+ unstakedLPTokenBalance,
+ allowance,
+ stakedLPTokenBalance,
+ rewards: rewards,
+ }
+}
+
+export async function fetchAccountWethLyraStakingL2(lyra: Lyra, address: string): Promise {
+ const arrakisPoolL2Contract = getGlobalContract(lyra, LyraGlobalContractId.ArrakisPoolL2, lyra.optimismProvider)
+ const wethLyraStakingL2RewardsContract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.WethLyraStakingRewardsL2,
+ lyra.optimismProvider
+ )
+
+ const [
+ {
+ returnData: [unstakedLPTokenBalance, allowance, stakedLPTokenBalance, rewards],
+ },
+ latestGlobalRewardEpoch,
+ ] = await Promise.all([
+ multicall<
+ [
+ MulticallRequest,
+ MulticallRequest,
+ MulticallRequest,
+ MulticallRequest
+ ]
+ >(lyra, [
+ {
+ contract: arrakisPoolL2Contract,
+ function: 'balanceOf',
+ args: [address],
+ },
+ {
+ contract: arrakisPoolL2Contract,
+ function: 'allowance',
+ args: [address, wethLyraStakingL2RewardsContract.address],
+ },
+ {
+ contract: wethLyraStakingL2RewardsContract,
+ function: 'balanceOf',
+ args: [address],
+ },
+ {
+ contract: wethLyraStakingL2RewardsContract,
+ function: 'earned',
+ args: [address],
+ },
+ ]),
+ lyra.latestGlobalRewardEpoch(),
+ ])
+
+ const accountRewardEpoch = await latestGlobalRewardEpoch?.accountRewardEpoch(address)
+ const opRewardsAmount =
+ accountRewardEpoch?.wethLyraStakingL2?.rewards?.find(token => token.symbol.toLowerCase() === 'op')?.amount ?? 0
+ const opRewards = toBigNumber(opRewardsAmount)
+ return {
+ unstakedLPTokenBalance,
+ allowance,
+ stakedLPTokenBalance,
+ rewards,
+ opRewards,
+ }
+}
diff --git a/sdk/src/account/index.ts b/sdk/src/account/index.ts
index 578fc274..f2b83104 100644
--- a/sdk/src/account/index.ts
+++ b/sdk/src/account/index.ts
@@ -1,37 +1,13 @@
-import { getAddress } from '@ethersproject/address'
import { BigNumber } from '@ethersproject/bignumber'
import { PopulatedTransaction } from '@ethersproject/contracts'
-import { MAX_BN, ONE_BN, UNIT, ZERO_BN } from '../constants/bn'
-import {
- Deployment,
- LYRA_ETHEREUM_MAINNET_ADDRESS,
- LYRA_OPTIMISM_KOVAN_ADDRESS,
- LyraContractId,
- LyraGlobalContractId,
- LyraMarketContractId,
- NEW_STAKED_LYRA_ARBITRUM_ADDRESS,
- NEW_STAKED_LYRA_OPTIMISM_ADDRESS,
- OLD_STAKED_LYRA_OPTIMISM_ADDRESS,
- OP_OPTIMISM_MAINNET_ADDRESS,
-} from '../constants/contracts'
-import { Network } from '../constants/network'
-import { LiquidityDeposit } from '../liquidity_deposit'
-import { LiquidityWithdrawal } from '../liquidity_withdrawal'
+import { Deployment, LyraContractId } from '../constants/contracts'
import Lyra from '../lyra'
-import { LyraStake } from '../lyra_stake'
-import { LyraStaking } from '../lyra_staking'
-import { LyraUnstake } from '../lyra_unstake'
import { Market } from '../market'
import buildTxWithGasEstimate from '../utils/buildTxWithGasEstimate'
import fetchLyraBalances from '../utils/fetchLyraBalances'
-import getERC20Contract from '../utils/getERC20Contract'
-import getGlobalContract from '../utils/getGlobalContract'
import getLyraContract from '../utils/getLyraContract'
-import getLyraMarketContract from '../utils/getLyraMarketContract'
-import toBigNumber from '../utils/toBigNumber'
import fetchAccountBalancesAndAllowances from './fetchAccountBalancesAndAllowances'
-import getAverageCostPerLPToken from './getAverageCostPerLPToken'
export type AccountTokenBalance = {
address: string
@@ -61,52 +37,22 @@ export type AccountBalances = {
liquidityToken: AccountLiquidityTokenBalance
}
-export type AccountLyraStaking = {
- staking: LyraStaking
- lyraBalances: AccountLyraBalances
- lyraAllowances: AccountLyraAllowances
- isInUnstakeWindow: boolean
- isInCooldown: boolean
- unstakeWindowStartTimestamp: number | null
- unstakeWindowEndTimestamp: number | null
-}
-
-export type AccountWethLyraStakingL2 = {
- unstakedLPTokenBalance: BigNumber
- stakedLPTokenBalance: BigNumber
- rewards: BigNumber
- opRewards: BigNumber
- allowance: BigNumber
-}
-
-export type AccountWethLyraStaking = {
- unstakedLPTokenBalance: BigNumber
- stakedLPTokenBalance: BigNumber
- rewards: BigNumber
- allowance: BigNumber
-}
-
export type AccountLyraBalances = {
ethereumLyra: BigNumber
optimismLyra: BigNumber
+ arbitrumLyra: BigNumber
optimismOldStkLyra: BigNumber
ethereumStkLyra: BigNumber
optimismStkLyra: BigNumber
-}
-
-export type AccountLyraAllowances = {
- stakingAllowance: BigNumber
+ arbitrumStkLyra: BigNumber
migrationAllowance: BigNumber
+ stakingAllowance: BigNumber
}
export type ClaimableBalanceL2 = {
op: BigNumber
oldStkLyra: BigNumber
newStkLyra: BigNumber
-}
-
-export type ClaimableBalanceL1 = {
- newStkLyra: BigNumber
lyra: BigNumber
}
@@ -145,129 +91,10 @@ export class Account {
return balance
}
- async liquidityUnrealizedPnl(marketAddressOrName: string): Promise<{ pnl: BigNumber; pnlPercent: BigNumber }> {
- const [market, balance, liquidityDeposits, liquidityWithdrawals] = await Promise.all([
- this.lyra.market(marketAddressOrName),
- this.marketBalances(marketAddressOrName),
- this.lyra.liquidityDeposits(marketAddressOrName, this.address),
- this.lyra.liquidityWithdrawals(marketAddressOrName, this.address),
- ])
- if (!balance) {
- throw new Error('No balance found for market')
- }
- const marketLiquidity = await market.liquidity()
- const value = marketLiquidity.tokenPrice.mul(balance.liquidityToken.balance).div(UNIT)
- const avgCostPerToken = getAverageCostPerLPToken(liquidityDeposits, liquidityWithdrawals)
- const avgValue = avgCostPerToken.mul(balance.liquidityToken.balance).div(UNIT)
- const pnl = value.sub(avgValue)
- const pnlPercent = avgCostPerToken.gt(0)
- ? marketLiquidity.tokenPrice.mul(UNIT).div(avgCostPerToken).sub(ONE_BN)
- : ZERO_BN
- return {
- pnl,
- pnlPercent,
- }
- }
-
async lyraBalances(): Promise {
- const lyraBalances = await fetchLyraBalances(this.address)
- return {
- ethereumLyra: lyraBalances.mainnetLYRA,
- optimismLyra: lyraBalances.opLYRA,
- optimismOldStkLyra: lyraBalances.opOldStkLYRA,
- ethereumStkLyra: lyraBalances.mainnetStkLYRA,
- optimismStkLyra: lyraBalances.opStkLYRA,
- }
- }
-
- async lyraStakingAllowance(): Promise {
- if (!this.lyra.ethereumProvider) {
- throw new Error('Ethereum provider required.')
- }
- const lyraTokenContract = getERC20Contract(this.lyra.ethereumProvider, LYRA_ETHEREUM_MAINNET_ADDRESS)
- const lyraStakingModuleContract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.LyraStakingModule,
- this.lyra.ethereumProvider
- )
- return await lyraTokenContract.allowance(this.address, lyraStakingModuleContract.address)
- }
-
- async stkLyraMigrationAllowance(): Promise {
- if (!this.lyra.optimismProvider) {
- throw new Error('Optimism provider required.')
- }
- const oldStakedLyraContract = getERC20Contract(this.lyra.optimismProvider, OLD_STAKED_LYRA_OPTIMISM_ADDRESS)
- const tokenMigratorContract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.TokenMigrator,
- this.lyra.optimismProvider
- )
- return await oldStakedLyraContract.allowance(this.address, tokenMigratorContract.address)
- }
-
- async lyraAllowances(): Promise {
- const [stakingAllowance, migrationAllowance] = await Promise.all([
- this.lyraStakingAllowance(),
- this.stkLyraMigrationAllowance(),
- ])
- return {
- stakingAllowance,
- migrationAllowance,
- }
+ return await fetchLyraBalances(this.address)
}
- async claimableRewardsL1(): Promise {
- const wethLyraStakingRewardsL1Contract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.WethLyraStakingRewardsL1,
- this.lyra.ethereumProvider
- )
- const [stakingRewardsClaimableBalance, wethLyraRewardsL1ClaimableBalance] = await Promise.all([
- LyraStaking.getStakingRewardsBalance(this.lyra, this.address),
- wethLyraStakingRewardsL1Contract.earned(this.address),
- ])
- return {
- newStkLyra: stakingRewardsClaimableBalance,
- lyra: wethLyraRewardsL1ClaimableBalance,
- }
- }
-
- async claimableRewardsL2(): Promise {
- const distributorContract = getGlobalContract(this.lyra, LyraGlobalContractId.MultiDistributor)
- const newStkLyraAddress =
- this.lyra.network === Network.Arbitrum
- ? getAddress(NEW_STAKED_LYRA_ARBITRUM_ADDRESS)
- : getAddress(NEW_STAKED_LYRA_OPTIMISM_ADDRESS)
- const oldStkLyraAddress = getAddress(OLD_STAKED_LYRA_OPTIMISM_ADDRESS)
- const opAddress =
- this.lyra.deployment === Deployment.Mainnet ? OP_OPTIMISM_MAINNET_ADDRESS : LYRA_OPTIMISM_KOVAN_ADDRESS
- const [newStkLyraClaimableBalance, oldStkLyraClaimableBalance, opClaimableBalance] = await Promise.all([
- distributorContract.claimableBalances(this.address, newStkLyraAddress),
- distributorContract.claimableBalances(this.address, oldStkLyraAddress),
- distributorContract.claimableBalances(this.address, opAddress),
- ])
- return {
- newStkLyra: newStkLyraClaimableBalance ?? ZERO_BN,
- oldStkLyra: oldStkLyraClaimableBalance ?? ZERO_BN,
- op: opClaimableBalance ?? ZERO_BN,
- }
- }
-
- async claim(tokenAddresses: string[]): Promise {
- const distributorContract = getGlobalContract(this.lyra, LyraGlobalContractId.MultiDistributor)
- const calldata = distributorContract.interface.encodeFunctionData('claim', [tokenAddresses])
- return await buildTxWithGasEstimate(
- this.lyra.provider,
- this.lyra.provider.network.chainId,
- distributorContract.address,
- this.address,
- calldata
- )
- }
-
- // Approval
-
async drip(): Promise {
if (this.lyra.deployment !== Deployment.Testnet) {
throw new Error('Faucet is only supported on testnet contracts')
@@ -286,280 +113,4 @@ export class Account {
}
return tx
}
-
- async approveDeposit(marketAddressOrName: string, amount: BigNumber): Promise {
- const market = await Market.get(this.lyra, marketAddressOrName)
- const liquidityPoolContract = getLyraMarketContract(
- this.lyra,
- market.contractAddresses,
- this.lyra.version,
- LyraMarketContractId.LiquidityPool
- )
- const erc20 = getERC20Contract(this.lyra.provider, market.quoteToken.address)
- const data = erc20.interface.encodeFunctionData('approve', [liquidityPoolContract.address, amount])
- const tx = await buildTxWithGasEstimate(
- this.lyra.provider,
- this.lyra.provider.network.chainId,
- erc20.address,
- this.address,
- data
- )
- return tx
- }
-
- async deposit(
- marketAddressOrName: string,
- beneficiary: string,
- amountQuote: BigNumber
- ): Promise {
- return await LiquidityDeposit.deposit(this.lyra, marketAddressOrName, beneficiary, amountQuote)
- }
-
- async withdraw(
- marketAddressOrName: string,
- beneficiary: string,
- amountLiquidityTokens: BigNumber
- ): Promise {
- return await LiquidityWithdrawal.withdraw(this.lyra, marketAddressOrName, beneficiary, amountLiquidityTokens)
- }
-
- async lyraStaking(): Promise {
- const lyraStakingModuleContract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.LyraStakingModule,
- this.lyra.ethereumProvider
- )
- const [block, lyraBalances, lyraAllowances, staking, accountCooldownBN] = await Promise.all([
- this.lyra.provider.getBlock('latest'),
- this.lyraBalances(),
- this.lyraAllowances(),
- this.lyra.lyraStaking(),
- lyraStakingModuleContract.stakersCooldowns(this.address),
- ])
- const accountCooldown = accountCooldownBN.toNumber()
- const cooldownStartTimestamp = accountCooldown > 0 ? accountCooldown : null
- const cooldownEndTimestamp = accountCooldown > 0 ? accountCooldown + staking.cooldownPeriod : null
- const unstakeWindowStartTimestamp = cooldownEndTimestamp
- const unstakeWindowEndTimestamp = unstakeWindowStartTimestamp
- ? unstakeWindowStartTimestamp + staking.unstakeWindow
- : null
- const isInUnstakeWindow =
- !!unstakeWindowStartTimestamp &&
- !!unstakeWindowEndTimestamp &&
- block.timestamp >= unstakeWindowStartTimestamp &&
- block.timestamp <= unstakeWindowEndTimestamp
- const isInCooldown =
- !!cooldownStartTimestamp &&
- !!cooldownEndTimestamp &&
- block.timestamp >= cooldownStartTimestamp &&
- block.timestamp <= cooldownEndTimestamp
- return {
- staking,
- lyraBalances,
- lyraAllowances,
- isInUnstakeWindow,
- isInCooldown,
- unstakeWindowStartTimestamp,
- unstakeWindowEndTimestamp,
- }
- }
-
- async wethLyraStakingL2(): Promise {
- const [gelatoPoolContract, wethLyraStakingL2RewardsContract] = await Promise.all([
- getGlobalContract(this.lyra, LyraGlobalContractId.ArrakisPoolL2, this.lyra.optimismProvider),
- getGlobalContract(this.lyra, LyraGlobalContractId.WethLyraStakingRewardsL2, this.lyra.optimismProvider),
- ])
- const [unstakedLPTokenBalance, allowance, stakedLPTokenBalance, rewards, latestGlobalRewardEpoch] =
- await Promise.all([
- gelatoPoolContract.balanceOf(this.address),
- gelatoPoolContract.allowance(this.address, wethLyraStakingL2RewardsContract.address),
- wethLyraStakingL2RewardsContract.balanceOf(this.address),
- wethLyraStakingL2RewardsContract.earned(this.address),
- this.lyra.latestGlobalRewardEpoch(),
- ])
- const accountRewardEpoch = await latestGlobalRewardEpoch?.accountRewardEpoch(this.address)
- const opRewardsAmount =
- accountRewardEpoch?.wethLyraStakingL2?.rewards?.find(token => token.symbol.toLowerCase() === 'op')?.amount ?? 0
- const opRewards = toBigNumber(opRewardsAmount)
- return {
- unstakedLPTokenBalance,
- allowance,
- stakedLPTokenBalance,
- rewards: rewards,
- opRewards: opRewards,
- }
- }
-
- async wethLyraStaking(): Promise {
- const [arrakisPoolL1Contract, wethLyraStakingRewardsL1Contract] = await Promise.all([
- getGlobalContract(this.lyra, LyraGlobalContractId.ArrakisPoolL1, this.lyra.ethereumProvider),
- getGlobalContract(this.lyra, LyraGlobalContractId.WethLyraStakingRewardsL1, this.lyra.ethereumProvider),
- ])
- const [unstakedLPTokenBalance, allowance, stakedLPTokenBalance, rewards] = await Promise.all([
- arrakisPoolL1Contract.balanceOf(this.address),
- arrakisPoolL1Contract.allowance(this.address, wethLyraStakingRewardsL1Contract.address),
- wethLyraStakingRewardsL1Contract.balanceOf(this.address),
- wethLyraStakingRewardsL1Contract.earned(this.address),
- ])
- return {
- unstakedLPTokenBalance,
- allowance,
- stakedLPTokenBalance,
- rewards: rewards,
- }
- }
-
- async approveStake(): Promise {
- return await LyraStake.approve(this.lyra, this.address)
- }
-
- async stake(amount: BigNumber): Promise {
- return await LyraStake.get(this.lyra, this.address, amount)
- }
-
- async requestUnstake(): Promise {
- return await LyraUnstake.requestUnstake(this.lyra, this.address)
- }
-
- async unstake(amount: BigNumber): Promise {
- return await LyraUnstake.get(this.lyra, this.address, amount)
- }
-
- async claimStakedLyraRewards(): Promise {
- return await LyraStaking.claimRewards(this.lyra, this.address)
- }
-
- async claimWethLyraRewards(): Promise {
- const wethLyraStakingL1RewardsContract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.WethLyraStakingRewardsL1,
- this.lyra.ethereumProvider
- )
- const calldata = wethLyraStakingL1RewardsContract.interface.encodeFunctionData('getReward')
- return await buildTxWithGasEstimate(
- this.lyra.ethereumProvider ?? this.lyra.provider,
- 1,
- wethLyraStakingL1RewardsContract.address,
- this.address,
- calldata
- )
- }
-
- async stakeWethLyra(amount: BigNumber): Promise {
- const wethLyraStakingL1RewardsContract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.WethLyraStakingRewardsL1,
- this.lyra.ethereumProvider
- )
- const calldata = wethLyraStakingL1RewardsContract.interface.encodeFunctionData('stake', [amount])
- return await buildTxWithGasEstimate(
- this.lyra.ethereumProvider ?? this.lyra.provider,
- 1,
- wethLyraStakingL1RewardsContract.address,
- this.address,
- calldata
- )
- }
-
- async unstakeWethLyraL2(amount: BigNumber) {
- const wethLyraStakingL2RewardsContract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.WethLyraStakingRewardsL2,
- this.lyra.optimismProvider
- )
- const calldata = wethLyraStakingL2RewardsContract.interface.encodeFunctionData('withdraw', [amount])
- return await buildTxWithGasEstimate(
- this.lyra.optimismProvider ?? this.lyra.provider,
- this.lyra.chainId,
- wethLyraStakingL2RewardsContract.address,
- this.address,
- calldata
- )
- }
-
- async unstakeWethLyra(amount: BigNumber) {
- const wethLyraStakingL1RewardsContract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.WethLyraStakingRewardsL1,
- this.lyra.ethereumProvider
- )
- const calldata = wethLyraStakingL1RewardsContract.interface.encodeFunctionData('withdraw', [amount])
- return await buildTxWithGasEstimate(
- this.lyra.ethereumProvider ?? this.lyra.provider,
- 1,
- wethLyraStakingL1RewardsContract.address,
- this.address,
- calldata
- )
- }
-
- async approveWethLyraTokens(): Promise {
- const arrakisPoolContract = getGlobalContract(this.lyra, LyraGlobalContractId.ArrakisPoolL1)
- const wethLyraStakingContract = getGlobalContract(this.lyra, LyraGlobalContractId.WethLyraStakingRewardsL1)
- const calldata = arrakisPoolContract.interface.encodeFunctionData('approve', [
- wethLyraStakingContract.address,
- MAX_BN,
- ])
- return await buildTxWithGasEstimate(
- this.lyra.ethereumProvider ?? this.lyra.provider,
- 1,
- arrakisPoolContract.address,
- this.address,
- calldata
- )
- }
-
- async claimWethLyraRewardsL2() {
- const wethLyraStakingL2RewardsContract = getGlobalContract(this.lyra, LyraGlobalContractId.WethLyraStakingRewardsL2)
- const calldata = wethLyraStakingL2RewardsContract.interface.encodeFunctionData('getReward')
- return await buildTxWithGasEstimate(
- this.lyra.optimismProvider ?? this.lyra.provider,
- this.lyra.chainId,
- wethLyraStakingL2RewardsContract.address,
- this.address,
- calldata
- )
- }
-
- async approveMigrateStakedLyra(): Promise {
- const tokenMigratorContract = getGlobalContract(this.lyra, LyraGlobalContractId.TokenMigrator)
- const erc20 = getERC20Contract(this.lyra.provider, OLD_STAKED_LYRA_OPTIMISM_ADDRESS)
- const data = erc20.interface.encodeFunctionData('approve', [tokenMigratorContract.address, MAX_BN])
- const tx = await buildTxWithGasEstimate(
- this.lyra.provider,
- this.lyra.provider.network.chainId,
- erc20.address,
- this.address,
- data
- )
- return tx
- }
-
- async migrateStakedLyra(): Promise {
- const tokenMigratorContract = getGlobalContract(
- this.lyra,
- LyraGlobalContractId.TokenMigrator,
- this.lyra.optimismProvider
- )
- const { optimismOldStkLyra } = await this.lyraBalances()
- const data = tokenMigratorContract.interface.encodeFunctionData('swap', [optimismOldStkLyra])
- const tx = await buildTxWithGasEstimate(
- this.lyra.provider,
- this.lyra.provider.network.chainId,
- tokenMigratorContract.address,
- this.address,
- data
- )
- return tx
- }
-
- // Edges
-
- async liquidityDeposits(marketAddressOrName: string): Promise {
- return await this.lyra.liquidityDeposits(marketAddressOrName, this.address)
- }
-
- async liquidityWithdrawals(marketAddressOrName: string): Promise {
- return await this.lyra.liquidityWithdrawals(marketAddressOrName, this.address)
- }
}
diff --git a/sdk/src/account_reward_epoch/index.ts b/sdk/src/account_reward_epoch/index.ts
index 4f73354f..eb7fea30 100644
--- a/sdk/src/account_reward_epoch/index.ts
+++ b/sdk/src/account_reward_epoch/index.ts
@@ -1,21 +1,65 @@
+import { BigNumber, PopulatedTransaction } from 'ethers'
+import { getAddress } from 'ethers/lib/utils'
+
import { AccountBalances, AccountLiquidityTokenBalance, AccountLyraBalances } from '../account'
-import { Deployment, LyraGlobalContractId } from '../constants/contracts'
+import { ZERO_BN } from '../constants/bn'
+import {
+ Deployment,
+ LYRA_ARBITRUM_MAINNET_ADDRESS,
+ LYRA_OPTIMISM_KOVAN_ADDRESS,
+ LYRA_OPTIMISM_MAINNET_ADDRESS,
+ LyraGlobalContractId,
+ NEW_STAKED_LYRA_ARBITRUM_ADDRESS,
+ NEW_STAKED_LYRA_OPTIMISM_ADDRESS,
+ OLD_STAKED_LYRA_OPTIMISM_ADDRESS,
+ OP_OPTIMISM_MAINNET_ADDRESS,
+} from '../constants/contracts'
+import { LyraGlobalContractMap } from '../constants/mappings'
import { Network } from '../constants/network'
-import { ClaimAddedEvent } from '../contracts/common/typechain/MultiDistributor'
import { GlobalRewardEpoch } from '../global_reward_epoch'
import { RewardEpochTokenAmount } from '../global_reward_epoch'
import Lyra from '../lyra'
+import buildTxWithGasEstimate from '../utils/buildTxWithGasEstimate'
import fetchAccountRewardEpochData, {
AccountArrakisRewards,
AccountRewardEpochData,
} from '../utils/fetchAccountRewardEpochData'
+import fetchClaimAddedEvents from '../utils/fetchClaimAddedEvents'
+import fetchClaimEvents from '../utils/fetchClaimEvents'
import findMarketX from '../utils/findMarketX'
import fromBigNumber from '../utils/fromBigNumber'
import getGlobalContract from '../utils/getGlobalContract'
-import parseClaimAddedTags from './parseClaimAddedTags'
+import getUniqueRewardTokenAmounts from '../utils/getUniqueRewardTokenAmounts'
+import multicall, { MulticallRequest } from '../utils/multicall'
+import parseClaimAddedEvents from './parseClaimAddedEvents'
+
+export type ClaimAddedEvent = {
+ amount: BigNumber
+ blockNumber: number
+ claimer: string
+ epochTimestamp: number
+ rewardToken: string
+ tag: string
+ timestamp: number
+}
+
+export type ClaimEvent = {
+ amount: BigNumber
+ blockNumber: number
+ claimer: string
+ rewardToken: string
+ timestamp: number
+}
+
+type ClaimableRewards = {
+ vaultRewards: Record
+ tradingRewards: RewardEpochTokenAmount[]
+ stakingRewards: RewardEpochTokenAmount[]
+ wethLyraRewards: RewardEpochTokenAmount[]
+ totalRewards: RewardEpochTokenAmount[]
+}
export class AccountRewardEpoch {
- private vaultTokenBalances: Record
lyra: Lyra
account: string
globalEpoch: GlobalRewardEpoch
@@ -30,6 +74,11 @@ export class AccountRewardEpoch {
tradingRewards: RewardEpochTokenAmount[]
shortCollateralRewards: RewardEpochTokenAmount[]
wethLyraStakingL2: AccountArrakisRewards
+ claimableRewards: ClaimableRewards
+ totalClaimableVaultRewards: RewardEpochTokenAmount[]
+ lyraBalances: AccountLyraBalances
+ vaultTokenBalances: Record
+
constructor(
lyra: Lyra,
account: string,
@@ -37,17 +86,17 @@ export class AccountRewardEpoch {
globalEpoch: GlobalRewardEpoch,
balances: AccountBalances[],
lyraBalances: AccountLyraBalances,
- claimAddedEvents: ClaimAddedEvent[]
+ claimAddedEvents: ClaimAddedEvent[],
+ claimableRewards: ClaimableRewards
) {
this.lyra = lyra
this.account = account
this.globalEpoch = globalEpoch
this.accountEpoch = accountEpoch
+ this.lyraBalances = lyraBalances
const avgStkLyraBalance =
- this.globalEpoch.progressDays > 0
- ? this.accountEpoch.stakingRewards.stkLyraDays / this.globalEpoch.progressDays
- : 0
- this.stakedLyraBalance = this.globalEpoch.isComplete
+ globalEpoch.progressDays > 0 ? accountEpoch.stakingRewards.stkLyraDays / globalEpoch.progressDays : 0
+ this.stakedLyraBalance = globalEpoch.isComplete
? avgStkLyraBalance
: fromBigNumber(lyraBalances.ethereumStkLyra.add(lyraBalances.optimismStkLyra))
this.vaultTokenBalances = balances.reduce(
@@ -58,13 +107,14 @@ export class AccountRewardEpoch {
{}
)
- this.stakingRewards = this.accountEpoch.stakingRewards.rewards
- this.stakingRewardsUnlockTimestamp = this.accountEpoch.stakingRewards?.rewards?.map(token => {
+ this.stakingRewards = accountEpoch.stakingRewards.rewards
+ this.stakingRewardsUnlockTimestamp = accountEpoch.stakingRewards?.rewards?.map(token => {
return {
...token,
- amount: this.globalEpoch.endTimestamp,
+ amount: globalEpoch.endTimestamp,
}
})
+
const marketVaultRewards = globalEpoch.markets.map(market => this.vaultRewards(market.address)).flat()
const marketVaultRewardsMap: { [tokenAddress: string]: RewardEpochTokenAmount } = {}
marketVaultRewards.forEach(vaultReward => {
@@ -75,54 +125,36 @@ export class AccountRewardEpoch {
}
})
this.totalVaultRewards = Object.values(marketVaultRewardsMap)
- this.tradingFeeRebate = this.globalEpoch.tradingFeeRebate(this.stakedLyraBalance)
- const integratorTradingFees = this.accountEpoch.integratorTradingRewards?.fees ?? 0
- this.tradingFees = integratorTradingFees > 0 ? integratorTradingFees : this.accountEpoch.tradingRewards.fees
- this.tradingRewards = this.globalEpoch.tradingRewards(this.tradingFees, this.stakedLyraBalance)
+ this.tradingFeeRebate = globalEpoch.tradingFeeRebate(this.stakedLyraBalance)
+ const integratorTradingFees = accountEpoch.integratorTradingRewards?.fees ?? 0
+ this.tradingFees = integratorTradingFees > 0 ? integratorTradingFees : accountEpoch.tradingRewards.fees
+ this.tradingRewards = globalEpoch.tradingRewards(this.tradingFees, this.stakedLyraBalance)
const integratorShortCollateralRewardDollars =
this.accountEpoch.integratorTradingRewards?.shortCollateralRewardDollars ?? 0
- this.shortCollateralRewards = this.globalEpoch.shortCollateralRewards(
+ this.shortCollateralRewards = globalEpoch.shortCollateralRewards(
integratorShortCollateralRewardDollars > 0
? integratorShortCollateralRewardDollars
: this.accountEpoch.tradingRewards.shortCollateralRewardDollars
)
- const claimAddedTags = parseClaimAddedTags(claimAddedEvents)
-
- // TODO @dillon: refactor this later
- const lyraTradingRewards =
- this.tradingRewards.find(token => ['lyra', 'stklyra'].includes(token.symbol.toLowerCase()))?.amount ?? 0
- const lyraShortCollateralRewards =
- this.shortCollateralRewards.find(token => ['lyra', 'stklyra'].includes(token.symbol.toLowerCase()))?.amount ?? 0
- const opTradingRewards = this.tradingRewards.find(token => ['op'].includes(token.symbol.toLowerCase()))?.amount ?? 0
- const opShortCollateralRewards =
- this.shortCollateralRewards.find(token => ['op'].includes(token.symbol.toLowerCase()))?.amount ?? 0
- const isTradingPending =
- (lyraTradingRewards + lyraShortCollateralRewards > 0 && !claimAddedTags.tradingRewards.LYRA) ||
- (opTradingRewards + opShortCollateralRewards > 0 && !claimAddedTags.tradingRewards.OP)
-
- // ignore lyra rewards due to 6mo lock
- const isStakingPending = false
-
- const isVaultsPending = globalEpoch.markets.every(market => {
- const vaultRewards = this.vaultRewards(market.address)
- const marketKey = market.baseToken.symbol
- // TODO @dillon - refactor this later
- const lyraVaultRewards =
- vaultRewards.find(token => ['lyra', 'stklyra'].includes(token.symbol.toLowerCase()))?.amount ?? 0
- const opVaultRewards = vaultRewards.find(token => ['op'].includes(token.symbol.toLowerCase()))?.amount ?? 0
- return (
- (lyraVaultRewards && !claimAddedTags.vaultRewards[marketKey]?.LYRA) ||
- (opVaultRewards && !claimAddedTags.vaultRewards[marketKey]?.OP)
- )
- })
-
- this.isPendingRewards = !this.globalEpoch.isComplete || isTradingPending || isStakingPending || isVaultsPending
-
+ const claimAddedCurrEpoch = claimAddedEvents.filter(ev => ev.epochTimestamp === globalEpoch.startTimestamp)
+ const claimableRewardsThisEpoch = parseClaimAddedEvents(claimAddedCurrEpoch, [], [globalEpoch])
+ const isTradingPending = claimableRewardsThisEpoch.tradingRewards.some(
+ rewardTokenAmount => rewardTokenAmount.amount > 0
+ )
+ const isVaultsPending = Object.values(claimableRewardsThisEpoch.vaultRewards)
+ .flat()
+ .some(rewardTokenAmount => rewardTokenAmount.amount > 0)
+ this.isPendingRewards = !this.globalEpoch.isComplete && (isTradingPending || isVaultsPending)
this.wethLyraStakingL2 = this.accountEpoch.arrakisRewards ?? {
rewards: [],
gUniTokensStaked: 0,
percentShare: 0,
}
+
+ this.claimableRewards = claimableRewards
+ this.totalClaimableVaultRewards = getUniqueRewardTokenAmounts(
+ Object.values(this.claimableRewards.vaultRewards).flat()
+ )
}
// Getters
@@ -131,22 +163,22 @@ export class AccountRewardEpoch {
if (lyra.deployment !== Deployment.Mainnet) {
return []
}
- const distributorContract =
- lyra.network === Network.Optimism
- ? getGlobalContract(lyra, LyraGlobalContractId.MultiDistributor, lyra.optimismProvider)
- : null
- const claimAddedEvents =
- lyra.network === Network.Optimism
- ? (await distributorContract?.queryFilter(
- distributorContract.filters.ClaimAdded(null, address, null, null, null)
- )) ?? []
- : []
- const [accountEpochDatas, globalEpochs, lyraBalances, balances] = await Promise.all([
- fetchAccountRewardEpochData(lyra, address),
- GlobalRewardEpoch.getAll(lyra),
- lyra.account(address).lyraBalances(),
- lyra.account(address).balances(),
- ])
+ const [accountEpochDatas, globalEpochs, lyraBalances, balances, _claimAddedEvents, claimEvents] = await Promise.all(
+ [
+ fetchAccountRewardEpochData(lyra, address),
+ GlobalRewardEpoch.getAll(lyra),
+ lyra.account(address).lyraBalances(),
+ lyra.account(address).balances(),
+ fetchClaimAddedEvents(lyra.chain, address),
+ fetchClaimEvents(lyra.chain, address),
+ ]
+ )
+ // HACK @michaelxuwu - Filter claimAdded mistake
+ const claimAddedEvents = _claimAddedEvents.filter(
+ event => event.rewardToken.toLowerCase() !== '0xCb9f85730f57732fc899fb158164b9Ed60c77D49'.toLowerCase()
+ )
+ // Get claimable rewards across all previous epochs
+ const claimableRewards = parseClaimAddedEvents(claimAddedEvents, claimEvents, globalEpochs)
return accountEpochDatas
.map(accountEpochData => {
const globalEpoch = globalEpochs.find(
@@ -157,10 +189,6 @@ export class AccountRewardEpoch {
if (!globalEpoch) {
throw new Error('Missing corresponding global epoch for account epoch')
}
- // Find claims added for or after epoch
- const epochClaimAddedEvents = claimAddedEvents.filter(
- claimAdded => claimAdded.args.epochTimestamp.toNumber() === globalEpoch.startTimestamp
- )
return new AccountRewardEpoch(
lyra,
address,
@@ -168,7 +196,8 @@ export class AccountRewardEpoch {
globalEpoch,
balances,
lyraBalances,
- epochClaimAddedEvents
+ claimAddedEvents,
+ claimableRewards
)
})
.sort((a, b) => a.globalEpoch.endTimestamp - b.globalEpoch.endTimestamp)
@@ -187,6 +216,69 @@ export class AccountRewardEpoch {
return epoch ?? null
}
+ static async getClaimableBalances(lyra: Lyra, address: string) {
+ const distributorContract = getGlobalContract(lyra, LyraGlobalContractId.MultiDistributor)
+ const newStkLyraAddress =
+ lyra.network === Network.Arbitrum
+ ? getAddress(NEW_STAKED_LYRA_ARBITRUM_ADDRESS)
+ : getAddress(NEW_STAKED_LYRA_OPTIMISM_ADDRESS)
+ const lyraAddress =
+ lyra.network === Network.Arbitrum
+ ? getAddress(LYRA_ARBITRUM_MAINNET_ADDRESS)
+ : getAddress(LYRA_OPTIMISM_MAINNET_ADDRESS)
+ const oldStkLyraAddress = getAddress(OLD_STAKED_LYRA_OPTIMISM_ADDRESS)
+ const opAddress = lyra.deployment === Deployment.Mainnet ? OP_OPTIMISM_MAINNET_ADDRESS : LYRA_OPTIMISM_KOVAN_ADDRESS
+ const {
+ returnData: [newStkLyraClaimableBalance, oldStkLyraClaimableBalance, opClaimableBalance, lyraClaimableBalance],
+ } = await multicall<
+ [
+ MulticallRequest,
+ MulticallRequest,
+ MulticallRequest,
+ MulticallRequest
+ ]
+ >(lyra, [
+ {
+ contract: distributorContract,
+ function: 'claimableBalances',
+ args: [address, newStkLyraAddress],
+ },
+ {
+ contract: distributorContract,
+ function: 'claimableBalances',
+ args: [address, oldStkLyraAddress],
+ },
+ {
+ contract: distributorContract,
+ function: 'claimableBalances',
+ args: [address, opAddress],
+ },
+ {
+ contract: distributorContract,
+ function: 'claimableBalances',
+ args: [address, lyraAddress],
+ },
+ ])
+ return {
+ newStkLyra: newStkLyraClaimableBalance ?? ZERO_BN,
+ oldStkLyra: oldStkLyraClaimableBalance ?? ZERO_BN,
+ op: opClaimableBalance ?? ZERO_BN,
+ lyra: lyraClaimableBalance ?? ZERO_BN,
+ }
+ }
+
+ static async claim(lyra: Lyra, address: string, tokenAddresses: string[]): Promise {
+ const distributorContract = getGlobalContract(lyra, LyraGlobalContractId.MultiDistributor)
+ const calldata = distributorContract.interface.encodeFunctionData('claim', [tokenAddresses])
+ return await buildTxWithGasEstimate(
+ lyra.provider,
+ lyra.provider.network.chainId,
+ distributorContract.address,
+ address,
+ calldata
+ )
+ }
+
// Dynamic Fields
vaultApy(marketAddressOrName: string): RewardEpochTokenAmount[] {
@@ -245,4 +337,14 @@ export class AccountRewardEpoch {
}
})
}
+
+ claimableVaultRewards(marketAddressOrName: string): RewardEpochTokenAmount[] {
+ const market = findMarketX(this.globalEpoch.markets, marketAddressOrName)
+ const marketKey = market.baseToken.symbol
+ const claimableVaultRewards = this.claimableRewards.vaultRewards[marketKey]
+ if (!claimableVaultRewards) {
+ return this.globalEpoch.totalVaultRewards(market.address).map(t => ({ ...t, amount: 0 }))
+ }
+ return claimableVaultRewards
+ }
}
diff --git a/sdk/src/account_reward_epoch/parseClaimAddedEvents.ts b/sdk/src/account_reward_epoch/parseClaimAddedEvents.ts
new file mode 100644
index 00000000..75b8f246
--- /dev/null
+++ b/sdk/src/account_reward_epoch/parseClaimAddedEvents.ts
@@ -0,0 +1,104 @@
+import { ClaimAddedEvent, ClaimEvent, GlobalRewardEpoch, RewardEpochTokenAmount } from '..'
+import fromBigNumber from '../utils/fromBigNumber'
+import getUniqueRewardTokenAmounts from '../utils/getUniqueRewardTokenAmounts'
+
+enum ClaimAddedProgramTags {
+ MMV = 'MMV',
+ // TRADING tag includes fee rebates + short collateral rewards
+ TRADING = 'TRADING',
+ STAKING = 'STAKING',
+ WETHLYRA = 'UNI-STAKING-OP',
+}
+
+export default function parseClaimAddedEvents(
+ claimAddedEvents: ClaimAddedEvent[],
+ claimEvents: ClaimEvent[],
+ globalRewardEpochs: GlobalRewardEpoch[]
+): {
+ vaultRewards: Record
+ tradingRewards: RewardEpochTokenAmount[]
+ stakingRewards: RewardEpochTokenAmount[]
+ wethLyraRewards: RewardEpochTokenAmount[]
+ totalRewards: RewardEpochTokenAmount[]
+} {
+ const vaultRewards: Record = {}
+ let tradingRewards: RewardEpochTokenAmount[] = []
+ let stakingRewards: RewardEpochTokenAmount[] = []
+ let wethLyraRewards: RewardEpochTokenAmount[] = []
+
+ claimAddedEvents.forEach(event => {
+ const tag = event.tag
+ const amount = fromBigNumber(event.amount)
+ const rewardTokenAddress = event.rewardToken
+ const globalRewardEpoch = globalRewardEpochs.find(
+ globalEpoch => globalEpoch.startTimestamp === event.epochTimestamp
+ )
+ const claimed = claimEvents.find(
+ claimEvent => claimEvent.timestamp > event.timestamp && claimEvent.rewardToken === rewardTokenAddress
+ )
+ if (!globalRewardEpoch || claimed) {
+ return
+ }
+ if (tag.startsWith(ClaimAddedProgramTags.MMV)) {
+ const [_program, marketKey] = tag.split('-')
+ const token = globalRewardEpoch.epoch.MMVConfig[marketKey]?.tokens?.find(
+ token => token.address.toLowerCase() === rewardTokenAddress
+ )
+ if (!vaultRewards[marketKey]) {
+ vaultRewards[marketKey] = []
+ }
+ const claimableToken = vaultRewards[marketKey]?.find(t => t.address.toLowerCase() === rewardTokenAddress)
+ if (!claimableToken) {
+ vaultRewards[marketKey] = token ? [...vaultRewards[marketKey], { ...token, amount }] : vaultRewards[marketKey]
+ return
+ }
+ claimableToken.amount += amount
+ } else if (tag.startsWith(ClaimAddedProgramTags.TRADING)) {
+ const token = globalRewardEpoch.epoch.tradingRewardConfig.tokens.find(
+ token => token.address.toLowerCase() === rewardTokenAddress
+ )
+ if (!token) {
+ return
+ }
+ const claimableToken = tradingRewards.find(t => t.address.toLowerCase() === rewardTokenAddress)
+ if (!claimableToken) {
+ tradingRewards = [...tradingRewards, { ...token, amount }]
+ return
+ }
+ claimableToken.amount += amount
+ } else if (tag.startsWith(ClaimAddedProgramTags.STAKING)) {
+ const token = globalRewardEpoch.epoch.stakingRewardConfig.find(
+ token => token.address.toLowerCase() === rewardTokenAddress
+ )
+ if (!token) {
+ return
+ }
+ const claimableToken = stakingRewards.find(t => t.address.toLowerCase() === rewardTokenAddress)
+ if (!claimableToken) {
+ stakingRewards = [...stakingRewards, { ...token, amount }]
+ return
+ }
+ claimableToken.amount += amount
+ } else if (tag.startsWith(ClaimAddedProgramTags.WETHLYRA)) {
+ const token = globalRewardEpoch.epoch.wethLyraStakingRewardConfig?.find(
+ token => token.address.toLowerCase() === rewardTokenAddress
+ )
+ if (!token) {
+ return
+ }
+ const claimableToken = wethLyraRewards.find(t => t.address.toLowerCase() === rewardTokenAddress)
+ if (!claimableToken) {
+ wethLyraRewards = [...wethLyraRewards, { ...token, amount }]
+ return
+ }
+ claimableToken.amount += amount
+ }
+ })
+ const totalRewards = getUniqueRewardTokenAmounts([
+ ...Object.values(vaultRewards).flat(),
+ ...tradingRewards,
+ ...wethLyraRewards,
+ ])
+
+ return { vaultRewards, tradingRewards, stakingRewards, wethLyraRewards, totalRewards }
+}
diff --git a/sdk/src/account_reward_epoch/parseClaimAddedTags.ts b/sdk/src/account_reward_epoch/parseClaimAddedTags.ts
deleted file mode 100644
index 4619351c..00000000
--- a/sdk/src/account_reward_epoch/parseClaimAddedTags.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { ClaimAddedEvent } from '../contracts/common/typechain/MultiDistributor'
-
-enum ClaimAddedProgramTags {
- MMV = 'MMV',
- TRADING = 'TRADING',
- STAKING = 'STAKING',
-}
-enum ClaimAddedRewardTags {
- OP = 'OP',
- LYRA = 'LYRA',
-}
-
-export default function parseClaimAddedTags(claimAddedEvents: ClaimAddedEvent[]): {
- vaultRewards: Record>
- tradingRewards: Record
- stakingRewards: Record
-} {
- const vaultRewards: Record> = {}
- const tradingRewards: Record = { LYRA: false, OP: false }
- const stakingRewards: Record = { LYRA: false, OP: false }
-
- claimAddedEvents.map(event => {
- const tag = event.args.tag
- if (tag.startsWith(ClaimAddedProgramTags.MMV)) {
- const [_tag, marketKey, token] = tag.split('-')
- if (!vaultRewards[marketKey]) {
- vaultRewards[marketKey] = { LYRA: false, OP: false }
- }
- vaultRewards[marketKey][token as ClaimAddedRewardTags] = true
- } else if (tag.startsWith(ClaimAddedProgramTags.TRADING)) {
- const [_tag, token] = tag.split('-')
- tradingRewards[token as ClaimAddedRewardTags] = true
- } else if (tag.startsWith(ClaimAddedProgramTags.STAKING)) {
- const [_tag, token] = tag.split('-')
- stakingRewards[token as ClaimAddedRewardTags] = true
- }
- })
-
- return { vaultRewards, tradingRewards, stakingRewards }
-}
diff --git a/sdk/src/constants/blocks.ts b/sdk/src/constants/blocks.ts
deleted file mode 100644
index 7177117a..00000000
--- a/sdk/src/constants/blocks.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export type PartialBlock = {
- number: number
- timestamp: number
-}
diff --git a/sdk/src/constants/links.ts b/sdk/src/constants/links.ts
index 92d165b8..2a7fa81b 100644
--- a/sdk/src/constants/links.ts
+++ b/sdk/src/constants/links.ts
@@ -1 +1 @@
-export const LYRA_API_URL = 'https://api.lyra.finance'
+export const LYRA_API_URL = process.env.NODE_ENV === 'production' ? 'https://api.lyra.finance' : 'http://localhost:3002'
diff --git a/sdk/src/constants/queries.ts b/sdk/src/constants/queries.ts
index 463eecd6..ad3c3c7b 100644
--- a/sdk/src/constants/queries.ts
+++ b/sdk/src/constants/queries.ts
@@ -211,6 +211,8 @@ export const MARKET_VOLUME_AND_FEES_SNAPSHOT_FRAGMENT = `
liquidatorFees
smLiquidationFees
lpLiquidationFees
+ totalShortPutOpenInterestUSD
+ totalShortCallOpenInterestUSD
`
export const MARKET_PENDING_LIQUIDITY_SNAPSHOT_FRAGMENT = `
@@ -368,6 +370,24 @@ export const LIQUIDITY_WITHDRAWAL_FRAGMENT = `
}
`
+export const CLAIM_ADDED_FRAGMENT = `
+ amount
+ blockNumber
+ claimer
+ epochTimestamp
+ rewardToken
+ tag
+ timestamp
+`
+
+export const CLAIM_FRAGMENT = `
+ amount
+ blockNumber
+ claimer
+ rewardToken
+ timestamp
+`
+
export type TradeQueryResult = {
timestamp: number
blockNumber: number
@@ -565,6 +585,8 @@ export type MarketVolumeAndFeesSnapshotQueryResult = {
liquidatorFees: string
smLiquidationFees: string
lpLiquidationFees: string
+ totalShortPutOpenInterestUSD: string
+ totalShortCallOpenInterestUSD: string
}
export type MarketPendingLiquiditySnapshotQueryResult = {
@@ -668,3 +690,21 @@ export type LiquidityWithdrawalQueryResult = {
transactionHash: string
}[]
}
+
+export type ClaimAddedQueryResult = {
+ amount: string
+ blockNumber: number
+ claimer: string
+ epochTimestamp: string
+ rewardToken: string
+ tag: string
+ timestamp: number
+}
+
+export type ClaimQueryResult = {
+ amount: string
+ blockNumber: number
+ claimer: string
+ rewardToken: string
+ timestamp: number
+}
diff --git a/sdk/src/contracts/common/abis/MultiDistributor.json b/sdk/src/contracts/common/abis/MultiDistributor.json
index 07e0597f..3fa317fa 100644
--- a/sdk/src/contracts/common/abis/MultiDistributor.json
+++ b/sdk/src/contracts/common/abis/MultiDistributor.json
@@ -36,6 +36,31 @@
"name": "ClaimAdded",
"type": "event"
},
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "contract IERC20",
+ "name": "rewardToken",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "claimer",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "amount",
+ "type": "uint256"
+ }
+ ],
+ "name": "Claimed",
+ "type": "event"
+ },
{
"inputs": [
{
diff --git a/sdk/src/contracts/common/typechain/MultiDistributor.ts b/sdk/src/contracts/common/typechain/MultiDistributor.ts
index 566c2781..9becfd61 100644
--- a/sdk/src/contracts/common/typechain/MultiDistributor.ts
+++ b/sdk/src/contracts/common/typechain/MultiDistributor.ts
@@ -54,9 +54,11 @@ export interface MultiDistributorInterface extends utils.Interface {
events: {
"ClaimAdded(address,address,uint256,uint256,string)": EventFragment;
+ "Claimed(address,address,uint256)": EventFragment;
};
getEvent(nameOrSignatureOrTopic: "ClaimAdded"): EventFragment;
+ getEvent(nameOrSignatureOrTopic: "Claimed"): EventFragment;
}
export interface ClaimAddedEventObject {
@@ -73,6 +75,18 @@ export type ClaimAddedEvent = TypedEvent<
export type ClaimAddedEventFilter = TypedEventFilter;
+export interface ClaimedEventObject {
+ rewardToken: string;
+ claimer: string;
+ amount: BigNumber;
+}
+export type ClaimedEvent = TypedEvent<
+ [string, string, BigNumber],
+ ClaimedEventObject
+>;
+
+export type ClaimedEventFilter = TypedEventFilter;
+
export interface MultiDistributor extends BaseContract {
connect(signerOrProvider: Signer | Provider | string): this;
attach(addressOrName: string): this;
@@ -151,6 +165,17 @@ export interface MultiDistributor extends BaseContract {
epochTimestamp?: PromiseOrValue | null,
tag?: null
): ClaimAddedEventFilter;
+
+ "Claimed(address,address,uint256)"(
+ rewardToken?: PromiseOrValue | null,
+ claimer?: PromiseOrValue | null,
+ amount?: null
+ ): ClaimedEventFilter;
+ Claimed(
+ rewardToken?: PromiseOrValue | null,
+ claimer?: PromiseOrValue | null,
+ amount?: null
+ ): ClaimedEventFilter;
};
estimateGas: {
diff --git a/sdk/src/contracts/common/typechain/factories/MultiDistributor__factory.ts b/sdk/src/contracts/common/typechain/factories/MultiDistributor__factory.ts
index 6a8f7b23..4a0a27c5 100644
--- a/sdk/src/contracts/common/typechain/factories/MultiDistributor__factory.ts
+++ b/sdk/src/contracts/common/typechain/factories/MultiDistributor__factory.ts
@@ -47,6 +47,31 @@ const _abi = [
name: "ClaimAdded",
type: "event",
},
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: true,
+ internalType: "contract IERC20",
+ name: "rewardToken",
+ type: "address",
+ },
+ {
+ indexed: true,
+ internalType: "address",
+ name: "claimer",
+ type: "address",
+ },
+ {
+ indexed: false,
+ internalType: "uint256",
+ name: "amount",
+ type: "uint256",
+ },
+ ],
+ name: "Claimed",
+ type: "event",
+ },
{
inputs: [
{
diff --git a/sdk/src/global_reward_epoch/getEpochRewardTokenPrices.ts b/sdk/src/global_reward_epoch/getEpochRewardTokenPrices.ts
new file mode 100644
index 00000000..5fad1b19
--- /dev/null
+++ b/sdk/src/global_reward_epoch/getEpochRewardTokenPrices.ts
@@ -0,0 +1,35 @@
+import { GlobalRewardEpochData } from '../utils/fetchGlobalRewardEpochData'
+import { RewardEpochTokenPriceMap, RewardTokenPrices } from '.'
+
+const getEpochRewardTokenPrices = (
+ epoch: GlobalRewardEpochData,
+ prices: RewardTokenPrices
+): RewardEpochTokenPriceMap => {
+ const rewardTokenPriceMap: RewardEpochTokenPriceMap = {}
+ const { globalStakingRewards, globalMMVRewards, globalTradingRewards } = epoch
+ const allRewardTokens = [
+ globalStakingRewards,
+ Object.values(globalMMVRewards).flat(),
+ globalTradingRewards.totalRewards ? globalTradingRewards.totalRewards : [],
+ ].flat()
+ allRewardTokens.forEach(token => {
+ if (['lyra', 'stklyra'].includes(token.symbol.toLowerCase())) {
+ rewardTokenPriceMap[token.address] = {
+ price: prices.lyraPrice,
+ address: token.address,
+ decimals: token.decimals,
+ symbol: token.symbol,
+ }
+ } else if (token.symbol.toLowerCase() === 'op') {
+ rewardTokenPriceMap[token.address] = {
+ price: prices.opPrice,
+ address: token.address,
+ decimals: token.decimals,
+ symbol: token.symbol,
+ }
+ }
+ })
+ return rewardTokenPriceMap
+}
+
+export default getEpochRewardTokenPrices
diff --git a/sdk/src/global_reward_epoch/index.ts b/sdk/src/global_reward_epoch/index.ts
index 156f75df..82e76c61 100644
--- a/sdk/src/global_reward_epoch/index.ts
+++ b/sdk/src/global_reward_epoch/index.ts
@@ -1,17 +1,19 @@
import { Block } from '@ethersproject/providers'
-import { Deployment, Market, MarketLiquiditySnapshot } from '..'
+import { Deployment, Market, MarketLiquiditySnapshot, Network, OP_OPTIMISM_MAINNET_ADDRESS } from '..'
import { AccountRewardEpoch } from '../account_reward_epoch'
import { SECONDS_IN_DAY, SECONDS_IN_WEEK, SECONDS_IN_YEAR } from '../constants/time'
import Lyra from '../lyra'
-import { LyraStaking } from '../lyra_staking'
import fetchGlobalRewardEpochData, { GlobalRewardEpochData } from '../utils/fetchGlobalRewardEpochData'
-import fetchLyraTokenSpotPrice from '../utils/fetchLyraTokenSpotPrice'
-import fetchOpTokenSpotPrice from '../utils/fetchOpTokenSpotPrice'
+import fetchLyraPrice from '../utils/fetchLyraPrice'
+import fetchLyraStakingParams, { LyraStakingParams } from '../utils/fetchLyraStakingParams'
+import fetchTokenSpotPrice from '../utils/fetchTokenSpotPrice'
import findMarketX from '../utils/findMarketX'
import fromBigNumber from '../utils/fromBigNumber'
import getEffectiveLiquidityTokens, { getMinimumStakedLyra } from '../utils/getEffectiveLiquidityTokens'
import getEffectiveTradingFeeRebate from '../utils/getEffectiveTradingFeeRebate'
+import isMarketEqual from '../utils/isMarketEqual'
+import getEpochRewardTokenPrices from './getEpochRewardTokenPrices'
export type GlobalRewardEpochTradingFeeRebateTier = {
stakedLyraCutoff: number
@@ -24,25 +26,31 @@ export type RewardEpochToken = {
decimals: number
}
-export type RewardEpochTokenAmount = {
- address: string
- symbol: string
- decimals: number
+export type RewardEpochTokenAmount = RewardEpochToken & {
amount: number
}
+export type RewardTokenPrices = {
+ lyraPrice: number
+ opPrice: number
+}
+
+export type RewardEpochTokenPriceMap = {
+ [address: string]: RewardEpochToken & { price: number }
+}
+
export type RewardEpochTokenConfig = RewardEpochToken & {
amount: number
}
export class GlobalRewardEpoch {
- private lyra: Lyra
- private epoch: GlobalRewardEpochData
+ lyra: Lyra
+ epoch: GlobalRewardEpochData
id: number
progressDays: number
markets: Market[]
marketsLiquidity: MarketLiquiditySnapshot[]
- staking: LyraStaking
+ stakingParams: LyraStakingParams
blockTimestamp: number
startTimestamp: number
startEarningTimestamp?: number
@@ -55,32 +63,33 @@ export class GlobalRewardEpoch {
totalAverageStakedLyra: number
minTradingFeeRebate: number
maxTradingFeeRebate: number
- stakingApy: RewardEpochTokenAmount[]
+ stakingApy: number
totalStakingRewards: RewardEpochTokenAmount[]
tradingRewardsCap: RewardEpochTokenAmount[]
- prices: RewardEpochTokenAmount[]
+ prices: RewardEpochTokenPriceMap
tradingFeeRebateTiers: GlobalRewardEpochTradingFeeRebateTier[]
wethLyraStakingL2: RewardEpochTokenAmount[]
constructor(
lyra: Lyra,
id: number,
epoch: GlobalRewardEpochData,
- prices: RewardEpochTokenAmount[],
+ prices: RewardTokenPrices,
markets: Market[],
marketsLiquidity: MarketLiquiditySnapshot[],
- staking: LyraStaking,
+ stakingParams: LyraStakingParams,
block: Block
) {
this.lyra = lyra
this.id = id
this.epoch = epoch
- this.prices = prices
- this.tradingFeeRebateTiers = epoch.tradingRewardConfig?.rebateRateTable?.map(tier => {
- return {
- stakedLyraCutoff: tier?.cutoff ?? 0,
- feeRebate: tier?.returnRate ?? 0,
- }
- })
+ this.markets = markets
+ this.stakingParams = stakingParams
+ this.marketsLiquidity = marketsLiquidity
+ this.prices = getEpochRewardTokenPrices(epoch, prices)
+ this.tradingFeeRebateTiers = epoch.tradingRewardConfig?.rebateRateTable?.map(tier => ({
+ stakedLyraCutoff: tier.cutoff,
+ feeRebate: tier.returnRate,
+ }))
this.blockTimestamp = block.timestamp
this.startTimestamp = epoch.startTimestamp
this.lastUpdatedTimestamp = epoch.lastUpdated
@@ -89,58 +98,38 @@ export class GlobalRewardEpoch {
this.startEarningTimestamp = epoch.startEarningTimestamp
this.isCurrent = this.blockTimestamp >= this.startTimestamp && this.blockTimestamp <= this.endTimestamp
this.isComplete = this.blockTimestamp > this.endTimestamp
- this.markets = markets
- this.staking = staking
- this.marketsLiquidity = marketsLiquidity
const durationSeconds = Math.max(0, this.endTimestamp - this.startTimestamp)
- this.duration = durationSeconds
const progressSeconds = durationSeconds - Math.max(0, this.endTimestamp - this.blockTimestamp)
+ this.duration = durationSeconds
this.progressDays = progressSeconds / SECONDS_IN_DAY
this.totalAverageStakedLyra = this.progressDays ? epoch.totalStkLyraDays / this.progressDays : 0
// Staking
- const stkLyraPrice =
- this.prices.find(token => ['lyra', 'stklyra'].includes(token.symbol.toLowerCase()))?.amount ?? 0 // TODO: @dillon refactor later
+ this.totalStakingRewards = this.epoch.stakingRewardConfig
+ const stkLyraPrice = prices.lyraPrice
const stkLyraPerDollar = stkLyraPrice > 0 ? 1 / stkLyraPrice : 0
- const totalStkLyra = this.isComplete ? this.totalAverageStakedLyra : fromBigNumber(staking.totalSupply)
+ const totalStkLyra = this.isComplete ? this.totalAverageStakedLyra : fromBigNumber(stakingParams.totalSupply)
const pctSharePerDollar = totalStkLyra > 0 ? stkLyraPerDollar / totalStkLyra : 0
-
- this.stakingApy = this.epoch.stakingRewardConfig.map(tokenReward => {
- const rewardAmount = tokenReward.amount
- const perDollarPerSecond = durationSeconds > 0 ? (pctSharePerDollar * rewardAmount) / durationSeconds : 0
+ this.stakingApy = this.totalStakingRewards.reduce((totalApy, tokenReward) => {
+ const perDollarPerSecond = durationSeconds > 0 ? (pctSharePerDollar * tokenReward.amount) / durationSeconds : 0
const price = this.findTokenPrice(tokenReward.address)
const apy = perDollarPerSecond * price * SECONDS_IN_YEAR
- return {
- address: tokenReward.address,
- symbol: tokenReward.symbol,
- decimals: tokenReward.decimals,
- amount: apy,
- }
- })
-
- this.totalStakingRewards = this.epoch.stakingRewardConfig.map(tokenReward => {
- return {
- address: tokenReward.address,
- symbol: tokenReward.symbol,
- decimals: tokenReward.decimals,
- amount: tokenReward.amount,
- }
- })
+ return apy + totalApy
+ }, 0)
+ // Trading
this.minTradingFeeRebate = this.tradingFeeRebate(0)
this.maxTradingFeeRebate = this.tradingFeeRebate(totalStkLyra)
-
- this.tradingRewardsCap = this.epoch.tradingRewardConfig.tokens.map(token => {
- return {
- address: token.address,
- symbol: token.symbol,
- decimals: token.decimals,
- amount: token.cap,
- }
- })
-
- this.wethLyraStakingL2 = this.epoch?.wethLyraStakingL2RewardConfig ?? []
+ this.tradingRewardsCap = epoch.tradingRewardConfig.tokens.map(token => ({
+ address: token.address,
+ symbol: token.symbol,
+ decimals: token.decimals,
+ amount: token.cap,
+ }))
+
+ // WETH/LYRA LP
+ this.wethLyraStakingL2 = epoch.wethLyraStakingRewardConfig ?? []
}
// Getters
@@ -149,72 +138,30 @@ export class GlobalRewardEpoch {
if (lyra.deployment !== Deployment.Mainnet) {
return []
}
- const [epochs, lyraPrice, opPrice, markets, staking, block] = await Promise.all([
+
+ const [epochs, lyraPrice, opPrice, stakingParams, markets, block] = await Promise.all([
fetchGlobalRewardEpochData(lyra),
- fetchLyraTokenSpotPrice(lyra),
- fetchOpTokenSpotPrice(lyra), // TODO: @dillon refactor later to map through tokens
+ fetchLyraPrice(),
+ fetchTokenSpotPrice(OP_OPTIMISM_MAINNET_ADDRESS, Network.Optimism),
+ fetchLyraStakingParams(lyra),
lyra.markets(),
- lyra.lyraStaking(),
lyra.provider.getBlock('latest'),
])
-
const marketsLiquidity = await Promise.all(markets.map(market => market.liquidity()))
- // TODO @dillon - come back think of better solution
- const prices: { [address: string]: RewardEpochTokenAmount } = {}
- epochs.forEach(epoch => {
- const stakingRewardTokens: RewardEpochToken[] =
- epoch?.globalStakingRewards.map(reward => {
- return {
- address: reward.address,
- decimals: reward.decimals,
- symbol: reward.symbol,
- }
- }) ?? []
- const mmvRewardTokens: RewardEpochToken[] = Object.values(epoch?.globalMMVRewards)
- .map(rewardTokens => {
- return rewardTokens.map(reward => {
- return {
- address: reward.address,
- decimals: reward.decimals,
- symbol: reward.symbol,
- }
- })
- })
- .flat()
- const tradingRewardTokens: RewardEpochToken[] =
- epoch.globalTradingRewards?.totalRewards?.map(reward => {
- return {
- address: reward.address,
- decimals: reward.decimals,
- symbol: reward.symbol,
- }
- }) ?? []
- const tokens = [...stakingRewardTokens, ...mmvRewardTokens, ...tradingRewardTokens]
- tokens.forEach(token => {
- if (['lyra', 'stklyra'].includes(token.symbol.toLowerCase())) {
- prices[token.address] = {
- amount: lyraPrice,
- address: token.address,
- decimals: token.decimals,
- symbol: token.symbol,
- }
- }
- if (['op'].includes(token.symbol.toLowerCase())) {
- prices[token.address] = {
- amount: opPrice,
- address: token.address,
- decimals: token.decimals,
- symbol: token.symbol,
- }
- }
- })
- })
-
return epochs
.map(
(epoch, idx) =>
- new GlobalRewardEpoch(lyra, idx + 1, epoch, Object.values(prices), markets, marketsLiquidity, staking, block)
+ new GlobalRewardEpoch(
+ lyra,
+ idx + 1,
+ epoch,
+ { lyraPrice, opPrice },
+ markets,
+ marketsLiquidity,
+ stakingParams,
+ block
+ )
)
.sort((a, b) => a.endTimestamp - b.endTimestamp)
}
@@ -242,13 +189,12 @@ export class GlobalRewardEpoch {
vaultApy(
marketAddressOrName: string,
stakedLyraBalance: number,
- _vaultTokenBalance: number
+ vaultTokenBalance: number
): RewardEpochTokenAmount[] {
- const market = findMarketX(this.markets, marketAddressOrName)
+ const marketIdx = this.markets.findIndex(m => isMarketEqual(m, marketAddressOrName))
+ const market = this.markets[marketIdx]
const marketKey = market.baseToken.symbol
- const vaultTokenBalance = _vaultTokenBalance
-
const totalAvgVaultTokens = this.totalAverageVaultTokens(marketAddressOrName)
const mmvConfig = this.epoch.MMVConfig[marketKey]
const scaledStkLyraDays = this.epoch.scaledStkLyraDays[marketKey]
@@ -256,7 +202,6 @@ export class GlobalRewardEpoch {
return []
}
- const x = mmvConfig.x
const totalAvgScaledStkLyra = this.progressDays ? scaledStkLyraDays / this.progressDays : 0
const effectiveLpTokensPerLpToken = getEffectiveLiquidityTokens(
@@ -264,9 +209,8 @@ export class GlobalRewardEpoch {
totalAvgVaultTokens,
stakedLyraBalance,
totalAvgScaledStkLyra,
- x
+ mmvConfig.x
)
-
const totalAvgBoostedVaultTokens = this.totalAverageBoostedVaultTokens(marketAddressOrName)
const boostedPortionOfLiquidity =
totalAvgBoostedVaultTokens > 0 ? effectiveLpTokensPerLpToken / totalAvgBoostedVaultTokens : 0
@@ -277,12 +221,10 @@ export class GlobalRewardEpoch {
const apyMultiplier = basePortionOfLiquidity > 0 ? boostedPortionOfLiquidity / basePortionOfLiquidity : 0
// Calculate total vault token balance, including pending deposits
- const marketIdx = this.markets.findIndex(m => m.address === market.address)
const tokenPrice = fromBigNumber(this.marketsLiquidity[marketIdx].tokenPrice)
const totalQueuedVaultTokens =
tokenPrice > 0 ? fromBigNumber(this.marketsLiquidity[marketIdx].pendingDeposits) / tokenPrice : 0
const totalAvgAndQueuedVaultTokens = totalAvgVaultTokens + totalQueuedVaultTokens
-
const vaultTokensPerDollar = tokenPrice > 0 ? 1 / tokenPrice : 0
const pctSharePerDollar = totalAvgAndQueuedVaultTokens > 0 ? vaultTokensPerDollar / totalAvgAndQueuedVaultTokens : 0
@@ -340,8 +282,7 @@ export class GlobalRewardEpoch {
totalVaultRewards(marketAddressOrName: string): RewardEpochTokenAmount[] {
const market = findMarketX(this.markets, marketAddressOrName)
const marketKey = market.baseToken.symbol
- this.epoch.globalMMVRewards[marketKey]
- return this.epoch.globalMMVRewards[marketKey]
+ return this.epoch.globalMMVRewards[marketKey] ?? []
}
totalAverageVaultTokens(marketAddressOrName: string): number {
@@ -452,7 +393,7 @@ export class GlobalRewardEpoch {
}
findTokenPrice(address: string): number {
- return this.prices.find(tokenPrice => tokenPrice.address.toLowerCase() === address.toLowerCase())?.amount ?? 0
+ return this.prices[address]?.price ?? 0
}
// Edge
diff --git a/sdk/src/index.ts b/sdk/src/index.ts
index db3f6162..6b1901f9 100644
--- a/sdk/src/index.ts
+++ b/sdk/src/index.ts
@@ -13,10 +13,8 @@ export * from './trade_event'
export * from './liquidity_deposit'
export * from './liquidity_withdrawal'
export * from './admin'
-export * from './lyra_staking'
export * from './weth_lyra_staking'
-export * from './lyra_stake'
-export * from './lyra_unstake'
+export * from './lyra_staking'
export * from './global_reward_epoch'
export * from './account_reward_epoch'
export * from './constants/contracts'
diff --git a/sdk/src/liquidity_deposit/index.ts b/sdk/src/liquidity_deposit/index.ts
index 2a96a94d..93c59396 100644
--- a/sdk/src/liquidity_deposit/index.ts
+++ b/sdk/src/liquidity_deposit/index.ts
@@ -1,11 +1,13 @@
import { BigNumber } from '@ethersproject/bignumber'
import { PopulatedTransaction } from '@ethersproject/contracts'
+import { MAX_BN } from '../constants/bn'
import { LyraMarketContractId } from '../constants/contracts'
import Lyra from '../lyra'
import { Market, MarketLiquiditySnapshot } from '../market'
import buildTxWithGasEstimate from '../utils/buildTxWithGasEstimate'
import fetchLiquidityDepositEventDataByOwner from '../utils/fetchLiquidityDepositEventDataByOwner'
+import getERC20Contract from '../utils/getERC20Contract'
import getLiquidityDelayReason from '../utils/getLiquidityDelayReason'
import getLyraMarketContract from '../utils/getLyraMarketContract'
@@ -132,6 +134,20 @@ export class LiquidityDeposit {
// Initiate Deposit
+ static async approve(lyra: Lyra, marketAddressOrName: string, address: string) {
+ const market = await Market.get(lyra, marketAddressOrName)
+ const liquidityPoolContract = getLyraMarketContract(
+ lyra,
+ market.contractAddresses,
+ lyra.version,
+ LyraMarketContractId.LiquidityPool
+ )
+ const erc20 = getERC20Contract(lyra.provider, market.quoteToken.address)
+ const data = erc20.interface.encodeFunctionData('approve', [liquidityPoolContract.address, MAX_BN])
+ const tx = await buildTxWithGasEstimate(lyra.provider, lyra.provider.network.chainId, erc20.address, address, data)
+ return tx
+ }
+
static async deposit(
lyra: Lyra,
marketAddressOrName: string,
diff --git a/sdk/src/lyra.ts b/sdk/src/lyra.ts
index 50157855..d6353a53 100644
--- a/sdk/src/lyra.ts
+++ b/sdk/src/lyra.ts
@@ -15,9 +15,7 @@ import { Network } from './constants/network'
import { GlobalRewardEpoch } from './global_reward_epoch'
import { LiquidityDeposit } from './liquidity_deposit'
import { LiquidityWithdrawal } from './liquidity_withdrawal'
-import { LyraStake } from './lyra_stake'
import { LyraStaking } from './lyra_staking'
-import { LyraUnstake } from './lyra_unstake'
import { Market, MarketContractAddresses, MarketQuotes, MarketTradeOptions } from './market'
import { Option, OptionQuotes } from './option'
import { Position, PositionFilter, PositionLeaderboard, PositionLeaderboardFilter } from './position'
@@ -36,7 +34,8 @@ import getLyraDeploymentForChain from './utils/getLyraDeploymentForChain'
import getLyraDeploymentProvider from './utils/getLyraDeploymentProvider'
import getLyraDeploymentSubgraphURI from './utils/getLyraDeploymentSubgraphURI'
import getNetworkForChain from './utils/getLyraNetworkForChain'
-import { WethLyraStaking } from './weth_lyra_staking'
+import getVersionForChain from './utils/getVersionForChain'
+import { AccountWethLyraStaking, WethLyraStaking } from './weth_lyra_staking'
export type LyraConfig = {
provider: JsonRpcProvider
@@ -63,7 +62,7 @@ export default class Lyra {
deployment: Deployment
network: Network
version: Version
- constructor(config: LyraConfig | Chain | number = Chain.Optimism, version: Version = Version.Avalon) {
+ constructor(config: LyraConfig | Chain | number = Chain.Optimism) {
if (typeof config === 'object') {
// Config
const configObj = config as LyraConfig
@@ -88,10 +87,10 @@ export default class Lyra {
link: new HttpLink({ uri: this.subgraphUri, fetch }),
cache: new InMemoryCache(),
})
- this.version = version
this.chainId = getLyraChainIdForChain(this.chain)
this.deployment = getLyraDeploymentForChain(this.chain)
this.network = getNetworkForChain(this.chain)
+ this.version = getVersionForChain(this.network)
}
// Quote
@@ -242,6 +241,10 @@ export default class Lyra {
return await LiquidityDeposit.getByOwner(this, marketAddressOrName, owner)
}
+ async approveDeposit(marketAddressOrName: string, address: string): Promise {
+ return await LiquidityDeposit.approve(this, marketAddressOrName, address)
+ }
+
async deposit(
beneficiary: string,
marketAddressOrName: string,
@@ -272,21 +275,44 @@ export default class Lyra {
// Rewards
+ async claimRewards(address: string, tokenAddresses: string[]) {
+ return await AccountRewardEpoch.claim(this, address, tokenAddresses)
+ }
+
+ async claimableRewards(address: string) {
+ return await AccountRewardEpoch.getClaimableBalances(this, address)
+ }
+
async lyraStaking(): Promise {
return await LyraStaking.get(this)
}
- async stake(address: string, amount: BigNumber): Promise {
- return await LyraStake.get(this, address, amount)
+ async lyraStakingAccount(address: string) {
+ return await LyraStaking.getByOwner(this, address)
}
- async requestUnstake(address: string): Promise {
- const account = this.account(address)
- return await account.requestUnstake()
+ async approveStaking(address: string) {
+ return await LyraStaking.approve(this, address)
+ }
+
+ async stake(address: string, amount: BigNumber) {
+ return await LyraStaking.stake(this, address, amount)
+ }
+
+ async unstake(address: string, amount: BigNumber) {
+ return await LyraStaking.unstake(this, address, amount)
+ }
+
+ async claimableStakingRewards(address: string) {
+ return LyraStaking.claimableRewards(this, address)
+ }
+
+ async claimStakingRewards(address: string) {
+ return await LyraStaking.claim(this, address)
}
- async unstake(address: string, amount: BigNumber): Promise {
- return await LyraUnstake.get(this, address, amount)
+ async requestUnstake(address: string): Promise {
+ return await LyraStaking.requestUnstake(this, address)
}
async globalRewardEpochs(): Promise {
@@ -304,4 +330,28 @@ export default class Lyra {
async wethLyraStaking(): Promise {
return await WethLyraStaking.get(this)
}
+
+ async wethLyraStakingAccount(address: string): Promise {
+ return await WethLyraStaking.getByOwner(this, address)
+ }
+
+ async approveWethLyraStaking(address: string) {
+ return await WethLyraStaking.approve(this, address)
+ }
+
+ async claimableWethLyraRewards(address: string) {
+ return WethLyraStaking.claimableRewards(this, address)
+ }
+
+ async claimWethLyraRewards(address: string) {
+ return await WethLyraStaking.claim(this, address)
+ }
+
+ async stakeWethLyra(address: string, amount: BigNumber) {
+ return await WethLyraStaking.stake(this, address, amount)
+ }
+
+ async unstakeWethLyra(address: string, amount: BigNumber) {
+ return await WethLyraStaking.unstake(this, address, amount)
+ }
}
diff --git a/sdk/src/lyra_stake/index.ts b/sdk/src/lyra_stake/index.ts
deleted file mode 100644
index 92206952..00000000
--- a/sdk/src/lyra_stake/index.ts
+++ /dev/null
@@ -1,244 +0,0 @@
-import { BigNumber } from '@ethersproject/bignumber'
-import { PopulatedTransaction } from '@ethersproject/contracts'
-
-import { Account, AccountBalances, AccountLiquidityTokenBalance, AccountLyraStaking } from '..'
-import { MAX_BN, ZERO_BN } from '../constants/bn'
-import {
- LYRA_ETHEREUM_KOVAN_ADDRESS,
- LYRA_ETHEREUM_MAINNET_ADDRESS,
- LyraGlobalContractId,
-} from '../constants/contracts'
-import { SECONDS_IN_DAY } from '../constants/time'
-import { GlobalRewardEpoch, RewardEpochTokenAmount } from '../global_reward_epoch'
-import Lyra, { Deployment } from '../lyra'
-import buildTx from '../utils/buildTx'
-import buildTxWithGasEstimate from '../utils/buildTxWithGasEstimate'
-import findMarketX from '../utils/findMarketX'
-import fromBigNumber from '../utils/fromBigNumber'
-import getERC20Contract from '../utils/getERC20Contract'
-import getGlobalContract from '../utils/getGlobalContract'
-import insertTxGasEstimate from '../utils/insertTxGasEstimate'
-
-type StakeData = {
- account: Account
- accountStaking: AccountLyraStaking
- globalEpoch: GlobalRewardEpoch | null
- accountBalances: AccountBalances[]
- amount: BigNumber
-}
-
-export enum StakeDisabledReason {
- InsufficientBalance = 'InsufficientBalance',
- InsufficientAllowance = 'InsufficientAllowance',
- ZeroAmount = 'ZeroAmount',
-}
-
-export class LyraStake {
- lyra: Lyra
- private vaultTokenBalances: Record
- globalEpoch: GlobalRewardEpoch | null
- accountStaking: AccountLyraStaking
- account: Account
- amount: BigNumber
- lyraBalance: BigNumber
- stakedLyraBalance: BigNumber
- allowance: BigNumber
- // Derived
- newStakedLyraBalance: BigNumber
- newStakingYieldPerDay: RewardEpochTokenAmount[]
- stakingYieldPerDay: RewardEpochTokenAmount[]
- tradingFeeRebate: number
- newTradingFeeRebate: number
- disabledReason: StakeDisabledReason | null
- tx: PopulatedTransaction | null
-
- constructor(lyra: Lyra, data: StakeData) {
- this.lyra = lyra
- this.globalEpoch = data.globalEpoch
- this.accountStaking = data.accountStaking
- this.account = data.account
- this.amount = data.amount
- this.lyraBalance = data.accountStaking.lyraBalances.ethereumLyra
- this.stakedLyraBalance = data.accountStaking.lyraBalances.ethereumStkLyra
- this.newStakedLyraBalance = this.stakedLyraBalance.add(this.amount)
- this.allowance = data.accountStaking.lyraAllowances.stakingAllowance
-
- this.vaultTokenBalances = data.accountBalances.reduce(
- (lpTokenBalances, accountBalance) => ({
- ...lpTokenBalances,
- [accountBalance.baseAsset.symbol]: accountBalance.liquidityToken,
- }),
- {}
- )
-
- const totalStakedLyraSupply = fromBigNumber(this.accountStaking.staking.totalSupply)
- const stakedLyraPctShare =
- totalStakedLyraSupply > 0 ? fromBigNumber(this.stakedLyraBalance) / totalStakedLyraSupply : 0
- const newStakedLyraPctShare =
- totalStakedLyraSupply > 0 ? fromBigNumber(this.newStakedLyraBalance) / totalStakedLyraSupply : 0
-
- this.stakingYieldPerDay =
- this.globalEpoch?.totalStakingRewards.map(token => {
- const totalTokensPerDay =
- this.globalEpoch && this.globalEpoch.duration > 0
- ? (token.amount / this.globalEpoch.duration) * SECONDS_IN_DAY
- : 0
- return {
- ...token,
- amount: totalTokensPerDay * stakedLyraPctShare,
- }
- }) ?? []
-
- this.newStakingYieldPerDay =
- this.globalEpoch?.totalStakingRewards.map(token => {
- const totalTokensPerDay =
- this.globalEpoch && this.globalEpoch.duration > 0
- ? (token.amount / this.globalEpoch.duration) * SECONDS_IN_DAY
- : 0
- return {
- ...token,
- amount: totalTokensPerDay * newStakedLyraPctShare,
- }
- }) ?? []
-
- this.tradingFeeRebate = this.globalEpoch?.tradingFeeRebate(fromBigNumber(this.stakedLyraBalance)) ?? 0
- this.newTradingFeeRebate = this.globalEpoch?.tradingFeeRebate(fromBigNumber(this.newStakedLyraBalance)) ?? 0
-
- // Determine disabled reason
- if (this.amount.gt(this.lyraBalance)) {
- this.disabledReason = StakeDisabledReason.InsufficientBalance
- } else if (this.amount.gt(this.allowance)) {
- this.disabledReason = StakeDisabledReason.InsufficientAllowance
- } else if (this.amount.eq(ZERO_BN)) {
- this.disabledReason = StakeDisabledReason.ZeroAmount
- } else {
- this.disabledReason = null
- }
-
- // Build transaction
- if (!this.disabledReason) {
- const lyraStakingModuleProxyContract = getGlobalContract(
- lyra,
- LyraGlobalContractId.LyraStakingModule,
- lyra.ethereumProvider
- )
- const txData = lyraStakingModuleProxyContract.interface.encodeFunctionData('stake', [
- this.account.address,
- this.amount,
- ])
- this.tx = buildTx(
- lyra.ethereumProvider ?? lyra.provider,
- 1,
- lyraStakingModuleProxyContract.address,
- this.account.address,
- txData
- )
- } else {
- this.tx = null
- }
- }
-
- // Getters
-
- static async get(lyra: Lyra, address: string, amount: BigNumber): Promise {
- const account = Account.get(lyra, address)
- const [accountStaking, globalEpoch, balances] = await Promise.all([
- account.lyraStaking(),
- lyra.latestGlobalRewardEpoch(),
- lyra.account(address).balances(),
- ])
- const stake = new LyraStake(lyra, {
- account,
- globalEpoch,
- amount,
- accountStaking,
- accountBalances: balances,
- })
- if (stake?.tx) {
- stake.tx = await insertTxGasEstimate(lyra.ethereumProvider ?? lyra.provider, stake.tx)
- }
- return stake
- }
-
- // Transactions
-
- static async approve(lyra: Lyra, account: string): Promise {
- const proxyContract = getGlobalContract(lyra, LyraGlobalContractId.LyraStakingModule, lyra.ethereumProvider)
-
- const lyraContract = getERC20Contract(
- lyra.ethereumProvider ?? lyra.provider,
- lyra.deployment === Deployment.Mainnet ? LYRA_ETHEREUM_MAINNET_ADDRESS : LYRA_ETHEREUM_KOVAN_ADDRESS
- )
- const data = lyraContract.interface.encodeFunctionData('approve', [proxyContract.address, MAX_BN])
- const tx = await buildTxWithGasEstimate(
- lyra.ethereumProvider ?? lyra.provider,
- 1,
- lyraContract.address,
- account,
- data
- )
- return tx
- }
-
- // Dynamic Fields
-
- vaultApy(marketAddressOrName: string): RewardEpochTokenAmount[] {
- if (!this.globalEpoch) {
- return []
- }
- const market = findMarketX(this.globalEpoch.markets, marketAddressOrName)
- const marketKey = market.baseToken.symbol
- const currStakedLyraBalance = fromBigNumber(this.stakedLyraBalance)
- const currVaultTokenBalance = fromBigNumber(this.vaultTokenBalances[marketKey].balance)
- if (currVaultTokenBalance === 0) {
- return this.globalEpoch.minVaultApy(marketAddressOrName)
- } else {
- return this.globalEpoch.vaultApy(marketAddressOrName, currStakedLyraBalance, currVaultTokenBalance)
- }
- }
-
- vaultApyMultiplier(marketAddressOrName: string): number {
- if (!this.globalEpoch) {
- return 1
- }
- const market = findMarketX(this.globalEpoch.markets, marketAddressOrName)
- const marketKey = market.baseToken.symbol
- const currStakedLyraBalance = fromBigNumber(this.stakedLyraBalance)
- const currVaultTokenBalance = fromBigNumber(this.vaultTokenBalances[marketKey].balance)
- if (currVaultTokenBalance === 0) {
- return 1
- } else {
- return this.globalEpoch.vaultApyMultiplier(marketAddressOrName, currStakedLyraBalance, currVaultTokenBalance)
- }
- }
-
- newVaultApy(marketAddressOrName: string): RewardEpochTokenAmount[] {
- if (!this.globalEpoch) {
- return []
- }
- const market = findMarketX(this.globalEpoch.markets, marketAddressOrName)
- const marketKey = market.baseToken.symbol
- const newStakedLyraBalance = fromBigNumber(this.newStakedLyraBalance)
- const currVaultTokenBalance = fromBigNumber(this.vaultTokenBalances[marketKey].balance)
- if (currVaultTokenBalance === 0) {
- return this.globalEpoch.minVaultApy(marketAddressOrName)
- } else {
- return this.globalEpoch.vaultApy(marketAddressOrName, newStakedLyraBalance, currVaultTokenBalance)
- }
- }
-
- newVaultApyMultiplier(marketAddressOrName: string): number {
- if (!this.globalEpoch) {
- return 1
- }
- const market = findMarketX(this.globalEpoch.markets, marketAddressOrName)
- const marketKey = market.baseToken.symbol
- const newStakedLyraBalance = fromBigNumber(this.newStakedLyraBalance)
- const currVaultTokenBalance = fromBigNumber(this.vaultTokenBalances[marketKey].balance)
- if (currVaultTokenBalance === 0) {
- return 1
- } else {
- return this.globalEpoch.vaultApyMultiplier(marketAddressOrName, newStakedLyraBalance, currVaultTokenBalance)
- }
- }
-}
diff --git a/sdk/src/lyra_staking/getStakingDisabledReason.ts b/sdk/src/lyra_staking/getStakingDisabledReason.ts
new file mode 100644
index 00000000..2d0a022a
--- /dev/null
+++ b/sdk/src/lyra_staking/getStakingDisabledReason.ts
@@ -0,0 +1,48 @@
+import { BigNumber } from 'ethers'
+
+import { ZERO_BN } from '../constants/bn'
+import { StakeDisabledReason, UnstakeDisabledReason } from '.'
+
+const getStakingDisabledReason = ({
+ isUnstake,
+ allowance,
+ amount,
+ lyraBalance,
+ stakedLyraBalance,
+ isInUnstakeWindow,
+}: {
+ isUnstake: boolean
+ allowance: BigNumber
+ amount: BigNumber
+ lyraBalance: BigNumber
+ stakedLyraBalance: BigNumber
+ isInUnstakeWindow: boolean
+}): StakeDisabledReason | UnstakeDisabledReason | null => {
+ let disabledReason
+ if (isUnstake) {
+ // Determine disabled reason
+ if (!isInUnstakeWindow) {
+ disabledReason = UnstakeDisabledReason.NotInUnstakeWindow
+ } else if (amount.gt(stakedLyraBalance)) {
+ disabledReason = UnstakeDisabledReason.InsufficientBalance
+ } else if (amount.eq(ZERO_BN)) {
+ disabledReason = UnstakeDisabledReason.ZeroAmount
+ } else {
+ disabledReason = null
+ }
+ return disabledReason
+ }
+ // Determine disabled reason
+ if (amount.gt(lyraBalance)) {
+ disabledReason = StakeDisabledReason.InsufficientBalance
+ } else if (amount.gt(allowance)) {
+ disabledReason = StakeDisabledReason.InsufficientAllowance
+ } else if (amount.eq(ZERO_BN)) {
+ disabledReason = StakeDisabledReason.ZeroAmount
+ } else {
+ disabledReason = null
+ }
+ return disabledReason
+}
+
+export default getStakingDisabledReason
diff --git a/sdk/src/lyra_staking/index.ts b/sdk/src/lyra_staking/index.ts
index 28456085..29d2b790 100644
--- a/sdk/src/lyra_staking/index.ts
+++ b/sdk/src/lyra_staking/index.ts
@@ -1,132 +1,214 @@
import { BigNumber } from '@ethersproject/bignumber'
import { PopulatedTransaction } from '@ethersproject/contracts'
-import { ZERO_BN } from '../constants/bn'
-import { LyraGlobalContractId } from '../constants/contracts'
-import { SECONDS_IN_YEAR } from '../constants/time'
-import Lyra from '../lyra'
+import { MAX_BN } from '../constants/bn'
+import {
+ LYRA_ETHEREUM_KOVAN_ADDRESS,
+ LYRA_ETHEREUM_MAINNET_ADDRESS,
+ LyraGlobalContractId,
+ OLD_STAKED_LYRA_OPTIMISM_ADDRESS,
+} from '../constants/contracts'
+import { GlobalRewardEpoch } from '../global_reward_epoch'
+import Lyra, { Deployment } from '../lyra'
import buildTxWithGasEstimate from '../utils/buildTxWithGasEstimate'
-import callContractWithMulticall from '../utils/callContractWithMulticall'
-import fetchLyraTokenSpotPrice from '../utils/fetchLyraTokenSpotPrice'
-import fromBigNumber from '../utils/fromBigNumber'
+import fetchLyraStakingParams, { LyraStakingParams } from '../utils/fetchLyraStakingParams'
+import getERC20Contract from '../utils/getERC20Contract'
import getGlobalContract from '../utils/getGlobalContract'
-import toBigNumber from '../utils/toBigNumber'
-type StakingData = {
- cooldownPeriod: number
- unstakeWindow: number
- lyraPrice: number
- totalSupply: BigNumber
+export type AccountLyraStaking = {
+ stakingParams: LyraStakingParams
+ isInUnstakeWindow: boolean
+ isInCooldown: boolean
+ unstakeWindowStartTimestamp: number | null
+ unstakeWindowEndTimestamp: number | null
+}
+
+export enum UnstakeDisabledReason {
+ NotInUnstakeWindow = 'NotInUnstakeWindow',
+ InsufficientBalance = 'InsufficientBalance',
+ ZeroAmount = 'ZeroAmount',
+}
+
+export enum StakeDisabledReason {
+ InsufficientBalance = 'InsufficientBalance',
+ InsufficientAllowance = 'InsufficientAllowance',
+ ZeroAmount = 'ZeroAmount',
}
export class LyraStaking {
lyra: Lyra
+ globalRewardEpoch: GlobalRewardEpoch | null
cooldownPeriod: number
unstakeWindow: number
- lyraPrice: number
totalSupply: BigNumber
- constructor(lyra: Lyra, data: StakingData) {
- // if ([Network.Arbitrum, Network.Optimism].includes(lyra.network)) {
- // throw new Error(`Staking is not supported on ${lyra.network}`)
- // }
- // Data
+
+ constructor(lyra: Lyra, stakingParams: LyraStakingParams, globalRewardEpoch: GlobalRewardEpoch | null) {
this.lyra = lyra
- this.cooldownPeriod = data.cooldownPeriod
- this.unstakeWindow = data.unstakeWindow
- this.totalSupply = data.totalSupply
- this.lyraPrice = data.lyraPrice
+ this.globalRewardEpoch = globalRewardEpoch
+ this.cooldownPeriod = stakingParams.cooldownPeriod
+ this.unstakeWindow = stakingParams.unstakeWindow
+ this.totalSupply = stakingParams.totalSupply
}
// Getters
static async get(lyra: Lyra): Promise {
+ const [stakingParams, globalRewardEpoch] = await Promise.all([
+ fetchLyraStakingParams(lyra),
+ lyra.latestGlobalRewardEpoch(),
+ ])
+ const stake = new LyraStaking(lyra, stakingParams, globalRewardEpoch)
+ return stake
+ }
+
+ static async getByOwner(lyra: Lyra, address: string): Promise {
+ if (!lyra.ethereumProvider || !lyra.optimismProvider) {
+ throw new Error('Ethereum and Optimism provider required.')
+ }
const lyraStakingModuleContract = getGlobalContract(
lyra,
LyraGlobalContractId.LyraStakingModule,
lyra.ethereumProvider
)
- const cooldownPeriodCallData = lyraStakingModuleContract.interface.encodeFunctionData('COOLDOWN_SECONDS')
- const unstakeWindowCallData = lyraStakingModuleContract.interface.encodeFunctionData('UNSTAKE_WINDOW')
- const totalSupplyCallData = lyraStakingModuleContract.interface.encodeFunctionData('totalSupply')
- const [[cooldownPeriod], [unstakeWindow], [totalSupply]] = await callContractWithMulticall<
- [[BigNumber], [BigNumber], [BigNumber]]
- >(
+ const [block, stakingParams, accountCooldownBN] = await Promise.all([
+ lyra.provider.getBlock('latest'),
+ fetchLyraStakingParams(lyra),
+ lyraStakingModuleContract.stakersCooldowns(address),
+ ])
+ const accountCooldown = accountCooldownBN.toNumber()
+ const cooldownStartTimestamp = accountCooldown > 0 ? accountCooldown : null
+ const cooldownEndTimestamp = accountCooldown > 0 ? accountCooldown + stakingParams.cooldownPeriod : null
+ const unstakeWindowStartTimestamp = cooldownEndTimestamp
+ const unstakeWindowEndTimestamp = unstakeWindowStartTimestamp
+ ? unstakeWindowStartTimestamp + stakingParams.unstakeWindow
+ : null
+ const isInUnstakeWindow =
+ !!unstakeWindowStartTimestamp &&
+ !!unstakeWindowEndTimestamp &&
+ block.timestamp >= unstakeWindowStartTimestamp &&
+ block.timestamp <= unstakeWindowEndTimestamp
+ const isInCooldown =
+ !!cooldownStartTimestamp &&
+ !!cooldownEndTimestamp &&
+ block.timestamp >= cooldownStartTimestamp &&
+ block.timestamp <= cooldownEndTimestamp
+ return {
+ stakingParams,
+ isInUnstakeWindow,
+ isInCooldown,
+ unstakeWindowStartTimestamp,
+ unstakeWindowEndTimestamp,
+ }
+ }
+
+ // Transactions
+
+ static async approve(lyra: Lyra, address: string): Promise {
+ const proxyContract = getGlobalContract(lyra, LyraGlobalContractId.LyraStakingModule, lyra.ethereumProvider)
+ const lyraContract = getERC20Contract(
+ lyra.ethereumProvider ?? lyra.provider,
+ lyra.deployment === Deployment.Mainnet ? LYRA_ETHEREUM_MAINNET_ADDRESS : LYRA_ETHEREUM_KOVAN_ADDRESS
+ )
+ const data = lyraContract.interface.encodeFunctionData('approve', [proxyContract.address, MAX_BN])
+ return await buildTxWithGasEstimate(lyra.ethereumProvider ?? lyra.provider, 1, lyraContract.address, address, data)
+ }
+
+ static async stake(lyra: Lyra, address: string, amount: BigNumber) {
+ const lyraStakingModuleProxyContract = getGlobalContract(
lyra,
- [
- {
- callData: cooldownPeriodCallData,
- contract: lyraStakingModuleContract,
- functionFragment: 'COOLDOWN_SECONDS',
- },
- {
- callData: unstakeWindowCallData,
- contract: lyraStakingModuleContract,
- functionFragment: 'UNSTAKE_WINDOW',
- },
- {
- callData: totalSupplyCallData,
- contract: lyraStakingModuleContract,
- functionFragment: 'totalSupply',
- },
- ],
+ LyraGlobalContractId.LyraStakingModule,
lyra.ethereumProvider
)
-
- const lyraPrice = await fetchLyraTokenSpotPrice(lyra)
- return new LyraStaking(lyra, {
- cooldownPeriod: cooldownPeriod.toNumber(),
- unstakeWindow: unstakeWindow.toNumber(),
- totalSupply,
- lyraPrice,
- })
+ const txData = lyraStakingModuleProxyContract.interface.encodeFunctionData('stake', [address, amount])
+ return await buildTxWithGasEstimate(
+ lyra.ethereumProvider ?? lyra.provider,
+ 1,
+ lyraStakingModuleProxyContract.address,
+ address,
+ txData
+ )
}
- static async getStakingRewardsBalance(lyra: Lyra, owner: string): Promise {
- const lyraStakingModuleContract = getGlobalContract(
+ static async requestUnstake(lyra: Lyra, address: string): Promise {
+ const lyraStakingModuleProxyContract = getGlobalContract(
lyra,
LyraGlobalContractId.LyraStakingModule,
lyra.ethereumProvider
)
- return await lyraStakingModuleContract.getTotalRewardsBalance(owner)
+ const data = lyraStakingModuleProxyContract.interface.encodeFunctionData('cooldown')
+ const tx = await buildTxWithGasEstimate(
+ lyra.ethereumProvider ?? lyra.provider,
+ 1,
+ lyraStakingModuleProxyContract.address,
+ address,
+ data
+ )
+ return tx
}
- static async getStakingRewardsApy(lyra: Lyra): Promise {
- const lyraStakingModuleContract = getGlobalContract(
+ static async unstake(lyra: Lyra, address: string, amount: BigNumber) {
+ const lyraStakingModuleProxyContract = getGlobalContract(
lyra,
LyraGlobalContractId.LyraStakingModule,
lyra.ethereumProvider
)
- const [assets, totalSupply, lyraTokenSpotPrice] = await Promise.all([
- lyraStakingModuleContract.assets('0xCb9f85730f57732fc899fb158164b9Ed60c77D49'),
- lyraStakingModuleContract.totalSupply(),
- fetchLyraTokenSpotPrice(lyra),
- ])
- const emissionPerSecond = assets.emissionPerSecond
-
- const apy = totalSupply.gt(ZERO_BN)
- ? (fromBigNumber(emissionPerSecond) * SECONDS_IN_YEAR * (lyraTokenSpotPrice ?? 0)) / fromBigNumber(totalSupply)
- : 0
- return toBigNumber(apy)
+ const txData = lyraStakingModuleProxyContract.interface.encodeFunctionData('redeem', [address, amount])
+ return await buildTxWithGasEstimate(
+ lyra.ethereumProvider ?? lyra.provider,
+ 1,
+ lyraStakingModuleProxyContract.address,
+ address,
+ txData
+ )
}
- static async claimRewards(lyra: Lyra, owner: string): Promise {
- const lyraStakingModuleProxyContract = getGlobalContract(
+ static async claim(lyra: Lyra, address: string) {
+ const lyraStakingModuleContract = getGlobalContract(
lyra,
LyraGlobalContractId.LyraStakingModule,
lyra.ethereumProvider
)
- const totalRewardsBalance = await this.getStakingRewardsBalance(lyra, owner)
- const data = lyraStakingModuleProxyContract.interface.encodeFunctionData('claimRewards', [
- owner,
- totalRewardsBalance,
- ])
+ const totalRewardsBalance = await lyraStakingModuleContract.getTotalRewardsBalance(address)
+ const data = lyraStakingModuleContract.interface.encodeFunctionData('claimRewards', [address, totalRewardsBalance])
const tx = await buildTxWithGasEstimate(
lyra.ethereumProvider ?? lyra.provider,
1,
- lyraStakingModuleProxyContract.address,
- owner,
+ lyraStakingModuleContract.address,
+ address,
data
)
return tx
}
+
+ static async approveMigrate(lyra: Lyra, address: string): Promise {
+ const tokenMigratorContract = getGlobalContract(lyra, LyraGlobalContractId.TokenMigrator)
+ const erc20 = getERC20Contract(lyra.provider, OLD_STAKED_LYRA_OPTIMISM_ADDRESS)
+ const data = erc20.interface.encodeFunctionData('approve', [tokenMigratorContract.address, MAX_BN])
+ const tx = await buildTxWithGasEstimate(lyra.provider, lyra.provider.network.chainId, erc20.address, address, data)
+ return tx
+ }
+
+ static async migrateStakedLyra(lyra: Lyra, address: string): Promise {
+ const account = lyra.account(address)
+ const tokenMigratorContract = getGlobalContract(lyra, LyraGlobalContractId.TokenMigrator, lyra.optimismProvider)
+ const { optimismOldStkLyra } = await account.lyraBalances()
+ const data = tokenMigratorContract.interface.encodeFunctionData('swap', [optimismOldStkLyra])
+ const tx = await buildTxWithGasEstimate(
+ lyra.provider,
+ lyra.provider.network.chainId,
+ tokenMigratorContract.address,
+ address,
+ data
+ )
+ return tx
+ }
+
+ static async claimableRewards(lyra: Lyra, address: string): Promise {
+ const lyraStakingModuleContract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.LyraStakingModule,
+ lyra.ethereumProvider
+ )
+ return await lyraStakingModuleContract.getTotalRewardsBalance(address)
+ }
}
diff --git a/sdk/src/lyra_unstake/index.ts b/sdk/src/lyra_unstake/index.ts
deleted file mode 100644
index dd5db08f..00000000
--- a/sdk/src/lyra_unstake/index.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { BigNumber } from '@ethersproject/bignumber'
-import { PopulatedTransaction } from '@ethersproject/contracts'
-
-import { Account, AccountBalances, AccountLiquidityTokenBalance, AccountLyraStaking } from '..'
-import { ZERO_BN } from '../constants/bn'
-import { LyraGlobalContractId } from '../constants/contracts'
-import { SECONDS_IN_DAY } from '../constants/time'
-import { GlobalRewardEpoch, RewardEpochTokenAmount } from '../global_reward_epoch'
-import Lyra from '../lyra'
-import buildTx from '../utils/buildTx'
-import buildTxWithGasEstimate from '../utils/buildTxWithGasEstimate'
-import findMarketX from '../utils/findMarketX'
-import fromBigNumber from '../utils/fromBigNumber'
-import getGlobalContract from '../utils/getGlobalContract'
-import insertTxGasEstimate from '../utils/insertTxGasEstimate'
-
-export enum UnstakeDisabledReason {
- NotInUnstakeWindow = 'NotInUnstakeWindow',
- InsufficientBalance = 'InsufficientBalance',
- ZeroAmount = 'ZeroAmount',
-}
-
-type UnstakeData = {
- globalEpoch: GlobalRewardEpoch | null
- account: Account
- accountStaking: AccountLyraStaking
- accountBalances: AccountBalances[]
- amount: BigNumber
-}
-
-export class LyraUnstake {
- lyra: Lyra
- private vaultTokenBalances: Record
- globalEpoch: GlobalRewardEpoch | null
- accountStaking: AccountLyraStaking
- account: Account
- amount: BigNumber
- stakedLyraBalance: BigNumber
- // Derived
- newStakedLyraBalance: BigNumber
- newStakingYieldPerDay: RewardEpochTokenAmount[]
- stakingYieldPerDay: RewardEpochTokenAmount[]
- tradingFeeRebate: number
- newTradingFeeRebate: number
- disabledReason: UnstakeDisabledReason | null
- tx: PopulatedTransaction | null
-
- constructor(lyra: Lyra, data: UnstakeData) {
- this.lyra = lyra
- this.globalEpoch = data.globalEpoch
- this.accountStaking = data.accountStaking
- this.account = data.account
- this.amount = data.amount
- this.stakedLyraBalance = data.accountStaking.lyraBalances.ethereumStkLyra
- this.newStakedLyraBalance = this.stakedLyraBalance.sub(this.amount)
- if (this.newStakedLyraBalance.lt(0)) {
- this.newStakedLyraBalance = ZERO_BN
- }
-
- this.vaultTokenBalances = data.accountBalances.reduce(
- (lpTokenBalances, accountBalance) => ({
- ...lpTokenBalances,
- [accountBalance.baseAsset.symbol]: accountBalance.liquidityToken,
- }),
- {}
- )
-
- const totalStakedLyraSupply = fromBigNumber(this.accountStaking.staking.totalSupply)
- const stakedLyraPctShare =
- totalStakedLyraSupply > 0 ? fromBigNumber(this.stakedLyraBalance) / totalStakedLyraSupply : 0
- const newStakedLyraPctShare =
- totalStakedLyraSupply > 0 ? fromBigNumber(this.newStakedLyraBalance) / totalStakedLyraSupply : 0
-
- this.stakingYieldPerDay =
- this.globalEpoch?.totalStakingRewards.map(token => {
- const totalTokensPerDay =
- this.globalEpoch && this.globalEpoch.duration > 0
- ? (token.amount / this.globalEpoch.duration) * SECONDS_IN_DAY
- : 0
- return {
- ...token,
- amount: totalTokensPerDay * stakedLyraPctShare,
- }
- }) ?? []
-
- this.newStakingYieldPerDay =
- this.globalEpoch?.totalStakingRewards.map(token => {
- const totalTokensPerDay =
- this.globalEpoch && this.globalEpoch.duration > 0
- ? (token.amount / this.globalEpoch.duration) * SECONDS_IN_DAY
- : 0
- return {
- ...token,
- amount: totalTokensPerDay * newStakedLyraPctShare,
- }
- }) ?? []
-
- this.tradingFeeRebate = this.globalEpoch?.tradingFeeRebate(fromBigNumber(this.stakedLyraBalance)) ?? 0
- this.newTradingFeeRebate = this.globalEpoch?.tradingFeeRebate(fromBigNumber(this.newStakedLyraBalance)) ?? 0
-
- // Determine disabled reason
- if (!data.accountStaking.isInUnstakeWindow) {
- this.disabledReason = UnstakeDisabledReason.NotInUnstakeWindow
- } else if (this.amount.gt(this.stakedLyraBalance)) {
- this.disabledReason = UnstakeDisabledReason.InsufficientBalance
- } else if (this.amount.eq(ZERO_BN)) {
- this.disabledReason = UnstakeDisabledReason.ZeroAmount
- } else {
- this.disabledReason = null
- }
-
- // Build transaction
- if (!this.disabledReason) {
- const lyraStakingModuleProxyContract = getGlobalContract(
- lyra,
- LyraGlobalContractId.LyraStakingModule,
- lyra.ethereumProvider
- )
- const txData = lyraStakingModuleProxyContract.interface.encodeFunctionData('redeem', [
- this.account.address,
- this.amount,
- ])
- this.tx = buildTx(
- lyra.ethereumProvider ?? lyra.provider,
- 1,
- lyraStakingModuleProxyContract.address,
- this.account.address,
- txData
- )
- } else {
- this.tx = null
- }
- }
-
- // Getters
-
- static async get(lyra: Lyra, address: string, amount: BigNumber): Promise {
- const account = Account.get(lyra, address)
- const [accountStaking, globalEpoch, accountBalances] = await Promise.all([
- account.lyraStaking(),
- lyra.latestGlobalRewardEpoch(),
- lyra.account(address).balances(),
- ])
- const unstake = new LyraUnstake(lyra, {
- globalEpoch,
- account,
- amount,
- accountStaking,
- accountBalances,
- })
- if (unstake?.tx) {
- unstake.tx = await insertTxGasEstimate(lyra.ethereumProvider ?? lyra.provider, unstake.tx)
- }
- return unstake
- }
-
- // Transactions
-
- static async requestUnstake(lyra: Lyra, address: string): Promise {
- const lyraStakingModuleProxyContract = getGlobalContract(
- lyra,
- LyraGlobalContractId.LyraStakingModule,
- lyra.ethereumProvider
- )
- const data = lyraStakingModuleProxyContract.interface.encodeFunctionData('cooldown')
- const tx = await buildTxWithGasEstimate(
- lyra.ethereumProvider ?? lyra.provider,
- 1,
- lyraStakingModuleProxyContract.address,
- address,
- data
- )
- return tx
- }
-
- // Dynamic Fields
-
- vaultApy(marketAddressOrName: string): RewardEpochTokenAmount[] {
- if (!this.globalEpoch) {
- return []
- }
- const market = findMarketX(this.globalEpoch.markets, marketAddressOrName)
- const marketKey = market.baseToken.symbol
- const currStakedLyraBalance = fromBigNumber(this.stakedLyraBalance)
- const currVaultTokenBalance = fromBigNumber(this.vaultTokenBalances[marketKey].balance)
- return this.globalEpoch.vaultApy(marketAddressOrName, currStakedLyraBalance, currVaultTokenBalance)
- }
-
- newVaultApy(marketAddressOrName: string): RewardEpochTokenAmount[] {
- if (!this.globalEpoch) {
- return []
- }
- const market = findMarketX(this.globalEpoch.markets, marketAddressOrName)
- const marketKey = market.baseToken.symbol
- const newStakedLyraBalance = fromBigNumber(this.newStakedLyraBalance)
- const currVaultTokenBalance = fromBigNumber(this.vaultTokenBalances[marketKey].balance)
- return this.globalEpoch.vaultApy(marketAddressOrName, newStakedLyraBalance, currVaultTokenBalance)
- }
-}
diff --git a/sdk/src/market/index.ts b/sdk/src/market/index.ts
index 036a14c1..6af4fa8c 100644
--- a/sdk/src/market/index.ts
+++ b/sdk/src/market/index.ts
@@ -80,6 +80,7 @@ export type MarketNetGreeksSnapshot = {
export type MarketTradingVolumeSnapshot = {
premiumVolume: BigNumber
notionalVolume: BigNumber
+ totalShortOpenInterestUSD: BigNumber
vaultFees: BigNumber
vaultFeeComponents: {
spotPriceFees: BigNumber
@@ -610,6 +611,10 @@ export class Market {
// Transactions
+ async approveDeposit(address: string) {
+ return await LiquidityDeposit.approve(this.lyra, this.address, address)
+ }
+
async deposit(beneficiary: string, amount: BigNumber): Promise {
return await LiquidityDeposit.deposit(this.lyra, this.address, beneficiary, amount)
}
diff --git a/sdk/src/utils/fetchAccountRewardEpochData.ts b/sdk/src/utils/fetchAccountRewardEpochData.ts
index af4ba4e9..db336dc1 100644
--- a/sdk/src/utils/fetchAccountRewardEpochData.ts
+++ b/sdk/src/utils/fetchAccountRewardEpochData.ts
@@ -46,7 +46,7 @@ export type AccountTradingRewards = {
}
export type AccountArrakisRewards = {
- rewards: RewardEpochTokenAmount[]
+ rewards?: RewardEpochTokenAmount[]
gUniTokensStaked: number
percentShare: number
}
diff --git a/sdk/src/utils/fetchClaimAddedEvents.ts b/sdk/src/utils/fetchClaimAddedEvents.ts
new file mode 100644
index 00000000..50034457
--- /dev/null
+++ b/sdk/src/utils/fetchClaimAddedEvents.ts
@@ -0,0 +1,43 @@
+import { ApolloClient, gql, HttpLink, InMemoryCache } from '@apollo/client'
+import { BigNumber } from 'ethers'
+
+import { ClaimAddedEvent } from '../account_reward_epoch'
+import { Chain } from '../constants/chain'
+import { CLAIM_ADDED_FRAGMENT, ClaimAddedQueryResult } from '../constants/queries'
+import getLyraGovernanceSubgraphURI from './getLyraGovernanceSubgraphURI'
+
+const claimAddedQuery = gql`
+ query claimAddeds($user: String!) {
+ claimAddeds(where: {
+ claimer: $user
+ }) {
+ ${CLAIM_ADDED_FRAGMENT}
+ }
+ }
+`
+
+type ClaimAddedVariables = {
+ user: string
+}
+
+export default async function fetchClaimAddedEvents(chain: Chain, address: string): Promise {
+ const client = new ApolloClient({
+ link: new HttpLink({ uri: getLyraGovernanceSubgraphURI(chain), fetch }),
+ cache: new InMemoryCache(),
+ })
+ const { data } = await client.query<{ claimAddeds: ClaimAddedQueryResult[] }, ClaimAddedVariables>({
+ query: claimAddedQuery,
+ variables: {
+ user: address.toLowerCase(),
+ },
+ })
+ return data.claimAddeds.map(ev => ({
+ amount: BigNumber.from(ev.amount),
+ blockNumber: ev.blockNumber,
+ claimer: ev.claimer,
+ epochTimestamp: parseInt(ev.epochTimestamp),
+ rewardToken: ev.rewardToken,
+ timestamp: ev.timestamp,
+ tag: ev.tag,
+ }))
+}
diff --git a/sdk/src/utils/fetchClaimEvents.ts b/sdk/src/utils/fetchClaimEvents.ts
new file mode 100644
index 00000000..a318b5c0
--- /dev/null
+++ b/sdk/src/utils/fetchClaimEvents.ts
@@ -0,0 +1,41 @@
+import { ApolloClient, gql, HttpLink, InMemoryCache } from '@apollo/client'
+import { BigNumber } from 'ethers'
+
+import { ClaimEvent } from '../account_reward_epoch'
+import { Chain } from '../constants/chain'
+import { CLAIM_FRAGMENT, ClaimAddedQueryResult } from '../constants/queries'
+import getLyraGovernanceSubgraphURI from './getLyraGovernanceSubgraphURI'
+
+const claimQuery = gql`
+ query claims($user: String!) {
+ claims(where: {
+ claimer: $user
+ }) {
+ ${CLAIM_FRAGMENT}
+ }
+ }
+`
+
+type ClaimAddedVariables = {
+ user: string
+}
+
+export default async function fetchClaimEvents(chain: Chain, address: string): Promise {
+ const client = new ApolloClient({
+ link: new HttpLink({ uri: getLyraGovernanceSubgraphURI(chain), fetch }),
+ cache: new InMemoryCache(),
+ })
+ const { data } = await client.query<{ claims: ClaimAddedQueryResult[] }, ClaimAddedVariables>({
+ query: claimQuery,
+ variables: {
+ user: address.toLowerCase(),
+ },
+ })
+ return data.claims.map(ev => ({
+ amount: BigNumber.from(ev.amount),
+ blockNumber: ev.blockNumber,
+ claimer: ev.claimer,
+ rewardToken: ev.rewardToken,
+ timestamp: ev.timestamp,
+ }))
+}
diff --git a/sdk/src/utils/fetchGlobalRewardEpochData.ts b/sdk/src/utils/fetchGlobalRewardEpochData.ts
index e2ae204a..5fab27fa 100644
--- a/sdk/src/utils/fetchGlobalRewardEpochData.ts
+++ b/sdk/src/utils/fetchGlobalRewardEpochData.ts
@@ -28,11 +28,11 @@ export type GlobalRewardEpochData = {
tradingRewardConfig: GlobalTradingRewardsConfig
MMVConfig: GlobalMMVConfig
stakingRewardConfig: GlobalStakingConfig
- wethLyraStakingL2RewardConfig?: GlobalArrakisConfig
+ wethLyraStakingRewardConfig?: GlobalArrakisConfig
}
export type GlobalTradingRewards = {
- totalRewards: RewardEpochTokenAmount[]
+ totalRewards?: RewardEpochTokenAmount[]
totalFees: number
totalTradingRebateRewards: RewardEpochTokenAmount[]
totalShortCollateralRewards: RewardEpochTokenAmount[]
@@ -79,9 +79,11 @@ export type GlobalStakingConfig = RewardEpochTokenConfig[]
export type GlobalArrakisConfig = RewardEpochTokenConfig[]
+const EMPTY: GlobalRewardEpochData[] = []
+
export default async function fetchGlobalRewardEpochData(lyra: Lyra): Promise {
if (lyra.deployment !== Deployment.Mainnet) {
- return []
+ return EMPTY
}
return fetchWithCache(`${LYRA_API_URL}/rewards/global?network=${lyra.network}`)
}
diff --git a/sdk/src/utils/fetchLyraBalances.ts b/sdk/src/utils/fetchLyraBalances.ts
index 35afc8d5..7d63a6f3 100644
--- a/sdk/src/utils/fetchLyraBalances.ts
+++ b/sdk/src/utils/fetchLyraBalances.ts
@@ -1,23 +1,30 @@
import { BigNumber } from 'ethers'
+import { AccountLyraBalances } from '../account'
import { LYRA_API_URL } from '../constants/links'
import fetchWithCache from './fetchWithCache'
-export type LyraBalancesData = {
- mainnetLYRA: BigNumber
- opLYRA: BigNumber
- opOldStkLYRA: BigNumber
- mainnetStkLYRA: BigNumber
- opStkLYRA: BigNumber
-}
-
-export default async function fetchLyraBalances(owner: string): Promise {
- const data = await fetchWithCache(`${LYRA_API_URL}/lyra-balances?&owner=${owner}`)
+export default async function fetchLyraBalances(owner: string): Promise {
+ const data = await fetchWithCache<{
+ mainnetLYRA: string
+ opLYRA: string
+ opOldStkLYRA: string
+ arbitrumLYRA: string
+ mainnetStkLYRA: string
+ opStkLYRA: string
+ arbitrumStkLYRA: string
+ migrationAllowance: string
+ stakingAllowance: string
+ }>(`${LYRA_API_URL}/lyra-balances?&owner=${owner}`)
return {
- mainnetLYRA: BigNumber.from(data.mainnetLYRA),
- opLYRA: BigNumber.from(data.opLYRA),
- opOldStkLYRA: BigNumber.from(data.opOldStkLYRA),
- mainnetStkLYRA: BigNumber.from(data.mainnetStkLYRA),
- opStkLYRA: BigNumber.from(data.opStkLYRA),
+ ethereumLyra: BigNumber.from(data.mainnetLYRA),
+ optimismLyra: BigNumber.from(data.opLYRA),
+ arbitrumLyra: BigNumber.from(data.arbitrumLYRA),
+ optimismOldStkLyra: BigNumber.from(data.opOldStkLYRA),
+ ethereumStkLyra: BigNumber.from(data.mainnetStkLYRA),
+ optimismStkLyra: BigNumber.from(data.opStkLYRA),
+ arbitrumStkLyra: BigNumber.from(data.arbitrumStkLYRA),
+ migrationAllowance: BigNumber.from(data.migrationAllowance),
+ stakingAllowance: BigNumber.from(data.stakingAllowance),
}
}
diff --git a/sdk/src/utils/fetchLyraPrice.ts b/sdk/src/utils/fetchLyraPrice.ts
new file mode 100644
index 00000000..17ea97b0
--- /dev/null
+++ b/sdk/src/utils/fetchLyraPrice.ts
@@ -0,0 +1,7 @@
+import { LYRA_API_URL } from '../constants/links'
+import fetchWithCache from './fetchWithCache'
+
+export default async function fetchLyraPrice(): Promise {
+ const res = await fetchWithCache<{ spotPrice: number }>(`${LYRA_API_URL}/lyra-price`)
+ return res.spotPrice
+}
diff --git a/sdk/src/utils/fetchLyraStakingParams.ts b/sdk/src/utils/fetchLyraStakingParams.ts
new file mode 100644
index 00000000..bd818061
--- /dev/null
+++ b/sdk/src/utils/fetchLyraStakingParams.ts
@@ -0,0 +1,54 @@
+import { BigNumber } from 'ethers'
+
+import Lyra, { LyraGlobalContractId } from '..'
+import { LyraGlobalContractMap } from '../constants/mappings'
+import getGlobalContract from './getGlobalContract'
+import multicall, { MulticallRequest } from './multicall'
+
+export type LyraStakingParams = {
+ cooldownPeriod: number
+ unstakeWindow: number
+ totalSupply: BigNumber
+}
+
+export default async function fetchLyraStakingParams(lyra: Lyra): Promise {
+ const lyraStakingModuleContract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.LyraStakingModule,
+ lyra.ethereumProvider
+ )
+ const {
+ returnData: [cooldownPeriod, unstakeWindow, totalSupply],
+ } = await multicall<
+ [
+ MulticallRequest,
+ MulticallRequest,
+ MulticallRequest
+ ]
+ >(
+ lyra,
+ [
+ {
+ args: [],
+ contract: lyraStakingModuleContract,
+ function: 'COOLDOWN_SECONDS',
+ },
+ {
+ args: [],
+ contract: lyraStakingModuleContract,
+ function: 'UNSTAKE_WINDOW',
+ },
+ {
+ args: [],
+ contract: lyraStakingModuleContract,
+ function: 'totalSupply',
+ },
+ ],
+ lyra.ethereumProvider
+ )
+ return {
+ cooldownPeriod: cooldownPeriod.toNumber(),
+ unstakeWindow: unstakeWindow.toNumber(),
+ totalSupply,
+ }
+}
diff --git a/sdk/src/utils/fetchLyraTokenSpotPrice.ts b/sdk/src/utils/fetchLyraTokenSpotPrice.ts
deleted file mode 100644
index cef646b6..00000000
--- a/sdk/src/utils/fetchLyraTokenSpotPrice.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import Lyra from '..'
-import { LYRA_OPTIMISM_MAINNET_ADDRESS } from '../constants/contracts'
-import fetchTokenSpotPrice from './fetchTokenSpotPrice'
-
-export default async function fetchLyraTokenSpotPrice(lyra: Lyra): Promise {
- return fetchTokenSpotPrice(lyra, LYRA_OPTIMISM_MAINNET_ADDRESS)
-}
diff --git a/sdk/src/utils/fetchOpTokenSpotPrice.ts b/sdk/src/utils/fetchOpTokenSpotPrice.ts
deleted file mode 100644
index bc2e03e8..00000000
--- a/sdk/src/utils/fetchOpTokenSpotPrice.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import Lyra from '..'
-import { OP_OPTIMISM_MAINNET_ADDRESS } from '../constants/contracts'
-import fetchTokenSpotPrice from './fetchTokenSpotPrice'
-
-export default async function fetchOpTokenSpotPrice(lyra: Lyra): Promise {
- return fetchTokenSpotPrice(lyra, OP_OPTIMISM_MAINNET_ADDRESS)
-}
diff --git a/sdk/src/utils/fetchTokenSpotPrice.ts b/sdk/src/utils/fetchTokenSpotPrice.ts
index 69c6cb8b..f99b4e99 100644
--- a/sdk/src/utils/fetchTokenSpotPrice.ts
+++ b/sdk/src/utils/fetchTokenSpotPrice.ts
@@ -1,41 +1,12 @@
-import { Contract } from '@ethersproject/contracts'
-import { JsonRpcProvider } from '@ethersproject/providers'
-
-import Lyra from '..'
-import { ZERO_BN } from '../constants/bn'
-import {
- Deployment,
- ONE_INCH_ORACLE_OPTIMISM_MAINNET_ADDRESS,
- USDC_OPTIMISM_MAINNET_ADDRESS,
- USDC_OPTIMISM_MAINNET_DECIMALS,
-} from '../constants/contracts'
-import ONE_INCH_OFFCHAIN_ORACLE_ABI from '../contracts/common/abis/OneInchOffChainOracle.json'
-import fromBigNumber from './fromBigNumber'
+import { LYRA_API_URL } from '../constants/links'
+import { Network } from '../constants/network'
+import fetchWithCache from './fetchWithCache'
export default async function fetchTokenSpotPrice(
- lyra: Lyra,
- tokenNameOrAddress: string,
- options?: {
- oracleAddress?: string
- customProvider?: JsonRpcProvider
- stableCoinAddress?: string
- stableCoinDecimals?: number
- }
+ tokenAddressOrName: string,
+ network: Network | 'ethereum'
): Promise {
- // use governance provider -> lyra.optimismProvider
- // BLOCK: remove before merging
- if (lyra.deployment === Deployment.Testnet) {
- return 0.05
- }
- const oneInchOffchainOracle = new Contract(
- options?.oracleAddress ?? ONE_INCH_ORACLE_OPTIMISM_MAINNET_ADDRESS,
- ONE_INCH_OFFCHAIN_ORACLE_ABI,
- options?.customProvider ?? lyra.optimismProvider
- )
- const data = await oneInchOffchainOracle.getRate(
- tokenNameOrAddress,
- options?.stableCoinAddress ?? USDC_OPTIMISM_MAINNET_ADDRESS,
- false
- )
- return fromBigNumber(data ?? ZERO_BN, options?.stableCoinDecimals ?? USDC_OPTIMISM_MAINNET_DECIMALS)
+ const url = new URL(`/token-price?tokenAddressOrName=${tokenAddressOrName}&network=${network}`, LYRA_API_URL)
+ const res = await fetchWithCache<{ spotPrice: number }>(url.toString())
+ return res.spotPrice
}
diff --git a/sdk/src/utils/fetchTradingVolumeHistory.ts b/sdk/src/utils/fetchTradingVolumeHistory.ts
index 0bbb21ba..92759ad9 100644
--- a/sdk/src/utils/fetchTradingVolumeHistory.ts
+++ b/sdk/src/utils/fetchTradingVolumeHistory.ts
@@ -26,6 +26,7 @@ const marketVolumeAndFeesSnapshotsQuery = gql`
const EMPTY: Omit = {
premiumVolume: ZERO_BN,
notionalVolume: ZERO_BN,
+ totalShortOpenInterestUSD: ZERO_BN,
vaultFees: ZERO_BN,
vaultFeeComponents: {
spotPriceFees: ZERO_BN,
@@ -71,9 +72,12 @@ export default async function fetchTradingVolumeHistory(
const varianceFees = BigNumber.from(marketVolumeAndFeesSnapshot.varianceFees)
const forceCloseFees = BigNumber.from(marketVolumeAndFeesSnapshot.deltaCutoffFees)
const liquidationFees = BigNumber.from(marketVolumeAndFeesSnapshot.lpLiquidationFees)
+ const totalShortPutOpenInterestUSD = BigNumber.from(marketVolumeAndFeesSnapshot.totalShortPutOpenInterestUSD)
+ const totalShortCallOpenInterestUSD = BigNumber.from(marketVolumeAndFeesSnapshot.totalShortCallOpenInterestUSD)
return {
premiumVolume: BigNumber.from(marketVolumeAndFeesSnapshot.premiumVolume),
notionalVolume: BigNumber.from(marketVolumeAndFeesSnapshot.notionalVolume),
+ totalShortOpenInterestUSD: totalShortCallOpenInterestUSD.add(totalShortPutOpenInterestUSD),
vaultFees: spotPriceFees
.add(optionPriceFees)
.add(vegaUtilFees)
diff --git a/sdk/src/utils/fetchWethLyraStakingData.ts b/sdk/src/utils/fetchWethLyraStakingData.ts
index 05dbfa92..94601fd0 100644
--- a/sdk/src/utils/fetchWethLyraStakingData.ts
+++ b/sdk/src/utils/fetchWethLyraStakingData.ts
@@ -1,16 +1,10 @@
import { BigNumber } from 'ethers'
-import {
- LYRA_ETHEREUM_MAINNET_ADDRESS,
- LyraGlobalContractId,
- ONE_INCH_ORACLE_ETHEREUM_MAINNET_ADDRESS,
- USDC_ETHEREUM_MAINNET_ADDRESS,
- USDC_OPTIMISM_MAINNET_DECIMALS,
- WETH_ETHEREUM_MAINNET_ADDRESS,
-} from '../constants/contracts'
+import { LyraGlobalContractId, WETH_ETHEREUM_MAINNET_ADDRESS } from '../constants/contracts'
import { SECONDS_IN_YEAR } from '../constants/time'
import Lyra from '../lyra'
import callContractWithMulticall from './callContractWithMulticall'
+import fetchLyraPrice from './fetchLyraPrice'
import fetchTokenSpotPrice from './fetchTokenSpotPrice'
import fromBigNumber from './fromBigNumber'
import getGlobalContract from './getGlobalContract'
@@ -20,6 +14,8 @@ const fetchWethLyraStakingData = async (
): Promise<{
apy: number
tokenValue: number
+ lyraPerToken: number
+ wethPerToken: number
}> => {
const arrakisVaultContract = getGlobalContract(lyra, LyraGlobalContractId.ArrakisPoolL1, lyra.ethereumProvider)
const wethLyraStakingContract = getGlobalContract(
@@ -28,25 +24,15 @@ const fetchWethLyraStakingData = async (
lyra.ethereumProvider
)
const [lyraPrice, wethPrice] = await Promise.all([
- fetchTokenSpotPrice(lyra, LYRA_ETHEREUM_MAINNET_ADDRESS, {
- oracleAddress: ONE_INCH_ORACLE_ETHEREUM_MAINNET_ADDRESS,
- customProvider: lyra.ethereumProvider,
- stableCoinAddress: USDC_ETHEREUM_MAINNET_ADDRESS,
- stableCoinDecimals: USDC_OPTIMISM_MAINNET_DECIMALS,
- }),
- fetchTokenSpotPrice(lyra, WETH_ETHEREUM_MAINNET_ADDRESS, {
- oracleAddress: ONE_INCH_ORACLE_ETHEREUM_MAINNET_ADDRESS,
- customProvider: lyra.ethereumProvider,
- stableCoinAddress: USDC_ETHEREUM_MAINNET_ADDRESS,
- stableCoinDecimals: USDC_OPTIMISM_MAINNET_DECIMALS,
- }),
+ fetchLyraPrice(),
+ fetchTokenSpotPrice(WETH_ETHEREUM_MAINNET_ADDRESS, 'ethereum'),
])
const getUnderlyingBalancesCallData = arrakisVaultContract.interface.encodeFunctionData('getUnderlyingBalances')
const totalSupplyCallData = wethLyraStakingContract.interface.encodeFunctionData('totalSupply')
const rewardRateCallData = wethLyraStakingContract.interface.encodeFunctionData('rewardRate')
- const [[amount0Current, amount1Current], [supply], [rewardRate]] = await callContractWithMulticall<
+ const [[amount0Current, amount1Current], [supplyBN], [rewardRate]] = await callContractWithMulticall<
[[BigNumber, BigNumber], [BigNumber], [BigNumber], [BigNumber]]
>(
lyra,
@@ -69,14 +55,16 @@ const fetchWethLyraStakingData = async (
],
lyra.ethereumProvider
)
-
- const poolLyraValue = fromBigNumber(amount0Current) * lyraPrice
- const poolWethValue = fromBigNumber(amount1Current) * wethPrice
- const tvl = poolWethValue + poolLyraValue
- const tokenValue = supply ? tvl / fromBigNumber(supply) : 0
- const rewardsPerSecondPerToken = supply.gt(0) ? fromBigNumber(rewardRate) / fromBigNumber(supply) : 0
+ const poolLyraBalance = fromBigNumber(amount0Current)
+ const poolwethBalance = fromBigNumber(amount1Current)
+ const supply = fromBigNumber(supplyBN)
+ const lyraPerToken = supply > 0 ? poolLyraBalance / supply : 0
+ const wethPerToken = supply > 0 ? poolwethBalance / supply : 0
+ const tvl = poolwethBalance * wethPrice + poolLyraBalance * lyraPrice
+ const tokenValue = supply > 0 ? tvl / supply : 0
+ const rewardsPerSecondPerToken = supply > 0 ? fromBigNumber(rewardRate) / supply : 0
const apy = tokenValue > 0 ? (rewardsPerSecondPerToken * SECONDS_IN_YEAR * (lyraPrice ?? 0)) / tokenValue : 0
- return { apy, tokenValue }
+ return { apy, tokenValue, lyraPerToken, wethPerToken }
}
export default fetchWethLyraStakingData
diff --git a/sdk/src/utils/fetchWithCache.ts b/sdk/src/utils/fetchWithCache.ts
index 6dfcbb9a..4da57ce1 100644
--- a/sdk/src/utils/fetchWithCache.ts
+++ b/sdk/src/utils/fetchWithCache.ts
@@ -3,16 +3,16 @@ import fetch from 'cross-fetch'
const CACHE: Record }> = {}
const CACHE_TIMEOUT = 5 * 1000
-const fetcher = async (url: string): Promise => {
+async function fetcher(url: string): Promise {
const data = await fetch(url)
return await data.json()
}
-export default async function fetchWithCache(url: string) {
+export default async function fetchWithCache(url: string): Promise {
const now = Date.now()
if (!CACHE[url] || now > CACHE[url].lastUpdated + CACHE_TIMEOUT) {
CACHE[url] = {
- fetch: fetcher(url),
+ fetch: fetcher(url),
lastUpdated: now,
}
}
diff --git a/sdk/src/utils/getLyraGovernanceSubgraphURI.ts b/sdk/src/utils/getLyraGovernanceSubgraphURI.ts
new file mode 100644
index 00000000..fbc6421b
--- /dev/null
+++ b/sdk/src/utils/getLyraGovernanceSubgraphURI.ts
@@ -0,0 +1,16 @@
+import { Chain } from '../constants/chain'
+import { LYRA_API_URL } from '../constants/links'
+
+const getLyraGovernanceSubgraphURI = (chain: Chain | 'ethereum'): string => {
+ switch (chain) {
+ case 'ethereum':
+ case Chain.Optimism:
+ case Chain.OptimismGoerli:
+ return new URL(`/subgraph/optimism-governance/v1/api`, LYRA_API_URL).toString()
+ case Chain.Arbitrum:
+ case Chain.ArbitrumGoerli:
+ return new URL(`/subgraph/arbitrum-governance/v1/api`, LYRA_API_URL).toString()
+ }
+}
+
+export default getLyraGovernanceSubgraphURI
diff --git a/sdk/src/utils/getUniqueRewardTokenAmounts.ts b/sdk/src/utils/getUniqueRewardTokenAmounts.ts
new file mode 100644
index 00000000..840a0d83
--- /dev/null
+++ b/sdk/src/utils/getUniqueRewardTokenAmounts.ts
@@ -0,0 +1,14 @@
+import { RewardEpochTokenAmount } from '../global_reward_epoch'
+
+export default function getUniqueRewardTokenAmounts(rewardTokenAmount: RewardEpochTokenAmount[]) {
+ return Object.values(
+ rewardTokenAmount.reduce((map, rewardTokenAmount) => {
+ if (!map[rewardTokenAmount.address]) {
+ map[rewardTokenAmount.address] = { ...rewardTokenAmount }
+ return map
+ }
+ map[rewardTokenAmount.address].amount += rewardTokenAmount.amount
+ return map
+ }, {} as Record)
+ )
+}
diff --git a/sdk/src/utils/getVersionForChain.ts b/sdk/src/utils/getVersionForChain.ts
new file mode 100644
index 00000000..37f21728
--- /dev/null
+++ b/sdk/src/utils/getVersionForChain.ts
@@ -0,0 +1,10 @@
+import { Network, Version } from '..'
+
+export default function getVersionForChain(network: Network): Version {
+ switch (network) {
+ case Network.Arbitrum:
+ return Version.Newport
+ case Network.Optimism:
+ return Version.Avalon
+ }
+}
diff --git a/sdk/src/utils/multicall.ts b/sdk/src/utils/multicall.ts
index 5a64a253..5c552207 100644
--- a/sdk/src/utils/multicall.ts
+++ b/sdk/src/utils/multicall.ts
@@ -1,3 +1,4 @@
+import { JsonRpcProvider } from '@ethersproject/providers'
import { Contract } from 'ethers'
import Lyra from '..'
@@ -16,12 +17,17 @@ type MulticallResponses = { [K in keyof Reqs]:
export default async function multicall(
lyra: Lyra,
- requests: Reqs
+ requests: Reqs,
+ customProvider?: JsonRpcProvider
): Promise<{
returnData: MulticallResponses
blockNumber: number
}> {
- const multicall3Contract = getGlobalContract(lyra, LyraGlobalContractId.Multicall3, lyra.provider)
+ const multicall3Contract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.Multicall3,
+ customProvider ? customProvider : lyra.provider
+ )
const calls = requests.map(req => ({
target: req.contract.address,
callData: req.contract.interface.encodeFunctionData(req.function, req.args),
diff --git a/sdk/src/weth_lyra_staking/index.ts b/sdk/src/weth_lyra_staking/index.ts
index 92cda894..ca0aabed 100644
--- a/sdk/src/weth_lyra_staking/index.ts
+++ b/sdk/src/weth_lyra_staking/index.ts
@@ -1,34 +1,177 @@
import { BigNumber } from '@ethersproject/bignumber'
+import { PopulatedTransaction } from 'ethers'
import Lyra, { LyraGlobalContractId } from '..'
-import { ZERO_BN } from '../constants/bn'
+import { fetchAccountWethLyraStaking, fetchAccountWethLyraStakingL2 } from '../account/fetchAccountWethLyraStaking'
+import { MAX_BN, ZERO_BN } from '../constants/bn'
+import buildTxWithGasEstimate from '../utils/buildTxWithGasEstimate'
import fetchWethLyraStakingData from '../utils/fetchWethLyraStakingData'
import fromBigNumber from '../utils/fromBigNumber'
import getGlobalContract from '../utils/getGlobalContract'
+export type AccountWethLyraStakingL2 = {
+ unstakedLPTokenBalance: BigNumber
+ stakedLPTokenBalance: BigNumber
+ rewards: BigNumber
+ opRewards: BigNumber
+ allowance: BigNumber
+}
+
+export type AccountWethLyraStaking = {
+ unstakedLPTokenBalance: BigNumber
+ stakedLPTokenBalance: BigNumber
+ rewards: BigNumber
+ allowance: BigNumber
+}
+
export class WethLyraStaking {
lyra: Lyra
totalStaked: BigNumber
stakedTokenBalance: BigNumber
lpTokenValue: number
+ wethPerToken: number
+ lyraPerToken: number
stakedTVL: number
apy: number
- constructor(lyra: Lyra, lpTokenValue: number, stakedTokenBalance: BigNumber, apy: number) {
+ constructor(
+ lyra: Lyra,
+ lpTokenValue: number,
+ stakedTokenBalance: BigNumber,
+ apy: number,
+ wethPerToken: number,
+ lyraPerToken: number
+ ) {
this.lyra = lyra
this.totalStaked = ZERO_BN
this.lpTokenValue = lpTokenValue
this.stakedTokenBalance = stakedTokenBalance
this.stakedTVL = fromBigNumber(stakedTokenBalance) * lpTokenValue
this.apy = apy
+ this.wethPerToken = wethPerToken
+ this.lyraPerToken = lyraPerToken
}
static async get(lyra: Lyra): Promise {
- const [arrakisVaultContract, arrakisRewardsContract, { apy, tokenValue }] = await Promise.all([
- getGlobalContract(lyra, LyraGlobalContractId.ArrakisPoolL1, lyra.ethereumProvider),
- getGlobalContract(lyra, LyraGlobalContractId.WethLyraStakingRewardsL1, lyra.ethereumProvider),
- await fetchWethLyraStakingData(lyra),
- ])
+ const [arrakisVaultContract, arrakisRewardsContract, { apy, tokenValue, wethPerToken, lyraPerToken }] =
+ await Promise.all([
+ getGlobalContract(lyra, LyraGlobalContractId.ArrakisPoolL1, lyra.ethereumProvider),
+ getGlobalContract(lyra, LyraGlobalContractId.WethLyraStakingRewardsL1, lyra.ethereumProvider),
+ fetchWethLyraStakingData(lyra),
+ ])
const stakedTokenBalance = await arrakisVaultContract.balanceOf(arrakisRewardsContract.address)
- return new WethLyraStaking(lyra, tokenValue, stakedTokenBalance, apy)
+ return new WethLyraStaking(lyra, tokenValue, stakedTokenBalance, apy, wethPerToken, lyraPerToken)
+ }
+
+ static async getByOwner(lyra: Lyra, address: string): Promise {
+ return await fetchAccountWethLyraStaking(lyra, address)
+ }
+
+ // DEPRECATED
+ static async getByOwnerL2(lyra: Lyra, address: string): Promise {
+ return await fetchAccountWethLyraStakingL2(lyra, address)
+ }
+
+ // Transactions
+
+ static async approve(lyra: Lyra, address: string): Promise {
+ const arrakisPoolContract = getGlobalContract(lyra, LyraGlobalContractId.ArrakisPoolL1)
+ const wethLyraStakingContract = getGlobalContract(lyra, LyraGlobalContractId.WethLyraStakingRewardsL1)
+ const calldata = arrakisPoolContract.interface.encodeFunctionData('approve', [
+ wethLyraStakingContract.address,
+ MAX_BN,
+ ])
+ return await buildTxWithGasEstimate(
+ lyra.ethereumProvider ?? lyra.provider,
+ 1,
+ arrakisPoolContract.address,
+ address,
+ calldata
+ )
+ }
+
+ static async stake(lyra: Lyra, address: string, amount: BigNumber): Promise {
+ const wethLyraStakingL1RewardsContract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.WethLyraStakingRewardsL1,
+ lyra.ethereumProvider
+ )
+ const calldata = wethLyraStakingL1RewardsContract.interface.encodeFunctionData('stake', [amount])
+ return await buildTxWithGasEstimate(
+ lyra.ethereumProvider ?? lyra.provider,
+ 1,
+ wethLyraStakingL1RewardsContract.address,
+ address,
+ calldata
+ )
+ }
+
+ static async unstake(lyra: Lyra, address: string, amount: BigNumber) {
+ const wethLyraStakingL1RewardsContract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.WethLyraStakingRewardsL1,
+ lyra.ethereumProvider
+ )
+ const calldata = wethLyraStakingL1RewardsContract.interface.encodeFunctionData('withdraw', [amount])
+ return await buildTxWithGasEstimate(
+ lyra.ethereumProvider ?? lyra.provider,
+ 1,
+ wethLyraStakingL1RewardsContract.address,
+ address,
+ calldata
+ )
+ }
+
+ static async claimableRewards(lyra: Lyra, address: string): Promise {
+ const wethLyraStakingRewardsL1Contract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.WethLyraStakingRewardsL1,
+ lyra.ethereumProvider
+ )
+ return await wethLyraStakingRewardsL1Contract.earned(address)
+ }
+
+ static async claim(lyra: Lyra, address: string): Promise {
+ const wethLyraStakingL1RewardsContract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.WethLyraStakingRewardsL1,
+ lyra.ethereumProvider
+ )
+ const calldata = wethLyraStakingL1RewardsContract.interface.encodeFunctionData('getReward')
+ return await buildTxWithGasEstimate(
+ lyra.ethereumProvider ?? lyra.provider,
+ 1,
+ wethLyraStakingL1RewardsContract.address,
+ address,
+ calldata
+ )
+ }
+
+ // Deprecated L2 Arrakis program
+ static async unstakeL2(lyra: Lyra, address: string, amount: BigNumber) {
+ const wethLyraStakingL2RewardsContract = getGlobalContract(
+ lyra,
+ LyraGlobalContractId.WethLyraStakingRewardsL2,
+ lyra.optimismProvider
+ )
+ const calldata = wethLyraStakingL2RewardsContract.interface.encodeFunctionData('withdraw', [amount])
+ return await buildTxWithGasEstimate(
+ lyra.optimismProvider ?? lyra.provider,
+ lyra.chainId,
+ wethLyraStakingL2RewardsContract.address,
+ address,
+ calldata
+ )
+ }
+
+ static async claimL2(lyra: Lyra, address: string) {
+ const wethLyraStakingL2RewardsContract = getGlobalContract(lyra, LyraGlobalContractId.WethLyraStakingRewardsL2)
+ const calldata = wethLyraStakingL2RewardsContract.interface.encodeFunctionData('getReward')
+ return await buildTxWithGasEstimate(
+ lyra.optimismProvider ?? lyra.provider,
+ lyra.chainId,
+ wethLyraStakingL2RewardsContract.address,
+ address,
+ calldata
+ )
}
}
diff --git a/ui/components/Background/index.tsx b/ui/components/Background/index.tsx
index faa6b0ee..4f7dc8d6 100644
--- a/ui/components/Background/index.tsx
+++ b/ui/components/Background/index.tsx
@@ -1,4 +1,3 @@
-import useIsDarkMode from '@lyra/ui/hooks/useIsDarkMode'
import useThemeColor from '@lyra/ui/hooks/useThemeColor'
import React from 'react'
import { LayoutProps, MarginProps } from 'styled-system'
@@ -14,7 +13,6 @@ export default function Background({
bloomLeft: left = '-33%',
...styleProps
}: Props): JSX.Element {
- const [isDarkMode] = useIsDarkMode()
const backgroundColorVal = useThemeColor(backgroundColor)
return (
@@ -32,23 +30,7 @@ export default function Background({
position: 'absolute',
// top,
// left,
- background: isDarkMode
- ? `
- radial-gradient(ellipse, #3DFFFF08 0%,
- #3DFFFF08 20%,
- #3DFFFF05 40%,
- #3DFFFF01 60%,
- #00000000 80%,
- #00000000 100%)
- `
- : `
- radial-gradient(ellipse, #3DFFFF1A 0%,
- #3DFFFF14 20%,
- #3DFFFF14 40%,
- #3DFFFF0A 60%,
- #3DFFFF00 80%,
- #00000000 100%)
- `,
+ background: '#131312',
transform: `translate(${top}, ${left})`,
}}
/>
@@ -57,21 +39,7 @@ export default function Background({
height="1000px"
sx={{
position: 'absolute',
- background: isDarkMode
- ? `
- radial-gradient(ellipse, #3DFFFF08 0%,
- #3DFFFF08 15%,
- #3DFFFF05 31%,
- #00000000 50%,
- #00000000 100%)
- `
- : `
- radial-gradient(ellipse, #3DFFFF05 0%,
- #3DFFFF05 15%,
- #3DFFFF03 65%,
- #00000000 75%,
- #00000000 100%)
- `,
+ background: '#131312',
transform: 'rotate(-45deg) translate(10%, 40%)',
}}
/>
diff --git a/ui/components/Button/index.tsx b/ui/components/Button/index.tsx
index 9a5f3c98..6b49d01c 100644
--- a/ui/components/Button/index.tsx
+++ b/ui/components/Button/index.tsx
@@ -274,7 +274,11 @@ const Button = React.forwardRef(
/>
) : null}
- {typeof rightIcon === 'string' ? : rightIcon}
+ {typeof rightIcon === 'string' ? (
+
+ ) : (
+ rightIcon
+ )}
) : null
diff --git a/ui/components/Card/index.tsx b/ui/components/Card/index.tsx
index 0af17649..0d429f71 100644
--- a/ui/components/Card/index.tsx
+++ b/ui/components/Card/index.tsx
@@ -3,7 +3,7 @@ import useIsDarkMode from '@lyra/ui/hooks/useIsDarkMode'
import useIsMobile from '@lyra/ui/hooks/useIsMobile'
import React from 'react'
-export type CardVariant = 'default' | 'elevated' | 'nested' | 'modal'
+export type CardVariant = 'default' | 'elevated' | 'nested' | 'modal' | 'outline'
export type CardProps = {
children?: React.ReactNode
@@ -26,6 +26,8 @@ const getVariant = (variant: CardVariant, isDarkMode: boolean, isMobile: boolean
return 'cardNested'
case 'elevated':
return 'cardElevated'
+ case 'outline':
+ return 'cardOutlined'
case 'modal':
return 'cardModal'
}
@@ -48,6 +50,20 @@ const Card = React.forwardRef(
textDecoration: 'none',
color: 'text',
overflow: 'hidden',
+ ':hover':
+ onClick || href
+ ? {
+ bg: 'cardNestedHover',
+ cursor: 'pointer',
+ }
+ : null,
+ ':active':
+ onClick || href
+ ? {
+ bg: 'active',
+ cursor: 'pointer',
+ }
+ : null,
...(styleProps as any)?.sx,
}}
variant={getVariant(variant, isDarkMode, isMobile)}
diff --git a/ui/components/LinearProgress/index.tsx b/ui/components/LinearProgress/index.tsx
new file mode 100644
index 00000000..e2d87eba
--- /dev/null
+++ b/ui/components/LinearProgress/index.tsx
@@ -0,0 +1,20 @@
+import useThemeColor from '@lyra/ui/hooks/useThemeColor'
+import { MarginProps, PaddingProps } from '@lyra/ui/types'
+import React from 'react'
+
+import Box from '../Box'
+
+type Props = {
+ progress: number
+ color?: string
+} & MarginProps &
+ PaddingProps
+
+export default function LinearProgress({ color = 'primaryText', progress, ...styleProps }: Props) {
+ const fill = useThemeColor(color)
+ return (
+
+
+
+ )
+}
diff --git a/ui/components/List/ListItem.tsx b/ui/components/List/ListItem.tsx
index 82fff521..060b0629 100644
--- a/ui/components/List/ListItem.tsx
+++ b/ui/components/List/ListItem.tsx
@@ -12,6 +12,7 @@ export type ListItemProps = {
label: React.ReactNode | string
sublabel?: React.ReactNode | string | null
icon?: IconType | string | React.ReactNode | null
+ rightIcon?: IconType | string | React.ReactNode | null
rightContent?: IconType | string | React.ReactNode | null
onClick?: (e: any) => void
isDisabled?: boolean
@@ -26,6 +27,7 @@ export default function ListItem({
label,
sublabel,
icon,
+ rightIcon,
rightContent,
onClick,
isDisabled,
@@ -69,7 +71,7 @@ export default function ListItem({
{typeof icon === 'string' ? : icon}
) : null}
-
+
{typeof label === 'string' || typeof label === 'number' ? (
{label}
@@ -83,6 +85,7 @@ export default function ListItem({
) : (
sublabel
))}
+ {typeof rightIcon === 'string' ? : rightIcon}
{rightContent ? (
diff --git a/ui/components/Modal/ModalDesktop.tsx b/ui/components/Modal/ModalDesktop.tsx
index 02bf1d77..1751afc3 100644
--- a/ui/components/Modal/ModalDesktop.tsx
+++ b/ui/components/Modal/ModalDesktop.tsx
@@ -19,9 +19,17 @@ export type Props = {
children?: React.ReactNode
noPadding?: boolean
width?: ResponsiveValue
+ centerTitle?: boolean
}
-export default function ModalDesktop({ isOpen, onClose, title, children, width = DESKTOP_MODAL_WIDTH }: Props) {
+export default function ModalDesktop({
+ isOpen,
+ onClose,
+ title,
+ children,
+ width = DESKTOP_MODAL_WIDTH,
+ centerTitle = false,
+}: Props) {
return isOpen
? ReactDOM.createPortal(
{
e.stopPropagation()
}}
+ sx={{ ':hover': null, ':active': null }}
>
- {typeof title === 'string' ? {title} : title}
+ {typeof title === 'string' ? (
+
+ {title}
+
+ ) : (
+ title
+ )}
{onClose ? (
+
{children}
)
diff --git a/ui/components/Table/TableRowMarker.tsx b/ui/components/Table/TableRowMarker.tsx
index 4df691d0..fdd79564 100644
--- a/ui/components/Table/TableRowMarker.tsx
+++ b/ui/components/Table/TableRowMarker.tsx
@@ -17,7 +17,7 @@ export default function TableRowMarker({ content, ...styleProps }: TableRowMarke
justifyContent="center"
px={3}
py={1}
- bg="primaryButtonBg"
+ bg="primaryButtonActive"
sx={{
borderRadius: 'card',
boxShadow: '10px 10px 10px elevatedShadowBg',
diff --git a/ui/theme/ThemeProvider.tsx b/ui/theme/ThemeProvider.tsx
index 420c78bf..1b852b11 100644
--- a/ui/theme/ThemeProvider.tsx
+++ b/ui/theme/ThemeProvider.tsx
@@ -13,8 +13,8 @@ type Props = {
isLightMode?: boolean
}
-export default function ThemeProvider({ children, isDarkMode = false, isLightMode = false }: Props) {
- const theme = isDarkMode ? darkTheme : isLightMode ? lightTheme : getThemePreset(true)
+export default function ThemeProvider({ children, isDarkMode = true, isLightMode = false }: Props) {
+ const theme = isDarkMode ? darkTheme : isLightMode ? lightTheme : getThemePreset(false)
useEffect(() => {
injectStyle()
diff --git a/ui/theme/index.ts b/ui/theme/index.ts
index 957c8f56..c690894e 100644
--- a/ui/theme/index.ts
+++ b/ui/theme/index.ts
@@ -7,9 +7,11 @@ const lightColors = {
hover: '#E8E8E880', // 50%
active: '#E8E8E8CC', // 80%
cardBg: '#FEFEFE73', // 45%
+ cardHoverBg: '#313c47', // 75%
cardElevatedBg: '#FEFEFE',
cardNestedBg: '#E8E8E8CC', // 80%
cardNestedHover: '#CFCECE80',
+ cardOutline: '#3A4047',
// default button
buttonBg: '#E8E8E899', // 60%
@@ -18,9 +20,9 @@ const lightColors = {
disabledButtonBg: '#E8E8E899',
// primary button
- primaryButtonBg: '#56C3A9E6', // 90%
- primaryButtonHover: '#56C3A9', // 100%
- primaryButtonActive: '#52b9a1',
+ primaryButtonBg: '#3E2D00E6', // 90%
+ primaryButtonHover: '#3E2D00', // 100%
+ primaryButtonActive: '#FFCC00',
// warning button
warningButtonBg: '#F7931A',
@@ -111,18 +113,20 @@ const lightColors = {
const darkColors = {
// backgrounds
- background: '#1A212B',
+ background: '#131312',
hover: '#3A445033', // 20%
active: '#3A445066', // 40%
- cardBg: '#25303BBF', // 75%
+ cardBg: '#181818',
+ cardHoverBg: '#313c47', // 75%
cardElevatedBg: '#1B252D',
cardNestedBg: '#3A445099',
- cardNestedHover: '#3e4a56',
+ cardNestedHover: '#2D3741',
+ cardOutline: '#3A4047',
// primary button
- primaryButtonBg: '#57B29C',
- primaryButtonHover: '#5BBCA5',
- primaryButtonActive: '#6bc3ae',
+ primaryButtonBg: '#FFB80088',
+ primaryButtonHover: '#2B2A2A',
+ primaryButtonActive: '#FFB800',
// error button
errorButtonBg: '#FC4D95',
@@ -157,12 +161,12 @@ const darkColors = {
// toggle
toggleTrackBg: '#3A445099',
- toggleCheckedTrackBg: '#69D8BD',
+ toggleCheckedTrackBg: '#B1B1B1',
toggleThumbBg: '#95A4B5',
toggleCheckedThumbBg: '#FFFFFF',
// charts
- primaryLine: '#69D8BD',
+ primaryLine: '#B1B1B1',
primaryArea: '#57B29C26',
errorLine: '#FC4D95',
errorArea: '#FC4D9526',
@@ -172,8 +176,8 @@ const darkColors = {
// spinner
lightSpinner: '#3A445099',
lightSpinnerBg: '#3A445033',
- primarySpinner: '#69D8BD',
- primarySpinnerBg: '#69D8BD33',
+ primarySpinner: '#B1B1B1',
+ primarySpinnerBg: '#B1B1B133',
errorSpinner: '#FC4D95',
errorSpinnerBg: '#FC4D9533',
warningSpinner: '#de8417',
@@ -187,13 +191,13 @@ const darkColors = {
secondaryText: '#95A4B5',
disabledText: '#6B7D94',
invertedText: '#000000',
- primaryText: '#69D8BD',
+ primaryText: '#FFFFFF',
errorText: '#FC4D95',
warningText: '#de8417',
// link
- link: '#69D8BD',
- linkHover: '#a0eedb',
+ link: '#B1B1B1',
+ linkHover: '#B1B1B1FF',
// input
inputBg: '#3A445099',
@@ -370,12 +374,12 @@ const theme = {
},
variants: {
card: {
- borderRadius: [0, 'card'],
+ borderRadius: [1, 'card'],
overflow: 'hidden',
bg: 'cardBg',
},
cardShadowBg: {
- borderRadius: [0, 'card'],
+ borderRadius: [1, 'card'],
overflow: 'hidden',
boxShadow: (theme: Theme) => `0px -4px 72px -16px ${theme.colors ? theme.colors['elevatedShadowBg'] : ''}`,
bg: 'cardBg',
@@ -397,6 +401,12 @@ const theme = {
boxShadow: (theme: Theme) => `0px 0px 40px ${theme.colors ? theme.colors['elevatedShadowBg'] : ''}`,
bg: 'modalBg',
},
+ cardOutlined: {
+ variant: 'variants.card',
+ bg: 'transparent',
+ border: `1px solid`,
+ borderColor: 'cardOutline',
+ },
cardSeparator: {
bg: 'background',
},
@@ -709,11 +719,11 @@ const theme = {
color: 'text',
'&:not(.disabled):hover': {
bg: 'hover',
- borderColor: 'hover',
+ borderColor: 'buttonHover',
},
'&:not(.disabled):active': {
bg: 'active',
- borderColor: 'active',
+ borderColor: 'buttonActive',
},
},
defaultTransparent: {
@@ -749,6 +759,10 @@ const theme = {
color: 'text',
borderColor: 'secondaryText',
},
+ '&:not(.disabled):active': {
+ color: 'text',
+ borderColor: 'secondaryText',
+ },
},
lightTransparent: {
variant: 'buttons.defaultTransparent',
@@ -865,9 +879,9 @@ const theme = {
},
}
-export const getThemePreset = (isRoot: boolean, isLightMode: boolean = true): Theme => ({
+export const getThemePreset = (isRoot: boolean, isLightMode: boolean = false): Theme => ({
useColorSchemeMediaQuery: isRoot ? true : false,
- initialColorModeName: 'light',
+ initialColorModeName: 'dark',
colors: {
...(isLightMode ? lightColors : darkColors),
modes: isLightMode