├── .env.example
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ ├── codeql.yml
│ ├── coverageBadges.yml
│ └── main.yml
├── .gitignore
├── .gitmodules
├── .gitpod.yml
├── .husky
└── pre-commit
├── .linguirc
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── index.d.ts
├── index.html
├── orval.config.ts
├── package.json
├── public
├── assets
│ └── images
│ │ ├── base.svg
│ │ ├── berachain.svg
│ │ ├── ethereum.png
│ │ ├── give
│ │ ├── angel-protocol
│ │ │ ├── angel-protocol-logo.png
│ │ │ └── angel-protocol-logo.svg
│ │ ├── gitcoin
│ │ │ ├── gitcoin-logo.png
│ │ │ └── gitcoin-logo.svg
│ │ ├── impact-market
│ │ │ ├── impact-market-logo.png
│ │ │ └── impact-market-logo.svg
│ │ ├── kolektivo
│ │ │ ├── kolektivo-logo.png
│ │ │ └── kolektivo-logo.svg
│ │ └── popcorndao
│ │ │ ├── popcorndao-logo.png
│ │ │ └── popcorndao-logo.svg
│ │ └── grants
│ │ ├── black-dao
│ │ └── black_dao_logo.svg
│ │ ├── composable
│ │ └── composable_logo.svg
│ │ ├── entropy
│ │ ├── entropy_logo.svg
│ │ └── ethereum.png
│ │ ├── gateway
│ │ └── gateway_logo.svg
│ │ ├── metamars
│ │ └── metamars_logo.svg
│ │ ├── mover
│ │ └── mover_logo.svg
│ │ ├── on-demand
│ │ └── metricsdao_logo.svg
│ │ └── playgrounds
│ │ └── playgrounds_logo.svg
├── favicon.ico
├── favicon.svg
├── logo.svg
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── scripts
└── fix-coverage-report.js
├── src
├── .gitignore
├── App.tsx
├── Root.tsx
├── abi
│ ├── BLEVaultLido.json
│ ├── BLEVaultManagerLido.json
│ ├── BalancerV2Pool.json
│ ├── BalancerVault.json
│ ├── BondAggregator.json
│ ├── BondAuctioneer.json
│ ├── BondDepository.json
│ ├── BondFixedExpirySDA.json
│ ├── BondFixedExpiryTeller.json
│ ├── BondFixedTermSDA.json
│ ├── BondFixedTermTeller.json
│ ├── BondTeller.json
│ ├── Cooler.json
│ ├── CoolerClearingHouse.json
│ ├── CoolerClearingHouseV3.json
│ ├── CoolerConsolidation.json
│ ├── CoolerFactory.json
│ ├── CoolerFactoryV2.json
│ ├── CoolerV2Composites.json
│ ├── CoolerV2Migrator.json
│ ├── CoolerV2MonoCooler.json
│ ├── CrossChainBridge.json
│ ├── CrossChainBridgeTestnet.json
│ ├── CrossChainMigrator.json
│ ├── CurveFactory.json
│ ├── CurveGaugeController.json
│ ├── CurveGaugeDeposit.json
│ ├── CurvePool.json
│ ├── CurveToken.json
│ ├── DevFaucet.json
│ ├── ERC20BondToken.json
│ ├── ERC4626.json
│ ├── EmissionManager.json
│ ├── FuseProxy.json
│ ├── GUniV3Lp.json
│ ├── IERC20.json
│ ├── OlympusDistributor.json
│ ├── OlympusGovernorBravo.json
│ ├── OlympusLiquidityRegistry.json
│ ├── OlympusProV2.json
│ ├── OlympusStaking.json
│ ├── OlympusStakingv2.json
│ ├── OlympusTokenMigrator.json
│ ├── PairContract.json
│ ├── Range.json
│ ├── RangeOperator.json
│ ├── RangePrice.json
│ ├── StakingHelper.json
│ ├── Timelock.json
│ ├── Zap.json
│ ├── ZeroDistributor.json
│ ├── ZeroEx.json
│ ├── gOHM.json
│ ├── sOhmv2.json
│ └── wsOHM.json
├── assets
│ ├── OHM shape.svg
│ ├── Olympus Logo.svg
│ ├── arbitrum.png
│ ├── fonts
│ │ ├── NHaasGroteskDSPro-55Rg.woff2
│ │ ├── NHaasGroteskDSPro-65Md.woff2
│ │ └── NHaasGroteskDSPro-75Bd.woff2
│ ├── icons
│ │ ├── arrow-down.svg
│ │ ├── arrow-up.svg
│ │ ├── fullscreen.svg
│ │ ├── graph-grt-logo.svg
│ │ ├── hamburger.svg
│ │ ├── heart.svg
│ │ ├── lendAndBorrow.svg
│ │ ├── olympus-nav-header.svg
│ │ ├── step-1.svg
│ │ ├── step-2.svg
│ │ ├── step-complete.svg
│ │ ├── time-remaining.svg
│ │ ├── wallet.svg
│ │ └── x.svg
│ ├── images
│ │ ├── 33-together-spritesheet.png
│ │ ├── angel-protocol-logo.svg
│ │ ├── gitcoin-logo.svg
│ │ ├── impact-market-logo.svg
│ │ ├── kolektivo-logo.svg
│ │ └── popcorn-logo.svg
│ ├── known-issues-commit.png
│ ├── metamask.svg
│ ├── rainbowkit.css
│ ├── tokens
│ │ ├── AVAX.svg
│ │ ├── avax.png
│ │ ├── matic.svg
│ │ ├── token_OHM.svg
│ │ ├── token_sOHM.svg
│ │ ├── usds.svg
│ │ └── wETH.svg
│ └── walletConnect.svg
├── components
│ ├── CallToAction
│ │ ├── CallToAction.scss
│ │ ├── CallToAction.tsx
│ │ └── __tests__
│ │ │ └── CallToAction.unit.test.tsx
│ ├── Chart
│ │ ├── Chart.tsx
│ │ ├── Constants.ts
│ │ ├── CustomTooltip.tsx
│ │ ├── ExpandedChart.tsx
│ │ ├── IntersectionHelper.ts
│ │ └── __tests__
│ │ │ ├── Chart.unit.test.tsx
│ │ │ └── IntersectionHelper.unit.test.tsx
│ ├── ConnectButton
│ │ ├── ConnectButton.tsx
│ │ └── __tests__
│ │ │ └── ConnectButton.unit.test.tsx
│ ├── DevFaucet
│ │ └── index.tsx
│ ├── Messages
│ │ └── Messages.tsx
│ ├── Migration
│ │ ├── MigrationModal.scss
│ │ ├── MigrationModal.tsx
│ │ ├── MigrationModalSingle.tsx
│ │ └── __tests__
│ │ │ ├── MigrationModal.unit.test.jsx
│ │ │ └── MigrationModalSingle.unit.test.tsx
│ ├── MigrationCallToAction.tsx
│ ├── MigrationNotification.tsx
│ ├── PageTitle.test.tsx
│ ├── PageTitle.tsx
│ ├── SafariFooter.test.tsx
│ ├── SafariFooter.tsx
│ ├── Sidebar
│ │ ├── NavContent.tsx
│ │ ├── NavDrawer.tsx
│ │ ├── Sidebar.scss
│ │ ├── Sidebar.tsx
│ │ └── __tests__
│ │ │ └── Sidebar.unit.test.tsx
│ ├── StagingNotification.test.tsx
│ ├── StagingNotification.tsx
│ ├── StakeVersionContainer.tsx
│ ├── TokenAllowanceGuard
│ │ ├── TokenAllowanceGuard.tsx
│ │ └── hooks
│ │ │ └── useApproveToken.ts
│ ├── TopBar
│ │ ├── ThemeSwitch.tsx
│ │ ├── TopBar.scss
│ │ ├── TopBar.tsx
│ │ ├── Wallet
│ │ │ └── hooks
│ │ │ │ └── useFaucet.ts
│ │ └── __tests__
│ │ │ ├── ThemeSwitch.test.unit.tsx
│ │ │ └── __snapshots__
│ │ │ └── ThemeSwitch.test.unit.tsx.snap
│ ├── WalletBalance
│ │ └── WalletBalance.tsx
│ ├── WalletConnectedGuard.tsx
│ └── library
│ │ └── NavItem.tsx
├── constants.ts
├── constants
│ ├── addresses.ts
│ ├── contracts.ts
│ └── tokens.ts
├── generated
│ └── coolerLoans.ts
├── helpers
│ ├── AllExternalPools.ts
│ ├── CountdownTimer.tsx
│ ├── DateHelper.ts
│ ├── DecimalBigNumber
│ │ ├── DecimalBigNumber.ts
│ │ └── DecimalBigNumber.unit.test.ts
│ ├── Migration.tsx
│ ├── Migration.unit.test.js
│ ├── NumberHelper.test.ts
│ ├── NumberHelper.ts
│ ├── SearchParamsHelper.test.ts
│ ├── SearchParamsHelper.ts
│ ├── ZapHelper.test.ts
│ ├── ZapHelper.ts
│ ├── analytics
│ │ └── trackGAEvent.ts
│ ├── bonds
│ │ ├── sortByDiscount.test.ts
│ │ └── sortByDiscount.ts
│ ├── contracts
│ │ ├── Contract.ts
│ │ ├── Token.ts
│ │ ├── getBalancerLPToken.ts
│ │ ├── getCurveLPToken.ts
│ │ ├── getGelatoLPToken.ts
│ │ ├── getLPTokenByAddress.ts
│ │ ├── getTokenByAddress.ts
│ │ └── getUniOrSushiLPToken.ts
│ ├── defiLlamaChainToNetwork.test.ts
│ ├── defiLlamaChainToNetwork.ts
│ ├── environment
│ │ └── Environment
│ │ │ ├── Environment.ts
│ │ │ └── Environment.unit.test.ts
│ ├── index.tsx
│ ├── misc
│ │ └── isValidAddress.ts
│ ├── normalizeSymbol.test.ts
│ ├── normalizeSymbol.ts
│ ├── pricing
│ │ ├── calculateBalancerLPValue.ts
│ │ ├── calculateCurveLPValue.ts
│ │ ├── calculateGelatoLPValue.ts
│ │ ├── calculateUniOrSushiLPValue.ts
│ │ ├── getCoingeckoPrice.ts
│ │ └── useGetDefillamaPrice.ts
│ ├── providers
│ │ └── Providers
│ │ │ ├── Providers.ts
│ │ │ └── Providers.unit.test.ts
│ ├── react-query
│ │ ├── getQueryData.ts
│ │ └── queryAssertion.ts
│ ├── subgraph
│ │ ├── Constants.ts
│ │ ├── ProtocolMetricsHelper.ts
│ │ ├── TreasuryQueryHelper.ts
│ │ └── __tests__
│ │ │ └── ProtocolMetricsHelper.unit.test.ts
│ ├── timeUtil.test.ts
│ ├── timeUtil.ts
│ ├── truncateAddress.ts
│ └── types
│ │ ├── assert.test.ts
│ │ ├── assert.ts
│ │ ├── enumToArray.ts
│ │ └── nonNullable.ts
├── hooks
│ ├── __tests__
│ │ └── wagmi.test.ts
│ ├── index.ts
│ ├── useBalance.ts
│ ├── useBridging.ts
│ ├── useCheckSecondsToNextEpoch.ts
│ ├── useContract.ts
│ ├── useContractAllowance.ts
│ ├── useCurrentIndex.ts
│ ├── useFederatedSubgraphQuery.ts
│ ├── useFetchZeroExSwapData.ts
│ ├── useGetLPStats.ts
│ ├── useGetLendBorrowStats.ts
│ ├── useGoogleAnalytics.ts
│ ├── useHasDust.ts
│ ├── useOldAssetsDetected.ts
│ ├── useOldAssetsEnoughToMigrate.ts
│ ├── usePathForNetwork.ts
│ ├── usePrices.ts
│ ├── useProtocolMetrics.ts
│ ├── useScreenSize.ts
│ ├── useStakingRebaseRate.ts
│ ├── useTestMode.ts
│ ├── useTestableNetworks.ts
│ ├── useTheme.ts
│ ├── useTokenPrice.ts
│ ├── useTokenRecordsMetrics.ts
│ ├── useTokenSupplyMetrics.ts
│ ├── useTreasuryMetrics.ts
│ ├── useTriggerRebase.ts
│ ├── useWarmupInfo.ts
│ ├── useZeroExSwap.ts
│ └── wagmi.ts
├── index.tsx
├── lib
│ ├── Bond.ts
│ ├── EthersTypes.ts
│ └── react-query.tsx
├── networkDetails.ts
├── react-app-env.d.ts
├── setupTests.tsx
├── slices
│ ├── AccountSlice.ts
│ ├── AppSlice.ts
│ ├── MigrateThunk.ts
│ ├── PendingTxnsSlice.ts
│ ├── StakeThunk.ts
│ └── interfaces.ts
├── store.ts
├── style.scss
├── testHandlers.js
├── testHelpers.ts
├── testUtils.tsx
├── testWagmiUtils.ts
├── themes
│ ├── dark.js
│ ├── darkPalette.js
│ ├── fonts.js
│ ├── girth.js
│ ├── global.js
│ ├── light.js
│ └── lightPalette.js
├── types
│ └── react-step-progress-bar.d.ts
└── views
│ ├── 404
│ ├── NotFound.scss
│ ├── NotFound.tsx
│ └── __tests__
│ │ └── NotFound.unit.test.tsx
│ ├── Bond
│ ├── Bond.tsx
│ ├── __mocks__
│ │ └── mockLiveMarkets.tsx
│ ├── __tests__
│ │ ├── Bond.unit.test.jsx
│ │ └── InverseBond.unit.test.jsx
│ ├── components
│ │ ├── BondDiscount.tsx
│ │ ├── BondDuration.tsx
│ │ ├── BondInfoText.tsx
│ │ ├── BondList.tsx
│ │ ├── BondModal
│ │ │ ├── BondModal.tsx
│ │ │ ├── BondModalContainerV3.tsx
│ │ │ └── components
│ │ │ │ ├── BondConfirmModal.tsx
│ │ │ │ ├── BondInputArea
│ │ │ │ ├── BondInputArea.tsx
│ │ │ │ └── hooks
│ │ │ │ │ └── usePurchaseBond.ts
│ │ │ │ └── BondSettingsModal.tsx
│ │ ├── BondPrice.tsx
│ │ └── ClaimBonds
│ │ │ ├── ClaimBonds.tsx
│ │ │ ├── ClaimBondsV3.tsx
│ │ │ └── hooks
│ │ │ ├── useBondNotes.ts
│ │ │ ├── useClaimBonds.ts
│ │ │ └── useClaimBondsV3.ts
│ ├── hooks
│ │ ├── useBond.ts
│ │ ├── useBondTokens.ts
│ │ ├── useBondV3.ts
│ │ └── useLiveBonds.ts
│ └── index.tsx
│ ├── Bridge
│ ├── __tests__
│ │ └── Bridge.unit.test.tsx
│ ├── components
│ │ ├── BridgeConfirmModal.tsx
│ │ ├── BridgeFees.tsx
│ │ ├── BridgeInputArea.tsx
│ │ ├── BridgeSettingsModal.tsx
│ │ └── ChainPickerModal.tsx
│ ├── helpers
│ │ └── index.ts
│ └── index.tsx
│ ├── Emission
│ ├── hooks
│ │ └── useGetEmissionConfig.ts
│ └── index.tsx
│ ├── Governance
│ ├── Components
│ │ ├── CallData.tsx
│ │ ├── ContractParameters.tsx
│ │ ├── CoolerV2GovernanceTableRow.tsx
│ │ ├── CurrentVotes.tsx
│ │ ├── GovernanceNavigation.tsx
│ │ ├── GovernanceTableRow.tsx
│ │ ├── ProposalContainer.tsx
│ │ ├── Status.tsx
│ │ ├── VoteModal.tsx
│ │ └── VotingOutcomeBar.tsx
│ ├── Delegation
│ │ ├── DelegateDetails.tsx
│ │ ├── DelegateRow.tsx
│ │ ├── DelegateVotingModal.tsx
│ │ ├── DelegationMessage.tsx
│ │ ├── index.tsx
│ │ └── manage.tsx
│ ├── Proposals
│ │ ├── VoteDetails.tsx
│ │ ├── VoteRow.tsx
│ │ └── index.tsx
│ ├── helpers
│ │ ├── fetchFunctionInterface.ts
│ │ ├── index.ts
│ │ └── normalizeProposal.ts
│ ├── hooks
│ │ ├── dev
│ │ │ ├── GovernanceDevTools.tsx
│ │ │ ├── useAddChain.tsx
│ │ │ ├── useCancelProposal.tsx
│ │ │ ├── useCreateProposal.tsx
│ │ │ ├── useMineBlocks.tsx
│ │ │ └── useVetoProposal.tsx
│ │ ├── useActivateProposal.tsx
│ │ ├── useCheckDelegation.tsx
│ │ ├── useDelegateVoting.tsx
│ │ ├── useExecuteProposal.tsx
│ │ ├── useGetCanceledTime.tsx
│ │ ├── useGetContractParameters.tsx
│ │ ├── useGetCurrentBlockTime.tsx
│ │ ├── useGetDelegate.tsx
│ │ ├── useGetDelegates.tsx
│ │ ├── useGetExecutedTime.tsx
│ │ ├── useGetProposalDetails.tsx
│ │ ├── useGetProposalFromSubgraph.tsx
│ │ ├── useGetProposalsFromSubgraph.tsx
│ │ ├── useGetQueuedTime.tsx
│ │ ├── useGetReceipt.tsx
│ │ ├── useGetVetoedTime.tsx
│ │ ├── useGetVotes.tsx
│ │ ├── useGetVotingWeight.tsx
│ │ ├── useGovernanceDelegationCheck.tsx
│ │ ├── useQueueProposal.tsx
│ │ └── useVoteForProposal.tsx
│ └── index.tsx
│ ├── Lending
│ ├── Cooler
│ │ ├── dashboard
│ │ │ ├── Dashboard.tsx
│ │ │ ├── IncomeGraph.tsx
│ │ │ ├── MaturityGraph.tsx
│ │ │ ├── Metrics.tsx
│ │ │ └── UtilisationGraph.tsx
│ │ ├── hooks
│ │ │ ├── customHttpClient.ts
│ │ │ ├── useConsolidateCooler.tsx
│ │ │ ├── useCreateCooler.tsx
│ │ │ ├── useCreateLoan.tsx
│ │ │ ├── useExtendLoan.tsx
│ │ │ ├── useGetClearingHouse.tsx
│ │ │ ├── useGetConsolidationAllowances.tsx
│ │ │ ├── useGetCoolerBalance.tsx
│ │ │ ├── useGetCoolerForWallet.tsx
│ │ │ ├── useGetCoolerLoans.tsx
│ │ │ ├── useGetWalletFundsRequired.tsx
│ │ │ ├── useRepayLoan.tsx
│ │ │ └── useSnapshot.tsx
│ │ ├── index.tsx
│ │ └── positions
│ │ │ ├── ConsolidateLoan.tsx
│ │ │ ├── CreateOrRepayLoan.tsx
│ │ │ ├── ExtendLoan.tsx
│ │ │ └── Positions.tsx
│ ├── CoolerV2
│ │ ├── components
│ │ │ ├── CollateralInputCard.tsx
│ │ │ ├── CoolerV2DelegationModal.tsx
│ │ │ ├── CreateOrRepayLoanV2.tsx
│ │ │ ├── DebtInputCard.tsx
│ │ │ ├── DelegationManagement.tsx
│ │ │ ├── DevTools.tsx
│ │ │ ├── LoanInformation.tsx
│ │ │ ├── LoanToValueSlider.tsx
│ │ │ ├── MonoCoolerPositions.tsx
│ │ │ └── PositionOverview.tsx
│ │ ├── hooks
│ │ │ ├── useMonoCoolerCalculations.tsx
│ │ │ ├── useMonoCoolerCapacity.tsx
│ │ │ ├── useMonoCoolerDebt.tsx
│ │ │ ├── useMonoCoolerDelegations.tsx
│ │ │ └── useMonoCoolerPosition.tsx
│ │ └── utils
│ │ │ └── getAuthorizationSignature.ts
│ └── LendingMarkets.tsx
│ ├── Liquidity
│ ├── ClaimModal.tsx
│ ├── ConfirmationModal.tsx
│ ├── DepositStepsModal.tsx
│ ├── ExternalStakePools
│ │ ├── ExternalStakePools.tsx
│ │ └── __tests__
│ │ │ └── ExternalStakePools.test.tsx
│ ├── Vault.tsx
│ ├── Vaults.tsx
│ ├── WithdrawModal.tsx
│ ├── YourAMODeposits.tsx
│ ├── ZapSteps.tsx
│ └── hooks
│ │ ├── useClaimRewards.tsx
│ │ ├── useCreateUserVault.tsx
│ │ ├── useDepositLiquidity.tsx
│ │ ├── useGetExpectedPairTokenAmount.tsx
│ │ ├── useGetLastDeposit.tsx
│ │ ├── useGetSingleSidedLiquidityVaults.tsx
│ │ ├── useGetUserVault.tsx
│ │ ├── useGetVault.tsx
│ │ └── useWithdrawLiquidity.tsx
│ ├── MyBalances
│ ├── LearnAboutGohm.tsx
│ ├── LearnAboutOhm.tsx
│ ├── MyCoolerLoans.tsx
│ ├── MyGohmBalances.tsx
│ ├── MyOhmBalances.tsx
│ └── index.tsx
│ ├── Range
│ ├── RangeChart.tsx
│ ├── RangeConfirmationModal.tsx
│ ├── RangeInputForm.tsx
│ ├── __mocks__
│ │ └── mockRangeCalls.tsx
│ ├── __tests__
│ │ ├── Range.test.tsx
│ │ ├── RangeBondsLower.test.tsx
│ │ └── RangeBondsUpper.test.tsx
│ ├── hooks.tsx
│ └── index.tsx
│ ├── Stake
│ ├── Stake.scss
│ ├── Stake.tsx
│ ├── __tests__
│ │ ├── Stake.unit.test.tsx
│ │ └── __snapshots__
│ │ │ └── StakeMobile.unit.test.jsx.snap
│ └── components
│ │ ├── ClaimsArea
│ │ └── ClaimsArea.tsx
│ │ └── StakeArea
│ │ ├── StakeArea.tsx
│ │ ├── __tests__
│ │ ├── StakeArea.unit.test.jsx
│ │ ├── StakeAreaHooks.unit.text.jsx
│ │ └── Wrap.unit.test.jsx
│ │ └── components
│ │ ├── StakeBalances.tsx
│ │ └── StakeInputArea
│ │ ├── StakeInputArea.tsx
│ │ ├── components
│ │ ├── StakeConfirmationModal.tsx
│ │ └── TokenModal.tsx
│ │ └── hooks
│ │ ├── useClaimToken.ts
│ │ ├── useForfeitToken.ts
│ │ ├── useStakeToken.ts
│ │ └── useUnstakeToken.ts
│ ├── TreasuryDashboard
│ ├── TreasuryDashboard.tsx
│ ├── __tests__
│ │ ├── TreasuryDashboard.unit.test.tsx
│ │ └── TreasuryMobile.unit.test.tsx
│ └── components
│ │ ├── DataWarning.tsx
│ │ ├── Graph
│ │ ├── ChartCard.tsx
│ │ ├── Constants.ts
│ │ ├── LiquidBackingComparisonGraph.tsx
│ │ ├── OhmSupply.tsx
│ │ ├── OhmSupplyGraph.tsx
│ │ ├── OhmSupplyTable.tsx
│ │ ├── OwnedLiquidityGraph.tsx
│ │ ├── TreasuryAssets.tsx
│ │ ├── TreasuryAssetsGraph.tsx
│ │ ├── TreasuryAssetsTable.tsx
│ │ └── helpers
│ │ │ ├── ChartHelper.tsx
│ │ │ ├── TokenRecordsQueryHelper.ts
│ │ │ └── __tests__
│ │ │ └── TokenRecordsQueryHelper.unit.test.tsx
│ │ ├── KnownIssues
│ │ └── KnownIssues.tsx
│ │ └── Metric
│ │ └── Metric.tsx
│ ├── Utility
│ └── index.tsx
│ ├── V1-Stake
│ ├── V1-Stake.jsx
│ ├── V1-Stake.scss
│ └── __tests__
│ │ └── V1-Stake.unit.test.tsx
│ ├── Wrap
│ └── components
│ │ └── WrapInputArea
│ │ └── hooks
│ │ └── useWrapSohm.tsx
│ ├── Zap
│ ├── SlippageModal.tsx
│ ├── ZapTransactionDetails.tsx
│ └── __mocks__
│ │ └── mockZapBalances.tsx
│ └── index.ts
├── tests
├── e2e
│ ├── testHelpers.ts
│ └── tsconfig.json
└── unit
│ └── launcher.js
├── tsconfig.json
├── vite-env.d.ts
├── vite.config.js
└── yarn.lock
/.env.example:
--------------------------------------------------------------------------------
1 |
2 | # Optional
3 | # Google Analytics (https://analytics.google.com/)
4 | VITE_GOOGLE_ANALYTICS_API_KEY=""
5 | # Google Analytics 4 API Key
6 | VITE_GA_4_API_KEY=""
7 |
8 | # Optional
9 | # If you run your own node, you can provide connection url(s) to that node.
10 | # To provide multiple urls for a network, please seperate them with a space.
11 | VITE_ETHEREUM_NODE_URL=""
12 | VITE_ETHEREUM_TESTNET_NODE_URL=""
13 |
14 | VITE_FANTOM_NODE_URL=""
15 | VITE_FANTOM_TESTNET_NODE_URL=""
16 |
17 | VITE_POLYGON_NODE_URL=""
18 | VITE_POLYGON_TESTNET_NODE_URL=""
19 |
20 |
21 | VITE_ARBITRUM_NODE_URL=""
22 | VITE_ARBITRUM_TESTNET_NODE_URL=""
23 |
24 | VITE_AVALANCHE_NODE_URL=""
25 | VITE_AVALANCHE_TESTNET_NODE_URL=""
26 |
27 | # Get a wallet connect project id here: https://cloud.walletconnect.com
28 | VITE_WALLETCONNECT_PROJECT_ID=""
29 |
30 | #Subgraph URL for Protocol Metrics. If not set Protocol Metrics will not be displayed.
31 | VITE_WG_PUBLIC_NODE_URL=""
32 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | node: true,
4 | jest: true,
5 | browser: true,
6 | },
7 | parser: "@typescript-eslint/parser",
8 | parserOptions: {
9 | ecmaVersion: 12,
10 | sourceType: "module",
11 | ecmaFeatures: {
12 | jsx: true,
13 | },
14 | },
15 | extends: [
16 | "plugin:prettier/recommended",
17 | "plugin:react-hooks/recommended",
18 | "plugin:@typescript-eslint/recommended",
19 | "plugin:@typescript-eslint/eslint-recommended",
20 | ],
21 | plugins: ["@typescript-eslint", "simple-import-sort", "unused-imports", "no-relative-import-paths"],
22 | rules: {
23 | "prettier/prettier": ["error"],
24 | "import/prefer-default-export": "off",
25 | "prefer-destructuring": "off",
26 | "prefer-template": "off",
27 | "react/prop-types": "off",
28 | "react/destructuring-assignment": "off",
29 | "no-console": "off",
30 | "jsx-a11y/accessible-emoji": ["off"],
31 | "jsx-a11y/click-events-have-key-events": ["off"],
32 | "jsx-a11y/no-static-element-interactions": ["off"],
33 | "no-underscore-dangle": "off",
34 | "no-nested-ternary": "off",
35 | "no-restricted-syntax": "off",
36 | "no-plusplus": "off",
37 | "simple-import-sort/imports": "error",
38 | "unused-imports/no-unused-imports": "error",
39 | "@typescript-eslint/explicit-function-return-type": "off",
40 | "@typescript-eslint/explicit-module-boundary-types": "off",
41 | "@typescript-eslint/ban-ts-comment": "off",
42 | "@typescript-eslint/ban-ts-ignore": "off",
43 | "no-relative-import-paths/no-relative-import-paths": ["warn"],
44 | },
45 | ignorePatterns: ["build", "node_modules"],
46 | globals: {
47 | React: true,
48 | JSX: true,
49 | },
50 | overrides: [
51 | {
52 | files: ["**/*.js", "**/*.jsx"],
53 | rules: {
54 | "no-undef": "error",
55 | },
56 | },
57 | ],
58 | };
59 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Create a report to help us improve
4 | title: "[BUG] "
5 | labels: bug, triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the Bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected Behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Workarounds (if any)**
24 | Is there an option to get around this bug to the intended action?
25 |
26 | **Screenshots**
27 | If applicable, add screenshots to help explain your problem.
28 |
29 | **Desktop (please complete the following information):**
30 | - OS: [e.g. iOS]
31 | - Browser [e.g. chrome, safari]
32 | - Version [e.g. 22]
33 |
34 | **Smartphone (please complete the following information):**
35 | - Device: [e.g. iPhone6]
36 | - OS: [e.g. iOS8.1]
37 | - Browser [e.g. stock browser, safari]
38 | - Version [e.g. 22]
39 |
40 | **Additional Context**
41 | Add any other context about the problem here.
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like. Include clear acceptance criteria.**
14 | A clear and concise description of what you want to happen using the Given, When, Then framework - "Given, I am in the Olympus app, When I want to stake, Then I should have the ability to stake through my wallet provider." You can include multiple examples of acceptance criteria.
15 |
16 | **Is any UX Design input needed?**
17 | If yes, give a description of what is required.
18 |
19 | **Describe alternatives you've considered**
20 | A clear and concise description of any alternative solutions or features you've considered.
21 |
22 | **Tech Notes/Testing Notes**
23 | Include any relevant information related to tech capabilities or testing needed to implement this feature.
24 |
25 | **Out of scope**
26 | Include any necessary out of scope information to make clear what this feature is not delivering.
27 |
28 | **Additional context**
29 | Add any other context or screenshots about the feature request here.
30 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'npm'
4 | directory: '/'
5 | open-pull-requests-limit: 7
6 | reviewers:
7 | - 'OlympusDAO/Frontend'
8 | schedule:
9 | interval: 'daily'
10 | time: '04:00'
11 | target-branch: 'develop'
12 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [master, develop]
6 |
7 | jobs:
8 | analyze:
9 | name: Analyze
10 | runs-on: ubuntu-latest
11 |
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | language: ["javascript"]
16 |
17 | steps:
18 | - name: Checkout repository
19 | uses: actions/checkout@v2
20 |
21 | - name: Initialize CodeQL
22 | uses: github/codeql-action/init@v2
23 | with:
24 | languages: ${{ matrix.language }}
25 |
26 | - name: Autobuild
27 | uses: github/codeql-action/autobuild@v2
28 |
29 | - name: Perform CodeQL Analysis
30 | uses: github/codeql-action/analyze@v2
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | packages/subgraph/subgraph.yaml
2 | packages/subgraph/generated
3 | packages/subgraph/abis
4 | packages/hardhat/*.txt
5 | **/aws.json
6 |
7 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
8 | **/node_modules
9 | packages/hardhat/artifacts
10 | packages/hardhat/deployments
11 | packages/react-app/src/contracts/*
12 | !packages/react-app/src/contracts/contracts.js
13 | packages/hardhat/cache
14 |
15 | packages/subgraph/config/config.json
16 | tenderly.yaml
17 |
18 | # dependencies
19 | /node_modules
20 | /.pnp
21 | .pnp.js
22 |
23 | # testing
24 | coverage
25 | tests/e2e/videos
26 | base-report.json
27 | branch-report.json
28 |
29 | # production
30 | build
31 |
32 | # misc
33 | .DS_Store
34 | .env
35 |
36 | # debug
37 | npm-debug.log*
38 | yarn-debug.log*
39 | yarn-error.log*
40 |
41 | # Integrated development environnements
42 | .idea
43 | *.sublime-*
44 |
45 | # Hardhat
46 | cache
47 |
48 | .vscode
49 |
50 | src/typechain
51 |
52 | # We use Yarn - prevent this file from being committed to avoid confusion
53 | package-lock.json
54 |
55 | tmp/
56 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/.gitmodules
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | node_modules/.bin/lint-staged
5 |
--------------------------------------------------------------------------------
/.linguirc:
--------------------------------------------------------------------------------
1 | {
2 | "locales": ["en", "fr", "ko", "tr", "pt", "zh", "ar", "de", "es", "vi", "pl", "ru", "el", "it", "se"],
3 | "sourceLocale": "en",
4 | "catalogs": [{
5 | "path": "src/locales/translations/olympus-frontend/{locale}/messages",
6 | "include": ["src"]
7 | }],
8 | "fallbackLocales": {
9 | "default": "en"
10 | },
11 | "format": "po"
12 | }
13 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "printWidth": 120,
5 | "singleQuote": false,
6 | "tabWidth": 2,
7 | "trailingComma": "all"
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Austin Griffith 2021
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.jpg";
2 | declare module "*.png";
3 | declare module "*.svg";
4 |
5 | export default global;
6 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
16 |
18 |
19 |
20 |
21 | OlympusDAO
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/orval.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'orval';
2 |
3 | export default defineConfig({
4 | coolerLoans: {
5 | input: "tmp/openapi.yaml",
6 | output: {
7 | target: "src/generated/coolerLoans.ts",
8 | client: "react-query",
9 | clean: true,
10 | override: {
11 | mutator: {
12 | path: "src/views/Lending/Cooler/hooks/customHttpClient.ts",
13 | name: "customHttpClient",
14 | },
15 | useTypeOverInterfaces: true,
16 | }
17 | },
18 | hooks: {
19 | afterAllFilesWrite: "yarn lint:fix",
20 | },
21 | },
22 | });
23 |
--------------------------------------------------------------------------------
/public/assets/images/base.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/images/ethereum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/assets/images/ethereum.png
--------------------------------------------------------------------------------
/public/assets/images/give/angel-protocol/angel-protocol-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/assets/images/give/angel-protocol/angel-protocol-logo.png
--------------------------------------------------------------------------------
/public/assets/images/give/gitcoin/gitcoin-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/assets/images/give/gitcoin/gitcoin-logo.png
--------------------------------------------------------------------------------
/public/assets/images/give/impact-market/impact-market-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/assets/images/give/impact-market/impact-market-logo.png
--------------------------------------------------------------------------------
/public/assets/images/give/kolektivo/kolektivo-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/assets/images/give/kolektivo/kolektivo-logo.png
--------------------------------------------------------------------------------
/public/assets/images/give/popcorndao/popcorndao-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/assets/images/give/popcorndao/popcorndao-logo.png
--------------------------------------------------------------------------------
/public/assets/images/grants/entropy/ethereum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/assets/images/grants/entropy/ethereum.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "OlympusDAO",
3 | "description": "A community-owned, decentralized and censorship-resistant reserve currency that is asset-backed, deeply liquid and used widely across Web3",
4 | "iconPath": "logo.svg"
5 | }
6 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/scripts/fix-coverage-report.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const coverageFinalFilename = "coverage-final.json";
3 | const outputFile = process.argv[2] && process.argv[2] === "--outputFile" ? process.argv[3] : "report.json";
4 | const cwd = process.cwd();
5 | const reportJsonFilepath = `${cwd}/${outputFile}`;
6 | const coverageFinalFilepath = `${cwd}/coverage/${coverageFinalFilename}`;
7 | let reportJsonFile;
8 | let coverageFinalJsonFile;
9 | if (fs.existsSync(reportJsonFilepath)) {
10 | reportJsonFile = require(reportJsonFilepath);
11 | }
12 | if (fs.existsSync(coverageFinalFilepath)) {
13 | coverageFinalJsonFile = require(coverageFinalFilepath);
14 | }
15 | console.log("Files exists?", { outputFilename: !!reportJsonFile, coverageFinalFilename: !!coverageFinalJsonFile });
16 | if (reportJsonFile && coverageFinalJsonFile) {
17 | if (!reportJsonFile.coverageMap) {
18 | console.log(`Adding coverageMap property to ${outputFile} based on ${coverageFinalFilename}`);
19 | reportJsonFile.coverageMap = coverageFinalJsonFile;
20 | fs.writeFileSync(reportJsonFilepath, JSON.stringify(reportJsonFile), err => {
21 | if (err) {
22 | console.error(err);
23 | process.exit(1);
24 | }
25 | });
26 | } else {
27 | console.log(`coverageMap already exists in ${outputFile}, not doing anything...`);
28 | process.exit(0);
29 | }
30 | } else {
31 | console.log("Not doing anything...");
32 | process.exit(0);
33 | }
34 |
--------------------------------------------------------------------------------
/src/.gitignore:
--------------------------------------------------------------------------------
1 | e2e/videos
2 | e2e/screenshots
3 |
--------------------------------------------------------------------------------
/src/Root.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | import { StyledEngineProvider } from "@mui/material/styles";
3 | import { FC } from "react";
4 | import { Provider } from "react-redux";
5 | import { HashRouter } from "react-router-dom";
6 | import App from "src/App";
7 | import { wagmiClient } from "src/hooks/wagmi";
8 | import { ReactQueryProvider } from "src/lib/react-query";
9 | import store from "src/store";
10 | import { WagmiConfig } from "wagmi";
11 |
12 | const Root: FC = () => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default Root;
29 |
--------------------------------------------------------------------------------
/src/abi/StakingHelper.json:
--------------------------------------------------------------------------------
1 | {
2 | "abi": [
3 | {
4 | "inputs": [
5 | { "internalType": "address", "name": "_staking", "type": "address" },
6 | { "internalType": "address", "name": "_OHM", "type": "address" }
7 | ],
8 | "stateMutability": "nonpayable",
9 | "type": "constructor"
10 | },
11 | {
12 | "inputs": [],
13 | "name": "OHM",
14 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
15 | "stateMutability": "view",
16 | "type": "function"
17 | },
18 | {
19 | "inputs": [{ "internalType": "uint256", "name": "_amount", "type": "uint256" }],
20 | "name": "stake",
21 | "outputs": [],
22 | "stateMutability": "nonpayable",
23 | "type": "function"
24 | },
25 | {
26 | "inputs": [],
27 | "name": "staking",
28 | "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
29 | "stateMutability": "view",
30 | "type": "function"
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/src/abi/ZeroDistributor.json:
--------------------------------------------------------------------------------
1 | {
2 | "abi": [
3 | {
4 | "inputs": [{ "internalType": "address", "name": "staking_", "type": "address" }],
5 | "stateMutability": "nonpayable",
6 | "type": "constructor"
7 | },
8 | { "inputs": [], "name": "Distributor_NoRebaseOccurred", "type": "error" },
9 | { "inputs": [], "name": "Distributor_NotUnlocked", "type": "error" },
10 | { "inputs": [], "name": "Distributor_OnlyStaking", "type": "error" },
11 | { "inputs": [], "name": "distribute", "outputs": [], "stateMutability": "nonpayable", "type": "function" },
12 | {
13 | "inputs": [],
14 | "name": "retrieveBounty",
15 | "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
16 | "stateMutability": "pure",
17 | "type": "function"
18 | },
19 | {
20 | "inputs": [],
21 | "name": "staking",
22 | "outputs": [{ "internalType": "contract IStaking", "name": "", "type": "address" }],
23 | "stateMutability": "view",
24 | "type": "function"
25 | },
26 | { "inputs": [], "name": "triggerRebase", "outputs": [], "stateMutability": "nonpayable", "type": "function" }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/assets/OHM shape.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/Olympus Logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/arbitrum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/src/assets/arbitrum.png
--------------------------------------------------------------------------------
/src/assets/fonts/NHaasGroteskDSPro-55Rg.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/src/assets/fonts/NHaasGroteskDSPro-55Rg.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/NHaasGroteskDSPro-65Md.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/src/assets/fonts/NHaasGroteskDSPro-65Md.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/NHaasGroteskDSPro-75Bd.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/src/assets/fonts/NHaasGroteskDSPro-75Bd.woff2
--------------------------------------------------------------------------------
/src/assets/icons/arrow-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
45 |
--------------------------------------------------------------------------------
/src/assets/icons/arrow-up.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fullscreen.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/graph-grt-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
--------------------------------------------------------------------------------
/src/assets/icons/hamburger.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/heart.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/olympus-nav-header.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/step-1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/step-2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/step-complete.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/time-remaining.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/wallet.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/x.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/33-together-spritesheet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/src/assets/images/33-together-spritesheet.png
--------------------------------------------------------------------------------
/src/assets/known-issues-commit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/src/assets/known-issues-commit.png
--------------------------------------------------------------------------------
/src/assets/tokens/avax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OlympusDAO/olympus-frontend/f793fb0b1b92d9f7d9923b321f171b1458ceea5f/src/assets/tokens/avax.png
--------------------------------------------------------------------------------
/src/assets/tokens/matic.svg:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/src/assets/tokens/token_OHM.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/tokens/token_sOHM.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/tokens/usds.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/src/assets/tokens/wETH.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/components/CallToAction/CallToAction.scss:
--------------------------------------------------------------------------------
1 | .call-to-action {
2 | display: flex;
3 | @media (max-width: 500px) {
4 | flex-direction: column;
5 | & .actionable {
6 | margin-top: 10px;
7 | width: 100%;
8 | }
9 | }
10 | align-items: center;
11 | justify-content: space-between;
12 | width: 97%;
13 | border-radius: 10px;
14 | margin: 0.9rem auto;
15 | padding: 30px;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/CallToAction/CallToAction.tsx:
--------------------------------------------------------------------------------
1 | import "src/components/CallToAction/CallToAction.scss";
2 |
3 | import { Box, Typography } from "@mui/material";
4 | import { Paper, PrimaryButton, TertiaryButton } from "@olympusdao/component-library";
5 |
6 | export const LearnMoreButton = () => (
7 |
8 | Learn More
9 |
10 | );
11 |
12 | export interface MigrationButtonProps {
13 | setMigrationModalOpen: (state: boolean) => void;
14 | btnText: string;
15 | }
16 |
17 | export const MigrateButton = ({ setMigrationModalOpen, btnText }: MigrationButtonProps) => (
18 | {
20 | setMigrationModalOpen(true);
21 | }}
22 | >
23 | {btnText}
24 |
25 | );
26 |
27 | export interface CallToActionProps {
28 | setMigrationModalOpen: (state: boolean) => void;
29 | }
30 |
31 | const CallToAction = ({ setMigrationModalOpen }: CallToActionProps) => (
32 |
33 |
34 |
35 |
36 | You have assets ready to migrate to v2
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 |
47 | export default CallToAction;
48 |
--------------------------------------------------------------------------------
/src/components/CallToAction/__tests__/CallToAction.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { LearnMoreButton, MigrateButton } from "src/components/CallToAction/CallToAction";
2 | import CallToAction from "src/components/CallToAction/CallToAction";
3 | import { fireEvent, render } from "src/testUtils";
4 | import { describe, expect, it, test, vi } from "vitest";
5 |
6 | describe("", () => {
7 | test("should render component", () => {
8 | it("should render component", () => {
9 | const container = render( console.log("setMigrationModalOpen")} />);
10 | expect(container).toMatchSnapshot();
11 | });
12 | });
13 | });
14 |
15 | describe("LearnMoreButton", () => {
16 | it("renders a link to the Olympus DAO migration docs", () => {
17 | const container = render();
18 | const link = container.getByRole("link", { name: /learn more/i });
19 | expect(link).toHaveProperty("href", "https://docs.olympusdao.finance/main/basics/migration");
20 | });
21 | });
22 |
23 | describe("MigrateButton", () => {
24 | it("renders button with correct text", () => {
25 | const { getByText } = render();
26 | expect(getByText("Migrate Now")).not.toBeNull();
27 | });
28 |
29 | it("calls setMigrationModalOpen when clicked", () => {
30 | const setMigrationModalOpen = vi.fn();
31 | const { getByRole } = render();
32 | const button = getByRole("button");
33 | fireEvent.click(button);
34 | expect(setMigrationModalOpen).toHaveBeenCalledTimes(1);
35 | expect(setMigrationModalOpen).toHaveBeenCalledWith(true);
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/src/components/Chart/Constants.ts:
--------------------------------------------------------------------------------
1 | export enum DataFormat {
2 | Currency,
3 | Percentage,
4 | DateMonth,
5 | Number,
6 | None,
7 | }
8 |
9 | export enum ChartType {
10 | Line,
11 | MultiLine,
12 | Area,
13 | StackedArea,
14 | Bar,
15 | AreaDifference,
16 | Composed,
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Chart/__tests__/Chart.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { formatCurrencyTick, formatPercentTick } from "src/components/Chart/Chart";
2 | import { describe, expect, test } from "vitest";
3 |
4 | describe("curency tick formatter", () => {
5 | test("shortens millions", () => {
6 | expect(formatCurrencyTick(10000000)).toEqual("$10M");
7 | });
8 |
9 | test("shortens millions with decimal", () => {
10 | expect(formatCurrencyTick(10000000.01)).toEqual("$10M");
11 | });
12 |
13 | test("shortens thousands", () => {
14 | expect(formatCurrencyTick(500000)).toEqual("$500k");
15 | });
16 |
17 | test("shortens thousands with decimal", () => {
18 | expect(formatCurrencyTick(500000.01)).toEqual("$500k");
19 | });
20 |
21 | test("small number", () => {
22 | expect(formatCurrencyTick(18.01)).toEqual("$18.01");
23 | });
24 |
25 | test("empty value", () => {
26 | expect(formatCurrencyTick(0)).toEqual("");
27 | });
28 | });
29 |
30 | describe("percentage tick formatter", () => {
31 | test("normal number", () => {
32 | expect(formatPercentTick(23.02)).toEqual("23.02%");
33 | });
34 |
35 | test("normal string", () => {
36 | expect(formatPercentTick("23.02")).toEqual("23.02%");
37 | });
38 |
39 | test("empty value", () => {
40 | expect(formatPercentTick(0)).toEqual("");
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/components/ConnectButton/__tests__/ConnectButton.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { ConnectButton, InPageConnectButton } from "src/components/ConnectButton/ConnectButton";
2 | import { render, screen } from "src/testUtils";
3 |
4 | describe("", () => {
5 | it("should display Connect Button for TopBar", () => {
6 | render();
7 | expect(screen.getByText("Connect Wallet"));
8 | });
9 |
10 | it("should display Connect Wallet for In-Page Connect Buttons", () => {
11 | render();
12 | expect(screen.getByText("Connect Wallet"));
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/src/components/DevFaucet/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box, FormControl, MenuItem, Select, Typography } from "@mui/material";
2 | import { SecondaryButton } from "@olympusdao/component-library";
3 | import { useState } from "react";
4 | import { useFaucet } from "src/components/TopBar/Wallet/hooks/useFaucet";
5 |
6 | export const DevFaucet = () => {
7 | const PREFIX = "AssetsIndex";
8 | const [faucetToken, setFaucetToken] = useState("OHM V2");
9 | const faucetMutation = useFaucet();
10 |
11 | const classes = {
12 | selector: `${PREFIX}-selector`,
13 | forecast: `${PREFIX}-forecast`,
14 | faucet: `${PREFIX}-faucet`,
15 | };
16 | const isFaucetLoading = faucetMutation.isLoading;
17 |
18 | return (
19 | <>
20 | Dev Faucet
21 |
22 |
23 |
38 |
39 | faucetMutation.mutate(faucetToken)}>
40 | {isFaucetLoading ? "Loading..." : "Get Tokens"}
41 |
42 |
43 | >
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/components/Messages/Messages.tsx:
--------------------------------------------------------------------------------
1 | import Alert from "@mui/material/Alert";
2 | import { Icon } from "@olympusdao/component-library";
3 | import { resolveValue, toast as hotToast } from "react-hot-toast";
4 |
5 | // A component that displays error messages
6 | const Messages = ({ toast }: { toast: any }) => {
7 | return (
8 | {
27 | hotToast.dismiss(toast.id);
28 | }}
29 | />
30 | }
31 | iconMapping={{
32 | success: ,
33 | error: ,
34 | warning: ,
35 | info: ,
36 | }}
37 | elevation={12}
38 | >
39 | {resolveValue(toast.message, toast)}
40 |
41 | );
42 | };
43 |
44 | export default Messages;
45 |
--------------------------------------------------------------------------------
/src/components/Migration/MigrationModal.scss:
--------------------------------------------------------------------------------
1 | .tooltip {
2 | z-index: 2000;
3 | }
4 |
5 | .docs-link {
6 | text-decoration: inherit;
7 | font: inherit;
8 | color: inherit;
9 | }
10 |
11 | .mig-modal-full {
12 | backdrop-filter: blur(33px);
13 | }
14 |
15 | #migration-modal-description {
16 | &.mobile {
17 | font-size: 0.85rem;
18 | }
19 | line-height: 1.5em;
20 | font-size: 1rem;
21 | padding: 0px 5px;
22 | }
23 |
24 | @media (max-width: 500px) {
25 | .mig-modal-full .MuiBox-root .MuiBox-root,
26 | .mig-modal-full .MuiBox-root .MuiTable-root {
27 | max-width: 100vw;
28 | margin: 0 auto;
29 | // padding: 2% 10%;
30 | }
31 | .modal-inner {
32 | width: 250px;
33 | overflow: scroll;
34 | }
35 | }
36 |
37 | .migration-card {
38 | @supports (-webkit-backdrop-filter: none) or (backdrop-filter: none) {
39 | .MuiBackdrop-root {
40 | background: rgba(100, 100, 100, 0.33) !important;
41 | backdrop-filter: blur(33px) !important;
42 | -webkit-backdrop-filter: blur(33px) !important;
43 | .ohm-card {
44 | opacity: 1 !important;
45 | height: auto;
46 | }
47 | }
48 | }
49 |
50 | /* slightly transparent fallback for Firefox (not supporting backdrop-filter) */
51 | @supports not ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) {
52 | .MuiBackdrop-root {
53 | background: rgba(100, 100, 100, 0.95);
54 | .ohm-card {
55 | height: auto;
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/Migration/__tests__/MigrationModalSingle.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import MigrationModalSingle from "src/components/Migration/MigrationModalSingle";
2 | import { render } from "src/testUtils";
3 | import { test } from "vitest";
4 |
5 | describe("", () => {
6 | test("should render component", () => {
7 | it("should render component", () => {
8 | const { container } = render(
9 | console.log("handleClose")} />,
10 | );
11 | expect(container).toMatchSnapshot();
12 | });
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/src/components/MigrationCallToAction.tsx:
--------------------------------------------------------------------------------
1 | import { Dispatch, SetStateAction } from "react";
2 | import { useLocation } from "react-router-dom";
3 | import CallToAction from "src/components/CallToAction/CallToAction";
4 | import { useOldAssetsDetected } from "src/hooks/useOldAssetsDetected";
5 | import { useOldAssetsEnoughToMigrate } from "src/hooks/useOldAssetsEnoughToMigrate";
6 |
7 | export const MigrationCallToAction: React.FC<{ setMigrationModalOpen: Dispatch> }> = props => {
8 | const location = useLocation();
9 | const trimmedPath = location.pathname + location.hash;
10 | const oldAssetsDetected = useOldAssetsDetected();
11 | const oldAssetsEnoughToMigrate = useOldAssetsEnoughToMigrate();
12 |
13 | if (oldAssetsDetected && trimmedPath.indexOf("dashboard") === -1 && oldAssetsEnoughToMigrate)
14 | return ;
15 |
16 | return null;
17 | };
18 |
--------------------------------------------------------------------------------
/src/components/MigrationNotification.tsx:
--------------------------------------------------------------------------------
1 | import MigrationModal from "src/components/Migration/MigrationModal";
2 | import MigrationModalSingle from "src/components/Migration/MigrationModalSingle";
3 | import { useHasDust } from "src/hooks/useHasDust";
4 | import { useOldAssetsDetected } from "src/hooks/useOldAssetsDetected";
5 |
6 | export const MigrationNotification: React.FC<{ isModalOpen: boolean; onClose: () => void }> = props => {
7 | const hasDust = useHasDust();
8 | const oldAssetsDetected = useOldAssetsDetected();
9 |
10 | if (!oldAssetsDetected) return null;
11 |
12 | if (hasDust) return ;
13 |
14 | return ;
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/PageTitle.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/react";
2 | import PageTitle, { OHMPageTitleProps } from "src/components/PageTitle";
3 | import { describe, expect, it, vi } from "vitest";
4 |
5 | vi.mock("@mui/material", () => ({
6 | Box: (props: any) => ,
7 | Typography: (props: any) => ,
8 | useMediaQuery: () => false,
9 | useTheme: () => ({ breakpoints: { down: () => "" } }),
10 | }));
11 |
12 | describe("PageTitle", () => {
13 | const props: OHMPageTitleProps = { name: "Splash Page" };
14 |
15 | it("renders the component with the correct name", () => {
16 | const { getByText } = render();
17 | expect(getByText("Splash Page")).not.toBeNull();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/components/PageTitle.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
2 | import { FC, ReactElement } from "react";
3 |
4 | export interface OHMPageTitleProps {
5 | name?: string | ReactElement;
6 | subtitle?: string | ReactElement;
7 | noMargin?: boolean;
8 | }
9 |
10 | /**
11 | * Component for Displaying PageTitle
12 | */
13 | const PageTitle: FC = ({ name, subtitle, noMargin }) => {
14 | const theme = useTheme();
15 | const mobile = useMediaQuery(theme.breakpoints.down("sm"));
16 |
17 | return (
18 |
24 |
25 | {name}
26 |
27 | {subtitle && (
28 |
29 | {subtitle}
30 |
31 | )}
32 |
33 | );
34 | };
35 |
36 | export default PageTitle;
37 |
--------------------------------------------------------------------------------
/src/components/SafariFooter.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/react";
2 | import { SafariFooter } from "src/components/SafariFooter";
3 | import { afterAll, beforeAll, describe, expect, it } from "vitest";
4 |
5 | describe("SafariFooter", () => {
6 | let userAgent: string;
7 |
8 | beforeAll(() => {
9 | userAgent = navigator.userAgent;
10 | Object.defineProperty(window, "navigator", {
11 | value: { userAgent: "iPhone Safari" },
12 | writable: true,
13 | });
14 | });
15 |
16 | afterAll(() => {
17 | Object.defineProperty(window, "navigator", {
18 | value: { userAgent },
19 | writable: true,
20 | });
21 | });
22 |
23 | it("renders the Safari iPhone footer", () => {
24 | const { container } = render();
25 |
26 | expect(container.querySelector("#safari-iphone-footer")).not.toBeNull();
27 | });
28 |
29 | it("does not render the Safari iPhone footer on other browsers", () => {
30 | Object.defineProperty(window, "navigator", {
31 | value: { userAgent: "Chrome" },
32 | writable: true,
33 | });
34 |
35 | const { container } = render();
36 |
37 | expect(container.querySelector("#safari-iphone-footer")).toBeNull();
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/src/components/SafariFooter.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Safari on iPhone prevents the user from scrolling to the bottom of the screen, due
3 | * to the address bar.
4 | *
5 | * On Safari for iPhone, this React component adds an empty div with padding, which ensures that no content
6 | * is hidden.
7 | *
8 | * On all other browsers, this has no effect.
9 | */
10 | export const SafariFooter: React.FC = () => {
11 | const isSafariOniPhone = navigator.userAgent.match(/(iPhone).*(Safari)/);
12 |
13 | if (!isSafariOniPhone) return <>>;
14 |
15 | return ;
16 | };
17 |
--------------------------------------------------------------------------------
/src/components/Sidebar/NavDrawer.tsx:
--------------------------------------------------------------------------------
1 | import { SwipeableDrawer } from "@mui/material";
2 | import { styled } from "@mui/material/styles";
3 | import { useEffect } from "react";
4 | import { useLocation } from "react-router-dom";
5 | import NavContent from "src/components/Sidebar/NavContent";
6 |
7 | const PREFIX = "NavDrawer";
8 |
9 | const classes = {
10 | drawer: `${PREFIX}-drawer`,
11 | drawerPaper: `${PREFIX}-drawerPaper`,
12 | };
13 |
14 | const StyledSwipeableDrawer = styled(SwipeableDrawer)(({ theme }) => ({
15 | [`& .${classes.drawer}`]: {
16 | [theme.breakpoints.up("md")]: {
17 | width: drawerWidth,
18 | flexShrink: 0,
19 | },
20 | },
21 |
22 | [`& .${classes.drawerPaper}`]: {
23 | width: drawerWidth,
24 | borderRight: 0,
25 | },
26 | }));
27 |
28 | const drawerWidth = 280;
29 |
30 | type NavDrawerProps = {
31 | mobileOpen: boolean;
32 | handleDrawerToggle: () => void;
33 | };
34 |
35 | const NavDrawer: React.FC = ({ mobileOpen, handleDrawerToggle }) => {
36 | const location = useLocation();
37 |
38 | const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
39 |
40 | useEffect(() => {
41 | if (mobileOpen && handleDrawerToggle) {
42 | handleDrawerToggle();
43 | }
44 | // eslint-disable-next-line react-hooks/exhaustive-deps
45 | }, [location]);
46 |
47 | return (
48 |
63 |
64 |
65 | );
66 | };
67 |
68 | export default NavDrawer;
69 |
--------------------------------------------------------------------------------
/src/components/Sidebar/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import "src/components/Sidebar/Sidebar.scss";
2 |
3 | import { Drawer } from "@mui/material";
4 | import React from "react";
5 | import NavContent from "src/components/Sidebar/NavContent";
6 |
7 | const Sidebar: React.FC = () => (
8 |
13 | );
14 |
15 | export default Sidebar;
16 |
--------------------------------------------------------------------------------
/src/components/Sidebar/__tests__/Sidebar.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import Sidebar from "src/components/Sidebar/Sidebar";
2 | import { render } from "src/testUtils";
3 | import { test } from "vitest";
4 | import { describe, expect, it } from "vitest";
5 |
6 | describe("", () => {
7 | test("should render component", () => {
8 | it("should render component", () => {
9 | const { container } = render();
10 | expect(container).toMatchSnapshot();
11 | });
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/components/StagingNotification.test.tsx:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from "@mui/material";
2 | import { render } from "@testing-library/react";
3 | import StagingNotification from "src/components/StagingNotification";
4 | import { Environment } from "src/helpers/environment/Environment/Environment";
5 | import { describe, expect, it, Mock, vi } from "vitest";
6 |
7 | vi.mock("@mui/material", () => ({
8 | Box: (props: any) => ,
9 | useMediaQuery: vi.fn(),
10 | }));
11 |
12 | vi.mock("@olympusdao/component-library", () => ({
13 | WarningNotification: (props: any) => {props.children}
,
14 | }));
15 |
16 | describe("StagingNotification", () => {
17 | it("renders the notification when on the staging site on large screen", () => {
18 | (useMediaQuery as Mock).mockReturnValue(false);
19 | vi.spyOn(Environment, "getStagingFlag").mockReturnValue("true");
20 |
21 | const { getByTestId } = render();
22 | expect(getByTestId("staging-notification")).not.toBeNull();
23 | });
24 |
25 | it("does not render the notification when not on the staging site", () => {
26 | (useMediaQuery as Mock).mockReturnValue(false);
27 | vi.spyOn(Environment, "getStagingFlag").mockReturnValue("false");
28 |
29 | const { queryByTestId } = render();
30 | expect(queryByTestId("staging-notification")).toBeNull();
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/src/components/StagingNotification.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useMediaQuery } from "@mui/material";
2 | import { styled } from "@mui/material/styles";
3 | import { WarningNotification } from "@olympusdao/component-library";
4 | import { Environment } from "src/helpers/environment/Environment/Environment";
5 |
6 | const PREFIX = "StagingNotification";
7 |
8 | const classes = {
9 | contentShift: `${PREFIX}-contentShift`,
10 | notification: `${PREFIX}-notification`,
11 | };
12 |
13 | const StyledNotification = styled("div")(() => ({
14 | zIndex: 100,
15 | [`& .${classes.contentShift}`]: {
16 | marginLeft: 0,
17 | },
18 |
19 | [`& .${classes.notification}`]: {
20 | marginLeft: "264px",
21 | },
22 | }));
23 |
24 | const StagingNotification = () => {
25 | const isSmallScreen = useMediaQuery("(max-width: 600px)");
26 | return (
27 |
28 | {Environment.getStagingFlag() === "true" && (
29 |
34 |
35 | You are on the staging site. Any interaction could result in loss of assets.{" "}
36 | Exit Here
37 |
38 |
39 | )}
40 |
41 | );
42 | };
43 |
44 | export default StagingNotification;
45 |
--------------------------------------------------------------------------------
/src/components/TopBar/ThemeSwitch.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@mui/material";
2 | import ToggleButton from "@mui/material/ToggleButton";
3 | import { Icon } from "@olympusdao/component-library";
4 |
5 | interface IThemeSwitcherProps {
6 | theme: string;
7 | toggleTheme: (e: any) => void;
8 | }
9 |
10 | function ThemeSwitcher({ theme, toggleTheme }: IThemeSwitcherProps) {
11 | return (
12 |
13 |
21 | {theme === "dark" ? (
22 |
23 | ) : (
24 |
25 | )}
26 |
27 |
28 | );
29 | }
30 |
31 | export default ThemeSwitcher;
32 |
--------------------------------------------------------------------------------
/src/components/TopBar/TopBar.scss:
--------------------------------------------------------------------------------
1 | .dapp-topbar {
2 | width: 100%;
3 | align-items: center;
4 | a,
5 | button {
6 | white-space: nowrap !important;
7 | overflow: hidden !important;
8 | margin: 6px;
9 | min-width: fit-content !important;
10 | hamburger {
11 | padding: 11px !important;
12 | }
13 | &.toggle-button {
14 | height: 39px;
15 | padding: 9.5px !important;
16 | }
17 | text-decoration: none;
18 | }
19 | }
20 | .tablet .dapp-topbar {
21 | justify-content: space-between;
22 | }
23 | .pending-txn-container {
24 | display: flex;
25 | align-items: center;
26 | padding: 10px !important;
27 | .MuiButton-label {
28 | margin-right: 0;
29 | transition: all 0.33s ease-out;
30 | font-size: 14px;
31 | font-weight: 500;
32 | }
33 | }
34 | .caret-down {
35 | position: absolute;
36 | right: 0.4rem;
37 | }
38 | .hovered-button {
39 | .MuiButton-label {
40 | margin-right: 0.5rem;
41 | transition: all 0.33s ease-out;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/TopBar/TopBar.tsx:
--------------------------------------------------------------------------------
1 | // import "src/components/TopBar/TopBar.scss";
2 |
3 | import { Box, Button, SvgIcon, useMediaQuery, useTheme } from "@mui/material";
4 | import MenuIcon from "src/assets/icons/hamburger.svg?react";
5 | import ConnectButton from "src/components/ConnectButton/ConnectButton";
6 | import ThemeSwitcher from "src/components/TopBar/ThemeSwitch";
7 |
8 | const PREFIX = "TopBar";
9 |
10 | const classes = {
11 | appBar: `${PREFIX}-appBar`,
12 | menuButton: `${PREFIX}-menuButton`,
13 | pageTitle: `${PREFIX}-pageTitle`,
14 | };
15 |
16 | interface TopBarProps {
17 | theme: string;
18 | toggleTheme: (e: KeyboardEvent) => void;
19 | handleDrawerToggle: () => void;
20 | }
21 |
22 | function TopBar({ handleDrawerToggle, theme, toggleTheme }: TopBarProps) {
23 | const themeColor = useTheme();
24 | const desktop = useMediaQuery(themeColor.breakpoints.up(1048));
25 | return (
26 |
33 |
34 |
35 |
36 |
37 |
38 | {!desktop && (
39 |
50 | )}
51 |
52 |
53 | );
54 | }
55 |
56 | export default TopBar;
57 |
--------------------------------------------------------------------------------
/src/components/TopBar/__tests__/ThemeSwitch.test.unit.tsx:
--------------------------------------------------------------------------------
1 | import ThemeSwitch from "src/components/TopBar/ThemeSwitch";
2 | import { render } from "src/testUtils";
3 |
4 | describe("", () => {
5 | it("should render component", () => {
6 | const { container } = render( console.log("toggleTheme")} />);
7 | expect(container).toMatchSnapshot();
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/WalletConnectedGuard.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Typography } from "@mui/material";
2 | import { InPageConnectButton } from "src/components/ConnectButton/ConnectButton";
3 | import { useAccount } from "wagmi";
4 |
5 | export const WalletConnectedGuard: React.FC<{
6 | message?: string;
7 | fullWidth?: boolean;
8 | children: any;
9 | buttonText?: string;
10 | }> = props => {
11 | const { isConnected } = useAccount();
12 | if (!isConnected)
13 | return (
14 | <>
15 |
16 |
17 | {props.message && (
18 |
19 | {props.message}
20 |
21 | )}
22 | >
23 | );
24 |
25 | return <>{props.children}>;
26 | };
27 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export * from "./networkDetails";
2 |
--------------------------------------------------------------------------------
/src/helpers/CountdownTimer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 |
3 | type CountdownProps = {
4 | targetDate: Date;
5 | };
6 |
7 | const CountdownTimer: React.FC = ({ targetDate }) => {
8 | const calculateTimeLeft = () => {
9 | const now = new Date();
10 | const difference = targetDate.getTime() - now.getTime();
11 | let timeLeft = { hours: 0, minutes: 0 };
12 |
13 | if (difference > 0) {
14 | timeLeft = {
15 | hours: Math.floor(difference / (1000 * 60 * 60)),
16 | minutes: Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60)),
17 | };
18 | }
19 |
20 | return timeLeft;
21 | };
22 |
23 | const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());
24 |
25 | useEffect(() => {
26 | const timer = setTimeout(() => {
27 | setTimeLeft(calculateTimeLeft());
28 | }, 60000);
29 |
30 | return () => clearTimeout(timer);
31 | });
32 |
33 | return (
34 | <>
35 | {timeLeft.hours > 0 || timeLeft.minutes > 0 ? (
36 | <>
37 | in {timeLeft.hours} hours, {timeLeft.minutes} minutes
38 | >
39 | ) : (
40 | <> in 0 hours, 0 minutes>
41 | )}
42 | >
43 | );
44 | };
45 |
46 | export default CountdownTimer;
47 |
--------------------------------------------------------------------------------
/src/helpers/DateHelper.ts:
--------------------------------------------------------------------------------
1 | export const adjustDateByDays = (date: Date, days: number): Date => {
2 | const newDate = new Date(date.getTime());
3 | newDate.setTime(newDate.getTime() + 1000 * 60 * 60 * 24 * days);
4 |
5 | return newDate;
6 | };
7 |
8 | export const getISO8601String = (date: Date): string => {
9 | return date.toISOString().split("T")[0];
10 | };
11 |
12 | export const dateGreaterThan = (one: string, two: string): boolean => {
13 | return new Date(one).getTime() > new Date(two).getTime();
14 | };
15 |
--------------------------------------------------------------------------------
/src/helpers/Migration.unit.test.js:
--------------------------------------------------------------------------------
1 | import * as fc from "fast-check";
2 | import { formatCurrency } from "src/helpers/Migration";
3 | import { expect, test } from "vitest";
4 |
5 | test("formatCurrency always returns value starting with $", async () => {
6 | // force 0.1 to a 32-bit float
7 | const min32 = new Float32Array([0.1])[0];
8 | fc.assert(
9 | fc.property(fc.float({ min: min32 }), value => {
10 | const formatted = formatCurrency(value);
11 | expect(formatted).toContain("$");
12 | }),
13 | );
14 | });
15 |
16 | test("formatCurrency splits thousands with comma", async () => {
17 | fc.assert(
18 | fc.property(fc.integer({ min: 1000, max: 999999 }), value => {
19 | const formatted = formatCurrency(value);
20 | expect(formatted).toContain(",");
21 | const chunks = formatted.split(",");
22 | expect(chunks.length).toEqual(2);
23 | }),
24 | );
25 | });
26 |
27 | test("formatCurrency trims cents", async () => {
28 | // force 1.01 to a 32-bit float
29 | const float32 = new Float32Array([1.01])[0];
30 | const max32 = new Float32Array([1.999])[0];
31 | fc.assert(
32 | fc.property(fc.float({ min: float32, max: max32 }), value => {
33 | const formatted = formatCurrency(value);
34 | expect(formatted).not.toContain(".");
35 | }),
36 | );
37 | });
38 |
--------------------------------------------------------------------------------
/src/helpers/NumberHelper.test.ts:
--------------------------------------------------------------------------------
1 | import { getFloat } from "src/helpers/NumberHelper";
2 | import { describe, expect, it } from "vitest";
3 |
4 | describe("NumberHelper", () => {
5 | it("getFloat should return 0 when undefined", () => {
6 | expect(getFloat(undefined)).toEqual(0);
7 | });
8 | it("getFloat should return the number when number", () => {
9 | expect(getFloat(1.9)).toEqual(1.9);
10 | expect(getFloat(1)).toEqual(1);
11 | });
12 | it("getFloat should return the number when string", () => {
13 | expect(getFloat("1.9")).toEqual(1.9);
14 | expect(getFloat("1")).toEqual(1);
15 | });
16 | it("should throw error when wrong type", async () => {
17 | const shouldThrow = async () => {
18 | getFloat(true);
19 | };
20 | await expect(shouldThrow()).rejects.toThrow("Unable to get float value");
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/src/helpers/NumberHelper.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * converts input to float
3 | * @param input should be: `undefined | number | string`
4 | * - throws Error when not in above types
5 | */
6 | export const getFloat = (input: unknown): number => {
7 | if (typeof input === "undefined") return 0;
8 |
9 | if (input == null) return 0;
10 |
11 | if (typeof input === "number") return input;
12 |
13 | if (typeof input === "string") return parseFloat(input);
14 |
15 | throw new Error(`Unable to get float value of ${input} with type ${typeof input}`);
16 | };
17 |
--------------------------------------------------------------------------------
/src/helpers/SearchParamsHelper.test.ts:
--------------------------------------------------------------------------------
1 | import { updateSearchParams } from "src/helpers/SearchParamsHelper";
2 | import { describe, expect, it } from "vitest";
3 |
4 | describe("updateSearchParams", () => {
5 | it("should return a new URLSearchParams object with updated value", () => {
6 | const params = new URLSearchParams({ foo: "bar" });
7 | const updatedParams = updateSearchParams(params, "foo", "baz");
8 |
9 | expect(params.get("foo")).toEqual("bar");
10 | expect(updatedParams.get("foo")).toEqual("baz");
11 | expect(updatedParams.toString()).toEqual("foo=baz");
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/helpers/SearchParamsHelper.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns a copy of the supplied URLSearchParams with an updated value.
3 | *
4 | * @param params
5 | * @param param
6 | * @param value
7 | * @returns
8 | */
9 | export const updateSearchParams = (params: URLSearchParams, param: string, value: string): URLSearchParams => {
10 | const updated = new URLSearchParams(params);
11 | updated.set(param, value);
12 |
13 | return updated;
14 | };
15 |
--------------------------------------------------------------------------------
/src/helpers/ZapHelper.test.ts:
--------------------------------------------------------------------------------
1 | import { NetworkId } from "src/constants";
2 | import { isSupportedChain } from "src/helpers/ZapHelper";
3 | import { describe, expect, it } from "vitest";
4 |
5 | describe("isSupportedChain", () => {
6 | it("should return true for supported chains", () => {
7 | expect(isSupportedChain(1)).toEqual(true);
8 | expect(isSupportedChain(5)).toEqual(false);
9 | });
10 |
11 | it("should return false for unsupported chains", () => {
12 | const unsupportedChains = [NetworkId.ARBITRUM, NetworkId.OPTIMISM, NetworkId.AVALANCHE, NetworkId.POLYGON];
13 | unsupportedChains.forEach(chainId => {
14 | expect(isSupportedChain(chainId as NetworkId)).toBe(false);
15 | });
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/helpers/ZapHelper.ts:
--------------------------------------------------------------------------------
1 | import { NetworkId } from "src/constants";
2 | import { ZAP_ADDRESSES } from "src/constants/addresses";
3 |
4 | export const isSupportedChain = (networkId: NetworkId): boolean => {
5 | return !!ZAP_ADDRESSES[networkId as keyof typeof ZAP_ADDRESSES];
6 | };
7 |
--------------------------------------------------------------------------------
/src/helpers/analytics/trackGAEvent.ts:
--------------------------------------------------------------------------------
1 | import ReactGA from "react-ga";
2 | import GA4 from "react-ga4";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 |
5 | const GA_API_KEY = Environment.getGoogleAnalyticsApiKey();
6 | const GA4_API_KEY = Environment.getGA4ApiKey();
7 |
8 | type Category =
9 | | "App"
10 | | "OlyZaps"
11 | | "Staking"
12 | | "Bonds"
13 | | "Bonds V3"
14 | | "Migration"
15 | | "Wrapping"
16 | | "Range"
17 | | "Liquidity"
18 | | "0XSwap"
19 | | "Cooler";
20 |
21 | interface TrackGAEventOptions extends ReactGA.EventArgs {
22 | category: Category;
23 | }
24 |
25 | export const trackGAEvent = (event: TrackGAEventOptions) => {
26 | try {
27 | // Universal GA (using react-ga)
28 | if (GA_API_KEY && ReactGA) {
29 | ReactGA.event(event);
30 | }
31 | } catch (error) {
32 | console.error("trackGAEvent", error);
33 | }
34 | };
35 |
36 | //used for event tracking in Google Analytics 4
37 | export const trackGtagEvent = (name: string, params: any) => {
38 | if (!name || !params) {
39 | return;
40 | }
41 |
42 | try {
43 | if (GA4_API_KEY && ReactGA) {
44 | GA4.gtag("event", name, {
45 | ...params,
46 | send_to: GA4_API_KEY,
47 | });
48 | }
49 | } catch (error) {
50 | console.error("trackGtagEvent", error);
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/src/helpers/bonds/sortByDiscount.ts:
--------------------------------------------------------------------------------
1 | import { Bond } from "src/views/Bond/hooks/useBond";
2 |
3 | export const sortByDiscount = (bonds: Bond[]) => {
4 | return Array.from(bonds).sort((a, b) => (a.discount.gt(b.discount) ? -1 : 1));
5 | };
6 |
--------------------------------------------------------------------------------
/src/helpers/contracts/getLPTokenByAddress.ts:
--------------------------------------------------------------------------------
1 | import { getBalancerLPToken } from "src/helpers/contracts/getBalancerLPToken";
2 | import { getCurveLPToken } from "src/helpers/contracts/getCurveLPToken";
3 | import { getGelatoLPToken } from "src/helpers/contracts/getGelatoLPToken";
4 | import { getUniOrSushiLPToken } from "src/helpers/contracts/getUniOrSushiLPToken";
5 | import { NetworkId } from "src/networkDetails";
6 |
7 | export const getLPTokenByAddress = async ({ address, networkId }: { address: string; networkId: NetworkId }) => {
8 | // Note that the order between gelato and uniOrSushi is important here. Since all contract
9 | // calls to uniOrSushi are a subset of the calls to gelato, checking if a token is a gelato LP
10 | // must always preceed checking if it's a uniOrSushi LP.
11 | const gelato = await getGelatoLPToken({ address, networkId });
12 | if (gelato) return gelato;
13 |
14 | const uniOrSushi = await getUniOrSushiLPToken({ address, networkId });
15 | if (uniOrSushi) return uniOrSushi;
16 |
17 | const balancer = await getBalancerLPToken({ address, networkId });
18 | if (balancer) return balancer;
19 |
20 | return getCurveLPToken({ address, networkId });
21 | };
22 |
--------------------------------------------------------------------------------
/src/helpers/contracts/getTokenByAddress.ts:
--------------------------------------------------------------------------------
1 | import * as _tokens from "src/constants/tokens";
2 | import { getLPTokenByAddress } from "src/helpers/contracts/getLPTokenByAddress";
3 | import { Token } from "src/helpers/contracts/Token";
4 | import { NetworkId } from "src/networkDetails";
5 |
6 | const tokens = Object.values(_tokens);
7 |
8 | export const getTokenByAddress = async ({
9 | address,
10 | networkId,
11 | }: {
12 | address: string;
13 | networkId: NetworkId;
14 | }): Promise => {
15 | const normalizedAddress = address.toLowerCase();
16 |
17 | // First, we attempt to find the token from our hardcoded list
18 | const hardcoded = tokens.find(token =>
19 | Object.entries(token.addresses)
20 | .filter(([_networkId]) => networkId === Number(_networkId))
21 | .find(([, _address]) => normalizedAddress === _address.toLowerCase()),
22 | );
23 |
24 | if (hardcoded) return hardcoded;
25 |
26 | // LP tokens aren't hardcoded, we attempt to get them dynamically
27 | return getLPTokenByAddress({ address, networkId });
28 | };
29 |
--------------------------------------------------------------------------------
/src/helpers/defiLlamaChainToNetwork.test.ts:
--------------------------------------------------------------------------------
1 | import { defiLlamaChainToNetwork } from "src/helpers/defiLlamaChainToNetwork";
2 | import { describe, expect, it } from "vitest";
3 |
4 | describe("defiLlamaChainToNetwork", () => {
5 | it('should return "ETH" for "ethereum"', () => {
6 | expect(defiLlamaChainToNetwork("ethereum")).toEqual("ETH");
7 | });
8 |
9 | it('should return "MATIC" for "polygon"', () => {
10 | expect(defiLlamaChainToNetwork("polygon")).toEqual("MATIC");
11 | });
12 |
13 | it("should return uppercase version of input for unknown chain", () => {
14 | expect(defiLlamaChainToNetwork("bsc")).toEqual("BSC");
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/helpers/defiLlamaChainToNetwork.ts:
--------------------------------------------------------------------------------
1 | export const defiLlamaChainToNetwork = (chain: string) => {
2 | switch (chain.toLowerCase()) {
3 | case "ethereum":
4 | return "ETH";
5 | case "polygon":
6 | return "MATIC";
7 | default:
8 | return chain.toUpperCase();
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/src/helpers/misc/isValidAddress.ts:
--------------------------------------------------------------------------------
1 | import { isAddress } from "@ethersproject/address";
2 | import { AddressZero } from "@ethersproject/constants";
3 |
4 | export const isValidAddress = (address?: string) => address && isAddress(address) && address !== AddressZero;
5 |
--------------------------------------------------------------------------------
/src/helpers/normalizeSymbol.test.ts:
--------------------------------------------------------------------------------
1 | import { normalizeSymbol } from "src/helpers/normalizeSymbol";
2 | import { describe, expect, it } from "vitest";
3 |
4 | describe("normalizeSymbol", () => {
5 | it("should return correct values", () => {
6 | const upperAndLower = (input: string) => {
7 | return [input.toLowerCase(), input.toUpperCase()];
8 | };
9 | expect(upperAndLower("weth")).toEqual(["weth", "WETH"]);
10 |
11 | expect(normalizeSymbol(upperAndLower("weth"))).toEqual(["wETH", "wETH"]);
12 | expect(normalizeSymbol(upperAndLower("wsteth"))).toEqual(["wstETH", "wstETH"]);
13 | expect(normalizeSymbol(upperAndLower("gohm"))).toEqual(["gOHM", "gOHM"]);
14 | expect(normalizeSymbol(upperAndLower("wftm"))).toEqual(["FTM", "FTM"]);
15 | expect(normalizeSymbol(upperAndLower("fraxbp"))).toEqual(["FRAX", "FRAX"]);
16 | expect(normalizeSymbol(upperAndLower("wavax"))).toEqual(["AVAX", "AVAX"]);
17 | expect(normalizeSymbol(upperAndLower("crvfrax"))).toEqual(["CRV", "CRV"]);
18 |
19 | expect(normalizeSymbol(upperAndLower("lowerCaseString"))).toEqual(["lowercasestring", "LOWERCASESTRING"]);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/src/helpers/normalizeSymbol.ts:
--------------------------------------------------------------------------------
1 | export const normalizeSymbol = (symbol: string[]) => {
2 | return symbol.map(s => {
3 | switch (s.toLowerCase()) {
4 | case "weth":
5 | return "wETH";
6 | case "wsteth":
7 | return "wstETH";
8 | case "gohm":
9 | return "gOHM";
10 | case "wftm":
11 | return "FTM";
12 | case "fraxbp":
13 | return "FRAX";
14 | case "usdc.e":
15 | return "USDC";
16 | case "wavax":
17 | return "AVAX";
18 | case "crvfrax":
19 | return "CRV";
20 | default:
21 | return s;
22 | }
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/src/helpers/pricing/calculateBalancerLPValue.ts:
--------------------------------------------------------------------------------
1 | import { BALANCER_VAULT } from "src/constants/contracts";
2 | import { Token } from "src/helpers/contracts/Token";
3 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
4 | import { NetworkId } from "src/networkDetails";
5 | import { BalancerV2Pool__factory } from "src/typechain";
6 |
7 | export const calculateBalancerLPValue = async ({
8 | lpToken,
9 | networkId,
10 | poolTokens,
11 | }: {
12 | poolTokens: Token[];
13 | networkId: NetworkId;
14 | lpToken: Token;
15 | }) => {
16 | const contract = lpToken.getEthersContract(networkId);
17 | const vault = BALANCER_VAULT.getEthersContract(NetworkId.MAINNET);
18 |
19 | const [poolId, lpSupply, ...tokenPrices] = await Promise.all([
20 | contract.getPoolId(),
21 | // Normalize to the correct number of decimal places
22 | contract.totalSupply().then(supply => new DecimalBigNumber(supply, lpToken.decimals)),
23 | // Get the prices of an arbitrary amount of tokens
24 | ...poolTokens.map(token => token.getPrice(networkId)),
25 | ]);
26 |
27 | const tokenBalances = await vault
28 | .getPoolTokens(poolId)
29 | // We take each balance and normalize it to that tokens specific amount of decimals
30 | .then(({ balances }) => balances.map((balance, i) => new DecimalBigNumber(balance, poolTokens[i].decimals)));
31 |
32 | const totalValueOfLpInUsd = tokenBalances.reduce(
33 | // For each token, we multiply the amount in the pool by it's USD value
34 | (sum, balance, i) => sum.add(balance.mul(tokenPrices[i])),
35 | new DecimalBigNumber("0"),
36 | );
37 |
38 | return totalValueOfLpInUsd.div(lpSupply);
39 | };
40 |
--------------------------------------------------------------------------------
/src/helpers/pricing/calculateGelatoLPValue.ts:
--------------------------------------------------------------------------------
1 | import { Token } from "src/helpers/contracts/Token";
2 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
3 | import { NetworkId } from "src/networkDetails";
4 | import { GUniV3Lp__factory } from "src/typechain";
5 |
6 | export const calculateGelatoLPValue = async ({
7 | lpToken,
8 | networkId,
9 | poolTokens,
10 | }: {
11 | networkId: NetworkId;
12 | poolTokens: readonly [Token, Token];
13 | lpToken: Token;
14 | }) => {
15 | const contract = lpToken.getEthersContract(networkId);
16 |
17 | const [tokenBalances, lpSupply, ...tokenPrices] = await Promise.all([
18 | contract
19 | .getUnderlyingBalances()
20 | .then(balances => [
21 | new DecimalBigNumber(balances.amount0Current, poolTokens[0].decimals),
22 | new DecimalBigNumber(balances.amount1Current, poolTokens[1].decimals),
23 | ]),
24 | contract.totalSupply().then(supply => new DecimalBigNumber(supply, lpToken.decimals)),
25 | ...poolTokens.map(token => token.getPrice(networkId as NetworkId)),
26 | ]);
27 |
28 | const totalValueOfLpInUsd = tokenBalances.reduce(
29 | // For each token, we multiply the amount in the pool by it's USD value
30 | (sum, balance, i) => sum.add(balance.mul(tokenPrices[i])),
31 | new DecimalBigNumber("0"),
32 | );
33 |
34 | return totalValueOfLpInUsd.div(lpSupply);
35 | };
36 |
--------------------------------------------------------------------------------
/src/helpers/pricing/calculateUniOrSushiLPValue.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers";
2 | import { Token } from "src/helpers/contracts/Token";
3 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
4 | import { NetworkId } from "src/networkDetails";
5 | import { PairContract__factory } from "src/typechain";
6 |
7 | export const calculateUniOrSushiLPValue = async ({
8 | lpToken,
9 | networkId,
10 | poolTokens,
11 | }: {
12 | networkId: NetworkId;
13 | poolTokens: [Token, Token];
14 | lpToken: Token;
15 | }) => {
16 | const contract = lpToken.getEthersContract(networkId);
17 |
18 | const [tokenBalances, lpSupply, ...tokenPrices] = await Promise.all([
19 | contract.getReserves().then(balances =>
20 | balances
21 | // We filter out blockTimestampLast from the balances
22 | .filter(balance => balance instanceof BigNumber)
23 | .map((balance, i) => new DecimalBigNumber(balance as BigNumber, poolTokens[i].decimals)),
24 | ),
25 | contract.totalSupply().then(supply => new DecimalBigNumber(supply, lpToken.decimals)),
26 | ...poolTokens.map(token => token.getPrice(networkId as NetworkId)),
27 | ]);
28 |
29 | const totalValueOfLpInUsd = tokenBalances.reduce(
30 | // For each token, we multiply the amount in the pool by it's USD value
31 | (sum, balance, i) => sum.add(balance.mul(tokenPrices[i])),
32 | new DecimalBigNumber("0"),
33 | );
34 |
35 | return totalValueOfLpInUsd.div(lpSupply);
36 | };
37 |
--------------------------------------------------------------------------------
/src/helpers/pricing/getCoingeckoPrice.ts:
--------------------------------------------------------------------------------
1 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
2 | import { NetworkId } from "src/networkDetails";
3 |
4 | /**
5 | * Attempts to get the price of a token from coingecko by contract address
6 | */
7 | export const getCoingeckoPrice = async (networkId: NetworkId, contractAddress: string) => {
8 | const normalizedAddress = contractAddress.toLowerCase();
9 |
10 | const params = new URLSearchParams({ contract_addresses: normalizedAddress, vs_currencies: "usd" }).toString();
11 |
12 | const url = `https://api.coingecko.com/api/v3/simple/token_price/ethereum?${params}`;
13 |
14 | const res = await fetch(url);
15 |
16 | const json = await res.json();
17 |
18 | const price: number = json[normalizedAddress].usd;
19 |
20 | if (!price) throw new Error(`Unable to get token price of ${normalizedAddress} from coingecko`);
21 |
22 | return new DecimalBigNumber(price.toString(), 9);
23 | };
24 |
--------------------------------------------------------------------------------
/src/helpers/pricing/useGetDefillamaPrice.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import axios from "axios";
3 |
4 | interface DefillamaPriceResponse {
5 | coins: {
6 | [address: string]: {
7 | symbol: string;
8 | price: number;
9 | };
10 | };
11 | }
12 | /**Mainnet only addresses */
13 | export const useGetDefillamaPrice = ({ addresses }: { addresses: string[] }) => {
14 | return useQuery(
15 | ["useGetDefillamaPrice", addresses],
16 | async () => {
17 | try {
18 | const joinedAddresses = addresses.map(address => `ethereum:${address}`).join(",");
19 | const response = await axios.get(
20 | `https://coins.llama.fi/prices/current/${joinedAddresses}`,
21 | );
22 |
23 | console.log("coins", response.data.coins);
24 | return response.data.coins;
25 | } catch (error) {
26 | return {};
27 | }
28 | },
29 | { enabled: addresses.length > 0 },
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/helpers/providers/Providers/Providers.ts:
--------------------------------------------------------------------------------
1 | import { StaticJsonRpcProvider } from "@ethersproject/providers";
2 | import { Environment } from "src/helpers/environment/Environment/Environment";
3 | import { NetworkId } from "src/networkDetails";
4 |
5 | export class Providers {
6 | private static _providerCache = {} as Record;
7 | private static _archiveProviderCache = {} as Record;
8 |
9 | /**
10 | * Returns a provider url for a given network
11 | */
12 | public static getProviderUrl(networkId: NetworkId) {
13 | const [url] = Environment.getNodeUrls(networkId);
14 |
15 | return url;
16 | }
17 | public static getArchiveProviderUrl(networkId: NetworkId) {
18 | const [url] = Environment.getArchiveNodeUrls(networkId);
19 |
20 | return url;
21 | }
22 |
23 | /**
24 | * Returns a static provider for a given network
25 | */
26 | public static getStaticProvider(networkId: NetworkId) {
27 | if (!this._providerCache[networkId])
28 | this._providerCache[networkId] = new StaticJsonRpcProvider(this.getProviderUrl(networkId));
29 |
30 | return this._providerCache[networkId];
31 | }
32 |
33 | public static getArchiveStaticProvider(networkId: NetworkId) {
34 | if (!this._archiveProviderCache[networkId])
35 | this._archiveProviderCache[networkId] = new StaticJsonRpcProvider(this.getArchiveProviderUrl(networkId));
36 |
37 | return this._archiveProviderCache[networkId];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/helpers/providers/Providers/Providers.unit.test.ts:
--------------------------------------------------------------------------------
1 | import { Providers } from "src/helpers/providers/Providers/Providers";
2 | import { enumToArray } from "src/helpers/types/enumToArray";
3 | import { NetworkId } from "src/networkDetails";
4 |
5 | describe("Providers", () => {
6 | it("has a provider url for every network", () => {
7 | enumToArray(NetworkId).forEach(networkId => {
8 | expect(Providers.getProviderUrl(networkId as NetworkId)).toBeTruthy();
9 | });
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/src/helpers/react-query/queryAssertion.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Assertion function helpful for asserting `enabled`
3 | * values from within a `react-query` function.
4 | * @param value The value(s) to assert
5 | * @param queryKey Key of current query
6 | */
7 | export function queryAssertion(value: unknown, queryKey: any = "not specified"): asserts value {
8 | if (!value) throw new Error(`Failed react-query assertion for key: ${queryKey}`);
9 | }
10 |
--------------------------------------------------------------------------------
/src/helpers/subgraph/Constants.ts:
--------------------------------------------------------------------------------
1 | export const CATEGORY_STABLE = "Stable";
2 | export const CATEGORY_VOLATILE = "Volatile";
3 | export const CATEGORY_POL = "Protocol-Owned Liquidity";
4 |
5 | export const TOKEN_SUPPLY_TYPE_TOTAL_SUPPLY = "Total Supply";
6 |
--------------------------------------------------------------------------------
/src/helpers/subgraph/TreasuryQueryHelper.ts:
--------------------------------------------------------------------------------
1 | import { CATEGORY_POL, CATEGORY_STABLE, CATEGORY_VOLATILE } from "src/helpers/subgraph/Constants";
2 | import { TokenRecord } from "src/hooks/useFederatedSubgraphQuery";
3 |
4 | const filterReduce = (
5 | records: TokenRecord[],
6 | filterPredicate: (value: TokenRecord) => unknown,
7 | valueExcludingOhm = false,
8 | ): number => {
9 | return records.filter(filterPredicate).reduce((previousValue, currentRecord) => {
10 | return previousValue + (valueExcludingOhm ? +currentRecord.valueExcludingOhm : +currentRecord.value);
11 | }, 0);
12 | };
13 |
14 | export const getTreasuryAssetValue = (
15 | records: TokenRecord[],
16 | liquidBacking: boolean,
17 | categories = [CATEGORY_STABLE, CATEGORY_VOLATILE, CATEGORY_POL],
18 | ): number => {
19 | if (liquidBacking) {
20 | return filterReduce(records, record => categories.includes(record.category) && record.isLiquid == true, true);
21 | }
22 |
23 | return filterReduce(records, record => categories.includes(record.category), false);
24 | };
25 |
--------------------------------------------------------------------------------
/src/helpers/truncateAddress.ts:
--------------------------------------------------------------------------------
1 | export function truncateEthereumAddress(address: string, length = 4) {
2 | if (!address || address.length < 11) {
3 | return address; // Return the original address if it's too short to truncate
4 | }
5 | return `${address.substring(0, length + 2)}...${address.substring(address.length - length)}`;
6 | }
7 |
--------------------------------------------------------------------------------
/src/helpers/types/assert.test.ts:
--------------------------------------------------------------------------------
1 | import { assert } from "src/helpers/types/assert";
2 | import { describe, expect, it } from "vitest";
3 |
4 | describe("Assert", () => {
5 | it("should throw the defined message when string", async () => {
6 | const theDefinedMessage = "the error message";
7 |
8 | const shouldThrow = async () => {
9 | assert(undefined !== undefined, theDefinedMessage);
10 | };
11 | await expect(shouldThrow()).rejects.toThrow(theDefinedMessage);
12 | });
13 |
14 | it("should throw the defined message when Error", async () => {
15 | const theDefinedMessage = new Error("the error message");
16 |
17 | const shouldThrow = async () => {
18 | assert(undefined !== undefined, theDefinedMessage);
19 | };
20 | await expect(shouldThrow()).rejects.toThrow(theDefinedMessage);
21 | });
22 |
23 | it("should resolve undefined when assertion is true", () => {
24 | expect(assert(("1" as string) !== ("2" as string), "whatever")).toEqual(undefined);
25 | expect(assert(1 === 1, "whatever")).toEqual(undefined);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/helpers/types/assert.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Assertion function
3 | */
4 | export function assert(value: unknown, message: string | Error): asserts value {
5 | if (!value) throw message instanceof Error ? message : new Error(message);
6 | }
7 |
--------------------------------------------------------------------------------
/src/helpers/types/enumToArray.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a typescript enum to an array of it's values
3 | */
4 | export const enumToArray = (_enum: any) => {
5 | const values = Object.values(_enum);
6 |
7 | return values.splice(values.length / 2);
8 | };
9 |
--------------------------------------------------------------------------------
/src/helpers/types/nonNullable.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type safe check for non defined values
3 | */
4 | export function nonNullable(value: Type): value is NonNullable {
5 | return value !== null && value !== undefined;
6 | }
7 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
2 | import { AppDispatch, RootState } from "src/store";
3 |
4 | export const useAppDispatch = () => useDispatch();
5 | export const useAppSelector: TypedUseSelectorHook = useSelector;
6 |
--------------------------------------------------------------------------------
/src/hooks/useCheckSecondsToNextEpoch.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { BigNumber, ethers } from "ethers";
3 | import { NetworkId } from "src/constants";
4 | import { STAKING_CONTRACT } from "src/constants/contracts";
5 |
6 | //Returns True if a rebase needs to be called.
7 | export const useCheckSecondsToNextEpoch = () => {
8 | return useQuery(["checkSecondsToNextEpoch"], async () => {
9 | const stakingContract = STAKING_CONTRACT.getEthersContract(NetworkId.MAINNET);
10 | const nextEpoch = await stakingContract.secondsToNextEpoch().catch(error => {
11 | //contract will throw an error if the current block time is past the next epoch time.
12 | if (error.code === ethers.errors.CALL_EXCEPTION) {
13 | if (error.reason.includes("SafeMath: subtraction overflow")) {
14 | return BigNumber.from("0");
15 | }
16 | }
17 | //If we're here we don't know why this reverted so return a positive number so we don't try to trigger a rebase.
18 | return BigNumber.from("1");
19 | });
20 |
21 | console.log(nextEpoch, "nextEpoch");
22 |
23 | if (nextEpoch.toNumber() === 0) {
24 | return true;
25 | }
26 | return false;
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/src/hooks/useContractAllowance.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "@ethersproject/bignumber";
2 | import { useQuery } from "@tanstack/react-query";
3 | import { NetworkId } from "src/constants";
4 | import { AddressMap } from "src/constants/addresses";
5 | import { queryAssertion } from "src/helpers/react-query/queryAssertion";
6 | import { nonNullable } from "src/helpers/types/nonNullable";
7 | import { useDynamicTokenContract } from "src/hooks/useContract";
8 | import { useAccount, useNetwork } from "wagmi";
9 |
10 | export const contractAllowanceQueryKey = (
11 | address?: string,
12 | networkId?: NetworkId,
13 | tokenMap?: AddressMap,
14 | contractMap?: AddressMap,
15 | ) => ["useContractAllowances", address, networkId, tokenMap, contractMap].filter(nonNullable);
16 |
17 | export const useContractAllowance = (tokenMap: AddressMap, contractMap: AddressMap) => {
18 | const token = useDynamicTokenContract(tokenMap);
19 | const { address = "", isConnected } = useAccount();
20 | const { chain = { id: 1 } } = useNetwork();
21 |
22 | const key = contractAllowanceQueryKey(address, chain.id, tokenMap, contractMap);
23 | return useQuery(
24 | [key],
25 | async () => {
26 | queryAssertion(address && chain.id, key);
27 |
28 | // NOTE: we originally threw an error here, but it caused problems with passing in null values
29 | // e.g. when the token has not yet been selected
30 | if (!token) {
31 | console.warn("Token was expected to exist on current network, but didn't.");
32 | return null;
33 | }
34 |
35 | const contractAddress = contractMap[chain.id as NetworkId];
36 | if (!contractAddress) throw new Error("Contract doesn't exist on current network");
37 |
38 | return token.allowance(address, contractAddress);
39 | },
40 | { enabled: !!address && !!isConnected },
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/hooks/useCurrentIndex.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { NetworkId } from "src/constants";
3 | import { STAKING_ADDRESSES } from "src/constants/addresses";
4 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
5 | import { useStaticStakingContract } from "src/hooks/useContract";
6 |
7 | export const currentIndexQueryKey = () => ["useCurrentIndex"];
8 |
9 | export const useCurrentIndex = () => {
10 | const stakingContract = useStaticStakingContract(STAKING_ADDRESSES[NetworkId.MAINNET], NetworkId.MAINNET);
11 |
12 | return useQuery([currentIndexQueryKey()], async () => {
13 | const index = await stakingContract.index();
14 |
15 | return new DecimalBigNumber(index, 9);
16 | });
17 | };
18 |
--------------------------------------------------------------------------------
/src/hooks/useFetchZeroExSwapData.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "@tanstack/react-query";
2 | import { BigNumber } from "ethers";
3 | import { fetchSwapData } from "src/hooks/useZeroExSwap";
4 |
5 | export const useFetchZeroExSwapData = () => {
6 | return useMutation(
7 | async ({
8 | slippage,
9 | amount,
10 | tokenAddress,
11 | buyAddress,
12 | isSell = true,
13 | }: {
14 | slippage: number;
15 | amount: BigNumber;
16 | tokenAddress: string;
17 | buyAddress: string;
18 | isSell?: boolean;
19 | }) => {
20 | const swapData = await fetchSwapData(amount, tokenAddress, +slippage / 100, buyAddress, isSell);
21 | return swapData;
22 | },
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/hooks/useGetLendBorrowStats.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import axios from "axios";
3 | import { DefiLlamaPool, getOhmPools } from "src/hooks/useGetLPStats";
4 |
5 | type LendAndBorrow = {
6 | apyBaseBorrow: number;
7 | apyRewardBorrow: number;
8 | borrowFactor: number | null;
9 | debtCeilingUsd: number | null;
10 | ltv: number;
11 | mintedCoin: string | null;
12 | pool: string;
13 | rewardTokens: string[];
14 | totalBorrowUsd: number;
15 | totalSupplyUsd: number;
16 | underlyingTokens: string[];
17 | };
18 | export const useGetLendAndBorrowStats = () => {
19 | const defillamaAPI = "https://yields.llama.fi/lendBorrow";
20 | const { data, isFetched, isLoading } = useQuery(["GetLendBorrowStats"], async () => {
21 | const ohmPools = await getOhmPools();
22 | console.log(ohmPools, "ohmPools");
23 | const lendAndBorrowPools = await axios.get(defillamaAPI).then(res => {
24 | return res.data;
25 | });
26 | const ohmLendAndBorrowPools = ohmPools.filter(pool =>
27 | lendAndBorrowPools.some(lendAndBorrowPool => lendAndBorrowPool.pool === pool.id),
28 | );
29 | const poolsAndLendBorrowStats = ohmLendAndBorrowPools.map(pool => {
30 | const lendBorrowPool = lendAndBorrowPools.find(lendAndBorrowPool => lendAndBorrowPool.pool === pool.id);
31 | return { ...pool, lendAndBorrow: lendBorrowPool };
32 | });
33 | return poolsAndLendBorrowStats;
34 | });
35 |
36 | return { data, isFetched, isLoading };
37 | };
38 |
39 | export interface LendAndBorrowPool extends DefiLlamaPool {
40 | lendAndBorrow: LendAndBorrow;
41 | }
42 |
--------------------------------------------------------------------------------
/src/hooks/useGoogleAnalytics.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import ReactGA from "react-ga";
3 | import GA4 from "react-ga4";
4 | import { useLocation } from "react-router-dom";
5 | import { Environment } from "src/helpers/environment/Environment/Environment";
6 | import { useAccount } from "wagmi";
7 |
8 | const GA_API_KEY = Environment.getGoogleAnalyticsApiKey();
9 | const GA4_API_KEY = Environment.getGA4ApiKey();
10 |
11 | const useGoogleAnalytics = () => {
12 | const location = useLocation();
13 |
14 | const { address = "" } = useAccount();
15 |
16 | useEffect(() => {
17 | const path = location.pathname + location.hash + location.search;
18 | const gaOptions = Object.assign(
19 | {
20 | cookieFlags: "SameSite=Strict; Secure",
21 | },
22 | address ? { userId: address } : {},
23 | );
24 | if (import.meta.env.MODE !== "test") {
25 | if (GA4_API_KEY && GA4_API_KEY.length > 1) {
26 | GA4.initialize([
27 | {
28 | trackingId: GA4_API_KEY,
29 | gaOptions,
30 | },
31 | ]);
32 |
33 | GA4.set({ anonymizeIp: true });
34 | GA4.send({ hitType: "pageview", page: path });
35 | }
36 |
37 | if (GA_API_KEY && GA_API_KEY.length > 1) {
38 | ReactGA.initialize(GA_API_KEY, {
39 | gaOptions,
40 | });
41 | ReactGA.set({ anonymizeIp: true });
42 | ReactGA.pageview(path);
43 | }
44 | }
45 | }, [location]);
46 | };
47 |
48 | export { useGoogleAnalytics };
49 |
--------------------------------------------------------------------------------
/src/hooks/useHasDust.ts:
--------------------------------------------------------------------------------
1 | import { useAppSelector } from ".";
2 |
3 | export const useHasDust = () => {
4 | return useAppSelector(state => {
5 | if (!state.app.currentIndex || !state.app.marketPrice) {
6 | return true;
7 | }
8 | const wrappedBalance = Number(state.account.balances.wsohm) * Number(state.app.currentIndex!);
9 | const ohmBalance = Number(state.account.balances.ohmV1);
10 | const sOhmbalance = Number(state.account.balances.sohmV1);
11 | if (ohmBalance > 0 && ohmBalance * state.app.marketPrice < 10) {
12 | return true;
13 | }
14 | if (sOhmbalance > 0 && sOhmbalance * state.app.marketPrice < 10) {
15 | return true;
16 | }
17 | if (wrappedBalance > 0 && wrappedBalance * state.app.marketPrice < 10) {
18 | return true;
19 | }
20 | return false;
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/src/hooks/useOldAssetsDetected.ts:
--------------------------------------------------------------------------------
1 | import { isChainEthereum } from "src/helpers";
2 | import { useNetwork } from "wagmi";
3 |
4 | import { useAppSelector } from ".";
5 |
6 | export const useOldAssetsDetected = () => {
7 | const { chain = { id: 1 } } = useNetwork();
8 |
9 | return useAppSelector(state => {
10 | if (chain.id && isChainEthereum({ chainId: chain.id, includeTestnets: true })) {
11 | return (
12 | state.account.balances &&
13 | (Number(state.account.balances.sohmV1) ||
14 | Number(state.account.balances.ohmV1) ||
15 | Number(state.account.balances.wsohm)
16 | ? true
17 | : false)
18 | );
19 | } else {
20 | return false;
21 | }
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/src/hooks/useOldAssetsEnoughToMigrate.ts:
--------------------------------------------------------------------------------
1 | import { useAppSelector } from ".";
2 |
3 | export const useOldAssetsEnoughToMigrate = () => {
4 | return useAppSelector(state => {
5 | if (!state.app.currentIndex || !state.app.marketPrice) {
6 | return true;
7 | }
8 | const wrappedBalance = Number(state.account.balances.wsohm) * Number(state.app.currentIndex!);
9 | const allAssetsBalance =
10 | Number(state.account.balances.sohmV1) + Number(state.account.balances.ohmV1) + wrappedBalance;
11 | return state.app.marketPrice * allAssetsBalance >= 10;
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/src/hooks/usePathForNetwork.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { NavigateFunction } from "react-router-dom";
3 | import { NetworkId, VIEWS_FOR_NETWORK } from "src/constants";
4 |
5 | /**
6 | * will redirect from paths that aren't active on a given network yet.
7 | */
8 | export function usePathForNetwork({
9 | pathName,
10 | networkID,
11 | navigate,
12 | }: {
13 | pathName: string;
14 | networkID: NetworkId;
15 | navigate: NavigateFunction;
16 | }) {
17 | const handlePathForNetwork = () => {
18 | // do nothing if networkID is -1 since that's a default state
19 | // if (networkID === -1) return;
20 |
21 | switch (pathName) {
22 | case "stake":
23 | if (VIEWS_FOR_NETWORK[networkID] && VIEWS_FOR_NETWORK[networkID].stake) {
24 | break;
25 | } else if ([NetworkId.ARBITRUM, NetworkId.ARBITRUM_GOERLI].includes(networkID)) {
26 | navigate("/bridge");
27 | break;
28 | } else {
29 | navigate("/dashboard");
30 | break;
31 | }
32 | case "bonds":
33 | if (VIEWS_FOR_NETWORK[networkID] && VIEWS_FOR_NETWORK[networkID].bonds) {
34 | break;
35 | } else {
36 | navigate("/stake");
37 | break;
38 | }
39 | case "zap":
40 | if (VIEWS_FOR_NETWORK[networkID] && VIEWS_FOR_NETWORK[networkID].zap) {
41 | break;
42 | } else {
43 | navigate("/stake");
44 | break;
45 | }
46 | case "range":
47 | if (VIEWS_FOR_NETWORK[networkID] && VIEWS_FOR_NETWORK[networkID].range) {
48 | break;
49 | } else {
50 | navigate("/stake");
51 | break;
52 | }
53 | default:
54 | console.log("pathForNetwork ok");
55 | }
56 | };
57 |
58 | useEffect(() => {
59 | handlePathForNetwork();
60 | }, [networkID]);
61 | }
62 |
--------------------------------------------------------------------------------
/src/hooks/useProtocolMetrics.ts:
--------------------------------------------------------------------------------
1 | import { useMetricsLatestQuery } from "src/hooks/useFederatedSubgraphQuery";
2 |
3 | export const useTotalValueDeposited = ({ ignoreCache }: { ignoreCache?: boolean }): number | undefined => {
4 | const { data } = useMetricsLatestQuery({ ignoreCache });
5 |
6 | if (!data) {
7 | return undefined;
8 | }
9 |
10 | return data.sOhmTotalValueLocked;
11 | };
12 |
13 | /**
14 | * Determines the current index.
15 | *
16 | * @returns
17 | */
18 | export const useCurrentIndex = ({ ignoreCache }: { ignoreCache?: boolean }): number | undefined => {
19 | const { data } = useMetricsLatestQuery({ ignoreCache });
20 |
21 | if (!data) {
22 | return undefined;
23 | }
24 |
25 | return data.ohmIndex;
26 | };
27 |
--------------------------------------------------------------------------------
/src/hooks/useScreenSize.ts:
--------------------------------------------------------------------------------
1 | import { useMediaQuery } from "@mui/material";
2 |
3 | export const useScreenSize = (size: "xs" | "sm" | "md" | "lg" | "xl") => {
4 | const breakpoint =
5 | size === "xs"
6 | ? "(max-width: 0px)"
7 | : size === "sm"
8 | ? "(max-width: 600px)"
9 | : size === "md"
10 | ? "(max-width: 900px)"
11 | : size === "lg"
12 | ? "(max-width: 1200px)"
13 | : size === "xl"
14 | ? "(max-width: 153600px)"
15 | : "";
16 |
17 | return useMediaQuery(breakpoint);
18 | };
19 |
--------------------------------------------------------------------------------
/src/hooks/useStakingRebaseRate.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { NetworkId } from "src/constants";
3 | import { DISTRIBUTOR_CONTRACT } from "src/constants/contracts";
4 | import { parseBigNumber } from "src/helpers";
5 |
6 | export const useStakingRebaseRate = () => {
7 | return useQuery(["useStakingRebaseRate"], async () => {
8 | const distributorContract = DISTRIBUTOR_CONTRACT.getEthersContract(NetworkId.MAINNET);
9 | const rewardRate = await distributorContract.rewardRate();
10 |
11 | return parseBigNumber(rewardRate, 9);
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/src/hooks/useTestMode.ts:
--------------------------------------------------------------------------------
1 | import { isTestnet } from "src/helpers";
2 | import { useNetwork } from "wagmi";
3 |
4 | /**
5 | * Returns a boolean indicating whether the user's wallet is connected to a testnet.
6 | */
7 | export const useTestMode = () => {
8 | const { chain = { id: 1 } } = useNetwork();
9 |
10 | return isTestnet(chain.id);
11 | };
12 |
--------------------------------------------------------------------------------
/src/hooks/useTestableNetworks.ts:
--------------------------------------------------------------------------------
1 | import { NetworkId } from "src/networkDetails";
2 | import { useNetwork } from "wagmi";
3 |
4 | /** if currentNetwork === testNetwork, then use testNetwork, else use targetNetwork */
5 | const getTestnet = (
6 | targetNetwork: TTargetNetwork,
7 | testNetwork: TTestNetwork,
8 | currentNetwork: NetworkId,
9 | ): TTargetNetwork | TTestNetwork => {
10 | return currentNetwork === testNetwork ? testNetwork : targetNetwork;
11 | };
12 |
13 | export const useTestableNetworks = () => {
14 | const { chain = { id: 1 } } = useNetwork();
15 |
16 | return {
17 | MAINNET: getTestnet(NetworkId.MAINNET, NetworkId.TESTNET_GOERLI, chain.id),
18 | AVALANCHE: getTestnet(NetworkId.AVALANCHE, NetworkId.AVALANCHE_TESTNET, chain.id),
19 | ARBITRUM_V0: getTestnet(NetworkId.ARBITRUM, NetworkId.ARBITRUM_GOERLI, chain.id),
20 | ARBITRUM: getTestnet(NetworkId.ARBITRUM, NetworkId.ARBITRUM_GOERLI, chain.id),
21 | POLYGON: getTestnet(NetworkId.POLYGON, NetworkId.POLYGON_TESTNET, chain.id),
22 | FANTOM: getTestnet(NetworkId.FANTOM, NetworkId.FANTOM_TESTNET, chain.id),
23 | BASE: getTestnet(NetworkId.BASE, NetworkId.BASE_TESTNET, chain.id),
24 | MAINNET_HOLESKY: getTestnet(NetworkId.MAINNET, NetworkId.HOLESKY, chain.id),
25 | BERACHAIN: getTestnet(NetworkId.BERACHAIN, NetworkId.BERACHAIN_TESTNET, chain.id),
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/src/hooks/useTheme.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | const useTheme = (): [string, (e: KeyboardEvent) => void, boolean] => {
4 | const [theme, setTheme] = useState("dark");
5 | const [mounted, setMounted] = useState(false);
6 |
7 | const setMode = (mode: string) => {
8 | window.localStorage.setItem("theme", mode);
9 | setTheme(mode);
10 | };
11 |
12 | const toggleTheme = (e: KeyboardEvent) => {
13 | if (e.metaKey) {
14 | setMode("girth");
15 | } else {
16 | if (theme === "light") {
17 | setMode("dark");
18 | } else {
19 | setMode("light");
20 | }
21 | }
22 | };
23 |
24 | useEffect(() => {
25 | const localTheme = window.localStorage.getItem("theme");
26 | window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches && !localTheme
27 | ? setMode("dark")
28 | : localTheme
29 | ? setTheme(localTheme)
30 | : setMode("dark");
31 | setMounted(true);
32 | }, []);
33 |
34 | return [theme, toggleTheme, mounted];
35 | };
36 |
37 | export default useTheme;
38 |
--------------------------------------------------------------------------------
/src/hooks/useTokenPrice.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { Token } from "src/helpers/contracts/Token";
3 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
4 | import { NetworkId } from "src/networkDetails";
5 |
6 | export interface UseTokenPriceOptions {
7 | token: TToken;
8 | networkId: keyof TToken["addresses"];
9 | }
10 |
11 | export const tokenPriceQueryKey = (options: UseTokenPriceOptions) => [
12 | "useTokenPrice",
13 | options.networkId,
14 | options.token.getAddress(options.networkId), // Address is smaller and nicer to serialize
15 | ];
16 |
17 | export const useTokenPrice = (options: UseTokenPriceOptions) => {
18 | const _networkId = options.networkId as NetworkId;
19 | const key = tokenPriceQueryKey({ token: options.token, networkId: _networkId });
20 | return useQuery([key], () => options.token.getPrice(_networkId));
21 | };
22 |
--------------------------------------------------------------------------------
/src/hooks/useTokenRecordsMetrics.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useMetricsLatestQuery } from "src/hooks/useFederatedSubgraphQuery";
3 |
4 | /**
5 | * Fetches the market value of treasury assets across all chains from the subgraph.
6 | *
7 | * @returns
8 | */
9 | export const useTreasuryMarketValueLatest = (ignoreCache?: boolean): number | undefined => {
10 | // State variables
11 | const [assetValue, setAssetValue] = useState();
12 |
13 | // Query hooks
14 | const { data: metricResult } = useMetricsLatestQuery({ ignoreCache });
15 |
16 | useEffect(() => {
17 | if (!metricResult) {
18 | setAssetValue(undefined);
19 | return;
20 | }
21 |
22 | setAssetValue(metricResult.treasuryMarketValue);
23 | }, [metricResult]);
24 |
25 | return assetValue;
26 | };
27 |
--------------------------------------------------------------------------------
/src/hooks/useTriggerRebase.ts:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import toast from "react-hot-toast";
3 | import { STAKING_CONTRACT } from "src/constants/contracts";
4 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
5 | import { ZeroDistributor__factory } from "src/typechain";
6 | import { useSigner } from "wagmi";
7 |
8 | export const useTriggerZeroDistributorRebase = () => {
9 | const network = useTestableNetworks();
10 | const { data: signer } = useSigner();
11 | const queryClient = useQueryClient();
12 | return useMutation(
13 | async () => {
14 | if (!signer) {
15 | throw new Error("No signer found");
16 | }
17 | const stakingContract = STAKING_CONTRACT.getEthersContract(network.MAINNET);
18 | const distributorAddress = await stakingContract.distributor();
19 |
20 | const zeroDistributorContract = ZeroDistributor__factory.connect(distributorAddress, signer);
21 |
22 | const triggerRebase = await zeroDistributorContract.triggerRebase();
23 |
24 | const confirmation = await triggerRebase.wait();
25 |
26 | return confirmation;
27 | },
28 | {
29 | onError: (error: Error) => {
30 | toast.error(error.message);
31 | },
32 | onSuccess: async tx => {
33 | queryClient.invalidateQueries({ queryKey: ["checkSecondsToNextEpoch"] });
34 | toast(`Successfully Triggered Rebase`);
35 | },
36 | },
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from "react-dom/client";
2 | import Root from "src/Root";
3 |
4 | const container = document.getElementById("root");
5 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
6 | const root = createRoot(container!);
7 | root.render();
8 |
--------------------------------------------------------------------------------
/src/lib/Bond.ts:
--------------------------------------------------------------------------------
1 | import { NetworkId } from "src/constants";
2 |
3 | interface BondAddresses {
4 | reserveAddress: string;
5 | bondAddress: string;
6 | }
7 |
8 | type NetworkAddresses = { [key in NetworkId]?: BondAddresses };
9 |
10 | // Keep all LP specific fields/logic within the LPBond class
11 | interface LPBondOpts {
12 | networkAddrs: NetworkAddresses; // Mapping of network --> Addresses
13 | }
14 |
15 | export class LPBond {
16 | readonly networkAddrs: NetworkAddresses;
17 |
18 | constructor(lpBondOpts: LPBondOpts) {
19 | this.networkAddrs = lpBondOpts.networkAddrs;
20 | }
21 |
22 | getAddressForReserve(NetworkId: NetworkId) {
23 | return this.networkAddrs[NetworkId]?.reserveAddress;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/lib/EthersTypes.ts:
--------------------------------------------------------------------------------
1 | export interface EthersError extends Error {
2 | error: Error;
3 | }
4 |
--------------------------------------------------------------------------------
/src/lib/react-query.tsx:
--------------------------------------------------------------------------------
1 | import { QueryCache, QueryClient, QueryClientProvider } from "@tanstack/react-query";
2 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
3 | import React, { ReactNode } from "react";
4 | import { Environment } from "src/helpers/environment/Environment/Environment";
5 |
6 | export const queryCache = new QueryCache({
7 | onError: (error, query) => {
8 | if (error instanceof Error) console.error({ key: query.queryKey, error: error.message });
9 | },
10 | });
11 |
12 | export const queryClient = new QueryClient({
13 | queryCache,
14 | defaultOptions: {
15 | queries: {
16 | refetchOnMount: false,
17 | refetchInterval: false,
18 | refetchOnReconnect: false,
19 | refetchOnWindowFocus: false,
20 | retry: Environment.env.MODE === "development" ? false : 3,
21 | },
22 | },
23 | });
24 |
25 | export const ReactQueryProvider: React.FC<{ children: ReactNode }> = ({ children }: { children: ReactNode }) => (
26 |
27 | {Environment.env.MODE === "development" && }
28 |
29 | {children}
30 |
31 | );
32 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | /**
4 | * Utility type to get the prop types of any component.
5 | */
6 | type PropsOf = TComponent extends React.ComponentType ? TProps : never;
7 |
--------------------------------------------------------------------------------
/src/setupTests.tsx:
--------------------------------------------------------------------------------
1 | import { cleanup } from "@testing-library/react";
2 | import { afterEach, beforeEach, vi } from "vitest";
3 |
4 | //expect.extend(matchers);
5 |
6 | //@ts-ignore
7 | global.CSS = { supports: vi.fn() };
8 |
9 | //vi.setTimeout(20000);
10 | beforeEach(() => {
11 | vi.mock("src/components/ConnectButton/ConnectButton", () => ({
12 | ConnectButton: vi.fn().mockReturnValue(<>Connect Wallet>),
13 | InPageConnectButton: vi.fn().mockReturnValue(<>Connect Wallet>),
14 | }));
15 | vi.mock("@rainbow-me/rainbowkit", () => ({
16 | RainbowKitProvider: vi.fn(),
17 | wallet: {
18 | metaMask: vi.fn(),
19 | brave: vi.fn(),
20 | rainbow: vi.fn(),
21 | walletConnect: vi.fn(),
22 | coinbase: vi.fn(),
23 | },
24 | connectorsForWallets: vi.fn(),
25 | darkTheme: vi.fn().mockReturnValue({}),
26 | lightTheme: vi.fn().mockReturnValue({}),
27 | ConnectButton: vi.fn().mockReturnValue({
28 | Custom: vi.fn(),
29 | }),
30 | }));
31 | vi.mock("recharts", () => ({
32 | default: vi.fn(),
33 | ComposedChart: vi.fn(),
34 | LineChart: vi.fn(),
35 | Line: vi.fn(),
36 | XAxis: vi.fn(),
37 | YAxis: vi.fn(),
38 | Tooltip: vi.fn(),
39 | ResponsiveContainer: vi.fn(),
40 | Area: vi.fn(),
41 | AreaChart: vi.fn(),
42 | }));
43 |
44 | Object.defineProperty(window, "matchMedia", {
45 | writable: true,
46 | value: vi.fn().mockImplementation(query => ({
47 | matches: false,
48 | media: query,
49 | onchange: null,
50 | addListener: vi.fn(), // Deprecated
51 | removeListener: vi.fn(), // Deprecated
52 | addEventListener: vi.fn(),
53 | removeEventListener: vi.fn(),
54 | dispatchEvent: vi.fn(),
55 | })),
56 | });
57 | });
58 |
59 | afterEach(() => {
60 | cleanup();
61 | });
62 |
63 | /*
64 | afterAll(() => {
65 | });
66 | */
67 |
--------------------------------------------------------------------------------
/src/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import accountReducer from "src/slices/AccountSlice";
3 | import appReducer from "src/slices/AppSlice";
4 | import pendingTransactionsReducer from "src/slices/PendingTxnsSlice";
5 | // reducers are named automatically based on the name field in the slice
6 | // exported in slice files by default as nameOfSlice.reducer
7 |
8 | const store = configureStore({
9 | reducer: {
10 | account: accountReducer,
11 | app: appReducer,
12 | pendingTransactions: pendingTransactionsReducer,
13 | },
14 | middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false }),
15 | });
16 |
17 | export type RootState = ReturnType;
18 | export type AppDispatch = typeof store.dispatch;
19 | export default store;
20 |
--------------------------------------------------------------------------------
/src/testHandlers.js:
--------------------------------------------------------------------------------
1 | import { rest } from "msw";
2 |
3 | // We use msw to intercept the network request during the test
4 | const handlers = [
5 | rest.get("https://api.coingecko.com/api/v3/simple/price", (req, res, ctx) => {
6 | return res(ctx.json({ olympus: { usd: 945.14 } }));
7 | }),
8 | rest.post("https://ipaddress/:port", (req, res, ctx) => {
9 | return res(ctx.json({}));
10 | }),
11 | rest.get("https://api.covalenthq.com/*", (req, res, ctx) => {
12 | return res(ctx.json({}));
13 | }),
14 | rest.post("https://api.thegraph.com/subgraphs/name/drondin/olympus-graph", (req, res, ctx) => {
15 | return res(
16 | ctx.json({
17 | data: {
18 | _meta: {
19 | __typename: "_Meta_",
20 | block: {
21 | __typename: "_Block_",
22 | number: 13404804,
23 | },
24 | },
25 | protocolMetrics: [
26 | {
27 | __typename: "ProtocolMetric",
28 | marketCap: "2850404741.408880328568714963827889",
29 | nextDistributedOhm: "9536.348673867",
30 | nextEpochRebase: "0.3984399885022833379898967937151319",
31 | ohmCirculatingSupply: "2606164.580244948",
32 | ohmPrice: "1093.716322835212771836103791185812",
33 | sOhmCirculatingSupply: "2393421.581431541",
34 | timestamp: "1633996882",
35 | totalSupply: "3217915.76256185",
36 | totalValueLocked: "2617724251.037744790511990363550964",
37 | },
38 | ],
39 | },
40 | }),
41 | // ctx.delay(150),
42 | );
43 | }),
44 | ];
45 |
46 | // override render method
47 | export default handlers;
48 |
--------------------------------------------------------------------------------
/src/themes/darkPalette.js:
--------------------------------------------------------------------------------
1 | export const darkPalette = {
2 | paper: {
3 | background: "linear-gradient(237.43deg, #2B313D -12.81%, #171A20 132.72%)",
4 | card: "#20222A",
5 | cardHover: "#3F4552",
6 | },
7 | background: `
8 | linear-gradient(180deg, rgba(8, 15, 53, 0), rgba(0, 0, 10, 0.9)),
9 | linear-gradient(333deg, rgba(153, 207, 255, 0.2), rgba(180, 255, 217, 0.08)),
10 | radial-gradient(circle at 77% 89%, rgba(125, 163, 169, 0.8), rgba(125, 163, 169, 0) 50%),
11 | radial-gradient(circle at 15% 95%, rgba(125, 163, 169, 0.8), rgba(125, 163, 169, 0) 43%),
12 | radial-gradient(circle at 65% 23%, rgba(137, 151, 119, 0.4), rgba(137, 151, 119, 0) 70%),
13 | radial-gradient(circle at 10% 0%, rgba(187, 211, 204, 0.33), rgba(187,211,204,0) 35%),
14 | radial-gradient(circle at 11% 100%, rgba(131, 165, 203, 0.3), rgba(131, 165, 203, 0) 30%)
15 | `,
16 | backgroundColor: "rgba(8, 15, 53, 1)",
17 | feedback: {
18 | success: "#94B9A1",
19 | userFeedback: "#49A1F2",
20 | error: "#FF6767",
21 | warning: "#FC8E5F",
22 | pnlGain: "#3D9C70",
23 | },
24 | gray: {
25 | 700: "#141722",
26 | 600: "#2C2E37",
27 | 500: "#3F4552",
28 | 90: "#8A8B90",
29 | 40: "#BBBDC0",
30 | 10: "#EEE9E2",
31 | },
32 | primary: {
33 | 300: "#F8CC82",
34 | 100: "#EAD8B8",
35 | },
36 | special: {
37 | olyZaps: "#8977F5",
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/src/themes/lightPalette.js:
--------------------------------------------------------------------------------
1 | export const lightPalette = {
2 | paper: {
3 | background: "linear-gradient(65.7deg, #F5F5F5 8.35%, #FFFFFF 100%)",
4 | card: "#EFEAE0",
5 | cardHover: "#FFFBF5",
6 | },
7 | background: "linear-gradient(180.37deg, #B3BFC5 0.49%, #D1D5D4 26.3%, #EEEAE3 99.85%)",
8 | feedback: {
9 | success: "#94B9A1",
10 | userFeedback: "#49A1F2",
11 | error: "#FF6767",
12 | warning: "#FC8E5F",
13 | pnlGain: "#3D9C70",
14 | },
15 | gray: {
16 | 700: "#FAFAFB",
17 | 600: "#A3A3A3",
18 | 500: "#676B74",
19 | 90: "#3F4552",
20 | 40: "#20222A",
21 | 10: "#181A1D",
22 | },
23 | primary: {
24 | 300: "#F8CC82",
25 | 100: "#EAD8B8",
26 | },
27 | special: {
28 | olyZaps: "#8977F5",
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/src/types/react-step-progress-bar.d.ts:
--------------------------------------------------------------------------------
1 | // Source: https://github.com/pierreericgarcia/react-step-progress-bar/issues/20#issuecomment-1033223029
2 | declare module "react-step-progress-bar" {
3 | import React from "react";
4 |
5 | interface ProgressBarProps {
6 | percent: number;
7 | stepPositions?: Array;
8 | unfilledBackground?: string;
9 | filledBackground?: string;
10 | width?: number;
11 | height?: number;
12 | hasStepZero?: boolean;
13 | text?: string;
14 | }
15 | interface StepProps {
16 | children: (props: {
17 | accomplished: boolean;
18 | transitionState: string;
19 | index: number;
20 | position: number;
21 | }) => React.ReactNode;
22 | transition?: "scale" | "rotate" | "skew";
23 | transitionDuration?: number;
24 | }
25 | class ProgressBar extends React.Component {}
26 | class Step extends React.Component {}
27 | }
28 |
--------------------------------------------------------------------------------
/src/views/404/NotFound.scss:
--------------------------------------------------------------------------------
1 | #not-found {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/src/views/404/NotFound.tsx:
--------------------------------------------------------------------------------
1 | import "src/views/404/NotFound.scss";
2 |
3 | import OlympusLogo from "src/assets/Olympus Logo.svg";
4 |
5 | export default function NotFound() {
6 | return (
7 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/views/404/__tests__/NotFound.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "src/testUtils";
2 | import NotFound from "src/views/404/NotFound";
3 | import { describe, expect, it, test } from "vitest";
4 |
5 | describe("", () => {
6 | test("should render component", () => {
7 | it("should render component", () => {
8 | const { container } = render();
9 | expect(container).toMatchSnapshot();
10 | });
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/views/Bond/components/BondDiscount.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from "@mui/material";
2 | import { Chip } from "@olympusdao/component-library";
3 | import { formatNumber } from "src/helpers";
4 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
5 |
6 | export const BondDiscount: React.VFC<{ discount: DecimalBigNumber; textOnly?: boolean }> = ({ discount, textOnly }) => {
7 | const theme = useTheme();
8 | const discountString = `${formatNumber(Number(discount.mul(new DecimalBigNumber("100").toString())), 2)}%`;
9 | return textOnly ? (
10 |
15 | {discountString}
16 |
17 | ) : (
18 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/views/Bond/components/BondDuration.tsx:
--------------------------------------------------------------------------------
1 | import { prettifySecondsInDays } from "src/helpers/timeUtil";
2 |
3 | export const BondDuration: React.VFC<{ duration: number }> = props => {
4 | return <>{props.duration === 0 ? "Instantly" : prettifySecondsInDays(props.duration)}>;
5 | };
6 |
--------------------------------------------------------------------------------
/src/views/Bond/components/BondInfoText.tsx:
--------------------------------------------------------------------------------
1 | export const BondInfoText: React.VFC<{ isInverseBond: boolean }> = ({ isInverseBond }) => (
2 | <>
3 | {isInverseBond
4 | ? `Important: Inverse bonds allow you to bond your OHM for treasury assets. Vesting time is 0 and payouts are instant.`
5 | : `Important: New bonds are auto-staked (accrue rebase rewards) and no longer vest linearly. Simply claim as gOHM at the end of the term.`}
6 | >
7 | );
8 |
--------------------------------------------------------------------------------
/src/views/Bond/components/BondModal/BondModalContainerV3.tsx:
--------------------------------------------------------------------------------
1 | import { useLocation, useNavigate, useParams } from "react-router";
2 | import { usePathForNetwork } from "src/hooks/usePathForNetwork";
3 | import { BondModal } from "src/views/Bond/components/BondModal/BondModal";
4 | import { useLiveBondsV3 } from "src/views/Bond/hooks/useLiveBonds";
5 | import { useNetwork } from "wagmi";
6 |
7 | export const BondModalContainerV3: React.VFC = () => {
8 | const navigate = useNavigate();
9 | const { chain = { id: 1 } } = useNetwork();
10 | const { id } = useParams<{ id: string }>();
11 | usePathForNetwork({ pathName: "bonds", networkID: chain.id, navigate });
12 |
13 | const { pathname } = useLocation();
14 | const isInverseBond = pathname.includes("/inverse/");
15 |
16 | const bonds = useLiveBondsV3({ isInverseBond }).data;
17 | const bond = bonds?.find(bond => bond.id === id);
18 |
19 | if (!bond) return null;
20 |
21 | return ;
22 | };
23 |
--------------------------------------------------------------------------------
/src/views/Bond/components/BondPrice.tsx:
--------------------------------------------------------------------------------
1 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
2 |
3 | export const BondPrice: React.VFC<{
4 | price: DecimalBigNumber;
5 | isInverseBond?: boolean;
6 | isV3Bond?: boolean;
7 | symbol: string;
8 | }> = ({ price, isInverseBond, symbol }) => {
9 | const oneOHM = new DecimalBigNumber("1");
10 | const bondPrice = isInverseBond ? oneOHM.div(price) : price;
11 | return (
12 | <>
13 | {bondPrice.toString({ decimals: 2, format: true, trim: false })} {symbol}
14 | >
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/views/Bond/index.tsx:
--------------------------------------------------------------------------------
1 | export { Bond as default } from "./Bond";
2 |
--------------------------------------------------------------------------------
/src/views/Bridge/__tests__/Bridge.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "src/testUtils";
2 | import { describe, expect, it } from "vitest";
3 |
4 | import Bridge from "..";
5 | describe("Bridge", () => {
6 | it("should render", async () => {
7 | render();
8 | expect(screen.queryByText("Bridge"));
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/src/views/Governance/Components/GovernanceNavigation.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Tab, Tabs } from "@mui/material";
2 | import { useLocation, useNavigate } from "react-router-dom";
3 |
4 | export const GovernanceNavigation = () => {
5 | const navigate = useNavigate();
6 | const location = useLocation();
7 |
8 | const currentPath = location.pathname;
9 | const value = currentPath.includes("/governance/delegate") ? 1 : 0;
10 |
11 | const handleChange = (_: React.SyntheticEvent, newValue: number) => {
12 | if (newValue === 0) {
13 | navigate("/governance");
14 | } else {
15 | navigate("/governance/delegate");
16 | }
17 | };
18 |
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/views/Governance/Delegation/DelegateRow.tsx:
--------------------------------------------------------------------------------
1 | import { TableCell, TableRow, Typography } from "@mui/material";
2 | import { SecondaryButton } from "@olympusdao/component-library";
3 | import { Voter } from "src/views/Governance/hooks/useGetDelegates";
4 | import { useEnsName } from "wagmi";
5 |
6 | export const DelegateRow = ({
7 | delegate,
8 | quorum,
9 | onClick,
10 | onDelegateClick,
11 | }: {
12 | delegate: Voter;
13 | quorum?: number;
14 | onClick: () => void;
15 | onDelegateClick: () => void;
16 | }) => {
17 | const { data: ensName } = useEnsName({ address: delegate.address as `0x${string}` });
18 |
19 | return (
20 |
21 |
22 | {ensName || delegate.id}
23 |
24 |
25 | {Number(delegate.latestVotingPowerSnapshot.votingPower).toFixed(2) || "0"} gOHM
26 |
27 |
28 | {delegate.delegators.length}
29 |
30 |
31 |
32 |
33 | {quorum && (Number(Number(delegate.latestVotingPowerSnapshot.votingPower) / quorum) * 100).toFixed(2)}%
34 |
35 |
36 |
37 |
38 | {
40 | onDelegateClick();
41 | }}
42 | >
43 | Delegate
44 |
45 |
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/src/views/Governance/Proposals/VoteRow.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Link, TableCell, Tooltip, Typography } from "@mui/material";
2 | import { abbreviatedNumber } from "src/helpers";
3 | import { truncateEthereumAddress } from "src/helpers/truncateAddress";
4 | import { useEnsName } from "wagmi";
5 |
6 | export const VoteRow = ({
7 | voter,
8 | reason,
9 | votes,
10 | tx,
11 | }: {
12 | voter: { address: string };
13 | reason?: string;
14 | votes: string;
15 | tx: string;
16 | }) => {
17 | const { data: ensName } = useEnsName({ address: voter.address as `0x${string}` });
18 | return (
19 | <>
20 |
21 |
22 |
23 | {ensName || truncateEthereumAddress(voter.address)}
24 |
25 |
26 | {/* Render the reason if provided, and style it as a comment */}
27 | {reason && (
28 |
29 | "{reason}"
30 |
31 | )}
32 |
33 | {abbreviatedNumber.format(Number(votes || 0))} gOHM
34 | >
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/src/views/Governance/helpers/fetchFunctionInterface.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export const fetchFunctionInterface = async (selector: string): Promise => {
4 | // from api.openchain.xyz
5 | const response = await axios.get("https://api.openchain.xyz/signature-database/v1/lookup", {
6 | params: {
7 | function: selector,
8 | },
9 | });
10 | const results = response.data.result.function[selector].map((f: { name: string }) => f.name);
11 |
12 | if (results.length > 0) {
13 | return results;
14 | } else {
15 | // from 4byte.directory
16 | const response = await axios.get("https://www.4byte.directory/api/v1/signatures/", {
17 | params: {
18 | hex_signature: selector,
19 | },
20 | });
21 | const results = response.data.results.map((f: { text_signature: string }) => f.text_signature);
22 |
23 | return results;
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/views/Governance/helpers/index.ts:
--------------------------------------------------------------------------------
1 | import { OHMChipProps } from "@olympusdao/component-library";
2 |
3 | export const toCapitalCase = (value: string): string => {
4 | return value.charAt(0).toUpperCase() + value.slice(1);
5 | };
6 |
7 | export function getDateFromBlock(
8 | targetBlock?: number,
9 | currentBlock?: number,
10 | averageBlockTimeInSeconds?: number,
11 | currentTimestamp?: number,
12 | ): Date | undefined {
13 | if (targetBlock && currentBlock && averageBlockTimeInSeconds && currentTimestamp) {
14 | const date = new Date();
15 | date.setTime((currentTimestamp + averageBlockTimeInSeconds * (targetBlock - currentBlock)) * 1000);
16 | return date;
17 | }
18 | return undefined;
19 | }
20 |
21 | export const mapProposalStatus = (status: string) => {
22 | switch (status) {
23 | case "Active":
24 | case "Succeeded":
25 | return "success" as OHMChipProps["template"];
26 | case "Executed":
27 | return "purple" as OHMChipProps["template"];
28 | case "Queued":
29 | return "userFeedback" as OHMChipProps["template"];
30 | case "Canceled":
31 | case "Expired":
32 | return "gray" as OHMChipProps["template"];
33 | case "Defeated":
34 | case "Vetoed":
35 | return "error" as OHMChipProps["template"];
36 | case "Pending":
37 | return "darkGray" as OHMChipProps["template"];
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/src/views/Governance/helpers/normalizeProposal.ts:
--------------------------------------------------------------------------------
1 | import { Proposal } from "src/views/Governance/hooks/useGetProposalFromSubgraph";
2 |
3 | // Normalizes the proposal data to match the onchain format
4 | export const normalizeProposal = (proposal: Proposal) => {
5 | return {
6 | createdAtBlock: new Date(Number(proposal.blockTimestamp) * 1000),
7 | details: {
8 | id: proposal.proposalId,
9 | proposer: proposal.proposer,
10 | targets: proposal.targets,
11 | values: proposal.values,
12 | signatures: proposal.signatures,
13 | calldatas: proposal.calldatas,
14 | startBlock: proposal.startBlock,
15 | description: proposal.description,
16 | },
17 | title: proposal.description.split(/#+\s|\n/g)[1] || `${proposal.description.slice(0, 20)}...`,
18 | txHash: proposal.transactionHash,
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/dev/useAddChain.tsx:
--------------------------------------------------------------------------------
1 | import { QueryClient, useMutation } from "@tanstack/react-query";
2 |
3 | export async function addToNetwork() {
4 | try {
5 | if (window.ethereum) {
6 | const chainId = 1;
7 | const params = {
8 | chainId: "0x" + chainId.toString(16),
9 | chainName: "Olympus Governance Fork - Ethereum",
10 | nativeCurrency: {
11 | name: "Ether",
12 | symbol: "ETH",
13 | decimals: 18,
14 | },
15 | rpcUrls: ["https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4"],
16 | };
17 |
18 | const result = await window.ethereum.request({
19 | method: "wallet_addEthereumChain",
20 | params: [params],
21 | });
22 |
23 | return result;
24 | } else {
25 | throw new Error("No Ethereum Wallet");
26 | }
27 | } catch (error) {
28 | console.log(error);
29 | return false;
30 | }
31 | }
32 |
33 | export default function useAddToNetwork() {
34 | const queryClient = new QueryClient();
35 |
36 | return useMutation(addToNetwork, {
37 | onSettled: () => {
38 | queryClient.invalidateQueries();
39 | },
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/dev/useCancelProposal.tsx:
--------------------------------------------------------------------------------
1 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
2 | import { NetworkId } from "src/networkDetails";
3 | import { useMutation, useSigner } from "wagmi";
4 |
5 | export const useCancelProposal = () => {
6 | const { data: signer } = useSigner();
7 | return useMutation(async ({ proposalId }: { proposalId: string }) => {
8 | if (signer) {
9 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
10 | const a = contract.connect(signer);
11 | const response = await a.cancel(proposalId);
12 | const tx = await response.wait();
13 | return tx;
14 | }
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/dev/useMineBlocks.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation } from "@tanstack/react-query";
2 | import { ethers } from "ethers";
3 |
4 | export const useMineBlocks = () => {
5 | return useMutation(async ({ blocks }: { blocks: number }) => {
6 | const provider = new ethers.providers.JsonRpcProvider(
7 | "https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4",
8 | );
9 | const params = [
10 | ethers.utils.hexValue(blocks), // hex encoded number of blocks to increase
11 | ];
12 | const timeParams = [ethers.utils.hexValue(blocks * 15)];
13 | const response2 = await provider.send("evm_increaseTime", timeParams);
14 | const response = await provider.send("evm_increaseBlocks", params);
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/dev/useVetoProposal.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation } from "@tanstack/react-query";
2 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
3 | import { NetworkId } from "src/networkDetails";
4 | import { useSigner } from "wagmi";
5 |
6 | export const useVetoProposal = () => {
7 | const { data: signer } = useSigner();
8 | return useMutation(async ({ proposalId }: { proposalId: string }) => {
9 | if (signer) {
10 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
11 | const a = contract.connect(signer);
12 | const response = await a.veto(proposalId);
13 | const tx = await response.wait();
14 | return tx;
15 | }
16 | });
17 | };
18 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useActivateProposal.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
3 | import { NetworkId } from "src/networkDetails";
4 | import { useSigner } from "wagmi";
5 |
6 | export const useActivateProposal = () => {
7 | const { data: signer } = useSigner();
8 | const queryClient = useQueryClient();
9 | return useMutation(
10 | async ({ proposalId }: { proposalId: number }) => {
11 | if (signer) {
12 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
13 | const a = contract.connect(signer);
14 |
15 | const activate = await a.activate(proposalId);
16 | return activate;
17 | }
18 | return true;
19 | },
20 | {
21 | onSuccess: (data, variables) => {
22 | queryClient.invalidateQueries({ queryKey: ["getProposalDetails", NetworkId.MAINNET, variables.proposalId] });
23 | },
24 | },
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useCheckDelegation.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { ethers } from "ethers";
3 | import { GOHM_ADDRESSES } from "src/constants/addresses";
4 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
5 | import { GOHM__factory } from "src/typechain";
6 | import { useProvider } from "wagmi";
7 |
8 | export const useCheckDelegation = ({ address }: { address?: string }) => {
9 | const networks = useTestableNetworks();
10 | const provider = useProvider();
11 |
12 | const { data, isFetched, isLoading } = useQuery(
13 | ["checkDelegation", networks.MAINNET, address],
14 | async () => {
15 | const contract = GOHM__factory.connect(GOHM_ADDRESSES[networks.MAINNET], provider);
16 | if (!address) {
17 | return "";
18 | } else {
19 | const delegationAddress = await contract.delegates(address);
20 | return delegationAddress === ethers.constants.AddressZero ? "" : delegationAddress;
21 | }
22 | },
23 | { enabled: !!address },
24 | );
25 | return { data, isFetched, isLoading };
26 | };
27 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useDelegateVoting.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import toast from "react-hot-toast";
3 | import { GOHM_ADDRESSES } from "src/constants/addresses";
4 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
5 | import { Cooler__factory, GOHM__factory } from "src/typechain";
6 | import { useSigner } from "wagmi";
7 |
8 | export const useDelegateVoting = () => {
9 | const { data: signer } = useSigner();
10 | const queryClient = useQueryClient();
11 | const networks = useTestableNetworks();
12 |
13 | return useMutation(
14 | async ({ address, delegationAddress }: { address: string; delegationAddress: string }) => {
15 | if (!signer) throw new Error(`Please connect a wallet`);
16 | const isGohm = address === GOHM_ADDRESSES[networks.MAINNET];
17 |
18 | if (!isGohm) {
19 | const coolerContract = Cooler__factory.connect(address, signer);
20 | const receipt = await coolerContract.delegateVoting(delegationAddress);
21 | const response = receipt.wait();
22 | return response;
23 | } else {
24 | const gohmContract = GOHM__factory.connect(address, signer);
25 | const receipt = await gohmContract.delegate(delegationAddress);
26 | const response = receipt.wait();
27 | return response;
28 | }
29 | },
30 | {
31 | onError: (error: Error) => {
32 | toast.error(error.message);
33 | },
34 | onSuccess: async tx => {
35 | queryClient.invalidateQueries({ queryKey: ["checkDelegation"] });
36 | queryClient.invalidateQueries({ queryKey: ["votingWeight"] });
37 | toast(`Successfully Delegated Voting`);
38 | },
39 | },
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useExecuteProposal.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
3 | import { NetworkId } from "src/networkDetails";
4 | import { useSigner } from "wagmi";
5 |
6 | export const useExecuteProposal = () => {
7 | const { data: signer } = useSigner();
8 | const queryClient = useQueryClient();
9 | return useMutation(
10 | async ({ proposalId }: { proposalId: number }) => {
11 | if (signer) {
12 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
13 | const a = contract.connect(signer);
14 |
15 | const execute = await a.execute(proposalId);
16 | return execute;
17 | }
18 | return true;
19 | },
20 | {
21 | onSuccess: (data, variables) => {
22 | queryClient.invalidateQueries({ queryKey: ["getProposalDetails", NetworkId.MAINNET, variables.proposalId] });
23 | },
24 | },
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetCanceledTime.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 | import { Providers } from "src/helpers/providers/Providers/Providers";
5 | import { NetworkId } from "src/networkDetails";
6 | import { ProposalCanceledEventObject } from "src/typechain/OlympusGovernorBravo";
7 |
8 | export const useGetCanceledTime = ({ proposalId, status }: { proposalId: number; status?: string }) => {
9 | const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET);
10 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
11 | return useQuery(
12 | ["getCanceledTime", NetworkId.MAINNET, proposalId, status],
13 | async () => {
14 | if (!status || status !== "Canceled") {
15 | return { createdAtBlockTime: undefined, details: undefined, txHash: undefined };
16 | }
17 | // using EVENTS
18 | const proposalExecutedEvents = await contract.queryFilter(
19 | contract.filters.ProposalCanceled(),
20 | Environment.getGovernanceStartBlock(),
21 | );
22 | const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId);
23 | const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp;
24 | if (proposal?.decode) {
25 | const details = proposal.decode(proposal.data) as ProposalCanceledEventObject;
26 | return { createdAtDate: timestamp && new Date(timestamp * 1000), details, txHash: proposal.transactionHash };
27 | }
28 | return { createdAtBlockTime: undefined, details: undefined, txHash: undefined };
29 | },
30 | { enabled: !!archiveProvider && !!contract && !!proposalId && !!status && status === "Canceled" },
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetCurrentBlockTime.tsx:
--------------------------------------------------------------------------------
1 | import { useProvider, useQuery } from "wagmi";
2 |
3 | export const useGetCurrentBlockTime = () => {
4 | const archiveProvider = useProvider();
5 | return useQuery(["getCurrentBlockTime"], async () => {
6 | const blockTime = await archiveProvider.getBlock("latest");
7 | return blockTime;
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetDelegate.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import request, { gql } from "graphql-request";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 | import { Voter } from "src/views/Governance/hooks/useGetDelegates";
5 |
6 | export const useGetDelegate = ({ id }: { id: string }) => {
7 | const query = gql`
8 | query {
9 | voter(id: "${id}") {
10 | address
11 | latestVotingPowerSnapshot {
12 | votingPower
13 | }
14 | votesCasted {
15 | proposalId
16 | reason
17 | support
18 | }
19 | delegators {
20 | id
21 | }
22 | }
23 | }
24 | `;
25 |
26 | return useQuery(["getDelegate", id], async () => {
27 | try {
28 | const subgraphUrl = Environment.getGovernanceSubgraphUrl();
29 | const response = await request<{ voter: Voter }>(subgraphUrl, query);
30 | return response.voter;
31 | } catch (error) {
32 | console.error("useGetDelegates", error);
33 | }
34 | });
35 | };
36 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetDelegates.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import request, { gql } from "graphql-request";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 |
5 | export type Voter = {
6 | id: string;
7 | address: string;
8 | latestVotingPowerSnapshot: {
9 | votingPower: string;
10 | };
11 | votesCasted: {
12 | proposalId: string;
13 | reason: string;
14 | support: number;
15 | }[];
16 | delegators: {
17 | id: string;
18 | }[];
19 | };
20 |
21 | export const useGetDelegates = () => {
22 | const query = gql`
23 | query {
24 | voters(
25 | orderBy: latestVotingPowerSnapshot__votingPower
26 | orderDirection: desc
27 | where: { latestVotingPowerSnapshot_not: null, latestVotingPowerSnapshot_: { votingPower_gt: 0.0001 } }
28 | ) {
29 | id
30 | address
31 | latestVotingPowerSnapshot {
32 | votingPower
33 | }
34 | delegators {
35 | id
36 | }
37 | }
38 | }
39 | `;
40 |
41 | return useQuery(["getDelegates"], async () => {
42 | try {
43 | const subgraphUrl = Environment.getGovernanceSubgraphUrl();
44 | const response = await request<{ voters: Voter[] }>(subgraphUrl, query);
45 | return response.voters;
46 | } catch (error) {
47 | console.error("useGetDelegates", error);
48 | return [];
49 | }
50 | });
51 | };
52 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetExecutedTime.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 | import { Providers } from "src/helpers/providers/Providers/Providers";
5 | import { NetworkId } from "src/networkDetails";
6 | import { ProposalExecutedEventObject } from "src/typechain/OlympusGovernorBravo";
7 |
8 | export const useGetExecutedTime = ({ proposalId, status }: { proposalId: number; status?: string }) => {
9 | const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET);
10 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
11 | return useQuery(
12 | ["getExecutedTime", NetworkId.MAINNET, proposalId, status],
13 | async () => {
14 | if (!status || status !== "Executed") {
15 | return { createdAtBlockTime: undefined, details: undefined, txHash: undefined };
16 | }
17 | // using EVENTS
18 | const proposalExecutedEvents = await contract.queryFilter(
19 | contract.filters.ProposalExecuted(),
20 | Environment.getGovernanceStartBlock(),
21 | );
22 | const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId);
23 | const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp;
24 | if (proposal?.decode) {
25 | const details = proposal.decode(proposal.data) as ProposalExecutedEventObject;
26 | return { createdAtDate: timestamp && new Date(timestamp * 1000), details, txHash: proposal.transactionHash };
27 | }
28 | return { createdAtBlockTime: undefined, details: undefined, txHash: undefined };
29 | },
30 | { enabled: !!archiveProvider && !!contract && !!proposalId && !!status && status === "Executed" },
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetProposalFromSubgraph.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import request, { gql } from "graphql-request";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 | import { normalizeProposal } from "src/views/Governance/helpers/normalizeProposal";
5 |
6 | export type Proposal = {
7 | proposalId: string;
8 | proposer: string;
9 | targets: string[];
10 | signatures: string[];
11 | calldatas: string[];
12 | transactionHash: string;
13 | description: string;
14 | blockTimestamp: string;
15 | blockNumber: string;
16 | startBlock: string;
17 | values: string[];
18 | };
19 |
20 | type ProposalResponse = {
21 | proposalCreated: Proposal;
22 | };
23 |
24 | export const useGetProposalFromSubgraph = ({ proposalId }: { proposalId?: string }) => {
25 | const query = gql`
26 | query {
27 | proposalCreated(id: ${proposalId}) {
28 | proposalId
29 | proposer
30 | targets
31 | signatures
32 | calldatas
33 | transactionHash
34 | description
35 | blockTimestamp
36 | blockNumber
37 | startBlock
38 | values
39 | }
40 | }
41 | `;
42 |
43 | return useQuery(
44 | ["getProposal", proposalId],
45 | async () => {
46 | try {
47 | const subgraphUrl = Environment.getGovernanceSubgraphUrl();
48 | const response = await request(subgraphUrl, query);
49 | if (!response.proposalCreated) {
50 | return null;
51 | }
52 | return normalizeProposal(response.proposalCreated);
53 | } catch (error) {
54 | console.error("useGetProposalFromSubgraph", error);
55 | return null;
56 | }
57 | },
58 | { enabled: !!proposalId },
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetProposalsFromSubgraph.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import request, { gql } from "graphql-request";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 | import { normalizeProposal } from "src/views/Governance/helpers/normalizeProposal";
5 | import { Proposal } from "src/views/Governance/hooks/useGetProposalFromSubgraph";
6 |
7 | export const useGetProposalsFromSubgraph = () => {
8 | const query = gql`
9 | query {
10 | proposalCreateds(orderBy: proposalId, orderDirection: desc) {
11 | proposalId
12 | proposer
13 | targets
14 | signatures
15 | calldatas
16 | transactionHash
17 | description
18 | blockTimestamp
19 | blockNumber
20 | startBlock
21 | values
22 | }
23 | }
24 | `;
25 |
26 | type Proposals = {
27 | proposalCreateds: Proposal[];
28 | };
29 |
30 | return useQuery(["getProposals"], async () => {
31 | try {
32 | const subgraphUrl = Environment.getGovernanceSubgraphUrl();
33 | const response = await request(subgraphUrl, query);
34 |
35 | return response.proposalCreateds.map(normalizeProposal);
36 | } catch (error) {
37 | console.error("useGetProposalsFromSubgraph", error);
38 | return [];
39 | }
40 | });
41 | };
42 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetQueuedTime.tsx:
--------------------------------------------------------------------------------
1 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
2 | import { Environment } from "src/helpers/environment/Environment/Environment";
3 | import { Providers } from "src/helpers/providers/Providers/Providers";
4 | import { NetworkId } from "src/networkDetails";
5 | import { ProposalQueuedEventObject } from "src/typechain/OlympusGovernorBravo";
6 | import { useQuery } from "wagmi";
7 |
8 | export const useGetQueuedTime = ({ proposalId }: { proposalId: number }) => {
9 | const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET);
10 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
11 | return useQuery(
12 | ["getQueuedTime", NetworkId.MAINNET, proposalId],
13 | async () => {
14 | // using EVENTS
15 | const proposalQueuedEvents = await contract.queryFilter(
16 | contract.filters.ProposalQueued(),
17 | Environment.getGovernanceStartBlock(),
18 | );
19 | const proposal = proposalQueuedEvents.find(item => item.args.id.toNumber() === proposalId);
20 | const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp;
21 | if (proposal?.decode) {
22 | const details = proposal.decode(proposal.data) as ProposalQueuedEventObject;
23 | return { createdAtDate: timestamp && new Date(timestamp * 1000), details, txHash: proposal.transactionHash };
24 | }
25 | return { createdAtBlockTime: undefined, details: undefined, txHash: undefined };
26 | },
27 | { enabled: !!archiveProvider && !!contract && !!proposalId },
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetReceipt.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
3 | import { NetworkId } from "src/networkDetails";
4 | import { useAccount, useProvider } from "wagmi";
5 |
6 | export const useGetReceipt = ({ proposalId }: { proposalId: number }) => {
7 | const { address } = useAccount();
8 | const provider = useProvider();
9 | return useQuery(
10 | ["getReceipt", NetworkId.MAINNET, proposalId, address],
11 | async () => {
12 | if (!provider || !address) return;
13 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
14 | const a = contract.connect(provider);
15 | const receipt = await a.getReceipt(proposalId, address);
16 | return receipt;
17 | },
18 | { enabled: !!provider && !!address },
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetVetoedTime.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 | import { Providers } from "src/helpers/providers/Providers/Providers";
5 | import { NetworkId } from "src/networkDetails";
6 | import { ProposalVetoedEventObject } from "src/typechain/OlympusGovernorBravo";
7 |
8 | export const useGetVetoedTime = ({ proposalId, status }: { proposalId: number; status?: string }) => {
9 | const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET);
10 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
11 | return useQuery(
12 | ["getVetoedTime", NetworkId.MAINNET, proposalId, status],
13 | async () => {
14 | if (!status || status !== "Vetoed") {
15 | return { createdAtBlockTime: undefined, details: undefined, txHash: undefined };
16 | }
17 | // using EVENTS
18 | const proposalExecutedEvents = await contract.queryFilter(
19 | contract.filters.ProposalVetoed(),
20 | Environment.getGovernanceStartBlock(),
21 | );
22 | const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId);
23 | const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp;
24 | if (proposal?.decode) {
25 | const details = proposal.decode(proposal.data) as ProposalVetoedEventObject;
26 | return { createdAtDate: timestamp && new Date(timestamp * 1000), details, txHash: proposal.transactionHash };
27 | }
28 | return { createdAtBlockTime: undefined, details: undefined, txHash: undefined };
29 | },
30 | { enabled: !!archiveProvider && !!contract && !!proposalId && !!status && status === "Vetoed" },
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetVotes.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import request, { gql } from "graphql-request";
3 | import { Environment } from "src/helpers/environment/Environment/Environment";
4 |
5 | export const useGetVotes = ({ proposalId, support }: { proposalId?: string; support: number }) => {
6 | return useQuery(
7 | ["getVotes", proposalId, support],
8 | async () => {
9 | const query = gql`
10 | query MyQuery {
11 | voteCasts(orderBy: votes, orderDirection: desc, where: {proposalId: ${proposalId}, support: ${support} }) {
12 | votes
13 | voter {
14 | address
15 | }
16 | reason
17 | support
18 | transactionHash
19 | }
20 | }
21 | `;
22 |
23 | type votesResponse = {
24 | voteCasts: {
25 | votes: string;
26 | voter: { address: string };
27 | reason: string;
28 | support: number;
29 | transactionHash: string;
30 | }[];
31 | };
32 |
33 | const subgraphUrl = Environment.getGovernanceSubgraphUrl();
34 | const response = await request(subgraphUrl, query);
35 |
36 | return response.voteCasts || [];
37 | },
38 | { enabled: !!proposalId && !!support },
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useGetVotingWeight.tsx:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers";
2 | import { formatEther } from "ethers/lib/utils.js";
3 | import { GOHM_ADDRESSES } from "src/constants/addresses";
4 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
5 | import { GOHM__factory } from "src/typechain";
6 | import { useAccount, useProvider, useQuery } from "wagmi";
7 |
8 | export const useGetVotingWeight = ({ startBlock }: { startBlock?: number }) => {
9 | const archiveProvider = useProvider();
10 | const { address } = useAccount();
11 | const networks = useTestableNetworks();
12 |
13 | return useQuery(
14 | ["votingWeight", address, startBlock],
15 | async () => {
16 | const currentBlock = await archiveProvider.getBlock("latest");
17 | if (!address) return "0";
18 | const contract = GOHM__factory.connect(GOHM_ADDRESSES[networks.MAINNET], archiveProvider);
19 | //votes at activation
20 | const currentVotes = await contract.getCurrentVotes(address);
21 |
22 | //if we're not activated yet
23 | if ((startBlock && currentBlock.number < startBlock) || !startBlock) {
24 | return formatEther(currentVotes);
25 | } else {
26 | //we're activated and need to return how contract determines weight. votes at activation or current votes, whichever is less
27 | const originalVotes = await contract.getPriorVotes(address, BigNumber.from(startBlock));
28 | const votes = originalVotes.gt(currentVotes) ? originalVotes : currentVotes;
29 | return formatEther(votes);
30 | }
31 | },
32 | { enabled: !!archiveProvider && !!address },
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useQueueProposal.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
3 | import { NetworkId } from "src/networkDetails";
4 | import { useSigner } from "wagmi";
5 |
6 | export const useQueueProposal = () => {
7 | const { data: signer } = useSigner();
8 | const queryClient = useQueryClient();
9 | return useMutation(
10 | async ({ proposalId }: { proposalId: number }) => {
11 | if (signer) {
12 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
13 | const a = contract.connect(signer);
14 |
15 | const queue = await a.queue(proposalId);
16 | return queue;
17 | }
18 | return true;
19 | },
20 | {
21 | onSuccess: (data, variables) => {
22 | queryClient.invalidateQueries({ queryKey: ["getProposalDetails", NetworkId.MAINNET, variables.proposalId] });
23 | },
24 | },
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/views/Governance/hooks/useVoteForProposal.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation } from "@tanstack/react-query";
2 | import { useQueryClient } from "@tanstack/react-query";
3 | import { GOVERNANCE_CONTRACT } from "src/constants/contracts";
4 | import { NetworkId } from "src/networkDetails";
5 | import { useSigner } from "wagmi";
6 |
7 | export const useVoteForProposal = () => {
8 | const { data: signer } = useSigner();
9 | const queryClient = useQueryClient();
10 | return useMutation(
11 | async ({ proposalId, vote, comment }: { proposalId: number; vote: number; comment?: string }) => {
12 | if (signer) {
13 | const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET);
14 | const a = contract.connect(signer);
15 | //. 0=against, 1=for, 2=abstain
16 | if (comment) {
17 | const voteResponse = await a.castVoteWithReason(proposalId, vote, comment);
18 | const receipt = await voteResponse.wait();
19 | return receipt;
20 | } else {
21 | const voteResponse = await a.castVote(proposalId, vote);
22 | const receipt = await voteResponse.wait();
23 | return receipt;
24 | }
25 | }
26 | return true;
27 | },
28 | {
29 | onSuccess: (data, variables) => {
30 | queryClient.invalidateQueries({ queryKey: ["getProposalDetails", NetworkId.MAINNET, variables.proposalId] });
31 | queryClient.invalidateQueries({ queryKey: ["getReceipt", NetworkId.MAINNET, variables.proposalId] });
32 | },
33 | },
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/views/Lending/Cooler/hooks/customHttpClient.ts:
--------------------------------------------------------------------------------
1 | const baseURL = "%{COOLER_LOANS_API_ENDPOINT}%";
2 |
3 | /**
4 | * We define a custom HTTP client for react-query to use. This is configured
5 | * through the orval tool. See orval.config.ts for more details.
6 | *
7 | * The reason this is used is to add a base URL to the requests, which orval
8 | * and react-query will not do.
9 | */
10 | export const customHttpClient = async ({
11 | url,
12 | method,
13 | params,
14 | data,
15 | }: {
16 | url: string;
17 | method: "GET" | "POST" | "PUT" | "DELETE";
18 | params?: any;
19 | data?: Record;
20 | }): Promise => {
21 | const response = await fetch(`${baseURL}${url}?` + new URLSearchParams(params), {
22 | method,
23 | headers: {
24 | "Content-Type": "application/json",
25 | },
26 | body: JSON.stringify(data),
27 | });
28 |
29 | if (!response.ok) {
30 | throw new Error(response.statusText);
31 | }
32 |
33 | return response.json();
34 | };
35 |
36 | export default customHttpClient;
37 |
--------------------------------------------------------------------------------
/src/views/Lending/Cooler/hooks/useCreateCooler.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import toast from "react-hot-toast";
3 | import { trackGAEvent, trackGtagEvent } from "src/helpers/analytics/trackGAEvent";
4 | import { CoolerFactory__factory } from "src/typechain";
5 | import { useSigner } from "wagmi";
6 |
7 | export const useCreateCooler = () => {
8 | const { data: signer } = useSigner();
9 | const queryClient = useQueryClient();
10 |
11 | return useMutation(
12 | async ({
13 | debtAddress,
14 | collateralAddress,
15 | factoryAddress,
16 | }: {
17 | debtAddress: string;
18 | collateralAddress: string;
19 | factoryAddress: string;
20 | }) => {
21 | if (!signer) throw new Error(`Please connect a wallet`);
22 | const contract = CoolerFactory__factory.connect(factoryAddress, signer);
23 | const cooler = await contract.generateCooler(collateralAddress, debtAddress);
24 | const receipt = await cooler.wait();
25 | return receipt;
26 | },
27 | {
28 | onError: (error: Error) => {
29 | toast.error(error.message);
30 | },
31 | onSuccess: async tx => {
32 | queryClient.invalidateQueries({ queryKey: ["getCoolerForWallet"] });
33 | if (tx.transactionHash) {
34 | trackGAEvent({
35 | category: "Cooler",
36 | action: "Create Cooler",
37 | dimension1: tx.transactionHash,
38 | dimension2: tx.from, // the signer, not necessarily the receipient
39 | });
40 |
41 | trackGtagEvent("Cooler", {
42 | event_category: "Create Cooler",
43 | address: tx.from.slice(2), // the signer, not necessarily the receipient
44 | txHash: tx.transactionHash.slice(2),
45 | });
46 | }
47 |
48 | toast(`Cooler Created Successfully`);
49 | },
50 | },
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/src/views/Lending/Cooler/hooks/useCreateLoan.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import { BigNumber } from "ethers";
3 | import toast from "react-hot-toast";
4 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
5 | import { balanceQueryKey } from "src/hooks/useBalance";
6 | import { contractAllowanceQueryKey } from "src/hooks/useContractAllowance";
7 | import { CoolerClearingHouse__factory } from "src/typechain";
8 | import { useSigner } from "wagmi";
9 |
10 | export const useCreateLoan = () => {
11 | const { data: signer } = useSigner();
12 | const queryClient = useQueryClient();
13 |
14 | return useMutation(
15 | async ({
16 | coolerAddress,
17 | borrowAmount,
18 | clearingHouseAddress,
19 | }: {
20 | coolerAddress: string;
21 | borrowAmount: DecimalBigNumber;
22 | clearingHouseAddress: string;
23 | }) => {
24 | if (!signer) throw new Error(`Please connect a wallet`);
25 | const contract = CoolerClearingHouse__factory.connect(clearingHouseAddress, signer);
26 | const loan = await contract.lendToCooler(coolerAddress, borrowAmount.toBigNumber(18), {
27 | gasLimit: BigNumber.from("1000000"),
28 | });
29 | const receipt = await loan.wait();
30 | return receipt;
31 | },
32 | {
33 | onError: (error: Error) => {
34 | toast.error(error.message);
35 | },
36 | onSuccess: async tx => {
37 | queryClient.invalidateQueries({ queryKey: ["getCoolerLoans"] });
38 | queryClient.invalidateQueries({ queryKey: [balanceQueryKey()] });
39 | queryClient.invalidateQueries({ queryKey: [contractAllowanceQueryKey()] });
40 | toast(`Loan Successful`);
41 | },
42 | },
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/src/views/Lending/Cooler/hooks/useExtendLoan.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import { BigNumber } from "ethers";
3 | import toast from "react-hot-toast";
4 | import { balanceQueryKey } from "src/hooks/useBalance";
5 | import { contractAllowanceQueryKey } from "src/hooks/useContractAllowance";
6 | import { CoolerClearingHouse__factory } from "src/typechain";
7 | import { useSigner } from "wagmi";
8 |
9 | export const useExtendLoan = () => {
10 | const { data: signer } = useSigner();
11 | const queryClient = useQueryClient();
12 |
13 | return useMutation(
14 | async ({
15 | coolerAddress,
16 | loanId,
17 | times,
18 | clearingHouseAddress,
19 | }: {
20 | clearingHouseAddress: string;
21 | coolerAddress: string;
22 | loanId: number;
23 | times: number;
24 | }) => {
25 | if (!signer) throw new Error(`Please connect a wallet`);
26 | const contract = CoolerClearingHouse__factory.connect(clearingHouseAddress, signer);
27 | const loan = await contract.extendLoan(coolerAddress, loanId, times, {
28 | gasLimit: BigNumber.from("1000000"),
29 | });
30 | const receipt = await loan.wait();
31 | return receipt;
32 | },
33 | {
34 | onError: (error: Error) => {
35 | toast.error(error.message);
36 | },
37 | onSuccess: async tx => {
38 | queryClient.invalidateQueries({ queryKey: ["getCoolerLoans"] });
39 | queryClient.invalidateQueries({ queryKey: [balanceQueryKey()] });
40 | queryClient.invalidateQueries({ queryKey: [contractAllowanceQueryKey()] });
41 | toast(`Successfully Extended Loan`);
42 | },
43 | },
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/views/Lending/Cooler/hooks/useGetCoolerBalance.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { GOHM_ADDRESSES } from "src/constants/addresses";
3 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
4 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
5 | import { IERC20__factory } from "src/typechain";
6 | import { useProvider } from "wagmi";
7 |
8 | export const useGetCoolerBalance = ({ coolerAddress }: { coolerAddress?: string }) => {
9 | const provider = useProvider();
10 | const networks = useTestableNetworks();
11 |
12 | const { data, isFetched, isLoading } = useQuery(
13 | ["useGetCoolerBalance", coolerAddress],
14 | async () => {
15 | try {
16 | if (!coolerAddress) return new DecimalBigNumber("0", 18);
17 | const contract = IERC20__factory.connect(GOHM_ADDRESSES[networks.MAINNET], provider);
18 | const balance = await contract.balanceOf(coolerAddress);
19 | return new DecimalBigNumber(balance, 18);
20 | } catch {
21 | return new DecimalBigNumber("0", 18);
22 | }
23 | },
24 | { enabled: !!coolerAddress },
25 | );
26 | return { data, isFetched, isLoading };
27 | };
28 |
--------------------------------------------------------------------------------
/src/views/Lending/Cooler/hooks/useRepayLoan.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import { BigNumber } from "ethers";
3 | import toast from "react-hot-toast";
4 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
5 | import { balanceQueryKey } from "src/hooks/useBalance";
6 | import { contractAllowanceQueryKey } from "src/hooks/useContractAllowance";
7 | import { Cooler__factory } from "src/typechain/factories/Cooler__factory";
8 | import { useSigner } from "wagmi";
9 |
10 | export const useRepayLoan = () => {
11 | const { data: signer } = useSigner();
12 | const queryClient = useQueryClient();
13 |
14 | return useMutation(
15 | async ({ coolerAddress, loanId, amount }: { coolerAddress: string; loanId: number; amount: DecimalBigNumber }) => {
16 | if (!signer) throw new Error(`Please connect a wallet`);
17 |
18 | const coolerContract = Cooler__factory.connect(coolerAddress, signer);
19 | const loan = await coolerContract.repayLoan(loanId, amount.toBigNumber(18), {
20 | gasLimit: BigNumber.from("1000000"),
21 | });
22 | const receipt = await loan.wait();
23 | return receipt;
24 | },
25 | {
26 | onError: (error: Error) => {
27 | toast.error(error.message);
28 | },
29 | onSuccess: async tx => {
30 | queryClient.invalidateQueries({ queryKey: ["getCoolerLoans"] });
31 | queryClient.invalidateQueries({ queryKey: [balanceQueryKey()] });
32 | queryClient.invalidateQueries({ queryKey: [contractAllowanceQueryKey()] });
33 | toast(`Successfully Repaid Loan`);
34 | },
35 | },
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/views/Lending/CoolerV2/components/CoolerV2DelegationModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SvgIcon } from "@mui/material";
2 | import { Modal } from "@olympusdao/component-library";
3 | import lendAndBorrowIcon from "src/assets/icons/lendAndBorrow.svg?react";
4 | import { DelegationManagement } from "src/views/Lending/CoolerV2/components/DelegationManagement";
5 |
6 | interface CoolerV2DelegationModalProps {
7 | open: boolean;
8 | setOpen: React.Dispatch>;
9 | }
10 |
11 | export const CoolerV2DelegationModal = ({ open, setOpen }: CoolerV2DelegationModalProps) => {
12 | return (
13 |
19 | Manage Delegations
20 |
21 | }
22 | onClose={() => setOpen(false)}
23 | >
24 |
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/views/Lending/CoolerV2/hooks/useMonoCoolerCapacity.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { multicall } from "@wagmi/core";
3 | import { DAO_TREASURY_ADDRESSES, SUSDS_ADDRESSES } from "src/constants/addresses";
4 | import { Providers } from "src/helpers/providers/Providers/Providers";
5 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
6 | import { ERC4626__factory } from "src/typechain";
7 |
8 | export const useMonoCoolerCapacity = () => {
9 | const networks = useTestableNetworks();
10 | const provider = Providers.getStaticProvider(networks.MAINNET_HOLESKY);
11 |
12 | return useQuery(["monoCoolerCapacity", networks.MAINNET_HOLESKY], async () => {
13 | const [sDebtTokenBalance] = await multicall({
14 | contracts: [
15 | {
16 | abi: ERC4626__factory.abi,
17 | address: SUSDS_ADDRESSES[networks.MAINNET_HOLESKY] as `0x${string}`,
18 | functionName: "balanceOf",
19 | args: [DAO_TREASURY_ADDRESSES[networks.MAINNET_HOLESKY] as `0x${string}`],
20 | },
21 | ],
22 | });
23 |
24 | const debtTokenBalance = await ERC4626__factory.connect(
25 | SUSDS_ADDRESSES[networks.MAINNET_HOLESKY],
26 | provider,
27 | ).convertToAssets(sDebtTokenBalance);
28 |
29 | return {
30 | globalBorrowingCapacity: debtTokenBalance,
31 | };
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/src/views/Lending/CoolerV2/utils/getAuthorizationSignature.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "ethers";
2 | import { IMonoCooler } from "src/typechain/CoolerV2Migrator";
3 | import { useSignTypedData } from "wagmi";
4 |
5 | export async function getAuthorizationSignature({
6 | userAddress,
7 | authorizedAddress,
8 | verifyingContract,
9 | chainId,
10 | deadline = Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
11 | nonce,
12 | signTypedDataAsync,
13 | }: {
14 | userAddress: string;
15 | authorizedAddress: string;
16 | verifyingContract: `0x${string}`;
17 | chainId: number;
18 | deadline?: number;
19 | nonce: string | number;
20 | signTypedDataAsync: ReturnType["signTypedDataAsync"];
21 | }) {
22 | const auth: IMonoCooler.AuthorizationStruct = {
23 | account: userAddress,
24 | authorized: authorizedAddress,
25 | authorizationDeadline: deadline,
26 | nonce: nonce.toString(),
27 | signatureDeadline: deadline,
28 | };
29 |
30 | const domain = {
31 | chainId,
32 | verifyingContract,
33 | };
34 |
35 | const types = {
36 | Authorization: [
37 | { name: "account", type: "address" },
38 | { name: "authorized", type: "address" },
39 | { name: "authorizationDeadline", type: "uint96" },
40 | { name: "nonce", type: "uint256" },
41 | { name: "signatureDeadline", type: "uint256" },
42 | ],
43 | };
44 |
45 | const signature = await signTypedDataAsync({ domain, types, value: auth });
46 | const { v, r, s } = ethers.utils.splitSignature(signature);
47 |
48 | return { auth, signature: { v, r, s } };
49 | }
50 |
--------------------------------------------------------------------------------
/src/views/Liquidity/ExternalStakePools/__tests__/ExternalStakePools.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "src/testUtils";
2 | import { ExternalStakePools } from "src/views/Liquidity/ExternalStakePools/ExternalStakePools";
3 | import { describe, expect, it } from "vitest";
4 |
5 | describe("ExternalStakePools", () => {
6 | it("renders all pool chips", () => {
7 | render();
8 |
9 | const allPoolChip = screen.getByText("All");
10 | const stablePoolChip = screen.getByText("Stable");
11 | const volatilePoolChip = screen.getByText("Volatile");
12 | const gohmPoolChip = screen.getByText("gOHM");
13 |
14 | expect(allPoolChip).not.toBeNull();
15 | expect(stablePoolChip).not.toBeNull();
16 | expect(volatilePoolChip).not.toBeNull();
17 | expect(gohmPoolChip).not.toBeNull();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/views/Liquidity/Vaults.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@mui/material";
2 | import PageTitle from "src/components/PageTitle";
3 | import { useGetSingleSidedLiquidityVaults } from "src/views/Liquidity/hooks/useGetSingleSidedLiquidityVaults";
4 | import { YourAmoDeposits } from "src/views/Liquidity/YourAMODeposits";
5 |
6 | export const Vaults = () => {
7 | const { data: vaults, isLoading } = useGetSingleSidedLiquidityVaults();
8 | const activeVaults = vaults && vaults.filter(vault => Number(vault.lpTokenBalance) > 0);
9 |
10 | return (
11 |
12 | {activeVaults && activeVaults.length > 0 && (
13 | <>
14 |
19 |
20 |
21 |
22 | >
23 | )}
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/views/Liquidity/hooks/useClaimRewards.tsx:
--------------------------------------------------------------------------------
1 | import { useQueryClient } from "@tanstack/react-query";
2 | import toast from "react-hot-toast";
3 | import { trackGAEvent, trackGtagEvent } from "src/helpers/analytics/trackGAEvent";
4 | import { BLEVaultLido__factory } from "src/typechain/factories";
5 | import { useMutation, useSigner } from "wagmi";
6 |
7 | export const useClaimRewards = () => {
8 | const { data: signer } = useSigner();
9 | const queryClient = useQueryClient();
10 |
11 | return useMutation(
12 | async ({ address }: { address: string }) => {
13 | if (!signer) throw new Error(`Please connect a wallet`);
14 | const contract = BLEVaultLido__factory.connect(address, signer);
15 | const claimTransaction = await contract.claimRewards();
16 |
17 | const receipt = await claimTransaction.wait();
18 | return receipt;
19 | },
20 | {
21 | onError: (error: Error) => {
22 | toast.error(error.message);
23 | },
24 | onSuccess: async tx => {
25 | queryClient.refetchQueries({ queryKey: ["getSingleSidedLiquidityVaults"] });
26 | queryClient.refetchQueries({ queryKey: ["getVault"] });
27 | if (tx.transactionHash) {
28 | trackGAEvent({
29 | category: "Liquidity",
30 | action: "Claim Rewards",
31 | dimension1: tx.transactionHash,
32 | dimension2: tx.from, // the signer, not necessarily the receipient
33 | });
34 |
35 | trackGtagEvent("Liquidty", {
36 | event_category: "Claim Rewards",
37 | address: tx.from.slice(2), // the signer, not necessarily the receipient
38 | txHash: tx.transactionHash.slice(2),
39 | });
40 | }
41 |
42 | toast(`Claim Successful`);
43 | },
44 | },
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/src/views/Liquidity/hooks/useCreateUserVault.tsx:
--------------------------------------------------------------------------------
1 | import { useQueryClient } from "@tanstack/react-query";
2 | import toast from "react-hot-toast";
3 | import { trackGAEvent, trackGtagEvent } from "src/helpers/analytics/trackGAEvent";
4 | import { BLEVaultManagerLido__factory } from "src/typechain";
5 | import { useMutation, useSigner } from "wagmi";
6 |
7 | export const useCreateUserVault = () => {
8 | const { data: signer } = useSigner();
9 | const queryClient = useQueryClient();
10 | return useMutation(
11 | async ({ address }: { address: string }) => {
12 | if (!signer) throw new Error(`Please connect a wallet`);
13 | const contract = BLEVaultManagerLido__factory.connect(address, signer);
14 | const createVault = await contract.deployVault();
15 |
16 | const receipt = await createVault.wait();
17 | return receipt;
18 | },
19 | {
20 | onError: (error: Error) => {
21 | toast.error(error.message);
22 | },
23 | onSuccess: async tx => {
24 | queryClient.invalidateQueries({ queryKey: ["getUserVault"] });
25 | if (tx.transactionHash) {
26 | trackGAEvent({
27 | category: "Liquidity",
28 | action: "Create Vault",
29 | dimension1: tx.transactionHash,
30 | dimension2: tx.from, // the signer, not necessarily the receipient
31 | });
32 |
33 | trackGtagEvent("Liquidty", {
34 | event_category: "Create Vault",
35 | address: tx.from.slice(2), // the signer, not necessarily the receipient
36 | txHash: tx.transactionHash.slice(2),
37 | });
38 | }
39 |
40 | toast(`Vault Created Successfully`);
41 | },
42 | },
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/src/views/Liquidity/hooks/useGetExpectedPairTokenAmount.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation } from "@tanstack/react-query";
2 | import { BigNumber } from "ethers";
3 | import { BLEVaultManagerLido__factory } from "src/typechain";
4 | import { useSigner } from "wagmi";
5 |
6 | export const useGetExpectedPairTokenAmount = () => {
7 | const { data: signer } = useSigner();
8 | return useMutation(async ({ address, lpAmount }: { address: string; lpAmount: BigNumber }) => {
9 | if (!signer) throw new Error(`Please connect a wallet`);
10 | const contract = BLEVaultManagerLido__factory.connect(address, signer);
11 | const pairTokensOut = await contract.callStatic.getExpectedPairTokenOutUser(lpAmount);
12 | return pairTokensOut;
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/src/views/Liquidity/hooks/useGetLastDeposit.tsx:
--------------------------------------------------------------------------------
1 | import { Providers } from "src/helpers/providers/Providers/Providers";
2 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
3 | import { BLEVaultLido__factory } from "src/typechain";
4 | import { useQuery } from "wagmi";
5 |
6 | export const useGetLastDeposit = ({ userVaultAddress }: { userVaultAddress?: string }) => {
7 | const networks = useTestableNetworks();
8 | return useQuery(
9 | ["getLastDeposit", networks.MAINNET, userVaultAddress],
10 | async () => {
11 | if (!userVaultAddress) return;
12 | const provider = Providers.getStaticProvider(networks.MAINNET);
13 | const contract = BLEVaultLido__factory.connect(userVaultAddress, provider);
14 | const lastDeposit = (await contract.lastDeposit()).toString();
15 | return lastDeposit;
16 | },
17 | { enabled: !!userVaultAddress },
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/Liquidity/hooks/useGetUserVault.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { Providers } from "src/helpers/providers/Providers/Providers";
3 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
4 | import { BLEVaultManagerLido__factory } from "src/typechain";
5 | import { useAccount } from "wagmi";
6 |
7 | export const useGetUserVault = ({ address }: { address?: string }) => {
8 | const networks = useTestableNetworks();
9 | const { address: walletAddress = "" } = useAccount();
10 | return useQuery(
11 | ["getUserVault", networks.MAINNET, address, walletAddress],
12 | async () => {
13 | if (!address) return;
14 | const provider = Providers.getStaticProvider(networks.MAINNET);
15 | const contract = BLEVaultManagerLido__factory.connect(address, provider);
16 | const userVaultAddress = await contract.userVaults(walletAddress).catch(() => undefined);
17 | return userVaultAddress;
18 | },
19 | { enabled: !!address },
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/views/Liquidity/hooks/useGetVault.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { useTestableNetworks } from "src/hooks/useTestableNetworks";
3 | import { getVaultInfo } from "src/views/Liquidity/hooks/useGetSingleSidedLiquidityVaults";
4 | import { useAccount } from "wagmi";
5 |
6 | export const useGetVault = ({ address }: { address?: string }) => {
7 | const networks = useTestableNetworks();
8 | const { address: walletAddress = "" } = useAccount();
9 | return useQuery(
10 | ["getVault", networks.MAINNET, address],
11 | async () => {
12 | if (!address) return;
13 | const vault = await getVaultInfo(address, networks.MAINNET, walletAddress);
14 | return vault;
15 | },
16 | { enabled: !!address },
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/src/views/MyBalances/LearnAboutGohm.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Typography, useTheme } from "@mui/material";
2 | import { SecondaryButton } from "@olympusdao/component-library";
3 | import { useAccount } from "wagmi";
4 |
5 | export const LearnAboutGohm = () => {
6 | const theme = useTheme();
7 | const { isConnected } = useAccount();
8 | return (
9 | <>
10 |
11 | What is gOHM?
12 |
13 | gOHM is Olympus protocol's governance token, acquired by wrapping OHM for voting and collateral. It can be
14 | unwrapped to OHM.
15 |
16 |
17 |
18 |
19 | Learn More
20 |
21 |
22 | >
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/views/MyBalances/LearnAboutOhm.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Typography, useTheme } from "@mui/material";
2 | import { SecondaryButton } from "@olympusdao/component-library";
3 | import { useAccount } from "wagmi";
4 |
5 | export const LearnAboutOhm = () => {
6 | const theme = useTheme();
7 | const { isConnected } = useAccount();
8 | return (
9 | <>
10 |
11 | What is OHM?
12 |
13 | OHM is the native token of the Olympus protocol. OHM is used in liquid markets. OHM is fully-backed by the
14 | Olympus treasury.
15 |
16 |
17 |
18 |
22 | Get OHM
23 |
24 |
25 | >
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/views/Stake/Stake.tsx:
--------------------------------------------------------------------------------
1 | import "src/views/Stake/Stake.scss";
2 |
3 | import { Box, Typography } from "@mui/material";
4 | import { Icon, Modal } from "@olympusdao/component-library";
5 | import { memo } from "react";
6 | import { useNavigate, useSearchParams } from "react-router-dom";
7 | import { usePathForNetwork } from "src/hooks/usePathForNetwork";
8 | import { ClaimsArea } from "src/views/Stake/components/ClaimsArea/ClaimsArea";
9 | import { StakeArea } from "src/views/Stake/components/StakeArea/StakeArea";
10 | import { useNetwork } from "wagmi";
11 |
12 | const Stake: React.FC = () => {
13 | const navigate = useNavigate();
14 | const { chain = { id: 1 } } = useNetwork();
15 | usePathForNetwork({ pathName: "stake", networkID: chain.id, navigate });
16 | const [searchParams, setSearchParams] = useSearchParams();
17 | const isStake = searchParams.get("unstake") ? false : true;
18 | return (
19 |
22 |
23 |
24 |
25 | {isStake ? "Wrap" : "Unwrap"}
26 |
27 |
28 | >
29 | }
30 | open={true}
31 | onClose={() => navigate("/my-balances")}
32 | maxWidth="520px"
33 | minHeight="200px"
34 | >
35 |
36 |
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default memo(Stake);
44 |
--------------------------------------------------------------------------------
/src/views/Stake/__tests__/Stake.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from "src/testUtils";
2 | import Stake from "src/views/Stake/Stake";
3 | import { describe, expect, it, test } from "vitest";
4 |
5 | describe("", () => {
6 | test("should render component", () => {
7 | it("should render component", async () => {
8 | const { container } = render();
9 | expect(container).toMatchSnapshot();
10 | });
11 | });
12 |
13 | test("should render correct staking headers", () => {
14 | it("should render correct staking headers", () => {
15 | const { container } = render();
16 | // there should be a header inviting user to Stake
17 | expect(screen.getAllByText("Stake")[0]);
18 | // there should be a Farm Pool table
19 |
20 | expect(screen.getByText("Farm Pool"));
21 | expect(container).toMatchSnapshot();
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/views/Stake/components/StakeArea/StakeArea.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@mui/material";
2 | import { useState } from "react";
3 | import { StakeInputArea } from "src/views/Stake/components/StakeArea/components/StakeInputArea/StakeInputArea";
4 | import { useAccount } from "wagmi";
5 |
6 | export const StakeArea: React.FC = () => {
7 | const [isZoomed, setIsZoomed] = useState(false);
8 | const { isConnected } = useAccount();
9 |
10 | return (
11 | <>
12 |
13 |
14 |
15 | >
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/views/Stake/components/StakeArea/components/StakeBalances.tsx:
--------------------------------------------------------------------------------
1 | import { DecimalBigNumber } from "src/helpers/DecimalBigNumber/DecimalBigNumber";
2 |
3 | const DECIMAL_PLACES_SHOWN = 4;
4 |
5 | export const formatBalance = (balance?: DecimalBigNumber) =>
6 | balance?.toString({ decimals: DECIMAL_PLACES_SHOWN, trim: false, format: true });
7 |
--------------------------------------------------------------------------------
/src/views/TreasuryDashboard/__tests__/TreasuryDashboard.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "src/testUtils";
2 | import TreasuryDashboard from "src/views/TreasuryDashboard/TreasuryDashboard";
3 | import { describe, expect, it, test } from "vitest";
4 |
5 | describe("", () => {
6 | test("should render component", () => {
7 | it("should render component", () => {
8 | const { container } = render();
9 | expect(container).toMatchSnapshot();
10 | });
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/views/TreasuryDashboard/__tests__/TreasuryMobile.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { createMatchMedia } from "src/testHelpers";
2 | import { render } from "src/testUtils";
3 | import TreasuryDashboard from "src/views/TreasuryDashboard/TreasuryDashboard";
4 | import { beforeAll, describe, expect, it, test } from "vitest";
5 |
6 | beforeAll(() => {
7 | window.matchMedia = createMatchMedia("300px");
8 | });
9 |
10 | describe(" Mobile", () => {
11 | test("should render component", () => {
12 | it("should render component", () => {
13 | const { container } = render();
14 | expect(container).toMatchSnapshot();
15 | });
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/views/TreasuryDashboard/components/Graph/Constants.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { CategoricalChartFunc } from "recharts/types/chart/generateCategoricalChart";
3 |
4 | export const PARAM_DAYS = "days";
5 | export const DEFAULT_DAYS = 30;
6 |
7 | export const PARAM_IGNORE_CACHE = "ignoreCache";
8 |
9 | export const PARAM_TOKEN = "token";
10 | export const PARAM_TOKEN_OHM = "OHM";
11 | export const PARAM_TOKEN_GOHM = "gOHM";
12 |
13 | export const PARAM_DAYS_OFFSET = "daysOffset";
14 |
15 | export type GraphProps = {
16 | /**
17 | * A value of null indicates that no earliestDate has been loaded (asynchronously).
18 | * Components should avoid loading any data until earliestDate is non-null.
19 | */
20 | earliestDate: string | null;
21 | subgraphDaysOffset: number | undefined;
22 | activeToken?: string;
23 | ignoreCache?: boolean;
24 | onMouseMove?: CategoricalChartFunc;
25 | };
26 |
27 | export type LiquidBackingProps = {
28 | isLiquidBackingActive: boolean;
29 | };
30 |
31 | export type AssetsTableProps = {
32 | selectedIndex: number;
33 | };
34 |
35 | // These constants are used by charts to have consistent colours
36 | // Source: https://www.figma.com/file/RCfzlYA1i8wbJI3rPGxxxz/SubGraph-Charts-V3?node-id=0%3A1
37 | export const DEFAULT_COLORS: string[] = [
38 | "#917BD9",
39 | "#49A1F2",
40 | "#95B7A1",
41 | "#E49471",
42 | "#D85F73",
43 | "#A3CFF0",
44 | "#70E8C7",
45 | "#DF7FD0",
46 | "#F6BD67",
47 | "#F090A0",
48 | ];
49 |
50 | export const DEFAULT_BULLETPOINT_COLOURS: CSSProperties[] = DEFAULT_COLORS.map(value => {
51 | return {
52 | background: value,
53 | };
54 | });
55 |
--------------------------------------------------------------------------------
/src/views/TreasuryDashboard/components/Graph/helpers/ChartHelper.tsx:
--------------------------------------------------------------------------------
1 | import { Theme } from "@mui/material/styles";
2 |
3 | export const getTickStyle = (theme: Theme): Record => {
4 | return {
5 | stroke: theme.palette.primary.light,
6 | fill: theme.palette.primary.light,
7 | strokeWidth: "0.1px",
8 | };
9 | };
10 |
--------------------------------------------------------------------------------
/src/views/TreasuryDashboard/components/KnownIssues/KnownIssues.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, Typography } from "@mui/material";
2 |
3 | /**
4 | * React Component that renders the contents of a Markdown file
5 | * and displays them in a notification banner.
6 | */
7 | const KnownIssues = (): JSX.Element => {
8 | return (
9 |
10 |
11 | {/* Consistent with heading titles of the other components in the TreasuryDashboard. See ChartCard. */}
12 |
13 | Disclaimers
14 |
15 |
16 |
29 |
30 | Illiquid assets have been removed from market value and will be re-introduced when they reach their date of
31 | maturity
32 |
33 | Due to technical limitations, the balance and value of native ETH is not included
34 | There may be a visible delay when capital is deployed to a new contract or blockchain
35 |
36 | The timestamp shown in each tooltip represents the time of the most recently-indexed block across all chains
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default KnownIssues;
44 |
--------------------------------------------------------------------------------
/src/views/Utility/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@mui/material";
2 | import PageTitle from "src/components/PageTitle";
3 | import { LendingMarkets } from "src/views/Lending/LendingMarkets";
4 | import { ExternalStakePools } from "src/views/Liquidity/ExternalStakePools/ExternalStakePools";
5 | import { Vaults } from "src/views/Liquidity/Vaults";
6 |
7 | export const Utility = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/src/views/V1-Stake/V1-Stake.scss:
--------------------------------------------------------------------------------
1 | .stake-action-row.v1-row {
2 | display: flex;
3 | width: 70%;
4 | margin: 0 auto;
5 | .learn-more-button {
6 | width: 91%;
7 | height: 40px;
8 | p {
9 | margin-right: 5px;
10 | }
11 | }
12 | .migrate-button {
13 | width: 100%;
14 | font-size: 16px;
15 | height: 40px;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/views/V1-Stake/__tests__/V1-Stake.unit.test.tsx:
--------------------------------------------------------------------------------
1 | import { connectWallet } from "src/testHelpers";
2 | import { render } from "src/testUtils";
3 | import V1Stake from "src/views/V1-Stake/V1-Stake";
4 | import { afterEach, describe, expect, it, test, vi } from "vitest";
5 |
6 | afterEach(() => {
7 | vi.clearAllMocks();
8 | vi.restoreAllMocks();
9 | });
10 |
11 | describe("", () => {
12 | test("should render component. not connected", () => {
13 | it("should render component. not connected", async () => {
14 | const { container } = render();
15 | expect(container).toMatchSnapshot();
16 | });
17 | });
18 | test("should render the stake input Area when connected", () => {
19 | it("should render the stake input Area when connected", async () => {
20 | connectWallet();
21 | const { container } = render();
22 | expect(container).toMatchSnapshot();
23 | });
24 | });
25 | test("should render the stake input Area when connected", () => {
26 | it("should render the v1 migration modal", async () => {
27 | connectWallet();
28 | const { container } = render();
29 | expect(container).toMatchSnapshot();
30 | });
31 | });
32 | test("should render the v1 migration modal and banner", () => {
33 | it("should render the v1 migration modal and banner", async () => {
34 | connectWallet();
35 | const { container } = render();
36 | expect(container).toMatchSnapshot();
37 | });
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/src/views/index.ts:
--------------------------------------------------------------------------------
1 | export { Bond } from "./Bond/Bond";
2 | export { default as Stake } from "./Stake/Stake";
3 | export { default as V1Stake } from "./V1-Stake/V1-Stake";
4 | export { default as TreasuryDashboard } from "./TreasuryDashboard/TreasuryDashboard";
5 |
--------------------------------------------------------------------------------
/tests/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "baseUrl": "../../node_modules",
5 | "types": ["cypress", "@types/puppeteer-core", "@synthetixio/synpress/support", "cypress-wait-until", "@testing-library/cypress"],
6 | "outDir": "./output"
7 | },
8 | "include": ["**/*.*"]
9 | }
--------------------------------------------------------------------------------
/tests/unit/launcher.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | const { spawn } = require("child_process");
3 |
4 | const NODE_PORT = 8545;
5 | const NODE_HOST = "127.0.0.1";
6 | const FORK_BLOCK = 13377190;
7 | // Fork network
8 | console.log("Starting Hardhat");
9 | const app_node = spawn("npx", [
10 | "hardhat",
11 | "node",
12 | "--fork",
13 | "https://eth-mainnet.alchemyapi.io/v2/_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC",
14 | "--fork-block-number",
15 | FORK_BLOCK,
16 | "--hostname",
17 | NODE_HOST,
18 | "--port",
19 | NODE_PORT,
20 | ]);
21 |
22 | app_node.stdout.on("data", data => {
23 | // console.log(`${data}`); // Uncomment this to see hardhat logging
24 | // Launch tests
25 | if (data.includes("Account #19")) {
26 | console.log("Starting Tests");
27 | process.env["VITE_SELF_HOSTED_NODE"] = `http://${NODE_HOST}:${NODE_PORT}`;
28 | const app_test = spawn("npx", ["react-scripts", "test"].concat(process.argv.slice(2)));
29 | app_test.stdout.on("data", data => {
30 | console.error(`${data}`);
31 | });
32 | app_test.stderr.on("data", data => {
33 | console.error(`${data}`);
34 | });
35 | app_test.on("close", code => {
36 | console.log(`Tests ended with code ${code}`);
37 | process.exit(code);
38 | });
39 | }
40 | });
41 |
42 | app_node.stderr.on("data", data => {
43 | console.error(`${data}`);
44 | });
45 |
46 | app_node.on("close", code => {
47 | console.log(`Network fork ended with code ${code}`);
48 | });
49 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "emitDecoratorMetadata": true,
5 | "experimentalDecorators": true,
6 | "esModuleInterop": true,
7 | "lib": ["dom", "dom.iterable", "esnext"],
8 | "types": ["vite/client", "vite-plugin-svgr/client"],
9 | "module": "ESNext",
10 | "moduleResolution": "node",
11 | "resolveJsonModule": true,
12 | "sourceMap": true,
13 | "target": "ESNext",
14 | "jsx": "react-jsx",
15 | "baseUrl": ".",
16 | "skipLibCheck": true,
17 | "allowJs": true,
18 | "allowSyntheticDefaultImports": true,
19 | "strict": true,
20 | "forceConsistentCasingInFileNames": true,
21 | "isolatedModules": true,
22 | "noEmit": true,
23 | "noFallthroughCasesInSwitch": true
24 | },
25 | "include": ["./index.d.ts", "src", "**/*.test.ts", "**/*.test.tsx"],
26 | "exclude": ["node_modules", "vendor", "public"],
27 | "compileOnSave": false
28 | }
29 |
--------------------------------------------------------------------------------
/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------