├── .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 |
--------------------------------------------------------------------------------
/public/images/flags/jp.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/arrow-down-grey.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/circle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/dropdown-blue.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/dropdown.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/dropup-blue.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/plus-grey.svg:
--------------------------------------------------------------------------------
1 |
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 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/question.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/spinner.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
4 |
--------------------------------------------------------------------------------
/src/assets/svg/lightcircle.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
{
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 |
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"
--------------------------------------------------------------------------------