├── .env-sample
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── _assets
└── 1.gif
├── client
├── .env-sample
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .yarnrc
├── LICENSE
├── package.json
├── public
│ ├── 451.html
│ ├── favicon.png
│ ├── images
│ │ ├── 192x192_App_Icon.png
│ │ └── 512x512_App_Icon.png
│ ├── index.html
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es-AR.json
│ │ ├── es-US.json
│ │ ├── it-IT.json
│ │ ├── iw.json
│ │ ├── ro.json
│ │ ├── ru.json
│ │ ├── vi.json
│ │ ├── zh-CN.json
│ │ └── zh-TW.json
│ └── manifest.json
├── src
│ ├── abis
│ │ ├── argent-wallet-detector.json
│ │ ├── eip_2612.json
│ │ ├── ens-public-resolver.json
│ │ ├── ens-registrar.json
│ │ ├── erc20.json
│ │ ├── erc20_bytes32.json
│ │ ├── migrator.json
│ │ ├── multicall.json
│ │ ├── multicall2.json
│ │ ├── staking-rewards.ts
│ │ ├── unisocks.json
│ │ └── weth.json
│ ├── assets
│ │ ├── images
│ │ │ ├── arrow-down-blue.svg
│ │ │ ├── arrow-down-grey.svg
│ │ │ ├── arrow-right-white.png
│ │ │ ├── arrow-right.svg
│ │ │ ├── big_unicorn.png
│ │ │ ├── blue-loader.svg
│ │ │ ├── circle-grey.svg
│ │ │ ├── circle.svg
│ │ │ ├── coinbaseWalletIcon.svg
│ │ │ ├── dropdown-blue.svg
│ │ │ ├── dropdown.svg
│ │ │ ├── dropup-blue.svg
│ │ │ ├── ethereum-logo.png
│ │ │ ├── fortmaticIcon.png
│ │ │ ├── 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
│ │ │ ├── token-list-logo.png
│ │ │ ├── token-list
│ │ │ │ ├── lists-dark.png
│ │ │ │ └── lists-light.png
│ │ │ ├── token-logo.png
│ │ │ ├── tokenlistsgrouped.png
│ │ │ ├── trustWallet.png
│ │ │ ├── walletConnectIcon.svg
│ │ │ ├── x.svg
│ │ │ └── xl_uni.png
│ │ └── svg
│ │ │ ├── 1inch-logo.svg
│ │ │ ├── QR.svg
│ │ │ ├── lightcircle.svg
│ │ │ ├── logo.svg
│ │ │ ├── logo_pink.svg
│ │ │ ├── logo_white.svg
│ │ │ ├── sushi-logo.svg
│ │ │ ├── tokenlist.svg
│ │ │ ├── uni-logo.svg
│ │ │ ├── wordmark.svg
│ │ │ ├── wordmark_pink.svg
│ │ │ └── wordmark_white.svg
│ ├── components
│ │ ├── AccountDetails
│ │ │ ├── Copy.tsx
│ │ │ ├── Transaction.tsx
│ │ │ └── index.tsx
│ │ ├── AddressInputPanel
│ │ │ └── index.tsx
│ │ ├── Badge
│ │ │ ├── Badge.stories.tsx
│ │ │ ├── RangeBadge.tsx
│ │ │ └── index.tsx
│ │ ├── Blocklist
│ │ │ └── index.tsx
│ │ ├── Button
│ │ │ ├── Button.stories.tsx
│ │ │ └── index.tsx
│ │ ├── Card
│ │ │ └── index.tsx
│ │ ├── Column
│ │ │ └── index.tsx
│ │ ├── Common
│ │ │ └── styled.ts
│ │ ├── Confetti
│ │ │ └── index.tsx
│ │ ├── CurrencyInputPanel
│ │ │ ├── FiatValue.tsx
│ │ │ └── index.tsx
│ │ ├── CurrencyLogo
│ │ │ └── index.tsx
│ │ ├── CustomAlertModal
│ │ │ └── index.tsx
│ │ ├── DoubleLogo
│ │ │ ├── DoubleCurrencyLogo.stories.tsx
│ │ │ └── index.tsx
│ │ ├── ErrorBoundary
│ │ │ └── index.tsx
│ │ ├── FeeSelector
│ │ │ └── index.tsx
│ │ ├── FormattedCurrencyAmount
│ │ │ └── index.tsx
│ │ ├── Header
│ │ │ ├── FooterPooling.tsx
│ │ │ ├── Menu.tsx
│ │ │ ├── Polling.tsx
│ │ │ ├── URLWarning.tsx
│ │ │ └── index.tsx
│ │ ├── HoverInlineText
│ │ │ └── index.tsx
│ │ ├── Identicon
│ │ │ └── index.tsx
│ │ ├── InputStepCounter
│ │ │ └── InputStepCounter.tsx
│ │ ├── LineChart
│ │ │ ├── LineChart.stories.tsx
│ │ │ ├── data.ts
│ │ │ └── index.tsx
│ │ ├── ListLogo
│ │ │ └── index.tsx
│ │ ├── Loader
│ │ │ └── index.tsx
│ │ ├── Logo
│ │ │ └── index.tsx
│ │ ├── Modal
│ │ │ └── index.tsx
│ │ ├── ModalViews
│ │ │ └── index.tsx
│ │ ├── NumericalInput
│ │ │ └── index.tsx
│ │ ├── Popover
│ │ │ └── index.tsx
│ │ ├── Popups
│ │ │ ├── ListUpdatePopup.tsx
│ │ │ ├── PopupItem.tsx
│ │ │ ├── TransactionPopup.tsx
│ │ │ └── index.tsx
│ │ ├── ProgressSteps
│ │ │ └── index.tsx
│ │ ├── QuestionHelper
│ │ │ └── index.tsx
│ │ ├── RangeSelector
│ │ │ └── index.tsx
│ │ ├── RateToggle
│ │ │ └── index.tsx
│ │ ├── Row
│ │ │ └── index.tsx
│ │ ├── SearchModal
│ │ │ ├── CommonBases.tsx
│ │ │ ├── CurrencyList.tsx
│ │ │ ├── CurrencySearch.tsx
│ │ │ ├── CurrencySearchModal.tsx
│ │ │ ├── CustomAlert.tsx
│ │ │ ├── ImportList.tsx
│ │ │ ├── ImportRow.tsx
│ │ │ ├── ImportToken.tsx
│ │ │ ├── Manage.tsx
│ │ │ ├── ManageLists.tsx
│ │ │ ├── ManageTokens.tsx
│ │ │ ├── SortButton.tsx
│ │ │ ├── filtering.ts
│ │ │ ├── sorting.ts
│ │ │ └── styleds.tsx
│ │ ├── Settings
│ │ │ └── index.tsx
│ │ ├── Slider
│ │ │ └── index.tsx
│ │ ├── ThemeColorPalette
│ │ │ ├── ThemeColorPalette.stories.tsx
│ │ │ └── index.tsx
│ │ ├── Toggle
│ │ │ ├── ListToggle.tsx
│ │ │ ├── MultiToggle.stories.tsx
│ │ │ ├── MultiToggle.tsx
│ │ │ └── index.tsx
│ │ ├── TokenWarningModal
│ │ │ └── index.tsx
│ │ ├── Tooltip
│ │ │ └── index.tsx
│ │ ├── TransactionConfirmationModal
│ │ │ └── index.tsx
│ │ ├── TransactionSettings
│ │ │ └── index.tsx
│ │ ├── Trojan
│ │ │ ├── CurrentBlock.tsx
│ │ │ ├── Emoji.tsx
│ │ │ ├── FadeIn.tsx
│ │ │ ├── NextBlock.tsx
│ │ │ ├── PriceItem.tsx
│ │ │ ├── TokenItem.tsx
│ │ │ ├── ToolsBlock.tsx
│ │ │ ├── ToolsCurrency.tsx
│ │ │ ├── TradeDetai1Inch.tsx
│ │ │ ├── TradeDetail.tsx
│ │ │ ├── TradeDetailMulticall.tsx
│ │ │ ├── TradeItem.tsx
│ │ │ ├── TradeItem1Inch.tsx
│ │ │ ├── TradeItemMulticall.tsx
│ │ │ └── TradePricePrediction.tsx
│ │ ├── WalletModal
│ │ │ ├── Option.tsx
│ │ │ ├── PendingView.tsx
│ │ │ └── index.tsx
│ │ ├── Web3ReactManager
│ │ │ └── index.tsx
│ │ ├── Web3Status
│ │ │ └── index.tsx
│ │ ├── analytics
│ │ │ └── GoogleAnalyticsReporter.tsx
│ │ └── swap
│ │ │ ├── AdvancedSwapDetails.tsx
│ │ │ ├── AdvancedSwapDetailsDropdown.tsx
│ │ │ ├── BetterTradeLink.tsx
│ │ │ ├── ConfirmSwapModal.tsx
│ │ │ ├── FormattedPriceImpact.tsx
│ │ │ ├── SwapHeader.tsx
│ │ │ ├── SwapModalFooter.tsx
│ │ │ ├── SwapModalHeader.tsx
│ │ │ ├── SwapRoute.tsx
│ │ │ ├── TradePrice.tsx
│ │ │ ├── UnsupportedCurrencyFooter.tsx
│ │ │ ├── confirmPriceImpactWithoutFee.ts
│ │ │ └── styleds.tsx
│ ├── connectors
│ │ ├── NetworkConnector.ts
│ │ └── index.ts
│ ├── constants
│ │ ├── addresses.ts
│ │ ├── governance.ts
│ │ ├── lists.ts
│ │ ├── misc.ts
│ │ ├── proposals
│ │ │ ├── index.ts
│ │ │ └── uniswap_grants_proposal_description.ts
│ │ ├── routing.ts
│ │ ├── tokenLists
│ │ │ └── uniswap-v2-unsupported.tokenlist.json
│ │ ├── tokens.ts
│ │ └── wallet.ts
│ ├── hooks
│ │ ├── Tokens.ts
│ │ ├── useAddTokenToMetamask.ts
│ │ ├── useAllCurrencyCombinations.ts
│ │ ├── useAllV3Routes.ts
│ │ ├── useAllV3Ticks.ts
│ │ ├── useApeModeQueryParamReader.ts
│ │ ├── useApproveCallback.ts
│ │ ├── useBestV3Trade.ts
│ │ ├── useColor.ts
│ │ ├── useContract.ts
│ │ ├── useCopyClipboard.ts
│ │ ├── useCurrentBlockTimestamp.ts
│ │ ├── useDebounce.ts
│ │ ├── useDebouncedChangeHandler.tsx
│ │ ├── useDerivedPositionInfo.ts
│ │ ├── useENS.ts
│ │ ├── useENSAddress.ts
│ │ ├── useENSContentHash.ts
│ │ ├── useENSName.ts
│ │ ├── useERC20Permit.ts
│ │ ├── useFetchListCallback.ts
│ │ ├── useHttpLocations.ts
│ │ ├── useInterval.ts
│ │ ├── useIsArgentWallet.ts
│ │ ├── useIsSwapUnsupported.ts
│ │ ├── useIsWindowVisible.ts
│ │ ├── useLast.ts
│ │ ├── useOnClickOutside.tsx
│ │ ├── useParsedQueryString.ts
│ │ ├── usePools.ts
│ │ ├── usePositionTokenURI.ts
│ │ ├── usePrevious.ts
│ │ ├── useSocksBalance.ts
│ │ ├── useSwapCallback.ts
│ │ ├── useSwapSlippageTolerance.ts
│ │ ├── useTheme.ts
│ │ ├── useTickToPrice.ts
│ │ ├── useToggle.ts
│ │ ├── useToggledVersion.ts
│ │ ├── useTokenAllowance.ts
│ │ ├── useTotalSupply.ts
│ │ ├── useTransactionDeadline.ts
│ │ ├── useUSDCPrice.ts
│ │ ├── useV2Pairs.ts
│ │ ├── useV2Trade.ts
│ │ ├── useV3PositionFees.ts
│ │ ├── useV3Positions.ts
│ │ ├── useV3SwapPools.ts
│ │ ├── useWindowSize.ts
│ │ ├── useWrapCallback.ts
│ │ └── web3.ts
│ ├── i18n.ts
│ ├── index.tsx
│ ├── pages
│ │ ├── App.tsx
│ │ ├── AppBody.tsx
│ │ ├── Explorer
│ │ │ ├── index.tsx
│ │ │ └── redirects.tsx
│ │ ├── Maintenance
│ │ │ └── index.tsx
│ │ ├── context
│ │ │ └── socket.ts
│ │ └── styled.tsx
│ ├── react-app-env.d.ts
│ ├── service-worker.ts
│ ├── serviceWorkerRegistration.ts
│ ├── state
│ │ ├── application
│ │ │ ├── actions.ts
│ │ │ ├── hooks.ts
│ │ │ ├── reducer.ts
│ │ │ └── updater.ts
│ │ ├── freshTokens
│ │ │ ├── actions.ts
│ │ │ ├── hooks.ts
│ │ │ └── reducer.ts
│ │ ├── global
│ │ │ └── actions.ts
│ │ ├── index.ts
│ │ ├── lists
│ │ │ ├── actions.ts
│ │ │ ├── hooks.ts
│ │ │ ├── reducer.ts
│ │ │ ├── updater.ts
│ │ │ └── wrappedTokenInfo.ts
│ │ ├── multicall
│ │ │ ├── actions.ts
│ │ │ ├── hooks.ts
│ │ │ ├── reducer.ts
│ │ │ └── updater.tsx
│ │ ├── transactions
│ │ │ ├── actions.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── reducer.ts
│ │ │ └── updater.tsx
│ │ ├── trojanBlocks
│ │ │ ├── actions.ts
│ │ │ ├── hooks.ts
│ │ │ └── reducer.ts
│ │ ├── trojanTxs
│ │ │ ├── actions.ts
│ │ │ ├── hooks.tsx
│ │ │ └── reducer.ts
│ │ ├── user
│ │ │ ├── actions.ts
│ │ │ ├── hooks.tsx
│ │ │ ├── reducer.ts
│ │ │ └── updater.tsx
│ │ └── wallet
│ │ │ └── hooks.ts
│ ├── theme
│ │ ├── DarkModeQueryParamReader.tsx
│ │ ├── components.tsx
│ │ ├── index.tsx
│ │ ├── rebass.d.ts
│ │ └── styled.d.ts
│ ├── types
│ │ ├── position.d.ts
│ │ └── trojan
│ │ │ └── tx-model.d.ts
│ └── utils
│ │ ├── calculateGasMargin.ts
│ │ ├── calculateSlippageAmount.ts
│ │ ├── chunkArray.ts
│ │ ├── computeFiatValuePriceImpact.tsx
│ │ ├── computeUniCirculation.ts
│ │ ├── constructSameAddressMap.ts
│ │ ├── contenthashToUri.ts
│ │ ├── currencyId.ts
│ │ ├── formatTokenAmount.ts
│ │ ├── getExplorerLink.ts
│ │ ├── getLibrary.ts
│ │ ├── getTickToPrice.ts
│ │ ├── getTokenList.ts
│ │ ├── getTradeVersion.ts
│ │ ├── getUserAgent.ts
│ │ ├── i18n.ts
│ │ ├── index.ts
│ │ ├── isTradeBetter.ts
│ │ ├── isZero.ts
│ │ ├── listSort.ts
│ │ ├── listVersionLabel.ts
│ │ ├── maxAmountSpend.ts
│ │ ├── parseENSAddress.ts
│ │ ├── prices.ts
│ │ ├── react-app-env.d.ts
│ │ ├── resolveENSContentHash.ts
│ │ ├── retry.ts
│ │ ├── service-worker.ts
│ │ ├── serviceWorkerRegistration.ts
│ │ ├── supportedChainId.ts
│ │ ├── uriToHttp.ts
│ │ └── wrappedCurrency.ts
├── tsconfig.json
└── yarn.lock
├── package.json
├── src
├── abis
│ ├── I1Inchv2.json
│ ├── I1Inchv3.json
│ └── Multicall2.json
├── fix-db.ts
├── listener-blocks.ts
├── listener-confirmed.ts
├── listener-mempool.ts
├── models
│ ├── BlockSchema.ts
│ ├── HashSchema.ts
│ ├── PoolsSchema.ts
│ ├── TokenSchema.ts
│ ├── TransactionSchema.ts
│ ├── WhalesSchema.ts
│ └── index.ts
├── swapsDecoders
│ └── _uni_sushi
│ │ ├── _decoders
│ │ └── getMempoolData.ts
│ │ ├── _v2
│ │ └── handleSwap.ts
│ │ ├── _v3
│ │ ├── getV3InternalSwap.ts
│ │ ├── handleMultiSwap.ts
│ │ └── handleSwap.ts
│ │ └── pending.ts
├── utils
│ ├── _websocket
│ │ └── utils.ts
│ ├── configs
│ │ └── utils.ts
│ ├── dev-utils
│ │ ├── drop-transactions.ts
│ │ ├── reset-blocks.ts
│ │ ├── reset-pools.ts
│ │ ├── reset-tokens.ts
│ │ └── reset-transactions.ts
│ ├── initServer
│ │ ├── create-indexes.ts
│ │ ├── init-pools.ts
│ │ ├── init-tokens.ts
│ │ └── init-whales.ts
│ ├── mongo
│ │ ├── config.ts
│ │ ├── saveBlock.ts
│ │ ├── saveConfirmed.ts
│ │ ├── savePending.ts
│ │ ├── savePools.ts
│ │ ├── saveToken.ts
│ │ └── saveWhale.ts
│ └── web3
│ │ ├── abis-interfaces.ts
│ │ ├── checkTxs.ts
│ │ ├── getBlocks.ts
│ │ ├── getContractData.ts
│ │ ├── getTokens.ts
│ │ ├── getTransactions.ts
│ │ ├── utils.ts
│ │ └── wsProvider.ts
└── websocket-server.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /dist
11 | /client_build
12 |
13 | # misc
14 | .DS_Store
15 | .vscode
16 | /.vscode
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
22 | #environment
23 | .env
24 | .env.*
25 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "none",
4 | "singleQuote": true,
5 | "printWidth": 160
6 | }
7 |
--------------------------------------------------------------------------------
/_assets/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/_assets/1.gif
--------------------------------------------------------------------------------
/client/.env-sample:
--------------------------------------------------------------------------------
1 | REACT_APP_CHAIN_ID=1
2 | REACT_APP_GOOGLE_ANALYTICS_ID=UA-999999999999999-1
3 | REACT_APP_NETWORK_URL=https://mainnet.infura.io/v3/999999999999999
4 | REACT_APP_WS_URL_TROJAN=http://localhost:3001/
5 | REACT_APP_APP_IS_OFFLINE=false
6 | SKIP_PREFLIGHT_CHECK=true
7 |
--------------------------------------------------------------------------------
/client/.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 | "settings": {
13 | "react": {
14 | "version": "detect"
15 | }
16 | },
17 | "extends": [
18 | "plugin:react/recommended",
19 | "plugin:@typescript-eslint/recommended",
20 | "plugin:react-hooks/recommended",
21 | "prettier/@typescript-eslint",
22 | "plugin:prettier/recommended"
23 | ],
24 | "rules": {
25 | "@typescript-eslint/explicit-function-return-type": "off",
26 | "prettier/prettier": "error",
27 | "@typescript-eslint/no-explicit-any": "off",
28 | "@typescript-eslint/ban-ts-comment": "off",
29 | "@typescript-eslint/ban-ts-ignore": "off",
30 | "@typescript-eslint/explicit-module-boundary-types": "off"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # generated contract types
4 | /src/types/v3
5 | /src/abis/types
6 |
7 | # dependencies
8 | /node_modules
9 |
10 | # testing
11 | /coverage
12 |
13 | # production
14 | /build
15 |
16 | # misc
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | /.netlify
24 |
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
29 | notes.txt
30 | .idea/
31 |
32 | .vscode/
33 |
34 | package-lock.json
35 |
36 | cypress/videos
37 | cypress/screenshots
38 | cypress/fixtures/example.json
39 |
40 | .env
41 | ./.env
--------------------------------------------------------------------------------
/client/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": false,
4 | "printWidth": 120
5 | }
6 |
--------------------------------------------------------------------------------
/client/.yarnrc:
--------------------------------------------------------------------------------
1 | ignore-scripts true
2 |
--------------------------------------------------------------------------------
/client/public/451.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Unavailable For Legal Reasons
6 |
7 |
8 | Unavailable For Legal Reasons
9 |
10 |
11 |
--------------------------------------------------------------------------------
/client/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/public/favicon.png
--------------------------------------------------------------------------------
/client/public/images/192x192_App_Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/public/images/192x192_App_Icon.png
--------------------------------------------------------------------------------
/client/public/images/512x512_App_Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/public/images/512x512_App_Icon.png
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
25 |
26 | Trojan.Finance
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background_color": "#fff",
3 | "display": "standalone",
4 | "homepage_url": "https://app.trojan.finance",
5 | "icons": [
6 | {
7 | "src": "./images/192x192_App_Icon.png",
8 | "sizes": "192x192",
9 | "type": "image/png",
10 | "purpose": "any maskable"
11 | },
12 | {
13 | "src": "./images/512x512_App_Icon.png",
14 | "sizes": "512x512",
15 | "type": "image/png",
16 | "purpose": "any maskable"
17 | }
18 | ],
19 | "orientation": "portrait",
20 | "name": "Trojan Finance",
21 | "short_name": "Trojan Finance",
22 | "start_url": ".",
23 | "theme_color": "#ff007a"
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/abis/eip_2612.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "constant": true,
4 | "inputs": [{ "name": "owner", "type": "address" }],
5 | "name": "nonces",
6 | "outputs": [{ "name": "", "type": "uint256" }],
7 | "payable": false,
8 | "stateMutability": "view",
9 | "type": "function"
10 | },
11 | {
12 | "constant": true,
13 | "inputs": [],
14 | "name": "DOMAIN_SEPARATOR",
15 | "outputs": [{ "name": "", "type": "bytes32" }],
16 | "payable": false,
17 | "stateMutability": "view",
18 | "type": "function"
19 | }
20 | ]
21 |
--------------------------------------------------------------------------------
/client/src/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 |
--------------------------------------------------------------------------------
/client/src/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 |
--------------------------------------------------------------------------------
/client/src/abis/staking-rewards.ts:
--------------------------------------------------------------------------------
1 | import { Interface } from "@ethersproject/abi"
2 | import { abi as STAKING_REWARDS_ABI } from "@uniswap/liquidity-staker/build/StakingRewards.json"
3 |
4 | const STAKING_REWARDS_INTERFACE = new Interface(STAKING_REWARDS_ABI)
5 |
6 | export { STAKING_REWARDS_INTERFACE }
7 |
--------------------------------------------------------------------------------
/client/src/assets/images/arrow-down-blue.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/arrow-down-grey.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/arrow-right-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/arrow-right-white.png
--------------------------------------------------------------------------------
/client/src/assets/images/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/big_unicorn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/big_unicorn.png
--------------------------------------------------------------------------------
/client/src/assets/images/blue-loader.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/circle-grey.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/circle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/dropdown-blue.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/dropdown.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/dropup-blue.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/ethereum-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/ethereum-logo.png
--------------------------------------------------------------------------------
/client/src/assets/images/fortmaticIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/fortmaticIcon.png
--------------------------------------------------------------------------------
/client/src/assets/images/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/assets/images/menu.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/client/src/assets/images/metamask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/metamask.png
--------------------------------------------------------------------------------
/client/src/assets/images/noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/noise.png
--------------------------------------------------------------------------------
/client/src/assets/images/plus-blue.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/plus-grey.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/images/portisIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/portisIcon.png
--------------------------------------------------------------------------------
/client/src/assets/images/question-mark.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/client/src/assets/images/question.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/client/src/assets/images/spinner.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/assets/images/token-list-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/token-list-logo.png
--------------------------------------------------------------------------------
/client/src/assets/images/token-list/lists-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/token-list/lists-dark.png
--------------------------------------------------------------------------------
/client/src/assets/images/token-list/lists-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/token-list/lists-light.png
--------------------------------------------------------------------------------
/client/src/assets/images/token-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/token-logo.png
--------------------------------------------------------------------------------
/client/src/assets/images/tokenlistsgrouped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/tokenlistsgrouped.png
--------------------------------------------------------------------------------
/client/src/assets/images/trustWallet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/trustWallet.png
--------------------------------------------------------------------------------
/client/src/assets/images/x.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/assets/images/xl_uni.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/we-commit/in-dex-explorer/3870a3a43e77318ceec7e54a50bd8346f13902d2/client/src/assets/images/xl_uni.png
--------------------------------------------------------------------------------
/client/src/assets/svg/QR.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/client/src/assets/svg/lightcircle.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/components/AccountDetails/Copy.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components/macro"
3 | import useCopyClipboard from "../../hooks/useCopyClipboard"
4 |
5 | import { LinkStyledButton } from "../../theme"
6 | import { CheckCircle, Copy } from "react-feather"
7 |
8 | const CopyIcon = styled(LinkStyledButton)`
9 | position: relative;
10 | border: none;
11 | background-color: ${({ theme }) => theme.bg2};
12 | margin: 0.5rem;
13 | padding: 0.15rem 0.5rem;
14 | border-radius: 0.5rem;
15 |
16 | :hover,
17 | :focus {
18 | cursor: pointer;
19 | outline: none;
20 | background-color: ${({ theme }) => theme.bg4};
21 | }
22 |
23 | svg {
24 | margin-top: 2px;
25 | }
26 | > * {
27 | stroke: ${({ theme }) => theme.blue1};
28 | }
29 | `
30 | const TransactionStatusText = styled.span`
31 | margin-left: 0.25rem;
32 | font-size: 0.825rem;
33 | ${({ theme }) => theme.flexRowNoWrap};
34 | align-items: center;
35 | `
36 |
37 | export default function CopyHelper(props: { toCopy: string; children?: React.ReactNode }) {
38 | const [isCopied, setCopied] = useCopyClipboard()
39 |
40 | return (
41 | setCopied(props.toCopy)}>
42 | {isCopied ? (
43 |
44 |
45 | Copied
46 |
47 | ) : (
48 |
49 |
50 |
51 | )}
52 | {isCopied ? "" : props.children}
53 |
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/client/src/components/Badge/Badge.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Story } from "@storybook/react/types-6-0"
2 | import React, { PropsWithChildren } from "react"
3 | import Component, { BadgeProps, BadgeVariant } from "./index"
4 |
5 | export default {
6 | title: "Badge",
7 | argTypes: {
8 | variant: {
9 | name: "variant",
10 | type: { name: "string", require: false },
11 | defaultValue: BadgeVariant.DEFAULT,
12 | description: "badge variant",
13 | control: {
14 | type: "select",
15 | options: Object.values(BadgeVariant),
16 | },
17 | },
18 | },
19 | args: {
20 | children: "🦄 UNISWAP 🦄",
21 | },
22 | }
23 |
24 | const Template: Story> = (args) => {args.children}
25 |
26 | export const DefaultBadge = Template.bind({})
27 | DefaultBadge.args = {
28 | variant: BadgeVariant.DEFAULT,
29 | }
30 |
31 | export const WarningBadge = Template.bind({})
32 | WarningBadge.args = {
33 | variant: BadgeVariant.WARNING,
34 | }
35 |
36 | export const NegativeBadge = Template.bind({})
37 | NegativeBadge.args = {
38 | variant: BadgeVariant.NEGATIVE,
39 | }
40 |
41 | export const PositiveBadge = Template.bind({})
42 | PositiveBadge.args = {
43 | variant: BadgeVariant.POSITIVE,
44 | }
45 |
--------------------------------------------------------------------------------
/client/src/components/Blocklist/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode, useMemo } from "react"
2 | import { useActiveWeb3React } from "../../hooks/web3"
3 |
4 | // SDN OFAC addresses
5 | const BLOCKED_ADDRESSES: string[] = [
6 | "0x7F367cC41522cE07553e823bf3be79A889DEbe1B",
7 | "0xd882cFc20F52f2599D84b8e8D58C7FB62cfE344b",
8 | "0x901bb9583b24D97e995513C6778dc6888AB6870e",
9 | "0xA7e5d5A720f06526557c513402f2e6B5fA20b008",
10 | "0x8576aCC5C05D6Ce88f4e49bf65BdF0C62F91353C",
11 | ]
12 |
13 | export default function Blocklist({ children }: { children: ReactNode }) {
14 | const { account } = useActiveWeb3React()
15 | const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account])
16 | if (blocked) {
17 | return Blocked address
18 | }
19 | return <>{children}>
20 | }
21 |
--------------------------------------------------------------------------------
/client/src/components/Column/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components/macro"
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 |
--------------------------------------------------------------------------------
/client/src/components/Common/styled.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import { AutoColumn } from "../Column"
3 |
4 | import uImage from "../../assets/images/big_unicorn.png"
5 | import xlUnicorn from "../../assets/images/xl_uni.png"
6 | import noise from "../../assets/images/noise.png"
7 |
8 | export const TextBox = styled.div`
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | padding: 4px 12px;
13 | border: 1px solid rgba(255, 255, 255, 0.4);
14 | border-radius: 20px;
15 | width: fit-content;
16 | justify-self: flex-end;
17 | `
18 |
19 | export const DataCard = styled(AutoColumn)<{ disabled?: boolean }>`
20 | border-radius: 12px;
21 | width: 100%;
22 | position: relative;
23 | overflow: hidden;
24 | `
25 |
26 | export const CardBGImage = styled.span<{ desaturate?: boolean }>`
27 | background: url(${uImage});
28 | width: 1000px;
29 | height: 600px;
30 | position: absolute;
31 | border-radius: 12px;
32 | opacity: 0.4;
33 | top: -100px;
34 | left: -100px;
35 | transform: rotate(-15deg);
36 | user-select: none;
37 |
38 | ${({ desaturate }) => desaturate && `filter: saturate(0)`}
39 | `
40 |
41 | export const CardBGImageSmaller = styled.span<{ desaturate?: boolean }>`
42 | background: url(${xlUnicorn});
43 | width: 1200px;
44 | height: 1200px;
45 | position: absolute;
46 | border-radius: 12px;
47 | top: -300px;
48 | left: -300px;
49 | opacity: 0.4;
50 | user-select: none;
51 |
52 | ${({ desaturate }) => desaturate && `filter: saturate(0)`}
53 | `
54 |
55 | export const CardNoise = styled.span`
56 | background: url(${noise});
57 | background-size: cover;
58 | mix-blend-mode: overlay;
59 | border-radius: 12px;
60 | width: 100%;
61 | height: 100%;
62 | opacity: 0.15;
63 | position: absolute;
64 | top: 0;
65 | left: 0;
66 | user-select: none;
67 | `
68 |
69 | export const CardSection = styled(AutoColumn)<{ disabled?: boolean }>`
70 | padding: 1rem;
71 | z-index: 1;
72 | opacity: ${({ disabled }) => disabled && "0.4"};
73 | `
74 |
75 | export const Break = styled.div`
76 | width: 100%;
77 | background-color: rgba(255, 255, 255, 0.2);
78 | height: 1px;
79 | `
80 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/components/CurrencyInputPanel/FiatValue.tsx:
--------------------------------------------------------------------------------
1 | import { Currency, CurrencyAmount, Percent } from "@uniswap/sdk-core"
2 | import React, { useMemo } from "react"
3 | import useTheme from "../../hooks/useTheme"
4 | import { TYPE } from "../../theme"
5 | import { warningSeverity } from "../../utils/prices"
6 | import HoverInlineText from "components/HoverInlineText"
7 |
8 | export function FiatValue({
9 | fiatValue,
10 | priceImpact,
11 | }: {
12 | fiatValue: CurrencyAmount | null | undefined
13 | priceImpact?: Percent
14 | }) {
15 | const theme = useTheme()
16 | const priceImpactColor = useMemo(() => {
17 | if (!priceImpact) return undefined
18 | if (priceImpact.lessThan("0")) return theme.green1
19 | const severity = warningSeverity(priceImpact)
20 | if (severity < 1) return theme.text4
21 | if (severity < 3) return theme.yellow1
22 | return theme.red1
23 | }, [priceImpact, theme.green1, theme.red1, theme.text4, theme.yellow1])
24 |
25 | return (
26 |
27 | {fiatValue ? "~" : ""}$
28 | {" "}
29 | {priceImpact ? (
30 | ({priceImpact.multiply(-1).toSignificant(3)}%)
31 | ) : null}
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/client/src/components/CurrencyLogo/index.tsx:
--------------------------------------------------------------------------------
1 | import { ChainId, Currency } from "@uniswap/sdk-core"
2 | import React, { useMemo } from "react"
3 | import styled from "styled-components/macro"
4 | import EthereumLogo from "../../assets/images/ethereum-logo.png"
5 | import useHttpLocations from "../../hooks/useHttpLocations"
6 | import { WrappedTokenInfo } from "../../state/lists/wrappedTokenInfo"
7 | import Logo from "../Logo"
8 |
9 | export const getTokenLogoURL = (address: string) =>
10 | `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
11 |
12 | const StyledEthereumLogo = styled.img<{ size: string }>`
13 | width: ${({ size }) => size};
14 | height: ${({ size }) => size};
15 | box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
16 | border-radius: 24px;
17 | `
18 |
19 | const StyledLogo = styled(Logo)<{ size: string }>`
20 | width: ${({ size }) => size};
21 | height: ${({ size }) => size};
22 | border-radius: ${({ size }) => size};
23 | box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
24 | background-color: ${({ theme }) => theme.white};
25 | `
26 |
27 | export default function CurrencyLogo({
28 | currency,
29 | size = "24px",
30 | style,
31 | ...rest
32 | }: {
33 | currency?: Currency
34 | size?: string
35 | style?: React.CSSProperties
36 | }) {
37 | const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)
38 |
39 | const srcs: string[] = useMemo(() => {
40 | if (!currency || currency.isEther) return []
41 |
42 | if (currency.isToken) {
43 | const defaultUrls = currency.chainId === ChainId.MAINNET ? [getTokenLogoURL(currency.address)] : []
44 | if (currency instanceof WrappedTokenInfo) {
45 | return [...uriLocations, ...defaultUrls]
46 | }
47 | return defaultUrls
48 | }
49 | return []
50 | }, [currency, uriLocations])
51 |
52 | if (currency?.isEther) {
53 | return
54 | }
55 |
56 | return
57 | }
58 |
--------------------------------------------------------------------------------
/client/src/components/CustomAlertModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Modal from "../Modal"
3 | import { CustomAlert } from "components/SearchModal/CustomAlert"
4 |
5 | export default function CustomAlertModal({
6 | isOpen,
7 | onConfirm,
8 | alertHeader,
9 | alertText,
10 | alertTitle,
11 | alertBody,
12 | links,
13 | buttonText,
14 | }: {
15 | alertHeader: string
16 | alertText: string
17 | alertTitle: string
18 | alertBody: string
19 | buttonText: string
20 | links: Array
21 | isOpen: boolean
22 | onConfirm: () => void
23 | }) {
24 | return (
25 |
26 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/components/DoubleLogo/DoubleCurrencyLogo.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Story } from "@storybook/react/types-6-0"
2 | import React from "react"
3 | import { DAI, WBTC } from "../../constants/tokens"
4 | import Component, { DoubleCurrencyLogoProps } from "./index"
5 |
6 | export default {
7 | title: "DoubleCurrencyLogo",
8 | decorators: [],
9 | }
10 |
11 | const Template: Story = (args) =>
12 |
13 | export const DoubleCurrencyLogo = Template.bind({})
14 | DoubleCurrencyLogo.args = {
15 | currency0: DAI,
16 | currency1: WBTC,
17 | size: 220,
18 | }
19 |
--------------------------------------------------------------------------------
/client/src/components/DoubleLogo/index.tsx:
--------------------------------------------------------------------------------
1 | import { Currency } from "@uniswap/sdk-core"
2 | import React from "react"
3 | import styled from "styled-components/macro"
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-left: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + "px"};
11 | `
12 |
13 | export 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 |
--------------------------------------------------------------------------------
/client/src/components/FormattedCurrencyAmount/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Currency, CurrencyAmount, WETH9, ChainId } from "@uniswap/sdk-core"
3 |
4 | export default function FormattedCurrencyAmount({
5 | currencyAmount,
6 | significantDigits = 4,
7 | id,
8 | }: {
9 | currencyAmount: CurrencyAmount
10 | significantDigits?: number
11 | id: string
12 | }) {
13 | if (id === WETH9[ChainId.MAINNET].address) return <>{currencyAmount.toSignificant(significantDigits)}>
14 |
15 | return <>{numberWithCommas(Number(currencyAmount.toSignificant(significantDigits)))}>
16 | }
17 |
18 | function numberWithCommas(x: number) {
19 | return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
20 | }
21 |
--------------------------------------------------------------------------------
/client/src/components/Header/FooterPooling.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import { TYPE, ExternalLink } from "../../theme"
4 | import { Zap } from "react-feather"
5 |
6 | const StyledPolling = styled.div`
7 | position: fixed;
8 | display: flex;
9 | justify-content: center;
10 | bottom: 0rem;
11 | right: 0rem;
12 | padding: 1rem;
13 | color: white;
14 | transition: opacity 0.25s ease;
15 | color: ${({ theme }) => theme.green1};
16 | :hover {
17 | opacity: 1;
18 | }
19 |
20 | ${({ theme }) => theme.mediaWidth.upToLarge`
21 | display: none;
22 | `}
23 | `
24 | export default function FooterPooling() {
25 | const footerStyles = { marginLeft: "0.5rem", marginRight: "0.5rem", fontSize: "11px" }
26 |
27 | return (
28 |
29 |
30 |
31 | Powered by Blocknative
32 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/client/src/components/Header/Polling.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import { TYPE, ExternalLink } from "../../theme"
4 | import { RowFixed } from "../Row"
5 | import useScrollPosition from "@react-hook/window-scroll"
6 |
7 | const HeaderRowT = styled(RowFixed)`
8 | position: fixed;
9 | bottom: 1rem;
10 | left: 50%;
11 | transform: translate(-50%);
12 | z-index: 100;
13 |
14 | ${({ theme }) => theme.mediaWidth.upToMedium`
15 | display: none;
16 | `};
17 | `
18 |
19 | export default function Polling() {
20 | const footerStyles = { marginLeft: "0.5rem", marginRight: "0.5rem" }
21 | const scrollY = useScrollPosition()
22 | if (scrollY <= 45)
23 | return (
24 |
25 |
26 | Discord
27 |
28 |
29 | Twitter
30 |
31 |
32 | Github
33 |
34 |
35 | )
36 | else {
37 | return <>>
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/client/src/components/Header/URLWarning.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | import { AlertTriangle, X } from "react-feather"
5 | import { useURLWarningToggle, useURLWarningVisible } from "../../state/user/hooks"
6 | import { isMobile } from "react-device-detect"
7 |
8 | const PhishAlert = styled.div<{ isActive: any }>`
9 | width: 100%;
10 | padding: 6px 6px;
11 | background-color: ${({ theme }) => theme.text1};
12 | color: ${({ theme }) => theme.bg1};
13 | font-size: 11px;
14 | justify-content: space-between;
15 | align-items: center;
16 | display: ${({ isActive }) => (isActive ? "flex" : "none")};
17 | `
18 |
19 | export const StyledClose = styled(X)`
20 | :hover {
21 | cursor: pointer;
22 | }
23 | `
24 |
25 | export default function URLWarning() {
26 | const toggleURLWarning = useURLWarningToggle()
27 | const showURLWarning = useURLWarningVisible()
28 |
29 | return isMobile ? (
30 |
31 |
32 |
Make sure the URL is
33 |
app.trojan.finance
34 |
35 |
36 |
37 | ) : window.location.hostname === "app.trojan.finance" ? (
38 |
39 |
40 |
Always make sure the URL is
41 |
app.trojan.finance
- bookmark
42 | it to be safe.
43 |
44 |
45 |
46 | ) : null
47 | }
48 |
--------------------------------------------------------------------------------
/client/src/components/HoverInlineText/index.tsx:
--------------------------------------------------------------------------------
1 | import Tooltip from "components/Tooltip"
2 | import React, { useState } from "react"
3 | import styled from "styled-components/macro"
4 |
5 | const TextWrapper = styled.span<{ margin: boolean; link?: boolean; fontSize?: string; adjustSize?: boolean }>`
6 | cursor: auto;
7 | margin-left: ${({ margin }) => margin && "4px"};
8 | color: ${({ theme, link }) => (link ? theme.blue1 : theme.text1)};
9 | font-size: ${({ fontSize }) => fontSize ?? "inherit"};
10 |
11 | @media screen and (max-width: 600px) {
12 | font-size: ${({ adjustSize }) => adjustSize && "12px"};
13 | }
14 | `
15 |
16 | const HoverInlineText = ({
17 | text,
18 | maxCharacters = 20,
19 | margin = false,
20 | adjustSize = false,
21 | fontSize,
22 | link,
23 | ...rest
24 | }: {
25 | text: string
26 | maxCharacters?: number
27 | margin?: boolean
28 | adjustSize?: boolean
29 | fontSize?: string
30 | link?: boolean
31 | }) => {
32 | const [showHover, setShowHover] = useState(false)
33 |
34 | if (!text) {
35 | return
36 | }
37 |
38 | if (text.length > maxCharacters) {
39 | return (
40 |
41 | setShowHover(true)}
43 | onMouseLeave={() => setShowHover(false)}
44 | margin={margin}
45 | adjustSize={adjustSize}
46 | link={link}
47 | fontSize={fontSize}
48 | {...rest}
49 | >
50 | {" " + text.slice(0, maxCharacters - 1) + "..."}
51 |
52 |
53 | )
54 | }
55 |
56 | return (
57 |
58 | {text}
59 |
60 | )
61 | }
62 |
63 | export default HoverInlineText
64 |
--------------------------------------------------------------------------------
/client/src/components/Identicon/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react"
2 |
3 | import styled from "styled-components/macro"
4 |
5 | import { useActiveWeb3React } from "../../hooks/web3"
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 |
--------------------------------------------------------------------------------
/client/src/components/ListLogo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components/macro"
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 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/components/Logo/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { Slash } from "react-feather"
3 | import { ImageProps } from "rebass"
4 | import useTheme from "../../hooks/useTheme"
5 |
6 | const BAD_SRCS: { [tokenAddress: string]: true } = {}
7 |
8 | export interface LogoProps extends Pick {
9 | srcs: string[]
10 | }
11 |
12 | /**
13 | * Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert
14 | */
15 | export default function Logo({ srcs, alt, style, ...rest }: LogoProps) {
16 | const [, refresh] = useState(0)
17 |
18 | const theme = useTheme()
19 |
20 | const src: string | undefined = srcs.find((src) => !BAD_SRCS[src])
21 |
22 | if (src) {
23 | return (
24 |
{
30 | if (src) BAD_SRCS[src] = true
31 | refresh((i) => i + 1)
32 | }}
33 | />
34 | )
35 | }
36 |
37 | return
38 | }
39 |
--------------------------------------------------------------------------------
/client/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/web3"
5 | import { TYPE } from "../../theme"
6 | import { ExternalLink } from "../../theme/components"
7 | import { ExplorerDataType, getExplorerLink } from "../../utils/getExplorerLink"
8 | import { AutoColumn } from "../Column"
9 | import { AutoRow } from "../Row"
10 |
11 | const RowNoFlex = styled(AutoRow)`
12 | flex-wrap: nowrap;
13 | `
14 |
15 | export default function TransactionPopup({
16 | hash,
17 | success,
18 | summary,
19 | }: {
20 | hash: string
21 | success?: boolean
22 | summary?: string
23 | }) {
24 | const { chainId } = useActiveWeb3React()
25 |
26 | const theme = useContext(ThemeContext)
27 |
28 | return (
29 |
30 |
33 |
34 | {summary ?? "Hash: " + hash.slice(0, 8) + "..." + hash.slice(58, 65)}
35 | {chainId && (
36 |
37 | View on Etherscan
38 |
39 | )}
40 |
41 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/client/src/components/RateToggle/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Currency } from "@uniswap/sdk-core"
3 | import { ToggleElement, ToggleWrapper } from "components/Toggle/MultiToggle"
4 | import { useActiveWeb3React } from "hooks/web3"
5 | import { wrappedCurrency } from "utils/wrappedCurrency"
6 |
7 | // the order of displayed base currencies from left to right is always in sort order
8 | // currencyA is treated as the preferred base currency
9 | export default function RateToggle({
10 | currencyA,
11 | currencyB,
12 | handleRateToggle,
13 | }: {
14 | currencyA: Currency
15 | currencyB: Currency
16 | handleRateToggle: () => void
17 | }) {
18 | const { chainId } = useActiveWeb3React()
19 |
20 | const tokenA = wrappedCurrency(currencyA, chainId)
21 | const tokenB = wrappedCurrency(currencyB, chainId)
22 |
23 | const isSorted = tokenA && tokenB && tokenA.sortsBefore(tokenB)
24 |
25 | return tokenA && tokenB ? (
26 |
27 |
28 |
29 | {isSorted ? currencyA.symbol + " price " : currencyB.symbol + " price "}
30 |
31 |
32 | {isSorted ? currencyB.symbol + " price " : currencyA.symbol + " price "}
33 |
34 |
35 |
36 | ) : null
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/components/Row/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components/macro"
2 | import { Box } from "rebass/styled-components"
3 |
4 | const Row = styled(Box)<{
5 | width?: string
6 | align?: string
7 | justify?: string
8 | padding?: string
9 | border?: string
10 | borderRadius?: string
11 | }>`
12 | width: ${({ width }) => width ?? "100%"};
13 | display: flex;
14 | padding: 0;
15 | align-items: ${({ align }) => align ?? "center"};
16 | justify-content: ${({ justify }) => justify ?? "flex-start"};
17 | padding: ${({ padding }) => padding};
18 | border: ${({ border }) => border};
19 | border-radius: ${({ borderRadius }) => borderRadius};
20 | `
21 |
22 | export const RowBetween = styled(Row)`
23 | justify-content: space-between;
24 | `
25 |
26 | export const RowFlat = styled.div`
27 | display: flex;
28 | align-items: flex-end;
29 | `
30 |
31 | export const AutoRow = styled(Row)<{ gap?: string; justify?: string }>`
32 | flex-wrap: wrap;
33 | margin: ${({ gap }) => gap && `-${gap}`};
34 | justify-content: ${({ justify }) => justify && justify};
35 |
36 | & > * {
37 | margin: ${({ gap }) => gap} !important;
38 | }
39 | `
40 |
41 | export const RowFixed = styled(Row)<{ gap?: string; justify?: string }>`
42 | width: fit-content;
43 | margin: ${({ gap }) => gap && `-${gap}`};
44 | `
45 |
46 | export default Row
47 |
--------------------------------------------------------------------------------
/client/src/components/SearchModal/SortButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Text } from "rebass"
3 | import styled from "styled-components/macro"
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 |
--------------------------------------------------------------------------------
/client/src/components/SearchModal/sorting.ts:
--------------------------------------------------------------------------------
1 | import { Token, CurrencyAmount, Currency } from "@uniswap/sdk-core"
2 | import { useMemo } from "react"
3 | import { useAllTokenBalances } from "../../state/wallet/hooks"
4 |
5 | // compare two token amounts with highest one coming first
6 | function balanceComparator(balanceA?: CurrencyAmount, balanceB?: CurrencyAmount) {
7 | if (balanceA && balanceB) {
8 | return balanceA.greaterThan(balanceB) ? -1 : balanceA.equalTo(balanceB) ? 0 : 1
9 | } else if (balanceA && balanceA.greaterThan("0")) {
10 | return -1
11 | } else if (balanceB && balanceB.greaterThan("0")) {
12 | return 1
13 | }
14 | return 0
15 | }
16 |
17 | function getTokenComparator(balances: {
18 | [tokenAddress: string]: CurrencyAmount | undefined
19 | }): (tokenA: Token, tokenB: Token) => number {
20 | return function sortTokens(tokenA: Token, tokenB: Token): number {
21 | // -1 = a is first
22 | // 1 = b is first
23 |
24 | // sort by balances
25 | const balanceA = balances[tokenA.address]
26 | const balanceB = balances[tokenB.address]
27 |
28 | const balanceComp = balanceComparator(balanceA, balanceB)
29 | if (balanceComp !== 0) return balanceComp
30 |
31 | if (tokenA.symbol && tokenB.symbol) {
32 | // sort by symbol
33 | return tokenA.symbol.toLowerCase() < tokenB.symbol.toLowerCase() ? -1 : 1
34 | } else {
35 | return tokenA.symbol ? -1 : tokenB.symbol ? -1 : 0
36 | }
37 | }
38 | }
39 |
40 | export function useTokenComparator(inverted: boolean): (tokenA: Token, tokenB: Token) => number {
41 | const balances = useAllTokenBalances()
42 | const comparator = useMemo(() => getTokenComparator(balances ?? {}), [balances])
43 | return useMemo(() => {
44 | if (inverted) {
45 | return (tokenA: Token, tokenB: Token) => comparator(tokenA, tokenB) * -1
46 | } else {
47 | return comparator
48 | }
49 | }, [inverted, comparator])
50 | }
51 |
--------------------------------------------------------------------------------
/client/src/components/ThemeColorPalette/ThemeColorPalette.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Story } from "@storybook/react/types-6-0"
2 | import React from "react"
3 | import Component from "./index"
4 |
5 | export default {
6 | title: "ThemeColorPalette",
7 | }
8 |
9 | const Template: Story = (_args: any, context: Record) => {
10 | const isDarkMode = context.globals.theme === "dark"
11 | return
12 | }
13 |
14 | export const Palette = Template.bind({})
15 |
--------------------------------------------------------------------------------
/client/src/components/ThemeColorPalette/index.tsx:
--------------------------------------------------------------------------------
1 | import { readableColor } from "polished"
2 | import React from "react"
3 | import styled from "styled-components/macro"
4 | import { colors } from "theme"
5 |
6 | const Swatch = styled.div`
7 | align-items: center;
8 | display: flex;
9 | flex-direction: column;
10 | height: 100px;
11 | justify-content: center;
12 | min-width: 200px;
13 | `
14 |
15 | const Wrapper = styled.div`
16 | display: flex;
17 | flex-wrap: wrap;
18 | flex-direction: row;
19 | `
20 |
21 | interface ThemePaletteProps {
22 | isDarkMode: boolean
23 | }
24 |
25 | export default function ThemePalette({ isDarkMode }: ThemePaletteProps) {
26 | const data = colors(isDarkMode)
27 | return (
28 |
29 | {Object.entries(data).map(([key, value]) => (
30 |
31 | {key}
32 | {value}
33 |
34 | ))}
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/components/Toggle/ListToggle.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components/macro"
3 | import { TYPE } from "../../theme"
4 |
5 | const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
6 | border-radius: 20px;
7 | border: none;
8 | background: ${({ theme }) => theme.bg1};
9 | display: flex;
10 | width: fit-content;
11 | cursor: pointer;
12 | outline: none;
13 | padding: 0.4rem 0.4rem;
14 | align-items: center;
15 | `
16 |
17 | const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string }>`
18 | border-radius: 50%;
19 | height: 24px;
20 | width: 24px;
21 | background-color: ${({ isActive, bgColor, theme }) => (isActive ? bgColor : theme.bg4)};
22 | :hover {
23 | opacity: 0.8;
24 | }
25 | `
26 |
27 | const StatusText = styled(TYPE.main)<{ isActive?: boolean }>`
28 | margin: 0 10px;
29 | width: 24px;
30 | color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
31 | `
32 |
33 | export interface ToggleProps {
34 | id?: string
35 | isActive: boolean
36 | bgColor: string
37 | toggle: () => void
38 | }
39 |
40 | export default function ListToggle({ id, isActive, bgColor, toggle }: ToggleProps) {
41 | return (
42 |
43 | {isActive && (
44 |
45 | ON
46 |
47 | )}
48 |
49 | {!isActive && (
50 |
51 | OFF
52 |
53 | )}
54 |
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/client/src/components/Toggle/MultiToggle.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Story } from "@storybook/react/types-6-0"
2 | import styled from "styled-components/macro"
3 | import React, { useState } from "react"
4 | import MultiToggle from "./MultiToggle"
5 |
6 | const wrapperCss = styled.main`
7 | font-size: 2em;
8 | margin: 3em;
9 | max-width: 300px;
10 | `
11 |
12 | export default {
13 | title: "Toggles",
14 | argTypes: {
15 | width: { control: { type: "string" } },
16 | },
17 | decorators: [
18 | (Component: Story) => (
19 |
20 |
21 |
22 | ),
23 | ],
24 | }
25 |
26 | export const MultiToggleExample = () => {
27 | const [active, setActive] = useState(0)
28 |
29 | function doSomethingWithIndex(index: number) {
30 | // here's where youd update state based on index choice
31 | // switch(index){} ...
32 | setActive(index)
33 | }
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/client/src/components/Toggle/MultiToggle.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components/macro"
3 |
4 | export const ToggleWrapper = styled.button<{ width?: string }>`
5 | display: flex;
6 | align-items: center;
7 | width: ${({ width }) => width ?? "100%"};
8 | padding: 1px;
9 | background: ${({ theme }) => theme.bg1};
10 | border-radius: 8px;
11 | border: ${({ theme }) => "1px solid " + theme.bg2};
12 | cursor: pointer;
13 | outline: none;
14 | `
15 |
16 | export const ToggleElement = styled.span<{ isActive?: boolean; fontSize?: string }>`
17 | display: flex;
18 | align-items: center;
19 | width: 100%;
20 | padding: 4px 0.5rem;
21 | border-radius: 6px;
22 | justify-content: center;
23 | height: 100%;
24 | background: ${({ theme, isActive }) => (isActive ? theme.bg0 : "none")};
25 | color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
26 | font-size: ${({ fontSize }) => fontSize ?? "1rem"};
27 | font-weight: 500;
28 | white-space: nowrap;
29 | :hover {
30 | user-select: initial;
31 | color: ${({ theme, isActive }) => (isActive ? theme.text2 : theme.text3)};
32 | }
33 | `
34 |
35 | export const ToggleText = styled.div`
36 | color: ${({ theme }) => theme.text3};
37 | font-size: 12px;
38 | margin-right: 0.5rem;
39 | width: 100%;
40 | white-space: nowrap;
41 | padding: 0 0 0 4px;
42 | `
43 |
44 | export interface ToggleProps {
45 | options: string[]
46 | activeIndex: number
47 | toggle: (index: number) => void
48 | id?: string
49 | width?: string
50 | }
51 |
52 | export default function MultiToggle({ id, options, activeIndex, toggle, width }: ToggleProps) {
53 | return (
54 |
55 | {options.map((option, index) => (
56 | toggle(index)}>
57 | {option}
58 |
59 | ))}
60 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/client/src/components/Toggle/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components/macro"
3 |
4 | const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
5 | padding: 0.25rem 0.5rem;
6 | border-radius: 14px;
7 | background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.text4) : "none")};
8 | color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text3)};
9 | font-size: 1rem;
10 | font-weight: 400;
11 |
12 | padding: 0.35rem 0.6rem;
13 | border-radius: 12px;
14 | background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.text4) : "none")};
15 | color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text2)};
16 | font-size: 1rem;
17 | font-weight: ${({ isOnSwitch }) => (isOnSwitch ? "500" : "400")};
18 | :hover {
19 | user-select: ${({ isOnSwitch }) => (isOnSwitch ? "none" : "initial")};
20 | background: ${({ theme, isActive, isOnSwitch }) =>
21 | isActive ? (isOnSwitch ? theme.primary1 : theme.text3) : "none"};
22 | color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text3)};
23 | }
24 | `
25 |
26 | const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
27 | border-radius: 12px;
28 | border: none;
29 | background: ${({ theme }) => theme.bg3};
30 | display: flex;
31 | width: fit-content;
32 | cursor: pointer;
33 | outline: none;
34 | padding: 0;
35 | `
36 |
37 | export interface ToggleProps {
38 | id?: string
39 | isActive: boolean
40 | toggle: () => void
41 | }
42 |
43 | export default function Toggle({ id, isActive, toggle }: ToggleProps) {
44 | return (
45 |
46 |
47 | On
48 |
49 |
50 | Off
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/client/src/components/TokenWarningModal/index.tsx:
--------------------------------------------------------------------------------
1 | import { Token } from "@uniswap/sdk-core"
2 | import React from "react"
3 | import Modal from "../Modal"
4 | import { ImportToken } from "components/SearchModal/ImportToken"
5 |
6 | export default function TokenWarningModal({
7 | isOpen,
8 | tokens,
9 | onConfirm,
10 | onDismiss,
11 | }: {
12 | isOpen: boolean
13 | tokens: Token[]
14 | onConfirm: () => void
15 | onDismiss: () => void
16 | }) {
17 | return (
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/client/src/components/Tooltip/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from "react"
2 | import styled from "styled-components/macro"
3 | import Popover, { PopoverProps } from "../Popover"
4 |
5 | const TooltipContainer = styled.div`
6 | width: 256px;
7 | padding: 0.6rem 1rem;
8 | font-weight: 400;
9 | word-break: break-word;
10 | `
11 |
12 | interface TooltipProps extends Omit {
13 | text: string
14 | }
15 |
16 | interface TooltipContentProps extends Omit {
17 | content: React.ReactNode
18 | }
19 |
20 | export default function Tooltip({ text, ...rest }: TooltipProps) {
21 | return {text}} {...rest} />
22 | }
23 |
24 | export function TooltipContent({ content, ...rest }: TooltipContentProps) {
25 | return {content}} {...rest} />
26 | }
27 |
28 | export function MouseoverTooltip({ children, ...rest }: Omit) {
29 | const [show, setShow] = useState(false)
30 | const open = useCallback(() => setShow(true), [setShow])
31 | const close = useCallback(() => setShow(false), [setShow])
32 | return (
33 |
34 |
35 | {children}
36 |
37 |
38 | )
39 | }
40 |
41 | export function MouseoverTooltipContent({ content, children, ...rest }: Omit) {
42 | const [show, setShow] = useState(false)
43 | const open = useCallback(() => setShow(true), [setShow])
44 | const close = useCallback(() => setShow(false), [setShow])
45 | return (
46 |
47 |
52 | {children}
53 |
54 |
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/client/src/components/Trojan/Emoji.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export default function Emoji(props?: any) {
4 | return (
5 |
12 | {props.symbol}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/components/Trojan/FadeIn.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react"
2 | import { useSpring } from "react-spring"
3 | import { animated } from "react-spring"
4 |
5 | interface FadeInProps {
6 | text: string
7 | }
8 |
9 | export default function FadeIn({ text }: FadeInProps) {
10 | const [isVisible, setVisible] = useState(true)
11 |
12 | const { opacity } = useSpring({ opacity: isVisible ? 1 : 0 })
13 |
14 | useEffect(() => {
15 | const timer1 = setTimeout(() => setVisible(true), 450)
16 |
17 | return () => {
18 | setVisible(false)
19 | clearTimeout(timer1)
20 | }
21 | }, [text])
22 |
23 | if (text) return {text}
24 | else return <>>
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/components/Trojan/TradePricePrediction.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useContext, useState } from "react"
3 | import { Repeat } from "react-feather"
4 | import { Text } from "rebass"
5 | import { ThemeContext } from "styled-components"
6 | import styled from "styled-components"
7 |
8 | const StyledBalanceMaxMini = styled.button`
9 | height: 22px;
10 | width: 22px;
11 | background-color: ${({ theme }) => theme.bg2};
12 | border: none;
13 | border-radius: 50%;
14 | padding: 0.2rem;
15 | font-size: 0.875rem;
16 | font-weight: 400;
17 | margin-left: 0.4rem;
18 | cursor: pointer;
19 | color: ${({ theme }) => theme.text2};
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 | float: right;
24 |
25 | :hover {
26 | background-color: ${({ theme }) => theme.bg3};
27 | }
28 | :focus {
29 | background-color: ${({ theme }) => theme.bg3};
30 | outline: none;
31 | }
32 | `
33 |
34 | interface TradePricePredictionProps {
35 | formattedPriceFrom: string
36 | formattedPriceTo: string
37 | label: string
38 | labelInverted: string
39 | }
40 |
41 | export default function TradePricePrediction({
42 | formattedPriceFrom,
43 | formattedPriceTo,
44 | label,
45 | labelInverted,
46 | }: TradePricePredictionProps) {
47 | const [showInverted, setShowInverted] = useState(false)
48 |
49 | const theme = useContext(ThemeContext)
50 |
51 | const formattedPrice = showInverted ? formattedPriceFrom : formattedPriceTo
52 | const labelFormatted = showInverted ? label : labelInverted
53 |
54 | return (
55 |
61 | {formattedPrice ?? "-"} {labelFormatted}
62 | setShowInverted(!showInverted)}>
63 |
64 |
65 |
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/components/swap/AdvancedSwapDetailsDropdown.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components/macro"
3 | import { useLastTruthy } from "../../hooks/useLast"
4 | import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from "./AdvancedSwapDetails"
5 |
6 | const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
7 | width: 100%;
8 | border-bottom-left-radius: 20px;
9 | border-bottom-right-radius: 20px;
10 | color: ${({ theme }) => theme.text2};
11 | `
12 |
13 | export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) {
14 | const lastTrade = useLastTruthy(trade)
15 |
16 | return (
17 |
18 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/components/swap/BetterTradeLink.tsx:
--------------------------------------------------------------------------------
1 | import { stringify } from "qs"
2 | import React, { useMemo } from "react"
3 | import { useLocation } from "react-router"
4 | import { Link } from "react-router-dom"
5 |
6 | import useParsedQueryString from "../../hooks/useParsedQueryString"
7 | import { DEFAULT_VERSION, Version } from "../../hooks/useToggledVersion"
8 | import { HideSmall, TYPE, SmallOnly } from "../../theme"
9 | import { ButtonPrimary } from "../Button"
10 | import styled from "styled-components/macro"
11 | import { Zap } from "react-feather"
12 |
13 | const ResponsiveButton = styled(ButtonPrimary)`
14 | width: fit-content;
15 | padding: 0.2rem 0.5rem;
16 | word-break: keep-all;
17 | height: 24px;
18 | margin-left: 0.75rem;
19 | ${({ theme }) => theme.mediaWidth.upToSmall`
20 | padding: 4px;
21 | border-radius: 8px;
22 | `};
23 | `
24 |
25 | export default function BetterTradeLink({
26 | version,
27 | otherTradeNonexistent = false,
28 | }: {
29 | version: Version
30 | otherTradeNonexistent: boolean
31 | }) {
32 | const location = useLocation()
33 | const search = useParsedQueryString()
34 |
35 | const linkDestination = useMemo(() => {
36 | return {
37 | ...location,
38 | search: `?${stringify({
39 | ...search,
40 | use: version !== DEFAULT_VERSION ? version : undefined,
41 | })}`,
42 | }
43 | }, [location, search, version])
44 |
45 | return (
46 |
47 |
48 |
49 |
50 | {otherTradeNonexistent
51 | ? `No liquidity! Click to trade with ${version.toUpperCase()}`
52 | : `Get a better price on ${version.toUpperCase()}`}
53 |
54 |
55 |
56 |
57 | {otherTradeNonexistent
58 | ? `No liquidity! Click to trade with ${version.toUpperCase()}`
59 | : `Better ${version.toUpperCase()} price`}
60 |
61 |
62 |
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/client/src/components/swap/FormattedPriceImpact.tsx:
--------------------------------------------------------------------------------
1 | import { Percent } from "@uniswap/sdk-core"
2 | import React from "react"
3 | import { warningSeverity } from "../../utils/prices"
4 | import { ErrorText, ErrorPill } from "./styleds"
5 |
6 | /**
7 | * Formatted version of price impact text with warning colors
8 | */
9 | export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
10 | return (
11 |
12 | {priceImpact ? `${priceImpact.multiply(-1).toFixed(2)}%` : "-"}
13 |
14 | )
15 | }
16 |
17 | export function SmallFormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
18 | return (
19 |
20 | {priceImpact ? `(${priceImpact.multiply(-1).toFixed(2)}%)` : "-"}
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/client/src/components/swap/SwapHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components/macro"
3 | import SettingsTab from "../Settings"
4 | import { Percent } from "@uniswap/sdk-core"
5 |
6 | import { RowBetween, RowFixed } from "../Row"
7 | import { TYPE } from "../../theme"
8 |
9 | const StyledSwapHeader = styled.div`
10 | padding: 1rem 1.25rem 0.5rem 1.25rem;
11 | width: 100%;
12 | color: ${({ theme }) => theme.text2};
13 | `
14 |
15 | export default function SwapHeader({ allowedSlippage }: { allowedSlippage: Percent }) {
16 | return (
17 |
18 |
19 |
20 |
21 | Swap{" "}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/client/src/components/swap/SwapModalFooter.tsx:
--------------------------------------------------------------------------------
1 | import { Currency, TradeType } from "@uniswap/sdk-core"
2 | import { Trade as V2Trade } from "@uniswap/v2-sdk"
3 | import { Trade as V3Trade } from "@uniswap/v3-sdk"
4 |
5 | import React from "react"
6 | import { Text } from "rebass"
7 | import { ButtonError } from "../Button"
8 | import { AutoRow } from "../Row"
9 | import { SwapCallbackError } from "./styleds"
10 |
11 | export default function SwapModalFooter({
12 | onConfirm,
13 | swapErrorMessage,
14 | disabledConfirm,
15 | }: {
16 | trade: V2Trade | V3Trade
17 | onConfirm: () => void
18 | swapErrorMessage: string | undefined
19 | disabledConfirm: boolean
20 | }) {
21 | return (
22 | <>
23 |
24 |
30 |
31 | Confirm Swap
32 |
33 |
34 |
35 | {swapErrorMessage ? : null}
36 |
37 | >
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/client/src/components/swap/SwapRoute.tsx:
--------------------------------------------------------------------------------
1 | import { Currency, TradeType } from "@uniswap/sdk-core"
2 | import { Trade as V2Trade } from "@uniswap/v2-sdk"
3 | import { Trade as V3Trade, FeeAmount } from "@uniswap/v3-sdk"
4 | import React, { Fragment, memo, useContext } from "react"
5 | import { ChevronRight } from "react-feather"
6 | import { Flex } from "rebass"
7 | import { ThemeContext } from "styled-components"
8 | import { TYPE } from "../../theme"
9 | import { unwrappedToken } from "utils/wrappedCurrency"
10 |
11 | function LabeledArrow({}: { fee: FeeAmount }) {
12 | const theme = useContext(ThemeContext)
13 |
14 | // todo: render the fee in the label
15 | return
16 | }
17 |
18 | export default memo(function SwapRoute({
19 | trade,
20 | }: {
21 | trade: V2Trade | V3Trade
22 | }) {
23 | const tokenPath = trade instanceof V2Trade ? trade.route.path : trade.route.tokenPath
24 | const theme = useContext(ThemeContext)
25 | return (
26 |
27 | {tokenPath.map((token, i, path) => {
28 | const isLastItem: boolean = i === path.length - 1
29 | const currency = unwrappedToken(token)
30 | return (
31 |
32 |
33 |
34 | {currency.symbol}
35 |
36 |
37 | {isLastItem ? null : trade instanceof V2Trade ? (
38 |
39 | ) : (
40 |
41 | )}
42 |
43 | )
44 | })}
45 |
46 | )
47 | })
48 |
--------------------------------------------------------------------------------
/client/src/components/swap/TradePrice.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react"
2 | import { Price, Currency } from "@uniswap/sdk-core"
3 | import { useContext } from "react"
4 | import { Text } from "rebass"
5 | import styled, { ThemeContext } from "styled-components"
6 |
7 | interface TradePriceProps {
8 | price: Price
9 | showInverted: boolean
10 | setShowInverted: (showInverted: boolean) => void
11 | }
12 |
13 | const StyledPriceContainer = styled.button`
14 | display: flex;
15 | justify-content: center;
16 | align-items: center;
17 | padding: 0;
18 | font-size: 0.875rem;
19 | font-weight: 400;
20 | background-color: transparent;
21 | border: none;
22 | height: 24px;
23 | cursor: pointer;
24 | `
25 |
26 | export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) {
27 | const theme = useContext(ThemeContext)
28 |
29 | let formattedPrice: string
30 | try {
31 | formattedPrice = showInverted ? price.toSignificant(4) : price.invert()?.toSignificant(4)
32 | } catch (error) {
33 | formattedPrice = "0"
34 | }
35 |
36 | const label = showInverted ? `${price.quoteCurrency?.symbol}` : `${price.baseCurrency?.symbol} `
37 | const labelInverted = showInverted ? `${price.baseCurrency?.symbol} ` : `${price.quoteCurrency?.symbol}`
38 | const flipPrice = useCallback(() => setShowInverted(!showInverted), [setShowInverted, showInverted])
39 |
40 | const text = `${"1 " + labelInverted + " = " + formattedPrice ?? "-"} ${label}`
41 |
42 | return (
43 |
44 |
45 |
46 | {text}
47 |
48 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/client/src/components/swap/confirmPriceImpactWithoutFee.ts:
--------------------------------------------------------------------------------
1 | import { Percent } from "@uniswap/sdk-core"
2 | import { ALLOWED_PRICE_IMPACT_HIGH, PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN } from "../../constants/misc"
3 |
4 | /**
5 | * Given the price impact, get user confirmation.
6 | *
7 | * @param priceImpactWithoutFee price impact of the trade without the fee.
8 | */
9 | export default function confirmPriceImpactWithoutFee(priceImpactWithoutFee: Percent): boolean {
10 | if (!priceImpactWithoutFee.lessThan(PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN)) {
11 | return (
12 | window.prompt(
13 | `This swap has a price impact of at least ${PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN.toFixed(
14 | 0
15 | )}%. Please type the word "confirm" to continue with this swap.`
16 | ) === "confirm"
17 | )
18 | } else if (!priceImpactWithoutFee.lessThan(ALLOWED_PRICE_IMPACT_HIGH)) {
19 | return window.confirm(
20 | `This swap has a price impact of at least ${ALLOWED_PRICE_IMPACT_HIGH.toFixed(
21 | 0
22 | )}%. Please confirm that you would like to continue with this swap.`
23 | )
24 | }
25 | return true
26 | }
27 |
--------------------------------------------------------------------------------
/client/src/connectors/index.ts:
--------------------------------------------------------------------------------
1 | import { Web3Provider } from "@ethersproject/providers"
2 | import { InjectedConnector } from "@web3-react/injected-connector"
3 |
4 | import { NetworkConnector } from "./NetworkConnector"
5 |
6 | const NETWORK_URL = process.env.REACT_APP_NETWORK_URL
7 |
8 | export const NETWORK_CHAIN_ID: number = parseInt(process.env.REACT_APP_CHAIN_ID ?? "1")
9 |
10 | if (typeof NETWORK_URL === "undefined") {
11 | throw new Error(`REACT_APP_NETWORK_URL must be a defined environment variable`)
12 | }
13 |
14 | export const network = new NetworkConnector({
15 | urls: { [NETWORK_CHAIN_ID]: NETWORK_URL },
16 | })
17 |
18 | let networkLibrary: Web3Provider | undefined
19 | export function getNetworkLibrary(): Web3Provider {
20 | return (networkLibrary = networkLibrary ?? new Web3Provider(network.provider as any))
21 | }
22 |
23 | export const injected = new InjectedConnector({
24 | supportedChainIds: [1, 3, 4, 5, 42],
25 | })
26 |
--------------------------------------------------------------------------------
/client/src/constants/addresses.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from "@uniswap/sdk-core"
2 | import { FACTORY_ADDRESS as V3_FACTORY_ADDRESS } from "@uniswap/v3-sdk"
3 | import { constructSameAddressMap } from "../utils/constructSameAddressMap"
4 |
5 | export const UNI_ADDRESS = constructSameAddressMap("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984")
6 | export const MULTICALL2_ADDRESSES = constructSameAddressMap("0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696")
7 | export const V2_ROUTER_ADDRESS = constructSameAddressMap("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D")
8 | export const GOVERNANCE_ADDRESS = constructSameAddressMap("0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F")
9 | export const TIMELOCK_ADDRESS = constructSameAddressMap("0x1a9C8182C09F50C8318d769245beA52c32BE35BC")
10 | export const MERKLE_DISTRIBUTOR_ADDRESS: { [chainId in ChainId]?: string } = {
11 | [ChainId.MAINNET]: "0x090D4613473dEE047c3f2706764f49E0821D256e",
12 | }
13 | export const ARGENT_WALLET_DETECTOR_ADDRESS: { [chainId in ChainId]?: string } = {
14 | [ChainId.MAINNET]: "0xeca4B0bDBf7c55E9b7925919d03CbF8Dc82537E8",
15 | }
16 | export const V3_CORE_FACTORY_ADDRESSES = constructSameAddressMap(V3_FACTORY_ADDRESS)
17 | export const QUOTER_ADDRESSES = constructSameAddressMap("0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6")
18 | export const TICK_LENS_ADDRESSES = constructSameAddressMap("0xbfd8137f7d1516D3ea5cA83523914859ec47F573")
19 | export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES = constructSameAddressMap(
20 | "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
21 | )
22 | export const ENS_REGISTRAR_ADDRESSES = {
23 | [ChainId.MAINNET]: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
24 | [ChainId.GÖRLI]: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
25 | [ChainId.RINKEBY]: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
26 | [ChainId.ROPSTEN]: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
27 | }
28 | export const SOCKS_CONTROLLER_ADDRESSES = {
29 | [ChainId.MAINNET]: "0x65770b5283117639760beA3F867b69b3697a91dd",
30 | }
31 | export const SWAP_ROUTER_ADDRESSES = constructSameAddressMap("0xE592427A0AEce92De3Edee1F18E0157C05861564")
32 | export const V3_MIGRATOR_ADDRESSES = constructSameAddressMap("0xA5644E29708357803b5A882D272c41cC0dF92B34")
33 |
--------------------------------------------------------------------------------
/client/src/constants/governance.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from "@uniswap/sdk-core"
2 | import { GOVERNANCE_ADDRESS, TIMELOCK_ADDRESS, UNI_ADDRESS } from "./addresses"
3 |
4 | export const COMMON_CONTRACT_NAMES: { [chainId in ChainId]?: { [address: string]: string } } = {
5 | [ChainId.MAINNET]: {
6 | [UNI_ADDRESS[ChainId.MAINNET]]: "UNI",
7 | [GOVERNANCE_ADDRESS[ChainId.MAINNET]]: "Governance",
8 | [TIMELOCK_ADDRESS[ChainId.MAINNET]]: "Timelock",
9 | },
10 | [ChainId.RINKEBY]: {
11 | [UNI_ADDRESS[ChainId.RINKEBY]]: "Rinkeby UNI",
12 | [GOVERNANCE_ADDRESS[ChainId.RINKEBY]]: "Rinkeby Governance",
13 | [TIMELOCK_ADDRESS[ChainId.RINKEBY]]: "Rinkeby Timelock",
14 | },
15 | [ChainId.ROPSTEN]: {
16 | [UNI_ADDRESS[ChainId.ROPSTEN]]: "Ropsten UNI",
17 | [GOVERNANCE_ADDRESS[ChainId.ROPSTEN]]: "Ropsten Governance",
18 | [TIMELOCK_ADDRESS[ChainId.ROPSTEN]]: "Ropsten Timelock",
19 | },
20 | [ChainId.KOVAN]: {
21 | [UNI_ADDRESS[ChainId.KOVAN]]: "Kovan UNI",
22 | [GOVERNANCE_ADDRESS[ChainId.KOVAN]]: "Kovan Governance",
23 | [TIMELOCK_ADDRESS[ChainId.KOVAN]]: "Kovan Timelock",
24 | },
25 | [ChainId.GÖRLI]: {
26 | [UNI_ADDRESS[ChainId.GÖRLI]]: "Goerli UNI",
27 | [GOVERNANCE_ADDRESS[ChainId.GÖRLI]]: "Goerli Governance",
28 | [TIMELOCK_ADDRESS[ChainId.GÖRLI]]: "Goerli Timelock",
29 | },
30 | }
31 |
32 | export const DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS = 13
33 |
34 | // Block time here is slightly higher (~1s) than average in order to avoid ongoing proposals past the displayed time
35 | export const AVERAGE_BLOCK_TIME_IN_SECS: { [chainId in ChainId]?: number } = {
36 | [ChainId.MAINNET]: DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS,
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/constants/lists.ts:
--------------------------------------------------------------------------------
1 | // used to mark unsupported tokens, these are hosted lists of unsupported tokens
2 |
3 | const COMPOUND_LIST = "https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json"
4 | const UMA_LIST = "https://umaproject.org/uma.tokenlist.json"
5 | const AAVE_LIST = "tokenlist.aave.eth"
6 | const SYNTHETIX_LIST = "synths.snx.eth"
7 | const WRAPPED_LIST = "wrapped.tokensoft.eth"
8 | const SET_LIST = "https://raw.githubusercontent.com/SetProtocol/uniswap-tokenlist/main/set.tokenlist.json"
9 | const OPYN_LIST = "https://raw.githubusercontent.com/opynfinance/opyn-tokenlist/master/opyn-v1.tokenlist.json"
10 | const ROLL_LIST = "https://app.tryroll.com/tokens.json"
11 | const COINGECKO_LIST = "https://tokens.coingecko.com/uniswap/all.json"
12 | const CMC_ALL_LIST = "defi.cmc.eth"
13 | const CMC_STABLECOIN = "stablecoin.cmc.eth"
14 | const KLEROS_LIST = "t2crtokens.eth"
15 | const GEMINI_LIST = "https://www.gemini.com/uniswap/manifest.json"
16 | const BA_LIST = "https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json"
17 |
18 | export const UNSUPPORTED_LIST_URLS: string[] = [BA_LIST]
19 |
20 | // lower index == higher priority for token import
21 | export const DEFAULT_LIST_OF_LISTS: string[] = [
22 | COMPOUND_LIST,
23 | AAVE_LIST,
24 | SYNTHETIX_LIST,
25 | UMA_LIST,
26 | WRAPPED_LIST,
27 | SET_LIST,
28 | OPYN_LIST,
29 | ROLL_LIST,
30 | COINGECKO_LIST,
31 | CMC_ALL_LIST,
32 | CMC_STABLECOIN,
33 | KLEROS_LIST,
34 | GEMINI_LIST,
35 | ...UNSUPPORTED_LIST_URLS, // need to load unsupported tokens as well
36 | ]
37 |
38 | // default lists to be 'active' aka searched across
39 | export const DEFAULT_ACTIVE_LIST_URLS: string[] = [GEMINI_LIST]
40 |
--------------------------------------------------------------------------------
/client/src/constants/misc.ts:
--------------------------------------------------------------------------------
1 | import { Percent } from "@uniswap/sdk-core"
2 | import JSBI from "jsbi"
3 |
4 | export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
5 |
6 | export const NetworkContextName = "NETWORK"
7 |
8 | // 30 minutes, denominated in seconds
9 | export const DEFAULT_DEADLINE_FROM_NOW = 60 * 30
10 |
11 | // used for rewards deadlines
12 | export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7)
13 |
14 | export const BIG_INT_ZERO = JSBI.BigInt(0)
15 |
16 | // one basis JSBI.BigInt
17 | export const ONE_BIPS = new Percent(JSBI.BigInt(1), JSBI.BigInt(10))
18 | export const BIPS_BASE = JSBI.BigInt(10000)
19 | // used for warning states
20 | export const ALLOWED_PRICE_IMPACT_LOW: Percent = new Percent(JSBI.BigInt(100), BIPS_BASE) // 1%
21 | export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(JSBI.BigInt(300), BIPS_BASE) // 3%
22 | export const ALLOWED_PRICE_IMPACT_HIGH: Percent = new Percent(JSBI.BigInt(500), BIPS_BASE) // 5%
23 | // if the price slippage exceeds this number, force the user to type 'confirm' to execute
24 | export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.BigInt(1000), BIPS_BASE) // 10%
25 | // for non expert mode disable swaps above this
26 | export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(1500), BIPS_BASE) // 15%
27 |
28 | // used to ensure the user doesn't send so much ETH so they end up with <.01
29 | export const MIN_ETH: JSBI = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(16)) // .01 ETH
30 | export const BETTER_TRADE_LESS_HOPS_THRESHOLD = new Percent(JSBI.BigInt(50), JSBI.BigInt(10000))
31 |
32 | export const ZERO_PERCENT = new Percent("0")
33 | export const ONE_HUNDRED_PERCENT = new Percent("1")
34 |
--------------------------------------------------------------------------------
/client/src/constants/proposals/index.ts:
--------------------------------------------------------------------------------
1 | import { UNISWAP_GRANTS_PROPOSAL_DESCRIPTION } from "./uniswap_grants_proposal_description"
2 |
3 | // Proposals are 0-indexed
4 | export const PROPOSAL_DESCRIPTION_TEXT: { [proposalId: number]: string } = {
5 | [2]: UNISWAP_GRANTS_PROPOSAL_DESCRIPTION,
6 | }
7 |
--------------------------------------------------------------------------------
/client/src/constants/tokenLists/uniswap-v2-unsupported.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Unsupported Tokens",
3 | "timestamp": "2021-01-05T20:47:02.923Z",
4 | "version": {
5 | "major": 1,
6 | "minor": 0,
7 | "patch": 0
8 | },
9 | "tags": {},
10 | "logoURI": "ipfs://QmNa8mQkrNKp1WEEeGjFezDmDeodkWRevGFN8JCV7b4Xir",
11 | "keywords": ["uniswap", "unsupported"],
12 | "tokens": [
13 | {
14 | "name": "Gold Tether",
15 | "address": "0x4922a015c4407F87432B179bb209e125432E4a2A",
16 | "symbol": "XAUt",
17 | "decimals": 6,
18 | "chainId": 1,
19 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png"
20 | },
21 | {
22 | "name": "Grump Cat",
23 | "address": "0x93B2FfF814FCaEFFB01406e80B4Ecd89Ca6A021b",
24 | "symbol": "GRUMPY",
25 | "decimals": 9,
26 | "chainId": 1,
27 | "logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x4922a015c4407F87432B179bb209e125432E4a2A/logo.png"
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/client/src/constants/wallet.ts:
--------------------------------------------------------------------------------
1 | import { AbstractConnector } from "@web3-react/abstract-connector"
2 | import INJECTED_ICON_URL from "../assets/images/arrow-right.svg"
3 | import METAMASK_ICON_URL from "../assets/images/metamask.png"
4 | import { injected } from "../connectors"
5 |
6 | export interface WalletInfo {
7 | connector?: AbstractConnector
8 | name: string
9 | iconURL: string
10 | description: string
11 | href: string | null
12 | color: string
13 | primary?: true
14 | mobile?: true
15 | mobileOnly?: true
16 | }
17 |
18 | export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
19 | INJECTED: {
20 | connector: injected,
21 | name: "Injected",
22 | iconURL: INJECTED_ICON_URL,
23 | description: "Injected web3 provider.",
24 | href: null,
25 | color: "#010101",
26 | primary: true,
27 | },
28 | METAMASK: {
29 | connector: injected,
30 | name: "MetaMask",
31 | iconURL: METAMASK_ICON_URL,
32 | description: "Easy-to-use browser extension.",
33 | href: null,
34 | color: "#E8831D",
35 | },
36 | }
37 |
--------------------------------------------------------------------------------
/client/src/hooks/useAddTokenToMetamask.ts:
--------------------------------------------------------------------------------
1 | import { getTokenLogoURL } from "./../components/CurrencyLogo/index"
2 | import { wrappedCurrency } from "utils/wrappedCurrency"
3 | import { Currency, Token } from "@uniswap/sdk-core"
4 | import { useCallback, useState } from "react"
5 | import { useActiveWeb3React } from "hooks/web3"
6 |
7 | export default function useAddTokenToMetamask(
8 | currencyToAdd: Currency | undefined
9 | ): { addToken: () => void; success: boolean | undefined } {
10 | const { library, chainId } = useActiveWeb3React()
11 |
12 | const token: Token | undefined = wrappedCurrency(currencyToAdd, chainId)
13 |
14 | const [success, setSuccess] = useState()
15 |
16 | const addToken = useCallback(() => {
17 | if (library && library.provider.isMetaMask && library.provider.request && token) {
18 | library.provider
19 | .request({
20 | method: "wallet_watchAsset",
21 | params: {
22 | //@ts-ignore // need this for incorrect ethers provider type
23 | type: "ERC20",
24 | options: {
25 | address: token.address,
26 | symbol: token.symbol,
27 | decimals: token.decimals,
28 | image: getTokenLogoURL(token.address),
29 | },
30 | },
31 | })
32 | .then((success) => {
33 | setSuccess(success)
34 | })
35 | .catch(() => setSuccess(false))
36 | } else {
37 | setSuccess(false)
38 | }
39 | }, [library, token])
40 |
41 | return { addToken, success }
42 | }
43 |
--------------------------------------------------------------------------------
/client/src/hooks/useApeModeQueryParamReader.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 | import { useDispatch } from "react-redux"
3 | import { AppDispatch } from "state"
4 | import { updateUserExpertMode } from "../state/user/actions"
5 | import useParsedQueryString from "./useParsedQueryString"
6 |
7 | export default function ApeModeQueryParamReader(): null {
8 | useApeModeQueryParamReader()
9 | return null
10 | }
11 |
12 | function useApeModeQueryParamReader() {
13 | const dispatch = useDispatch()
14 | const { ape } = useParsedQueryString()
15 |
16 | useEffect(() => {
17 | if (typeof ape !== "string") return
18 | if (ape === "" || ape.toLowerCase() === "true") {
19 | dispatch(updateUserExpertMode({ userExpertMode: true }))
20 | }
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/client/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 = 1400): [boolean, (toCopy: string) => void] {
5 | const [isCopied, setIsCopied] = useState(false)
6 |
7 | const staticCopy = useCallback((text) => {
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 |
--------------------------------------------------------------------------------
/client/src/hooks/useCurrentBlockTimestamp.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers"
2 | import { useSingleCallResult } from "../state/multicall/hooks"
3 | import { useMulticall2Contract } from "./useContract"
4 |
5 | // gets the current timestamp from the blockchain
6 | export default function useCurrentBlockTimestamp(): BigNumber | undefined {
7 | const multicall = useMulticall2Contract()
8 | return useSingleCallResult(multicall, "getCurrentBlockTimestamp")?.result?.[0]
9 | }
10 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/hooks/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 |
--------------------------------------------------------------------------------
/client/src/hooks/useDerivedPositionInfo.ts:
--------------------------------------------------------------------------------
1 | import { Pool, Position } from "@uniswap/v3-sdk"
2 | import { usePool } from "hooks/usePools"
3 | import { PositionDetails } from "types/position"
4 | import { useCurrency } from "./Tokens"
5 |
6 | export function useDerivedPositionInfo(
7 | positionDetails: PositionDetails | undefined
8 | ): {
9 | position: Position | undefined
10 | pool: Pool | undefined
11 | } {
12 | const currency0 = useCurrency(positionDetails?.token0)
13 | const currency1 = useCurrency(positionDetails?.token1)
14 |
15 | // construct pool data
16 | const [, pool] = usePool(currency0 ?? undefined, currency1 ?? undefined, positionDetails?.fee)
17 |
18 | let position = undefined
19 | if (pool && positionDetails) {
20 | position = new Position({
21 | pool,
22 | liquidity: positionDetails.liquidity.toString(),
23 | tickLower: positionDetails.tickLower,
24 | tickUpper: positionDetails.tickUpper,
25 | })
26 | }
27 |
28 | return {
29 | position,
30 | pool: pool ?? undefined,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/client/src/hooks/useENS.ts:
--------------------------------------------------------------------------------
1 | import { isAddress } from "../utils"
2 | import useENSAddress from "./useENSAddress"
3 | import useENSName from "./useENSName"
4 |
5 | /**
6 | * Given a name or address, does a lookup to resolve to an address and name
7 | * @param nameOrAddress ENS name or address
8 | */
9 | export default function useENS(
10 | nameOrAddress?: string | null
11 | ): { loading: boolean; address: string | null; name: string | null } {
12 | const validated = isAddress(nameOrAddress)
13 | const reverseLookup = useENSName(validated ? validated : undefined)
14 | const lookup = useENSAddress(nameOrAddress)
15 |
16 | return {
17 | loading: reverseLookup.loading || lookup.loading,
18 | address: validated ? validated : lookup.address,
19 | name: reverseLookup.ENSName ? reverseLookup.ENSName : !validated && lookup.address ? nameOrAddress || null : null,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/hooks/useENSAddress.ts:
--------------------------------------------------------------------------------
1 | import { namehash } from "ethers/lib/utils"
2 | import { useMemo } from "react"
3 | import { useSingleCallResult } from "../state/multicall/hooks"
4 | import isZero from "../utils/isZero"
5 | import { useENSRegistrarContract, useENSResolverContract } from "./useContract"
6 | import useDebounce from "./useDebounce"
7 |
8 | /**
9 | * Does a lookup for an ENS name to find its address.
10 | */
11 | export default function useENSAddress(ensName?: string | null): { loading: boolean; address: string | null } {
12 | const debouncedName = useDebounce(ensName, 200)
13 | const ensNodeArgument = useMemo(() => {
14 | if (!debouncedName) return [undefined]
15 | try {
16 | return debouncedName ? [namehash(debouncedName)] : [undefined]
17 | } catch (error) {
18 | return [undefined]
19 | }
20 | }, [debouncedName])
21 | const registrarContract = useENSRegistrarContract(false)
22 | const resolverAddress = useSingleCallResult(registrarContract, "resolver", ensNodeArgument)
23 | const resolverAddressResult = resolverAddress.result?.[0]
24 | const resolverContract = useENSResolverContract(
25 | resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined,
26 | false
27 | )
28 | const addr = useSingleCallResult(resolverContract, "addr", ensNodeArgument)
29 |
30 | const changed = debouncedName !== ensName
31 | return {
32 | address: changed ? null : addr.result?.[0] ?? null,
33 | loading: changed || resolverAddress.loading || addr.loading,
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/client/src/hooks/useENSContentHash.ts:
--------------------------------------------------------------------------------
1 | import { namehash } from "ethers/lib/utils"
2 | import { useMemo } from "react"
3 | import { useSingleCallResult } from "../state/multicall/hooks"
4 | import isZero from "../utils/isZero"
5 | import { useENSRegistrarContract, useENSResolverContract } from "./useContract"
6 |
7 | /**
8 | * Does a lookup for an ENS name to find its contenthash.
9 | */
10 | export default function useENSContentHash(ensName?: string | null): { loading: boolean; contenthash: string | null } {
11 | const ensNodeArgument = useMemo(() => {
12 | if (!ensName) return [undefined]
13 | try {
14 | return ensName ? [namehash(ensName)] : [undefined]
15 | } catch (error) {
16 | return [undefined]
17 | }
18 | }, [ensName])
19 | const registrarContract = useENSRegistrarContract(false)
20 | const resolverAddressResult = useSingleCallResult(registrarContract, "resolver", ensNodeArgument)
21 | const resolverAddress = resolverAddressResult.result?.[0]
22 | const resolverContract = useENSResolverContract(
23 | resolverAddress && isZero(resolverAddress) ? undefined : resolverAddress,
24 | false
25 | )
26 | const contenthash = useSingleCallResult(resolverContract, "contenthash", ensNodeArgument)
27 |
28 | return {
29 | contenthash: contenthash.result?.[0] ?? null,
30 | loading: resolverAddressResult.loading || contenthash.loading,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/client/src/hooks/useENSName.ts:
--------------------------------------------------------------------------------
1 | import { namehash } from "ethers/lib/utils"
2 | import { useMemo } from "react"
3 | import { useSingleCallResult } from "../state/multicall/hooks"
4 | import { isAddress } from "../utils"
5 | import isZero from "../utils/isZero"
6 | import { useENSRegistrarContract, useENSResolverContract } from "./useContract"
7 | import useDebounce from "./useDebounce"
8 |
9 | /**
10 | * Does a reverse lookup for an address to find its ENS name.
11 | * Note this is not the same as looking up an ENS name to find an address.
12 | */
13 | export default function useENSName(address?: string): { ENSName: string | null; loading: boolean } {
14 | const debouncedAddress = useDebounce(address, 200)
15 | const ensNodeArgument = useMemo(() => {
16 | if (!debouncedAddress || !isAddress(debouncedAddress)) return [undefined]
17 | try {
18 | return debouncedAddress ? [namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`)] : [undefined]
19 | } catch (error) {
20 | return [undefined]
21 | }
22 | }, [debouncedAddress])
23 | const registrarContract = useENSRegistrarContract(false)
24 | const resolverAddress = useSingleCallResult(registrarContract, "resolver", ensNodeArgument)
25 | const resolverAddressResult = resolverAddress.result?.[0]
26 | const resolverContract = useENSResolverContract(
27 | resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined,
28 | false
29 | )
30 | const name = useSingleCallResult(resolverContract, "name", ensNodeArgument)
31 |
32 | const changed = debouncedAddress !== address
33 | return {
34 | ENSName: changed ? null : name.result?.[0] ?? null,
35 | loading: changed || resolverAddress.loading || name.loading,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/hooks/useFetchListCallback.ts:
--------------------------------------------------------------------------------
1 | import { nanoid } from "@reduxjs/toolkit"
2 | import { ChainId } from "@uniswap/sdk-core"
3 | import { TokenList } from "@uniswap/token-lists"
4 | import { useCallback } from "react"
5 | import { useDispatch } from "react-redux"
6 | import { getNetworkLibrary } from "../connectors"
7 | import { AppDispatch } from "../state"
8 | import { fetchTokenList } from "../state/lists/actions"
9 | import getTokenList from "../utils/getTokenList"
10 | import resolveENSContentHash from "../utils/resolveENSContentHash"
11 | import { useActiveWeb3React } from "./web3"
12 |
13 | export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean) => Promise {
14 | const { chainId, library } = useActiveWeb3React()
15 | const dispatch = useDispatch()
16 |
17 | const ensResolver = useCallback(
18 | async (ensName: string) => {
19 | if (!library || chainId !== ChainId.MAINNET) {
20 | const networkLibrary = getNetworkLibrary()
21 | const network = await networkLibrary.getNetwork()
22 | if (networkLibrary && network.chainId === ChainId.MAINNET) {
23 | return resolveENSContentHash(ensName, networkLibrary)
24 | }
25 | throw new Error("Could not construct mainnet ENS resolver")
26 | }
27 | return resolveENSContentHash(ensName, library)
28 | },
29 | [chainId, library]
30 | )
31 |
32 | // note: prevent dispatch if using for list search or unsupported list
33 | return useCallback(
34 | async (listUrl: string, sendDispatch = true) => {
35 | const requestId = nanoid()
36 | sendDispatch && dispatch(fetchTokenList.pending({ requestId, url: listUrl }))
37 | return getTokenList(listUrl, ensResolver)
38 | .then((tokenList) => {
39 | sendDispatch && dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId }))
40 | return tokenList
41 | })
42 | .catch((error) => {
43 | console.debug(`Failed to get list at url ${listUrl}`, error)
44 | sendDispatch && dispatch(fetchTokenList.rejected({ url: listUrl, requestId, errorMessage: error.message }))
45 | throw error
46 | })
47 | },
48 | [dispatch, ensResolver]
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/client/src/hooks/useHttpLocations.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react"
2 | import contenthashToUri from "../utils/contenthashToUri"
3 | import { parseENSAddress } from "../utils/parseENSAddress"
4 | import uriToHttp from "../utils/uriToHttp"
5 | import useENSContentHash from "./useENSContentHash"
6 |
7 | export default function useHttpLocations(uri: string | undefined): string[] {
8 | const ens = useMemo(() => (uri ? parseENSAddress(uri) : undefined), [uri])
9 | const resolvedContentHash = useENSContentHash(ens?.ensName)
10 | return useMemo(() => {
11 | if (ens) {
12 | return resolvedContentHash.contenthash ? uriToHttp(contenthashToUri(resolvedContentHash.contenthash)) : []
13 | } else {
14 | return uri ? uriToHttp(uri) : []
15 | }
16 | }, [ens, resolvedContentHash.contenthash, uri])
17 | }
18 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/hooks/useIsArgentWallet.ts:
--------------------------------------------------------------------------------
1 | import { NEVER_RELOAD, useSingleCallResult } from "../state/multicall/hooks"
2 | import { useActiveWeb3React } from "./web3"
3 | import { useArgentWalletDetectorContract } from "./useContract"
4 |
5 | export default function useIsArgentWallet(): boolean {
6 | const { account } = useActiveWeb3React()
7 | const argentWalletDetector = useArgentWalletDetectorContract()
8 | const call = useSingleCallResult(argentWalletDetector, "isArgentWallet", [account ?? undefined], NEVER_RELOAD)
9 | return call?.result?.[0] ?? false
10 | }
11 |
--------------------------------------------------------------------------------
/client/src/hooks/useIsSwapUnsupported.ts:
--------------------------------------------------------------------------------
1 | import { Currency, Token } from "@uniswap/sdk-core"
2 | import { useMemo } from "react"
3 | import { useUnsupportedTokens } from "./Tokens"
4 |
5 | /**
6 | * Returns true if the input currency or output currency cannot be traded in the interface
7 | * @param currencyIn the input currency to check
8 | * @param currencyOut the output currency to check
9 | */
10 | export function useIsSwapUnsupported(currencyIn?: Currency, currencyOut?: Currency): boolean {
11 | const unsupportedTokens: { [address: string]: Token } = useUnsupportedTokens()
12 |
13 | return useMemo(() => {
14 | // if unsupported list loaded & either token on list, mark as unsupported
15 | return Boolean(
16 | unsupportedTokens &&
17 | ((currencyIn?.isToken && unsupportedTokens[currencyIn.address]) ||
18 | (currencyOut?.isToken && unsupportedTokens[currencyOut.address]))
19 | )
20 | }, [currencyIn, currencyOut, unsupportedTokens])
21 | }
22 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/hooks/usePositionTokenURI.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers"
2 | import JSBI from "jsbi"
3 | import { useMemo } from "react"
4 | import { NEVER_RELOAD, useSingleCallResult } from "../state/multicall/hooks"
5 | import { useV3NFTPositionManagerContract } from "./useContract"
6 |
7 | type TokenId = number | JSBI | BigNumber
8 |
9 | const STARTS_WITH = "data:application/json;base64,"
10 |
11 | type UsePositionTokenURIResult =
12 | | {
13 | valid: true
14 | loading: false
15 | result: {
16 | name: string
17 | description: string
18 | image: string
19 | }
20 | }
21 | | {
22 | valid: false
23 | loading: false
24 | }
25 | | {
26 | valid: true
27 | loading: true
28 | }
29 |
30 | export function usePositionTokenURI(tokenId: TokenId | undefined): UsePositionTokenURIResult {
31 | const contract = useV3NFTPositionManagerContract()
32 | const inputs = useMemo(() => [tokenId instanceof BigNumber ? tokenId.toHexString() : tokenId?.toString(16)], [
33 | tokenId,
34 | ])
35 | const { result, error, loading, valid } = useSingleCallResult(contract, "tokenURI", inputs, NEVER_RELOAD, 1_600_000)
36 |
37 | return useMemo(() => {
38 | if (error || !valid || !tokenId) {
39 | return {
40 | valid: false,
41 | loading: false,
42 | }
43 | }
44 | if (loading) {
45 | return {
46 | valid: true,
47 | loading: true,
48 | }
49 | }
50 | if (!result) {
51 | return {
52 | valid: false,
53 | loading: false,
54 | }
55 | }
56 | const [tokenURI] = result as [string]
57 | if (!tokenURI || !tokenURI.startsWith(STARTS_WITH))
58 | return {
59 | valid: false,
60 | loading: false,
61 | }
62 |
63 | try {
64 | const json = JSON.parse(atob(tokenURI.slice(STARTS_WITH.length)))
65 |
66 | return {
67 | valid: true,
68 | loading: false,
69 | result: json,
70 | }
71 | } catch (error) {
72 | return { valid: false, loading: false }
73 | }
74 | }, [error, loading, result, tokenId, valid])
75 | }
76 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/hooks/useSocksBalance.ts:
--------------------------------------------------------------------------------
1 | import JSBI from "jsbi"
2 | import { useMemo } from "react"
3 | import { NEVER_RELOAD, useSingleCallResult } from "../state/multicall/hooks"
4 | import { useActiveWeb3React } from "./web3"
5 | import { useSocksController } from "./useContract"
6 |
7 | export default function useSocksBalance(): JSBI | undefined {
8 | const { account } = useActiveWeb3React()
9 | const socksContract = useSocksController()
10 |
11 | const { result } = useSingleCallResult(socksContract, "balanceOf", [account ?? undefined], NEVER_RELOAD)
12 | const data = result?.[0]
13 | return data ? JSBI.BigInt(data.toString()) : undefined
14 | }
15 |
16 | export function useHasSocks(): boolean | undefined {
17 | const balance = useSocksBalance()
18 | return useMemo(() => balance && JSBI.greaterThan(balance, JSBI.BigInt(0)), [balance])
19 | }
20 |
--------------------------------------------------------------------------------
/client/src/hooks/useSwapSlippageTolerance.ts:
--------------------------------------------------------------------------------
1 | import { Currency, Percent, TradeType } from "@uniswap/sdk-core"
2 | import { Trade as V3Trade } from "@uniswap/v3-sdk"
3 | import { Trade as V2Trade } from "@uniswap/v2-sdk"
4 | import { useMemo } from "react"
5 | import { useUserSlippageToleranceWithDefault } from "../state/user/hooks"
6 |
7 | const V2_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
8 | const V3_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
9 | const ONE_TENTHS_PERCENT = new Percent(10, 10_000) // .10%
10 |
11 | export default function useSwapSlippageTolerance(
12 | trade: V2Trade | V3Trade | undefined
13 | ): Percent {
14 | const defaultSlippageTolerance = useMemo(() => {
15 | if (!trade) return ONE_TENTHS_PERCENT
16 | if (trade instanceof V2Trade) return V2_SWAP_DEFAULT_SLIPPAGE
17 | return V3_SWAP_DEFAULT_SLIPPAGE
18 | }, [trade])
19 | return useUserSlippageToleranceWithDefault(defaultSlippageTolerance)
20 | }
21 |
--------------------------------------------------------------------------------
/client/src/hooks/useTheme.ts:
--------------------------------------------------------------------------------
1 | import { ThemeContext } from "styled-components"
2 | import { useContext } from "react"
3 |
4 | export default function useTheme() {
5 | return useContext(ThemeContext)
6 | }
7 |
--------------------------------------------------------------------------------
/client/src/hooks/useTickToPrice.ts:
--------------------------------------------------------------------------------
1 | import { Token, Price } from "@uniswap/sdk-core"
2 | import { tickToPrice } from "@uniswap/v3-sdk"
3 |
4 | export function getTickToPrice(
5 | baseToken: Token | undefined,
6 | quoteToken: Token | undefined,
7 | tick: number | undefined
8 | ): Price | undefined {
9 | if (!baseToken || !quoteToken || !tick) {
10 | return undefined
11 | }
12 | return tickToPrice(baseToken, quoteToken, tick)
13 | }
14 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/hooks/useToggledVersion.ts:
--------------------------------------------------------------------------------
1 | import useParsedQueryString from "./useParsedQueryString"
2 |
3 | export enum Version {
4 | v2 = "V2",
5 | v3 = "V3",
6 | }
7 |
8 | export const DEFAULT_VERSION: Version = Version.v3
9 |
10 | export default function useToggledVersion(): Version {
11 | const { use } = useParsedQueryString()
12 | if (typeof use !== "string") {
13 | return DEFAULT_VERSION
14 | }
15 | switch (use.toLowerCase()) {
16 | case "v2":
17 | return Version.v2
18 | case "v3":
19 | return Version.v3
20 | default:
21 | return Version.v3
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/client/src/hooks/useTokenAllowance.ts:
--------------------------------------------------------------------------------
1 | import { Token, CurrencyAmount } from "@uniswap/sdk-core"
2 | import { useMemo } from "react"
3 | import { useSingleCallResult } from "../state/multicall/hooks"
4 | import { useTokenContract } from "./useContract"
5 |
6 | export function useTokenAllowance(token?: Token, owner?: string, spender?: string): CurrencyAmount | undefined {
7 | const contract = useTokenContract(token?.address, false)
8 |
9 | const inputs = useMemo(() => [owner, spender], [owner, spender])
10 | const allowance = useSingleCallResult(contract, "allowance", inputs).result
11 |
12 | return useMemo(() => (token && allowance ? CurrencyAmount.fromRawAmount(token, allowance.toString()) : undefined), [
13 | token,
14 | allowance,
15 | ])
16 | }
17 |
--------------------------------------------------------------------------------
/client/src/hooks/useTotalSupply.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "@ethersproject/bignumber"
2 | import { Token, CurrencyAmount, Currency } from "@uniswap/sdk-core"
3 | import { useTokenContract } from "./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?: Currency): CurrencyAmount | undefined {
9 | const contract = useTokenContract(token?.isToken ? token.address : undefined, false)
10 |
11 | const totalSupply: BigNumber = useSingleCallResult(contract, "totalSupply")?.result?.[0]
12 |
13 | return token?.isToken && totalSupply ? CurrencyAmount.fromRawAmount(token, totalSupply.toString()) : undefined
14 | }
15 |
--------------------------------------------------------------------------------
/client/src/hooks/useTransactionDeadline.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "ethers"
2 | import { useMemo } from "react"
3 | import { useSelector } from "react-redux"
4 | import { AppState } from "../state"
5 | import useCurrentBlockTimestamp from "./useCurrentBlockTimestamp"
6 |
7 | // combines the block timestamp with the user setting to give the deadline that should be used for any submitted transaction
8 | export default function useTransactionDeadline(): BigNumber | undefined {
9 | const ttl = useSelector((state) => state.user.userDeadline)
10 | const blockTimestamp = useCurrentBlockTimestamp()
11 | return useMemo(() => {
12 | if (blockTimestamp && ttl) return blockTimestamp.add(ttl)
13 | return undefined
14 | }, [blockTimestamp, ttl])
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/hooks/useV3SwapPools.ts:
--------------------------------------------------------------------------------
1 | import { Currency, Token } from "@uniswap/sdk-core"
2 | import { FeeAmount, Pool } from "@uniswap/v3-sdk"
3 | import { useMemo } from "react"
4 | import { useAllCurrencyCombinations } from "./useAllCurrencyCombinations"
5 | import { PoolState, usePools } from "./usePools"
6 |
7 | /**
8 | * Returns all the existing pools that should be considered for swapping between an input currency and an output currency
9 | * @param currencyIn the input currency
10 | * @param currencyOut the output currency
11 | */
12 | export function useV3SwapPools(
13 | currencyIn?: Currency,
14 | currencyOut?: Currency
15 | ): {
16 | pools: Pool[]
17 | loading: boolean
18 | } {
19 | const allCurrencyCombinations = useAllCurrencyCombinations(currencyIn, currencyOut)
20 |
21 | const allCurrencyCombinationsWithAllFees: [Token, Token, FeeAmount][] = useMemo(
22 | () =>
23 | allCurrencyCombinations.reduce<[Token, Token, FeeAmount][]>((list, [tokenA, tokenB]) => {
24 | return list.concat([
25 | [tokenA, tokenB, FeeAmount.LOW],
26 | [tokenA, tokenB, FeeAmount.MEDIUM],
27 | [tokenA, tokenB, FeeAmount.HIGH],
28 | ])
29 | }, []),
30 | [allCurrencyCombinations]
31 | )
32 |
33 | const pools = usePools(allCurrencyCombinationsWithAllFees)
34 |
35 | return useMemo(() => {
36 | return {
37 | pools: pools
38 | .filter((tuple): tuple is [PoolState.EXISTS, Pool] => {
39 | return tuple[0] === PoolState.EXISTS && tuple[1] !== null
40 | })
41 | .map(([, pool]) => pool),
42 | loading: pools.some(([state]) => state === PoolState.LOADING),
43 | }
44 | }, [pools])
45 | }
46 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/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 | i18next
7 | .use(XHR)
8 | .use(LanguageDetector)
9 | .use(initReactI18next)
10 | .init({
11 | backend: {
12 | loadPath: `./locales/{{lng}}.json`,
13 | },
14 | react: {
15 | useSuspense: true,
16 | },
17 | fallbackLng: "en",
18 | preload: ["en"],
19 | keySeparator: false,
20 | interpolation: { escapeValue: false },
21 | })
22 |
23 | export default i18next
24 |
--------------------------------------------------------------------------------
/client/src/pages/AppBody.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components/macro"
3 |
4 | export const BodyWrapper = styled.div<{ margin?: string }>`
5 | position: relative;
6 | margin-top: ${({ margin }) => margin ?? "0px"};
7 | max-width: 480px;
8 | width: 100%;
9 | background: ${({ theme }) => theme.bg0};
10 | 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),
11 | 0px 24px 32px rgba(0, 0, 0, 0.01);
12 | border-radius: 24px;
13 | margin-top: 1rem;
14 | `
15 |
16 | /**
17 | * The styled container element that wraps the content of most pages and the tabs.
18 | */
19 | export default function AppBody({ children, ...rest }: { children: React.ReactNode }) {
20 | return {children}
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/pages/Explorer/redirects.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Redirect, RouteComponentProps } from "react-router-dom"
3 |
4 | // Redirects to swap but only replace the pathname
5 | export function RedirectPathToTrojanOnly({ location }: RouteComponentProps) {
6 | return
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/pages/Maintenance/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Row, Col, Grid } from "react-styled-flexboxgrid"
3 | import { Text } from "rebass/styled-components"
4 | import { ExternalLink } from "theme"
5 | import LogoDark from "assets/svg/logo_white.svg"
6 | import { DataCard, CardSection } from "components/Common/styled"
7 | import { AutoColumn } from "components/Column"
8 | import { UniIcon } from "../styled"
9 |
10 | export default function Maintenance({}) {
11 | return (
12 | <>
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {"Trojan is currently under maintenance -- It'll be back shortly."}
31 |
32 |
33 |
34 |
35 |
36 | Trojan is part of the Offshore DAO 🏝
37 |
38 | [Discord]
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | >
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/client/src/pages/context/socket.ts:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { io } from "socket.io-client"
3 |
4 | export const socket = io((process.env.REACT_APP_WS_URL_TROJAN || "http://localhost:3001/") + "explorer")
5 | export const SocketContext = React.createContext(socket)
6 |
--------------------------------------------------------------------------------
/client/src/pages/styled.tsx:
--------------------------------------------------------------------------------
1 | import { Text } from "rebass"
2 | import styled, { keyframes } from "styled-components"
3 | export const StandardPageWrapper = styled.div`
4 | padding-top: 160px;
5 | width: 100%;
6 | `
7 | export const ClickableText = styled(Text)`
8 | :hover {
9 | cursor: pointer;
10 | }
11 | color: ${({ theme }) => theme.primary1};
12 | `
13 | export const Wrapper = styled.div`
14 | position: relative;
15 | width: 100%;
16 | overflow: auto;
17 | `
18 |
19 | export const rotate = keyframes`
20 | from {
21 | transform: rotate(0deg);
22 | }
23 | to {
24 | transform: rotate(360deg);
25 | }
26 | `
27 |
28 | export const UniIcon = styled.div`
29 | animation: ${rotate} linear 1800ms;
30 |
31 | :hover {
32 | animation: infinite ${rotate} linear 1400ms;
33 | }
34 | `
35 |
36 | export const ButtonRefrash = styled.button`
37 | position: relative;
38 | border: none;
39 | background-color: ${({ theme }) => theme.bg2};
40 | margin: 0.5rem;
41 | padding: 0.1rem 0.1rem;
42 | border-radius: 0.5rem;
43 |
44 | :hover,
45 | :focus {
46 | cursor: pointer;
47 | outline: none;
48 | background-color: ${({ theme }) => theme.bg4};
49 | }
50 |
51 | svg {
52 | margin-top: 2px;
53 | }
54 | > * {
55 | stroke: ${({ theme }) => theme.blue1};
56 | }
57 | `
58 | export const ButtonRefrashPad = styled.button`
59 | position: relative;
60 | border: none;
61 | background-color: ${({ theme }) => theme.bg2};
62 | margin: 0.5rem;
63 | padding: 1rem 0.8rem;
64 | border-radius: 0.5rem;
65 |
66 | :hover,
67 | :focus {
68 | cursor: pointer;
69 | outline: none;
70 | background-color: ${({ theme }) => theme.bg4};
71 | }
72 |
73 | svg {
74 | margin-top: 2px;
75 | }
76 | > * {
77 | stroke: ${({ theme }) => theme.blue1};
78 | }
79 | `
80 |
--------------------------------------------------------------------------------
/client/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?: true
12 | on?: (...args: any[]) => void
13 | removeListener?: (...args: any[]) => void
14 | autoRefreshOnNetworkChange?: boolean
15 | }
16 | web3?: Record
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 |
--------------------------------------------------------------------------------
/client/src/state/application/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from "@reduxjs/toolkit"
2 | import { TokenList } from "@uniswap/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 | POOL_OVERVIEW_OPTIONS,
31 | }
32 |
33 | export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>("application/updateBlockNumber")
34 | export const setOpenModal = createAction("application/setOpenModal")
35 | export const addPopup = createAction<{ key?: string; removeAfterMs?: number | null; content: PopupContent }>(
36 | "application/addPopup"
37 | )
38 | export const removePopup = createAction<{ key: string }>("application/removePopup")
39 |
--------------------------------------------------------------------------------
/client/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 = 25000 } }) => {
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 |
--------------------------------------------------------------------------------
/client/src/state/application/updater.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from "react"
2 | import { useActiveWeb3React } from "../../hooks/web3"
3 | import useDebounce from "../../hooks/useDebounce"
4 | import useIsWindowVisible from "../../hooks/useIsWindowVisible"
5 | import { updateBlockNumber } from "./actions"
6 | import { useDispatch } from "react-redux"
7 |
8 | export default function Updater(): null {
9 | const { library, chainId } = useActiveWeb3React()
10 | const dispatch = useDispatch()
11 |
12 | const windowVisible = useIsWindowVisible()
13 |
14 | const [state, setState] = useState<{ chainId: number | undefined; blockNumber: number | null }>({
15 | chainId,
16 | blockNumber: null,
17 | })
18 |
19 | const blockNumberCallback = useCallback(
20 | (blockNumber: number) => {
21 | setState((state) => {
22 | if (chainId === state.chainId) {
23 | if (typeof state.blockNumber !== "number") return { chainId, blockNumber }
24 | return { chainId, blockNumber: Math.max(blockNumber, state.blockNumber) }
25 | }
26 | return state
27 | })
28 | },
29 | [chainId, setState]
30 | )
31 |
32 | // attach/detach listeners
33 | useEffect(() => {
34 | if (!library || !chainId || !windowVisible) return undefined
35 |
36 | setState({ chainId, blockNumber: null })
37 |
38 | library
39 | .getBlockNumber()
40 | .then(blockNumberCallback)
41 | .catch((error) => console.error(`Failed to get block number for chainId: ${chainId}`, error))
42 |
43 | library.on("block", blockNumberCallback)
44 | return () => {
45 | library.removeListener("block", blockNumberCallback)
46 | }
47 | }, [dispatch, chainId, library, blockNumberCallback, windowVisible])
48 |
49 | const debouncedState = useDebounce(state, 100)
50 |
51 | useEffect(() => {
52 | if (!debouncedState.chainId || !debouncedState.blockNumber || !windowVisible) return
53 | dispatch(updateBlockNumber({ chainId: debouncedState.chainId, blockNumber: debouncedState.blockNumber }))
54 | }, [windowVisible, dispatch, debouncedState.blockNumber, debouncedState.chainId])
55 |
56 | return null
57 | }
58 |
--------------------------------------------------------------------------------
/client/src/state/freshTokens/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from "@reduxjs/toolkit"
2 | import { Currency } from "@uniswap/sdk-core"
3 |
4 | export const setHoursFilter = createAction<{ hoursFilter: number }>("freshTokens/setHoursFilter")
5 | export const setSelectedFreshToken = createAction<{ selectedFreshToken: Currency | null }>(
6 | "freshTokens/setSelectedFreshToken"
7 | )
8 |
--------------------------------------------------------------------------------
/client/src/state/freshTokens/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react"
2 | import { useDispatch, useSelector } from "react-redux"
3 | import { AppDispatch, AppState } from "../index"
4 | import { setHoursFilter, setSelectedFreshToken } from "./actions"
5 | import { Currency } from "@uniswap/sdk-core"
6 |
7 | export function useFreshTokensState(): AppState["freshTokens"] {
8 | return useSelector((state) => state.freshTokens)
9 | }
10 |
11 | export function useFreshTokensActionHandlers(): {
12 | onFreshTokenSelection: (selectedFreshToken: Currency | null) => void
13 | onHourFilterSwitch: (hoursFilter: number) => void
14 | } {
15 | const dispatch = useDispatch()
16 | const onFreshTokenSelection = useCallback(
17 | (selectedFreshToken: Currency | null) => {
18 | dispatch(
19 | setSelectedFreshToken({
20 | selectedFreshToken,
21 | })
22 | )
23 | },
24 | [dispatch]
25 | )
26 |
27 | const onHourFilterSwitch = useCallback(
28 | (hoursFilter: number) => {
29 | dispatch(
30 | setHoursFilter({
31 | hoursFilter,
32 | })
33 | )
34 | },
35 | [dispatch]
36 | )
37 |
38 | return {
39 | onFreshTokenSelection,
40 | onHourFilterSwitch,
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/client/src/state/freshTokens/reducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from "@reduxjs/toolkit"
2 | import { setHoursFilter, setSelectedFreshToken } from "./actions"
3 | import { Currency } from "@uniswap/sdk-core"
4 |
5 | export interface FreshTokensState {
6 | readonly hoursFilter: number | undefined
7 | readonly selectedFreshToken: Currency | null
8 | }
9 |
10 | const initialState: FreshTokensState = {
11 | hoursFilter: 6,
12 | selectedFreshToken: null,
13 | }
14 |
15 | export default createReducer(initialState, (builder) =>
16 | builder
17 | .addCase(setHoursFilter, (state, { payload: { hoursFilter } }) => {
18 | return {
19 | ...state,
20 | hoursFilter,
21 | }
22 | })
23 | .addCase(setSelectedFreshToken, (state, { payload: { selectedFreshToken } }) => {
24 | return {
25 | ...state,
26 | selectedFreshToken,
27 | }
28 | })
29 | )
30 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/state/index.ts:
--------------------------------------------------------------------------------
1 | import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit"
2 | import { save, load } from "redux-localstorage-simple"
3 |
4 | import application from "./application/reducer"
5 | import { updateVersion } from "./global/actions"
6 | import user from "./user/reducer"
7 | import transactions from "./transactions/reducer"
8 | import lists from "./lists/reducer"
9 | import multicall from "./multicall/reducer"
10 |
11 | import freshTokens from "./freshTokens/reducer"
12 | import trojanTxs from "./trojanTxs/reducer"
13 | import trojanBlocks from "./trojanBlocks/reducer"
14 |
15 | const PERSISTED_KEYS: string[] = ["user", "transactions", "lists"]
16 |
17 | const store = configureStore({
18 | reducer: {
19 | application,
20 | user,
21 | transactions,
22 | multicall,
23 | lists,
24 |
25 | freshTokens,
26 | trojanTxs,
27 | trojanBlocks,
28 | },
29 | middleware: [...getDefaultMiddleware({ thunk: false }), save({ states: PERSISTED_KEYS, debounce: 1000 })],
30 | preloadedState: load({ states: PERSISTED_KEYS }),
31 | })
32 |
33 | store.dispatch(updateVersion())
34 |
35 | export default store
36 |
37 | export type AppState = ReturnType
38 | export type AppDispatch = typeof store.dispatch
39 |
--------------------------------------------------------------------------------
/client/src/state/lists/actions.ts:
--------------------------------------------------------------------------------
1 | import { ActionCreatorWithPayload, createAction } from "@reduxjs/toolkit"
2 | import { TokenList, Version } from "@uniswap/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 | // add and remove from list options
14 | export const addList = createAction("lists/addList")
15 | export const removeList = createAction("lists/removeList")
16 |
17 | // select which lists to search across from loaded lists
18 | export const enableList = createAction("lists/enableList")
19 | export const disableList = createAction("lists/disableList")
20 |
21 | // versioning
22 | export const acceptListUpdate = createAction("lists/acceptListUpdate")
23 | export const rejectVersionUpdate = createAction("lists/rejectVersionUpdate")
24 |
--------------------------------------------------------------------------------
/client/src/state/multicall/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from "@reduxjs/toolkit"
2 |
3 | export interface Call {
4 | address: string
5 | callData: string
6 | gasRequired?: number
7 | }
8 |
9 | const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/
10 | const LOWER_HEX_REGEX = /^0x[a-f0-9]*$/
11 | export function toCallKey(call: Call): string {
12 | if (!ADDRESS_REGEX.test(call.address)) {
13 | throw new Error(`Invalid address: ${call.address}`)
14 | }
15 | if (!LOWER_HEX_REGEX.test(call.callData)) {
16 | throw new Error(`Invalid hex: ${call.callData}`)
17 | }
18 | let key = `${call.address}-${call.callData}`
19 | if (call.gasRequired) {
20 | if (!Number.isSafeInteger(call.gasRequired)) {
21 | throw new Error(`Invalid number: ${call.gasRequired}`)
22 | }
23 | key += `-${call.gasRequired}`
24 | }
25 | return key
26 | }
27 |
28 | export function parseCallKey(callKey: string): Call {
29 | const pcs = callKey.split("-")
30 | if (![2, 3].includes(pcs.length)) {
31 | throw new Error(`Invalid call key: ${callKey}`)
32 | }
33 | return {
34 | address: pcs[0],
35 | callData: pcs[1],
36 | ...(pcs[2] ? { gasRequired: Number.parseInt(pcs[2]) } : {}),
37 | }
38 | }
39 |
40 | export interface ListenerOptions {
41 | // how often this data should be fetched, by default 1
42 | readonly blocksPerFetch?: number
43 | }
44 |
45 | export const addMulticallListeners = createAction<{ chainId: number; calls: Call[]; options?: ListenerOptions }>(
46 | "multicall/addMulticallListeners"
47 | )
48 | export const removeMulticallListeners = createAction<{ chainId: number; calls: Call[]; options?: ListenerOptions }>(
49 | "multicall/removeMulticallListeners"
50 | )
51 | export const fetchingMulticallResults = createAction<{ chainId: number; calls: Call[]; fetchingBlockNumber: number }>(
52 | "multicall/fetchingMulticallResults"
53 | )
54 | export const errorFetchingMulticallResults = createAction<{
55 | chainId: number
56 | calls: Call[]
57 | fetchingBlockNumber: number
58 | }>("multicall/errorFetchingMulticallResults")
59 | export const updateMulticallResults = createAction<{
60 | chainId: number
61 | blockNumber: number
62 | results: {
63 | [callKey: string]: string | null
64 | }
65 | }>("multicall/updateMulticallResults")
66 |
--------------------------------------------------------------------------------
/client/src/state/transactions/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from "@reduxjs/toolkit"
2 | import { ChainId } from "@uniswap/sdk-core"
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 |
--------------------------------------------------------------------------------
/client/src/state/transactions/reducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from "@reduxjs/toolkit"
2 | import {
3 | addTransaction,
4 | checkedTransaction,
5 | clearAllTransactions,
6 | finalizeTransaction,
7 | SerializableTransactionReceipt,
8 | } from "./actions"
9 |
10 | const now = () => new Date().getTime()
11 |
12 | export interface TransactionDetails {
13 | hash: string
14 | approval?: { tokenAddress: string; spender: string }
15 | summary?: string
16 | claim?: { recipient: string }
17 | receipt?: SerializableTransactionReceipt
18 | lastCheckedBlockNumber?: number
19 | addedTime: number
20 | confirmedTime?: number
21 | from: string
22 | }
23 |
24 | export interface TransactionState {
25 | [chainId: number]: {
26 | [txHash: string]: TransactionDetails
27 | }
28 | }
29 |
30 | export const initialState: TransactionState = {}
31 |
32 | export default createReducer(initialState, (builder) =>
33 | builder
34 | .addCase(addTransaction, (transactions, { payload: { chainId, from, hash, approval, summary, claim } }) => {
35 | if (transactions[chainId]?.[hash]) {
36 | }
37 | const txs = transactions[chainId] ?? {}
38 | txs[hash] = { hash, approval, summary, claim, from, addedTime: now() }
39 | transactions[chainId] = txs
40 | })
41 | .addCase(clearAllTransactions, (transactions, { payload: { chainId } }) => {
42 | if (!transactions[chainId]) return
43 | transactions[chainId] = {}
44 | })
45 | .addCase(checkedTransaction, (transactions, { payload: { chainId, hash, blockNumber } }) => {
46 | const tx = transactions[chainId]?.[hash]
47 | if (!tx) {
48 | return
49 | }
50 | if (!tx.lastCheckedBlockNumber) {
51 | tx.lastCheckedBlockNumber = blockNumber
52 | } else {
53 | tx.lastCheckedBlockNumber = Math.max(blockNumber, tx.lastCheckedBlockNumber)
54 | }
55 | })
56 | .addCase(finalizeTransaction, (transactions, { payload: { hash, chainId, receipt } }) => {
57 | const tx = transactions[chainId]?.[hash]
58 | if (!tx) {
59 | return
60 | }
61 | tx.receipt = receipt
62 | tx.confirmedTime = now()
63 | })
64 | )
65 |
--------------------------------------------------------------------------------
/client/src/state/trojanBlocks/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from "@reduxjs/toolkit"
2 |
3 | export const setBlock = createAction<{ block: any }>("trojanBlocks/setBlock")
4 | export const clearBlock = createAction("trojanBlocks/clearBlock")
5 |
--------------------------------------------------------------------------------
/client/src/state/trojanBlocks/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react"
2 | import { useDispatch, useSelector } from "react-redux"
3 | import { AppDispatch, AppState } from "../index"
4 | import { clearBlock, setBlock } from "./actions"
5 |
6 | export function useTrojanBlockState(): AppState["trojanBlocks"] {
7 | return useSelector((state) => state.trojanBlocks)
8 | }
9 |
10 | export function useTrojanBlockActionHandlers(): {
11 | onClearBlock: () => void
12 | onSetBlock: (block: any) => void
13 | } {
14 | const dispatch = useDispatch()
15 |
16 | const onClearBlock = useCallback(() => {
17 | dispatch(clearBlock())
18 | }, [dispatch])
19 |
20 | const onSetBlock = useCallback(
21 | (block: any) => {
22 | dispatch(
23 | setBlock({
24 | block,
25 | })
26 | )
27 | },
28 | [dispatch]
29 | )
30 |
31 | return {
32 | onClearBlock,
33 | onSetBlock,
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/client/src/state/trojanBlocks/reducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from "@reduxjs/toolkit"
2 | import { clearBlock, setBlock } from "./actions"
3 |
4 | export interface TrojanBlocksState {
5 | readonly block: any
6 | }
7 |
8 | const initialState: TrojanBlocksState = {
9 | block: null,
10 | }
11 |
12 | export default createReducer(initialState, (builder) =>
13 | builder
14 | .addCase(setBlock, (state, { payload: { block } }) => {
15 | // the case where we have to swap the order
16 | state.block = block
17 | })
18 |
19 | .addCase(clearBlock, (state) => {
20 | state.block = null
21 | })
22 | )
23 |
--------------------------------------------------------------------------------
/client/src/state/trojanTxs/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from "@reduxjs/toolkit"
2 | import { ITransaction } from "types/trojan/tx-model"
3 |
4 | export const addPending = createAction("trojanTxs/addPending")
5 | export const removePending = createAction("trojanTxs/removePending")
6 |
7 | export const addConfirmed = createAction("trojanTxs/addConfirmed")
8 | export const removeConfirmed = createAction("trojanTxs/removeConfirmed")
9 |
10 | export const loadPendings = createAction>("trojanTxs/loadPendings")
11 | export const loadConfirmed = createAction>("trojanTxs/loadConfirmed")
12 |
13 | export const selectCurrency = createAction("trojanTxs/selectCurrency")
14 |
15 | export const resetStateTx = createAction("trojanTxs/resetStateTx")
16 |
--------------------------------------------------------------------------------
/client/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 updateUserSingleHopOnly = createAction<{ userSingleHopOnly: boolean }>("user/updateUserSingleHopOnly")
20 | export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number | "auto" }>(
21 | "user/updateUserSlippageTolerance"
22 | )
23 | export const updateUserDeadline = createAction<{ userDeadline: number }>("user/updateUserDeadline")
24 | export const addSerializedToken = createAction<{ serializedToken: SerializedToken }>("user/addSerializedToken")
25 | export const removeSerializedToken = createAction<{ chainId: number; address: string }>("user/removeSerializedToken")
26 | export const addSerializedPair = createAction<{ serializedPair: SerializedPair }>("user/addSerializedPair")
27 | export const removeSerializedPair = createAction<{ chainId: number; tokenAAddress: string; tokenBAddress: string }>(
28 | "user/removeSerializedPair"
29 | )
30 | export const toggleURLWarning = createAction("app/toggleURLWarning")
31 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/theme/rebass.d.ts:
--------------------------------------------------------------------------------
1 | import { InterpolationWithTheme } from "@emotion/core"
2 | import {
3 | BoxProps as BoxP,
4 | ButtonProps as ButtonP,
5 | FlexProps as FlexP,
6 | LinkProps as LinkP,
7 | TextProps as TextP,
8 | } from "rebass"
9 |
10 | declare module "rebass" {
11 | interface BoxProps extends BoxP {
12 | css?: InterpolationWithTheme
13 | }
14 | interface ButtonProps extends ButtonP {
15 | css?: InterpolationWithTheme
16 | }
17 | interface FlexProps extends FlexP {
18 | css?: InterpolationWithTheme
19 | }
20 | interface LinkProps extends LinkP {
21 | css?: InterpolationWithTheme
22 | }
23 | interface TextProps extends TextP {
24 | css?: InterpolationWithTheme
25 | }
26 | }
27 |
28 | declare global {
29 | namespace JSX {
30 | interface IntrinsicAttributes {
31 | css?: InterpolationWithTheme
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/client/src/theme/styled.d.ts:
--------------------------------------------------------------------------------
1 | import { FlattenSimpleInterpolation, ThemedCssFunction } from "styled-components"
2 |
3 | export type Color = string
4 | export interface Colors {
5 | // base
6 | white: Color
7 | black: Color
8 |
9 | // text
10 | text1: Color
11 | text2: Color
12 | text3: Color
13 | text4: Color
14 | text5: Color
15 |
16 | // backgrounds / greys
17 | bg0: Color
18 | bg1: Color
19 | bg2: Color
20 | bg3: Color
21 | bg4: Color
22 | bg5: Color
23 | bg6: Color
24 |
25 | modalBG: Color
26 | advancedBG: Color
27 |
28 | //blues
29 | primary1: Color
30 | primary2: Color
31 | primary3: Color
32 | primary4: Color
33 | primary5: Color
34 |
35 | primaryText1: Color
36 |
37 | // pinks
38 | secondary1: Color
39 | secondary2: Color
40 | secondary3: Color
41 |
42 | // other
43 | red1: Color
44 | red2: Color
45 | red3: Color
46 | green1: Color
47 | yellow1: Color
48 | yellow2: Color
49 | yellow3: Color
50 | blue1: Color
51 | blue2: Color
52 |
53 | error: Color
54 | success: Color
55 | warning: Color
56 | }
57 |
58 | export interface Grids {
59 | sm: number
60 | md: number
61 | lg: number
62 | }
63 |
64 | declare module "styled-components" {
65 | export interface DefaultTheme extends Colors {
66 | grids: Grids
67 |
68 | // shadows
69 | shadow1: string
70 |
71 | // media queries
72 | mediaWidth: {
73 | upToExtraSmall: ThemedCssFunction
74 | upToSmall: ThemedCssFunction
75 | upToMedium: ThemedCssFunction
76 | upToLarge: ThemedCssFunction
77 | }
78 |
79 | // css snippets
80 | flexColumnNoWrap: FlattenSimpleInterpolation
81 | flexRowNoWrap: FlattenSimpleInterpolation
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/client/src/types/position.d.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@ethersproject/bignumber'
2 |
3 | export interface PositionDetails {
4 | nonce: BigNumber
5 | tokenId: BigNumber
6 | operator: string
7 | token0: string
8 | token1: string
9 | fee: number
10 | tickLower: number
11 | tickUpper: number
12 | liquidity: BigNumber
13 | feeGrowthInside0LastX128: BigNumber
14 | feeGrowthInside1LastX128: BigNumber
15 | tokensOwed0: BigNumber
16 | tokensOwed1: BigNumber
17 | }
18 |
--------------------------------------------------------------------------------
/client/src/types/trojan/tx-model.d.ts:
--------------------------------------------------------------------------------
1 | export interface ITransaction {
2 | status: string // current status of the transaction
3 | hash: string
4 | txHash?: string
5 | to: string
6 |
7 | isV2?: boolean
8 | isV3?: boolean
9 | isV2Sushi?: boolean
10 | isV2Bal?: boolean
11 | isV2_1Inch?: boolean
12 | isV3_1Inch?: boolean
13 |
14 | from: string
15 | gas: any
16 | gasPrice: any
17 | gasUsed: string // present on on-chain txns
18 | nonce?: number
19 | value?: any
20 | blockHash?: string
21 | cumulativeGasUsed?: number
22 | transactionHash?: string
23 | blockNumber?: number
24 | data: string
25 | timestampTx: number // the UTC time of first detection of current status
26 | transactionIndex?: number // optional, present if status confirmed, failed
27 | logsBloom?: string
28 |
29 | // CUSTOM DATA
30 | links?: {
31 | etherscan?: string
32 | }
33 | fromTokenAddress: string
34 | midTokenAddress?: string
35 | toTokenAddress: string
36 | checkedPath?: Array
37 | // CUSTOM DATA
38 | whaleData?: any
39 | // FOR PENDING
40 | mempoolData?: any
41 | // FOR CONFIRMED
42 | logs?: any
43 | events?: any
44 | // wich server, when
45 | notes?: {
46 | message?: string
47 | timestampTx?: number
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/client/src/utils/calculateGasMargin.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "@ethersproject/bignumber"
2 |
3 | // add 20%
4 | export function calculateGasMargin(value: BigNumber): BigNumber {
5 | return value.mul(BigNumber.from(10000 + 2000)).div(BigNumber.from(10000))
6 | }
7 |
--------------------------------------------------------------------------------
/client/src/utils/calculateSlippageAmount.ts:
--------------------------------------------------------------------------------
1 | import { Currency, CurrencyAmount, Fraction, Percent } from "@uniswap/sdk-core"
2 | import JSBI from "jsbi"
3 |
4 | const ONE = new Fraction(1, 1)
5 |
6 | export function calculateSlippageAmount(value: CurrencyAmount, slippage: Percent): [JSBI, JSBI] {
7 | if (slippage.lessThan(0) || slippage.greaterThan(ONE)) throw new Error("Unexpected slippage")
8 | return [value.multiply(ONE.subtract(slippage)).quotient, value.multiply(ONE.add(slippage)).quotient]
9 | }
10 |
--------------------------------------------------------------------------------
/client/src/utils/chunkArray.ts:
--------------------------------------------------------------------------------
1 | const CONSERVATIVE_BLOCK_GAS_LIMIT = 10_000_000 // conservative, hard-coded estimate of the current block gas limit
2 | export const DEFAULT_GAS_REQUIRED = 200_000 // the default value for calls that don't specify gasRequired
3 |
4 | // chunks array into chunks
5 | // evenly distributes items among the chunks
6 | export default function chunkArray(items: T[], gasLimit = CONSERVATIVE_BLOCK_GAS_LIMIT * 10): T[][] {
7 | const chunks: T[][] = []
8 | let currentChunk: T[] = []
9 | let currentChunkCumulativeGas = 0
10 |
11 | for (let i = 0; i < items.length; i++) {
12 | const item = items[i]
13 |
14 | // calculate the gas required by the current item
15 | const gasRequired = (item as { gasRequired?: number })?.gasRequired ?? DEFAULT_GAS_REQUIRED
16 |
17 | // if the current chunk is empty, or the current item wouldn't push it over the gas limit,
18 | // append the current item and increment the cumulative gas
19 | if (currentChunk.length === 0 || currentChunkCumulativeGas + gasRequired < gasLimit) {
20 | currentChunk.push(item)
21 | currentChunkCumulativeGas += gasRequired
22 | } else {
23 | // otherwise, push the current chunk and create a new chunk
24 | chunks.push(currentChunk)
25 | currentChunk = [item]
26 | currentChunkCumulativeGas = gasRequired
27 | }
28 | }
29 | if (currentChunk.length > 0) chunks.push(currentChunk)
30 |
31 | return chunks
32 | }
33 |
--------------------------------------------------------------------------------
/client/src/utils/computeFiatValuePriceImpact.tsx:
--------------------------------------------------------------------------------
1 | import { Token, CurrencyAmount, currencyEquals, Percent } from "@uniswap/sdk-core"
2 | import JSBI from "jsbi"
3 | import { ONE_HUNDRED_PERCENT } from "../constants/misc"
4 |
5 | export function computeFiatValuePriceImpact(
6 | fiatValueInput: CurrencyAmount | undefined | null,
7 | fiatValueOutput: CurrencyAmount | undefined | null
8 | ): Percent | undefined {
9 | if (!fiatValueOutput || !fiatValueInput) return undefined
10 | if (!currencyEquals(fiatValueInput.currency, fiatValueOutput.currency)) return undefined
11 | if (JSBI.equal(fiatValueInput.quotient, JSBI.BigInt(0))) return undefined
12 | const pct = ONE_HUNDRED_PERCENT.subtract(fiatValueOutput.divide(fiatValueInput))
13 | return new Percent(pct.numerator, pct.denominator)
14 | }
15 |
--------------------------------------------------------------------------------
/client/src/utils/constructSameAddressMap.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from "@uniswap/sdk-core"
2 |
3 | export function constructSameAddressMap(address: T): { [chainId in ChainId]: T } {
4 | return {
5 | [ChainId.MAINNET]: address,
6 | [ChainId.ROPSTEN]: address,
7 | [ChainId.KOVAN]: address,
8 | [ChainId.RINKEBY]: address,
9 | [ChainId.GÖRLI]: address,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/client/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("utf-8")
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 |
--------------------------------------------------------------------------------
/client/src/utils/currencyId.ts:
--------------------------------------------------------------------------------
1 | import { Currency } from "@uniswap/sdk-core"
2 |
3 | export function currencyId(currency: Currency): string {
4 | if (currency.isEther) return "ETH"
5 | if (currency.isToken) return currency.address
6 | throw new Error("invalid currency")
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/utils/formatTokenAmount.ts:
--------------------------------------------------------------------------------
1 | import { Price, CurrencyAmount, Currency, Fraction } from "@uniswap/sdk-core"
2 | import JSBI from "jsbi"
3 |
4 | export function formatTokenAmount(amount: CurrencyAmount | undefined, sigFigs: number) {
5 | if (!amount) {
6 | return "-"
7 | }
8 |
9 | if (JSBI.equal(amount.quotient, JSBI.BigInt(0))) {
10 | return "0"
11 | }
12 |
13 | if (amount.divide(amount.decimalScale).lessThan(new Fraction(1, 100000))) {
14 | return "<0.00001"
15 | }
16 |
17 | return amount.toSignificant(sigFigs)
18 | }
19 |
20 | export function formatPrice(price: Price | undefined, sigFigs: number) {
21 | if (!price) {
22 | return "-"
23 | }
24 |
25 | if (parseFloat(price.toFixed(sigFigs)) < 0.0001) {
26 | return "<0.0001"
27 | }
28 |
29 | return price.toSignificant(sigFigs)
30 | }
31 |
--------------------------------------------------------------------------------
/client/src/utils/getExplorerLink.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from "@uniswap/sdk-core"
2 |
3 | const ETHERSCAN_PREFIXES: { [chainId in ChainId]: string } = {
4 | 1: "",
5 | 3: "ropsten.",
6 | 4: "rinkeby.",
7 | 5: "goerli.",
8 | 42: "kovan.",
9 | }
10 |
11 | export enum ExplorerDataType {
12 | TRANSACTION = "transaction",
13 | TOKEN = "token",
14 | ADDRESS = "address",
15 | BLOCK = "block",
16 | }
17 |
18 | /**
19 | * Return the explorer link for the given data and data type
20 | * @param chainId the ID of the chain for which to return the data
21 | * @param data the data to return a link for
22 | * @param type the type of the data
23 | */
24 | export function getExplorerLink(chainId: ChainId, data: string, type: ExplorerDataType): string {
25 | const prefix = `https://${ETHERSCAN_PREFIXES[chainId] || ETHERSCAN_PREFIXES[1]}etherscan.io`
26 |
27 | switch (type) {
28 | case ExplorerDataType.TRANSACTION: {
29 | return `${prefix}/tx/${data}`
30 | }
31 | case ExplorerDataType.TOKEN: {
32 | return `${prefix}/token/${data}`
33 | }
34 | case ExplorerDataType.BLOCK: {
35 | return `${prefix}/block/${data}`
36 | }
37 | case ExplorerDataType.ADDRESS:
38 | default: {
39 | return `${prefix}/address/${data}`
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/client/src/utils/getLibrary.ts:
--------------------------------------------------------------------------------
1 | import { Web3Provider, Network } from "@ethersproject/providers"
2 |
3 | class WorkaroundWeb3Provider extends Web3Provider {
4 | private _detectNetworkResult: Promise | null = null
5 |
6 | async detectNetwork(): Promise {
7 | return this._detectNetworkResult ?? (this._detectNetworkResult = this._uncachedDetectNetwork())
8 | }
9 | }
10 |
11 | export default function getLibrary(provider: any): Web3Provider {
12 | const library = new WorkaroundWeb3Provider(
13 | provider,
14 | typeof provider.chainId === "number"
15 | ? provider.chainId
16 | : typeof provider.chainId === "string"
17 | ? parseInt(provider.chainId)
18 | : "any"
19 | )
20 | library.pollingInterval = 15000
21 | return library
22 | }
23 |
--------------------------------------------------------------------------------
/client/src/utils/getTickToPrice.ts:
--------------------------------------------------------------------------------
1 | import { Token, Price } from "@uniswap/sdk-core"
2 | import { tickToPrice } from "@uniswap/v3-sdk"
3 |
4 | export function getTickToPrice(baseToken?: Token, quoteToken?: Token, tick?: number): Price | undefined {
5 | if (!baseToken || !quoteToken || typeof tick !== "number") {
6 | return undefined
7 | }
8 | return tickToPrice(baseToken, quoteToken, tick)
9 | }
10 |
--------------------------------------------------------------------------------
/client/src/utils/getTradeVersion.ts:
--------------------------------------------------------------------------------
1 | import { Currency, TradeType } from "@uniswap/sdk-core"
2 | import { Trade as V2Trade } from "@uniswap/v2-sdk"
3 | import { Trade as V3Trade } from "@uniswap/v3-sdk"
4 | import { Version } from "../hooks/useToggledVersion"
5 |
6 | export function getTradeVersion(
7 | trade?: V2Trade | V3Trade
8 | ): Version | undefined {
9 | if (!trade) return undefined
10 | if (trade instanceof V2Trade) return Version.v2
11 | return Version.v3
12 | }
13 |
--------------------------------------------------------------------------------
/client/src/utils/getUserAgent.ts:
--------------------------------------------------------------------------------
1 | import { UAParser } from "ua-parser-js"
2 |
3 | export function getUserAgent(): UAParser.IResult {
4 | const parser = new UAParser(window.navigator.userAgent)
5 | return parser.getResult()
6 | }
7 |
--------------------------------------------------------------------------------
/client/src/utils/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 | i18next
7 | .use(XHR)
8 | .use(LanguageDetector)
9 | .use(initReactI18next)
10 | .init({
11 | backend: {
12 | loadPath: `./locales/{{lng}}.json`,
13 | },
14 | react: {
15 | useSuspense: true,
16 | },
17 | fallbackLng: "en",
18 | preload: ["en"],
19 | keySeparator: false,
20 | interpolation: { escapeValue: false },
21 | })
22 |
23 | export default i18next
24 |
--------------------------------------------------------------------------------
/client/src/utils/isTradeBetter.ts:
--------------------------------------------------------------------------------
1 | import { ZERO_PERCENT, ONE_HUNDRED_PERCENT } from "../constants/misc"
2 | import { Percent, currencyEquals, Currency, TradeType } from "@uniswap/sdk-core"
3 | import { Trade as V2Trade } from "@uniswap/v2-sdk"
4 | import { Trade as V3Trade } from "@uniswap/v3-sdk"
5 |
6 | // returns whether tradeB is better than tradeA by at least a threshold percentage amount
7 | export function isTradeBetter(
8 | tradeA: V2Trade | V3Trade | undefined | null,
9 | tradeB: V2Trade | V3Trade | undefined | null,
10 | minimumDelta: Percent = ZERO_PERCENT
11 | ): boolean | undefined {
12 | if (tradeA && !tradeB) return false
13 | if (tradeB && !tradeA) return true
14 | if (!tradeA || !tradeB) return undefined
15 |
16 | if (
17 | tradeA.tradeType !== tradeB.tradeType ||
18 | !currencyEquals(tradeA.inputAmount.currency, tradeB.inputAmount.currency) ||
19 | !currencyEquals(tradeB.outputAmount.currency, tradeB.outputAmount.currency)
20 | ) {
21 | throw new Error("Comparing incomparable trades")
22 | }
23 |
24 | if (minimumDelta.equalTo(ZERO_PERCENT)) {
25 | return tradeA.executionPrice.lessThan(tradeB.executionPrice)
26 | } else {
27 | return tradeA.executionPrice.asFraction
28 | .multiply(minimumDelta.add(ONE_HUNDRED_PERCENT))
29 | .lessThan(tradeB.executionPrice)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/utils/listSort.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_LIST_OF_LISTS } from "./../constants/lists"
2 |
3 | const DEFAULT_LIST_PRIORITIES = DEFAULT_LIST_OF_LISTS.reduce<{ [listUrl: string]: number }>((memo, listUrl, index) => {
4 | memo[listUrl] = index + 1
5 | return memo
6 | }, {})
7 |
8 | // use ordering of default list of lists to assign priority
9 | export default function sortByListPriority(urlA: string, urlB: string) {
10 | if (DEFAULT_LIST_PRIORITIES[urlA] && DEFAULT_LIST_PRIORITIES[urlB]) {
11 | return DEFAULT_LIST_PRIORITIES[urlA] - DEFAULT_LIST_PRIORITIES[urlB]
12 | }
13 | return 0
14 | }
15 |
--------------------------------------------------------------------------------
/client/src/utils/listVersionLabel.ts:
--------------------------------------------------------------------------------
1 | import { Version } from "@uniswap/token-lists"
2 |
3 | export default function listVersionLabel(version: Version): string {
4 | return `v${version.major}.${version.minor}.${version.patch}`
5 | }
6 |
--------------------------------------------------------------------------------
/client/src/utils/maxAmountSpend.ts:
--------------------------------------------------------------------------------
1 | import { Currency, CurrencyAmount } from "@uniswap/sdk-core"
2 | import JSBI from "jsbi"
3 | import { MIN_ETH } from "../constants/misc"
4 |
5 | /**
6 | * Given some token amount, return the max that can be spent of it
7 | * @param currencyAmount to return max of
8 | */
9 | export function maxAmountSpend(currencyAmount?: CurrencyAmount): CurrencyAmount | undefined {
10 | if (!currencyAmount) return undefined
11 | if (currencyAmount.currency.isEther) {
12 | if (JSBI.greaterThan(currencyAmount.quotient, MIN_ETH)) {
13 | return CurrencyAmount.ether(JSBI.subtract(currencyAmount.quotient, MIN_ETH))
14 | } else {
15 | return CurrencyAmount.ether(JSBI.BigInt(0))
16 | }
17 | }
18 | return currencyAmount
19 | }
20 |
--------------------------------------------------------------------------------
/client/src/utils/parseENSAddress.ts:
--------------------------------------------------------------------------------
1 | const ENS_NAME_REGEX = /^(([a-zA-Z0-9]+(-[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[4] }
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/utils/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?: true
12 | on?: (...args: any[]) => void
13 | removeListener?: (...args: any[]) => void
14 | autoRefreshOnNetworkChange?: boolean
15 | }
16 | web3?: Record
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 |
--------------------------------------------------------------------------------
/client/src/utils/resolveENSContentHash.ts:
--------------------------------------------------------------------------------
1 | import { Contract } from "@ethersproject/contracts"
2 | import { Provider } from "@ethersproject/abstract-provider"
3 | import { namehash } from "ethers/lib/utils"
4 |
5 | const REGISTRAR_ABI = [
6 | {
7 | constant: true,
8 | inputs: [
9 | {
10 | name: "node",
11 | type: "bytes32",
12 | },
13 | ],
14 | name: "resolver",
15 | outputs: [
16 | {
17 | name: "resolverAddress",
18 | type: "address",
19 | },
20 | ],
21 | payable: false,
22 | stateMutability: "view",
23 | type: "function",
24 | },
25 | ]
26 | const REGISTRAR_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
27 |
28 | const RESOLVER_ABI = [
29 | {
30 | constant: true,
31 | inputs: [
32 | {
33 | internalType: "bytes32",
34 | name: "node",
35 | type: "bytes32",
36 | },
37 | ],
38 | name: "contenthash",
39 | outputs: [
40 | {
41 | internalType: "bytes",
42 | name: "",
43 | type: "bytes",
44 | },
45 | ],
46 | payable: false,
47 | stateMutability: "view",
48 | type: "function",
49 | },
50 | ]
51 |
52 | // cache the resolver contracts since most of them are the public resolver
53 | function resolverContract(resolverAddress: string, provider: Provider): Contract {
54 | return new Contract(resolverAddress, RESOLVER_ABI, provider)
55 | }
56 |
57 | /**
58 | * Fetches and decodes the result of an ENS contenthash lookup on mainnet to a URI
59 | * @param ensName to resolve
60 | * @param provider provider to use to fetch the data
61 | */
62 | export default async function resolveENSContentHash(ensName: string, provider: Provider): Promise {
63 | const ensRegistrarContract = new Contract(REGISTRAR_ADDRESS, REGISTRAR_ABI, provider)
64 | const hash = namehash(ensName)
65 | const resolverAddress = await ensRegistrarContract.resolver(hash)
66 | return resolverContract(resolverAddress, provider).contenthash(hash)
67 | }
68 |
--------------------------------------------------------------------------------
/client/src/utils/retry.ts:
--------------------------------------------------------------------------------
1 | function wait(ms: number): Promise {
2 | return new Promise((resolve) => setTimeout(resolve, ms))
3 | }
4 |
5 | function waitRandom(min: number, max: number): Promise {
6 | return wait(min + Math.round(Math.random() * Math.max(0, max - min)))
7 | }
8 |
9 | /**
10 | * This error is thrown if the function is cancelled before completing
11 | */
12 | export class CancelledError extends Error {
13 | constructor() {
14 | super("Cancelled")
15 | }
16 | }
17 |
18 | /**
19 | * Throw this error if the function should retry
20 | */
21 | export class RetryableError extends Error {}
22 |
23 | /**
24 | * Retries the function that returns the promise until the promise successfully resolves up to n retries
25 | * @param fn function to retry
26 | * @param n how many times to retry
27 | * @param minWait min wait between retries in ms
28 | * @param maxWait max wait between retries in ms
29 | */
30 | export function retry(
31 | fn: () => Promise,
32 | { n, minWait, maxWait }: { n: number; minWait: number; maxWait: number }
33 | ): { promise: Promise; cancel: () => void } {
34 | let completed = false
35 | let rejectCancelled: (error: Error) => void
36 | const promise = new Promise(async (resolve, reject) => {
37 | rejectCancelled = reject
38 | while (true) {
39 | let result: T
40 | try {
41 | result = await fn()
42 | if (!completed) {
43 | resolve(result)
44 | completed = true
45 | }
46 | break
47 | } catch (error) {
48 | if (completed) {
49 | break
50 | }
51 | if (n <= 0 || !(error instanceof RetryableError)) {
52 | reject(error)
53 | completed = true
54 | break
55 | }
56 | n--
57 | }
58 | await waitRandom(minWait, maxWait)
59 | }
60 | })
61 | return {
62 | promise,
63 | cancel: () => {
64 | if (completed) return
65 | completed = true
66 | rejectCancelled(new CancelledError())
67 | },
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/client/src/utils/supportedChainId.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from "@uniswap/sdk-core"
2 |
3 | /**
4 | * Returns the input chain ID if chain is supported. If not, return undefined
5 | * @param chainId a chain ID, which will be returned if it is a supported chain ID
6 | */
7 | export function supportedChainId(chainId: number): ChainId | undefined {
8 | if (chainId in ChainId) {
9 | return chainId
10 | }
11 | return undefined
12 | }
13 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/src/utils/wrappedCurrency.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChainId,
3 | Currency,
4 | ETHER,
5 | Token,
6 | CurrencyAmount,
7 | wrappedCurrency as wrappedCurrencyInternal,
8 | wrappedCurrencyAmount as wrappedCurrencyAmountInternal,
9 | WETH9,
10 | } from "@uniswap/sdk-core"
11 | import { supportedChainId } from "./supportedChainId"
12 |
13 | export function wrappedCurrency(currency: Currency | undefined, chainId: ChainId | undefined): Token | undefined {
14 | return chainId && currency ? wrappedCurrencyInternal(currency, chainId) : undefined
15 | }
16 |
17 | export function wrappedCurrencyAmount(
18 | currencyAmount: CurrencyAmount | undefined,
19 | chainId: ChainId | undefined
20 | ): CurrencyAmount | undefined {
21 | return currencyAmount && chainId ? wrappedCurrencyAmountInternal(currencyAmount, chainId) : undefined
22 | }
23 |
24 | export function unwrappedToken(token: Token): Currency {
25 | if (token.isEther) return token
26 | const formattedChainId = supportedChainId(token.chainId)
27 | if (formattedChainId && token.equals(WETH9[formattedChainId])) return ETHER
28 | return token
29 | }
30 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "strict": true,
12 | "alwaysStrict": true,
13 | "strictNullChecks": true,
14 | "noUnusedLocals": false,
15 | "noFallthroughCasesInSwitch": true,
16 | "noImplicitAny": true,
17 | "noImplicitThis": true,
18 | "noImplicitReturns": true,
19 | "moduleResolution": "node",
20 | "resolveJsonModule": true,
21 | "isolatedModules": true,
22 | "jsx": "react-jsx",
23 | "downlevelIteration": true,
24 | "allowSyntheticDefaultImports": true,
25 | "types": ["react-spring", "jest"],
26 | "baseUrl": "src"
27 | },
28 | "exclude": ["node_modules", "cypress"],
29 | "include": ["./src/**/*.ts", "./src/**/*.tsx", "src/components/Confetti/index.js"]
30 | }
31 |
--------------------------------------------------------------------------------
/src/fix-db.ts:
--------------------------------------------------------------------------------
1 | import { startMongo, models } from './utils/mongo/config';
2 | import { _log, timeout } from './utils/configs/utils';
3 | import { pendingOld } from './utils/mongo/saveConfirmed';
4 |
5 | const { txM, g } = models;
6 |
7 | const FIXER_INTERVAL: number = 60000 * 1;
8 | const FIXER_INTERVAL_G: number = 60000 * 60;
9 |
10 | const serverName = 'dbFixer';
11 |
12 | startMongo(serverName).then(async (started) => {
13 | await timeout(5000);
14 |
15 | if (started) {
16 | _log.start('startFixer Go run every', FIXER_INTERVAL / 60000, 'minutes');
17 | startFixerJustKill();
18 | startFixerG();
19 |
20 | setInterval(() => {
21 | startFixerJustKill();
22 | }, FIXER_INTERVAL);
23 |
24 | setInterval(() => {
25 | startFixerG();
26 | }, FIXER_INTERVAL_G);
27 | } else {
28 | _log.warn('---> startFixer ', started);
29 | }
30 | });
31 |
32 | const startFixerJustKill = async () => {
33 | const old_txs = 4;
34 |
35 | let end = new Date();
36 | end.setMinutes(new Date().getMinutes() - old_txs);
37 | _log.start('startFixer starting to fix all txs older than', old_txs, 'minutes');
38 |
39 | const _txM = await txM.pending.find({ timestampTx: { $lt: end.getTime() } }, {});
40 | if (_txM) fixOlds(_txM);
41 | };
42 |
43 | const fixOlds = async (oldTxs: Array) => {
44 | try {
45 | for (const tx of oldTxs) {
46 | let nTx = { ...tx._doc };
47 | delete nTx._id;
48 | pendingOld({ ...nTx }, serverName);
49 | }
50 | } catch (e) {
51 | _log.error('fixOlds catch ', e);
52 | }
53 | };
54 |
55 | const startFixerG = async () => {
56 | const old_g = 240;
57 |
58 | let end = new Date();
59 | end.setMinutes(new Date().getMinutes() - old_g);
60 | _log.start('startFixerG starting to delete all trash hashes older than', old_g, 'minutes');
61 |
62 | await g.hashes.deleteMany({ timestampTx: { $lt: end.getTime() } }, {});
63 | await g.trash.deleteMany({ timestampTx: { $lt: end.getTime() } }, {});
64 | };
65 |
--------------------------------------------------------------------------------
/src/listener-mempool.ts:
--------------------------------------------------------------------------------
1 | import { startMongo, models } from './utils/mongo/config';
2 | import { KEYS, nowMs, timeout, _log } from './utils/configs/utils';
3 | import { getPendingTxResponse } from './utils/web3/getTransactions';
4 | import { proccessPending as pendingTx_uni_sushi } from './swapsDecoders/_uni_sushi/pending';
5 | import { ethers } from 'ethers';
6 | import { keepAlive } from './utils/web3/wsProvider';
7 |
8 | const { g } = models;
9 | const { whales, hashes } = g;
10 | const serverName = 'qnPending';
11 |
12 | let whalesCache = new Array();
13 |
14 | startMongo(serverName).then(async (started) => {
15 | await timeout(5000);
16 |
17 | if (started) {
18 | whales.find({}, null, {}, (e, docs) => {
19 | if (!e) whalesCache = docs;
20 | });
21 |
22 | startListen();
23 | } else {
24 | _log.warn('---> started ', started);
25 | }
26 | });
27 |
28 | const startListen = () => {
29 | _log.info('startListen - Mempool');
30 | const provider = new ethers.providers.WebSocketProvider(KEYS.CONFIRMED_URL);
31 | keepAlive({
32 | provider,
33 | onDisconnect: (err) => {
34 | startListen();
35 | _log.error('The ws connection was closed', JSON.stringify(err, null, 2));
36 | }
37 | });
38 |
39 | provider._subscribe('pending', ['newPendingTransactions'], async (hash: string) => {
40 | new hashes({
41 | hash,
42 | txHash: hash,
43 | timestampTx: nowMs()
44 | }).save(async (e: any) => {
45 | if (!e) {
46 | const tx = await getPendingTxResponse(hash, provider);
47 | if (tx) {
48 | const whaleData = whalesCache.find((w) => (w ? w.address.toLowerCase() === tx.from.toLowerCase() : false));
49 | pendingTx_uni_sushi(tx, whaleData, false, provider);
50 | }
51 | }
52 | });
53 | });
54 | };
55 |
--------------------------------------------------------------------------------
/src/models/BlockSchema.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'mongoose';
2 |
3 | const BlockHeaderSchema = new Schema({
4 | author: String,
5 | difficulty: String,
6 | extraData: String,
7 | gasLimit: Number,
8 | gasUsed: Number,
9 | hash: String,
10 | miner: String,
11 | nonce: String,
12 | parentHash: String,
13 | size: Number,
14 | sha3Uncles: String,
15 | transactionRoot: String,
16 | stateRoot: String,
17 | receiptRoot: String,
18 | timestamp: String,
19 | number: Number
20 | });
21 |
22 | const BlockNativeSchema = new Schema({
23 | system: String,
24 | network: String,
25 | unit: String,
26 | maxPrice: Number,
27 | currentBlockNumber: Number,
28 | msSinceLastBlock: Number,
29 | blockPrices: Array
30 | });
31 |
32 | const BlockGasSchema = new Schema({
33 | fastest: Number,
34 | fast: Number,
35 | safeLow: Number,
36 | average: Number
37 | });
38 |
39 | const BlockSchema = new Schema(
40 | {
41 | blockLink: String,
42 | blockHash: String,
43 | blockNumber: Number,
44 | fullyUpdated: Boolean,
45 | responseData: BlockNativeSchema,
46 | blockHeader: BlockHeaderSchema,
47 | responseDataGas: BlockGasSchema,
48 | by: String,
49 | timestampTx: Number
50 | },
51 | { autoIndex: false }
52 | );
53 |
54 | export { BlockSchema };
55 |
--------------------------------------------------------------------------------
/src/models/HashSchema.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'mongoose';
2 |
3 | const HashSchema = new Schema(
4 | {
5 | hash: String,
6 | txHash: String,
7 | timestampTx: Number
8 | },
9 | { autoIndex: false }
10 | );
11 |
12 | export { HashSchema };
13 |
--------------------------------------------------------------------------------
/src/models/PoolsSchema.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'mongoose';
2 |
3 | const PoolsSchema = new Schema(
4 | {
5 | address: String,
6 | hashAddress: String,
7 | t0: String,
8 | t1: String,
9 | transactionHash: String,
10 | timestampTx: Number,
11 | blockNumber: Number,
12 | isV2: Boolean,
13 | isV3: Boolean,
14 | isV2Sushi: Boolean,
15 | state: {},
16 | immutables: {},
17 | decoded: {}
18 | },
19 | { autoIndex: false }
20 | );
21 |
22 | export { PoolsSchema };
23 |
--------------------------------------------------------------------------------
/src/models/TokenSchema.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'mongoose';
2 |
3 | const TokenSchema = new Schema(
4 | {
5 | address: String,
6 | hashAddress: String,
7 | chainId: Number,
8 | name: String,
9 | symbol: String,
10 | decimals: Number,
11 | logoURI: String,
12 | by: String,
13 |
14 | timestampTx: Number,
15 | msV3: Number,
16 | msV2: Number,
17 | msV2Sushi: Number,
18 |
19 | isGeneral: Boolean,
20 | isV2: Boolean,
21 | isV3: Boolean,
22 | isV2Sushi: Boolean
23 | },
24 | { autoIndex: false }
25 | );
26 |
27 | export { TokenSchema };
28 |
--------------------------------------------------------------------------------
/src/models/WhalesSchema.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'mongoose';
2 |
3 | const WhalesSchema = new Schema(
4 | {
5 | address: String,
6 | hashAddress: String,
7 | timestampTx: Number,
8 | twitter: {
9 | timestamp: Number,
10 | tweetID: String,
11 | handle: String
12 | }
13 | },
14 | { autoIndex: false }
15 | );
16 |
17 | export { WhalesSchema };
18 |
--------------------------------------------------------------------------------
/src/models/index.ts:
--------------------------------------------------------------------------------
1 | import { TokenSchema } from './TokenSchema';
2 | import { TransactionSchema } from './TransactionSchema';
3 | import { BlockSchema } from './BlockSchema';
4 | import { WhalesSchema } from './WhalesSchema';
5 | import { HashSchema } from './HashSchema';
6 | import { PoolsSchema } from './PoolsSchema';
7 |
8 | export { TokenSchema, TransactionSchema, BlockSchema, WhalesSchema, HashSchema, PoolsSchema };
9 |
--------------------------------------------------------------------------------
/src/swapsDecoders/_uni_sushi/_decoders/getMempoolData.ts:
--------------------------------------------------------------------------------
1 | import { _log } from '../../../utils/configs/utils';
2 | import { ITrojanTx } from '../../../models/TransactionSchema';
3 | import { V3_SWAP_FNAME } from '../../../utils/web3/utils';
4 |
5 | const { EO, EOS, EI, EIS } = V3_SWAP_FNAME;
6 |
7 | export const getMempoolData = async (tx: ITrojanTx, tokens: Array, dexSpace: string) => {
8 | try {
9 | const { value, mempoolData } = tx;
10 | const { decodedData, txMethod } = mempoolData;
11 |
12 | const isExactOutV3 = txMethod === EOS || txMethod === EO;
13 | const isExactInV3 = txMethod === EI || txMethod === EIS;
14 |
15 | let amountIn = null;
16 | let amountOut = null;
17 |
18 | if (isExactOutV3 || isExactInV3) {
19 | amountIn = isExactOutV3 ? decodedData['amountInMaximum'] : decodedData['amountIn'];
20 | amountOut = isExactOutV3 ? decodedData['amountOut'] : decodedData['amountOutMinimum'];
21 | } else {
22 | amountIn = decodedData['amountIn'] || decodedData['amountInMax'] || value;
23 | amountOut = decodedData['amountOut'] || decodedData['amountOutMin'];
24 | }
25 | if (amountIn && amountOut) {
26 | const tl = tokens.length - 1;
27 | const t0 = tokens[0];
28 | const t1 = tokens[tl];
29 |
30 | return {
31 | amountIn: amountIn.toString(),
32 | amountOut: amountOut.toString(),
33 | input: t0,
34 | output: t1,
35 | tokens
36 | };
37 | }
38 | } catch (e: any) {
39 | _log.error('getMempoolData catch', dexSpace, tx.hash, e);
40 | }
41 | return null;
42 | };
43 |
--------------------------------------------------------------------------------
/src/utils/dev-utils/drop-transactions.ts:
--------------------------------------------------------------------------------
1 | import { startMongo, models } from '../mongo/config';
2 | import { _log } from '../configs/utils';
3 |
4 | const serverName = 'dropTransactions';
5 |
6 | startMongo(serverName).then((started) => {
7 | if (started) {
8 | startServer();
9 | } else {
10 | _log.error('---> started ', serverName, started);
11 | }
12 | });
13 |
14 | const startServer = () => {
15 | _log.start('---> startServer ', serverName);
16 | start();
17 | };
18 |
19 | const start = async () => {
20 | _log.start(serverName);
21 | await models.txM.pending.collection.drop();
22 | await models.txM.confirmed.collection.drop();
23 | await models.g.hashes.collection.drop();
24 | await models.g.trash.collection.drop();
25 | _log.ready('done');
26 | };
27 |
--------------------------------------------------------------------------------
/src/utils/dev-utils/reset-blocks.ts:
--------------------------------------------------------------------------------
1 | import { startMongo, models } from '../mongo/config';
2 | import { _log } from '../configs/utils';
3 |
4 | const serverName = 'resetBlocks';
5 |
6 | startMongo(serverName).then((started) => {
7 | if (started) {
8 | startServer();
9 | } else {
10 | _log.error('---> started ', serverName, started);
11 | }
12 | });
13 |
14 | const startServer = () => {
15 | _log.start('---> startServer ', serverName);
16 | start();
17 | };
18 |
19 | const start = async () => {
20 | _log.start(serverName);
21 | await models.g.blocks.deleteMany({}, {});
22 | _log.ready('done');
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/dev-utils/reset-pools.ts:
--------------------------------------------------------------------------------
1 | import { startMongo, models } from '../mongo/config';
2 | import { _log } from '../configs/utils';
3 |
4 | const serverName = 'resetPools';
5 |
6 | startMongo(serverName).then((started) => {
7 | if (started) {
8 | startServer();
9 | } else {
10 | _log.error('---> started ', serverName, started);
11 | }
12 | });
13 |
14 | const startServer = () => {
15 | _log.start('---> startServer ', serverName);
16 | start();
17 | };
18 |
19 | const start = async () => {
20 | _log.start(serverName);
21 | await models.g.pools.deleteMany({}, {});
22 | _log.ready('done');
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/dev-utils/reset-tokens.ts:
--------------------------------------------------------------------------------
1 | import { startMongo, models } from '../mongo/config';
2 | import { _log } from '../configs/utils';
3 |
4 | const serverName = 'resetTokens';
5 |
6 | startMongo(serverName).then((started) => {
7 | if (started) {
8 | startServer();
9 | } else {
10 | _log.error('---> started ', serverName, started);
11 | }
12 | });
13 |
14 | const startServer = () => {
15 | _log.start('---> startServer ', serverName);
16 | start();
17 | };
18 |
19 | const start = async () => {
20 | _log.start(serverName);
21 | await models.g.tokens.deleteMany({}, {});
22 | _log.ready('done');
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/dev-utils/reset-transactions.ts:
--------------------------------------------------------------------------------
1 | import { startMongo, models } from '../mongo/config';
2 | import { _log } from '../configs/utils';
3 |
4 | const serverName = 'resetTransactions';
5 |
6 | startMongo(serverName).then((started) => {
7 | if (started) {
8 | startServer();
9 | } else {
10 | _log.error('---> started ', serverName, started);
11 | }
12 | });
13 |
14 | const startServer = () => {
15 | _log.start('---> startServer ', serverName);
16 | start();
17 | };
18 |
19 | const start = async () => {
20 | _log.start(serverName);
21 | await models.txM.pending.deleteMany({}, {});
22 | await models.txM.confirmed.deleteMany({}, {});
23 | await models.g.trash.deleteMany({}, {});
24 | await models.g.hashes.deleteMany({}, {});
25 | _log.ready('done');
26 | };
27 |
--------------------------------------------------------------------------------
/src/utils/initServer/init-whales.ts:
--------------------------------------------------------------------------------
1 | import { get } from 'https';
2 | import { _log } from '../configs/utils';
3 | import { startMongo } from '../mongo/config';
4 | import { saveWhale } from '../mongo/saveWhale';
5 |
6 | const serverName = 'initWhales';
7 | const URLData = 'https://raw.githubusercontent.com/Uniswap/sybil-list/master/verified.json';
8 |
9 | const startServer = () => {
10 | _log.start('---> startServer ', serverName);
11 | startAddWhales();
12 | };
13 |
14 | startMongo(serverName).then((started) => {
15 | if (started) {
16 | startServer();
17 | } else {
18 | _log.error('---> started ', serverName, started);
19 | }
20 | });
21 |
22 | const startAddWhales = () => {
23 | try {
24 | get(URLData, (res) => {
25 | if (res) {
26 | let body = '';
27 |
28 | res.on('error', (e) => {
29 | _log.warn(e.message);
30 | });
31 |
32 | res.on('data', (chunk) => {
33 | body += chunk;
34 | });
35 |
36 | res.on('end', () => {
37 | const whalesList = JSON.parse(body);
38 | const whalesArray = Object.entries(whalesList);
39 |
40 | for (const whale of whalesArray) {
41 | saveWhale({
42 | address: whale[0],
43 | twitter: whale[1]
44 | });
45 | }
46 | });
47 | }
48 | }).on('error', (e) => {
49 | _log.error(serverName, e.message);
50 | });
51 | } catch (e: any) {
52 | _log.error(serverName, e.message);
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/src/utils/mongo/saveBlock.ts:
--------------------------------------------------------------------------------
1 | import { _log } from '../configs/utils';
2 | import { models } from './config';
3 |
4 | const { g } = models;
5 |
6 | const saveBlock = async (doc: any) => {
7 | try {
8 | new g.blocks({ ...doc, fullyUpdated: false }).save((e: any) => {
9 | if (e) _log.error('savedBlock error ', e.message);
10 | });
11 | } catch (e: any) {
12 | _log.error('not saveBlock', e);
13 | }
14 | return;
15 | };
16 |
17 | const updateBlock = async (blockHash: any, blockNumber: any, newData: any) => {
18 | try {
19 | g.blocks.updateOne({ blockHash }, { ...newData, fullyUpdated: true }, {}, (e: any) => {
20 | if (!e) _log.success('updatedBlock OK ', blockNumber);
21 | });
22 | } catch (e: any) {
23 | _log.error('not updatedBlock', e);
24 | }
25 | return;
26 | };
27 |
28 | export { saveBlock, updateBlock };
29 |
--------------------------------------------------------------------------------
/src/utils/mongo/savePending.ts:
--------------------------------------------------------------------------------
1 | import { ITrojanTx } from '../../models/TransactionSchema';
2 | import { nowMs, _log } from '../configs/utils';
3 | import { models } from './config';
4 |
5 | const createPending = async (tx: ITrojanTx, message: string) => {
6 | try {
7 | const timestampTx = nowMs();
8 | new models.txM.pending({
9 | ...tx,
10 | notes: {
11 | message,
12 | timestampTx
13 | }
14 | }).save((e: any) => {
15 | if (!e) _log.info('New Pending Saved', '|', tx.hash, '|', message);
16 | if (e) _log.error(e);
17 | });
18 | } catch (e: any) {
19 | _log.error('txM catch', e.message);
20 | }
21 | return;
22 | };
23 |
24 | export { createPending };
25 |
--------------------------------------------------------------------------------
/src/utils/mongo/savePools.ts:
--------------------------------------------------------------------------------
1 | import { _log } from '../configs/utils';
2 | import { models } from './config';
3 |
4 | const { g } = models;
5 |
6 | const savePools = async (data: any) => {
7 | try {
8 | new g.pools(data).save((e: any, doc: any) => {
9 | //if (!e) _log.success('savedPools', doc.address);
10 | });
11 | } catch (e: any) {
12 | _log.error('savedPools', e.message);
13 | }
14 | return;
15 | };
16 |
17 | export { savePools };
18 |
--------------------------------------------------------------------------------
/src/utils/mongo/saveWhale.ts:
--------------------------------------------------------------------------------
1 | import { checksum, nowMs, _log } from '../configs/utils';
2 | import { models } from './config';
3 |
4 | const { g } = models;
5 |
6 | const saveWhale = async (whale: any) => {
7 | try {
8 | const w = {
9 | address: checksum(whale.address),
10 | hashAddress: checksum(whale.address),
11 | twitter: whale.twitter.twitter,
12 | timestampTx: nowMs()
13 | };
14 |
15 | new g.whales(w).save((e: any, doc: any) => {
16 | if (!e) _log.success('savedWhale', doc.address);
17 | });
18 | } catch (e: any) {
19 | _log.error('not savedWhale', e.message);
20 | }
21 | return;
22 | };
23 |
24 | export { saveWhale };
25 |
--------------------------------------------------------------------------------
/src/utils/web3/abis-interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Interface } from '@ethersproject/abi';
2 | import SushiV2Router from '@sushiswap/core/build/abi/IUniswapV2Router02.json';
3 | import { abi as UniV2Router } from '@uniswap/v2-periphery/build/IUniswapV2Router02.json';
4 | import { abi as UniV3Router } from '@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json';
5 | import { abi as IUniswapV3PoolABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json';
6 | import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json';
7 | import { abi as _erc20abi } from '@uniswap/v2-core/build/ERC20.json';
8 | import { abi as _multicallabi } from '../../abis/Multicall2.json';
9 |
10 | export const erc20abi = _erc20abi;
11 | export const multicallabi = _multicallabi;
12 | export const iUniV2Router = new Interface(UniV2Router);
13 | export const iUniV3Router = new Interface(UniV3Router);
14 | export const iSushiV2Router = new Interface(SushiV2Router);
15 | export const iUniswapV3PoolABI = new Interface(IUniswapV3PoolABI);
16 | export const iUniswapV2PairABI = new Interface(IUniswapV2PairABI);
17 |
--------------------------------------------------------------------------------
/src/utils/web3/checkTxs.ts:
--------------------------------------------------------------------------------
1 | import { ENV, _log, nowMs } from '../configs/utils';
2 | import { ITrojanTx } from '../../models/TransactionSchema';
3 | import { TransactionResponse } from '@ethersproject/abstract-provider';
4 | import { Currency, Price } from '@uniswap/sdk-core';
5 |
6 | const checkTx = (tx: TransactionResponse, whaleData: any, isV2: boolean, isV3: boolean, isV2Sushi: boolean): ITrojanTx => {
7 | return {
8 | ...tx,
9 | txHash: tx.hash,
10 | isV2,
11 | isV3,
12 | isV2Sushi,
13 | links: { etherscan: ENV.ES_TX + tx.hash },
14 | status: 'pending',
15 | timestampTx: nowMs(),
16 | whaleData
17 | };
18 | };
19 |
20 | const getPrice = (price: Price) => {
21 | return {
22 | priceFrom: price.toSignificant(6),
23 | priceFromInverted: price.invert().toSignificant(6),
24 | priceTo: price.invert().toSignificant(6),
25 | priceToInverted: price.toSignificant(6),
26 | label: `${price.baseCurrency.symbol} per ${price.quoteCurrency.symbol}`,
27 | labelInverted: `${price.quoteCurrency.symbol} per ${price.baseCurrency.symbol}`
28 | };
29 | };
30 |
31 | export { getPrice, checkTx };
32 |
--------------------------------------------------------------------------------
/src/utils/web3/getBlocks.ts:
--------------------------------------------------------------------------------
1 | import { get } from 'https';
2 | import { KEYS, _log } from '../configs/utils';
3 |
4 | const getBlock = async (number: number, provider: any) => {
5 | try {
6 | const block = await getFromBackupProviders(number, provider);
7 | return block;
8 | } catch (e: any) {
9 | _log.error('getBlock catch', number, e.message);
10 | }
11 | };
12 |
13 | const getFromBackupProviders = async (number: number, provider: any) => {
14 | try {
15 | const blockResponse = await goGetIt(provider, number);
16 | if (blockResponse) {
17 | return blockResponse;
18 | }
19 | } catch (e: any) {
20 | if (e.message === 'noNetwork') {
21 | const blockResponse = await goGetIt(KEYS.ALCHEMY_URL, number);
22 | if (blockResponse) {
23 | return blockResponse;
24 | }
25 | }
26 | }
27 | return null;
28 | };
29 |
30 | const goGetIt = async (provider: any, number: number) => {
31 | try {
32 | const _blockResponse = await provider.getBlock(number);
33 | if (_blockResponse) return _blockResponse;
34 | } catch (e: any) {
35 | throw new Error(e.event);
36 | }
37 | return null;
38 | };
39 |
40 | async function getBlockInfo(url: string, opts: any): Promise {
41 | return new Promise((resolve) => {
42 | get(url, opts, (res) => {
43 | if (res) {
44 | let body = '';
45 |
46 | res.on('error', (e: any) => {
47 | _log.error(e.message);
48 | resolve(null);
49 | });
50 |
51 | res.on('data', (chunk) => {
52 | body += chunk;
53 | });
54 |
55 | res.on('end', () => {
56 | const responseData = JSON.parse(body);
57 | if (responseData) resolve(responseData);
58 | else resolve(null);
59 | });
60 | }
61 | }).on('error', (e: any) => {
62 | _log.error('Get getBlockInfo on error', e.message);
63 | resolve(null);
64 | });
65 | });
66 | }
67 |
68 | export { getBlockInfo, getBlock };
69 |
--------------------------------------------------------------------------------
/src/utils/web3/getContractData.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from 'ethers';
2 | import { checksum, _log } from '../configs/utils';
3 | import { erc20abi } from './abis-interfaces';
4 | import { MAINNET } from './utils';
5 |
6 | const getContractData = async (contractAddress: string, provider: any): Promise => {
7 | try {
8 | const address = checksum(contractAddress);
9 |
10 | const contract = new ethers.Contract(address, erc20abi, provider);
11 |
12 | const [decimals, name, symbol] = await Promise.all([contract.functions.decimals(), contract.functions.name(), contract.functions.symbol()]);
13 |
14 | return {
15 | chainId: MAINNET,
16 | address,
17 | decimals: decimals[0],
18 | name: name[0],
19 | symbol: symbol[0]
20 | };
21 | } catch (e: any) {
22 | _log.error('GetContractData catch', contractAddress, e);
23 | }
24 | return null;
25 | };
26 |
27 | export { getContractData, MAINNET };
28 |
--------------------------------------------------------------------------------
/src/utils/web3/getTransactions.ts:
--------------------------------------------------------------------------------
1 | import { _log } from '../configs/utils';
2 |
3 | const getPendingTxResponse = async (hash: string, provider: any) => {
4 | try {
5 | const _txResponse = await getFromProviders(hash, provider);
6 | return _txResponse;
7 | } catch (e: any) {
8 | _log.error('getPendingTxResponse catch ', hash);
9 | }
10 | return null;
11 | };
12 |
13 | const getFromProviders = async (hash: string, provider: any) => {
14 | try {
15 | const txResponse = await goGetIt(hash, provider);
16 | if (txResponse) {
17 | const { to, from } = txResponse;
18 | if (to && from) {
19 | return txResponse;
20 | }
21 | }
22 | } catch (e: any) {
23 | if (e.message === 'noNetwork') {
24 | const txResponse = await goGetIt(hash, provider);
25 | if (txResponse) {
26 | const { to, from } = txResponse;
27 | if (to && from) {
28 | return txResponse;
29 | }
30 | }
31 | }
32 | }
33 | return null;
34 | };
35 |
36 | const goGetIt = async (hash: string, provider: any) => {
37 | try {
38 | let _txResponse = await provider.getTransaction(hash);
39 | if (_txResponse) return _txResponse;
40 | } catch (e: any) {
41 | _log.info(e.message);
42 | }
43 |
44 | try {
45 | let _txResponse = await provider.getTransaction(hash);
46 | if (_txResponse) return _txResponse;
47 | } catch (e: any) {
48 | _log.info(e.message);
49 | }
50 |
51 | return null;
52 | };
53 |
54 | export { getPendingTxResponse };
55 |
--------------------------------------------------------------------------------
/src/utils/web3/utils.ts:
--------------------------------------------------------------------------------
1 | const MAINNET = 1;
2 | const _WETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
3 | const _ETH_ADDRESS = '0x0000000000000000000000000000000000000000';
4 |
5 | const _MULTICALL = 'multicall';
6 |
7 | const _V3_FUNC_ALLOWED_METHODS = new Array('multicall', 'exactInputSingle', 'exactInput', 'exactOutputSingle', 'exactOutput');
8 |
9 | //Same Order
10 | const _V3_FNAME_ONLY_SWAP = new Array('exactInputSingle', 'exactInput', 'exactOutputSingle', 'exactOutput');
11 | const _V3_SIGS_ONLY_SWAP = new Array('0x414bf389', '0xc04b8d59', '0xdb3e2198', '0xf28c0498');
12 |
13 | const V3_SWAP_FNAME = {
14 | EIS: 'exactInputSingle',
15 | EI: 'exactInput',
16 | EOS: 'exactOutputSingle',
17 | EO: 'exactOutput'
18 | };
19 |
20 | const V3_SWAP_SIGS = {
21 | exactInputSingleSig: '0x414bf389',
22 | exactInputSig: '0xc04b8d59',
23 | exactOutputSingleSig: '0xdb3e2198',
24 | exactOutputSig: '0xf28c0498'
25 | };
26 |
27 | export { V3_SWAP_SIGS, V3_SWAP_FNAME, _V3_SIGS_ONLY_SWAP, _V3_FNAME_ONLY_SWAP, _V3_FUNC_ALLOWED_METHODS, _MULTICALL, _ETH_ADDRESS, _WETH_ADDRESS, MAINNET };
28 |
--------------------------------------------------------------------------------
/src/utils/web3/wsProvider.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from 'ethers';
2 |
3 | type KeepAliveParams = {
4 | provider: ethers.providers.WebSocketProvider;
5 | onDisconnect: (err: any) => void;
6 | expectedPongBack?: number;
7 | checkInterval?: number;
8 | };
9 |
10 | export const keepAlive = ({ provider, onDisconnect, expectedPongBack = 15000, checkInterval = 7500 }: KeepAliveParams) => {
11 | let pingTimeout: NodeJS.Timeout | null = null;
12 | let keepAliveInterval: NodeJS.Timeout | null = null;
13 |
14 | provider._websocket.on('open', () => {
15 | keepAliveInterval = setInterval(() => {
16 | // Ping https://github.com/ethers-io/ethers.js/issues/1053
17 | provider._websocket.ping();
18 |
19 | // Use `WebSocket#terminate()`, which immediately destroys the connection,
20 | // instead of `WebSocket#close()`, which waits for the close timer.
21 | // Delay should be equal to the interval at which your server
22 | // sends out pings plus a conservative assumption of the latency.
23 | pingTimeout = setTimeout(() => {
24 | provider._websocket.terminate();
25 | }, expectedPongBack);
26 | }, checkInterval);
27 | });
28 |
29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
30 | provider._websocket.on('close', (err: any) => {
31 | if (keepAliveInterval) clearInterval(keepAliveInterval);
32 | if (pingTimeout) clearTimeout(pingTimeout);
33 | onDisconnect(err);
34 | });
35 |
36 | provider._websocket.on('pong', () => {
37 | if (pingTimeout) clearInterval(pingTimeout);
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "CommonJS",
5 | "resolveJsonModule": true,
6 | "esModuleInterop": true,
7 | "rootDir": "./src",
8 | "outDir": "./dist",
9 | "sourceMap": true,
10 | "strict": true,
11 | "skipLibCheck": true
12 | },
13 | "exclude": ["client", "client_build"]
14 | }
15 |
--------------------------------------------------------------------------------