├── .env ├── .envrc ├── .eslintrc.json ├── .gcloudignore ├── .gitignore ├── .gitmodules ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .yarnrc ├── LICENSE ├── README.md ├── app.yaml ├── babel.config.js ├── cypress.json ├── cypress ├── integration │ ├── add-liquidity.test.ts │ ├── landing.test.ts │ ├── lists.test.ts │ ├── pool.test.ts │ ├── remove-liquidity.test.ts │ ├── send.test.ts │ ├── swap.test.ts │ └── token-warning.ts ├── support │ ├── commands.d.ts │ ├── commands.js │ └── index.js └── tsconfig.json ├── dev ├── generate_tokens │ ├── base_tokens_map.js │ ├── index.js │ └── utils.js └── setupTests.ts ├── jest.config.js ├── package.json ├── public ├── 451.html ├── CNAME ├── favicon-new.png ├── favicon.png ├── images │ ├── 180x180_App_Icon.png │ ├── 192x192_App_Icon.png │ ├── 384x384_App_Icon.png │ └── flags │ │ ├── de.svg │ │ ├── en.svg │ │ ├── es.svg │ │ ├── fr.svg │ │ ├── jp.svg │ │ ├── pt-br.svg │ │ ├── round │ │ ├── ar.svg │ │ ├── au.svg │ │ ├── br.svg │ │ ├── ca.svg │ │ ├── ch.svg │ │ ├── cl.svg │ │ ├── co.svg │ │ ├── cz.svg │ │ ├── de.svg │ │ ├── dk.svg │ │ ├── eu.svg │ │ ├── hk.svg │ │ ├── il.svg │ │ ├── in.svg │ │ ├── is.svg │ │ ├── jp.svg │ │ ├── kr.svg │ │ ├── mx.svg │ │ ├── my.svg │ │ ├── no.svg │ │ ├── nz.svg │ │ ├── ph.svg │ │ ├── pl.svg │ │ ├── se.svg │ │ ├── sg.svg │ │ ├── th.svg │ │ ├── uk.svg │ │ ├── us.svg │ │ ├── vn.svg │ │ └── za.svg │ │ ├── tr.svg │ │ └── zh.svg ├── index.html ├── locales │ ├── aurora │ │ └── en.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── polygon │ │ └── en.json │ ├── pt-br.json │ ├── tr.json │ └── zh.json └── manifest.json ├── src ├── apollo │ ├── client.js │ └── queries.js ├── assets │ ├── images │ │ ├── arrow-down-blue.svg │ │ ├── arrow-down-grey.svg │ │ ├── arrow-right-white.png │ │ ├── arrow-right.svg │ │ ├── auroraplus.svg │ │ ├── avalanche_token_round.png │ │ ├── big_unicorn.png │ │ ├── blue-loader.svg │ │ ├── brave.png │ │ ├── circle-grey.svg │ │ ├── circle.svg │ │ ├── coinbaseWalletIcon.svg │ │ ├── dragonfly.svg │ │ ├── dropdown-blue.svg │ │ ├── dropdown.svg │ │ ├── dropup-blue.svg │ │ ├── electric.svg │ │ ├── ethereal.svg │ │ ├── ethereum-logo.png │ │ ├── farm.webp │ │ ├── fortmaticIcon.png │ │ ├── hero.webp │ │ ├── jump.svg │ │ ├── lemniscap.svg │ │ ├── link.svg │ │ ├── magnifying-glass.svg │ │ ├── menu.svg │ │ ├── metamask.png │ │ ├── noise.png │ │ ├── plus-blue.svg │ │ ├── plus-grey.svg │ │ ├── portisIcon.png │ │ ├── question-mark.svg │ │ ├── question.svg │ │ ├── spinner.svg │ │ ├── swap.webp │ │ ├── token-list │ │ │ ├── lists-dark.png │ │ │ └── lists-light.png │ │ ├── token-logo.png │ │ ├── trustWallet.png │ │ ├── walletConnectIcon.svg │ │ ├── x.svg │ │ └── xl_uni.png │ └── svg │ │ ├── QR.svg │ │ ├── avaxwapBlack.svg │ │ ├── avaxwapWhite.svg │ │ ├── icon.svg │ │ ├── lightcircle.svg │ │ ├── planets.svg │ │ ├── spaceman_on_planet.svg │ │ ├── spacemen_and_planets.svg │ │ └── trilogo-new.svg ├── components │ ├── AccountDetails │ │ ├── Copy.tsx │ │ ├── Transaction.tsx │ │ └── index.tsx │ ├── AddToMetaMask │ │ └── index.tsx │ ├── AddressInputPanel │ │ └── index.tsx │ ├── ApproveButton │ │ └── index.tsx │ ├── BackButton │ │ └── index.tsx │ ├── BalanceButton │ │ ├── BalanceButton.styles.tsx │ │ ├── BalanceButtonValueEnum.ts │ │ └── index.tsx │ ├── BridgesMenu │ │ ├── BridgesMenu.constants.tsx │ │ ├── BridgesMenu.styles.tsx │ │ └── index.tsx │ ├── Button │ │ └── index.tsx │ ├── CaptionWithIcon │ │ └── index.tsx │ ├── Card │ │ └── index.tsx │ ├── CoingeckoPriceChart │ │ └── index.tsx │ ├── Column │ │ └── index.tsx │ ├── Confetti │ │ └── index.tsx │ ├── ContractAddress │ │ ├── ContractAddress.styles.tsx │ │ └── index.tsx │ ├── CountUp │ │ └── index.tsx │ ├── CurrencyInputPanel │ │ ├── index.tsx │ │ └── useCurrencyInputPanel.ts │ ├── CurrencyLogo │ │ └── index.tsx │ ├── DoubleLogo │ │ └── index.tsx │ ├── EarnTriSortAndFilter │ │ ├── EarnTriSortAndFilterContainer.tsx │ │ └── SortingArrow.tsx │ ├── ErrorBoundary │ │ └── index.tsx │ ├── FormattedCurrencyAmount │ │ └── index.tsx │ ├── GasFeeAlert.tsx │ ├── GovernanceMenu │ │ ├── GovernanceMenu.constants.tsx │ │ └── index.tsx │ ├── Header │ │ ├── Header.styles.tsx │ │ ├── Polling.tsx │ │ ├── URLWarning.tsx │ │ └── index.tsx │ ├── Identicon │ │ └── index.tsx │ ├── LanguageSelection │ │ └── index.tsx │ ├── ListLogo │ │ └── index.tsx │ ├── Loader │ │ └── index.tsx │ ├── Logo │ │ └── index.tsx │ ├── Menu │ │ └── index.tsx │ ├── Modal │ │ └── index.tsx │ ├── ModalViews │ │ └── index.tsx │ ├── MultipleCurrencyLogo │ │ └── index.tsx │ ├── NavigationTabs │ │ └── index.tsx │ ├── NumericalInput │ │ └── index.tsx │ ├── Page │ │ └── index.tsx │ ├── Popover │ │ └── index.tsx │ ├── Popups │ │ ├── ListUpdatePopup.tsx │ │ ├── PopupItem.tsx │ │ ├── TransactionPopup.tsx │ │ └── index.tsx │ ├── PositionCard │ │ ├── MinimalPositionCard │ │ │ └── index.tsx │ │ ├── PositionCard.styles.tsx │ │ ├── PositionCard.types.ts │ │ ├── StablePositionCard │ │ │ └── index.tsx │ │ └── index.tsx │ ├── ProgressSteps │ │ └── index.tsx │ ├── PurchaseForm │ │ ├── index.tsx │ │ └── input.tsx │ ├── QuestionHelper │ │ └── index.tsx │ ├── Row │ │ └── index.tsx │ ├── SearchModal │ │ ├── CommonBases.tsx │ │ ├── CurrencyList.tsx │ │ ├── CurrencySearch.tsx │ │ ├── CurrencySearchModal.tsx │ │ ├── ListSelect.tsx │ │ ├── SortButton.tsx │ │ ├── filtering.ts │ │ ├── sorting.ts │ │ └── styleds.tsx │ ├── Select │ │ ├── Select.styles.tsx │ │ └── index.tsx │ ├── Settings │ │ ├── Settings.styles.tsx │ │ └── index.tsx │ ├── Slider │ │ └── index.tsx │ ├── SponsoredFarmLink │ │ ├── SponsoredFarmLink.constants.ts │ │ ├── SponsoredFarmLink.styles.tsx │ │ └── index.tsx │ ├── StableSwapLiquiditySlippage │ │ └── index.tsx │ ├── StakeTri │ │ ├── StakeInputPanel.styles.tsx │ │ ├── StakeInputPanel.tsx │ │ └── StakeTriDataCard.tsx │ ├── StyledMenu │ │ └── index.tsx │ ├── Toggle │ │ └── index.tsx │ ├── TokenWarningModal │ │ └── index.tsx │ ├── Tooltip │ │ └── index.tsx │ ├── TransactionConfirmationModal │ │ └── index.tsx │ ├── TransactionSettings │ │ └── index.tsx │ ├── TriPriceModal │ │ ├── CirculatingSupplyMarketCap.tsx │ │ └── index.tsx │ ├── TripleCurrencyLogo │ │ └── index.tsx │ ├── WalletModal │ │ ├── Option.tsx │ │ ├── PendingView.tsx │ │ └── index.tsx │ ├── Warning │ │ └── index.tsx │ ├── Web3Provider │ │ └── index.tsx │ ├── Web3Status │ │ └── index.tsx │ ├── analytics │ │ └── GoogleAnalyticsReporter.tsx │ ├── earn │ │ ├── FarmBanner │ │ │ └── index.tsx │ │ ├── FarmsPortfolio │ │ │ └── index.tsx │ │ ├── PoolCardTri │ │ │ ├── ClaimRewardModalTri.tsx │ │ │ ├── ClaimableRewards.tsx │ │ │ ├── ManageStake.tsx │ │ │ ├── PoolCardTri.styles.tsx │ │ │ ├── PoolCardTriRewardText.tsx │ │ │ ├── StakingModalTri.tsx │ │ │ ├── UnstakingModalTri.tsx │ │ │ ├── ZapModal.tsx │ │ │ └── index.tsx │ │ └── styled.ts │ └── swap │ │ ├── AdvancedSwapDetails.tsx │ │ ├── AdvancedSwapDetailsDropdown.tsx │ │ ├── BetterTradeLink.tsx │ │ ├── ConfirmSwapModal.tsx │ │ ├── FormattedPriceImpact.tsx │ │ ├── SwapModalFooter.tsx │ │ ├── SwapModalHeader.tsx │ │ ├── SwapRoute.tsx │ │ ├── TradePrice.tsx │ │ ├── confirmPriceImpactWithoutFee.ts │ │ └── styleds.tsx ├── connectors │ ├── __mocks__ │ │ ├── NetworkConnector.ts │ │ └── index.ts │ ├── fortmatic.d.ts │ └── index.ts ├── constants │ ├── abis │ │ ├── argent-wallet-detector.json │ │ ├── argent-wallet-detector.ts │ │ ├── bridge-token.json │ │ ├── complex-n-rewarder.json │ │ ├── complex-rewarder.json │ │ ├── ens-public-resolver.json │ │ ├── ens-registrar.json │ │ ├── erc20.json │ │ ├── erc20.ts │ │ ├── erc20_bytes32.json │ │ ├── masterchef.json │ │ ├── masterchefv2.json │ │ ├── migrator.json │ │ ├── migrator.ts │ │ ├── pTri │ │ │ └── ptri.json │ │ ├── polygon │ │ │ ├── IUniswapV2Pair.json │ │ │ ├── IUniswapV2Router02.json │ │ │ └── weth.json │ │ ├── stableswap │ │ │ ├── auErc20.json │ │ │ ├── lpToken.json │ │ │ ├── metaSwap.json │ │ │ ├── metaSwapDeposit.json │ │ │ └── swapFlashLoan.json │ │ ├── staking-rewards.ts │ │ └── unisocks.json │ ├── farms.ts │ ├── fiat │ │ └── index.ts │ ├── index.ts │ ├── lists.ts │ ├── multicall │ │ ├── abi.json │ │ └── index.ts │ └── tokens │ │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── index.test.ts.snap │ │ └── index.test.ts │ │ ├── index.ts │ │ └── tokenListValidationSchema.json ├── data │ ├── Allowances.ts │ ├── Reserves.ts │ ├── TotalStakedInPool.ts │ └── TotalSupply.ts ├── fetchers │ ├── coingecko-api-id.ts │ ├── farms.ts │ └── pTRI.ts ├── hooks │ ├── Tokens.ts │ ├── Trades.ts │ ├── index.ts │ ├── useApproveCallback.ts │ ├── useCalculateStableSwapPairs.ts │ ├── useColor.ts │ ├── useContract.ts │ ├── useCopyClipboard.ts │ ├── useCurrentBlockTimestamp.ts │ ├── useDebounce.ts │ ├── useENS.ts │ ├── useEmbeddedSwapUI.ts │ ├── useFetchListCallback.ts │ ├── useGetTokenByAddress.ts │ ├── useGetTokenPrice.ts │ ├── useHttpLocations.ts │ ├── useInterval.ts │ ├── useIsWindowVisible.ts │ ├── useLast.ts │ ├── useMigrateCallback.ts │ ├── useNormalizeTokensToDecimal.tsx │ ├── useOnClickOutside.tsx │ ├── usePTRIAPR.ts │ ├── usePTRIRemittances.ts │ ├── useParsedQueryString.ts │ ├── usePrevious.ts │ ├── usePtri.ts │ ├── useRemoveLiquidityPriceImpact.tsx │ ├── useSelectChain.ts │ ├── useStablePoolsData.ts │ ├── useStableSwapCallback.ts │ ├── useStableSwapPoolsStatuses.ts │ ├── useStableSwapRemoveLiquidity.ts │ ├── useSwapCallback.ts │ ├── useTLP.ts │ ├── useTimeout.ts │ ├── useTimestampFromBlock.ts │ ├── useToggle.ts │ ├── useToggledVersion.ts │ ├── useTransactionDeadline.ts │ ├── useTriPrice.ts │ ├── useUSDCPrice.ts │ ├── useWindowSize.ts │ └── useWrapCallback.ts ├── i18n.ts ├── index.tsx ├── pages │ ├── AddLiquidity │ │ ├── ConfirmAddModalBottom.tsx │ │ ├── PoolPriceBar.tsx │ │ ├── PriceAndPoolShare.tsx │ │ ├── index.tsx │ │ └── redirects.tsx │ ├── App.tsx │ ├── AppBody.tsx │ ├── EarnTri │ │ ├── EarnTri.styles.tsx │ │ ├── EarnTri.tsx │ │ ├── FarmType.tsx │ │ ├── index.tsx │ │ └── useFarmsSortAndFilter.tsx │ ├── Landing │ │ ├── Landing.styles.tsx │ │ └── index.tsx │ ├── Pool │ │ ├── index.tsx │ │ └── styleds.tsx │ ├── PoolFinder │ │ └── index.tsx │ ├── RemoveLiquidity │ │ ├── index.tsx │ │ └── redirects.tsx │ ├── StableSwapPool │ │ ├── index.tsx │ │ └── styleds.tsx │ ├── StableSwapPoolAddLiquidity │ │ ├── StableSwapPoolAddLiquidityApprovalsRow.tsx │ │ ├── StableSwapPoolAddLiquidityImpl.tsx │ │ ├── confirmStableSwapAddLiquiditySlippage.tsx │ │ └── index.tsx │ ├── StableSwapPoolRemoveLiquidity │ │ ├── StableSwapPoolRemoveLiquidity.styles.tsx │ │ ├── StableSwapPoolRemoveLiquidityImpl.tsx │ │ ├── StableSwapRemoveLiquidityCurrencyRow.tsx │ │ ├── StableSwapRemoveLiquidityInputPanel.tsx │ │ ├── StableSwapRemoveLiquidityTokenSelector.tsx │ │ └── index.tsx │ ├── StakeTri │ │ ├── AboutContainer.tsx │ │ ├── ClaimPtri.tsx │ │ ├── MigrateXtri │ │ │ ├── MigrationTransactionModal.tsx │ │ │ └── index.tsx │ │ ├── PTRIRemittances.tsx │ │ ├── StakeBox.tsx │ │ ├── StakeButton.tsx │ │ ├── StatsBox.tsx │ │ └── index.tsx │ └── Swap │ │ ├── Swap.styles.tsx │ │ ├── index.tsx │ │ └── redirects.tsx ├── react-app-env.d.ts ├── state │ ├── application │ │ ├── actions.ts │ │ ├── hooks.ts │ │ ├── reducer.ts │ │ └── updater.ts │ ├── burn │ │ ├── actions.ts │ │ ├── hooks.ts │ │ └── reducer.ts │ ├── global │ │ └── actions.ts │ ├── index.ts │ ├── lists │ │ ├── actions.ts │ │ ├── hooks.ts │ │ ├── reducer.test.ts │ │ ├── reducer.ts │ │ └── updater.ts │ ├── mint │ │ ├── actions.ts │ │ ├── hooks.ts │ │ ├── reducer.test.ts │ │ └── reducer.ts │ ├── multicall │ │ ├── actions.test.ts │ │ ├── actions.ts │ │ ├── hooks.ts │ │ ├── reducer.test.ts │ │ ├── reducer.ts │ │ ├── updater.test.ts │ │ └── updater.tsx │ ├── stableswap-add-liquidity │ │ ├── actions.ts │ │ ├── hooks.ts │ │ └── reducer.ts │ ├── stableswap │ │ ├── actions.ts │ │ ├── constants.ts │ │ ├── hooks.ts │ │ └── reducer.ts │ ├── stake │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── stake-constants.test.ts.snap │ │ │ └── stake-constants.test.ts │ │ ├── apr.ts │ │ ├── hooks-sushi.ts │ │ ├── hooks.ts │ │ ├── stake-constants.ts │ │ ├── useFarmContractsForStableSwap.ts │ │ ├── useFarmContractsForVersion.ts │ │ ├── useFarmsAPI.ts │ │ ├── useFarmsPortfolio.ts │ │ ├── useGetNonTriRewardsForPoolID.tsx │ │ ├── useUserFarmStatistics.ts │ │ ├── user-farms.ts │ │ └── user-stable-farms.ts │ ├── stakeTri │ │ └── hooks.ts │ ├── swap │ │ ├── actions.ts │ │ ├── hooks.test.ts │ │ ├── hooks.ts │ │ ├── reducer.test.ts │ │ └── reducer.ts │ ├── transactions │ │ ├── actions.ts │ │ ├── hooks.tsx │ │ ├── reducer.ts │ │ ├── updater.test.ts │ │ └── updater.tsx │ ├── user │ │ ├── actions.ts │ │ ├── hooks.tsx │ │ ├── reducer.test.ts │ │ ├── reducer.ts │ │ └── updater.tsx │ ├── wallet │ │ └── hooks.ts │ └── wyre │ │ ├── actions.ts │ │ ├── hooks.tsx │ │ └── reducer.ts ├── theme │ ├── DarkModeQueryParamReader.tsx │ ├── components.tsx │ ├── index.tsx │ └── styled.d.ts └── utils │ ├── chunkArray.test.ts │ ├── chunkArray.ts │ ├── contenthashToUri.test.skip.ts │ ├── contenthashToUri.ts │ ├── currencyId.ts │ ├── extractCountry.ts │ ├── getLibrary.ts │ ├── getPairRenderOrder.ts │ ├── getTokenList.ts │ ├── index.ts │ ├── isZero.ts │ ├── listVersionLabel.ts │ ├── maxAmountSpend.ts │ ├── parseENSAddress.test.ts │ ├── parseENSAddress.ts │ ├── pools.ts │ ├── prices.ts │ ├── resolveENSContentHash.ts │ ├── retry.test.ts │ ├── retry.ts │ ├── stableSwap.ts │ ├── uriToHttp.test.ts │ ├── uriToHttp.ts │ ├── useDebouncedChangeHandler.tsx │ ├── wallet.ts │ └── wrappedCurrency.ts ├── test ├── tsconfig.json ├── wrangler.toml └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_CHAIN_ID="1313161554" 2 | REACT_APP_NETWORK_ID="1" 3 | REACT_APP_NETWORK_URL="https://mainnet.aurora.dev/" 4 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | export CLOUDSDK_ACTIVE_CONFIG_NAME="trisolaris-ad-hoc" -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2020, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | // Allows for the parsing of JSX 8 | "jsx": true 9 | } 10 | }, 11 | "ignorePatterns": ["node_modules/**/*"], 12 | "plugins": ["only-warn"], 13 | "settings": { 14 | "react": { 15 | "version": "detect" 16 | } 17 | }, 18 | "extends": [ 19 | "plugin:react/recommended", 20 | "plugin:@typescript-eslint/recommended", 21 | "plugin:react-hooks/recommended", 22 | "prettier/@typescript-eslint", 23 | "plugin:prettier/recommended" 24 | ], 25 | "rules": { 26 | "@typescript-eslint/explicit-function-return-type": "off", 27 | "prettier/prettier": "error", 28 | "@typescript-eslint/no-explicit-any": "off", 29 | "@typescript-eslint/no-use-before-define": "off" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | 10 | # Ignore everything by default 11 | /* 12 | 13 | # Explicitly allow build folder 14 | !build/** 15 | 16 | # Explicitly allow current folder 17 | !. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | .env.production 19 | 20 | /.netlify 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | notes.txt 27 | .idea/ 28 | 29 | .vscode/ 30 | 31 | package-lock.json 32 | 33 | cypress/videos 34 | cypress/screenshots 35 | cypress/fixtures/example.json 36 | # Local Netlify folder 37 | .netlify 38 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/.gitmodules -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | 5 | 6 | # Ignore all HTML files: 7 | *.html 8 | 9 | # *.test.ts 10 | *.svg 11 | 12 | # Assets 13 | src/assets/* 14 | 15 | 16 | node_modules 17 | # cypress 18 | yarn.lock 19 | 20 | # from .gitignore 21 | # misc 22 | .DS_Store 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | .env 28 | .env.production 29 | 30 | /.netlify 31 | 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | notes.txt 37 | .idea/ 38 | 39 | .vscode/ 40 | 41 | package-lock.json 42 | 43 | cypress/videos 44 | cypress/screenshots 45 | cypress/fixtures/example.json 46 | # Local Netlify folder 47 | .netlify -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | ignore-scripts true 2 | 3 | -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | runtime: nodejs20 2 | handlers: 3 | # Serve all static files with url ending with a file extension 4 | - url: /(.*\..+)$ 5 | static_files: build/\1 6 | upload: build/(.*\..+)$ 7 | # Catch all handler to index.html 8 | - url: /.* 9 | static_files: build/index.html 10 | upload: build/index.html 11 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['@babel/plugin-proposal-nullish-coalescing-operator'], 3 | presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'] 4 | } 5 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "pluginsFile": false, 4 | "fixturesFolder": false, 5 | "supportFile": "cypress/support/index.js", 6 | "video": false, 7 | "defaultCommandTimeout": 10000, 8 | "env": { 9 | "NETWORK_ID": "1313161554", 10 | "PROVIDER_HOST": "https://mainnet.aurora.dev", 11 | "PRIVATE_TEST_WALLET_PK": "0xad20c82497421e9784f18460ad2fe84f73569068e98e270b3e63743268af5763", 12 | "PRIVATE_TEST_WALLET_ADDRESS": "0x0fF2D1eFd7A57B7562b2bf27F3f37899dB27F4a5", 13 | "PRIVATE_TEST_WALLET_ADDRESS_SHORTENED": "0x0fF2...F4a5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cypress/integration/landing.test.ts: -------------------------------------------------------------------------------- 1 | import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands' 2 | 3 | describe('Landing Page', () => { 4 | beforeEach(() => cy.visit('/')) 5 | it('loads swap page', () => { 6 | cy.get('#swap-page') 7 | }) 8 | 9 | it('redirects to url /swap', () => { 10 | cy.url().should('include', '/swap') 11 | }) 12 | 13 | it('allows navigation to pool', () => { 14 | cy.get('#pool-nav-link').click() 15 | cy.url().should('include', '/pool') 16 | }) 17 | 18 | it('is connected', () => { 19 | cy.get('#web3-status-connected').click() 20 | cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /cypress/integration/lists.test.ts: -------------------------------------------------------------------------------- 1 | describe('Lists', () => { 2 | beforeEach(() => { 3 | cy.visit('/swap') 4 | }) 5 | 6 | it('defaults to Aurora list', () => { 7 | cy.get('#swap-currency-output .open-currency-select-button').click() 8 | cy.get('#currency-search-selected-list-name').should('contain', 'Aurora') 9 | }) 10 | 11 | it('change list', () => { 12 | cy.get('#swap-currency-output .open-currency-select-button').click() 13 | cy.get('#currency-search-change-list-button').click() 14 | cy.get('#toggle-expert-mode-button').first().within(() => { 15 | cy.get('span').contains('On').click() 16 | }) 17 | cy.get('#list-add-input').type('https://tokenlist.aave.eth.link/') 18 | cy.get('button').contains('Add').click() 19 | cy.get('div').contains('Manage Lists').next().click() 20 | cy.get('#swap-currency-output .open-currency-select-button').click() 21 | cy.get('.token-item-ETH').siblings().should('have.length', 0) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /cypress/integration/pool.test.ts: -------------------------------------------------------------------------------- 1 | describe('Pool', () => { 2 | beforeEach(() => cy.visit('/pool')) 3 | it('add liquidity links to /add/ETH', () => { 4 | cy.get('#join-pool-button').click() 5 | cy.url().should('contain', '/add/ETH') 6 | }) 7 | 8 | it('import pool links to /import', () => { 9 | cy.get('#import-pool-link').click() 10 | cy.url().should('contain', '/find') 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /cypress/integration/send.test.ts: -------------------------------------------------------------------------------- 1 | describe('Send', () => { 2 | it('should redirect', () => { 3 | cy.visit('/send') 4 | cy.url().should('include', '/swap') 5 | }) 6 | 7 | it('should redirect with url params', () => { 8 | cy.visit('/send?outputCurrency=ETH&recipient=bob.argent.xyz') 9 | cy.url().should('contain', '/swap?outputCurrency=ETH&recipient=bob.argent.xyz') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /cypress/integration/token-warning.ts: -------------------------------------------------------------------------------- 1 | describe('Warning', () => { 2 | beforeEach(() => { 3 | cy.visit('/swap?outputCurrency=0x7821c773a12485b12a2b5b7bc451c3eb200986b1') 4 | }) 5 | 6 | it('Check that warning is displayed', () => { 7 | cy.get('.token-warning-container').should('be.visible') 8 | }) 9 | 10 | it('Check that warning hides after button dismissal', () => { 11 | cy.get('.token-dismiss-button').should('be.disabled') 12 | cy.get('.understand-checkbox').click() 13 | cy.get('.token-dismiss-button').should('not.be.disabled') 14 | cy.get('.token-dismiss-button').click() 15 | cy.get('.token-warning-container').should('not.exist') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /cypress/support/commands.d.ts: -------------------------------------------------------------------------------- 1 | export const TEST_ADDRESS_NEVER_USE: string 2 | 3 | export const TEST_ADDRESS_NEVER_USE_SHORTENED: string 4 | 5 | // declare namespace Cypress { 6 | // // eslint-disable-next-line @typescript-eslint/class-name-casing 7 | // interface cy { 8 | // additionalCommands(): void 9 | // } 10 | // } 11 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This file is processed and loaded automatically before your test files. 3 | // 4 | // You can read more here: 5 | // https://on.cypress.io/configuration 6 | // *********************************************************** 7 | 8 | // Import commands.ts using ES2015 syntax: 9 | import './commands' 10 | 11 | const app = window.top 12 | 13 | if (!app.document.head.querySelector('[data-hide-command-log-request]')) { 14 | const style = app.document.createElement('style') 15 | style.innerHTML = '.command-name-request, .command-name-xhr { display: none }' 16 | style.setAttribute('data-hide-command-log-request', '') 17 | 18 | app.document.head.appendChild(style) 19 | } 20 | 21 | // // Returning false here prevents Cypress from failing the test. 22 | Cypress.on('uncaught:exception', err => { 23 | console.error({ err }) 24 | return false 25 | }) 26 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": "../node_modules", 5 | "target": "es5", 6 | "lib": ["es5", "dom"], 7 | "types": ["cypress"] 8 | }, 9 | "include": ["**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /dev/generate_tokens/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const { ChainId, Token } = require('@trisolaris/sdk') 3 | const _ = require('lodash') 4 | 5 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' 6 | 7 | function createDummyToken({ chainID, ...props }) { 8 | return new Token( 9 | chainID, 10 | props?.address ?? ZERO_ADDRESS, 11 | props?.decimals ?? 18, 12 | props?.symbol ?? 'ZERO', 13 | props?.name ?? 'ZERO' 14 | ) 15 | } 16 | 17 | function createXChainToken(props = {}) { 18 | // get shared properties from the first populated item 19 | const tokenProps = 20 | _.chain(props) 21 | .values() 22 | .head() 23 | .pick(['decimals', 'name', 'symbol']) 24 | .value() ?? {} 25 | 26 | return _.defaultsDeep(props, { 27 | [ChainId.FUJI]: createDummyToken({ chainID: ChainId.FUJI, ...tokenProps }), 28 | [ChainId.AVALANCHE]: createDummyToken({ chainID: ChainId.AVALANCHE, ...tokenProps }), 29 | [ChainId.POLYGON]: createDummyToken({ chainID: ChainId.POLYGON, ...tokenProps }), 30 | [ChainId.AURORA]: createDummyToken({ chainID: ChainId.AURORA, ...tokenProps }) 31 | }) 32 | } 33 | 34 | module.exports = { 35 | createDummyToken, 36 | createXChainToken, 37 | ZERO_ADDRESS 38 | } 39 | -------------------------------------------------------------------------------- /dev/setupTests.ts: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime' 2 | 3 | jest.mock('../src/connectors') 4 | jest.mock('../src/connectors/NetworkConnector.ts') 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | setupFilesAfterEnv: ['./dev/setupTests.ts'], 6 | watchPathIgnorePatterns: ['node_modules'] 7 | } 8 | -------------------------------------------------------------------------------- /public/451.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Unavailable For Legal Reasons 6 | 7 | 8 |

Unavailable For Legal Reasons

9 | 10 | 11 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | www.trisolaris.io -------------------------------------------------------------------------------- /public/favicon-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/public/favicon-new.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/public/favicon.png -------------------------------------------------------------------------------- /public/images/180x180_App_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/public/images/180x180_App_Icon.png -------------------------------------------------------------------------------- /public/images/192x192_App_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/public/images/192x192_App_Icon.png -------------------------------------------------------------------------------- /public/images/384x384_App_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/public/images/384x384_App_Icon.png -------------------------------------------------------------------------------- /public/images/flags/fr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FR 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/images/flags/jp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JP 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/images/flags/round/ar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/au.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/br.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/ca.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/ch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/cl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/co.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/cz.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/dk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/eu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/hk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/il.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/in.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/is.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/jp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/kr.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/mx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/my.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/no.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/nz.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/ph.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/pl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/se.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/sg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/th.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/uk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/us.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/vn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flags/round/za.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Trisolaris", 3 | "name": "Trisolaris", 4 | "icons": [ 5 | { 6 | "src": "./images/192x192_App_Icon.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "any maskable" 10 | }, 11 | { 12 | "src": "./images/512x512_App_Icon.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "any maskable" 16 | } 17 | ], 18 | "orientation": "portrait", 19 | "display": "standalone", 20 | "theme_color": "#cc0058", 21 | "background_color": "#fff" 22 | } 23 | -------------------------------------------------------------------------------- /src/apollo/client.js: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from 'apollo-client' 2 | import { InMemoryCache } from 'apollo-cache-inmemory' 3 | import { HttpLink } from 'apollo-link-http' 4 | 5 | export const blockClient = new ApolloClient({ 6 | link: new HttpLink({ 7 | uri: 'https://api.thegraph.com/subgraphs/name/dasconnor/avalanche-blocks' 8 | }), 9 | cache: new InMemoryCache() 10 | }) 11 | -------------------------------------------------------------------------------- /src/apollo/queries.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | export const GET_BLOCK = gql` 4 | query blocks($timestampFrom: Int!, $timestampTo: Int!) { 5 | blocks( 6 | first: 1 7 | orderBy: timestamp 8 | orderDirection: asc 9 | where: { timestamp_gt: $timestampFrom, timestamp_lt: $timestampTo } 10 | ) { 11 | id 12 | number 13 | timestamp 14 | } 15 | } 16 | ` 17 | -------------------------------------------------------------------------------- /src/assets/images/arrow-down-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/arrow-down-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/arrow-right-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/arrow-right-white.png -------------------------------------------------------------------------------- /src/assets/images/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/avalanche_token_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/avalanche_token_round.png -------------------------------------------------------------------------------- /src/assets/images/big_unicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/big_unicorn.png -------------------------------------------------------------------------------- /src/assets/images/blue-loader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/brave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/brave.png -------------------------------------------------------------------------------- /src/assets/images/circle-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/dropdown-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/dropup-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/ethereum-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/ethereum-logo.png -------------------------------------------------------------------------------- /src/assets/images/farm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/farm.webp -------------------------------------------------------------------------------- /src/assets/images/fortmaticIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/fortmaticIcon.png -------------------------------------------------------------------------------- /src/assets/images/hero.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/hero.webp -------------------------------------------------------------------------------- /src/assets/images/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/images/metamask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/metamask.png -------------------------------------------------------------------------------- /src/assets/images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/noise.png -------------------------------------------------------------------------------- /src/assets/images/plus-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/plus-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/portisIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/portisIcon.png -------------------------------------------------------------------------------- /src/assets/images/question-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/question.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/images/swap.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/swap.webp -------------------------------------------------------------------------------- /src/assets/images/token-list/lists-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/token-list/lists-dark.png -------------------------------------------------------------------------------- /src/assets/images/token-list/lists-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/token-list/lists-light.png -------------------------------------------------------------------------------- /src/assets/images/token-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/token-logo.png -------------------------------------------------------------------------------- /src/assets/images/trustWallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/trustWallet.png -------------------------------------------------------------------------------- /src/assets/images/x.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/xl_uni.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/src/assets/images/xl_uni.png -------------------------------------------------------------------------------- /src/assets/svg/QR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/lightcircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Path 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/AccountDetails/Copy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import useCopyClipboard from '../../hooks/useCopyClipboard' 4 | 5 | import { LinkStyledButton } from '../../theme' 6 | import { CheckCircle, Copy } from 'react-feather' 7 | import { useTranslation } from 'react-i18next' 8 | 9 | const CopyIcon = styled(LinkStyledButton)` 10 | color: ${({ theme }) => theme.text3}; 11 | flex-shrink: 0; 12 | display: flex; 13 | text-decoration: none; 14 | font-size: 0.825rem; 15 | :hover, 16 | :active, 17 | :focus { 18 | text-decoration: none; 19 | color: ${({ theme }) => theme.text2}; 20 | } 21 | ` 22 | const TransactionStatusText = styled.span` 23 | margin-left: 0.25rem; 24 | font-size: 0.825rem; 25 | ${({ theme }) => theme.flexRowNoWrap}; 26 | align-items: center; 27 | ` 28 | 29 | export default function CopyHelper(props: { toCopy: string; children?: React.ReactNode }) { 30 | const [isCopied, setCopied] = useCopyClipboard() 31 | const { t } = useTranslation() 32 | 33 | return ( 34 | setCopied(props.toCopy)}> 35 | {isCopied ? ( 36 | 37 | 38 | {t('accountDetails.copied')} 39 | 40 | ) : ( 41 | 42 | 43 | 44 | )} 45 | {isCopied ? '' : props.children} 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/components/AddToMetaMask/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Token, CETH } from '@trisolaris/sdk' 3 | 4 | import { LinkStyledButton } from '../../theme' 5 | 6 | import { useActiveWeb3React } from '../../hooks' 7 | 8 | import { registerToken } from '../../utils/wallet' 9 | 10 | function AddToMetaMaskButton({ token, ...otherProps }: { token: Token }) { 11 | const { connector } = useActiveWeb3React() 12 | 13 | if (!token || token?.symbol == null || connector.watchAsset == null || token === CETH) { 14 | return null 15 | } 16 | 17 | const { symbol, address, decimals } = token 18 | 19 | function addToken() { 20 | registerToken(address, symbol, decimals) 21 | } 22 | 23 | return ( 24 | 25 | Add {symbol} to MetaMask 26 | 27 | ) 28 | } 29 | 30 | export default AddToMetaMaskButton 31 | -------------------------------------------------------------------------------- /src/components/ApproveButton/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ButtonConfirmed } from '../../components/Button' 4 | import { Dots } from '../../pages/Pool/styleds' 5 | 6 | import { ApprovalState } from '../../hooks/useApproveCallback' 7 | 8 | type ApproveButtonProps = { 9 | approvalState: ApprovalState 10 | handleApproval: () => void 11 | } 12 | function ApproveButton({ approvalState, handleApproval }: ApproveButtonProps) { 13 | return ( 14 | 20 | {approvalState === ApprovalState.PENDING ? ( 21 | Approving 22 | ) : approvalState === ApprovalState.APPROVED ? ( 23 | 'Approved' 24 | ) : ( 25 | 'Approve' 26 | )} 27 | 28 | ) 29 | } 30 | 31 | export default ApproveButton 32 | -------------------------------------------------------------------------------- /src/components/BackButton/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ArrowLeft } from 'react-feather' 3 | import { Link as HistoryLink, useHistory } from 'react-router-dom' 4 | import styled from 'styled-components' 5 | 6 | const StyledArrowLeft = styled(ArrowLeft)` 7 | color: ${({ theme }) => theme.text1}; 8 | ` 9 | 10 | export default function BackButton({ fallbackPath }: { fallbackPath: string }) { 11 | const history = useHistory() 12 | return ( 13 | { 16 | e.preventDefault() 17 | if (history.length > 0) { 18 | history.goBack() 19 | } else { 20 | history.push(fallbackPath) 21 | } 22 | }} 23 | > 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/components/BalanceButton/BalanceButton.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | import { ButtonSecondary } from '../Button' 4 | import { RowFlat } from '../Row' 5 | 6 | const StyledBalanceButton = styled(ButtonSecondary)` 7 | background-color: ${({ theme }) => theme.primary5}; 8 | border: 1px solid ${({ theme }) => theme.primary5}; 9 | border-radius: 0.5rem; 10 | padding: 4px 4px; 11 | font-weight: 500; 12 | cursor: pointer; 13 | color: ${({ theme }) => theme.primaryText1}; 14 | height: 100%; 15 | 16 | :hover { 17 | border: 1px solid ${({ theme }) => theme.primary1}; 18 | } 19 | :focus { 20 | border: 1px solid ${({ theme }) => theme.primary1}; 21 | outline: none; 22 | box-shadow: none; 23 | } 24 | :active { 25 | box-shadow: 0 0 0 1pt #1350ff; 26 | } 27 | :focus-visible { 28 | box-shadow: 0 0 0 1pt #1350ff; 29 | } 30 | ` 31 | 32 | export const StyledBalanceLeftButton = styled(StyledBalanceButton)` 33 | border-top-left-radius: 0.5rem; 34 | border-top-right-radius: unset; 35 | border-bottom-right-radius: unset; 36 | border-bottom-left-radius: 0.5rem; 37 | margin-right: 0.1rem; 38 | ` 39 | 40 | export const StyledBalanceRightButton = styled(StyledBalanceButton)` 41 | border-top-left-radius: unset; 42 | border-top-right-radius: 0.5rem; 43 | border-bottom-right-radius: 0.5rem; 44 | border-bottom-left-radius: unset; 45 | ` 46 | 47 | export const StyledRowFlat = styled(RowFlat)` 48 | margin-left: 4px; 49 | height: 16px; 50 | ` 51 | -------------------------------------------------------------------------------- /src/components/BalanceButton/BalanceButtonValueEnum.ts: -------------------------------------------------------------------------------- 1 | enum BalanceButtonValueEnum { 2 | HALF, 3 | MAX 4 | } 5 | 6 | export default BalanceButtonValueEnum 7 | -------------------------------------------------------------------------------- /src/components/BalanceButton/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useTranslation } from 'react-i18next' 3 | 4 | import BalanceButtonValueEnum from './BalanceButtonValueEnum' 5 | import { TYPE } from '../../theme' 6 | 7 | import { StyledBalanceLeftButton, StyledBalanceRightButton, StyledRowFlat } from './BalanceButton.styles' 8 | 9 | export type Props = { 10 | disableHalfButton?: boolean 11 | disableMaxButton?: boolean 12 | onClickBalanceButton?: (value: BalanceButtonValueEnum) => void 13 | } 14 | 15 | export default function BalanceButton({ disableHalfButton, disableMaxButton, onClickBalanceButton, ...rest }: Props) { 16 | const { t } = useTranslation() 17 | 18 | if (onClickBalanceButton == null) { 19 | return null 20 | } 21 | 22 | return ( 23 | 24 | onClickBalanceButton(BalanceButtonValueEnum.HALF)} 27 | > 28 | 50% 29 | 30 | onClickBalanceButton(BalanceButtonValueEnum.MAX)} 33 | > 34 | {t('currencyInputPanel.max')} 35 | 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/components/BridgesMenu/BridgesMenu.constants.tsx: -------------------------------------------------------------------------------- 1 | export const BRIDGES = [ 2 | { id: 'ethereum', link: 'https://rainbowbridge.app/transfer', label: 'Bridge from Ethereum' }, 3 | 4 | { id: 'near', link: 'https://rainbowbridge.app/transfer', label: 'Bridge from Near' }, 5 | { 6 | id: 'bsc', 7 | link: 'https://synapseprotocol.com/?inputCurrency=USDC&outputCurrency=USDC&outputChain=1313161554', 8 | label: 'Bridge from BSC' 9 | }, 10 | 11 | { 12 | id: 'avalanche', 13 | link: 'https://synapseprotocol.com/?inputCurrency=USDC&outputCurrency=USDC&outputChain=1313161554', 14 | label: 'Bridge from Avalanche' 15 | }, 16 | 17 | { 18 | id: 'polygon', 19 | link: 'https://synapseprotocol.com/?inputCurrency=USDC&outputCurrency=USDC&outputChain=1313161554', 20 | label: 'Bridge from Polygon' 21 | }, 22 | { 23 | id: 'synapse', 24 | link: 'https://synapseprotocol.com/?inputCurrency=USDC&outputCurrency=USDC&outputChain=1313161554', 25 | label: 'Synapse Bridge' 26 | }, 27 | { 28 | id: 'frax', 29 | link: 'https://app.frax.finance/crosschain#aurora', 30 | label: 'Bridge Native FRAX' 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /src/components/BridgesMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | 3 | import { StyledMenu } from '../StyledMenu' 4 | 5 | import { useModalOpen, useToggleModal } from '../../state/application/hooks' 6 | import { ApplicationModal } from '../../state/application/actions' 7 | import { useOnClickOutside } from '../../hooks/useOnClickOutside' 8 | 9 | import { StyledMenuFlyout, StyledExternalLink, MenuButton, StyledArrow } from './BridgesMenu.styles' 10 | 11 | import { BRIDGES } from './BridgesMenu.constants' 12 | 13 | export default function BridgesMenu({ ...rest }) { 14 | const node = useRef() 15 | const open = useModalOpen(ApplicationModal.BRIDGES_MENU) 16 | const toggle = useToggleModal(ApplicationModal.BRIDGES_MENU) 17 | useOnClickOutside(node, open ? toggle : undefined) 18 | 19 | return ( 20 | 21 | 22 | Bridges 23 | 24 | 25 | {open && ( 26 | 27 | {BRIDGES.map(bridge => ( 28 | 29 | {bridge.label} 30 | 31 | ))} 32 | 33 | )} 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/components/CaptionWithIcon/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { Info } from 'react-feather' 3 | import styled, { ThemeContext } from 'styled-components' 4 | import { TYPE } from '../../theme' 5 | 6 | const CaptionContainer = styled.span` 7 | display: inline-flex; 8 | align-items: center; 9 | margin-top: 0.25rem; 10 | ` 11 | 12 | type Props = { 13 | children: React.ReactText 14 | } 15 | 16 | export default function CaptionWithIcon({ children }: Props) { 17 | const theme = useContext(ThemeContext) 18 | 19 | return ( 20 | 21 | 22 | 23 | {children} 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/components/CoingeckoPriceChart/index.tsx: -------------------------------------------------------------------------------- 1 | import { Currency } from '@trisolaris/sdk' 2 | import React, { useEffect, useRef } from 'react' 3 | import useCoinSearch, { Coin } from '../../fetchers/coingecko-api-id' 4 | import styled from 'styled-components' 5 | 6 | type Props = { 7 | coin: Coin | undefined 8 | } 9 | 10 | // NOTE - Can't style rendered coingecko script tag, cause it's external zzz 11 | const CoingeckoContainer = styled.div` 12 | padding-top: 20px; 13 | ` 14 | 15 | const CoinGeckoWidget: React.FC = ({ coin }) => { 16 | const containerRef = useRef(null) 17 | 18 | useEffect(() => { 19 | const script = document.createElement('script') 20 | script.src = 'https://widgets.coingecko.com/coingecko-coin-price-chart-widget.js' 21 | script.async = true 22 | document.body.appendChild(script) 23 | 24 | return () => { 25 | document.body.removeChild(script) 26 | } 27 | }, []) 28 | 29 | useEffect(() => { 30 | if (coin) { 31 | if (containerRef.current) { 32 | containerRef.current.innerHTML = '' // Clear the container 33 | 34 | const widget = document.createElement('coingecko-coin-price-chart-widget') 35 | widget.setAttribute('coin-id', coin.api_symbol) 36 | widget.setAttribute('currency', 'usd') 37 | widget.setAttribute('height', '500') 38 | widget.setAttribute('locale', 'en') 39 | containerRef.current.appendChild(widget) 40 | } 41 | } 42 | }, [coin]) 43 | 44 | return 45 | } 46 | 47 | export default CoinGeckoWidget 48 | -------------------------------------------------------------------------------- /src/components/Column/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Column = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: flex-start; 7 | ` 8 | export const ColumnCenter = styled(Column)` 9 | width: 100%; 10 | align-items: center; 11 | ` 12 | 13 | export const AutoColumn = styled.div<{ 14 | gap?: 'sm' | 'md' | 'lg' | string 15 | justify?: 'stretch' | 'center' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'space-between' 16 | }>` 17 | display: grid; 18 | grid-auto-rows: auto; 19 | grid-row-gap: ${({ gap }) => (gap === 'sm' && '8px') || (gap === 'md' && '12px') || (gap === 'lg' && '24px') || gap}; 20 | justify-items: ${({ justify }) => justify && justify}; 21 | ` 22 | 23 | export default Column 24 | -------------------------------------------------------------------------------- /src/components/Confetti/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactConfetti from 'react-confetti' 3 | import { useWindowSize } from '../../hooks/useWindowSize' 4 | 5 | // eslint-disable-next-line react/prop-types 6 | export default function Confetti({ start, variant }: { start: boolean; variant?: string }) { 7 | const { width, height } = useWindowSize() 8 | 9 | const _variant = variant ? variant : height && width && height > 1.5 * width ? 'bottom' : variant 10 | 11 | return start && width && height ? ( 12 | 31 | ) : null 32 | } 33 | -------------------------------------------------------------------------------- /src/components/ContractAddress/ContractAddress.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { darken } from 'polished' 3 | 4 | import Tooltip from '../Tooltip' 5 | import { Text } from 'rebass' 6 | 7 | export const StyledAddressContainer = styled.div` 8 | display: flex; 9 | align-items: center; 10 | ` 11 | 12 | export const StyledContractButton = styled.button` 13 | z-index: 1; 14 | border: none; 15 | height: 28px; 16 | width: 28px; 17 | background-color: ${({ theme }) => theme.bg6}; 18 | border-radius: 100%; 19 | color: ${({ theme }) => theme.text1}; 20 | 21 | :hover, 22 | :focus { 23 | cursor: pointer; 24 | outline: none; 25 | background-color: ${({ theme }) => darken(0.1, theme.bg6)}; 26 | } 27 | 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | svg { 32 | margin-top: 2px; 33 | } 34 | ` 35 | 36 | export const StyledTooltip = styled(Tooltip)` 37 | width: fit-content; 38 | min-width: 190px; 39 | font-size: 0.75rem; 40 | text-align: center; 41 | ` 42 | 43 | export const StyledText = styled(Text)` 44 | font-size: 16px; 45 | font-weight: 500; 46 | margin-right: 10px; 47 | ` 48 | -------------------------------------------------------------------------------- /src/components/ContractAddress/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Token } from '@trisolaris/sdk' 3 | import { Text } from 'rebass' 4 | import { Copy, Check } from 'lucide-react' 5 | 6 | import useCopyClipboard from '../../hooks/useCopyClipboard' 7 | import { shortenAddress } from '../../utils' 8 | 9 | import { StyledAddressContainer, StyledContractButton, StyledTooltip, StyledText } from './ContractAddress.styles' 10 | 11 | const ContractAddress = ({ token, address, ...rest }: { token?: Token; address?: string }) => { 12 | const addressToShow = address ?? token?.address ?? '' 13 | 14 | const [showTooltip, setShowTooltip] = useState(false) 15 | const [isCopied, setCopied] = useCopyClipboard(750) 16 | 17 | const handleMouseEnter = () => { 18 | setShowTooltip(true) 19 | } 20 | 21 | const handleMouseLeave = () => { 22 | setShowTooltip(false) 23 | } 24 | 25 | const handleClick = () => { 26 | if (!showTooltip) { 27 | setShowTooltip(true) 28 | } 29 | setCopied(addressToShow) 30 | setTimeout(() => { 31 | setShowTooltip(false) 32 | }, 600) 33 | } 34 | 35 | return ( 36 | 37 | {shortenAddress(addressToShow)} 38 | 44 | 45 | {isCopied ? : } 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default ContractAddress 53 | -------------------------------------------------------------------------------- /src/components/CountUp/index.tsx: -------------------------------------------------------------------------------- 1 | import usePrevious from '../../hooks/usePrevious' 2 | import React from 'react' 3 | import { CountUp as CountUpImpl } from 'use-count-up' 4 | 5 | const DEFAULT_DECIMAL_PLACES = 4 6 | 7 | export default function CountUp({ 8 | enabled, 9 | value, 10 | decimalPlaces = DEFAULT_DECIMAL_PLACES 11 | }: { 12 | enabled: boolean 13 | value: number 14 | decimalPlaces?: number 15 | }) { 16 | const previousValue = usePrevious(value) 17 | 18 | return ( 19 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/DoubleLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import { Currency } from '@trisolaris/sdk' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import CurrencyLogo from '../CurrencyLogo' 5 | 6 | const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>` 7 | position: relative; 8 | display: flex; 9 | flex-direction: row; 10 | margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'}; 11 | ` 12 | 13 | interface DoubleCurrencyLogoProps { 14 | margin?: boolean 15 | size?: number 16 | currency0?: Currency 17 | currency1?: Currency 18 | } 19 | 20 | const HigherLogo = styled(CurrencyLogo)` 21 | z-index: 2; 22 | ` 23 | const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>` 24 | position: absolute; 25 | left: ${({ sizeraw }) => '-' + (sizeraw / 2).toString() + 'px'} !important; 26 | ` 27 | 28 | export default function DoubleCurrencyLogo({ 29 | currency0, 30 | currency1, 31 | size = 16, 32 | margin = false 33 | }: DoubleCurrencyLogoProps) { 34 | return ( 35 | 36 | {currency0 && } 37 | {currency1 && } 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/components/EarnTriSortAndFilter/SortingArrow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ChevronDown, ChevronUp } from 'react-feather' 3 | 4 | export default function SortingArrow({ isDescending }: { isDescending: boolean }) { 5 | return isDescending ? : 6 | } 7 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary/index.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorBoundary as ErrorBoundaryImpl } from 'react-error-boundary' 2 | import React from 'react' 3 | 4 | type Props = Exclude, 'onError'> 5 | 6 | export default function ErrorBoundary(props: Props) { 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | function handleError(error: Error, _info: { componentStack: string }) { 9 | console.error(error) 10 | } 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /src/components/FormattedCurrencyAmount/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CurrencyAmount, Fraction, JSBI } from '@trisolaris/sdk' 3 | 4 | import { BIG_INT_ZERO } from '../../constants' 5 | 6 | const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000)) 7 | 8 | export default function FormattedCurrencyAmount({ 9 | currencyAmount, 10 | significantDigits = 4 11 | }: { 12 | currencyAmount: CurrencyAmount 13 | significantDigits?: number 14 | }) { 15 | return ( 16 | <> 17 | {currencyAmount.equalTo(BIG_INT_ZERO) 18 | ? '0' 19 | : currencyAmount.greaterThan(CURRENCY_AMOUNT_MIN) 20 | ? currencyAmount.toSignificant(significantDigits) 21 | : `<${CURRENCY_AMOUNT_MIN.toSignificant(1)}`} 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/GasFeeAlert.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import Column, { AutoColumn } from './Column' 3 | import styled, { ThemeContext } from 'styled-components' 4 | import { transparentize } from 'polished' 5 | import { AlertTriangle } from 'react-feather' 6 | import { TYPE } from '../theme' 7 | import { RowFixed } from './Row' 8 | import { GAS_PRICE } from '../constants' 9 | import { useTranslation } from 'react-i18next' 10 | 11 | export const GasFee = styled(AutoColumn)` 12 | background-color: ${({ theme }) => transparentize(0.9, theme.primary1)}; 13 | color: ${({ theme }) => theme.primary1}; 14 | padding: 0.5rem; 15 | border-radius: 12px; 16 | margin-top: 8px; 17 | ` 18 | 19 | export default function GasFeeAlert() { 20 | const theme = useContext(ThemeContext) 21 | const { t } = useTranslation() 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | {t('gasFeeAlert.gasFeeReduction')} !
29 |
30 | 31 | {t('gasFeeAlert.transactionsNotAccept', { gasPrice: GAS_PRICE })} 32 | 33 |
34 |
35 |
36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/components/GovernanceMenu/GovernanceMenu.constants.tsx: -------------------------------------------------------------------------------- 1 | export const GOVERNANCE = [{ id: 'vote', link: 'https://snapshot.org/#/trisolarislabs.eth', label: 'Vote on Snapshot' }] 2 | -------------------------------------------------------------------------------- /src/components/GovernanceMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | 3 | import { StyledMenu } from '../StyledMenu' 4 | 5 | import { useModalOpen, useToggleModal } from '../../state/application/hooks' 6 | import { ApplicationModal } from '../../state/application/actions' 7 | import { useOnClickOutside } from '../../hooks/useOnClickOutside' 8 | 9 | import { StyledMenuFlyout, StyledExternalLink, MenuButton, StyledArrow } from '../BridgesMenu/BridgesMenu.styles' 10 | 11 | import { GOVERNANCE } from './GovernanceMenu.constants' 12 | 13 | export default function GovernanceMenu({ ...rest }) { 14 | const node = useRef() 15 | const open = useModalOpen(ApplicationModal.GOVERNANCE_MENU) 16 | const toggle = useToggleModal(ApplicationModal.GOVERNANCE_MENU) 17 | useOnClickOutside(node, open ? toggle : undefined) 18 | 19 | return ( 20 | 21 | 22 | Governance 23 | 24 | 25 | {open && ( 26 | 27 | {GOVERNANCE.map(governance => ( 28 | 29 | {governance.label} 30 | 31 | ))} 32 | 33 | )} 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Identicon/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react' 2 | 3 | import styled from 'styled-components' 4 | 5 | import { useActiveWeb3React } from '../../hooks' 6 | import Jazzicon from 'jazzicon' 7 | 8 | const StyledIdenticonContainer = styled.div` 9 | height: 1rem; 10 | width: 1rem; 11 | border-radius: 1.125rem; 12 | background-color: ${({ theme }) => theme.bg4}; 13 | ` 14 | 15 | export default function Identicon() { 16 | const ref = useRef() 17 | 18 | const { account } = useActiveWeb3React() 19 | 20 | useEffect(() => { 21 | if (account && ref.current) { 22 | ref.current.innerHTML = '' 23 | ref.current.appendChild(Jazzicon(16, parseInt(account.slice(2, 10), 16))) 24 | } 25 | }, [account]) 26 | 27 | // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /src/components/ListLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import useHttpLocations from '../../hooks/useHttpLocations' 4 | 5 | import Logo from '../Logo' 6 | 7 | const StyledListLogo = styled(Logo)<{ size: string }>` 8 | width: ${({ size }) => size}; 9 | height: ${({ size }) => size}; 10 | ` 11 | 12 | export default function ListLogo({ 13 | logoURI, 14 | style, 15 | size = '24px', 16 | alt 17 | }: { 18 | logoURI: string 19 | size?: string 20 | style?: React.CSSProperties 21 | alt?: string 22 | }) { 23 | const srcs: string[] = useHttpLocations(logoURI) 24 | 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Loader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import styled, { keyframes } from 'styled-components' 4 | 5 | const rotate = keyframes` 6 | from { 7 | transform: rotate(0deg); 8 | } 9 | to { 10 | transform: rotate(360deg); 11 | } 12 | ` 13 | 14 | const StyledSVG = styled.svg<{ size: string; stroke?: string }>` 15 | animation: 2s ${rotate} linear infinite; 16 | height: ${({ size }) => size}; 17 | width: ${({ size }) => size}; 18 | path { 19 | stroke: ${({ stroke, theme }) => stroke ?? theme.primary1}; 20 | } 21 | ` 22 | 23 | /** 24 | * Takes in custom size and stroke for circle color, default to primary color as fill, 25 | * need ...rest for layered styles on top 26 | */ 27 | export default function Loader({ 28 | size = '16px', 29 | stroke, 30 | ...rest 31 | }: { 32 | size?: string 33 | stroke?: string 34 | [k: string]: any 35 | }) { 36 | return ( 37 | 38 | 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { HelpCircle } from 'react-feather' 3 | import { ImageProps } from 'rebass' 4 | 5 | const BAD_SRCS: { [tokenAddress: string]: true } = {} 6 | 7 | export interface LogoProps extends Pick { 8 | srcs: string[] 9 | } 10 | 11 | /** 12 | * Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert 13 | */ 14 | export default function Logo({ srcs, alt, ...rest }: LogoProps) { 15 | const [, refresh] = useState(0) 16 | 17 | const src: string | undefined = srcs.find(src => !BAD_SRCS[src]) 18 | 19 | if (src) { 20 | return ( 21 | {alt} { 26 | if (src) BAD_SRCS[src] = true 27 | refresh(i => i + 1) 28 | }} 29 | /> 30 | ) 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Page/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { AutoColumn } from '../Column' 3 | 4 | export const PageWrapper = styled(AutoColumn)` 5 | max-width: ${({ theme }) => theme.pageWidth}; 6 | width: 100%; 7 | ` 8 | -------------------------------------------------------------------------------- /src/components/Popups/TransactionPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { AlertCircle, CheckCircle } from 'react-feather' 3 | import styled, { ThemeContext } from 'styled-components' 4 | import { useActiveWeb3React } from '../../hooks' 5 | import { TYPE } from '../../theme' 6 | import { ExternalLink } from '../../theme/components' 7 | import { getEtherscanLink } from '../../utils' 8 | import { AutoColumn } from '../Column' 9 | import { AutoRow } from '../Row' 10 | import { useTranslation } from 'react-i18next' 11 | 12 | const RowNoFlex = styled(AutoRow)` 13 | flex-wrap: nowrap; 14 | ` 15 | 16 | export default function TransactionPopup({ 17 | hash, 18 | success, 19 | summary 20 | }: { 21 | hash: string 22 | success?: boolean 23 | summary?: string 24 | }) { 25 | const { chainId } = useActiveWeb3React() 26 | 27 | const { t } = useTranslation() 28 | const theme = useContext(ThemeContext) 29 | 30 | return ( 31 | 32 |
33 | {success ? : } 34 |
35 | 36 | 37 | {summary ?? t('popups.hash') + hash.slice(0, 8) + '...' + hash.slice(58, 65)} 38 | 39 | {chainId && ( 40 | {t('popups.viewExplorer')} 41 | )} 42 | 43 |
44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/components/PositionCard/PositionCard.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { darken } from 'polished' 3 | 4 | import { RowBetween } from '../Row' 5 | import Card, { LightCard } from '../Card' 6 | import { ButtonEmpty } from '../Button' 7 | 8 | export const ManageButton = styled(ButtonEmpty)` 9 | color: ${({ theme }) => theme.white}; 10 | ` 11 | 12 | export const FixedHeightRow = styled(RowBetween)` 13 | height: 24px; 14 | ` 15 | 16 | export const HoverCard = styled(Card)` 17 | border: 1px solid transparent; 18 | :hover { 19 | border: 1px solid ${({ theme }) => darken(0.06, theme.bg2)}; 20 | } 21 | ` 22 | export const StyledPositionCard = styled(LightCard)<{ bgColor: any }>` 23 | background-color: unset; 24 | border: none; 25 | position: relative; 26 | overflow: hidden; 27 | ` 28 | -------------------------------------------------------------------------------- /src/components/PositionCard/PositionCard.types.ts: -------------------------------------------------------------------------------- 1 | import { Pair } from '@trisolaris/sdk' 2 | 3 | export interface PositionCardProps { 4 | pair: Pair 5 | showUnwrapped?: boolean 6 | border?: string 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Row/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from 'rebass/styled-components' 3 | 4 | const Row = styled(Box)<{ align?: string; padding?: string; border?: string; borderRadius?: string }>` 5 | width: 100%; 6 | display: flex; 7 | padding: 0; 8 | align-items: ${({ align }) => (align ? align : 'center')}; 9 | padding: ${({ padding }) => padding}; 10 | border: ${({ border }) => border}; 11 | border-radius: ${({ borderRadius }) => borderRadius}; 12 | ` 13 | 14 | export const RowBetween = styled(Row)` 15 | justify-content: space-between; 16 | ` 17 | 18 | export const RowFlat = styled.div` 19 | display: flex; 20 | align-items: flex-end; 21 | ` 22 | 23 | export const AutoRow = styled(Row)<{ gap?: string; justify?: string }>` 24 | flex-wrap: wrap; 25 | margin: ${({ gap }) => gap && `-${gap}`}; 26 | justify-content: ${({ justify }) => justify && justify}; 27 | 28 | & > * { 29 | margin: ${({ gap }) => gap} !important; 30 | } 31 | ` 32 | 33 | export const RowFixed = styled(Row)<{ gap?: string; justify?: string }>` 34 | width: fit-content; 35 | margin: ${({ gap }) => gap && `-${gap}`}; 36 | ` 37 | 38 | export default Row 39 | -------------------------------------------------------------------------------- /src/components/SearchModal/SortButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text } from 'rebass' 3 | import styled from 'styled-components' 4 | import { RowFixed } from '../Row' 5 | 6 | export const FilterWrapper = styled(RowFixed)` 7 | padding: 8px; 8 | background-color: ${({ theme }) => theme.bg2}; 9 | color: ${({ theme }) => theme.text1}; 10 | border-radius: 8px; 11 | user-select: none; 12 | & > * { 13 | user-select: none; 14 | } 15 | :hover { 16 | cursor: pointer; 17 | } 18 | ` 19 | 20 | export default function SortButton({ 21 | toggleSortOrder, 22 | ascending 23 | }: { 24 | toggleSortOrder: () => void 25 | ascending: boolean 26 | }) { 27 | return ( 28 | 29 | 30 | {ascending ? '↑' : '↓'} 31 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/components/SearchModal/filtering.ts: -------------------------------------------------------------------------------- 1 | import { isAddress } from '../../utils' 2 | import { Token } from '@trisolaris/sdk' 3 | 4 | export function filterTokens(tokens: Token[], search: string): Token[] { 5 | if (search.length === 0) return tokens 6 | 7 | const searchingAddress = isAddress(search) 8 | 9 | if (searchingAddress) { 10 | return tokens.filter(token => token.address === searchingAddress) 11 | } 12 | 13 | const lowerSearchParts = search 14 | .toLowerCase() 15 | .split(/\s+/) 16 | .filter(s => s.length > 0) 17 | 18 | if (lowerSearchParts.length === 0) { 19 | return tokens 20 | } 21 | 22 | const matchesSearch = (s: string): boolean => { 23 | const sParts = s 24 | .toLowerCase() 25 | .split(/\s+/) 26 | .filter(s => s.length > 0) 27 | 28 | return lowerSearchParts.every(p => p.length === 0 || sParts.some(sp => sp.startsWith(p) || sp.endsWith(p))) 29 | } 30 | 31 | return tokens.filter(token => { 32 | const { symbol, name } = token 33 | 34 | return (symbol && matchesSearch(symbol)) || (name && matchesSearch(name)) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Settings/Settings.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Settings, X } from 'react-feather' 3 | 4 | import { MenuFlyout } from '../StyledMenu' 5 | 6 | export const StyledMenuIcon = styled(Settings)` 7 | height: 20px; 8 | width: 20px; 9 | 10 | > * { 11 | stroke: ${({ theme }) => theme.text1}; 12 | } 13 | ` 14 | 15 | export const StyledCloseIcon = styled(X)` 16 | height: 20px; 17 | width: 20px; 18 | 19 | :hover { 20 | cursor: pointer; 21 | } 22 | 23 | > * { 24 | stroke: ${({ theme }) => theme.text1}; 25 | } 26 | ` 27 | 28 | export const EmojiWrapper = styled.div` 29 | position: absolute; 30 | bottom: -6px; 31 | right: 0px; 32 | font-size: 14px; 33 | ` 34 | 35 | export const Break = styled.div` 36 | width: 100%; 37 | height: 1px; 38 | background-color: ${({ theme }) => theme.bg3}; 39 | ` 40 | 41 | export const ModalContentWrapper = styled.div` 42 | display: flex; 43 | align-items: center; 44 | justify-content: center; 45 | padding: 2rem 0; 46 | background-color: ${({ theme }) => theme.bg2}; 47 | border-radius: 20px; 48 | ` 49 | 50 | export const SettingsMenuFlyout = styled(MenuFlyout)` 51 | top: 3rem; 52 | border: ${({ theme }) => `1px solid ${theme.bg3}`}; 53 | ${({ theme }) => theme.mediaWidth.upToMedium` 54 | min-width: 18.125rem; 55 | right: 0.15rem; 56 | `}; 57 | ` 58 | -------------------------------------------------------------------------------- /src/components/SponsoredFarmLink/SponsoredFarmLink.constants.ts: -------------------------------------------------------------------------------- 1 | import { STNEAR, AUSDO, NEARX } from '../../constants/tokens' 2 | 3 | export const SPONSORED_TOKENS = [ 4 | { token: STNEAR, link: 'https://metapool.app/dapp/mainnet/metapool-aurora/' }, 5 | { 6 | token: AUSDO, 7 | link: 'https://v3.oin.finance/' 8 | }, 9 | { 10 | token: NEARX, 11 | link: 'https://near.staderlabs.com/lt/near?tab=Stake' 12 | } 13 | ] 14 | 15 | type FARMS_CUSTOM_HEADING_TYPE = { 16 | [id: number]: { 17 | customText: string 18 | customLink: string 19 | } 20 | } 21 | 22 | export const FARMS_CUSTOM_HEADING: FARMS_CUSTOM_HEADING_TYPE = { 23 | 43: { 24 | customText: 'Deposit in Aurigami', 25 | customLink: 'https://app.aurigami.finance/' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/SponsoredFarmLink/SponsoredFarmLink.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { darken } from 'polished' 3 | 4 | import { ExternalLink } from '../../theme' 5 | 6 | export const StyledExternalLink = styled(ExternalLink)` 7 | z-index: 1; 8 | text-decoration: underline; 9 | color: ${({ theme }) => theme.text2}; 10 | font-weight: 400; 11 | font-size: 0.7rem; 12 | margin: 0; 13 | position: absolute; 14 | top: 4.5px; 15 | left: 70px; 16 | 17 | :active { 18 | text-decoration: underline; 19 | } 20 | 21 | :hover { 22 | text-decoration: none; 23 | color: ${({ theme }) => darken(0.1, theme.text1)}; 24 | } 25 | 26 | ${({ theme }) => theme.mediaWidth.upToSmall` 27 | left:55px; 28 | `}; 29 | ${({ theme }) => theme.mediaWidth.upToExtraSmall` 30 | left:45px; 31 | `}; 32 | ${({ theme }) => theme.mediaWidth.upToXxSmall` 33 | left: 12px; 34 | `}; 35 | ` 36 | -------------------------------------------------------------------------------- /src/components/SponsoredFarmLink/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Token, ChainId } from '@trisolaris/sdk' 3 | 4 | import { SPONSORED_TOKENS, FARMS_CUSTOM_HEADING } from './SponsoredFarmLink.constants' 5 | 6 | import { StyledExternalLink } from './SponsoredFarmLink.styles' 7 | 8 | const SponsoredFarmLink = ({ tokens, farmID }: { tokens: Token[]; farmID: number }) => { 9 | const foundToken = SPONSORED_TOKENS.find(sponsoredToken => 10 | tokens.some(cardToken => sponsoredToken.token[ChainId.AURORA] === cardToken) 11 | ) 12 | const customSponsoredFarm = FARMS_CUSTOM_HEADING[farmID] 13 | 14 | return customSponsoredFarm ? ( 15 | {customSponsoredFarm.customText} ↗ 16 | ) : foundToken ? ( 17 | Get {foundToken.token[ChainId.AURORA].symbol} ↗ 18 | ) : null 19 | } 20 | 21 | export default SponsoredFarmLink 22 | -------------------------------------------------------------------------------- /src/components/StakeTri/StakeTriDataCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { AutoColumn } from '../Column' 4 | import { AutoRow } from '../Row' 5 | import { TYPE } from '../../theme' 6 | import { CardSection } from '../earn/styled' 7 | import { DarkGreyCard } from '../Card' 8 | 9 | type Props = { 10 | children: React.ReactNode 11 | label: string 12 | } 13 | 14 | export const StyledDataCard = styled(DarkGreyCard)` 15 | padding: 0; 16 | ${({ theme }) => theme.mediaWidth.upToSmall` 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | min-height: 110px; 21 | `}; 22 | ` 23 | 24 | export default function StakeTriDataCard({ children, label }: Props) { 25 | return ( 26 | 27 | 28 | 29 | 30 | {label} 31 | 32 | {children} 33 | 34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/components/StyledMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { ExternalLink } from '../../theme' 3 | 4 | export const StyledMenuButton = styled.button` 5 | position: relative; 6 | width: 100%; 7 | height: 100%; 8 | border: none; 9 | background-color: transparent; 10 | margin: 0; 11 | padding: 0; 12 | height: 35px; 13 | background-color: ${({ theme }) => theme.bg3}; 14 | 15 | padding: 0.15rem 0.5rem; 16 | border-radius: 0.5rem; 17 | 18 | :hover, 19 | :focus { 20 | cursor: pointer; 21 | outline: none; 22 | background-color: ${({ theme }) => theme.bg4}; 23 | } 24 | 25 | svg { 26 | margin-top: 2px; 27 | } 28 | ` 29 | 30 | export const StyledMenu = styled.div` 31 | display: flex; 32 | justify-content: center; 33 | align-items: center; 34 | position: relative; 35 | border: none; 36 | text-align: left; 37 | ` 38 | 39 | export const MenuFlyout = styled.span` 40 | min-width: 20.125rem; 41 | background-color: ${({ theme }) => theme.bg2}; 42 | box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 43 | 0px 24px 32px rgba(0, 0, 0, 0.01); 44 | border-radius: 12px; 45 | display: flex; 46 | flex-direction: column; 47 | font-size: 1rem; 48 | position: absolute; 49 | top: 4rem; 50 | right: 0rem; 51 | z-index: 100; 52 | ` 53 | 54 | export const MenuItem = styled(ExternalLink)` 55 | flex: 1; 56 | padding: 0.5rem 0.5rem; 57 | color: ${({ theme }) => theme.text2}; 58 | :hover { 59 | color: ${({ theme }) => theme.text1}; 60 | cursor: pointer; 61 | text-decoration: none; 62 | } 63 | > svg { 64 | margin-right: 8px; 65 | } 66 | ` 67 | -------------------------------------------------------------------------------- /src/components/Tooltip/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react' 2 | import styled from 'styled-components' 3 | import Popover, { PopoverProps } from '../Popover' 4 | 5 | const TooltipContainer = styled.div` 6 | width: 228px; 7 | padding: 0.6rem 1rem; 8 | line-height: 150%; 9 | font-weight: 400; 10 | ` 11 | 12 | interface TooltipProps extends Omit { 13 | text: string 14 | } 15 | 16 | export default function Tooltip({ text, ...rest }: TooltipProps) { 17 | return {text}} {...rest} /> 18 | } 19 | 20 | export function MouseoverTooltip({ children, text, ...rest }: Omit) { 21 | const [show, setShow] = useState(false) 22 | const open = useCallback(() => setShow(true), [setShow]) 23 | const close = useCallback(() => setShow(false), [setShow]) 24 | return ( 25 | 1}> 26 |
27 | {children} 28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/components/TripleCurrencyLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import { Currency } from '@trisolaris/sdk' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import CurrencyLogo from '../CurrencyLogo' 5 | 6 | const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>` 7 | position: relative; 8 | display: flex; 9 | flex-direction: row; 10 | margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'}; 11 | ` 12 | 13 | interface TripleCurrencyLogoProps { 14 | margin?: boolean 15 | size?: number 16 | currency0?: Currency 17 | currency1?: Currency 18 | currency2?: Currency 19 | } 20 | 21 | const HigherLogo = styled(CurrencyLogo)` 22 | z-index: 4; 23 | ` 24 | const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>` 25 | position: absolute; 26 | left: ${({ sizeraw }) => '-' + (sizeraw / 2).toString() + 'px'} !important; 27 | z-index: 2; 28 | ` 29 | 30 | const SecondCoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>` 31 | position: absolute; 32 | left: ${({ sizeraw }) => '-' + sizeraw.toString() + 'px'} !important; 33 | ` 34 | 35 | export default function TripleCurrencyLogo({ 36 | currency0, 37 | currency1, 38 | currency2, 39 | size = 16, 40 | margin = false 41 | }: TripleCurrencyLogoProps) { 42 | return ( 43 | 44 | {currency0 && } 45 | {currency1 && } 46 | {currency2 && } 47 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/components/Warning/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { Text } from 'rebass' 4 | import { AlertTriangle } from 'react-feather' 5 | import { RowFixed } from '../Row' 6 | import { AutoColumn } from '../Column' 7 | 8 | const WarningWrapper = styled.div` 9 | border-radius: 20px; 10 | border: 1px solid #f82d3a; 11 | background: rgba(248, 45, 58, 0.05); 12 | padding: 1rem; 13 | color: #f82d3a; 14 | position: relative; 15 | @media screen and (max-width: 800px) { 16 | width: 80% !important; 17 | margin-left: 5%; 18 | } 19 | ` 20 | 21 | const StyledWarningIcon = styled(AlertTriangle)` 22 | min-height: 20px; 23 | min-width: 20px; 24 | stroke: red; 25 | ` 26 | 27 | const ConvertLink = styled.a` 28 | color: #ed147a; 29 | text-decoration: none; 30 | ` 31 | 32 | export function DeprecatedWarning() { 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | Old AEB tokens Alert 40 | 41 | 42 | 43 | Please note these tokens were used by the old AEB bridge and have been deprecated. If you still hold old AEB 44 | tokens, please convert them here{' '} 45 | 46 | https://bridge.avax.network/convert 47 | 48 | 49 | 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Web3Provider/index.tsx: -------------------------------------------------------------------------------- 1 | import { Web3ReactProvider } from '@web3-react/core' 2 | import { Connector } from '@web3-react/types' 3 | import { Wallet, coinbaseWallet, injected, network, useConnectors, walletConnect } from '../../connectors' 4 | import { ReactNode, useEffect } from 'react' 5 | 6 | const connect = async (connector: Connector) => { 7 | try { 8 | if (connector.connectEagerly) { 9 | await connector.connectEagerly() 10 | } else { 11 | await connector.activate() 12 | } 13 | } catch (error) { 14 | console.debug(`web3-react eager connection error: ${error}`) 15 | } 16 | } 17 | 18 | export default function Web3Provider({ children }: { children: ReactNode }) { 19 | const connectors = useConnectors(undefined) 20 | useEffect(() => { 21 | const selectedWallet = window.localStorage.getItem('selectedWallet') 22 | // connect(gnosisSafe) 23 | connect(network) 24 | if (selectedWallet === Wallet.INJECTED) { 25 | connect(injected) 26 | } 27 | if (selectedWallet === Wallet.COINBASE_WALLET) { 28 | connect(coinbaseWallet) 29 | } 30 | if (selectedWallet === Wallet.WALLET_CONNECT) { 31 | connect(walletConnect) 32 | } 33 | }, []) // eslint-disable-line react-hooks/exhaustive-deps 34 | 35 | return {children} 36 | } 37 | -------------------------------------------------------------------------------- /src/components/analytics/GoogleAnalyticsReporter.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import ReactGA from 'react-ga' 3 | import { RouteComponentProps } from 'react-router-dom' 4 | 5 | // fires a GA pageview every time the route changes 6 | export default function GoogleAnalyticsReporter({ location: { pathname, search } }: RouteComponentProps): null { 7 | useEffect(() => { 8 | ReactGA.pageview(`${pathname}${search}`) 9 | }, [pathname, search]) 10 | return null 11 | } 12 | -------------------------------------------------------------------------------- /src/components/swap/AdvancedSwapDetailsDropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { useLastTruthy } from '../../hooks/useLast' 4 | import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails' 5 | 6 | export const AdvancedDetailsFooter = styled.div<{ show: boolean }>` 7 | padding-top: calc(16px + 2rem); 8 | padding-bottom: 20px; 9 | margin-top: -2rem; 10 | width: 100%; 11 | max-width: 400px; 12 | border-bottom-left-radius: 20px; 13 | border-bottom-right-radius: 20px; 14 | color: ${({ theme }) => theme.text2}; 15 | background-color: ${({ theme }) => theme.advancedBG}; 16 | z-index: -1; 17 | 18 | transform: ${({ show }) => (show ? 'translateY(0%)' : 'translateY(-100%)')}; 19 | transition: transform 300ms ease-in-out; 20 | ` 21 | 22 | export default function AdvancedSwapDetailsDropdown({ trade, stableswapTrade, ...rest }: AdvancedSwapDetailsProps) { 23 | const lastTrade = useLastTruthy(trade) 24 | 25 | return ( 26 | 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/components/swap/FormattedPriceImpact.tsx: -------------------------------------------------------------------------------- 1 | import { Percent } from '@trisolaris/sdk' 2 | import React from 'react' 3 | import { ONE_BIPS } from '../../constants' 4 | import { warningSeverity } from '../../utils/prices' 5 | import { ErrorText } from './styleds' 6 | 7 | /** 8 | * Formatted version of price impact text with warning colors 9 | */ 10 | export default function FormattedPriceImpact({ 11 | priceImpact, 12 | isStableSwapPriceImpactSevere, 13 | isRoutedViaStableSwap 14 | }: { 15 | priceImpact?: Percent 16 | isStableSwapPriceImpactSevere?: boolean 17 | isRoutedViaStableSwap?: boolean 18 | }) { 19 | return ( 20 | 25 | {priceImpact ? (priceImpact.lessThan(ONE_BIPS) ? '<0.01%' : `${priceImpact.toFixed(2)}%`) : '-'} 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/swap/SwapRoute.tsx: -------------------------------------------------------------------------------- 1 | import { Trade } from '@trisolaris/sdk' 2 | import React, { Fragment, memo, useContext } from 'react' 3 | import { ChevronRight } from 'react-feather' 4 | import { Flex } from 'rebass' 5 | import { ThemeContext } from 'styled-components' 6 | import { TYPE } from '../../theme' 7 | import CurrencyLogo from '../CurrencyLogo' 8 | 9 | export default memo(function SwapRoute({ trade }: { trade: Trade }) { 10 | const theme = useContext(ThemeContext) 11 | return ( 12 | 22 | {trade.route.path.map((token, i, path) => { 23 | const isLastItem: boolean = i === path.length - 1 24 | return ( 25 | 26 | 27 | 28 | 29 | {token.symbol} 30 | 31 | 32 | {isLastItem ? null : } 33 | 34 | ) 35 | })} 36 | 37 | ) 38 | }) 39 | -------------------------------------------------------------------------------- /src/components/swap/confirmPriceImpactWithoutFee.ts: -------------------------------------------------------------------------------- 1 | import { Percent } from '@trisolaris/sdk' 2 | import { ALLOWED_PRICE_IMPACT_HIGH, PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN } from '../../constants' 3 | import i18next from '../../i18n' 4 | 5 | /** 6 | * Given the price impact, get user confirmation. 7 | * 8 | * @param priceImpactWithoutFee price impact of the trade without the fee. 9 | */ 10 | export default function confirmPriceImpactWithoutFee(priceImpactWithoutFee: Percent): boolean { 11 | if (!priceImpactWithoutFee.lessThan(PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN)) { 12 | return ( 13 | window.prompt( 14 | i18next.t('swap.priceImpactMinPrompt', { 15 | priceImpact: PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN.toFixed(0) 16 | }) 17 | ) === i18next.t('swap.confirm') 18 | ) 19 | } else if (!priceImpactWithoutFee.lessThan(ALLOWED_PRICE_IMPACT_HIGH)) { 20 | return window.confirm( 21 | i18next.t('swap.priceImpactHighPrompt', { priceImpact: ALLOWED_PRICE_IMPACT_HIGH.toFixed(0) }) 22 | ) 23 | } 24 | return true 25 | } 26 | -------------------------------------------------------------------------------- /src/connectors/__mocks__/NetworkConnector.ts: -------------------------------------------------------------------------------- 1 | class MiniRpcProvider {} 2 | 3 | export const NetworkConnector = jest.fn().mockImplementation( 4 | () => 5 | class NetworkConnectorMock { 6 | public get provider(): MiniRpcProvider { 7 | return jest.fn() 8 | } 9 | 10 | public async activate(): Promise { 11 | return jest.fn() 12 | } 13 | 14 | public async getProvider(): Promise { 15 | return jest.fn() 16 | } 17 | 18 | public async getChainId(): Promise { 19 | return jest.fn() 20 | } 21 | 22 | public async getAccount(): Promise { 23 | return null 24 | } 25 | 26 | public deactivate() { 27 | return 28 | } 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /src/connectors/__mocks__/index.ts: -------------------------------------------------------------------------------- 1 | export const NETWORK_CHAIN_ID: number = parseInt(process.env.REACT_APP_CHAIN_ID ?? '1313161554') 2 | 3 | export const network = jest.fn() 4 | 5 | export const getNetworkLibrary = jest.fn() 6 | 7 | export const injected = jest.fn() 8 | 9 | export const walletlink = jest.fn() 10 | 11 | export const walletconnect = jest.fn() 12 | -------------------------------------------------------------------------------- /src/connectors/fortmatic.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'formatic' 2 | -------------------------------------------------------------------------------- /src/constants/abis/argent-wallet-detector.ts: -------------------------------------------------------------------------------- 1 | import ARGENT_WALLET_DETECTOR_ABI from './argent-wallet-detector.json' 2 | 3 | const ARGENT_WALLET_DETECTOR_MAINNET_ADDRESS = '0xeca4B0bDBf7c55E9b7925919d03CbF8Dc82537E8' 4 | 5 | export { ARGENT_WALLET_DETECTOR_ABI, ARGENT_WALLET_DETECTOR_MAINNET_ADDRESS } 6 | -------------------------------------------------------------------------------- /src/constants/abis/erc20.ts: -------------------------------------------------------------------------------- 1 | import { Interface } from '@ethersproject/abi' 2 | import ERC20_ABI from './erc20.json' 3 | import ERC20_BYTES32_ABI from './erc20_bytes32.json' 4 | 5 | const ERC20_INTERFACE = new Interface(ERC20_ABI) 6 | 7 | const ERC20_BYTES32_INTERFACE = new Interface(ERC20_BYTES32_ABI) 8 | 9 | export default ERC20_INTERFACE 10 | export { ERC20_ABI, ERC20_BYTES32_INTERFACE, ERC20_BYTES32_ABI } 11 | -------------------------------------------------------------------------------- /src/constants/abis/erc20_bytes32.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "bytes32" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [], 19 | "name": "symbol", 20 | "outputs": [ 21 | { 22 | "name": "", 23 | "type": "bytes32" 24 | } 25 | ], 26 | "payable": false, 27 | "stateMutability": "view", 28 | "type": "function" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /src/constants/abis/migrator.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_factoryV1", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "address", 11 | "name": "_router", 12 | "type": "address" 13 | } 14 | ], 15 | "stateMutability": "nonpayable", 16 | "type": "constructor" 17 | }, 18 | { 19 | "inputs": [ 20 | { 21 | "internalType": "address", 22 | "name": "token", 23 | "type": "address" 24 | }, 25 | { 26 | "internalType": "uint256", 27 | "name": "amountTokenMin", 28 | "type": "uint256" 29 | }, 30 | { 31 | "internalType": "uint256", 32 | "name": "amountETHMin", 33 | "type": "uint256" 34 | }, 35 | { 36 | "internalType": "address", 37 | "name": "to", 38 | "type": "address" 39 | }, 40 | { 41 | "internalType": "uint256", 42 | "name": "deadline", 43 | "type": "uint256" 44 | } 45 | ], 46 | "name": "migrate", 47 | "outputs": [], 48 | "stateMutability": "nonpayable", 49 | "type": "function" 50 | }, 51 | { 52 | "stateMutability": "payable", 53 | "type": "receive" 54 | } 55 | ] 56 | -------------------------------------------------------------------------------- /src/constants/abis/migrator.ts: -------------------------------------------------------------------------------- 1 | import MIGRATOR_ABI from './migrator.json' 2 | 3 | const MIGRATOR_ADDRESS = '0x16D4F26C15f3658ec65B1126ff27DD3dF2a2996b' 4 | 5 | export { MIGRATOR_ADDRESS, MIGRATOR_ABI } 6 | -------------------------------------------------------------------------------- /src/constants/abis/staking-rewards.ts: -------------------------------------------------------------------------------- 1 | import { Interface } from '@ethersproject/abi' 2 | import { abi as STAKING_REWARDS_ABI } from '@pangolindex/governance/artifacts/contracts/StakingRewards.sol/StakingRewards.json' 3 | 4 | const STAKING_REWARDS_INTERFACE = new Interface(STAKING_REWARDS_ABI) 5 | 6 | export { STAKING_REWARDS_INTERFACE } 7 | -------------------------------------------------------------------------------- /src/constants/farms.ts: -------------------------------------------------------------------------------- 1 | export const DUAL_REWARDS_POOLS = [51, 52] 2 | 3 | export const TRI_ONLY_REWARDS_POOLS = [7, 8, 11, 0, 3, 4, 31, 32, 33, 45] 4 | 5 | export const ECOSYSTEM_POOLS = [] 6 | 7 | export const STABLE_POOLS = [ 8 | 47, 9 | 53, 10 | // NOTE - this is actually mcv2 pool id 56 but evie screwed up the lp token when setting up the stableswap pool farms :( 11 | 54 12 | ] 13 | 14 | export const LEGACY_POOLS = [ 15 | 24, 16 | 49, 17 | 20, 18 | 41, 19 | 1, 20 | 2, 21 | 6, 22 | 16, 23 | 12, 24 | 13, 25 | 14, 26 | 9, 27 | 10, 28 | 21, 29 | 22, 30 | 17, 31 | 18, 32 | 23, 33 | 27, 34 | 26, 35 | 28, 36 | 29, 37 | 36, 38 | 5, 39 | 40, 40 | 37, 41 | 42, 42 | 34, 43 | 35, 44 | 15, 45 | 39, 46 | 21, 47 | 30, 48 | 48, 49 | 19, 50 | 46, 51 | 38, 52 | 43, 53 | 44 54 | ] 55 | 56 | export const MULTIPLE_REWARD_POOLS = [50] 57 | -------------------------------------------------------------------------------- /src/constants/multicall/index.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '@trisolaris/sdk' 2 | import MULTICALL_ABI from './abi.json' 3 | 4 | const MULTICALL_NETWORKS = { 5 | [ChainId.FUJI]: '0xb465Fd2d9C71d5D6e6c069aaC9b4E21c69aAA78f', 6 | [ChainId.AVALANCHE]: '0x0FB54156B496b5a040b51A71817aED9e2927912E', 7 | [ChainId.POLYGON]: '0x11ce4B23bD875D7F5C6a31084f55fDe1e9A87507', 8 | [ChainId.AURORA]: '0x49eb1F160e167aa7bA96BdD88B6C1f2ffda5212A' 9 | } 10 | 11 | export { MULTICALL_ABI, MULTICALL_NETWORKS } 12 | -------------------------------------------------------------------------------- /src/constants/tokens/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import * as TOKENS from '../../tokens' 3 | 4 | const { 5 | PNG, 6 | USDT, 7 | WBTC, 8 | DAI, 9 | TRI, 10 | XTRI, 11 | AURORA, 12 | ATUST, 13 | ATLUNA, 14 | USDC, 15 | AAVE, 16 | WNEAR, 17 | AVAX, 18 | MATIC, 19 | BNB, 20 | FLX, 21 | MECHA, 22 | SOLACE, 23 | STNEAR, 24 | META, 25 | GBA, 26 | XNL 27 | } = TOKENS 28 | 29 | const BASE_TOKENS = { 30 | PNG, 31 | USDT, 32 | WBTC, 33 | DAI, 34 | TRI, 35 | XTRI, 36 | AURORA, 37 | ATUST, 38 | ATLUNA, 39 | USDC, 40 | AAVE, 41 | WNEAR, 42 | AVAX, 43 | MATIC, 44 | BNB, 45 | FLX, 46 | MECHA, 47 | SOLACE, 48 | STNEAR, 49 | META, 50 | GBA, 51 | XNL 52 | } 53 | 54 | describe('constants/index.ts', () => { 55 | test('Base Tokens', () => { 56 | _.map(BASE_TOKENS, (symbol, token) => { 57 | expect({ [token]: symbol }).toMatchSnapshot() 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /src/data/Allowances.ts: -------------------------------------------------------------------------------- 1 | import { Token, TokenAmount } from '@trisolaris/sdk' 2 | import { useMemo } from 'react' 3 | 4 | import { useTokenContract } from '../hooks/useContract' 5 | import { useSingleCallResult } from '../state/multicall/hooks' 6 | 7 | export function useTokenAllowance(token?: Token, owner?: string, spender?: string): TokenAmount | undefined { 8 | const contract = useTokenContract(token?.address, false) 9 | 10 | const inputs = useMemo(() => [owner, spender], [owner, spender]) 11 | const allowance = useSingleCallResult(contract, 'allowance', inputs).result 12 | 13 | return useMemo(() => (token && allowance ? new TokenAmount(token, allowance.toString()) : undefined), [ 14 | token, 15 | allowance 16 | ]) 17 | } 18 | -------------------------------------------------------------------------------- /src/data/TotalStakedInPool.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from '@ethersproject/bignumber' 2 | import { Token, TokenAmount, ChainId } from '@trisolaris/sdk' 3 | import { ChefVersions } from '../state/stake/stake-constants' 4 | import { MASTERCHEF_ADDRESS_V1, MASTERCHEF_ADDRESS_V2 } from '../state/stake/hooks-sushi' 5 | import { useTokenContract } from '../hooks/useContract' 6 | import { useSingleCallResult } from '../state/multicall/hooks' 7 | 8 | // returns undefined if input token is undefined, or fails to get token contract, 9 | // or contract total supply cannot be fetched 10 | export function useTotalStakedInPool(token?: Token, version?: ChefVersions): TokenAmount | undefined { 11 | const masterChefAddress = 12 | version === ChefVersions.V1 ? MASTERCHEF_ADDRESS_V1[ChainId.AURORA] : MASTERCHEF_ADDRESS_V2[ChainId.AURORA] 13 | 14 | const contract = useTokenContract(token?.address, false) 15 | const totalStakedInPool: BigNumber = useSingleCallResult(contract, 'balanceOf', [masterChefAddress])?.result?.[0] 16 | 17 | return token && totalStakedInPool ? new TokenAmount(token, totalStakedInPool.toString()) : undefined 18 | } 19 | -------------------------------------------------------------------------------- /src/data/TotalSupply.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from '@ethersproject/bignumber' 2 | import { Token, TokenAmount } from '@trisolaris/sdk' 3 | import { useTokenContract } from '../hooks/useContract' 4 | import { useSingleCallResult } from '../state/multicall/hooks' 5 | 6 | // returns undefined if input token is undefined, or fails to get token contract, 7 | // or contract total supply cannot be fetched 8 | export function useTotalSupply(token?: Token): TokenAmount | undefined { 9 | const contract = useTokenContract(token?.address, false) 10 | 11 | const totalSupply: BigNumber = useSingleCallResult(contract, 'totalSupply')?.result?.[0] 12 | 13 | return token && totalSupply ? new TokenAmount(token, totalSupply.toString()) : undefined 14 | } 15 | -------------------------------------------------------------------------------- /src/fetchers/coingecko-api-id.ts: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr' 2 | 3 | export interface Coin { 4 | id: string 5 | name: string 6 | api_symbol: string 7 | symbol: string 8 | } 9 | 10 | interface CoinGeckoSearchResponse { 11 | coins: Array 12 | } 13 | 14 | const fetcher = async (url: string, tokenName: string): Promise => { 15 | const response = await fetch(`${url}?query=${encodeURIComponent(tokenName)}`) 16 | if (response.ok) { 17 | const data: CoinGeckoSearchResponse = await response.json() 18 | if (data && data.coins) { 19 | const coin: Coin | undefined = data.coins?.find(({ symbol }) => symbol.toLowerCase() === tokenName.toLowerCase()) 20 | if (!coin) { 21 | throw new Error('No coins found') 22 | } 23 | // eslint-disable-next-line @typescript-eslint/camelcase 24 | return { id: coin.id, name: coin.name, api_symbol: coin.api_symbol, symbol: coin.symbol } 25 | } else { 26 | throw new Error('No coins found') 27 | } 28 | } 29 | 30 | throw new Error('response not ok') 31 | } 32 | 33 | const useCoinSearch = (tokenName: string | undefined) => { 34 | const { data, error } = useSWR( 35 | tokenName ? [`https://api.coingecko.com/api/v3/search`, tokenName] : null, 36 | fetcher 37 | ) 38 | if (error) { 39 | console.error(error) 40 | } 41 | 42 | return { 43 | coin: data, 44 | isLoading: !error && !data, 45 | error 46 | } 47 | } 48 | 49 | export default useCoinSearch 50 | -------------------------------------------------------------------------------- /src/fetchers/farms.ts: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr' 2 | import { ExternalInfo } from '../state/stake/stake-constants' 3 | 4 | async function fetcher() { 5 | try { 6 | const response = await fetch('https://cdn.trisolaris.io/datav2.json') 7 | 8 | return response.json() 9 | } catch (e) { 10 | console.debug('Error loading datav2.json from cdn') 11 | } 12 | } 13 | 14 | async function getStakingInfoData() { 15 | return (await fetcher()) ?? {} 16 | } 17 | 18 | export function useFetchStakingInfoData(): ExternalInfo[] | undefined { 19 | const { data } = useSWR(['useFetchStakingInfoData'], getStakingInfoData) 20 | 21 | return data 22 | } 23 | -------------------------------------------------------------------------------- /src/fetchers/pTRI.ts: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr' 2 | 3 | async function fetcher() { 4 | try { 5 | const response = await fetch('https://cdn.trisolaris.io/ptri.json') 6 | 7 | return response.json() 8 | } catch (e) { 9 | console.debug('Error loading datav2.json from cdn') 10 | } 11 | } 12 | 13 | async function getPTRIAPRData() { 14 | return (await fetcher()) ?? {} 15 | } 16 | 17 | type PTRI_APR = { 18 | apr: number 19 | triBalance: number 20 | convertedUsdcAmount: number 21 | tri_price: number 22 | timestamp: number 23 | } 24 | 25 | export function useFetchPTRIAPR(): PTRI_APR[] { 26 | const { data } = useSWR(['useFetchPTRIAPR'], getPTRIAPRData) 27 | 28 | return data ?? [] 29 | } 30 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { useWeb3React as useWeb3ReactCore } from '@web3-react/core' 2 | import { NETWORK_CHAIN_ID } from '../connectors' 3 | 4 | export function useActiveWeb3React() { 5 | const result = useWeb3ReactCore() 6 | return { ...result, chainId: result.chainId === NETWORK_CHAIN_ID ? result.chainId : undefined } 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/useCopyClipboard.ts: -------------------------------------------------------------------------------- 1 | import copy from 'copy-to-clipboard' 2 | import { useCallback, useEffect, useState } from 'react' 3 | 4 | export default function useCopyClipboard(timeout = 500): [boolean, (toCopy: string) => void] { 5 | const [isCopied, setIsCopied] = useState(false) 6 | 7 | const staticCopy = useCallback((text: string) => { 8 | const didCopy = copy(text) 9 | setIsCopied(didCopy) 10 | }, []) 11 | 12 | useEffect(() => { 13 | if (isCopied) { 14 | const hide = setTimeout(() => { 15 | setIsCopied(false) 16 | }, timeout) 17 | 18 | return () => { 19 | clearTimeout(hide) 20 | } 21 | } 22 | return undefined 23 | }, [isCopied, setIsCopied, timeout]) 24 | 25 | return [isCopied, staticCopy] 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/useCurrentBlockTimestamp.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import { useSingleCallResult } from '../state/multicall/hooks' 3 | import { useMulticallContract } from './useContract' 4 | 5 | // gets the current timestamp from the blockchain 6 | export default function useCurrentBlockTimestamp(): BigNumber | undefined { 7 | const multicall = useMulticallContract() 8 | return useSingleCallResult(multicall, 'getCurrentBlockTimestamp')?.result?.[0] 9 | } 10 | -------------------------------------------------------------------------------- /src/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | // modified from https://usehooks.com/useDebounce/ 4 | export default function useDebounce(value: T, delay: number): T { 5 | const [debouncedValue, setDebouncedValue] = useState(value) 6 | 7 | useEffect(() => { 8 | // Update debounced value after delay 9 | const handler = setTimeout(() => { 10 | setDebouncedValue(value) 11 | }, delay) 12 | 13 | // Cancel the timeout if value changes (also on delay change or unmount) 14 | // This is how we prevent debounced value from updating if value is changed ... 15 | // .. within the delay period. Timeout gets cleared and restarted. 16 | return () => { 17 | clearTimeout(handler) 18 | } 19 | }, [value, delay]) 20 | 21 | return debouncedValue 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/useENS.ts: -------------------------------------------------------------------------------- 1 | import { isAddress } from '../utils' 2 | 3 | /** 4 | * Given a name or address, does a lookup to resolve to an address and name 5 | * @param nameOrAddress ENS name or address 6 | */ 7 | export default function useENS( 8 | nameOrAddress?: string | null 9 | ): { loading: boolean; address: string | null; name: string | null } { 10 | const validated = isAddress(nameOrAddress) 11 | 12 | return { 13 | loading: false, 14 | address: validated ? validated : null, 15 | name: null 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/useEmbeddedSwapUI.ts: -------------------------------------------------------------------------------- 1 | export default function useEmbeddedSwapUI() { 2 | return window.self !== window.top 3 | } 4 | -------------------------------------------------------------------------------- /src/hooks/useFetchListCallback.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from '@reduxjs/toolkit' 2 | import { TokenList } from '@pangolindex/token-lists' 3 | import { useCallback } from 'react' 4 | import { useDispatch } from 'react-redux' 5 | import { AppDispatch } from '../state' 6 | import { fetchTokenList } from '../state/lists/actions' 7 | import getTokenList from '../utils/getTokenList' 8 | 9 | export function useFetchListCallback(): (listUrl: string) => Promise { 10 | const dispatch = useDispatch() 11 | 12 | const ensResolver = useCallback(() => { 13 | throw new Error('Could not construct mainnet ENS resolver') 14 | }, []) 15 | 16 | return useCallback( 17 | async (listUrl: string) => { 18 | const requestId = nanoid() 19 | dispatch(fetchTokenList.pending({ requestId, url: listUrl })) 20 | return getTokenList(listUrl, ensResolver) 21 | .then(tokenList => { 22 | dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId })) 23 | return tokenList 24 | }) 25 | .catch(error => { 26 | console.debug(`Failed to get list at url ${listUrl}`, error) 27 | dispatch(fetchTokenList.rejected({ url: listUrl, requestId, errorMessage: error.message })) 28 | throw error 29 | }) 30 | }, 31 | [dispatch, ensResolver] 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/hooks/useGetTokenByAddress.ts: -------------------------------------------------------------------------------- 1 | import { Token, ChainId } from '@trisolaris/sdk' 2 | import _ from 'lodash' 3 | import { useCallback } from 'react' 4 | import { useAllTokens } from './Tokens' 5 | import React from 'react' 6 | 7 | export default function useGetTokenByAddress(): (address: string) => Token { 8 | const allTokens = useAllTokens() 9 | const getTokenByAddress = useCallback( 10 | (address: string) => 11 | _.find(allTokens, token => token.address.toLowerCase() === address.toLowerCase()) ?? 12 | new Token(ChainId.AURORA, address, 18), 13 | [allTokens] 14 | ) 15 | 16 | return getTokenByAddress 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/useGetTokenPrice.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, Token } from '@trisolaris/sdk' 2 | 3 | import { useDerivedSwapInfo } from '../state/swap/hooks' 4 | import useUSDCPrice from '../hooks/useUSDCPrice' 5 | 6 | import { Field } from '../state/swap/actions' 7 | import { USDT } from '../constants/tokens' 8 | 9 | const useGetTokenPrice = (token: Token) => { 10 | const { address: usdtAddress } = USDT[ChainId.AURORA] 11 | 12 | const usdcPrice = useUSDCPrice(token) 13 | const tradeParameters = usdcPrice 14 | ? { 15 | INPUT: { currencyId: undefined }, 16 | OUTPUT: { currencyId: undefined }, 17 | independentField: Field.INPUT, 18 | recipient: null, 19 | typedValue: '1' 20 | } 21 | : { 22 | INPUT: { currencyId: token.address }, 23 | OUTPUT: { currencyId: usdtAddress }, 24 | independentField: Field.INPUT, 25 | recipient: null, 26 | typedValue: '1' 27 | } 28 | const swapToUsdtResult = useDerivedSwapInfo(tradeParameters) 29 | 30 | const { v2Trade } = swapToUsdtResult 31 | const swapPrice = v2Trade?.executionPrice 32 | 33 | // If getUsdcprice doesn't work, we simulate a swap for getting the price. 34 | const tokenPrice = usdcPrice ?? swapPrice 35 | 36 | return tokenPrice 37 | } 38 | 39 | export default useGetTokenPrice 40 | -------------------------------------------------------------------------------- /src/hooks/useHttpLocations.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import uriToHttp from '../utils/uriToHttp' 3 | 4 | export default function useHttpLocations(uri: string | undefined): string[] { 5 | return useMemo(() => { 6 | return uri ? uriToHttp(uri) : [] 7 | }, [uri]) 8 | } 9 | -------------------------------------------------------------------------------- /src/hooks/useInterval.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export default function useInterval(callback: () => void, delay: null | number, leading = true) { 4 | const savedCallback = useRef<() => void>() 5 | 6 | // Remember the latest callback. 7 | useEffect(() => { 8 | savedCallback.current = callback 9 | }, [callback]) 10 | 11 | // Set up the interval. 12 | useEffect(() => { 13 | function tick() { 14 | const current = savedCallback.current 15 | current && current() 16 | } 17 | 18 | if (delay !== null) { 19 | if (leading) tick() 20 | const id = setInterval(tick, delay) 21 | return () => clearInterval(id) 22 | } 23 | return undefined 24 | }, [delay, leading]) 25 | } 26 | -------------------------------------------------------------------------------- /src/hooks/useIsWindowVisible.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react' 2 | 3 | const VISIBILITY_STATE_SUPPORTED = 'visibilityState' in document 4 | 5 | function isWindowVisible() { 6 | return !VISIBILITY_STATE_SUPPORTED || document.visibilityState !== 'hidden' 7 | } 8 | 9 | /** 10 | * Returns whether the window is currently visible to the user. 11 | */ 12 | export default function useIsWindowVisible(): boolean { 13 | const [focused, setFocused] = useState(isWindowVisible()) 14 | const listener = useCallback(() => { 15 | setFocused(isWindowVisible()) 16 | }, [setFocused]) 17 | 18 | useEffect(() => { 19 | if (!VISIBILITY_STATE_SUPPORTED) return undefined 20 | 21 | document.addEventListener('visibilitychange', listener) 22 | return () => { 23 | document.removeEventListener('visibilitychange', listener) 24 | } 25 | }, [listener]) 26 | 27 | return focused 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/useLast.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | /** 4 | * Returns the last value of type T that passes a filter function 5 | * @param value changing value 6 | * @param filterFn function that determines whether a given value should be considered for the last value 7 | */ 8 | export default function useLast( 9 | value: T | undefined | null, 10 | filterFn?: (value: T | null | undefined) => boolean 11 | ): T | null | undefined { 12 | const [last, setLast] = useState(filterFn && filterFn(value) ? value : undefined) 13 | useEffect(() => { 14 | setLast(last => { 15 | const shouldUse: boolean = filterFn ? filterFn(value) : true 16 | if (shouldUse) return value 17 | return last 18 | }) 19 | }, [filterFn, value]) 20 | return last 21 | } 22 | 23 | function isDefined(x: T | null | undefined): x is T { 24 | return x !== null && x !== undefined 25 | } 26 | 27 | /** 28 | * Returns the last truthy value of type T 29 | * @param value changing value 30 | */ 31 | export function useLastTruthy(value: T | undefined | null): T | null | undefined { 32 | return useLast(value, isDefined) 33 | } 34 | -------------------------------------------------------------------------------- /src/hooks/useMigrateCallback.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from 'react' 2 | import { CurrencyAmount, ChainId } from '@trisolaris/sdk' 3 | 4 | import { useTransactionAdder } from '../state/transactions/hooks' 5 | import { usePTriContract } from './useContract' 6 | 7 | import { XTRI } from '../constants/tokens' 8 | 9 | enum MIGRATION_STATUS { 10 | NOT_MIGRATED, 11 | MIGRATING, 12 | MIGRATED 13 | } 14 | 15 | export function useMigrateCallback( 16 | amount: CurrencyAmount | undefined 17 | ): { callback: null | (() => Promise); error: string | null; migrationStatus: MIGRATION_STATUS } { 18 | const addTransaction = useTransactionAdder() 19 | const pTriContract = usePTriContract() 20 | 21 | const [migrationStatus, setMigrationStatus] = useState(MIGRATION_STATUS.NOT_MIGRATED) 22 | 23 | return useMemo(() => { 24 | const migrate = async (): Promise => { 25 | try { 26 | setMigrationStatus(MIGRATION_STATUS.MIGRATING) 27 | const xTriAmount = amount?.raw.toString() 28 | const tx = await pTriContract?.migrate(XTRI[ChainId.AURORA].address, xTriAmount) 29 | addTransaction(tx, { summary: `Migrated xTRI` }) 30 | setMigrationStatus(MIGRATION_STATUS.MIGRATED) 31 | return tx.hash 32 | } catch (error) { 33 | setMigrationStatus(MIGRATION_STATUS.NOT_MIGRATED) 34 | if ((error as any)?.code === 4001) { 35 | throw new Error('Transaction rejected.') 36 | } else { 37 | console.error(`Migration failed`, error, 'migrate') 38 | throw new Error(`Migration failed: ${(error as any).message}`) 39 | } 40 | } 41 | } 42 | 43 | return { callback: migrate, error: null, migrationStatus } 44 | }, [addTransaction, amount]) 45 | } 46 | -------------------------------------------------------------------------------- /src/hooks/useNormalizeTokensToDecimal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CurrencyAmount, JSBI, Token, TokenAmount } from '@trisolaris/sdk' 3 | import { BIG_INT_ZERO } from '../constants' 4 | 5 | type Props = { 6 | currencyAmounts: (CurrencyAmount | undefined)[] 7 | normalizationToken: Token 8 | } 9 | 10 | export default function useNormalizeTokensToDecimal({ currencyAmounts, normalizationToken }: Props) { 11 | const normalizationDecimal = normalizationToken.decimals 12 | 13 | const normalizedAmounts = currencyAmounts.map(currencyAmount => { 14 | if (currencyAmount == null) { 15 | return BIG_INT_ZERO 16 | } 17 | 18 | const { 19 | currency: { decimals }, 20 | raw: amount 21 | } = currencyAmount 22 | const decimalDelta = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(Math.abs(decimals - normalizationDecimal))) 23 | 24 | switch (true) { 25 | case decimals > normalizationDecimal: 26 | return JSBI.divide(amount, decimalDelta) 27 | case decimals < normalizationDecimal: 28 | return JSBI.multiply(amount, decimalDelta) 29 | default: 30 | return amount 31 | } 32 | }) 33 | 34 | return normalizedAmounts.map(amount => new TokenAmount(normalizationToken, amount)) 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/useOnClickOutside.tsx: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect, useRef } from 'react' 2 | 3 | export function useOnClickOutside( 4 | node: RefObject, 5 | handler: undefined | (() => void) 6 | ) { 7 | const handlerRef = useRef void)>(handler) 8 | useEffect(() => { 9 | handlerRef.current = handler 10 | }, [handler]) 11 | 12 | useEffect(() => { 13 | const handleClickOutside = (e: MouseEvent) => { 14 | if (node.current?.contains(e.target as Node) ?? false) { 15 | return 16 | } 17 | if (handlerRef.current) handlerRef.current() 18 | } 19 | 20 | document.addEventListener('mousedown', handleClickOutside) 21 | 22 | return () => { 23 | document.removeEventListener('mousedown', handleClickOutside) 24 | } 25 | }, [node]) 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/usePTRIAPR.ts: -------------------------------------------------------------------------------- 1 | import { Fraction, JSBI, Percent } from '@trisolaris/sdk' 2 | import _ from 'lodash' 3 | import { useMemo } from 'react' 4 | import { BIG_INT_ZERO } from '../constants' 5 | import { useFetchPTRIAPR } from '../fetchers/pTRI' 6 | 7 | export default function usePTRIAPR() { 8 | const aprData = useFetchPTRIAPR() 9 | 10 | const data = aprData.map(({ apr, triBalance }) => ({ apr, triBalance })) 11 | 12 | return useMemo( 13 | (): Percent | null => 14 | _.chain(data) 15 | .map(({ apr, triBalance }) => { 16 | const [int = '0', decimal = '0'] = apr.toString().split('.') 17 | 18 | const aprJSBI = new Fraction(decimal, 1 + '0'.repeat(decimal.length)) // Create the decimal portion 19 | .add(JSBI.BigInt(int)) // Add the integer portion 20 | .divide('100') // Divide by 100 (APR comes in as a percent) 21 | 22 | return { 23 | apr: aprJSBI, 24 | triBalance: JSBI.BigInt(triBalance) 25 | } 26 | }) 27 | .reduce( 28 | (acc, { apr, triBalance }) => ({ 29 | numerator: apr.multiply(triBalance).add(acc.numerator), 30 | denominator: JSBI.add(acc.denominator, triBalance) 31 | }), 32 | { numerator: new Fraction('0'), denominator: BIG_INT_ZERO } 33 | ) 34 | .thru(({ numerator, denominator }) => 35 | JSBI.equal(denominator, BIG_INT_ZERO) ? null : numerator.divide(denominator) 36 | ) 37 | .thru(fraction => (fraction != null ? new Percent(fraction.numerator, fraction.denominator) : null)) 38 | .value(), 39 | [data] 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/hooks/usePTRIRemittances.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, CurrencyAmount, JSBI } from '@trisolaris/sdk' 2 | import _ from 'lodash' 3 | import { useMemo } from 'react' 4 | import { USDC_E_USDT_E_TLP } from '../constants/tokens' 5 | import { useFetchPTRIAPR } from '../fetchers/pTRI' 6 | 7 | export default function usePTRIRemittances() { 8 | const aprData = useFetchPTRIAPR() 9 | 10 | return useMemo( 11 | () => 12 | _(aprData) 13 | .map(({ convertedUsdcAmount, timestamp }) => ({ 14 | amount: CurrencyAmount.fromRawAmount(USDC_E_USDT_E_TLP[ChainId.AURORA], JSBI.BigInt(convertedUsdcAmount)), 15 | timestamp: new Date(timestamp * 1000) 16 | })) 17 | .orderBy(({ timestamp }) => timestamp.valueOf(), 'desc') 18 | .slice(0, 7) 19 | .value(), 20 | [aprData] 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/useParsedQueryString.ts: -------------------------------------------------------------------------------- 1 | import { parse, ParsedQs } from 'qs' 2 | import { useMemo } from 'react' 3 | import { useLocation } from 'react-router-dom' 4 | 5 | export default function useParsedQueryString(): ParsedQs { 6 | const { search } = useLocation() 7 | return useMemo( 8 | () => (search && search.length > 1 ? parse(search, { parseArrays: false, ignoreQueryPrefix: true }) : {}), 9 | [search] 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | // modified from https://usehooks.com/usePrevious/ 4 | export default function usePrevious(value: T) { 5 | // The ref object is a generic container whose current property is mutable ... 6 | // ... and can hold any value, similar to an instance property on a class 7 | const ref = useRef() 8 | 9 | // Store current value in ref 10 | useEffect(() => { 11 | ref.current = value 12 | }, [value]) // Only re-run if value changes 13 | 14 | // Return previous value (happens before update in useEffect above) 15 | return ref.current 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/useSelectChain.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | 3 | import { NETWORK_CHAIN_ID, injected } from '../connectors' 4 | import { CHAIN_PARAMS } from '../constants' 5 | import { useWeb3React } from '@web3-react/core' 6 | 7 | export default function useSelectChain() { 8 | const { connector } = useWeb3React() 9 | 10 | return useCallback(async () => { 11 | if (!connector) return 12 | 13 | try { 14 | try { 15 | const addChainParameter = CHAIN_PARAMS[NETWORK_CHAIN_ID] 16 | 17 | if (injected !== connector) { 18 | console.log('Please switch to Aurora network in wallet settings.') 19 | } else { 20 | await connector.activate(addChainParameter) 21 | } 22 | } catch (error) { 23 | // In activating a new chain, the connector passes through a deactivated state. 24 | // If we fail to switch chains, it may remain in this state, and no longer be usable. 25 | // We defensively re-activate the connector to ensure the user does not notice any change. 26 | try { 27 | await connector.activate() 28 | } catch (error) { 29 | console.error('Failed to re-activate connector', error) 30 | } 31 | } 32 | } catch (error) { 33 | console.error('Failed to switch networks', error) 34 | } 35 | }, [connector]) 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/useTLP.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, Token } from '@trisolaris/sdk' 2 | 3 | type Props = { 4 | lpAddress: string 5 | token0: Token 6 | token1: Token 7 | } 8 | 9 | export default function useTLP({ lpAddress, token0, token1 }: Props) { 10 | return new Token(ChainId.AURORA, lpAddress, 18, 'TLP', `TLP ${token0?.symbol}-${token1?.symbol}`) 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react' 2 | 3 | export default function useTimeout(callback: () => void, delay?: number) { 4 | const timeoutRef = useRef(null) 5 | const savedCallback = useRef(callback) 6 | useEffect(() => { 7 | savedCallback.current = callback 8 | }, [callback]) 9 | useEffect(() => { 10 | const tick = () => savedCallback.current() 11 | if (typeof delay === 'number') { 12 | timeoutRef.current = window.setTimeout(tick, delay) 13 | return () => window.clearTimeout(timeoutRef?.current ?? 0) 14 | } 15 | }, [delay]) 16 | 17 | return timeoutRef 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/useTimestampFromBlock.ts: -------------------------------------------------------------------------------- 1 | import { useActiveWeb3React } from '.' 2 | import { useState, useEffect } from 'react' 3 | 4 | export function useTimestampFromBlock(block: number | undefined): number | undefined { 5 | const { provider } = useActiveWeb3React() 6 | const [timestamp, setTimestamp] = useState() 7 | useEffect(() => { 8 | async function fetchTimestamp() { 9 | if (block) { 10 | const blockData = await provider?.getBlock(block) 11 | blockData && setTimestamp(blockData.timestamp) 12 | } 13 | } 14 | if (!timestamp) { 15 | fetchTimestamp() 16 | } 17 | }, [block, provider, timestamp]) 18 | return timestamp 19 | } 20 | -------------------------------------------------------------------------------- /src/hooks/useToggle.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react' 2 | 3 | export default function useToggle(initialState = false): [boolean, () => void] { 4 | const [state, setState] = useState(initialState) 5 | const toggle = useCallback(() => setState(state => !state), []) 6 | return [state, toggle] 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/useToggledVersion.ts: -------------------------------------------------------------------------------- 1 | import useParsedQueryString from './useParsedQueryString' 2 | 3 | export enum Version { 4 | v1 = 'v1', 5 | v2 = 'v2' 6 | } 7 | 8 | export const DEFAULT_VERSION: Version = Version.v2 9 | 10 | export default function useToggledVersion(): Version { 11 | const { use } = useParsedQueryString() 12 | if (!use || typeof use !== 'string') return Version.v2 13 | if (use.toLowerCase() === 'v1') return Version.v1 14 | return DEFAULT_VERSION 15 | } 16 | -------------------------------------------------------------------------------- /src/hooks/useTransactionDeadline.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import { useSelector } from 'react-redux' 3 | import { AppState } from '../state' 4 | 5 | // combines the current timestamp with the user setting to give the deadline that should be used for any submitted transaction 6 | export default function useTransactionDeadline(): BigNumber | undefined { 7 | const ttl = useSelector(state => state.user.userDeadline) 8 | const currentTimestamp = BigNumber.from(new Date().getTime() + 100000) 9 | if (currentTimestamp && ttl) return currentTimestamp.add(ttl) 10 | return undefined 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | const isClient = typeof window === 'object' 4 | 5 | function getSize() { 6 | return { 7 | width: isClient ? window.innerWidth : undefined, 8 | height: isClient ? window.innerHeight : undefined 9 | } 10 | } 11 | 12 | // https://usehooks.com/useWindowSize/ 13 | export function useWindowSize() { 14 | const [windowSize, setWindowSize] = useState(getSize) 15 | 16 | useEffect(() => { 17 | function handleResize() { 18 | setWindowSize(getSize()) 19 | } 20 | 21 | if (isClient) { 22 | window.addEventListener('resize', handleResize) 23 | return () => { 24 | window.removeEventListener('resize', handleResize) 25 | } 26 | } 27 | return undefined 28 | }, []) 29 | 30 | return windowSize 31 | } 32 | -------------------------------------------------------------------------------- /src/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next' 2 | import { initReactI18next } from 'react-i18next' 3 | import XHR from 'i18next-xhr-backend' 4 | import LanguageDetector from 'i18next-browser-languagedetector' 5 | 6 | // export const availableLanguages = ['en', 'de', 'tr', 'zh', 'es', 'fr', 'pt-br'] 7 | export const availableLanguages = ['en'] 8 | export const defaultLocale = 'en' 9 | 10 | const determineLngFn = (code: string): string => { 11 | if (!code || code.length === 0) { 12 | return (i18next.language = defaultLocale) 13 | } 14 | 15 | // Full locale match 16 | if (availableLanguages.includes(code.toLowerCase())) { 17 | return (i18next.language = code.toLowerCase()) 18 | } 19 | 20 | // Base locale match 21 | const codeBase = code.split('-')[0].toLowerCase() 22 | if (availableLanguages.includes(codeBase)) { 23 | return (i18next.language = codeBase) 24 | } 25 | 26 | // Fallback 27 | return (i18next.language = defaultLocale) 28 | } 29 | 30 | declare module 'i18next' { 31 | interface CustomTypeOptions { 32 | returnNull: false 33 | } 34 | } 35 | 36 | i18next 37 | .use(XHR) 38 | .use(LanguageDetector) 39 | .use(initReactI18next) 40 | .init({ 41 | returnNull: false, 42 | backend: { 43 | loadPath: `./locales/aurora/{{lng}}.json` 44 | }, 45 | react: { 46 | useSuspense: true 47 | }, 48 | lowerCaseLng: true, 49 | fallbackLng: determineLngFn, 50 | preload: [defaultLocale], 51 | keySeparator: '.', 52 | interpolation: { escapeValue: false } 53 | }) 54 | 55 | export default i18next 56 | -------------------------------------------------------------------------------- /src/pages/AddLiquidity/PriceAndPoolShare.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Percent, Price } from '@trisolaris/sdk' 3 | import { useTranslation } from 'react-i18next' 4 | import { LightCard } from '../../components/Card' 5 | import Row from '../../components/Row' 6 | import { TYPE } from '../../theme' 7 | import { PoolPriceBar } from './PoolPriceBar' 8 | 9 | type Props = { 10 | currencies: React.ComponentProps['currencies'] 11 | noLiquidity?: boolean 12 | poolTokenPercentage?: Percent 13 | price?: Price 14 | } 15 | 16 | export default function PriceAndPoolShare({ currencies, noLiquidity, poolTokenPercentage, price }: Props) { 17 | const { t } = useTranslation() 18 | 19 | return ( 20 | 21 | 22 | 23 | {noLiquidity ? t('addLiquidity.initialPrices') : t('addLiquidity.prices')} {t('addLiquidity.poolShare')} 24 | 25 | 26 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/AddLiquidity/redirects.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Redirect, RouteComponentProps } from 'react-router-dom' 3 | import AddLiquidity from './index' 4 | 5 | export function RedirectToAddLiquidity() { 6 | return 7 | } 8 | 9 | const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/ 10 | export function RedirectOldAddLiquidityPathStructure(props: RouteComponentProps<{ currencyIdA: string }>) { 11 | const { 12 | match: { 13 | params: { currencyIdA } 14 | } 15 | } = props 16 | const match = currencyIdA.match(OLD_PATH_STRUCTURE) 17 | if (match?.length) { 18 | return 19 | } 20 | 21 | return 22 | } 23 | 24 | export function RedirectDuplicateTokenIds(props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) { 25 | const { 26 | match: { 27 | params: { currencyIdA, currencyIdB } 28 | } 29 | } = props 30 | if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) { 31 | return 32 | } 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/AppBody.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | export const BodyWrapper = styled.div` 5 | position: relative; 6 | max-width: 420px; 7 | width: 100%; 8 | background: ${({ theme }) => theme.bg2}; 9 | box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 10 | 0px 24px 32px rgba(0, 0, 0, 0.01); 11 | border-radius: 30px; 12 | padding: 1rem; 13 | ` 14 | 15 | /** 16 | * The styled container element that wraps the content of most pages and the tabs. 17 | */ 18 | export default function AppBody({ children }: { children: React.ReactNode }) { 19 | return {children} 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/EarnTri/FarmType.tsx: -------------------------------------------------------------------------------- 1 | export enum FarmType { 2 | NORMAL = 'normal', 3 | STABLE = 'stable' 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/EarnTri/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { PageWrapper } from '../../components/Page' 3 | import FarmBanner from '../../components/earn/FarmBanner' 4 | import EarnTri from './EarnTri' 5 | 6 | export default function Earn() { 7 | return ( 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/RemoveLiquidity/redirects.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { RouteComponentProps, Redirect } from 'react-router-dom' 3 | 4 | const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/ 5 | 6 | export function RedirectOldRemoveLiquidityPathStructure({ 7 | match: { 8 | params: { tokens } 9 | } 10 | }: RouteComponentProps<{ tokens: string }>) { 11 | if (!OLD_PATH_STRUCTURE.test(tokens)) { 12 | return 13 | } 14 | const [currency0, currency1] = tokens.split('-') 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/StableSwapPool/styleds.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from 'rebass' 2 | import styled from 'styled-components' 3 | 4 | export const Wrapper = styled.div` 5 | position: relative; 6 | ` 7 | 8 | export const ClickableText = styled(Text)` 9 | :hover { 10 | cursor: pointer; 11 | } 12 | color: ${({ theme }) => theme.primary1}; 13 | ` 14 | export const MaxButton = styled.button<{ width: string }>` 15 | padding: 0.5rem 1rem; 16 | background-color: ${({ theme }) => theme.primary5}; 17 | border: 1px solid ${({ theme }) => theme.primary5}; 18 | border-radius: 0.5rem; 19 | font-size: 1rem; 20 | ${({ theme }) => theme.mediaWidth.upToSmall` 21 | padding: 0.25rem 0.5rem; 22 | `}; 23 | font-weight: 500; 24 | cursor: pointer; 25 | margin: 0.25rem; 26 | overflow: hidden; 27 | color: ${({ theme }) => theme.text1}; 28 | :hover { 29 | border: 1px solid ${({ theme }) => theme.white}; 30 | } 31 | :focus { 32 | border: 1px solid ${({ theme }) => theme.white}; 33 | outline: none; 34 | } 35 | ` 36 | 37 | export const Dots = styled.span` 38 | &::after { 39 | display: inline-block; 40 | animation: ellipsis 1.25s infinite; 41 | content: '.'; 42 | width: 1em; 43 | text-align: left; 44 | } 45 | @keyframes ellipsis { 46 | 0% { 47 | content: '.'; 48 | } 49 | 33% { 50 | content: '..'; 51 | } 52 | 66% { 53 | content: '...'; 54 | } 55 | } 56 | ` 57 | -------------------------------------------------------------------------------- /src/pages/StableSwapPoolAddLiquidity/confirmStableSwapAddLiquiditySlippage.tsx: -------------------------------------------------------------------------------- 1 | import { Percent } from '@trisolaris/sdk' 2 | import { PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN } from '../../constants' 3 | import i18next from '../../i18n' 4 | 5 | /** 6 | * Given the price impact, get user confirmation. 7 | * 8 | * @param priceImpactWithoutFee price impact of the trade without the fee. 9 | */ 10 | export default function confirmStableSwapAddLiquiditySlippage(priceImpact: Percent, allowedSlippage: Percent): boolean { 11 | if (priceImpact.greaterThan(allowedSlippage)) { 12 | return ( 13 | window.prompt( 14 | 'Warning: There is a high Price Impact on this transaction due either to the ' + 15 | "transaction's size or insufficient liquidity, which will result in a loss of funds. " + 16 | '\n\nPlease type "confirm" if you are understand the implications of submitting your transaction.' 17 | ) === i18next.t('swap.confirm') 18 | ) 19 | } 20 | 21 | return true 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/StableSwapPoolAddLiquidity/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Redirect, RouteComponentProps } from 'react-router-dom' 3 | import { StableSwapPoolName } from '../../state/stableswap/constants' 4 | import StableSwapPoolAddLiquidityImpl from './StableSwapPoolAddLiquidityImpl' 5 | 6 | type Props = RouteComponentProps<{ stableSwapPoolName?: StableSwapPoolName }> 7 | 8 | export default function StableSwapPoolAddLiquidity(props: Props) { 9 | const { stableSwapPoolName } = props?.match?.params ?? {} 10 | 11 | // If invalid StableSwapPoolName is passed in, redirect to the stableswap pools page 12 | if (stableSwapPoolName == null || !StableSwapPoolName.hasOwnProperty(stableSwapPoolName)) { 13 | return 14 | } 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/StableSwapPoolRemoveLiquidity/StableSwapRemoveLiquidityCurrencyRow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Currency } from '@trisolaris/sdk' 3 | 4 | import { Input as NumericalInput } from '../../components/NumericalInput' 5 | import { useTranslation } from 'react-i18next' 6 | 7 | import { InputRow, Aligner, InputPanel, Container, StyledTokenName } from './StableSwapPoolRemoveLiquidity.styles' 8 | import CurrencyLogo from '../../components/CurrencyLogo' 9 | import _ from 'lodash' 10 | 11 | type Props = { 12 | currency: Currency 13 | value: string 14 | index: number 15 | } 16 | 17 | export default function StableSwapRemoveCurrencyRow({ currency, value, index }: Props) { 18 | const { t } = useTranslation() 19 | 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | {(currency && currency.symbol && currency.symbol.length > 20 31 | ? currency.symbol.slice(0, 4) + 32 | '...' + 33 | currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length) 34 | : currency?.symbol) || t('currencyInputPanel.selectToken')} 35 | 36 | 37 |
38 |
39 |
40 |
41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/StableSwapPoolRemoveLiquidity/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Redirect, RouteComponentProps } from 'react-router-dom' 3 | import { StableSwapPoolName } from '../../state/stableswap/constants' 4 | import StableSwapPoolRemoveLiquidityImpl from './StableSwapPoolRemoveLiquidityImpl' 5 | 6 | type Props = RouteComponentProps<{ stableSwapPoolName?: StableSwapPoolName }> 7 | 8 | export default function StableSwapPoolRemoveLiquidity(props: Props) { 9 | const { stableSwapPoolName } = props?.match?.params ?? {} 10 | 11 | // If invalid StableSwapPoolName is passed in, redirect to the stableswap pools page 12 | if (stableSwapPoolName == null || !StableSwapPoolName.hasOwnProperty(stableSwapPoolName)) { 13 | return 14 | } 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/StakeTri/PTRIRemittances.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { DarkGreyCard } from '../../components/Card' 4 | import { AutoColumn } from '../../components/Column' 5 | import { AutoRow } from '../../components/Row' 6 | import usePTRIRemittances from '../../hooks/usePTRIRemittances' 7 | import { TYPE } from '../../theme' 8 | import { addCommasToNumber } from '../../utils' 9 | 10 | const Row = styled(AutoRow)` 11 | border-radius: 12px; 12 | justify-content: space-between; 13 | padding: 0.2rem 0.5rem; 14 | :nth-child(odd) { 15 | ${({ theme }) => ` 16 | background-color: ${theme.bg3} 17 | `} 18 | } 19 | ` 20 | 21 | export default function PTRIRemittances() { 22 | const remittanceData = usePTRIRemittances() 23 | return ( 24 | 25 | 26 | Remittances 27 | 28 | Date 29 | USD Remitted 30 | 31 | {remittanceData.map(({ amount, timestamp }) => ( 32 | 33 | {timestamp.toLocaleDateString()} 34 | {addCommasToNumber(amount.toFixed(2))} 35 | 36 | ))} 37 | 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/pages/StakeTri/StakeButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CurrencyAmount, TokenAmount } from '@trisolaris/sdk' 3 | 4 | import { ButtonError, ButtonPrimary } from '../../components/Button' 5 | import { BIG_INT_ZERO } from '../../constants' 6 | import { ApprovalState } from '../../hooks/useApproveCallback' 7 | 8 | type StakeButtonProps = { 9 | balance: TokenAmount 10 | stakingAmount: CurrencyAmount | undefined 11 | approvalState: ApprovalState 12 | isStaking: boolean 13 | pendingTx: boolean 14 | handleStakeAndUnstake: () => void 15 | } 16 | 17 | function StakeButton({ 18 | balance, 19 | stakingAmount, 20 | approvalState, 21 | isStaking, 22 | pendingTx, 23 | handleStakeAndUnstake 24 | }: StakeButtonProps) { 25 | const insufficientFunds = (balance?.equalTo(BIG_INT_ZERO) ?? false) || stakingAmount?.greaterThan(balance) 26 | if (insufficientFunds && stakingAmount?.greaterThan(BIG_INT_ZERO)) { 27 | return ( 28 | 29 | Insufficient Balance 30 | 31 | ) 32 | } 33 | 34 | const isValid = 35 | // If user is unstaking, we don't need to check approval status 36 | (isStaking ? approvalState === ApprovalState.APPROVED : true) && 37 | !pendingTx && 38 | stakingAmount?.greaterThan(BIG_INT_ZERO) === true 39 | 40 | return ( 41 | 42 | {isStaking ? 'Stake' : 'Unstake'} 43 | 44 | ) 45 | } 46 | 47 | export default StakeButton 48 | -------------------------------------------------------------------------------- /src/pages/Swap/redirects.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { Redirect, RouteComponentProps } from 'react-router-dom' 4 | import { AppDispatch } from '../../state' 5 | import { ApplicationModal, setOpenModal } from '../../state/application/actions' 6 | 7 | // Redirects to swap but only replace the pathname 8 | export function RedirectPathToSwapOnly({ location }: RouteComponentProps) { 9 | return 10 | } 11 | 12 | // Redirects from the /swap/:outputCurrency path to the /swap?outputCurrency=:outputCurrency format 13 | export function RedirectToSwap(props: RouteComponentProps<{ outputCurrency: string }>) { 14 | const { 15 | location: { search }, 16 | match: { 17 | params: { outputCurrency } 18 | } 19 | } = props 20 | 21 | return ( 22 | 1 28 | ? `${search}&outputCurrency=${outputCurrency}` 29 | : `?outputCurrency=${outputCurrency}` 30 | }} 31 | /> 32 | ) 33 | } 34 | 35 | export function OpenClaimAddressModalAndRedirectToSwap(props: RouteComponentProps) { 36 | const dispatch = useDispatch() 37 | useEffect(() => { 38 | dispatch(setOpenModal(ApplicationModal.ADDRESS_CLAIM)) 39 | }, [dispatch]) 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module 'jazzicon' { 4 | export default function(diameter: number, seed: number): HTMLElement 5 | } 6 | 7 | declare module 'fortmatic' 8 | 9 | interface Window { 10 | ethereum?: { 11 | isMetaMask?: boolean 12 | isBraveWallet?: boolean 13 | on?: (...args: any[]) => void 14 | removeListener?: (...args: any[]) => void 15 | } 16 | web3?: {} 17 | } 18 | 19 | declare module 'content-hash' { 20 | declare function decode(x: string): string 21 | declare function getCodec(x: string): string 22 | } 23 | 24 | declare module 'multihashes' { 25 | declare function decode(buff: Uint8Array): { code: number; name: string; length: number; digest: Uint8Array } 26 | declare function toB58String(hash: Uint8Array): string 27 | } 28 | -------------------------------------------------------------------------------- /src/state/application/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | import { TokenList } from '@pangolindex/token-lists' 3 | 4 | export type PopupContent = 5 | | { 6 | txn: { 7 | hash: string 8 | success: boolean 9 | summary?: string 10 | } 11 | } 12 | | { 13 | listUpdate: { 14 | listUrl: string 15 | oldList: TokenList 16 | newList: TokenList 17 | auto: boolean 18 | } 19 | } 20 | 21 | export enum ApplicationModal { 22 | WALLET, 23 | SETTINGS, 24 | SELF_CLAIM, 25 | ADDRESS_CLAIM, 26 | CLAIM_POPUP, 27 | MENU, 28 | DELEGATE, 29 | VOTE, 30 | LANGUAGE, 31 | TRI_PRICE, 32 | BRIDGES_MENU, 33 | GOVERNANCE_MENU 34 | } 35 | 36 | export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber') 37 | export const setOpenModal = createAction('application/setOpenModal') 38 | export const addPopup = createAction<{ key?: string; removeAfterMs?: number | null; content: PopupContent }>( 39 | 'application/addPopup' 40 | ) 41 | export const removePopup = createAction<{ key: string }>('application/removePopup') 42 | -------------------------------------------------------------------------------- /src/state/application/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer, nanoid } from '@reduxjs/toolkit' 2 | import { addPopup, PopupContent, removePopup, updateBlockNumber, ApplicationModal, setOpenModal } from './actions' 3 | 4 | type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }> 5 | 6 | export interface ApplicationState { 7 | readonly blockNumber: { readonly [chainId: number]: number } 8 | readonly popupList: PopupList 9 | readonly openModal: ApplicationModal | null 10 | } 11 | 12 | const initialState: ApplicationState = { 13 | blockNumber: {}, 14 | popupList: [], 15 | openModal: null 16 | } 17 | 18 | export default createReducer(initialState, builder => 19 | builder 20 | .addCase(updateBlockNumber, (state, action) => { 21 | const { chainId, blockNumber } = action.payload 22 | if (typeof state.blockNumber[chainId] !== 'number') { 23 | state.blockNumber[chainId] = blockNumber 24 | } else { 25 | state.blockNumber[chainId] = Math.max(blockNumber, state.blockNumber[chainId]) 26 | } 27 | }) 28 | .addCase(setOpenModal, (state, action) => { 29 | state.openModal = action.payload 30 | }) 31 | .addCase(addPopup, (state, { payload: { content, key, removeAfterMs = 15000 } }) => { 32 | state.popupList = (key ? state.popupList.filter(popup => popup.key !== key) : state.popupList).concat([ 33 | { 34 | key: key || nanoid(), 35 | show: true, 36 | content, 37 | removeAfterMs 38 | } 39 | ]) 40 | }) 41 | .addCase(removePopup, (state, { payload: { key } }) => { 42 | state.popupList.forEach(p => { 43 | if (p.key === key) { 44 | p.show = false 45 | } 46 | }) 47 | }) 48 | ) 49 | -------------------------------------------------------------------------------- /src/state/burn/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export enum Field { 4 | LIQUIDITY_PERCENT = 'LIQUIDITY_PERCENT', 5 | LIQUIDITY = 'LIQUIDITY', 6 | CURRENCY_A = 'CURRENCY_A', 7 | CURRENCY_B = 'CURRENCY_B' 8 | } 9 | 10 | export const typeInput = createAction<{ field: Field; typedValue: string }>('burn/typeInputBurn') 11 | -------------------------------------------------------------------------------- /src/state/burn/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { Field, typeInput } from './actions' 3 | 4 | export interface BurnState { 5 | readonly independentField: Field 6 | readonly typedValue: string 7 | } 8 | 9 | const initialState: BurnState = { 10 | independentField: Field.LIQUIDITY_PERCENT, 11 | typedValue: '0' 12 | } 13 | 14 | export default createReducer(initialState, builder => 15 | builder.addCase(typeInput, (state, { payload: { field, typedValue } }) => { 16 | return { 17 | ...state, 18 | independentField: field, 19 | typedValue 20 | } 21 | }) 22 | ) 23 | -------------------------------------------------------------------------------- /src/state/global/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | // fired once when the app reloads but before the app renders 4 | // allows any updates to be applied to store data loaded from localStorage 5 | export const updateVersion = createAction('global/updateVersion') 6 | -------------------------------------------------------------------------------- /src/state/lists/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionCreatorWithPayload, createAction } from '@reduxjs/toolkit' 2 | import { TokenList, Version } from '@pangolindex/token-lists' 3 | 4 | export const fetchTokenList: Readonly<{ 5 | pending: ActionCreatorWithPayload<{ url: string; requestId: string }> 6 | fulfilled: ActionCreatorWithPayload<{ url: string; tokenList: TokenList; requestId: string }> 7 | rejected: ActionCreatorWithPayload<{ url: string; errorMessage: string; requestId: string }> 8 | }> = { 9 | pending: createAction('lists/fetchTokenList/pending'), 10 | fulfilled: createAction('lists/fetchTokenList/fulfilled'), 11 | rejected: createAction('lists/fetchTokenList/rejected') 12 | } 13 | 14 | export const acceptListUpdate = createAction('lists/acceptListUpdate') 15 | export const addList = createAction('lists/addList') 16 | export const removeList = createAction('lists/removeList') 17 | export const selectList = createAction<{ url: string; shouldSelect: boolean }>('lists/selectList') 18 | export const rejectVersionUpdate = createAction('lists/rejectVersionUpdate') 19 | export const setDefaultList = createAction('lists/setDefaultList') 20 | -------------------------------------------------------------------------------- /src/state/mint/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export enum Field { 4 | CURRENCY_A = 'CURRENCY_A', 5 | CURRENCY_B = 'CURRENCY_B' 6 | } 7 | 8 | export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('mint/typeInputMint') 9 | export const resetMintState = createAction('mint/resetMintState') 10 | -------------------------------------------------------------------------------- /src/state/mint/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Store } from 'redux' 2 | 3 | import { Field, typeInput } from './actions' 4 | import reducer, { MintState } from './reducer' 5 | 6 | describe('mint reducer', () => { 7 | let store: Store 8 | 9 | beforeEach(() => { 10 | store = createStore(reducer, { 11 | independentField: Field.CURRENCY_A, 12 | typedValue: '', 13 | otherTypedValue: '' 14 | }) 15 | }) 16 | 17 | describe('typeInput', () => { 18 | it('sets typed value', () => { 19 | store.dispatch(typeInput({ field: Field.CURRENCY_A, typedValue: '1.0', noLiquidity: false })) 20 | expect(store.getState()).toEqual({ independentField: Field.CURRENCY_A, typedValue: '1.0', otherTypedValue: '' }) 21 | }) 22 | it('clears other value', () => { 23 | store.dispatch(typeInput({ field: Field.CURRENCY_A, typedValue: '1.0', noLiquidity: false })) 24 | store.dispatch(typeInput({ field: Field.CURRENCY_B, typedValue: '1.0', noLiquidity: false })) 25 | expect(store.getState()).toEqual({ independentField: Field.CURRENCY_B, typedValue: '1.0', otherTypedValue: '' }) 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/state/mint/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { Field, resetMintState, typeInput } from './actions' 3 | 4 | export interface MintState { 5 | readonly independentField: Field 6 | readonly typedValue: string 7 | readonly otherTypedValue: string // for the case when there's no liquidity 8 | } 9 | 10 | const initialState: MintState = { 11 | independentField: Field.CURRENCY_A, 12 | typedValue: '', 13 | otherTypedValue: '' 14 | } 15 | 16 | export default createReducer(initialState, builder => 17 | builder 18 | .addCase(resetMintState, () => initialState) 19 | .addCase(typeInput, (state, { payload: { field, typedValue, noLiquidity } }) => { 20 | if (noLiquidity) { 21 | // they're typing into the field they've last typed in 22 | if (field === state.independentField) { 23 | return { 24 | ...state, 25 | independentField: field, 26 | typedValue 27 | } 28 | } 29 | // they're typing into a new field, store the other value 30 | else { 31 | return { 32 | ...state, 33 | independentField: field, 34 | typedValue, 35 | otherTypedValue: state.typedValue 36 | } 37 | } 38 | } else { 39 | return { 40 | ...state, 41 | independentField: field, 42 | typedValue, 43 | otherTypedValue: '' 44 | } 45 | } 46 | }) 47 | ) 48 | -------------------------------------------------------------------------------- /src/state/stableswap-add-liquidity/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export enum Field { 4 | CURRENCY_0 = 'CURRENCY_0', 5 | CURRENCY_1 = 'CURRENCY_1', 6 | CURRENCY_2 = 'CURRENCY_2', 7 | CURRENCY_3 = 'CURRENCY_3', 8 | CURRENCY_4 = 'CURRENCY_4' 9 | } 10 | 11 | export const typeInput = createAction<{ field: Field; typedValue: string }>('stableswap-add-liquidity/typeInput') 12 | export const resetAddLiquidityState = createAction('stableswap-add-liquidity/resetAddLiquidityState') 13 | -------------------------------------------------------------------------------- /src/state/stableswap-add-liquidity/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { Field, resetAddLiquidityState, typeInput } from './actions' 3 | 4 | export interface StableSwapAddLiquidityState { 5 | readonly [Field.CURRENCY_0]: string 6 | readonly [Field.CURRENCY_1]: string 7 | readonly [Field.CURRENCY_2]: string 8 | readonly [Field.CURRENCY_3]: string 9 | readonly [Field.CURRENCY_4]: string 10 | } 11 | 12 | const initialState: StableSwapAddLiquidityState = { 13 | [Field.CURRENCY_0]: '', 14 | [Field.CURRENCY_1]: '', 15 | [Field.CURRENCY_2]: '', 16 | [Field.CURRENCY_3]: '', 17 | [Field.CURRENCY_4]: '' 18 | } 19 | 20 | export default createReducer(initialState, builder => 21 | builder 22 | .addCase(resetAddLiquidityState, () => initialState) 23 | .addCase(typeInput, (state, { payload: { field, typedValue } }) => { 24 | return { 25 | ...state, 26 | [field]: typedValue 27 | } 28 | }) 29 | ) 30 | -------------------------------------------------------------------------------- /src/state/stableswap/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export enum Field { 4 | INPUT = 'INPUT', 5 | OUTPUT = 'OUTPUT' 6 | } 7 | 8 | export const selectCurrency = createAction<{ field: Field; currencyId: string }>('stableswap/selectCurrency') 9 | export const switchCurrencies = createAction('stableswap/switchCurrencies') 10 | export const typeInput = createAction<{ field: Field; typedValue: string }>('stableswap/typeInput') 11 | export const replaceStableSwapState = createAction<{ 12 | field: Field 13 | typedValue: string 14 | inputCurrencyId?: string 15 | outputCurrencyId?: string 16 | recipient: string | null 17 | }>('stableswap/replaceSwapState') 18 | export const setRecipient = createAction<{ recipient: string | null }>('stableswap/setRecipient') 19 | -------------------------------------------------------------------------------- /src/state/stake/__tests__/stake-constants.test.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '@trisolaris/sdk' 2 | import { STAKING } from '../stake-constants' 3 | 4 | describe('stake-constants.ts', () => { 5 | test('Polygon Pools', () => { 6 | STAKING[ChainId.POLYGON].forEach(pool => expect(pool).toMatchSnapshot(`Polygon Pool [ID: ${pool.ID}]`)) 7 | }) 8 | test('Aurora Pools', () => { 9 | STAKING[ChainId.AURORA].forEach(pool => expect(pool).toMatchSnapshot(`Aurora Pool [ID: ${pool.ID}]`)) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/state/stake/apr.ts: -------------------------------------------------------------------------------- 1 | import { ChefVersions, STAKING, StakingTri } from './stake-constants' 2 | import { useMemo } from 'react' 3 | import { useFarmsAPI } from './useFarmsAPI' 4 | import { ChainId } from '@trisolaris/sdk' 5 | import { useFarmContractsForVersion } from './useFarmContractsForVersion' 6 | 7 | // gets the staking info from the network for the active chain id 8 | export function useFarms(): StakingTri[] { 9 | const activeFarms = STAKING[ChainId.AURORA] 10 | const farms = useFarmsAPI() 11 | const stakingInfoV1 = useFarmContractsForVersion(ChefVersions.V1) 12 | const stakingInfoV2 = useFarmContractsForVersion(ChefVersions.V2) 13 | 14 | const stakingInfo = stakingInfoV1.concat(stakingInfoV2) 15 | 16 | const stakingInfoMap = useMemo( 17 | () => 18 | stakingInfo.reduce((acc, item) => { 19 | acc.set(item.ID, item) 20 | return acc 21 | }, new Map()), 22 | [stakingInfo] 23 | ) 24 | 25 | const farmsMap = useMemo( 26 | () => 27 | farms.reduce((acc, item) => { 28 | acc.set(item.ID, item) 29 | return acc 30 | }, new Map()), 31 | [farms] 32 | ) 33 | 34 | const result = useMemo( 35 | () => 36 | activeFarms.reduce((acc, farm) => { 37 | const farmID = farm.ID 38 | const farmResult = farmsMap.has(farmID) ? farmsMap.get(farmID) : farm 39 | 40 | if (stakingInfoMap.has(farmID)) { 41 | const { stakedAmount } = stakingInfoMap.get(farmID) 42 | farmResult.stakedAmount = stakedAmount 43 | } 44 | 45 | acc.push(farmResult) 46 | 47 | return acc 48 | }, []), 49 | [activeFarms, farmsMap, stakingInfoMap] 50 | ) 51 | 52 | return result 53 | } 54 | -------------------------------------------------------------------------------- /src/state/stake/useFarmsAPI.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '@trisolaris/sdk' 2 | import { STAKING, StakingTriFarms, tokenAmount } from './stake-constants' 3 | import { useFetchStakingInfoData } from '../../fetchers/farms' 4 | import React, { useRef } from 'react' 5 | 6 | import { roundDecimal } from '../../utils' 7 | import { NETWORK_CHAIN_ID } from '../../connectors' 8 | 9 | // gets the staking info from the network for the active chain id 10 | export function useFarmsAPI(): StakingTriFarms[] { 11 | const chainId = NETWORK_CHAIN_ID 12 | const activeFarms = STAKING[ChainId.AURORA] 13 | const lpAddresses = activeFarms.map(key => key.lpAddress) 14 | 15 | const result = useRef(activeFarms) 16 | const stakingInfoData = useFetchStakingInfoData() 17 | 18 | // get all the info from the staking rewards contracts 19 | 20 | if (!chainId) { 21 | return activeFarms 22 | } 23 | 24 | result.current = lpAddresses.map((_, index) => { 25 | const { totalStakedInUSD, totalRewardRate, apr: _apr, nonTriAPRs: _nonTriAPRs = [] } = 26 | stakingInfoData?.[index] ?? {} 27 | 28 | const apr = roundDecimal(_apr ?? 0) 29 | const nonTriAPRs = _nonTriAPRs.filter(({ apr }) => apr > 0).map(data => ({ ...data, apr: roundDecimal(data.apr) })) 30 | 31 | return { 32 | ...activeFarms[index], 33 | earnedAmount: tokenAmount, 34 | totalStakedAmount: tokenAmount, 35 | totalStakedInUSD: Math.round(totalStakedInUSD ?? 0), 36 | totalRewardRate: Math.round(totalRewardRate ?? 0), 37 | rewardRate: tokenAmount, 38 | apr, 39 | noTriRewards: apr === 0, 40 | nonTriAPRs, 41 | hasNonTriRewards: nonTriAPRs.some(({ apr }) => apr > 0) 42 | } 43 | }) 44 | 45 | return result.current 46 | } 47 | -------------------------------------------------------------------------------- /src/state/stake/useGetNonTriRewardsForPoolID.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react' 2 | import { ChainId, TokenAmount, JSBI } from '@trisolaris/sdk' 3 | import _ from 'lodash' 4 | import { useActiveWeb3React } from '../../hooks' 5 | import { CallState, useSingleCallResult } from '../multicall/hooks' 6 | import { useComplexNRewarderContract } from './hooks-sushi' 7 | import { ChefVersions, EarnedNonTriRewards, STAKING } from './stake-constants' 8 | import useGetTokenByAddress from '../../hooks/useGetTokenByAddress' 9 | 10 | export default function useGetNonTriRewardsForPoolID( 11 | version: number 12 | ): Pick & { result: EarnedNonTriRewards[] } { 13 | const { chainId, account } = useActiveWeb3React() 14 | const activeFarms = STAKING[chainId ?? ChainId.AURORA] 15 | const { chefVersion, poolId, rewarderAddress } = activeFarms[version] 16 | const complexNRewarderContract = useComplexNRewarderContract(rewarderAddress) 17 | const getTokenByAddress = useGetTokenByAddress() 18 | 19 | const { error, loading, result } = useSingleCallResult( 20 | chefVersion === ChefVersions.V2 ? complexNRewarderContract : null, 21 | 'pendingTokens', 22 | [poolId.toString(), account?.toString(), '0'] 23 | ) 24 | 25 | const earnedNonTriRewards = useMemo(() => { 26 | if (result == null) { 27 | return [] 28 | } 29 | 30 | const { rewardAmounts = [], rewardTokens = [] } = result 31 | return rewardTokens.map((rewardTokenAddress: string, i: number) => { 32 | const token = getTokenByAddress(rewardTokenAddress) 33 | return { 34 | token, 35 | amount: new TokenAmount(token, JSBI.BigInt(rewardAmounts[i] ?? 0)) 36 | } 37 | }) 38 | }, [getTokenByAddress, result]) 39 | 40 | return { error, loading, result: earnedNonTriRewards } 41 | } 42 | -------------------------------------------------------------------------------- /src/state/stake/useUserFarmStatistics.ts: -------------------------------------------------------------------------------- 1 | import { JSBI, Token, TokenAmount } from '@trisolaris/sdk' 2 | import { useTotalStakedInPool } from '../../data/TotalStakedInPool' 3 | import { ChefVersions } from './stake-constants' 4 | import { addCommasToNumber } from '../../utils' 5 | 6 | import { BIG_INT_ZERO } from '../../constants' 7 | 8 | type Props = { 9 | lpToken?: Token 10 | userLPStakedAmount?: TokenAmount | null 11 | totalPoolAmountUSD?: number 12 | chefVersion?: ChefVersions 13 | } 14 | 15 | export default function useUserFarmStatistics({ lpToken, userLPStakedAmount, totalPoolAmountUSD, chefVersion }: Props) { 16 | const totalStakedInPool = useTotalStakedInPool(lpToken, chefVersion) 17 | 18 | if ( 19 | totalStakedInPool == null || 20 | lpToken == null || 21 | userLPStakedAmount == null || 22 | totalPoolAmountUSD == null || 23 | userLPStakedAmount.equalTo(BIG_INT_ZERO) 24 | ) { 25 | return null 26 | } 27 | 28 | const userLPShare = userLPStakedAmount.divide(totalStakedInPool) 29 | const userLPAmountUSD = userLPShare?.multiply(JSBI.BigInt(totalPoolAmountUSD)) 30 | const userLPAmountUSDFormatted = userLPAmountUSD != null ? `$${addCommasToNumber(userLPAmountUSD.toFixed(2))}` : null 31 | 32 | return { 33 | totalStakedInPool, 34 | userLPShare, 35 | userLPAmountUSD, 36 | userLPAmountUSDFormatted 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/state/stakeTri/hooks.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '@trisolaris/sdk' 2 | 3 | import { TRI, XTRI } from '../../constants/tokens' 4 | import { useTotalSupply } from '../../data/TotalSupply' 5 | import { useTokenBalance } from '../wallet/hooks' 6 | 7 | export function useTriBarStats() { 8 | const chainId = ChainId.AURORA 9 | const totalXTri = useTotalSupply(XTRI[chainId]) 10 | const totalTriStaked = useTokenBalance(XTRI[chainId].address, TRI[chainId]) 11 | 12 | const xtriToTRIRatio = totalTriStaked != null && totalXTri != null ? totalTriStaked.divide(totalXTri) : null 13 | const triToXTRIRatio = totalTriStaked != null && totalXTri != null ? totalXTri.divide(totalTriStaked) : null 14 | 15 | return { 16 | totalTriStaked, 17 | totalXTri, 18 | triToXTRIRatio, 19 | xtriToTRIRatio 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/state/swap/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export enum Field { 4 | INPUT = 'INPUT', 5 | OUTPUT = 'OUTPUT' 6 | } 7 | 8 | export const selectCurrency = createAction<{ field: Field; currencyId: string }>('swap/selectCurrency') 9 | export const switchCurrencies = createAction('swap/switchCurrencies') 10 | export const typeInput = createAction<{ field: Field; typedValue: string }>('swap/typeInput') 11 | export const replaceSwapState = createAction<{ 12 | field: Field 13 | typedValue: string 14 | inputCurrencyId?: string 15 | outputCurrencyId?: string 16 | recipient: string | null 17 | }>('swap/replaceSwapState') 18 | export const setRecipient = createAction<{ recipient: string | null }>('swap/setRecipient') 19 | -------------------------------------------------------------------------------- /src/state/swap/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Store } from 'redux' 2 | import { Field, selectCurrency } from './actions' 3 | import reducer, { SwapState } from './reducer' 4 | 5 | describe('swap reducer', () => { 6 | let store: Store 7 | 8 | beforeEach(() => { 9 | store = createStore(reducer, { 10 | [Field.OUTPUT]: { currencyId: '' }, 11 | [Field.INPUT]: { currencyId: '' }, 12 | typedValue: '', 13 | independentField: Field.INPUT, 14 | recipient: null 15 | }) 16 | }) 17 | 18 | describe('selectToken', () => { 19 | it('changes token', () => { 20 | store.dispatch( 21 | selectCurrency({ 22 | field: Field.OUTPUT, 23 | currencyId: '0x0000' 24 | }) 25 | ) 26 | 27 | expect(store.getState()).toEqual({ 28 | [Field.OUTPUT]: { currencyId: '0x0000' }, 29 | [Field.INPUT]: { currencyId: '' }, 30 | typedValue: '', 31 | independentField: Field.INPUT, 32 | recipient: null 33 | }) 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /src/state/transactions/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | import { ChainId } from '@trisolaris/sdk' 3 | 4 | export interface SerializableTransactionReceipt { 5 | to: string 6 | from: string 7 | contractAddress: string 8 | transactionIndex: number 9 | blockHash: string 10 | transactionHash: string 11 | blockNumber: number 12 | status?: number 13 | } 14 | 15 | export const addTransaction = createAction<{ 16 | chainId: ChainId 17 | hash: string 18 | from: string 19 | approval?: { tokenAddress: string; spender: string } 20 | claim?: { recipient: string } 21 | summary?: string 22 | }>('transactions/addTransaction') 23 | export const clearAllTransactions = createAction<{ chainId: ChainId }>('transactions/clearAllTransactions') 24 | export const finalizeTransaction = createAction<{ 25 | chainId: ChainId 26 | hash: string 27 | receipt: SerializableTransactionReceipt 28 | }>('transactions/finalizeTransaction') 29 | export const checkedTransaction = createAction<{ 30 | chainId: ChainId 31 | hash: string 32 | blockNumber: number 33 | }>('transactions/checkedTransaction') 34 | -------------------------------------------------------------------------------- /src/state/transactions/updater.test.ts: -------------------------------------------------------------------------------- 1 | import { shouldCheck } from './updater' 2 | 3 | describe('transactions updater', () => { 4 | describe('shouldCheck', () => { 5 | it('returns true if no receipt and never checked', () => { 6 | expect(shouldCheck(10, { addedTime: 100 })).toEqual(true) 7 | }) 8 | it('returns false if has receipt and never checked', () => { 9 | expect(shouldCheck(10, { addedTime: 100, receipt: {} })).toEqual(false) 10 | }) 11 | it('returns true if has not been checked in 1 blocks', () => { 12 | expect(shouldCheck(10, { addedTime: new Date().getTime(), lastCheckedBlockNumber: 9 })).toEqual(true) 13 | }) 14 | it('returns false if checked in last 3 blocks and greater than 20 minutes old', () => { 15 | expect(shouldCheck(10, { addedTime: new Date().getTime() - 21 * 60 * 1000, lastCheckedBlockNumber: 8 })).toEqual( 16 | false 17 | ) 18 | }) 19 | it('returns true if not checked in last 5 blocks and greater than 20 minutes old', () => { 20 | expect(shouldCheck(10, { addedTime: new Date().getTime() - 21 * 60 * 1000, lastCheckedBlockNumber: 5 })).toEqual( 21 | true 22 | ) 23 | }) 24 | it('returns false if checked in last 10 blocks and greater than 60 minutes old', () => { 25 | expect(shouldCheck(20, { addedTime: new Date().getTime() - 61 * 60 * 1000, lastCheckedBlockNumber: 11 })).toEqual( 26 | false 27 | ) 28 | }) 29 | it('returns true if checked in last 3 blocks and greater than 20 minutes old', () => { 30 | expect(shouldCheck(20, { addedTime: new Date().getTime() - 61 * 60 * 1000, lastCheckedBlockNumber: 10 })).toEqual( 31 | true 32 | ) 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/state/user/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export interface SerializedToken { 4 | chainId: number 5 | address: string 6 | decimals: number 7 | symbol?: string 8 | name?: string 9 | } 10 | 11 | export interface SerializedPair { 12 | token0: SerializedToken 13 | token1: SerializedToken 14 | } 15 | 16 | export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('user/updateMatchesDarkMode') 17 | export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode') 18 | export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode') 19 | export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number }>( 20 | 'user/updateUserSlippageTolerance' 21 | ) 22 | export const updateUserDeadline = createAction<{ userDeadline: number }>('user/updateUserDeadline') 23 | export const addSerializedToken = createAction<{ serializedToken: SerializedToken }>('user/addSerializedToken') 24 | export const removeSerializedToken = createAction<{ chainId: number; address: string }>('user/removeSerializedToken') 25 | export const addSerializedPair = createAction<{ serializedPair: SerializedPair }>('user/addSerializedPair') 26 | export const removeSerializedPair = createAction<{ chainId: number; tokenAAddress: string; tokenBAddress: string }>( 27 | 'user/removeSerializedPair' 28 | ) 29 | export const toggleURLWarning = createAction('app/toggleURLWarning') 30 | export const toggleFilterActiveFarms = createAction('user/toggleFilterActiveFarms') 31 | -------------------------------------------------------------------------------- /src/state/user/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Store } from 'redux' 2 | import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants' 3 | import { updateVersion } from '../global/actions' 4 | import reducer, { initialState, UserState } from './reducer' 5 | 6 | describe('swap reducer', () => { 7 | let store: Store 8 | 9 | beforeEach(() => { 10 | store = createStore(reducer, initialState) 11 | }) 12 | 13 | describe('updateVersion', () => { 14 | it('has no timestamp originally', () => { 15 | expect(store.getState().lastUpdateVersionTimestamp).toBeUndefined() 16 | }) 17 | it('sets the lastUpdateVersionTimestamp', () => { 18 | const time = new Date().getTime() 19 | store.dispatch(updateVersion()) 20 | expect(store.getState().lastUpdateVersionTimestamp).toBeGreaterThanOrEqual(time) 21 | }) 22 | it('sets allowed slippage and deadline', () => { 23 | store = createStore(reducer, { 24 | ...initialState, 25 | userDeadline: undefined, 26 | userSlippageTolerance: undefined 27 | } as any) 28 | store.dispatch(updateVersion()) 29 | expect(store.getState().userDeadline).toEqual(DEFAULT_DEADLINE_FROM_NOW) 30 | expect(store.getState().userSlippageTolerance).toEqual(INITIAL_ALLOWED_SLIPPAGE) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/state/user/updater.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { AppDispatch } from '../index' 4 | import { updateMatchesDarkMode } from './actions' 5 | 6 | export default function Updater(): null { 7 | const dispatch = useDispatch() 8 | 9 | // keep dark mode in sync with the system 10 | useEffect(() => { 11 | const darkHandler = (match: MediaQueryListEvent) => { 12 | dispatch(updateMatchesDarkMode({ matchesDarkMode: match.matches })) 13 | } 14 | 15 | const match = window?.matchMedia('(prefers-color-scheme: dark)') 16 | dispatch(updateMatchesDarkMode({ matchesDarkMode: match.matches })) 17 | 18 | if (match?.addListener) { 19 | match?.addListener(darkHandler) 20 | } else if (match?.addEventListener) { 21 | match?.addEventListener('change', darkHandler) 22 | } 23 | 24 | return () => { 25 | if (match?.removeListener) { 26 | match?.removeListener(darkHandler) 27 | } else if (match?.removeEventListener) { 28 | match?.removeEventListener('change', darkHandler) 29 | } 30 | } 31 | }, [dispatch]) 32 | 33 | return null 34 | } 35 | -------------------------------------------------------------------------------- /src/state/wyre/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export const updateQuote = createAction<{ quote: any }>('price/updateQuote') 4 | -------------------------------------------------------------------------------- /src/state/wyre/reducer.ts: -------------------------------------------------------------------------------- 1 | import { updateQuote } from './actions' 2 | import { createReducer } from '@reduxjs/toolkit' 3 | 4 | const currentTimestamp = () => new Date().getTime() 5 | 6 | export interface WyreState { 7 | quote: any | false 8 | timestamp: number 9 | } 10 | 11 | export const initialState: WyreState = { 12 | quote: false, 13 | timestamp: currentTimestamp() 14 | } 15 | 16 | export default createReducer(initialState, builder => 17 | builder.addCase(updateQuote, (state, action) => { 18 | state.quote = action.payload.quote 19 | state.timestamp = currentTimestamp() 20 | }) 21 | ) 22 | -------------------------------------------------------------------------------- /src/theme/DarkModeQueryParamReader.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { RouteComponentProps } from 'react-router-dom' 4 | import { parse } from 'qs' 5 | import { AppDispatch } from '../state' 6 | import { updateUserDarkMode } from '../state/user/actions' 7 | 8 | export default function DarkModeQueryParamReader({ location: { search } }: RouteComponentProps): null { 9 | const dispatch = useDispatch() 10 | 11 | useEffect(() => { 12 | if (!search) return 13 | if (search.length < 2) return 14 | 15 | const parsed = parse(search, { 16 | parseArrays: false, 17 | ignoreQueryPrefix: true 18 | }) 19 | 20 | const theme = parsed.theme 21 | 22 | if (typeof theme !== 'string') return 23 | 24 | if (theme.toLowerCase() === 'light') { 25 | dispatch(updateUserDarkMode({ userDarkMode: false })) 26 | } else if (theme.toLowerCase() === 'dark') { 27 | dispatch(updateUserDarkMode({ userDarkMode: true })) 28 | } 29 | }, [dispatch, search]) 30 | 31 | return null 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/chunkArray.test.ts: -------------------------------------------------------------------------------- 1 | import chunkArray from './chunkArray' 2 | 3 | describe('#chunkArray', () => { 4 | it('size 1', () => { 5 | expect(chunkArray([1, 2, 3], 1)).toEqual([[1], [2], [3]]) 6 | }) 7 | it('size 0 throws', () => { 8 | expect(() => chunkArray([1, 2, 3], 0)).toThrow('maxChunkSize must be gte 1') 9 | }) 10 | it('size gte items', () => { 11 | expect(chunkArray([1, 2, 3], 3)).toEqual([[1, 2, 3]]) 12 | expect(chunkArray([1, 2, 3], 4)).toEqual([[1, 2, 3]]) 13 | }) 14 | it('size exact half', () => { 15 | expect(chunkArray([1, 2, 3, 4], 2)).toEqual([ 16 | [1, 2], 17 | [3, 4] 18 | ]) 19 | }) 20 | it('evenly distributes', () => { 21 | const chunked = chunkArray([...Array(100).keys()], 40) 22 | 23 | expect(chunked).toEqual([ 24 | [...Array(34).keys()], 25 | [...Array(34).keys()].map(i => i + 34), 26 | [...Array(32).keys()].map(i => i + 68) 27 | ]) 28 | 29 | expect(chunked[0][0]).toEqual(0) 30 | expect(chunked[2][31]).toEqual(99) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/utils/chunkArray.ts: -------------------------------------------------------------------------------- 1 | // chunks array into chunks of maximum size 2 | // evenly distributes items among the chunks 3 | export default function chunkArray(items: T[], maxChunkSize: number): T[][] { 4 | if (maxChunkSize < 1) throw new Error('maxChunkSize must be gte 1') 5 | if (items.length <= maxChunkSize) return [items] 6 | 7 | const numChunks: number = Math.ceil(items.length / maxChunkSize) 8 | const chunkSize = Math.ceil(items.length / numChunks) 9 | 10 | return [...Array(numChunks).keys()].map(ix => items.slice(ix * chunkSize, ix * chunkSize + chunkSize)) 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/contenthashToUri.test.skip.ts: -------------------------------------------------------------------------------- 1 | import contenthashToUri, { hexToUint8Array } from './contenthashToUri' 2 | 3 | // this test is skipped for now because importing CID results in 4 | // TypeError: TextDecoder is not a constructor 5 | 6 | describe('#contenthashToUri', () => { 7 | it('1inch.tokens.eth contenthash', () => { 8 | expect(contenthashToUri('0xe3010170122013e051d1cfff20606de36845d4fe28deb9861a319a5bc8596fa4e610e8803918')).toEqual( 9 | 'ipfs://QmPgEqyV3m8SB52BS2j2mJpu9zGprhj2BGCHtRiiw2fdM1' 10 | ) 11 | }) 12 | it('uniswap.eth contenthash', () => { 13 | expect(contenthashToUri('0xe5010170000f6170702e756e69737761702e6f7267')).toEqual('ipns://app.uniswap.org') 14 | }) 15 | }) 16 | 17 | describe('#hexToUint8Array', () => { 18 | it('common case', () => { 19 | expect(hexToUint8Array('0x010203fdfeff')).toEqual(new Uint8Array([1, 2, 3, 253, 254, 255])) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/utils/contenthashToUri.ts: -------------------------------------------------------------------------------- 1 | import CID from 'cids' 2 | import { getCodec, rmPrefix } from 'multicodec' 3 | import { decode, toB58String } from 'multihashes' 4 | 5 | export function hexToUint8Array(hex: string): Uint8Array { 6 | hex = hex.startsWith('0x') ? hex.substr(2) : hex 7 | if (hex.length % 2 !== 0) throw new Error('hex must have length that is multiple of 2') 8 | const arr = new Uint8Array(hex.length / 2) 9 | for (let i = 0; i < arr.length; i++) { 10 | arr[i] = parseInt(hex.substr(i * 2, 2), 16) 11 | } 12 | return arr 13 | } 14 | 15 | const UTF_8_DECODER = new TextDecoder() 16 | 17 | /** 18 | * Returns the URI representation of the content hash for supported codecs 19 | * @param contenthash to decode 20 | */ 21 | export default function contenthashToUri(contenthash: string): string { 22 | const buff = hexToUint8Array(contenthash) 23 | const codec = getCodec(buff as Buffer) // the typing is wrong for @types/multicodec 24 | switch (codec) { 25 | case 'ipfs-ns': { 26 | const data = rmPrefix(buff as Buffer) 27 | const cid = new CID(data) 28 | return `ipfs://${toB58String(cid.multihash)}` 29 | } 30 | case 'ipns-ns': { 31 | const data = rmPrefix(buff as Buffer) 32 | const cid = new CID(data) 33 | const multihash = decode(cid.multihash) 34 | if (multihash.name === 'identity') { 35 | return `ipns://${UTF_8_DECODER.decode(multihash.digest).trim()}` 36 | } else { 37 | return `ipns://${toB58String(cid.multihash)}` 38 | } 39 | } 40 | default: 41 | throw new Error(`Unrecognized codec: ${codec}`) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/currencyId.ts: -------------------------------------------------------------------------------- 1 | import { Currency, CETH, Token } from '@trisolaris/sdk' 2 | 3 | export function currencyId(currency: Currency): string { 4 | if (currency === CETH) return 'ETH' 5 | if (currency instanceof Token) return currency.address 6 | throw new Error('invalid currency') 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/extractCountry.ts: -------------------------------------------------------------------------------- 1 | // assumes user's country based on locale 2 | export default function getCountry(): string { 3 | const { languages } = navigator 4 | for (let i = 0; i < languages.length; i++) { 5 | const [, countryCode] = languages[i].split('-') 6 | if (countryCode) { 7 | return countryCode.toUpperCase() 8 | } 9 | } 10 | return 'US' 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/getLibrary.ts: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from '@ethersproject/providers' 2 | import { ChainId } from '@trisolaris/sdk' 3 | 4 | const DEFAULT_POLLING_INTERVAL = 15_000 // 15 seconds 5 | 6 | const NETWORK_POLLING_INTERVALS: { [chainId: number]: number } = { 7 | [ChainId.AURORA]: 1_000, // 1 second 8 | [ChainId.AVALANCHE]: 1_000, // 1 second 9 | [ChainId.POLYGON]: DEFAULT_POLLING_INTERVAL 10 | } 11 | 12 | export default function getLibrary(provider: any): Web3Provider { 13 | const library = new Web3Provider( 14 | provider, 15 | typeof provider.chainId === 'number' 16 | ? provider.chainId 17 | : typeof provider.chainId === 'string' 18 | ? parseInt(provider.chainId) 19 | : 'any' 20 | ) 21 | 22 | library.pollingInterval = DEFAULT_POLLING_INTERVAL 23 | 24 | library.detectNetwork().then(network => { 25 | const networkPollingInterval = NETWORK_POLLING_INTERVALS[network.chainId] 26 | if (networkPollingInterval) { 27 | console.debug('Setting polling interval', networkPollingInterval) 28 | library.pollingInterval = networkPollingInterval 29 | } 30 | }) 31 | 32 | return library 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/getPairRenderOrder.ts: -------------------------------------------------------------------------------- 1 | import { Token, CETH } from '@trisolaris/sdk' 2 | import { TRI } from '../constants/tokens' 3 | import { unwrappedToken } from './wrappedCurrency' 4 | 5 | export default function getPairRenderOrder(token0: Token, token1: Token) { 6 | const currency0 = unwrappedToken(token0) 7 | const currency1 = unwrappedToken(token1) 8 | 9 | const token0IsFirst = { 10 | currency0, 11 | currency1, 12 | token0, 13 | token1 14 | } 15 | const token1IsFirst = { 16 | currency0: currency1, 17 | currency1: currency0, 18 | token0: token1, 19 | token1: token0 20 | } 21 | 22 | // If pair has CETH, put CETH second 23 | // If TRI is the other token, it'll be first 24 | if (currency0 === CETH || currency1 === CETH) { 25 | return currency0 === CETH ? token1IsFirst : token0IsFirst 26 | } 27 | 28 | // If pair has TRI, put TRI first 29 | return token0.equals(TRI[token0.chainId]) ? token0IsFirst : token1IsFirst 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/isZero.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the string value is zero in hex 3 | * @param hexNumberString 4 | */ 5 | export default function isZero(hexNumberString: string) { 6 | return /^0x0*$/.test(hexNumberString) 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/listVersionLabel.ts: -------------------------------------------------------------------------------- 1 | import { Version } from '@pangolindex/token-lists' 2 | 3 | export default function listVersionLabel(version: Version): string { 4 | return `v${version.major}.${version.minor}.${version.patch}` 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/maxAmountSpend.ts: -------------------------------------------------------------------------------- 1 | import { CurrencyAmount, CETH, JSBI } from '@trisolaris/sdk' 2 | import { BIG_INT_ZERO as MIN_ETH } from '../constants' 3 | 4 | /** 5 | * Given some token amount, return the max that can be spent of it 6 | * @param currencyAmount to return max of 7 | */ 8 | export function maxAmountSpend(currencyAmount?: CurrencyAmount): CurrencyAmount | undefined { 9 | if (!currencyAmount) return undefined 10 | if (currencyAmount.currency === CETH) { 11 | if (JSBI.greaterThan(currencyAmount.raw, MIN_ETH)) { 12 | return CurrencyAmount.ether(JSBI.subtract(currencyAmount.raw, MIN_ETH)) 13 | } else { 14 | return CurrencyAmount.ether(MIN_ETH) 15 | } 16 | } 17 | return currencyAmount 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/parseENSAddress.test.ts: -------------------------------------------------------------------------------- 1 | import { parseENSAddress } from './parseENSAddress' 2 | 3 | describe('parseENSAddress', () => { 4 | it('test cases', () => { 5 | expect(parseENSAddress('hello.eth')).toEqual({ ensName: 'hello.eth', ensPath: undefined }) 6 | expect(parseENSAddress('hello.eth/')).toEqual({ ensName: 'hello.eth', ensPath: '/' }) 7 | expect(parseENSAddress('hello.world.eth/')).toEqual({ ensName: 'hello.world.eth', ensPath: '/' }) 8 | expect(parseENSAddress('hello.world.eth/abcdef')).toEqual({ ensName: 'hello.world.eth', ensPath: '/abcdef' }) 9 | expect(parseENSAddress('abso.lutely')).toEqual(undefined) 10 | expect(parseENSAddress('abso.lutely.eth')).toEqual({ ensName: 'abso.lutely.eth', ensPath: undefined }) 11 | expect(parseENSAddress('eth')).toEqual(undefined) 12 | expect(parseENSAddress('eth/hello-world')).toEqual(undefined) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/utils/parseENSAddress.ts: -------------------------------------------------------------------------------- 1 | const ENS_NAME_REGEX = /^(([a-zA-Z0-9]+\.)+)eth(\/.*)?$/ 2 | 3 | export function parseENSAddress(ensAddress: string): { ensName: string; ensPath: string | undefined } | undefined { 4 | const match = ensAddress.match(ENS_NAME_REGEX) 5 | if (!match) return undefined 6 | return { ensName: `${match[1].toLowerCase()}eth`, ensPath: match[3] } 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/pools.ts: -------------------------------------------------------------------------------- 1 | import { Token, TokenAmount, CETH } from '@trisolaris/sdk' 2 | import { TRI } from '../constants/tokens' 3 | import { unwrappedToken } from './wrappedCurrency' 4 | 5 | export const getPairRenderOrder = (tokens: Token[]) => { 6 | const currencyMap = tokens.map(token => unwrappedToken(token)) 7 | if (tokens.length > 2) { 8 | return { 9 | currencies: currencyMap, 10 | tokens 11 | } 12 | } 13 | 14 | const currency0 = currencyMap[0] 15 | const currency1 = currencyMap[1] 16 | const token0 = tokens[0] 17 | const token1 = tokens[1] 18 | 19 | const token0IsFirst = { 20 | currencies: [currency0, currency1], 21 | tokens: [token0, token1] 22 | } 23 | const token1IsFirst = { 24 | currencies: [currency1, currency0], 25 | tokens: [token1, token0] 26 | } 27 | 28 | // If pair has CETH, put CETH second 29 | // If TRI is the other token, it'll be first 30 | if (currency0 === CETH || currency1 === CETH) { 31 | return currency0 === CETH ? token1IsFirst : token0IsFirst 32 | } 33 | 34 | // If pair has TRI, put TRI first 35 | return token0.equals(TRI[token0.chainId]) ? token0IsFirst : token1IsFirst 36 | } 37 | 38 | export const isTokenAmountPositive = (stakedAmount: TokenAmount | null | undefined) => { 39 | return Boolean(stakedAmount?.greaterThan('0') ?? false) 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/stableSwap.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, CurrencyAmount, JSBI, Token, TokenAmount } from '@trisolaris/sdk' 2 | import { USDC_E } from '../constants/tokens' 3 | 4 | export function getLpTokenUsdEstimate(lpTokenPriceUSDC: TokenAmount, amount: CurrencyAmount) { 5 | return new TokenAmount( 6 | USDC_E[ChainId.AURORA], 7 | JSBI.divide(JSBI.multiply(lpTokenPriceUSDC.raw, amount.raw), JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))) 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/uriToHttp.test.ts: -------------------------------------------------------------------------------- 1 | import uriToHttp from './uriToHttp' 2 | 3 | describe('uriToHttp', () => { 4 | it('returns .eth.link for ens names', () => { 5 | expect(uriToHttp('t2crtokens.eth')).toEqual([]) 6 | }) 7 | it('returns https first for http', () => { 8 | expect(uriToHttp('http://test.com')).toEqual(['https://test.com', 'http://test.com']) 9 | }) 10 | it('returns https for https', () => { 11 | expect(uriToHttp('https://test.com')).toEqual(['https://test.com']) 12 | }) 13 | it('returns ipfs gateways for ipfs:// urls', () => { 14 | expect(uriToHttp('ipfs://QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ')).toEqual([ 15 | 'https://cloudflare-ipfs.com/ipfs/QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ/', 16 | 'https://ipfs.io/ipfs/QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ/' 17 | ]) 18 | }) 19 | it('returns ipns gateways for ipns:// urls', () => { 20 | expect(uriToHttp('ipns://app.uniswap.org')).toEqual([ 21 | 'https://cloudflare-ipfs.com/ipns/app.uniswap.org/', 22 | 'https://ipfs.io/ipns/app.uniswap.org/' 23 | ]) 24 | }) 25 | it('returns empty array for invalid scheme', () => { 26 | expect(uriToHttp('blah:test')).toEqual([]) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/utils/uriToHttp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Given a URI that may be ipfs, ipns, http, or https protocol, return the fetch-able http(s) URLs for the same content 3 | * @param uri to convert to fetch-able http url 4 | */ 5 | export default function uriToHttp(uri: string): string[] { 6 | const protocol = uri.split(':')[0].toLowerCase() 7 | switch (protocol) { 8 | case 'https': 9 | return [uri] 10 | case 'http': 11 | return ['https' + uri.substr(4), uri] 12 | case 'ipfs': 13 | const hash = uri.match(/^ipfs:(\/\/)?(.*)$/i)?.[2] 14 | return [`https://cloudflare-ipfs.com/ipfs/${hash}/`, `https://ipfs.io/ipfs/${hash}/`] 15 | case 'ipns': 16 | const name = uri.match(/^ipns:(\/\/)?(.*)$/i)?.[2] 17 | return [`https://cloudflare-ipfs.com/ipns/${name}/`, `https://ipfs.io/ipns/${name}/`] 18 | default: 19 | return [] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/useDebouncedChangeHandler.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef, useState } from 'react' 2 | 3 | /** 4 | * Easy way to debounce the handling of a rapidly changing value, e.g. a changing slider input 5 | * @param value value that is rapidly changing 6 | * @param onChange change handler that should receive the debounced updates to the value 7 | * @param debouncedMs how long we should wait for changes to be applied 8 | */ 9 | export default function useDebouncedChangeHandler( 10 | value: T, 11 | onChange: (newValue: T) => void, 12 | debouncedMs = 100 13 | ): [T, (value: T) => void] { 14 | const [inner, setInner] = useState(() => value) 15 | const timer = useRef>() 16 | 17 | const onChangeInner = useCallback( 18 | (newValue: T) => { 19 | setInner(newValue) 20 | if (timer.current) { 21 | clearTimeout(timer.current) 22 | } 23 | timer.current = setTimeout(() => { 24 | onChange(newValue) 25 | timer.current = undefined 26 | }, debouncedMs) 27 | }, 28 | [debouncedMs, onChange] 29 | ) 30 | 31 | useEffect(() => { 32 | if (timer.current) { 33 | clearTimeout(timer.current) 34 | timer.current = undefined 35 | } 36 | setInner(value) 37 | }, [value]) 38 | 39 | return [inner, onChangeInner] 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/wallet.ts: -------------------------------------------------------------------------------- 1 | import { getTokenLogoURL } from '../components/CurrencyLogo' 2 | 3 | export const registerToken = async ( 4 | tokenAddress: string, 5 | tokenSymbol: string, 6 | tokenDecimals: number, 7 | tokenLogoUrl?: string 8 | ) => { 9 | const doesImageExist = (url: string): Promise => 10 | new Promise(resolve => { 11 | const img = new Image() 12 | 13 | img.src = url 14 | img.onload = () => resolve(true) 15 | img.onerror = () => resolve(false) 16 | }) 17 | 18 | const logoUrls = getTokenLogoURL(tokenAddress) 19 | 20 | const availableLogos = await Promise.all(logoUrls.map(async url => ({ url: url, exists: await doesImageExist(url) }))) 21 | const src: string | undefined = availableLogos.find(logo => logo.exists)?.url 22 | const image = tokenLogoUrl ?? src 23 | 24 | try { 25 | const wasAdded = await (window as any).ethereum.request({ 26 | method: 'wallet_watchAsset', 27 | params: { 28 | type: 'ERC20', 29 | options: { 30 | address: tokenAddress, 31 | symbol: tokenSymbol, 32 | decimals: tokenDecimals, 33 | image 34 | } 35 | } 36 | }) 37 | 38 | if (wasAdded) { 39 | return wasAdded 40 | } 41 | } catch (error) { 42 | console.log(error) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/wrappedCurrency.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, Currency, CurrencyAmount, CETH, Token, TokenAmount, WETH } from '@trisolaris/sdk' 2 | 3 | export function wrappedCurrency(currency: Currency | undefined, chainId: ChainId | undefined): Token | undefined { 4 | return chainId && currency === CETH ? WETH[chainId] : currency instanceof Token ? currency : undefined 5 | } 6 | 7 | export function wrappedCurrencyAmount( 8 | currencyAmount: CurrencyAmount | undefined, 9 | chainId: ChainId | undefined 10 | ): TokenAmount | undefined { 11 | const token = currencyAmount && chainId ? wrappedCurrency(currencyAmount.currency, chainId) : undefined 12 | return token && currencyAmount ? new TokenAmount(token, currencyAmount.raw) : undefined 13 | } 14 | 15 | export function unwrappedToken(token: Token): Currency { 16 | if (token.equals(WETH[token.chainId])) return CETH 17 | return token 18 | } 19 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trisolaris-labs/interface/9c029b1e413a97664368a2b3d0511412b30778c4/test -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "strict": true, 16 | "alwaysStrict": true, 17 | "strictNullChecks": true, 18 | "noUnusedLocals": false, 19 | "noFallthroughCasesInSwitch": true, 20 | "noImplicitAny": true, 21 | "noImplicitThis": true, 22 | "noImplicitReturns": false, 23 | "moduleResolution": "node", 24 | "resolveJsonModule": true, 25 | "isolatedModules": true, 26 | "jsx": "react-jsx", 27 | "downlevelIteration": true, 28 | "allowSyntheticDefaultImports": true, 29 | "types": [ 30 | "react-spring", 31 | "jest" 32 | ] 33 | }, 34 | "exclude": [ 35 | "node_modules", 36 | "cypress" 37 | ], 38 | "include": [ 39 | "./src/**/*.ts", 40 | "./src/**/*.tsx", 41 | "src/components/Confetti/index.js" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "pangolin-app-temp" 2 | type = "javascript" 3 | route = "app.pangolin.exchange/*" 4 | 5 | [site] 6 | bucket = "./build" --------------------------------------------------------------------------------