├── .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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/arrow-down-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/circle-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/dropdown-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/dropup-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/plus-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/src/assets/images/question.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/src/assets/images/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/svg/lightcircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Path 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | {alt} { 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 |
31 | {success ? : } 32 |
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 | logo 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 | --------------------------------------------------------------------------------