├── .yarnrc ├── src ├── connectors │ ├── fortmatic.d.ts │ └── Fortmatic.ts ├── assets │ ├── images │ │ ├── noise.png │ │ ├── xl_uni.png │ │ ├── metamask.png │ │ ├── big_unicorn.png │ │ ├── no_service.png │ │ ├── portisIcon.png │ │ ├── token-logo.png │ │ ├── trustWallet.png │ │ ├── ethereum-logo.png │ │ ├── fortmaticIcon.png │ │ ├── arrow-right-white.png │ │ ├── token-list-logo.png │ │ ├── tokenlistsgrouped.png │ │ ├── token-list │ │ │ ├── lists-dark.png │ │ │ └── lists-light.png │ │ ├── dropdown.svg │ │ ├── dropup-blue.svg │ │ ├── dropdown-blue.svg │ │ ├── plus-blue.svg │ │ ├── plus-grey.svg │ │ ├── arrow-right.svg │ │ ├── x.svg │ │ ├── blue-loader.svg │ │ ├── link.svg │ │ ├── circle.svg │ │ ├── circle-grey.svg │ │ ├── arrow-down-blue.svg │ │ ├── arrow-down-grey.svg │ │ ├── menu.svg │ │ ├── spinner.svg │ │ ├── question.svg │ │ └── question-mark.svg │ └── svg │ │ ├── menu.svg │ │ ├── down.svg │ │ ├── arrow_up.svg │ │ ├── gray_loader.svg │ │ ├── put_icon.svg │ │ ├── binance.svg │ │ ├── bsc_logo_selected.svg │ │ ├── cross.svg │ │ ├── copy.svg │ │ ├── QR.svg │ │ ├── call_icon.svg │ │ ├── avax_logo.svg │ │ ├── eth_logo.svg │ │ ├── circle_ETH.svg │ │ ├── sell.svg │ │ ├── buy.svg │ │ ├── huobi.svg │ │ ├── huobi_inverted.svg │ │ ├── check_circle.svg │ │ ├── lightcircle.svg │ │ ├── cross_circle.svg │ │ ├── plus.svg │ │ ├── put_type.svg │ │ ├── transaction_submitted.svg │ │ ├── call_type.svg │ │ ├── walletConnect_logo.svg │ │ ├── circle_unknown.svg │ │ ├── transaction_error.svg │ │ ├── put_token.svg │ │ ├── helper.svg │ │ ├── search.svg │ │ └── call_token.svg ├── constants │ ├── proposals │ │ └── index.ts │ ├── abis │ │ ├── migrator.ts │ │ ├── argent-wallet-detector.ts │ │ ├── erc20.ts │ │ ├── staking-rewards.ts │ │ ├── erc20_bytes32.json │ │ └── migrator.json │ ├── lists.ts │ ├── multicall │ │ └── index.ts │ ├── tokenLists │ │ └── uniswap-v2-unsupported.tokenlist.json │ └── v1 │ │ └── index.ts ├── hooks │ ├── useTheme.ts │ ├── useToggle.ts │ ├── useParsedQueryString.ts │ ├── useCurrentBlockTimestamp.ts │ ├── useToggledVersion.ts │ ├── useIsArgentWallet.ts │ ├── useMediaWidth.ts │ ├── usePrevious.ts │ ├── useTimestampFromBlock.ts │ ├── useInterval.ts │ ├── useCopyClipboard.ts │ ├── useTransactionDeadline.ts │ ├── useHttpLocations.ts │ ├── useDebounce.ts │ ├── useWindowSize.ts │ ├── useOnClickOutside.tsx │ ├── useSocksBalance.ts │ ├── useENS.ts │ ├── useIsWindowVisible.ts │ ├── useLast.ts │ ├── useENSContentHash.ts │ ├── useENSAddress.ts │ ├── useCreationCallback.ts │ ├── useAddTokenToMetamask.ts │ ├── useENSName.ts │ ├── useOptionList.ts │ └── useCalculatorCallback.ts ├── utils │ ├── isZero.ts │ ├── listVersionLabel.ts │ ├── getLibrary.ts │ ├── currencyId.ts │ ├── option │ │ ├── location.ts │ │ ├── axios.tsx │ │ └── wrapPromise.ts │ ├── getCurrencySymbol.ts │ ├── parseENSAddress.ts │ ├── listSort.ts │ ├── maxAmountSpend.ts │ ├── showPassDateTime.ts │ ├── uriToHttp.ts │ ├── wrappedCurrency.ts │ ├── contenthashToUri.test.skip.ts │ ├── chunkArray.test.ts │ ├── trades.ts │ ├── uriToHttp.test.ts │ ├── parseENSAddress.test.ts │ ├── useDebouncedChangeHandler.tsx │ ├── chunkArray.ts │ ├── marketStrategyUtils.ts │ ├── computeUniCirculation.test.ts │ ├── contenthashToUri.ts │ └── prices.test.ts ├── state │ ├── global │ │ └── actions.ts │ ├── burn │ │ ├── actions.ts │ │ └── reducer.ts │ ├── mint │ │ ├── actions.ts │ │ ├── reducer.test.ts │ │ └── reducer.ts │ ├── multicall │ │ └── utils.ts │ ├── swap │ │ ├── actions.ts │ │ └── reducer.test.ts │ ├── application │ │ ├── actions.ts │ │ └── reducer.ts │ ├── transactions │ │ ├── actions.ts │ │ └── updater.test.ts │ ├── index.ts │ ├── user │ │ ├── updater.tsx │ │ └── reducer.test.ts │ └── lists │ │ └── actions.ts ├── components │ ├── swap │ │ ├── AdvancedSwapDetailsDropdown.tsx │ │ ├── FormattedPriceImpact.tsx │ │ ├── SwapHeader.tsx │ │ ├── TradePrice.tsx │ │ ├── confirmPriceImpactWithoutFee.ts │ │ └── SwapRoute.tsx │ ├── analytics │ │ └── GoogleAnalyticsReporter.tsx │ ├── Blocklist │ │ └── index.tsx │ ├── TokenWarningModal │ │ └── index.tsx │ ├── PairChart │ │ ├── constants.js │ │ ├── apollo │ │ │ └── client.js │ │ └── utils │ │ │ └── data.ts │ ├── ListLogo │ │ └── index.tsx │ ├── FormattedCurrencyAmount │ │ └── index.tsx │ ├── Column │ │ └── index.tsx │ ├── SearchModal │ │ ├── SortButton.tsx │ │ └── sorting.ts │ ├── Identicon │ │ └── index.tsx │ ├── Header │ │ ├── ShortLogo.tsx │ │ └── URLWarning.tsx │ ├── MarketStrategy │ │ ├── TypeRadioButton.tsx │ │ └── RadioButton.tsx │ ├── Image │ │ └── index.tsx │ ├── CurrencyLogo │ │ └── CallPutToken.tsx │ ├── Tooltip │ │ └── index.tsx │ ├── Confetti │ │ └── index.tsx │ ├── Logo │ │ └── index.tsx │ ├── DoubleLogo │ │ ├── index.tsx │ │ └── DoubleLogoReverse.tsx │ ├── Row │ │ └── index.tsx │ ├── AccountDetails │ │ └── Copy.tsx │ ├── Pagination │ │ └── index.tsx │ ├── Popups │ │ └── TransactionPopup.tsx │ ├── SwitchTab │ │ └── index.tsx │ ├── Spinner │ │ └── index.tsx │ ├── Loader │ │ └── index.tsx │ └── Toggle │ │ ├── ListToggle.tsx │ │ └── index.tsx ├── pages │ ├── MigrateV1 │ │ └── EmptyState.tsx │ ├── RemoveLiquidity │ │ └── redirects.tsx │ ├── OptionCreation │ │ └── Liquidity.tsx │ ├── ComingSoon.tsx │ ├── MatterToken │ │ ├── index.tsx │ │ └── ConfirmRedeemModalBottom.tsx │ ├── Vote │ │ └── styled.tsx │ ├── AddLiquidity │ │ ├── redirects.tsx │ │ └── PoolPriceBar.tsx │ ├── Pool │ │ └── styleds.tsx │ ├── Swap │ │ └── redirects.tsx │ ├── Info │ │ └── PriceItem.tsx │ ├── Redeem │ │ └── ConfirmRedeemModalBottom.tsx │ ├── Generate │ │ └── ConfirmAddModalBottom.tsx │ └── AppBody.tsx ├── i18n.ts ├── data │ ├── TotalSupply.ts │ └── Allowances.ts ├── react-app-env.d.ts └── theme │ ├── DarkModeQueryParamReader.tsx │ └── styled.d.ts ├── icon_32.png ├── icon_6.png ├── icon_64.png ├── .prettierrc ├── antimatter_icon.png ├── public ├── favicon.png ├── fonts │ ├── Roboto-Bold.ttf │ ├── Roboto-Light.ttf │ ├── FuturaPT-Demi.ttf │ ├── Roboto-Italic.ttf │ ├── Roboto-Medium.ttf │ └── Roboto-Regular.ttf ├── images │ ├── 192x192_App_Icon.png │ └── 512x512_App_Icon.png ├── 451.html ├── manifest.json └── css │ └── font.css ├── .env ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── proposal.md │ ├── ask-a-question.md │ ├── enhancement.md │ ├── bug-report.md │ └── feature-request.md └── workflows │ └── lint.yml ├── cypress.json ├── cypress ├── tsconfig.json ├── integration │ ├── migrate-v1.test.ts │ ├── lists.test.ts │ ├── send.test.ts │ ├── pool.test.ts │ └── landing.test.ts └── support │ ├── commands.d.ts │ └── index.js ├── .env.production ├── .gitignore ├── .eslintrc.json ├── tsconfig.json ├── README.md ├── sitemap.xml └── test └── unit └── functions └── approveAmountCallData.test.ts /.yarnrc: -------------------------------------------------------------------------------- 1 | ignore-scripts true 2 | -------------------------------------------------------------------------------- /src/connectors/fortmatic.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'formatic' 2 | -------------------------------------------------------------------------------- /icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/icon_32.png -------------------------------------------------------------------------------- /icon_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/icon_6.png -------------------------------------------------------------------------------- /icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/icon_64.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /antimatter_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/antimatter_icon.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /src/assets/images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/noise.png -------------------------------------------------------------------------------- /src/assets/images/xl_uni.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/xl_uni.png -------------------------------------------------------------------------------- /public/fonts/FuturaPT-Demi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/fonts/FuturaPT-Demi.ttf -------------------------------------------------------------------------------- /public/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /public/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/assets/images/metamask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/metamask.png -------------------------------------------------------------------------------- /public/images/192x192_App_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/images/192x192_App_Icon.png -------------------------------------------------------------------------------- /public/images/512x512_App_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/public/images/512x512_App_Icon.png -------------------------------------------------------------------------------- /src/assets/images/big_unicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/big_unicorn.png -------------------------------------------------------------------------------- /src/assets/images/no_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/no_service.png -------------------------------------------------------------------------------- /src/assets/images/portisIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/portisIcon.png -------------------------------------------------------------------------------- /src/assets/images/token-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/token-logo.png -------------------------------------------------------------------------------- /src/assets/images/trustWallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/trustWallet.png -------------------------------------------------------------------------------- /src/assets/images/ethereum-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/ethereum-logo.png -------------------------------------------------------------------------------- /src/assets/images/fortmaticIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/fortmaticIcon.png -------------------------------------------------------------------------------- /src/assets/images/arrow-right-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/arrow-right-white.png -------------------------------------------------------------------------------- /src/assets/images/token-list-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/token-list-logo.png -------------------------------------------------------------------------------- /src/assets/images/tokenlistsgrouped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/tokenlistsgrouped.png -------------------------------------------------------------------------------- /src/assets/images/token-list/lists-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/token-list/lists-dark.png -------------------------------------------------------------------------------- /src/assets/images/token-list/lists-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/HEAD/src/assets/images/token-list/lists-light.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_CHAIN_ID="1" 2 | REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/3f6f55ba1ee540328662f8496ddbc228" 3 | -------------------------------------------------------------------------------- /src/constants/proposals/index.ts: -------------------------------------------------------------------------------- 1 | import { UNISWAP_GRANTS } from './uniswap_grants' 2 | 3 | // Proposals are 0-indexed 4 | export const PRELOADED_PROPOSALS = new Map([[2, UNISWAP_GRANTS]]) 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Document 4 | url: https://docs.antimatter.finance/ 5 | about: About how antimatter work. 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Proposal 3 | about: Propose application ideas to Antimatter ecosystem. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/images/dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/dropup-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/constants/abis/migrator.ts: -------------------------------------------------------------------------------- 1 | import MIGRATOR_ABI from './migrator.json' 2 | 3 | const MIGRATOR_ADDRESS = '0x16D4F26C15f3658ec65B1126ff27DD3dF2a2996b' 4 | 5 | export { MIGRATOR_ADDRESS, MIGRATOR_ABI } 6 | -------------------------------------------------------------------------------- /src/assets/images/dropdown-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/menu.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask-a-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a Question 3 | about: I want to ask a question about the product and technology. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "pluginsFile": false, 4 | "fixturesFolder": false, 5 | "supportFile": "cypress/support/index.js", 6 | "video": false, 7 | "defaultCommandTimeout": 10000 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/images/plus-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/plus-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/isZero.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the string value is zero in hex 3 | * @param hexNumberString 4 | */ 5 | export default function isZero(hexNumberString: string) { 6 | return /^0x0*$/.test(hexNumberString) 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/listVersionLabel.ts: -------------------------------------------------------------------------------- 1 | import { Version } from '@uniswap/token-lists' 2 | 3 | export default function listVersionLabel(version: Version): string { 4 | return `v${version.major}.${version.minor}.${version.patch}` 5 | } 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement 3 | about: As a BlockChain developer, I want to make an enhancement for Antimatter ecology. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": "../node_modules", 5 | "target": "es5", 6 | "lib": ["es5", "dom"], 7 | "types": ["cypress"] 8 | }, 9 | "include": ["**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/svg/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/451.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Unavailable For Legal Reasons 6 | 7 | 8 |

Unavailable For Legal Reasons

9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/arrow_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/getLibrary.ts: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from '@ethersproject/providers' 2 | 3 | export default function getLibrary(provider: any): Web3Provider { 4 | const library = new Web3Provider(provider, 'any') 5 | library.pollingInterval = 15000 6 | return library 7 | } 8 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_CHAIN_ID="1" 2 | REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/3f6f55ba1ee540328662f8496ddbc228" 3 | REACT_APP_PORTIS_ID="c0e2bf01-4b08-4fd5-ac7b-8e26b58cd236" 4 | REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF" 5 | REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-4" 6 | -------------------------------------------------------------------------------- /src/utils/currencyId.ts: -------------------------------------------------------------------------------- 1 | import { Currency, ETHER, Token } from '@uniswap/sdk' 2 | 3 | export function currencyId(currency: Currency): string { 4 | if (currency === ETHER) return 'ETH' 5 | if (currency instanceof Token) return currency.address 6 | throw new Error('invalid currency') 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/images/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/constants/abis/argent-wallet-detector.ts: -------------------------------------------------------------------------------- 1 | import ARGENT_WALLET_DETECTOR_ABI from './argent-wallet-detector.json' 2 | 3 | const ARGENT_WALLET_DETECTOR_MAINNET_ADDRESS = '0xeca4B0bDBf7c55E9b7925919d03CbF8Dc82537E8' 4 | 5 | export { ARGENT_WALLET_DETECTOR_ABI, ARGENT_WALLET_DETECTOR_MAINNET_ADDRESS } 6 | -------------------------------------------------------------------------------- /src/state/global/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | // fired once when the app reloads but before the app renders 4 | // allows any updates to be applied to store data loaded from localStorage 5 | export const updateVersion = createAction('global/updateVersion') 6 | -------------------------------------------------------------------------------- /src/assets/images/x.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hooks/useToggle.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react' 2 | 3 | export default function useToggle(initialState = false): [boolean, () => void] { 4 | const [state, setState] = useState(initialState) 5 | const toggle = useCallback(() => setState(state => !state), []) 6 | return [state, toggle] 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/images/blue-loader.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/gray_loader.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/images/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cypress/integration/migrate-v1.test.ts: -------------------------------------------------------------------------------- 1 | describe('Migrate V1 Liquidity', () => { 2 | describe('Remove V1 liquidity', () => { 3 | it('renders the correct page', () => { 4 | cy.visit('/remove/v1/0x93bB63aFe1E0180d0eF100D774B473034fd60C36') 5 | cy.get('#remove-v1-exchange').should('contain', 'MKR/ETH') 6 | }) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /cypress/support/commands.d.ts: -------------------------------------------------------------------------------- 1 | export const TEST_ADDRESS_NEVER_USE: string 2 | 3 | export const TEST_ADDRESS_NEVER_USE_SHORTENED: string 4 | 5 | // declare namespace Cypress { 6 | // // eslint-disable-next-line @typescript-eslint/class-name-casing 7 | // interface cy { 8 | // additionalCommands(): void 9 | // } 10 | // } 11 | -------------------------------------------------------------------------------- /src/assets/images/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/circle-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/state/burn/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export enum Field { 4 | LIQUIDITY_PERCENT = 'LIQUIDITY_PERCENT', 5 | LIQUIDITY = 'LIQUIDITY', 6 | CURRENCY_A = 'CURRENCY_A', 7 | CURRENCY_B = 'CURRENCY_B' 8 | } 9 | 10 | export const typeInput = createAction<{ field: Field; typedValue: string }>('burn/typeInputBurn') 11 | -------------------------------------------------------------------------------- /src/utils/option/location.ts: -------------------------------------------------------------------------------- 1 | import wrapPromise from './wrapPromise' 2 | 3 | export const fetchLocation = () => { 4 | return wrapPromise( 5 | fetch('https://geolocation-db.com/json/') 6 | .then(r => r.clone().json()) 7 | .then(json => json.country_code) 8 | .catch(e => { 9 | console.error(e) 10 | }) 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/images/arrow-down-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/arrow-down-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /cypress/integration/lists.test.ts: -------------------------------------------------------------------------------- 1 | describe('Lists', () => { 2 | beforeEach(() => { 3 | cy.visit('/swap') 4 | }) 5 | 6 | // @TODO check if default lists are active when we have them 7 | it('change list', () => { 8 | cy.get('#swap-currency-output .open-currency-select-button').click() 9 | cy.get('.list-token-manage-button').click() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/components/swap/AdvancedSwapDetailsDropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails' 3 | 4 | export default function AdvancedSwapDetailsDropdown({ undTrade, curTrade, ...rest }: AdvancedSwapDetailsProps) { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /src/state/mint/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export enum Field { 4 | CURRENCY_A = 'CURRENCY_A', 5 | CURRENCY_B = 'CURRENCY_B' 6 | } 7 | 8 | export const typeInput = createAction<{ field: Field; typedValue: string; noLiquidity: boolean }>('mint/typeInputMint') 9 | export const resetMintState = createAction('mint/resetMintState') 10 | -------------------------------------------------------------------------------- /src/utils/getCurrencySymbol.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, Currency, ETHER, Token } from '@uniswap/sdk' 2 | import { Symbol } from 'constants/index' 3 | 4 | export function getCurrencySymbol(currency: Currency, chainId: ChainId | undefined): string { 5 | if (currency === ETHER) return Symbol[chainId ?? 1] ?? '' 6 | if (currency instanceof Token) return currency.symbol ?? '' 7 | return '' 8 | } 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This file is processed and loaded automatically before your test files. 3 | // 4 | // You can read more here: 5 | // https://on.cypress.io/configuration 6 | // *********************************************************** 7 | 8 | // Import commands.ts using ES2015 syntax: 9 | import './commands' 10 | -------------------------------------------------------------------------------- /cypress/integration/send.test.ts: -------------------------------------------------------------------------------- 1 | describe('Send', () => { 2 | it('should redirect', () => { 3 | cy.visit('/send') 4 | cy.url().should('include', '/swap') 5 | }) 6 | 7 | it('should redirect with url params', () => { 8 | cy.visit('/send?outputCurrency=ETH&recipient=bob.argent.xyz') 9 | cy.url().should('contain', '/swap?outputCurrency=ETH&recipient=bob.argent.xyz') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /cypress/integration/pool.test.ts: -------------------------------------------------------------------------------- 1 | describe('Pool', () => { 2 | beforeEach(() => cy.visit('/pool')) 3 | it('add liquidity links to /add/ETH', () => { 4 | cy.get('#join-pool-button').click() 5 | cy.url().should('contain', '/add/ETH') 6 | }) 7 | 8 | it('import pool links to /import', () => { 9 | cy.get('#import-pool-link').click() 10 | cy.url().should('contain', '/find') 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/constants/abis/erc20.ts: -------------------------------------------------------------------------------- 1 | import { Interface } from '@ethersproject/abi' 2 | import ERC20_ABI from './erc20.json' 3 | import ERC20_BYTES32_ABI from './erc20_bytes32.json' 4 | 5 | const ERC20_INTERFACE = new Interface(ERC20_ABI) 6 | 7 | const ERC20_BYTES32_INTERFACE = new Interface(ERC20_BYTES32_ABI) 8 | 9 | export default ERC20_INTERFACE 10 | export { ERC20_ABI, ERC20_BYTES32_INTERFACE, ERC20_BYTES32_ABI } 11 | -------------------------------------------------------------------------------- /src/pages/MigrateV1/EmptyState.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AutoColumn } from '../../components/Column' 3 | import { TYPE } from '../../theme' 4 | 5 | export function EmptyState({ message }: { message: string }) { 6 | return ( 7 | 8 | {message} 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/svg/put_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/hooks/useParsedQueryString.ts: -------------------------------------------------------------------------------- 1 | import { parse, ParsedQs } from 'qs' 2 | import { useMemo } from 'react' 3 | import { useLocation } from 'react-router-dom' 4 | 5 | export default function useParsedQueryString(): ParsedQs { 6 | const { search } = useLocation() 7 | return useMemo( 8 | () => (search && search.length > 1 ? parse(search, { parseArrays: false, ignoreQueryPrefix: true }) : {}), 9 | [search] 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/useCurrentBlockTimestamp.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import { useSingleCallResult } from '../state/multicall/hooks' 3 | import { useMulticallContract } from './useContract' 4 | 5 | // gets the current timestamp from the blockchain 6 | export default function useCurrentBlockTimestamp(): BigNumber | undefined { 7 | const multicall = useMulticallContract() 8 | return useSingleCallResult(multicall, 'getCurrentBlockTimestamp')?.result?.[0] 9 | } 10 | -------------------------------------------------------------------------------- /src/components/analytics/GoogleAnalyticsReporter.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import ReactGA from 'react-ga' 3 | import { RouteComponentProps } from 'react-router-dom' 4 | 5 | // fires a GA pageview every time the route changes 6 | export default function GoogleAnalyticsReporter({ location: { pathname, search } }: RouteComponentProps): null { 7 | useEffect(() => { 8 | ReactGA.pageview(`${pathname}${search}`) 9 | }, [pathname, search]) 10 | return null 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/useToggledVersion.ts: -------------------------------------------------------------------------------- 1 | import useParsedQueryString from './useParsedQueryString' 2 | 3 | export enum Version { 4 | v1 = 'v1', 5 | v2 = 'v2' 6 | } 7 | 8 | export const DEFAULT_VERSION: Version = Version.v2 9 | 10 | export default function useToggledVersion(): Version { 11 | const { use } = useParsedQueryString() 12 | if (!use || typeof use !== 'string') return Version.v2 13 | if (use.toLowerCase() === 'v1') return Version.v1 14 | return DEFAULT_VERSION 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/svg/binance.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/bsc_logo_selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/constants/abis/staking-rewards.ts: -------------------------------------------------------------------------------- 1 | import { Interface } from '@ethersproject/abi' 2 | import { abi as STAKING_REWARDS_ABI } from '../abis/StakingRewards.json' 3 | import { abi as STAKING_REWARDS_FACTORY_ABI } from '@uniswap/liquidity-staker/build/StakingRewardsFactory.json' 4 | 5 | const STAKING_REWARDS_INTERFACE = new Interface(STAKING_REWARDS_ABI) 6 | 7 | const STAKING_REWARDS_FACTORY_INTERFACE = new Interface(STAKING_REWARDS_FACTORY_ABI) 8 | 9 | export { STAKING_REWARDS_FACTORY_INTERFACE, STAKING_REWARDS_INTERFACE } 10 | -------------------------------------------------------------------------------- /src/components/Blocklist/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useMemo } from 'react' 2 | import { BLOCKED_ADDRESSES } from '../../constants' 3 | import { useActiveWeb3React } from '../../hooks' 4 | 5 | export default function Blocklist({ children }: { children: ReactNode }) { 6 | const { account } = useActiveWeb3React() 7 | const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account]) 8 | if (blocked) { 9 | return
Blocked address
10 | } 11 | return <>{children} 12 | } 13 | -------------------------------------------------------------------------------- /src/constants/lists.ts: -------------------------------------------------------------------------------- 1 | // used to mark unsupported tokens, these are hosted lists of unsupported tokens 2 | 3 | const GEMINI_LIST = 'https://www.gemini.com/uniswap/manifest.json' 4 | 5 | export const UNSUPPORTED_LIST_URLS: string[] = [] 6 | 7 | // lower index == higher priority for token import 8 | export const DEFAULT_LIST_OF_LISTS: string[] = [ 9 | ...UNSUPPORTED_LIST_URLS // need to load unsupported tokens as well 10 | ] 11 | 12 | // default lists to be 'active' aka searched across 13 | export const DEFAULT_ACTIVE_LIST_URLS: string[] = [GEMINI_LIST] 14 | -------------------------------------------------------------------------------- /src/utils/option/axios.tsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const axiosInstance = axios.create({ 4 | baseURL: 'https://testapi.antimatter.finance/app/', 5 | timeout: 5000, 6 | headers: { 'content-type': 'application/json', accept: 'application/json' } 7 | }) 8 | 9 | export const Axios = { 10 | get(url: string, params: { [key: string]: any }) { 11 | return axiosInstance.get(url, { params }) 12 | }, 13 | post(url: string, data: { [key: string]: any }, params = {}) { 14 | return axiosInstance.post(url, data, { params }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/useIsArgentWallet.ts: -------------------------------------------------------------------------------- 1 | import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks' 2 | import { useActiveWeb3React } from './index' 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | /.netlify 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | notes.txt 26 | .idea/ 27 | 28 | .vscode/ 29 | 30 | package-lock.json 31 | 32 | cypress/videos 33 | cypress/screenshots 34 | cypress/fixtures/example.json -------------------------------------------------------------------------------- /src/assets/svg/cross.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/hooks/useMediaWidth.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { MEDIA_WIDTHS } from '../theme' 3 | import { useWindowSize } from './useWindowSize' 4 | 5 | export default function useMediaWidth(width: keyof typeof MEDIA_WIDTHS) { 6 | const [isUpTo, setIsUpTo] = useState(false) 7 | const windowSize = useWindowSize() 8 | 9 | useEffect(() => { 10 | if (windowSize.width && windowSize.width >= MEDIA_WIDTHS[width]) { 11 | setIsUpTo(false) 12 | } else { 13 | setIsUpTo(true) 14 | } 15 | }, [windowSize, width]) 16 | 17 | return isUpTo 18 | } 19 | -------------------------------------------------------------------------------- /src/constants/multicall/index.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '@uniswap/sdk' 2 | import MULTICALL_ABI from './abi.json' 3 | 4 | const MULTICALL_NETWORKS: { [chainId in ChainId]: string } = { 5 | [ChainId.MAINNET]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696', 6 | [ChainId.ROPSTEN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696', 7 | [ChainId.BSC]: '0xa9193376D09C7f31283C54e56D013fCF370Cd9D9', 8 | [ChainId.Arbitrum]: '0x80C7DD17B01855a6D2347444a0FCC36136a314de', 9 | [ChainId.Avalanche]: '0xdDCbf776dF3dE60163066A5ddDF2277cB445E0F3' 10 | } 11 | 12 | export { MULTICALL_ABI, MULTICALL_NETWORKS } 13 | -------------------------------------------------------------------------------- /src/assets/svg/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/pages/RemoveLiquidity/redirects.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { RouteComponentProps, Redirect } from 'react-router-dom' 3 | 4 | const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/ 5 | 6 | export function RedirectOldRemoveLiquidityPathStructure({ 7 | match: { 8 | params: { tokens } 9 | } 10 | }: RouteComponentProps<{ tokens: string }>) { 11 | if (!OLD_PATH_STRUCTURE.test(tokens)) { 12 | return 13 | } 14 | const [currency0, currency1] = tokens.split('-') 15 | 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Describe an issue in the Antimatter Interface 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Bug Description** 11 | A clear and concise description of the bug. 12 | 13 | **Steps to Reproduce** 14 | 1. Go to ... 15 | 2. Click on ... 16 | ... 17 | 18 | **Expected Behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Additional Context** 22 | Add any other context about the problem here (screenshots, whether the bug only occurs only in certain mobile/desktop/browser environments, etc.) 23 | -------------------------------------------------------------------------------- /src/assets/svg/QR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | // modified from https://usehooks.com/usePrevious/ 4 | export default function usePrevious(value: T) { 5 | // The ref object is a generic container whose current property is mutable ... 6 | // ... and can hold any value, similar to an instance property on a class 7 | const ref = useRef() 8 | 9 | // Store current value in ref 10 | useEffect(() => { 11 | ref.current = value 12 | }, [value]) // Only re-run if value changes 13 | 14 | // Return previous value (happens before update in useEffect above) 15 | return ref.current 16 | } 17 | -------------------------------------------------------------------------------- /src/assets/svg/call_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/constants/abis/erc20_bytes32.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "bytes32" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [], 19 | "name": "symbol", 20 | "outputs": [ 21 | { 22 | "name": "", 23 | "type": "bytes32" 24 | } 25 | ], 26 | "payable": false, 27 | "stateMutability": "view", 28 | "type": "function" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /src/components/TokenWarningModal/index.tsx: -------------------------------------------------------------------------------- 1 | import { Token } from '@uniswap/sdk' 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 | -------------------------------------------------------------------------------- /src/state/burn/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { Field, typeInput } from './actions' 3 | 4 | export interface BurnState { 5 | readonly independentField: Field 6 | readonly typedValue: string 7 | } 8 | 9 | const initialState: BurnState = { 10 | independentField: Field.LIQUIDITY_PERCENT, 11 | typedValue: '0' 12 | } 13 | 14 | export default createReducer(initialState, builder => 15 | builder.addCase(typeInput, (state, { payload: { field, typedValue } }) => { 16 | return { 17 | ...state, 18 | independentField: field, 19 | typedValue 20 | } 21 | }) 22 | ) 23 | -------------------------------------------------------------------------------- /src/utils/listSort.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_LIST_OF_LISTS } from './../constants/lists' 2 | 3 | // use ordering of default list of lists to assign priority 4 | export default function sortByListPriority(urlA: string, urlB: string) { 5 | const first = DEFAULT_LIST_OF_LISTS.includes(urlA) ? DEFAULT_LIST_OF_LISTS.indexOf(urlA) : Number.MAX_SAFE_INTEGER 6 | const second = DEFAULT_LIST_OF_LISTS.includes(urlB) ? DEFAULT_LIST_OF_LISTS.indexOf(urlB) : Number.MAX_SAFE_INTEGER 7 | 8 | // need reverse order to make sure mapping includes top priority last 9 | if (first < second) return 1 10 | else if (first > second) return -1 11 | return 0 12 | } 13 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "#000", 3 | "display": "standalone", 4 | "homepage_url": "https://antimatter.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": "Antimatter", 21 | "short_name": "Antimatter", 22 | "start_url": ".", 23 | "theme_color": "#B2F355" 24 | } -------------------------------------------------------------------------------- /src/hooks/useTimestampFromBlock.ts: -------------------------------------------------------------------------------- 1 | import { useActiveWeb3React } from '.' 2 | import { useState, useEffect } from 'react' 3 | 4 | export function useTimestampFromBlock(block: number | undefined): number | undefined { 5 | const { library } = useActiveWeb3React() 6 | const [timestamp, setTimestamp] = useState() 7 | useEffect(() => { 8 | async function fetchTimestamp() { 9 | if (block) { 10 | const blockData = await library?.getBlock(block) 11 | blockData && setTimestamp(blockData.timestamp) 12 | } 13 | } 14 | if (!timestamp) { 15 | fetchTimestamp() 16 | } 17 | }, [block, library, timestamp]) 18 | return timestamp 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/svg/avax_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/eth_logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/swap/FormattedPriceImpact.tsx: -------------------------------------------------------------------------------- 1 | import { Percent } from '@uniswap/sdk' 2 | import React from 'react' 3 | import { ONE_BIPS } from '../../constants' 4 | import { warningSeverity } from '../../utils/prices' 5 | import { ErrorText } from './styleds' 6 | 7 | /** 8 | * Formatted version of price impact text with warning colors 9 | */ 10 | export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) { 11 | return ( 12 | 13 | {priceImpact ? (priceImpact.lessThan(ONE_BIPS) ? '<0.01%' : `${priceImpact.toFixed(2)}%`) : '-'} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/PairChart/constants.js: -------------------------------------------------------------------------------- 1 | export const timeframeOptions = { 2 | WEEK: '1 week', 3 | MONTH: '1 month', 4 | // THREE_MONTHS: '3 months', 5 | // YEAR: '1 year', 6 | HALF_YEAR: '6 months', 7 | ALL_TIME: 'All time' 8 | } 9 | 10 | export const BUNDLE_ID = '1' 11 | 12 | export const FACTORY_ADDRESS = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f' 13 | 14 | // tokens that should be tracked but arent due to lag in subgraph 15 | export const TRACKED_OVERRIDES = [ 16 | '0x9928e4046d7c6513326ccea028cd3e7a91c7590a', 17 | '0x87da823b6fc8eb8575a235a824690fda94674c88', 18 | '0xcd7989894bc033581532d2cd88da5db0a4b12859', 19 | '0xe1573b9d29e2183b1af0e743dc2754979a40d237' 20 | ] 21 | -------------------------------------------------------------------------------- /src/pages/OptionCreation/Liquidity.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react' 2 | import { useOptionTypeCount } from '../../state/market/hooks' 3 | import AppBody from '../AppBody' 4 | import FullPositionCard from '../../components/PositionCard' 5 | 6 | export function Liquidity() { 7 | const optionCount = useOptionTypeCount() 8 | 9 | const optionIndexes = useMemo(() => { 10 | return Array.from({ length: optionCount }, (v, i) => i.toString()) 11 | }, [optionCount]) 12 | 13 | return ( 14 | 15 | {optionIndexes.map(item => { 16 | return 17 | })} 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /cypress/integration/landing.test.ts: -------------------------------------------------------------------------------- 1 | import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands' 2 | 3 | describe('Landing Page', () => { 4 | beforeEach(() => cy.visit('/')) 5 | it('loads swap page', () => { 6 | cy.get('#swap-page') 7 | }) 8 | 9 | it('redirects to url /swap', () => { 10 | cy.url().should('include', '/swap') 11 | }) 12 | 13 | it('allows navigation to pool', () => { 14 | cy.get('#pool-nav-link').click() 15 | cy.url().should('include', '/pool') 16 | }) 17 | 18 | it('is connected', () => { 19 | cy.get('#web3-status-connected').click() 20 | cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/ListLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import useHttpLocations from '../../hooks/useHttpLocations' 4 | 5 | import Logo from '../Logo' 6 | 7 | const StyledListLogo = styled(Logo)<{ size: string }>` 8 | width: ${({ size }) => size}; 9 | height: ${({ size }) => size}; 10 | ` 11 | 12 | export default function ListLogo({ 13 | logoURI, 14 | style, 15 | size = '24px', 16 | alt 17 | }: { 18 | logoURI: string 19 | size?: string 20 | style?: React.CSSProperties 21 | alt?: string 22 | }) { 23 | const srcs: string[] = useHttpLocations(logoURI) 24 | 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /src/components/swap/SwapHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Settings from '../Settings' 4 | import { RowBetween } from '../Row' 5 | import { TYPE } from '../../theme' 6 | 7 | const StyledSwapHeader = styled.div` 8 | margin-bottom: -4px; 9 | font-size: 22px; 10 | width: 100%; 11 | max-width: 480px; 12 | font-weight: 500; 13 | color: ${({ theme }) => theme.text1}; 14 | ` 15 | 16 | export default function SwapHeader() { 17 | return ( 18 | 19 | 20 | Option Trading 21 | 22 | 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/maxAmountSpend.ts: -------------------------------------------------------------------------------- 1 | import { CurrencyAmount, ETHER, JSBI } from '@uniswap/sdk' 2 | import { MIN_ETH } from '../constants' 3 | 4 | /** 5 | * Given some token amount, return the max that can be spent of it 6 | * @param currencyAmount to return max of 7 | */ 8 | export function maxAmountSpend(currencyAmount?: CurrencyAmount): CurrencyAmount | undefined { 9 | if (!currencyAmount) return undefined 10 | if (currencyAmount.currency === ETHER) { 11 | if (JSBI.greaterThan(currencyAmount.raw, MIN_ETH)) { 12 | return CurrencyAmount.ether(JSBI.subtract(currencyAmount.raw, MIN_ETH)) 13 | } else { 14 | return CurrencyAmount.ether(JSBI.BigInt(0)) 15 | } 16 | } 17 | return currencyAmount 18 | } 19 | -------------------------------------------------------------------------------- /src/components/FormattedCurrencyAmount/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CurrencyAmount, Fraction, JSBI } from '@uniswap/sdk' 3 | 4 | const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000)) 5 | 6 | export default function FormattedCurrencyAmount({ 7 | currencyAmount, 8 | significantDigits = 4 9 | }: { 10 | currencyAmount: CurrencyAmount 11 | significantDigits?: number 12 | }) { 13 | return ( 14 | <> 15 | {currencyAmount.equalTo(JSBI.BigInt(0)) 16 | ? '0' 17 | : currencyAmount.greaterThan(CURRENCY_AMOUNT_MIN) 18 | ? currencyAmount.toSignificant(significantDigits) 19 | : `<${CURRENCY_AMOUNT_MIN.toSignificant(1)}`} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/data/TotalSupply.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from '@ethersproject/bignumber' 2 | import { Token, TokenAmount } from '@uniswap/sdk' 3 | import { useTokenContract } from '../hooks/useContract' 4 | import { useSingleCallResult } from '../state/multicall/hooks' 5 | 6 | // returns undefined if input token is undefined, or fails to get token contract, 7 | // or contract total supply cannot be fetched 8 | export function useTotalSupply(token?: Token): TokenAmount | undefined { 9 | const contract = useTokenContract(token?.address, false) 10 | 11 | const totalSupply: BigNumber = useSingleCallResult(contract, 'totalSupply')?.result?.[0] 12 | 13 | return token && totalSupply ? new TokenAmount(token, totalSupply.toString()) : undefined 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/svg/circle_ETH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/data/Allowances.ts: -------------------------------------------------------------------------------- 1 | import { Token, TokenAmount } from '@uniswap/sdk' 2 | import { useMemo } from 'react' 3 | 4 | import { useTokenContract } from '../hooks/useContract' 5 | import { useSingleCallResult } from '../state/multicall/hooks' 6 | 7 | export function useTokenAllowance(token?: Token, owner?: string, spender?: string): TokenAmount | undefined { 8 | const contract = useTokenContract(token?.address, false) 9 | 10 | const inputs = useMemo(() => [owner, spender], [owner, spender]) 11 | const allowance = useSingleCallResult(contract, 'allowance', inputs).result 12 | 13 | return useMemo(() => (token && allowance ? new TokenAmount(token, allowance.toString()) : undefined), [ 14 | token, 15 | allowance 16 | ]) 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for improving the UX of the Antimatter Interface 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/assets/images/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/hooks/useInterval.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export default function useInterval(callback: () => void, delay: null | number, leading = true) { 4 | const savedCallback = useRef<() => void>() 5 | 6 | // Remember the latest callback. 7 | useEffect(() => { 8 | savedCallback.current = callback 9 | }, [callback]) 10 | 11 | // Set up the interval. 12 | useEffect(() => { 13 | function tick() { 14 | const current = savedCallback.current 15 | current && current() 16 | } 17 | 18 | if (delay !== null) { 19 | if (leading) tick() 20 | const id = setInterval(tick, delay) 21 | return () => clearInterval(id) 22 | } 23 | return undefined 24 | }, [delay, leading]) 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/showPassDateTime.ts: -------------------------------------------------------------------------------- 1 | export default function showPassDateTime(preTime: Date): string { 2 | const nowTime = new Date() 3 | const passTime = nowTime.getTime() - preTime.getTime() 4 | const base = 1000 5 | if (passTime < 0) return '-' 6 | if (passTime < base * 60) { 7 | return Math.floor(passTime / base) + ' seconds ago' 8 | } 9 | if (passTime < base * 60 * 60) { 10 | return Math.floor(passTime / (base * 60)) + ' minutes ago' 11 | } 12 | if (passTime < base * 60 * 60 * 24) { 13 | return Math.floor(passTime / (base * 60 * 60)) + ' hours ago' 14 | } 15 | if (passTime < base * 60 * 60 * 24 * 3) { 16 | return Math.floor(passTime / (base * 60 * 60 * 24)) + ' days ago' 17 | } 18 | return preTime.toLocaleString('en') 19 | } 20 | -------------------------------------------------------------------------------- /src/hooks/useCopyClipboard.ts: -------------------------------------------------------------------------------- 1 | import copy from 'copy-to-clipboard' 2 | import { useCallback, useEffect, useState } from 'react' 3 | 4 | export default function useCopyClipboard(timeout = 500): [boolean, (toCopy: string) => void] { 5 | const [isCopied, setIsCopied] = useState(false) 6 | 7 | const staticCopy = useCallback(text => { 8 | const didCopy = copy(text) 9 | setIsCopied(didCopy) 10 | }, []) 11 | 12 | useEffect(() => { 13 | if (isCopied) { 14 | const hide = setTimeout(() => { 15 | setIsCopied(false) 16 | }, timeout) 17 | 18 | return () => { 19 | clearTimeout(hide) 20 | } 21 | } 22 | return undefined 23 | }, [isCopied, setIsCopied, timeout]) 24 | 25 | return [isCopied, staticCopy] 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/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 | -------------------------------------------------------------------------------- /src/assets/svg/sell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Column/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Column = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: flex-start; 7 | ` 8 | export const ColumnCenter = styled(Column)` 9 | width: 100%; 10 | align-items: center; 11 | ` 12 | 13 | export const AutoColumn = styled.div<{ 14 | gap?: 'sm' | 'md' | 'lg' | string 15 | justify?: 'stretch' | 'center' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'space-between' 16 | }>` 17 | display: grid; 18 | grid-auto-rows: auto; 19 | grid-row-gap: ${({ gap }) => (gap === 'sm' && '8px') || (gap === 'md' && '12px') || (gap === 'lg' && '24px') || gap}; 20 | justify-items: ${({ justify }) => justify && justify}; 21 | ` 22 | 23 | export default Column 24 | -------------------------------------------------------------------------------- /src/constants/tokenLists/uniswap-v2-unsupported.tokenlist.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Uniswap V2 Unsupported List", 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 | } 23 | -------------------------------------------------------------------------------- /src/constants/v1/index.ts: -------------------------------------------------------------------------------- 1 | import { Interface } from '@ethersproject/abi' 2 | import { ChainId } from '@uniswap/sdk' 3 | import V1_EXCHANGE_ABI from './v1_exchange.json' 4 | import V1_FACTORY_ABI from './v1_factory.json' 5 | 6 | const V1_FACTORY_ADDRESSES: { [chainId in ChainId]: string } = { 7 | [ChainId.MAINNET]: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95', 8 | [ChainId.ROPSTEN]: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351', 9 | [ChainId.BSC]: '', 10 | [ChainId.Arbitrum]: '', 11 | [ChainId.Avalanche]: '' 12 | } 13 | 14 | const V1_FACTORY_INTERFACE = new Interface(V1_FACTORY_ABI) 15 | const V1_EXCHANGE_INTERFACE = new Interface(V1_EXCHANGE_ABI) 16 | 17 | export { V1_FACTORY_ADDRESSES, V1_FACTORY_INTERFACE, V1_FACTORY_ABI, V1_EXCHANGE_INTERFACE, V1_EXCHANGE_ABI } 18 | -------------------------------------------------------------------------------- /src/assets/images/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/images/question.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/option/wrapPromise.ts: -------------------------------------------------------------------------------- 1 | export interface WrappedPromise { 2 | read: () => T | undefined | Error 3 | } 4 | 5 | export default function wrapPromise(promise: Promise): WrappedPromise { 6 | let status = 'pending' 7 | let result: T | Error | undefined 8 | const suspender = promise.then( 9 | (r: any) => { 10 | status = 'success' 11 | result = r 12 | }, 13 | (e: Error) => { 14 | status = 'error' 15 | result = e 16 | } 17 | ) 18 | return { 19 | read() { 20 | if (status === 'pending') { 21 | throw suspender 22 | } else if (status === 'error') { 23 | throw result 24 | } else if (status === 'success') { 25 | return result 26 | } 27 | return undefined 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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": [ 12 | "node_modules/**/*" 13 | ], 14 | "settings": { 15 | "react": { 16 | "version": "detect" 17 | } 18 | }, 19 | "extends": [ 20 | "plugin:react/recommended", 21 | "plugin:@typescript-eslint/recommended", 22 | "plugin:react-hooks/recommended", 23 | "prettier/@typescript-eslint", 24 | "plugin:prettier/recommended" 25 | ], 26 | "rules": { 27 | "@typescript-eslint/explicit-function-return-type": "off", 28 | "prettier/prettier": "error", 29 | "@typescript-eslint/no-explicit-any": "off" 30 | } 31 | } -------------------------------------------------------------------------------- /src/hooks/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | const isClient = typeof window === 'object' 4 | 5 | function getSize() { 6 | return { 7 | width: isClient ? window.innerWidth : undefined, 8 | height: isClient ? window.innerHeight : undefined 9 | } 10 | } 11 | 12 | // https://usehooks.com/useWindowSize/ 13 | export function useWindowSize() { 14 | const [windowSize, setWindowSize] = useState(getSize) 15 | 16 | useEffect(() => { 17 | function handleResize() { 18 | setWindowSize(getSize()) 19 | } 20 | 21 | if (isClient) { 22 | window.addEventListener('resize', handleResize) 23 | return () => { 24 | window.removeEventListener('resize', handleResize) 25 | } 26 | } 27 | return undefined 28 | }, []) 29 | 30 | return windowSize 31 | } 32 | -------------------------------------------------------------------------------- /src/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?: {} 17 | } 18 | 19 | declare module 'content-hash' { 20 | declare function decode(x: string): string 21 | declare function getCodec(x: string): string 22 | } 23 | 24 | declare module 'multihashes' { 25 | declare function decode(buff: Uint8Array): { code: number; name: string; length: number; digest: Uint8Array } 26 | declare function toB58String(hash: Uint8Array): string 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/svg/buy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/question-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/huobi.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/hooks/useOnClickOutside.tsx: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect, useRef } from 'react' 2 | 3 | export function useOnClickOutside( 4 | node: RefObject, 5 | handler: undefined | (() => void) 6 | ) { 7 | const handlerRef = useRef void)>(handler) 8 | useEffect(() => { 9 | handlerRef.current = handler 10 | }, [handler]) 11 | 12 | useEffect(() => { 13 | const handleClickOutside = (e: MouseEvent) => { 14 | if (node.current?.contains(e.target as Node) ?? false) { 15 | return 16 | } 17 | if (handlerRef.current) handlerRef.current() 18 | } 19 | 20 | document.addEventListener('mousedown', handleClickOutside) 21 | 22 | return () => { 23 | document.removeEventListener('mousedown', handleClickOutside) 24 | } 25 | }, [node]) 26 | } 27 | -------------------------------------------------------------------------------- /src/assets/svg/huobi_inverted.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/css/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | font-style: normal; 4 | font-weight: 300; 5 | font-display: swap; 6 | src: url('../fonts/Roboto-Light.ttf'); 7 | } 8 | 9 | @font-face { 10 | font-family: 'Roboto'; 11 | font-style: normal; 12 | font-weight: 400; 13 | font-display: swap; 14 | src: url('../fonts/Roboto-Regular.ttf'); 15 | } 16 | @font-face { 17 | font-family: 'Roboto'; 18 | font-style: normal; 19 | font-weight: 500; 20 | font-display: swap; 21 | src: url('../fonts/Roboto-Medium.ttf'); 22 | } 23 | @font-face { 24 | font-family: 'Roboto'; 25 | font-style: normal; 26 | font-weight: bold; 27 | font-display: swap; 28 | src: url('../fonts/Roboto-Bold.ttf'); 29 | } 30 | 31 | @font-face { 32 | font-family: 'Futura PT'; 33 | font-display: block; 34 | src: url('../fonts/FuturaPT-Demi.ttf'); 35 | } -------------------------------------------------------------------------------- /src/hooks/useSocksBalance.ts: -------------------------------------------------------------------------------- 1 | import { JSBI } from '@uniswap/sdk' 2 | import { useMemo } from 'react' 3 | import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks' 4 | import { useActiveWeb3React } from './index' 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 | -------------------------------------------------------------------------------- /src/utils/uriToHttp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Given a URI that may be ipfs, ipns, http, or https protocol, return the fetch-able http(s) URLs for the same content 3 | * @param uri to convert to fetch-able http url 4 | */ 5 | export default function uriToHttp(uri: string): string[] { 6 | const protocol = uri.split(':')[0].toLowerCase() 7 | switch (protocol) { 8 | case 'https': 9 | return [uri] 10 | case 'http': 11 | return ['https' + uri.substr(4), uri] 12 | case 'ipfs': 13 | const hash = uri.match(/^ipfs:(\/\/)?(.*)$/i)?.[2] 14 | return [`https://cloudflare-ipfs.com/ipfs/${hash}/`, `https://ipfs.io/ipfs/${hash}/`] 15 | case 'ipns': 16 | const name = uri.match(/^ipns:(\/\/)?(.*)$/i)?.[2] 17 | return [`https://cloudflare-ipfs.com/ipns/${name}/`, `https://ipfs.io/ipns/${name}/`] 18 | default: 19 | return [] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/state/multicall/utils.ts: -------------------------------------------------------------------------------- 1 | export interface Call { 2 | address: string 3 | callData: string 4 | gasRequired?: number 5 | } 6 | 7 | export function toCallKey(call: Call): string { 8 | let key = `${call.address}-${call.callData}` 9 | if (call.gasRequired) { 10 | if (!Number.isSafeInteger(call.gasRequired)) { 11 | throw new Error(`Invalid number: ${call.gasRequired}`) 12 | } 13 | key += `-${call.gasRequired}` 14 | } 15 | return key 16 | } 17 | 18 | export function parseCallKey(callKey: string): Call { 19 | const pcs = callKey.split('-') 20 | if (![2, 3].includes(pcs.length)) { 21 | throw new Error(`Invalid call key: ${callKey}`) 22 | } 23 | return { 24 | address: pcs[0], 25 | callData: pcs[1], 26 | ...(pcs[2] ? { gasRequired: Number.parseInt(pcs[2]) } : {}), 27 | } 28 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/SearchModal/SortButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text } from 'rebass' 3 | import styled from 'styled-components' 4 | import { RowFixed } from '../Row' 5 | 6 | export const FilterWrapper = styled(RowFixed)` 7 | padding: 8px; 8 | background-color: ${({ theme }) => theme.bg2}; 9 | color: ${({ theme }) => theme.text1}; 10 | border-radius: 8px; 11 | user-select: none; 12 | & > * { 13 | user-select: none; 14 | } 15 | :hover { 16 | cursor: pointer; 17 | } 18 | ` 19 | 20 | export default function SortButton({ 21 | toggleSortOrder, 22 | ascending 23 | }: { 24 | toggleSortOrder: () => void 25 | ascending: boolean 26 | }) { 27 | return ( 28 | 29 | 30 | {ascending ? '↑' : '↓'} 31 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/wrappedCurrency.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, Currency, CurrencyAmount, ETHER, Token, TokenAmount, WETH } from '@uniswap/sdk' 2 | 3 | export function wrappedCurrency(currency: Currency | undefined, chainId: ChainId | undefined): Token | undefined { 4 | return chainId && currency === ETHER ? WETH[chainId] : currency instanceof Token ? currency : undefined 5 | } 6 | 7 | export function wrappedCurrencyAmount( 8 | currencyAmount: CurrencyAmount | undefined, 9 | chainId: ChainId | undefined 10 | ): TokenAmount | undefined { 11 | const token = currencyAmount && chainId ? wrappedCurrency(currencyAmount.currency, chainId) : undefined 12 | return token && currencyAmount ? new TokenAmount(token, currencyAmount.raw) : undefined 13 | } 14 | 15 | export function unwrappedToken(token: Token): Currency { 16 | if (token.equals(WETH[token.chainId])) return ETHER 17 | return token 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/contenthashToUri.test.skip.ts: -------------------------------------------------------------------------------- 1 | import contenthashToUri, { hexToUint8Array } from './contenthashToUri' 2 | 3 | // this test is skipped for now because importing CID results in 4 | // TypeError: TextDecoder is not a constructor 5 | 6 | describe('#contenthashToUri', () => { 7 | it('1inch.tokens.eth contenthash', () => { 8 | expect(contenthashToUri('0xe3010170122013e051d1cfff20606de36845d4fe28deb9861a319a5bc8596fa4e610e8803918')).toEqual( 9 | 'ipfs://QmPgEqyV3m8SB52BS2j2mJpu9zGprhj2BGCHtRiiw2fdM1' 10 | ) 11 | }) 12 | it('uniswap.eth contenthash', () => { 13 | expect(contenthashToUri('0xe5010170000f6170702e756e69737761702e6f7267')).toEqual('ipns://app.uniswap.org') 14 | }) 15 | }) 16 | 17 | describe('#hexToUint8Array', () => { 18 | it('common case', () => { 19 | expect(hexToUint8Array('0x010203fdfeff')).toEqual(new Uint8Array([1, 2, 3, 253, 254, 255])) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/components/Identicon/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react' 2 | 3 | import styled from 'styled-components' 4 | 5 | import { useActiveWeb3React } from '../../hooks' 6 | import Jazzicon from 'jazzicon' 7 | 8 | const StyledIdenticonContainer = styled.div` 9 | height: 1rem; 10 | width: 1rem; 11 | border-radius: 1.125rem; 12 | background-color: ${({ theme }) => theme.bg4}; 13 | ` 14 | 15 | export default function Identicon() { 16 | const ref = useRef() 17 | 18 | const { account } = useActiveWeb3React() 19 | 20 | useEffect(() => { 21 | if (account && ref.current) { 22 | ref.current.innerHTML = '' 23 | ref.current.appendChild(Jazzicon(16, parseInt(account.slice(2, 10), 16))) 24 | } 25 | }, [account]) 26 | 27 | // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /src/state/swap/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export enum Field { 4 | INPUT = 'INPUT', 5 | OUTPUT = 'OUTPUT' 6 | } 7 | 8 | export enum OptionType { 9 | CALL = 'CALL', 10 | PUT = 'PUT' 11 | } 12 | 13 | export enum Auction { 14 | BUY = 'BUY', 15 | SELL = 'SELL' 16 | } 17 | 18 | export const selectCurrency = createAction<{ field: Field; currencyId: string }>('swap/selectCurrency') 19 | export const switchCurrencies = createAction('swap/switchCurrencies') 20 | export const typeInput = createAction<{ field: Field; typedValue: string }>('swap/typeInput') 21 | export const replaceSwapState = createAction<{ 22 | field: Field 23 | typedValue: string 24 | inputCurrencyId?: string 25 | outputCurrencyId?: string 26 | recipient: string | null 27 | }>('swap/replaceSwapState') 28 | export const setRecipient = createAction<{ recipient: string | null }>('swap/setRecipient') 29 | -------------------------------------------------------------------------------- /src/components/Header/ShortLogo.tsx: -------------------------------------------------------------------------------- 1 | import { SVGAttributes } from 'react' 2 | 3 | export const ShortLogo = (props: SVGAttributes) => { 4 | return ( 5 | 6 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/hooks/useIsWindowVisible.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react' 2 | 3 | const VISIBILITY_STATE_SUPPORTED = 'visibilityState' in document 4 | 5 | function isWindowVisible() { 6 | return !VISIBILITY_STATE_SUPPORTED || document.visibilityState !== 'hidden' 7 | } 8 | 9 | /** 10 | * Returns whether the window is currently visible to the user. 11 | */ 12 | export default function useIsWindowVisible(): boolean { 13 | const [focused, setFocused] = useState(isWindowVisible()) 14 | const listener = useCallback(() => { 15 | setFocused(isWindowVisible()) 16 | }, [setFocused]) 17 | 18 | useEffect(() => { 19 | if (!VISIBILITY_STATE_SUPPORTED) return undefined 20 | 21 | document.addEventListener('visibilitychange', listener) 22 | return () => { 23 | document.removeEventListener('visibilitychange', listener) 24 | } 25 | }, [listener]) 26 | 27 | return focused 28 | } 29 | -------------------------------------------------------------------------------- /src/assets/svg/check_circle.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/pages/ComingSoon.tsx: -------------------------------------------------------------------------------- 1 | import { Dots } from 'components/swap/styleds' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | const Frame = styled.div` 5 | width: 500px; 6 | height: 280px; 7 | border: 1px solid rgba(255, 255, 255, 0.2); 8 | box-sizing: border-box; 9 | border-radius: 32px; 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | justify-content: center; 14 | color: rgba(255, 255, 255, 0.6); 15 | ` 16 | 17 | const Title = styled.p` 18 | font-weight: 500; 19 | font-size: 24px; 20 | line-height: 88.69%; 21 | color: #ffffff; 22 | ` 23 | 24 | export default function ComingSoon() { 25 | return ( 26 | 27 | 28 | Coming Soon <Dots /> 29 | 30 | 31 |
This section is still implemeting.
32 |
Please come back later
33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/MarketStrategy/TypeRadioButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { RadioButton } from './RadioButton' 3 | 4 | interface RadioOption { 5 | label: string 6 | option: string 7 | } 8 | 9 | export function TypeRadioButton({ 10 | name, 11 | options, 12 | selected, 13 | onCheck 14 | }: { 15 | name: string 16 | options: RadioOption[] 17 | selected: string 18 | onCheck: (tokenType: string) => void 19 | }) { 20 | return ( 21 |
22 | {options.map(({ label, option }) => { 23 | return ( 24 | onCheck(option)} 30 | /> 31 | ) 32 | })} 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/assets/svg/lightcircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Path 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/Image/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ImgHTMLAttributes, useMemo, useState } from 'react' 2 | 3 | const BAD_SRCS: { [tokenAddress: string]: true } = {} 4 | 5 | export default function Image({ 6 | src, 7 | alt = '', 8 | style, 9 | className, 10 | altSrc, 11 | ...rest 12 | }: { 13 | src: string 14 | alt?: string 15 | style?: React.CSSProperties 16 | className?: string 17 | altSrc?: string 18 | } & Omit, 'src' | 'alt' | 'style'>) { 19 | const [, refresh] = useState(0) 20 | const srcs = useMemo(() => [src, altSrc], [src, altSrc]) 21 | const srcStr = srcs.find(item => !BAD_SRCS[item ?? '']) 22 | return ( 23 | {alt} { 30 | if (srcStr) BAD_SRCS[srcStr] = true 31 | refresh(i => i + 1) 32 | }} 33 | /> 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/swap/TradePrice.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CurrencyAmount, ETHER } from '@uniswap/sdk' 3 | import { Text } from 'rebass' 4 | import { Symbol } from '../../constants' 5 | import { useActiveWeb3React } from '../../hooks' 6 | 7 | interface TradePriceProps { 8 | currencyAmount?: CurrencyAmount 9 | } 10 | 11 | export default function TradePrice({ currencyAmount }: TradePriceProps) { 12 | const { chainId } = useActiveWeb3React() 13 | return ( 14 | 20 | {currencyAmount ? ( 21 | <> 22 | ~ {currencyAmount.toExact().toString() ?? '-'}{' '} 23 | {currencyAmount.currency === ETHER ? Symbol[chainId ?? 1] : currencyAmount.currency.symbol} 24 | 25 | ) : ( 26 | '-' 27 | )} 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/pages/MatterToken/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { LPT_TYPE } from 'constants/matterToken/matterTokenTokens' 4 | import MatterTokenManageModal, { cardWidth } from './MatterTokenManageModal' 5 | 6 | const Wrapper = styled.div` 7 | width: 100%; 8 | padding: 50px; 9 | grid-template-rows: 340px; 10 | grid-template-columns: repeat(auto-fill, ${cardWidth}); 11 | grid-gap: 20px; 12 | display: grid; 13 | ${({ theme }) => theme.mediaWidth.upToMedium` 14 | grid-template-columns: 100%; 15 | padding:0 16 | `} 17 | justify-content: center; 18 | margin-bottom: auto; 19 | ${({ theme }) => theme.mediaWidth.upToLarge` 20 | padding-bottom: 100px 21 | `} 22 | ` 23 | export default function MatterToken() { 24 | return ( 25 | 26 | {Object.keys(LPT_TYPE).map(lpt => ( 27 | 28 | ))} 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /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": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitThis": true, 18 | "noImplicitReturns": true, 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "jsx": "preserve", 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/components/CurrencyLogo/CallPutToken.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Currency } from '@uniswap/sdk' 3 | import styled from 'styled-components' 4 | import CurrencyLogo from './' 5 | import { ReactComponent as CallIcon } from '../../assets/svg/call_icon.svg' 6 | import { ReactComponent as PutIcon } from '../../assets/svg/put_icon.svg' 7 | 8 | const StyledCallIcon = styled(CallIcon)` 9 | position:absolute 10 | bottom:-2px; 11 | right:-2px 12 | ` 13 | 14 | const StyledPutIcon = styled(PutIcon)` 15 | position:absolute 16 | bottom:-2px; 17 | right:-2px 18 | ` 19 | 20 | export default function CallPutToken({ 21 | currency, 22 | isCall, 23 | size = '24px' 24 | }: { 25 | currency: Currency 26 | isCall: boolean 27 | size?: string 28 | }) { 29 | return ( 30 |
31 | 32 | {isCall ? : } 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/chunkArray.test.ts: -------------------------------------------------------------------------------- 1 | import chunkArray from './chunkArray' 2 | 3 | describe('#chunkArray', () => { 4 | it('size 1', () => { 5 | expect(chunkArray([1, 2, 3], 1)).toEqual([[1], [2], [3]]) 6 | }) 7 | it('size 0 throws', () => { 8 | expect(() => chunkArray([1, 2, 3], 0)).toThrow('maxChunkSize must be gte 1') 9 | }) 10 | it('size gte items', () => { 11 | expect(chunkArray([1, 2, 3], 3)).toEqual([[1, 2, 3]]) 12 | expect(chunkArray([1, 2, 3], 4)).toEqual([[1, 2, 3]]) 13 | }) 14 | it('size exact half', () => { 15 | expect(chunkArray([1, 2, 3, 4], 2)).toEqual([ 16 | [1, 2], 17 | [3, 4] 18 | ]) 19 | }) 20 | it('evenly distributes', () => { 21 | const chunked = chunkArray([...Array(100).keys()], 40) 22 | 23 | expect(chunked).toEqual([ 24 | [...Array(34).keys()], 25 | [...Array(34).keys()].map(i => i + 34), 26 | [...Array(32).keys()].map(i => i + 68) 27 | ]) 28 | 29 | expect(chunked[0][0]).toEqual(0) 30 | expect(chunked[2][31]).toEqual(99) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/state/swap/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Store } from 'redux' 2 | import { Field, selectCurrency } from './actions' 3 | import reducer, { SwapState } from './reducer' 4 | 5 | describe('swap reducer', () => { 6 | let store: Store 7 | 8 | beforeEach(() => { 9 | store = createStore(reducer, { 10 | [Field.OUTPUT]: { currencyId: '' }, 11 | [Field.INPUT]: { currencyId: '' }, 12 | typedValue: '', 13 | independentField: Field.INPUT, 14 | recipient: null 15 | }) 16 | }) 17 | 18 | describe('selectToken', () => { 19 | it('changes token', () => { 20 | store.dispatch( 21 | selectCurrency({ 22 | field: Field.OUTPUT, 23 | currencyId: '0x0000' 24 | }) 25 | ) 26 | 27 | expect(store.getState()).toEqual({ 28 | [Field.OUTPUT]: { currencyId: '0x0000' }, 29 | [Field.INPUT]: { currencyId: '' }, 30 | typedValue: '', 31 | independentField: Field.INPUT, 32 | recipient: null 33 | }) 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /src/pages/Vote/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const handleColorType = (status?: any, theme?: any) => { 4 | switch (status) { 5 | case 'pending': 6 | return theme.blue1 7 | case 'active': 8 | return theme.blue1 9 | case 'succeeded': 10 | return theme.green1 11 | case 'defeated': 12 | return theme.red1 13 | case 'queued': 14 | return theme.text3 15 | case 'executed': 16 | return theme.green1 17 | case 'canceled': 18 | return theme.text3 19 | case 'expired': 20 | return theme.text3 21 | default: 22 | return theme.text3 23 | } 24 | } 25 | 26 | export const ProposalStatus = styled.span<{ status: string }>` 27 | font-size: 0.825rem; 28 | font-weight: 600; 29 | padding: 0.5rem; 30 | border-radius: 8px; 31 | color: ${({ status, theme }) => handleColorType(status, theme)}; 32 | border: 1px solid ${({ status, theme }) => handleColorType(status, theme)}; 33 | width: fit-content; 34 | justify-self: flex-end; 35 | text-transform: uppercase; 36 | ` 37 | -------------------------------------------------------------------------------- /src/assets/svg/cross_circle.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/MarketStrategy/RadioButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Label = styled.label<{ checked: boolean }>` 5 | wrap: nowrap; 6 | display: flex; 7 | align-items:center; 8 | 9 | :before { 10 | content: ''; 11 | display: block; 12 | width: 6px; 13 | height: 6px; 14 | background-color: ${({ theme, checked }) => (checked ? theme.primary1 : 'transparent')}; 15 | border-radius: 50%; 16 | margin-right:8px 17 | border: 4px solid ${({ theme }) => theme.bg1}; 18 | box-shadow: 0 0 0 1px ${({ theme, checked }) => (checked ? theme.primary1 : theme.text1)}; 19 | 20 | } 21 | & input { 22 | display: none; 23 | } 24 | ` 25 | 26 | export function RadioButton({ 27 | label, 28 | name, 29 | checked, 30 | onCheck 31 | }: { 32 | label: string 33 | name: string 34 | checked: boolean 35 | onCheck: () => void 36 | }) { 37 | return ( 38 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Tooltip/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react' 2 | import styled from 'styled-components' 3 | import Popover, { PopoverProps } from '../Popover' 4 | 5 | const TooltipContainer = styled.div` 6 | width: 228px; 7 | padding: 0.6rem 1rem; 8 | line-height: 150%; 9 | font-weight: 400; 10 | white-space: pre-line; 11 | ` 12 | 13 | interface TooltipProps extends Omit { 14 | text: string 15 | } 16 | 17 | export default function Tooltip({ text, ...rest }: TooltipProps) { 18 | return {text}} {...rest} /> 19 | } 20 | 21 | export function MouseoverTooltip({ children, ...rest }: Omit) { 22 | const [show, setShow] = useState(false) 23 | const open = useCallback(() => setShow(true), [setShow]) 24 | const close = useCallback(() => setShow(false), [setShow]) 25 | return ( 26 | 27 |
28 | {children} 29 |
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Confetti/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactConfetti from 'react-confetti' 3 | import { useWindowSize } from '../../hooks/useWindowSize' 4 | 5 | // eslint-disable-next-line react/prop-types 6 | export default function Confetti({ start, variant }: { start: boolean; variant?: string }) { 7 | const { width, height } = useWindowSize() 8 | 9 | const _variant = variant ? variant : height && width && height > 1.5 * width ? 'bottom' : variant 10 | 11 | return start && width && height ? ( 12 | 31 | ) : null 32 | } 33 | -------------------------------------------------------------------------------- /src/assets/svg/plus.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /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 | } 31 | 32 | export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber') 33 | export const setOpenModal = createAction('application/setOpenModal') 34 | export const addPopup = createAction<{ key?: string; removeAfterMs?: number | null; content: PopupContent }>( 35 | 'application/addPopup' 36 | ) 37 | export const removePopup = createAction<{ key: string }>('application/removePopup') 38 | -------------------------------------------------------------------------------- /src/state/transactions/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | import { ChainId } from '@uniswap/sdk' 3 | 4 | export interface SerializableTransactionReceipt { 5 | to: string 6 | from: string 7 | contractAddress: string 8 | transactionIndex: number 9 | blockHash: string 10 | transactionHash: string 11 | blockNumber: number 12 | status?: number 13 | } 14 | 15 | export const addTransaction = createAction<{ 16 | chainId: ChainId 17 | hash: string 18 | from: string 19 | approval?: { tokenAddress: string; spender: string } 20 | claim?: { recipient: string } 21 | summary?: string 22 | }>('transactions/addTransaction') 23 | export const clearAllTransactions = createAction<{ chainId: ChainId }>('transactions/clearAllTransactions') 24 | export const finalizeTransaction = createAction<{ 25 | chainId: ChainId 26 | hash: string 27 | receipt: SerializableTransactionReceipt 28 | }>('transactions/finalizeTransaction') 29 | export const checkedTransaction = createAction<{ 30 | chainId: ChainId 31 | hash: string 32 | blockNumber: number 33 | }>('transactions/checkedTransaction') 34 | -------------------------------------------------------------------------------- /src/components/swap/confirmPriceImpactWithoutFee.ts: -------------------------------------------------------------------------------- 1 | import { Percent } from '@uniswap/sdk' 2 | import { ALLOWED_PRICE_IMPACT_HIGH, PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN } from '../../constants' 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 | -------------------------------------------------------------------------------- /src/assets/svg/put_type.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/utils/trades.ts: -------------------------------------------------------------------------------- 1 | import { ZERO_PERCENT, ONE_HUNDRED_PERCENT } from './../constants/index' 2 | import { Trade, Percent, currencyEquals } from '@uniswap/sdk' 3 | 4 | // returns whether tradeB is better than tradeA by at least a threshold percentage amount 5 | export function isTradeBetter( 6 | tradeA: Trade | undefined | null, 7 | tradeB: Trade | undefined | null, 8 | minimumDelta: Percent = ZERO_PERCENT 9 | ): boolean | undefined { 10 | if (tradeA && !tradeB) return false 11 | if (tradeB && !tradeA) return true 12 | if (!tradeA || !tradeB) return undefined 13 | 14 | if ( 15 | tradeA.tradeType !== tradeB.tradeType || 16 | !currencyEquals(tradeA.inputAmount.currency, tradeB.inputAmount.currency) || 17 | !currencyEquals(tradeB.outputAmount.currency, tradeB.outputAmount.currency) 18 | ) { 19 | throw new Error('Trades are not comparable') 20 | } 21 | 22 | if (minimumDelta.equalTo(ZERO_PERCENT)) { 23 | return tradeA.executionPrice.lessThan(tradeB.executionPrice) 24 | } else { 25 | return tradeA.executionPrice.raw.multiply(minimumDelta.add(ONE_HUNDRED_PERCENT)).lessThan(tradeB.executionPrice) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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: true })) 26 | //dispatch(updateUserDarkMode({ userDarkMode: false })) 27 | } else if (theme.toLowerCase() === 'dark') { 28 | dispatch(updateUserDarkMode({ userDarkMode: true })) 29 | } 30 | }, [dispatch, search]) 31 | 32 | return null 33 | } 34 | -------------------------------------------------------------------------------- /src/state/mint/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Store } from 'redux' 2 | 3 | import { Field, typeInput } from './actions' 4 | import reducer, { MintState } from './reducer' 5 | 6 | describe('mint reducer', () => { 7 | let store: Store 8 | 9 | beforeEach(() => { 10 | store = createStore(reducer, { 11 | independentField: Field.CURRENCY_A, 12 | typedValue: '', 13 | otherTypedValue: '' 14 | }) 15 | }) 16 | 17 | describe('typeInput', () => { 18 | it('sets typed value', () => { 19 | store.dispatch(typeInput({ field: Field.CURRENCY_A, typedValue: '1.0', noLiquidity: false })) 20 | expect(store.getState()).toEqual({ independentField: Field.CURRENCY_A, typedValue: '1.0', otherTypedValue: '' }) 21 | }) 22 | it('clears other value', () => { 23 | store.dispatch(typeInput({ field: Field.CURRENCY_A, typedValue: '1.0', noLiquidity: false })) 24 | store.dispatch(typeInput({ field: Field.CURRENCY_B, typedValue: '1.0', noLiquidity: false })) 25 | expect(store.getState()).toEqual({ independentField: Field.CURRENCY_B, typedValue: '1.0', otherTypedValue: '' }) 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Antimatter Overview 2 | Antimatter = Dual Investment + Bull & Bear Tokens + Financial NFTs + Antimatter DAO + more 3 | Antimatter is positioned to be the gateway for DeFi derivatives, including four main products now, including Dual Investment, Bull & Bear Tokens, Financial NFTs, as well as Antimatter DAO. 4 | ## Product - Bull & Bear Tokens 5 | In its tokenized none-oracle perpetual options protocol, users can buy call and put options as an individual holder (buyer) or generate or redeem call and put options as one of the liquidity providers on (Currently on testnet). They can also trade on-chain options for trading profits or to hedge their positions. 6 | Understand Tokenized Perpetual Options in 1 minute 7 | What Antimatter creates is decentralized Bull and Bear Tokens, which are leverage embedded and non-expiry. 8 | When the Underlying Asset price increases, the Bull Token price will increase more. 9 | When the Underlying Asset price decreases, the Bear token price will decrease more. 10 | It is simple as it is, leveraged tokens. 11 | Bullish and want to have more price exposure? Buy Bull Token. Bearish and want to have some hedge? Buy Bear Token. 12 | ​ 13 | 14 | -------------------------------------------------------------------------------- /src/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | // import { HelpCircle } from 'react-feather' 3 | import { ImageProps } from 'rebass' 4 | import { ReactComponent as Unknown } from 'assets/svg/circle_unknown.svg' 5 | import unknownUrl from 'assets/svg/circle_unknown.svg' 6 | 7 | const BAD_SRCS: { [tokenAddress: string]: true } = {} 8 | 9 | export interface LogoProps extends Pick { 10 | srcs: string[] 11 | } 12 | 13 | /** 14 | * Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert 15 | */ 16 | export default function Logo({ srcs, alt, ...rest }: LogoProps) { 17 | const [, refresh] = useState(0) 18 | 19 | const src: string | undefined = srcs.find(src => !BAD_SRCS[src]) 20 | 21 | if (src) { 22 | return ( 23 | {alt} { 28 | if (src) BAD_SRCS[src] = true 29 | refresh(i => i + 1) 30 | }} 31 | /> 32 | ) 33 | } 34 | return 35 | // return 36 | } 37 | -------------------------------------------------------------------------------- /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 swap from './swap/reducer' 9 | import mint from './mint/reducer' 10 | import lists from './lists/reducer' 11 | import burn from './burn/reducer' 12 | import multicall from './multicall/reducer' 13 | 14 | const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists'] 15 | 16 | const store = configureStore({ 17 | reducer: { 18 | application, 19 | user, 20 | transactions, 21 | swap, 22 | mint, 23 | burn, 24 | multicall, 25 | lists 26 | }, 27 | middleware: [...getDefaultMiddleware({ thunk: false }), save({ states: PERSISTED_KEYS })], 28 | preloadedState: load({ states: PERSISTED_KEYS }) 29 | }) 30 | 31 | store.dispatch(updateVersion()) 32 | 33 | export default store 34 | 35 | export type AppState = ReturnType 36 | export type AppDispatch = typeof store.dispatch 37 | -------------------------------------------------------------------------------- /src/state/user/updater.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { AppDispatch } from '../index' 4 | import { updateMatchesDarkMode } from './actions' 5 | 6 | export default function Updater(): null { 7 | const dispatch = useDispatch() 8 | 9 | // keep dark mode in sync with the system 10 | useEffect(() => { 11 | const darkHandler = (match: MediaQueryListEvent) => { 12 | dispatch(updateMatchesDarkMode({ matchesDarkMode: match.matches })) 13 | } 14 | 15 | const match = window?.matchMedia('(prefers-color-scheme: dark)') 16 | dispatch(updateMatchesDarkMode({ matchesDarkMode: match.matches })) 17 | 18 | if (match?.addListener) { 19 | match?.addListener(darkHandler) 20 | } else if (match?.addEventListener) { 21 | match?.addEventListener('change', darkHandler) 22 | } 23 | 24 | return () => { 25 | if (match?.removeListener) { 26 | match?.removeListener(darkHandler) 27 | } else if (match?.removeEventListener) { 28 | match?.removeEventListener('change', darkHandler) 29 | } 30 | } 31 | }, [dispatch]) 32 | 33 | return null 34 | } 35 | -------------------------------------------------------------------------------- /src/components/swap/SwapRoute.tsx: -------------------------------------------------------------------------------- 1 | import { Trade } from '@uniswap/sdk' 2 | import React, { Fragment, memo, useContext } from 'react' 3 | import { ChevronRight } from 'react-feather' 4 | import { Flex } from 'rebass' 5 | import { ThemeContext } from 'styled-components' 6 | import { TYPE } from '../../theme' 7 | import { unwrappedToken } from 'utils/wrappedCurrency' 8 | 9 | export default memo(function SwapRoute({ trade }: { trade: Trade }) { 10 | const theme = useContext(ThemeContext) 11 | return ( 12 | 13 | {trade.route.path.map((token, i, path) => { 14 | const isLastItem: boolean = i === path.length - 1 15 | const currency = unwrappedToken(token) 16 | return ( 17 | 18 | 19 | 20 | {currency.symbol} 21 | 22 | 23 | {isLastItem ? null : } 24 | 25 | ) 26 | })} 27 | 28 | ) 29 | }) 30 | -------------------------------------------------------------------------------- /src/utils/uriToHttp.test.ts: -------------------------------------------------------------------------------- 1 | import uriToHttp from './uriToHttp' 2 | 3 | describe('uriToHttp', () => { 4 | it('returns .eth.link for ens names', () => { 5 | expect(uriToHttp('t2crtokens.eth')).toEqual([]) 6 | }) 7 | it('returns https first for http', () => { 8 | expect(uriToHttp('http://test.com')).toEqual(['https://test.com', 'http://test.com']) 9 | }) 10 | it('returns https for https', () => { 11 | expect(uriToHttp('https://test.com')).toEqual(['https://test.com']) 12 | }) 13 | it('returns ipfs gateways for ipfs:// urls', () => { 14 | expect(uriToHttp('ipfs://QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ')).toEqual([ 15 | 'https://cloudflare-ipfs.com/ipfs/QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ/', 16 | 'https://ipfs.io/ipfs/QmV8AfDE8GFSGQvt3vck8EwAzsPuNTmtP8VcQJE3qxRPaZ/' 17 | ]) 18 | }) 19 | it('returns ipns gateways for ipns:// urls', () => { 20 | expect(uriToHttp('ipns://app.uniswap.org')).toEqual([ 21 | 'https://cloudflare-ipfs.com/ipns/app.uniswap.org/', 22 | 'https://ipfs.io/ipns/app.uniswap.org/' 23 | ]) 24 | }) 25 | it('returns empty array for invalid scheme', () => { 26 | expect(uriToHttp('blah:test')).toEqual([]) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/pages/AddLiquidity/redirects.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Redirect, RouteComponentProps } from 'react-router-dom' 3 | import AddLiquidity from './index' 4 | 5 | export function RedirectToAddLiquidity() { 6 | return 7 | } 8 | 9 | const OLD_PATH_STRUCTURE = /^(0x[a-fA-F0-9]{40})-(0x[a-fA-F0-9]{40})$/ 10 | export function RedirectOldAddLiquidityPathStructure(props: RouteComponentProps<{ currencyIdA: string }>) { 11 | const { 12 | match: { 13 | params: { currencyIdA } 14 | } 15 | } = props 16 | const match = currencyIdA.match(OLD_PATH_STRUCTURE) 17 | if (match?.length) { 18 | return 19 | } 20 | 21 | // return 22 | return 23 | } 24 | 25 | export function RedirectDuplicateTokenIds(props: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) { 26 | const { 27 | match: { 28 | params: { currencyIdA, currencyIdB } 29 | } 30 | } = props 31 | if (currencyIdA.toLowerCase() === currencyIdB.toLowerCase()) { 32 | return 33 | } 34 | // return 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/constants/abis/migrator.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_factoryV1", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "address", 11 | "name": "_router", 12 | "type": "address" 13 | } 14 | ], 15 | "stateMutability": "nonpayable", 16 | "type": "constructor" 17 | }, 18 | { 19 | "inputs": [ 20 | { 21 | "internalType": "address", 22 | "name": "token", 23 | "type": "address" 24 | }, 25 | { 26 | "internalType": "uint256", 27 | "name": "amountTokenMin", 28 | "type": "uint256" 29 | }, 30 | { 31 | "internalType": "uint256", 32 | "name": "amountETHMin", 33 | "type": "uint256" 34 | }, 35 | { 36 | "internalType": "address", 37 | "name": "to", 38 | "type": "address" 39 | }, 40 | { 41 | "internalType": "uint256", 42 | "name": "deadline", 43 | "type": "uint256" 44 | } 45 | ], 46 | "name": "migrate", 47 | "outputs": [], 48 | "stateMutability": "nonpayable", 49 | "type": "function" 50 | }, 51 | { 52 | "stateMutability": "payable", 53 | "type": "receive" 54 | } 55 | ] 56 | -------------------------------------------------------------------------------- /src/utils/parseENSAddress.test.ts: -------------------------------------------------------------------------------- 1 | import { parseENSAddress } from './parseENSAddress' 2 | 3 | describe('parseENSAddress', () => { 4 | it('test cases', () => { 5 | expect(parseENSAddress('hello.eth')).toEqual({ ensName: 'hello.eth', ensPath: undefined }) 6 | expect(parseENSAddress('hello.eth/')).toEqual({ ensName: 'hello.eth', ensPath: '/' }) 7 | expect(parseENSAddress('hello.world.eth/')).toEqual({ ensName: 'hello.world.eth', ensPath: '/' }) 8 | expect(parseENSAddress('hello.world.eth/abcdef')).toEqual({ ensName: 'hello.world.eth', ensPath: '/abcdef' }) 9 | expect(parseENSAddress('abso.lutely')).toEqual(undefined) 10 | expect(parseENSAddress('abso.lutely.eth')).toEqual({ ensName: 'abso.lutely.eth', ensPath: undefined }) 11 | expect(parseENSAddress('eth')).toEqual(undefined) 12 | expect(parseENSAddress('eth/hello-world')).toEqual(undefined) 13 | expect(parseENSAddress('hello-world.eth')).toEqual({ ensName: 'hello-world.eth', ensPath: undefined }) 14 | expect(parseENSAddress('-prefix-dash.eth')).toEqual(undefined) 15 | expect(parseENSAddress('suffix-dash-.eth')).toEqual(undefined) 16 | expect(parseENSAddress('it.eth')).toEqual({ ensName: 'it.eth', ensPath: undefined }) 17 | expect(parseENSAddress('only-single--dash.eth')).toEqual(undefined) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/components/PairChart/apollo/client.js: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from 'apollo-client' 2 | import { InMemoryCache } from 'apollo-cache-inmemory' 3 | import { HttpLink } from 'apollo-link-http' 4 | 5 | export const client = new ApolloClient({ 6 | link: new HttpLink({ 7 | uri: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswapv2', 8 | }), 9 | cache: new InMemoryCache(), 10 | shouldBatch: true, 11 | }) 12 | 13 | export const healthClient = new ApolloClient({ 14 | link: new HttpLink({ 15 | uri: 'https://api.thegraph.com/index-node/graphql', 16 | }), 17 | cache: new InMemoryCache(), 18 | shouldBatch: true, 19 | }) 20 | 21 | export const v1Client = new ApolloClient({ 22 | link: new HttpLink({ 23 | uri: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap', 24 | }), 25 | cache: new InMemoryCache(), 26 | shouldBatch: true, 27 | }) 28 | 29 | export const stakingClient = new ApolloClient({ 30 | link: new HttpLink({ 31 | uri: 'https://api.thegraph.com/subgraphs/name/way2rach/talisman', 32 | }), 33 | cache: new InMemoryCache(), 34 | shouldBatch: true, 35 | }) 36 | 37 | export const blockClient = new ApolloClient({ 38 | link: new HttpLink({ 39 | uri: 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks', 40 | }), 41 | cache: new InMemoryCache(), 42 | }) 43 | -------------------------------------------------------------------------------- /src/components/DoubleLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import { Currency } from '@uniswap/sdk' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import CurrencyLogo from '../CurrencyLogo' 5 | 6 | const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>` 7 | position: relative; 8 | display: flex; 9 | flex-direction: row; 10 | margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'}; 11 | ` 12 | 13 | interface DoubleCurrencyLogoProps { 14 | margin?: boolean 15 | size?: number 16 | currency0?: Currency 17 | currency1?: Currency 18 | } 19 | 20 | const HigherLogo = styled(CurrencyLogo)` 21 | z-index: 2; 22 | ` 23 | const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>` 24 | position: absolute; 25 | left: ${({ sizeraw }) => '-' + (sizeraw / 2).toString() + 'px'} !important; 26 | ` 27 | 28 | export default function DoubleCurrencyLogo({ 29 | currency0, 30 | currency1, 31 | size = 16, 32 | margin = false 33 | }: DoubleCurrencyLogoProps) { 34 | return ( 35 | 36 | {currency0 && } 37 | {currency1 && } 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/useDebouncedChangeHandler.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef, useState } from 'react' 2 | 3 | /** 4 | * Easy way to debounce the handling of a rapidly changing value, e.g. a changing slider input 5 | * @param value value that is rapidly changing 6 | * @param onChange change handler that should receive the debounced updates to the value 7 | * @param debouncedMs how long we should wait for changes to be applied 8 | */ 9 | export default function useDebouncedChangeHandler( 10 | value: T, 11 | onChange: (newValue: T) => void, 12 | debouncedMs = 100 13 | ): [T, (value: T) => void] { 14 | const [inner, setInner] = useState(() => value) 15 | const timer = useRef>() 16 | 17 | const onChangeInner = useCallback( 18 | (newValue: T) => { 19 | setInner(newValue) 20 | if (timer.current) { 21 | clearTimeout(timer.current) 22 | } 23 | timer.current = setTimeout(() => { 24 | onChange(newValue) 25 | timer.current = undefined 26 | }, debouncedMs) 27 | }, 28 | [debouncedMs, onChange] 29 | ) 30 | 31 | useEffect(() => { 32 | if (timer.current) { 33 | clearTimeout(timer.current) 34 | timer.current = undefined 35 | } 36 | setInner(value) 37 | }, [value]) 38 | 39 | return [inner, onChangeInner] 40 | } 41 | -------------------------------------------------------------------------------- /src/state/user/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Store } from 'redux' 2 | import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants' 3 | import { updateVersion } from '../global/actions' 4 | import reducer, { initialState, UserState } from './reducer' 5 | 6 | describe('swap reducer', () => { 7 | let store: Store 8 | 9 | beforeEach(() => { 10 | store = createStore(reducer, initialState) 11 | }) 12 | 13 | describe('updateVersion', () => { 14 | it('has no timestamp originally', () => { 15 | expect(store.getState().lastUpdateVersionTimestamp).toBeUndefined() 16 | }) 17 | it('sets the lastUpdateVersionTimestamp', () => { 18 | const time = new Date().getTime() 19 | store.dispatch(updateVersion()) 20 | expect(store.getState().lastUpdateVersionTimestamp).toBeGreaterThanOrEqual(time) 21 | }) 22 | it('sets allowed slippage and deadline', () => { 23 | store = createStore(reducer, { 24 | ...initialState, 25 | userDeadline: undefined, 26 | userSlippageTolerance: undefined 27 | } as any) 28 | store.dispatch(updateVersion()) 29 | expect(store.getState().userDeadline).toEqual(DEFAULT_DEADLINE_FROM_NOW) 30 | expect(store.getState().userSlippageTolerance).toEqual(INITIAL_ALLOWED_SLIPPAGE) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/components/Row/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from 'rebass/styled-components' 3 | 4 | export 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 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request_target: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | run-linters: 13 | name: Run linters 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Check out Git repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Set up node 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: 12 24 | always-auth: true 25 | registry-url: https://registry.npmjs.org 26 | 27 | - name: Set output of cache 28 | id: yarn-cache 29 | run: echo "::set-output name=dir::$(yarn cache dir)" 30 | 31 | - name: Node dependency cache 32 | uses: actions/cache@v1 33 | with: 34 | path: ${{ steps.yarn-cache.outputs.dir }} 35 | key: yarn-${{ hashFiles('**/yarn.lock') }} 36 | restore-keys: | 37 | yarn- 38 | 39 | - name: Install dependencies 40 | run: yarn install --frozen-lockfile 41 | 42 | - name: Run linters 43 | uses: wearerequired/lint-action@77d70b9a07ecb93bc98dc46dc27d96c4f004d035 44 | with: 45 | github_token: ${{ secrets.github_token }} 46 | eslint: true 47 | eslint_extensions: js,jsx,ts,tsx,json 48 | auto_fix: true 49 | -------------------------------------------------------------------------------- /src/assets/svg/transaction_submitted.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/PairChart/utils/data.ts: -------------------------------------------------------------------------------- 1 | interface BasicData { 2 | token0?: { 3 | id: string 4 | name: string 5 | symbol: string 6 | } 7 | token1?: { 8 | id: string 9 | name: string 10 | symbol: string 11 | } 12 | } 13 | 14 | // Override data return from graph - usually because proxy token has changed 15 | // names since entitiy was created in subgraph 16 | // keys are lowercase token addresses <-------- 17 | const TOKEN_OVERRIDES: { [address: string]: { name: string; symbol: string } } = { 18 | '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2': { 19 | name: 'Ether (Wrapped)', 20 | symbol: 'ETH', 21 | }, 22 | '0x1416946162b1c2c871a73b07e932d2fb6c932069': { 23 | name: 'Energi', 24 | symbol: 'NRGE', 25 | }, 26 | } 27 | 28 | // override tokens with incorrect symbol or names 29 | export function updateNameData(data: BasicData): BasicData | undefined { 30 | if (data?.token0?.id && Object.keys(TOKEN_OVERRIDES).includes(data.token0.id)) { 31 | data.token0.name = TOKEN_OVERRIDES[data.token0.id].name 32 | data.token0.symbol = TOKEN_OVERRIDES[data.token0.id].symbol 33 | } 34 | 35 | if (data?.token1?.id && Object.keys(TOKEN_OVERRIDES).includes(data.token1.id)) { 36 | data.token1.name = TOKEN_OVERRIDES[data.token1.id].name 37 | data.token1.symbol = TOKEN_OVERRIDES[data.token1.id].symbol 38 | } 39 | 40 | return data 41 | } 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/pages/AddLiquidity/PoolPriceBar.tsx: -------------------------------------------------------------------------------- 1 | import { Currency, Percent, Price } from '@uniswap/sdk' 2 | import React from 'react' 3 | import DataCard from 'components/Card/DataCard' 4 | import { ONE_BIPS } from '../../constants' 5 | import { Field } from '../../state/mint/actions' 6 | 7 | export function PoolPriceBar({ 8 | cardTitle, 9 | currencies, 10 | noLiquidity, 11 | poolTokenPercentage, 12 | price 13 | }: { 14 | cardTitle?: string 15 | currencies: { [field in Field]?: Currency } 16 | noLiquidity?: boolean 17 | poolTokenPercentage?: Percent 18 | price?: Price 19 | }) { 20 | return ( 21 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/Pool/styleds.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from 'rebass' 2 | import styled from 'styled-components' 3 | 4 | export const Wrapper = styled.div` 5 | position: relative; 6 | padding: 1rem; 7 | ` 8 | 9 | export const ClickableText = styled(Text)` 10 | :hover { 11 | cursor: pointer; 12 | } 13 | color: ${({ theme }) => theme.primary1}; 14 | ` 15 | export const MaxButton = styled.button<{ width: string }>` 16 | padding: 0.3rem 0.5rem; 17 | background-color: ${({ theme }) => theme.bg4}; 18 | border: none; 19 | border-radius: 14px; 20 | font-size: 14px; 21 | ${({ theme }) => theme.mediaWidth.upToSmall` 22 | padding: 0.25rem 0.5rem; 23 | `}; 24 | font-weight: 400; 25 | cursor: pointer; 26 | margin: 0.25rem; 27 | overflow: hidden; 28 | color: ${({ theme }) => theme.text1}; 29 | :hover { 30 | border: 1px solid ${({ theme }) => theme.primary1}; 31 | } 32 | :focus { 33 | border: 1px solid ${({ theme }) => theme.primary1}; 34 | outline: none; 35 | } 36 | ` 37 | 38 | export const Dots = styled.span` 39 | &::after { 40 | display: inline-block; 41 | animation: ellipsis 1.25s infinite; 42 | content: '.'; 43 | width: 1em; 44 | text-align: left; 45 | } 46 | @keyframes ellipsis { 47 | 0% { 48 | content: '.'; 49 | } 50 | 33% { 51 | content: '..'; 52 | } 53 | 66% { 54 | content: '...'; 55 | } 56 | } 57 | ` 58 | -------------------------------------------------------------------------------- /src/assets/svg/call_type.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/walletConnect_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /src/components/DoubleLogo/DoubleLogoReverse.tsx: -------------------------------------------------------------------------------- 1 | import { Currency } from '@uniswap/sdk' 2 | import React from 'react' 3 | import styled from 'styled-components' 4 | import CurrencyLogo from '../CurrencyLogo' 5 | 6 | const Wrapper = styled.div<{ mr?: string; sizeraw: number }>` 7 | position: relative; 8 | display: flex; 9 | flex-direction: row; 10 | width: ${({ sizeraw }) => sizeraw * 1.68 + 'px'}; 11 | margin-right: ${({ mr }) => mr ?? 0}; 12 | ` 13 | 14 | interface DoubleCurrencyLogoProps { 15 | mr?: string 16 | size?: number 17 | currency0?: Currency 18 | currency1?: Currency 19 | } 20 | 21 | const HigherLogo = styled(CurrencyLogo)` 22 | z-index: 1; 23 | ` 24 | const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>` 25 | position: absolute; 26 | left: ${({ sizeraw }) => '-' + (sizeraw / 2).toString() + 'px'} !important; 27 | ` 28 | 29 | export default function DoubleCurrencyLogoReverse({ currency0, currency1, size = 16, mr }: DoubleCurrencyLogoProps) { 30 | return ( 31 | 32 | {currency0 && } 33 | {currency1 && ( 34 | 40 | )} 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/AccountDetails/Copy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import useCopyClipboard from '../../hooks/useCopyClipboard' 4 | 5 | import { LinkStyledButton } from '../../theme' 6 | import { CheckCircle, Copy } from 'react-feather' 7 | 8 | const CopyIcon = styled(LinkStyledButton)` 9 | color: ${({ theme }) => theme.text3}; 10 | flex-shrink: 0; 11 | display: flex; 12 | text-decoration: none; 13 | font-size: 0.825rem; 14 | :hover, 15 | :active, 16 | :focus { 17 | text-decoration: none; 18 | color: ${({ theme }) => theme.text1}; 19 | } 20 | ` 21 | const TransactionStatusText = styled.span` 22 | margin-left: 0.25rem; 23 | font-size: 0.825rem; 24 | ${({ theme }) => theme.flexRowNoWrap}; 25 | align-items: center; 26 | ` 27 | 28 | export default function CopyHelper(props: { toCopy: string; children?: React.ReactNode; fixedSize?: boolean }) { 29 | const [isCopied, setCopied] = useCopyClipboard() 30 | 31 | return ( 32 | setCopied(props.toCopy)}> 33 | {isCopied ? ( 34 | 35 | 36 | {!props.fixedSize && Copied} 37 | 38 | ) : ( 39 | 40 | 41 | 42 | )} 43 | {isCopied ? '' : props.children} 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |   4 |     https://antimatter.finance/ecosystem 5 |     2020-06-04 6 |   7 | 8 | 9 | 10 | https://www.google.com/ping?sitemap=https://raw.githubusercontent.com/antimatter-dao/Antimatter-BullBear/master/sitemap.xml 11 | 12 | 13 | 14 | 15 | 16 | 17 |     18 | 19 |       https://antimatter.finance/ 20 | 21 |       2021-01-01 22 | 23 |       monthly 24 | 25 |       0.8 26 | 27 |     28 | 29 |     30 | 31 |       https://antimatter.finance/ecosystem 32 | 33 |       weekly 34 | 35 |     36 | 37 |     38 | 39 |       https://explorer-test.antimatter.finance/ 40 | 41 |       2021-12-23 42 | 43 |       weekly 44 | 45 |     46 | 47 |     48 | 49 |       http://bas-node.antimatter.finance:5000/ 50 | 51 |       20202104-12-23T18:00:15+00:00 52 | 53 |       0.3 54 | 55 |     56 | 57 |     58 | 59 |       https://docs.antimatter.finance/introduction/antimatter-overview 60 | 61 |       2004-11-23 62 | 63 |     64 | 65 | 66 | -------------------------------------------------------------------------------- /src/state/mint/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { Field, resetMintState, typeInput } from './actions' 3 | 4 | export interface MintState { 5 | readonly independentField: Field 6 | readonly typedValue: string 7 | readonly otherTypedValue: string // for the case when there's no liquidity 8 | } 9 | 10 | const initialState: MintState = { 11 | independentField: Field.CURRENCY_A, 12 | typedValue: '', 13 | otherTypedValue: '' 14 | } 15 | 16 | export default createReducer(initialState, builder => 17 | builder 18 | .addCase(resetMintState, () => initialState) 19 | .addCase(typeInput, (state, { payload: { field, typedValue, noLiquidity } }) => { 20 | if (noLiquidity) { 21 | // they're typing into the field they've last typed in 22 | if (field === state.independentField) { 23 | return { 24 | ...state, 25 | independentField: field, 26 | typedValue 27 | } 28 | } 29 | // they're typing into a new field, store the other value 30 | else { 31 | return { 32 | ...state, 33 | independentField: field, 34 | typedValue, 35 | otherTypedValue: state.typedValue 36 | } 37 | } 38 | } else { 39 | return { 40 | ...state, 41 | independentField: field, 42 | typedValue, 43 | otherTypedValue: '' 44 | } 45 | } 46 | }) 47 | ) 48 | -------------------------------------------------------------------------------- /src/pages/Swap/redirects.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { Redirect, RouteComponentProps } from 'react-router-dom' 4 | import { AppDispatch } from '../../state' 5 | import { ApplicationModal, setOpenModal } from '../../state/application/actions' 6 | 7 | // Redirects to swap but only replace the pathname 8 | export function RedirectPathToSwapOnly({ location }: RouteComponentProps) { 9 | return 10 | } 11 | 12 | // Redirects from the /swap/:outputCurrency path to the /swap?outputCurrency=:outputCurrency format 13 | export function RedirectToSwap(props: RouteComponentProps<{ outputCurrency: string }>) { 14 | const { 15 | location: { search }, 16 | match: { 17 | params: { outputCurrency } 18 | } 19 | } = props 20 | 21 | return ( 22 | 1 28 | ? `${search}&outputCurrency=${outputCurrency}` 29 | : `?outputCurrency=${outputCurrency}` 30 | }} 31 | /> 32 | ) 33 | } 34 | 35 | export function OpenClaimAddressModalAndRedirectToSwap(props: RouteComponentProps) { 36 | const dispatch = useDispatch() 37 | useEffect(() => { 38 | dispatch(setOpenModal(ApplicationModal.ADDRESS_CLAIM)) 39 | }, [dispatch]) 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Pagination/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MuiPagination from '@material-ui/lab/Pagination' 3 | import styled from 'styled-components' 4 | import { ThemeProvider as MaterialThemeProvider } from '@material-ui/core/styles' 5 | import { createTheme } from '@material-ui/core/styles' 6 | 7 | const materialTheme = createTheme({ 8 | palette: { 9 | type: 'dark' 10 | } 11 | }) 12 | 13 | const materialThemeLight = createTheme({ 14 | palette: { 15 | type: 'light' 16 | } 17 | }) 18 | 19 | const StyledPagination = styled.div` 20 | display: flex; 21 | justify-content: center; 22 | & > * { 23 | margin-bottom: 20px; 24 | } 25 | ` 26 | 27 | interface PaginationProps { 28 | count: number 29 | page: number 30 | setPage?: (page: number) => void 31 | onChange?: (event: object, page: number) => void 32 | isLightBg?: boolean 33 | } 34 | export default function Pagination({ count, page, onChange, setPage, isLightBg }: PaginationProps) { 35 | return ( 36 | 37 | {count && ( 38 | 39 | { 43 | onChange && onChange(event, page) 44 | setPage && setPage(page) 45 | }} 46 | size="large" 47 | /> 48 | 49 | )} 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/marketStrategyUtils.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, Currency, ETHER, JSBI, Token, TokenAmount } from '@uniswap/sdk' 2 | import { tryParseAmount } from 'state/swap/hooks' 3 | import { absolute } from 'state/market/hooks' 4 | import { Symbol } from 'constants/index' 5 | 6 | export const isNegative = (val?: string): boolean => val?.toString()[0] === '-' 7 | 8 | export const parseBalance = ({ 9 | val, 10 | token, 11 | toSignificant = 6 12 | }: { 13 | val?: string 14 | token: Token 15 | toSignificant?: number 16 | }) => { 17 | const string = val?.toString() 18 | const amount = new TokenAmount(token, JSBI.BigInt(absolute(string ?? ''))).toSignificant(toSignificant) 19 | if (string && string[0] === '-') { 20 | return '-' + amount 21 | } else { 22 | return amount 23 | } 24 | } 25 | export const parsedGreaterThan = (userInput: string, balance: string) => { 26 | if (userInput && balance) { 27 | const v1 = tryParseAmount(userInput, ETHER)?.raw 28 | const v2 = JSBI.BigInt(balance.toString()) 29 | return v1 && v2 ? JSBI.greaterThan(v1, v2) : undefined 30 | } 31 | return 32 | } 33 | export const currencyNameHelper = (currency?: Currency | null, defaultString?: string, chainId: ChainId = 1) => { 34 | const symbol = currency === ETHER ? Symbol[chainId] : currency?.symbol 35 | return ( 36 | (currency && symbol && symbol.length > 20 37 | ? symbol.slice(0, 4) + '...' + symbol.slice(symbol.length - 5, symbol.length) 38 | : symbol) || 39 | defaultString || 40 | '' 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Popups/TransactionPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { AlertCircle } from 'react-feather' 3 | import styled, { ThemeContext } from 'styled-components' 4 | import { ReactComponent as CheckCircle } from '../../assets/svg/check_circle.svg' 5 | import { useActiveWeb3React } from '../../hooks' 6 | import { TYPE } from '../../theme' 7 | import { ExternalLink } from '../../theme/components' 8 | import { getEtherscanLink } from '../../utils' 9 | import { AutoColumn } from '../Column' 10 | import { AutoRow } from '../Row' 11 | 12 | const RowNoFlex = styled(AutoRow)` 13 | flex-wrap: nowrap; 14 | ` 15 | 16 | export default function TransactionPopup({ 17 | hash, 18 | success, 19 | summary 20 | }: { 21 | hash: string 22 | success?: boolean 23 | summary?: string 24 | }) { 25 | const { chainId } = useActiveWeb3React() 26 | 27 | const theme = useContext(ThemeContext) 28 | 29 | return ( 30 | 31 | 32 |
33 | {success ? : } 34 |
35 | {summary ?? 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}{' '} 36 |
37 | {chainId && ( 38 | 39 | View on explorer 40 | 41 | )} 42 |
43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/SwitchTab/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | export const SwitchTabWrapper = styled.div` 5 | border-bottom: 1px solid ${({ theme }) => theme.text5}; 6 | white-space: nowrap; 7 | ${({ theme }) => theme.mediaWidth.upToSmall` 8 | overflow-x: auto; 9 | overflow-y: hidden; 10 | `}; 11 | ` 12 | 13 | export const Tab = styled.button<{ selected: boolean }>` 14 | border: none; 15 | background: none; 16 | padding: 14px 0; 17 | margin-right: 40px; 18 | font-size: 16px; 19 | font-weight: 500; 20 | color: ${({ selected, theme }) => (selected ? theme.primary1 : theme.white)}; 21 | border-bottom: 2px solid ${({ selected, theme }) => (selected ? theme.primary4 : 'transparent')}; 22 | margin-bottom: -1px; 23 | transition: 0.3s; 24 | cursor: pointer; 25 | &:hover { 26 | color: ${({ theme }) => theme.primary1}; 27 | } 28 | ` 29 | 30 | export default function SwitchTab({ 31 | currentTab, 32 | onTabClick, 33 | tabs, 34 | tabStyle 35 | }: { 36 | currentTab: string 37 | onTabClick: (tab: string) => () => void 38 | tabs: { [key: string]: string } 39 | tabStyle?: object 40 | }) { 41 | return ( 42 | 43 | {Object.keys(tabs).map(tab => { 44 | const tabName = tabs[tab as keyof typeof tabs] 45 | return ( 46 | 47 | {tabName} 48 | 49 | ) 50 | })} 51 | 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /src/assets/svg/circle_unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/Info/PriceItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AutoColumn } from '../../components/Column' 3 | import { TYPE } from '../../theme' 4 | import { Divider, NumberWithUnit } from './index' 5 | import { useCurrency } from '../../hooks/Tokens' 6 | import { TranslucentCard } from '../../components/Card' 7 | import { ReactComponent as CallToken } from '../../assets/svg/call_token.svg' 8 | import { ReactComponent as PutToken } from '../../assets/svg/put_token.svg' 9 | import { useUSDTPrice } from '../../utils/useUSDCPrice' 10 | 11 | export default function PriceItem({ address, total, isCall }: { address: string; total: string; isCall?: boolean }) { 12 | const currency = useCurrency(address) 13 | const price = useUSDTPrice(currency ?? undefined) 14 | return ( 15 | 16 | 17 |
18 | 19 | TOTAL NUMBER OF {currency?.symbol} 20 | 21 | {isCall ? : } 22 | {total} 23 | 24 | 25 |
26 | 27 |
28 | 29 | PRICE OF {currency?.symbol} 30 | 31 | 32 |
33 |
34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /test/unit/functions/approveAmountCallData.test.ts: -------------------------------------------------------------------------------- 1 | import { AddressZero } from '@ethersproject/constants' 2 | import { ChainId, Currency, CurrencyAmount, Token } from '@sushiswap/core-sdk' 3 | import approveAmountCalldata, { toHex } from 'app/functions/approveAmountCalldata' 4 | 5 | describe('approveAmountCalldata functions', () => { 6 | describe('toHex', () => { 7 | it('handles null address', () => { 8 | const input = '0x0000000000000000000000000000000000000000' 9 | expect(toHex(input)).toEqual('0x00') 10 | }) 11 | }) 12 | describe('approveAmountCalldata', () => { 13 | it('must be called with token', () => { 14 | const tokenAmount = CurrencyAmount.fromRawAmount({ isToken: false, decimals: 0 } as Currency, '100') 15 | const spender = AddressZero 16 | 17 | expect(() => approveAmountCalldata(tokenAmount, spender)).toThrowError('Must call with an amount of token') 18 | }) 19 | 20 | it('returns data', () => { 21 | const tokenAmount = CurrencyAmount.fromRawAmount(new Token(ChainId.ETHEREUM, AddressZero, 0), '100') 22 | const spender = AddressZero 23 | 24 | const res = approveAmountCalldata(tokenAmount, spender) 25 | expect(res).toEqual({ 26 | to: '0x0000000000000000000000000000000000000000', 27 | data: '0x095ea7b300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064', 28 | value: '0x0', 29 | }) 30 | }) 31 | }) 32 | }) -------------------------------------------------------------------------------- /src/utils/computeUniCirculation.test.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, JSBI, Token, TokenAmount } from '@uniswap/sdk' 2 | import { BigNumber } from 'ethers' 3 | import { ZERO_ADDRESS } from '../constants' 4 | import { computeUniCirculation } from './computeUniCirculation' 5 | 6 | describe('computeUniCirculation', () => { 7 | const token = new Token(ChainId.RINKEBY, ZERO_ADDRESS, 18) 8 | 9 | function expandTo18Decimals(num: JSBI | string | number) { 10 | return JSBI.multiply(JSBI.BigInt(num), JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))) 11 | } 12 | 13 | function tokenAmount(num: JSBI | string | number) { 14 | return new TokenAmount(token, expandTo18Decimals(num)) 15 | } 16 | 17 | it('before staking', () => { 18 | expect(computeUniCirculation(token, BigNumber.from(0), undefined)).toEqual(tokenAmount(150_000_000)) 19 | expect(computeUniCirculation(token, BigNumber.from(1600387200), undefined)).toEqual(tokenAmount(150_000_000)) 20 | }) 21 | it('mid staking', () => { 22 | expect(computeUniCirculation(token, BigNumber.from(1600387200 + 15 * 24 * 60 * 60), undefined)).toEqual( 23 | tokenAmount(155_000_000) 24 | ) 25 | }) 26 | it('after staking and treasury vesting cliff', () => { 27 | expect(computeUniCirculation(token, BigNumber.from(1600387200 + 60 * 24 * 60 * 60), undefined)).toEqual( 28 | tokenAmount(224_575_341) 29 | ) 30 | }) 31 | it('subtracts unclaimed uni', () => { 32 | expect(computeUniCirculation(token, BigNumber.from(1600387200 + 15 * 24 * 60 * 60), tokenAmount(1000))).toEqual( 33 | tokenAmount(154_999_000) 34 | ) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /src/components/Spinner/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { CircularProgress, makeStyles, createStyles } from '@material-ui/core' 3 | 4 | interface Props { 5 | size?: string | number 6 | thickness?: number 7 | color?: string 8 | marginLeft?: string | number 9 | marginRight?: string | number 10 | } 11 | 12 | const useStyles = makeStyles(() => 13 | createStyles({ 14 | root: { 15 | position: 'relative', 16 | marginLeft: (props: Props) => props.marginLeft ?? 0, 17 | marginRight: (props: Props) => props.marginRight ?? 0, 18 | height: (props: Props) => props.size, 19 | width: (props: Props) => props.size 20 | }, 21 | bottom: { 22 | '& svg circle': { 23 | stroke: '#A1A1A1' 24 | } 25 | }, 26 | top: { 27 | color: (props: Props) => props.color ?? '#B2F355', 28 | animationDuration: '850ms', 29 | position: 'absolute', 30 | left: 0, 31 | top: 0 32 | } 33 | }) 34 | ) 35 | 36 | export default function Spinner({ size = 16, thickness = 3, ...props }: Props) { 37 | const classes = useStyles({ ...props, size }) 38 | 39 | return ( 40 |
41 | 48 | 55 |
56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /src/hooks/useCreationCallback.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, Currency, WETH } from '@uniswap/sdk' 2 | import { useActiveWeb3React } from 'hooks' 3 | import { useMemo } from 'react' 4 | import { tryParseAmount } from 'state/swap/hooks' 5 | import { currencyId } from 'utils/currencyId' 6 | import { useAntimatterContract } from './useContract' 7 | 8 | const parseNumber = (val: string, currency: Currency) => { 9 | return tryParseAmount(val, currency)?.raw.toString() ?? '0' 10 | } 11 | const getAddress = (currency: Currency, chainId: ChainId) => { 12 | const id = currencyId(currency) 13 | if (id === 'ETH') { 14 | return currencyId(WETH[chainId as ChainId]) 15 | } 16 | return id 17 | } 18 | 19 | export function useCreationCallback(): { 20 | callback: null | ((...args: any[]) => Promise) 21 | } { 22 | const contract = useAntimatterContract() 23 | const { chainId } = useActiveWeb3React() 24 | return useMemo(() => { 25 | if (!contract) return { callback: null } 26 | return { 27 | callback: async function( 28 | underlying: Currency, 29 | currency: Currency, 30 | priceFloor: string, 31 | priceCap: string 32 | ): Promise<{} | null> { 33 | if (!underlying || !currency || !priceCap || !priceFloor || !chainId) { 34 | return null 35 | } 36 | return contract.createOption( 37 | getAddress(underlying, chainId), 38 | getAddress(currency, chainId), 39 | parseNumber(priceFloor, currency), 40 | parseNumber(priceCap, currency) 41 | ) 42 | } 43 | } 44 | }, [chainId, contract]) 45 | } 46 | -------------------------------------------------------------------------------- /src/utils/contenthashToUri.ts: -------------------------------------------------------------------------------- 1 | import CID from 'cids' 2 | import { getCodec, rmPrefix } from 'multicodec' 3 | import { decode, toB58String } from 'multihashes' 4 | 5 | export function hexToUint8Array(hex: string): Uint8Array { 6 | hex = hex.startsWith('0x') ? hex.substr(2) : hex 7 | if (hex.length % 2 !== 0) throw new Error('hex must have length that is multiple of 2') 8 | const arr = new Uint8Array(hex.length / 2) 9 | for (let i = 0; i < arr.length; i++) { 10 | arr[i] = parseInt(hex.substr(i * 2, 2), 16) 11 | } 12 | return arr 13 | } 14 | 15 | const UTF_8_DECODER = new TextDecoder() 16 | 17 | /** 18 | * Returns the URI representation of the content hash for supported codecs 19 | * @param contenthash to decode 20 | */ 21 | export default function contenthashToUri(contenthash: string): string { 22 | const buff = hexToUint8Array(contenthash) 23 | const codec = getCodec(buff as Buffer) // the typing is wrong for @types/multicodec 24 | switch (codec) { 25 | case 'ipfs-ns': { 26 | const data = rmPrefix(buff as Buffer) 27 | const cid = new CID(data) 28 | return `ipfs://${toB58String(cid.multihash)}` 29 | } 30 | case 'ipns-ns': { 31 | const data = rmPrefix(buff as Buffer) 32 | const cid = new CID(data) 33 | const multihash = decode(cid.multihash) 34 | if (multihash.name === 'identity') { 35 | return `ipns://${UTF_8_DECODER.decode(multihash.digest).trim()}` 36 | } else { 37 | return `ipns://${toB58String(cid.multihash)}` 38 | } 39 | } 40 | default: 41 | throw new Error(`Unrecognized codec: ${codec}`) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/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 | 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/hooks/useAddTokenToMetamask.ts: -------------------------------------------------------------------------------- 1 | import { getTokenLogoURL } from './../components/CurrencyLogo/index' 2 | import { wrappedCurrency } from 'utils/wrappedCurrency' 3 | import { Currency, Token } from '@uniswap/sdk' 4 | import { useCallback, useState } from 'react' 5 | import { useActiveWeb3React } from 'hooks' 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 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 23 | //@ts-ignore // need this for incorrect ethers provider type 24 | type: 'ERC20', 25 | options: { 26 | address: token.address, 27 | symbol: token.symbol, 28 | decimals: token.decimals, 29 | image: getTokenLogoURL(token.address) 30 | } 31 | } 32 | }) 33 | .then(success => { 34 | setSuccess(success) 35 | }) 36 | .catch(() => setSuccess(false)) 37 | } else { 38 | setSuccess(false) 39 | } 40 | }, [library, token]) 41 | 42 | return { addToken, success } 43 | } 44 | -------------------------------------------------------------------------------- /src/assets/svg/transaction_error.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/put_token.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/state/transactions/updater.test.ts: -------------------------------------------------------------------------------- 1 | import { shouldCheck } from './updater' 2 | 3 | describe('transactions updater', () => { 4 | describe('shouldCheck', () => { 5 | it('returns true if no receipt and never checked', () => { 6 | expect(shouldCheck(10, { addedTime: 100 })).toEqual(true) 7 | }) 8 | it('returns false if has receipt and never checked', () => { 9 | expect(shouldCheck(10, { addedTime: 100, receipt: {} })).toEqual(false) 10 | }) 11 | it('returns true if has not been checked in 1 blocks', () => { 12 | expect(shouldCheck(10, { addedTime: new Date().getTime(), lastCheckedBlockNumber: 9 })).toEqual(true) 13 | }) 14 | it('returns false if checked in last 3 blocks and greater than 20 minutes old', () => { 15 | expect(shouldCheck(10, { addedTime: new Date().getTime() - 21 * 60 * 1000, lastCheckedBlockNumber: 8 })).toEqual( 16 | false 17 | ) 18 | }) 19 | it('returns true if not checked in last 5 blocks and greater than 20 minutes old', () => { 20 | expect(shouldCheck(10, { addedTime: new Date().getTime() - 21 * 60 * 1000, lastCheckedBlockNumber: 5 })).toEqual( 21 | true 22 | ) 23 | }) 24 | it('returns false if checked in last 10 blocks and greater than 60 minutes old', () => { 25 | expect(shouldCheck(20, { addedTime: new Date().getTime() - 61 * 60 * 1000, lastCheckedBlockNumber: 11 })).toEqual( 26 | false 27 | ) 28 | }) 29 | it('returns true if checked in last 3 blocks and greater than 20 minutes old', () => { 30 | expect(shouldCheck(20, { addedTime: new Date().getTime() - 61 * 60 * 1000, lastCheckedBlockNumber: 10 })).toEqual( 31 | true 32 | ) 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/assets/svg/helper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/Toggle/ListToggle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { ButtonOutlined } from 'components/Button' 4 | 5 | const Wrapper = styled(ButtonOutlined)<{ isActive?: boolean; activeElement?: boolean }>` 6 | background: transparent 7 | border-color:${({ theme, isActive }) => isActive && theme.primary1}; 8 | display: flex; 9 | width: 80px; 10 | cursor: pointer; 11 | padding: 0.4rem 0.5rem; 12 | align-items: center; 13 | justify-content: space-between; 14 | font-size: 14px; 15 | :hover{ 16 | box-shadow: unset 17 | border-color: ${({ theme }) => theme.primary1}; 18 | opacity: .8 19 | } 20 | ` 21 | 22 | const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string }>` 23 | border-radius: 50%; 24 | height: 15px; 25 | width: 15px; 26 | background-color: ${({ isActive, bgColor, theme }) => (isActive ? theme.primary1 : theme.bg5)}; 27 | :hover { 28 | opacity: 0.8; 29 | } 30 | ` 31 | 32 | const StatusText = styled.div<{ isActive?: boolean }>` 33 | margin: 0 10px; 34 | width: 24px; 35 | font-weight: 500; 36 | color: ${({ theme, isActive }) => (isActive ? theme.primary1 : theme.text3)}; 37 | ` 38 | 39 | export interface ToggleProps { 40 | id?: string 41 | isActive: boolean 42 | bgColor: string 43 | toggle: () => void 44 | } 45 | 46 | export default function ListToggle({ id, isActive, bgColor, toggle }: ToggleProps) { 47 | return ( 48 | 49 | {isActive && ON} 50 | 51 | {!isActive && OFF} 52 | 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/assets/svg/search.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/connectors/Fortmatic.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '@uniswap/sdk' 2 | import { FortmaticConnector as FortmaticConnectorCore } from '@web3-react/fortmatic-connector' 3 | 4 | export const OVERLAY_READY = 'OVERLAY_READY' 5 | 6 | type FormaticSupportedChains = ChainId | ChainId.MAINNET | ChainId.BSC | ChainId.Arbitrum | ChainId.Avalanche 7 | 8 | const CHAIN_ID_NETWORK_ARGUMENT: { readonly [chainId in FormaticSupportedChains]: string | undefined } = { 9 | [ChainId.MAINNET]: '', 10 | [ChainId.ROPSTEN]: 'ropsten', 11 | [ChainId.BSC]: '', 12 | [ChainId.Arbitrum]: '', 13 | [ChainId.Avalanche]: '' 14 | } 15 | 16 | export class FortmaticConnector extends FortmaticConnectorCore { 17 | async activate() { 18 | if (!this.fortmatic) { 19 | const { default: Fortmatic } = await import('fortmatic') 20 | 21 | const { apiKey, chainId } = this as any 22 | if (chainId in CHAIN_ID_NETWORK_ARGUMENT) { 23 | this.fortmatic = new Fortmatic(apiKey, CHAIN_ID_NETWORK_ARGUMENT[chainId as FormaticSupportedChains]) 24 | } else { 25 | throw new Error(`Unsupported network ID: ${chainId}`) 26 | } 27 | } 28 | 29 | const provider = this.fortmatic.getProvider() 30 | 31 | const pollForOverlayReady = new Promise(resolve => { 32 | const interval = setInterval(() => { 33 | if (provider.overlayReady) { 34 | clearInterval(interval) 35 | this.emit(OVERLAY_READY) 36 | resolve() 37 | } 38 | }, 200) 39 | }) 40 | 41 | const [account] = await Promise.all([ 42 | provider.enable().then((accounts: string[]) => accounts[0]), 43 | pollForOverlayReady 44 | ]) 45 | 46 | return { provider: this.fortmatic.getProvider(), chainId: (this as any).chainId, account } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/state/application/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer, nanoid } from '@reduxjs/toolkit' 2 | import { addPopup, PopupContent, removePopup, updateBlockNumber, ApplicationModal, setOpenModal } from './actions' 3 | 4 | type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }> 5 | 6 | export interface ApplicationState { 7 | readonly blockNumber: { readonly [chainId: number]: number } 8 | readonly popupList: PopupList 9 | readonly openModal: ApplicationModal | null 10 | } 11 | 12 | const initialState: ApplicationState = { 13 | blockNumber: {}, 14 | popupList: [], 15 | openModal: null 16 | } 17 | 18 | export default createReducer(initialState, builder => 19 | builder 20 | .addCase(updateBlockNumber, (state, action) => { 21 | const { chainId, blockNumber } = action.payload 22 | if (typeof state.blockNumber[chainId] !== 'number') { 23 | state.blockNumber[chainId] = blockNumber 24 | } else { 25 | state.blockNumber[chainId] = Math.max(blockNumber, state.blockNumber[chainId]) 26 | } 27 | }) 28 | .addCase(setOpenModal, (state, action) => { 29 | state.openModal = action.payload 30 | }) 31 | .addCase(addPopup, (state, { payload: { content, key, removeAfterMs = 15000 } }) => { 32 | state.popupList = (key ? state.popupList.filter(popup => popup.key !== key) : state.popupList).concat([ 33 | { 34 | key: key || nanoid(), 35 | show: true, 36 | content, 37 | removeAfterMs 38 | } 39 | ]) 40 | }) 41 | .addCase(removePopup, (state, { payload: { key } }) => { 42 | state.popupList.forEach(p => { 43 | if (p.key === key) { 44 | p.show = false 45 | } 46 | }) 47 | }) 48 | ) 49 | -------------------------------------------------------------------------------- /src/components/Toggle/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>` 5 | width: 50%; 6 | height: 3rem; 7 | line-height: 48px; 8 | border-radius: 14px; 9 | background: ${({ theme, isActive }) => (isActive ? theme.primary1 : 'none')}; 10 | color: ${({ theme, isActive }) => (isActive ? theme.black : theme.white)}; 11 | font-size: 1rem; 12 | font-weight: 400; 13 | 14 | padding: 0 0.6rem; 15 | font-weight: ${({ isOnSwitch }) => (isOnSwitch ? '500' : '400')}; 16 | :hover { 17 | user-select: ${({ isOnSwitch }) => (isOnSwitch ? 'none' : 'initial')}; 18 | background: ${({ theme, isActive }) => (isActive ? theme.primary4 : 'none')}; 19 | color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text3)}; 20 | } 21 | ` 22 | 23 | const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean }>` 24 | border-radius: 14px; 25 | border: 1px solid ${({ theme }) => theme.text1} 26 | background: ${({ theme }) => theme.bg1}; 27 | display: flex; 28 | width: 100%; 29 | max-width: 178px 30 | cursor: pointer; 31 | outline: none; 32 | padding: 0; 33 | ` 34 | 35 | export interface ToggleProps { 36 | id?: string 37 | isActive: boolean 38 | toggle: () => void 39 | } 40 | 41 | export default function Toggle({ id, isActive, toggle }: ToggleProps) { 42 | return ( 43 | 44 | 45 | On 46 | 47 | 48 | Off 49 | 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/prices.test.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, JSBI, Pair, Route, Token, TokenAmount, Trade, TradeType } from '@uniswap/sdk' 2 | import { computeTradePriceBreakdown } from './prices' 3 | 4 | describe('prices', () => { 5 | const token1 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000001', 18) 6 | const token2 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000002', 18) 7 | const token3 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000003', 18) 8 | 9 | const pair12 = new Pair(new TokenAmount(token1, JSBI.BigInt(10000)), new TokenAmount(token2, JSBI.BigInt(20000))) 10 | const pair23 = new Pair(new TokenAmount(token2, JSBI.BigInt(20000)), new TokenAmount(token3, JSBI.BigInt(30000))) 11 | 12 | describe('computeTradePriceBreakdown', () => { 13 | it('returns undefined for undefined', () => { 14 | expect(computeTradePriceBreakdown(undefined)).toEqual({ 15 | priceImpactWithoutFee: undefined, 16 | realizedLPFee: undefined 17 | }) 18 | }) 19 | 20 | it('correct realized lp fee for single hop', () => { 21 | expect( 22 | computeTradePriceBreakdown( 23 | new Trade(new Route([pair12], token1), new TokenAmount(token1, JSBI.BigInt(1000)), TradeType.EXACT_INPUT) 24 | ).realizedLPFee 25 | ).toEqual(new TokenAmount(token1, JSBI.BigInt(3))) 26 | }) 27 | 28 | it('correct realized lp fee for double hop', () => { 29 | expect( 30 | computeTradePriceBreakdown( 31 | new Trade( 32 | new Route([pair12, pair23], token1), 33 | new TokenAmount(token1, JSBI.BigInt(1000)), 34 | TradeType.EXACT_INPUT 35 | ) 36 | ).realizedLPFee 37 | ).toEqual(new TokenAmount(token1, JSBI.BigInt(5))) 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /src/pages/Redeem/ConfirmRedeemModalBottom.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text } from 'rebass' 3 | import { ButtonPrimary } from '../../components/Button' 4 | import { DeltaData } from '../../state/market/hooks' 5 | import { Currency, Token } from '@uniswap/sdk' 6 | import { GenerateBar } from '../../components/MarketStrategy/GenerateBar' 7 | import { parseBalance } from '../../utils/marketStrategyUtils' 8 | import { ZERO_ADDRESS } from '../../constants' 9 | 10 | export function ConfirmRedeemModalBottom({ 11 | delta, 12 | callTyped, 13 | putTyped, 14 | currencyA, 15 | currencyB, 16 | onRedeem 17 | }: { 18 | delta?: DeltaData | undefined 19 | callTyped?: string 20 | putTyped?: string 21 | currencyA?: Currency | undefined | null 22 | currencyB?: Currency | undefined | null 23 | onRedeem: () => void 24 | }) { 25 | return ( 26 | <> 27 | 35 | 45 | 46 | 47 | Confirm Redemption 48 | 49 | 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /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.blue1}; 12 | color: white; 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.antimatter.finance 34 |
35 | 36 |
37 | ) : window.location.hostname === 'app.antimatter.finance' ? ( 38 | 39 |
40 | Always make sure the URL is 41 | app.antimatter.finance - 42 | bookmark it to be safe. 43 |
44 | 45 |
46 | ) : null 47 | } 48 | -------------------------------------------------------------------------------- /src/hooks/useOptionList.ts: -------------------------------------------------------------------------------- 1 | import { SearchQuery } from 'components/Search' 2 | import { useEffect, useState } from 'react' 3 | import { Axios } from '../utils/option/axios' 4 | import { useActiveWeb3React } from './index' 5 | import { useBlockNumber } from 'state/application/hooks' 6 | import { useNetwork } from './useNetwork' 7 | 8 | export function useOptionList( 9 | searchParams: SearchQuery 10 | ): { 11 | page: { 12 | totalPages: number 13 | currentPage: number 14 | setCurrentPage: (page: number) => void 15 | } 16 | data: string[] 17 | firstLoading: boolean 18 | } { 19 | const [totalPages, setTotalPages] = useState(0) 20 | const [currentPage, setCurrentPage] = useState(1) 21 | const { chainId } = useActiveWeb3React() 22 | const [ids, setIds] = useState([]) 23 | const blockNumber = useBlockNumber() 24 | const [firstLoading, setFirstLoading] = useState(true) 25 | 26 | const { 27 | httpHandlingFunctions: { errorFunction } 28 | } = useNetwork() 29 | 30 | useEffect(() => { 31 | ;(async () => { 32 | if (!chainId) { 33 | return 34 | } 35 | const r: any = await Axios.post( 36 | 'getCreateOptionList', 37 | {}, 38 | { chainId, pageNum: currentPage, ...searchParams } 39 | ).catch(e => { 40 | console.error(e) 41 | errorFunction() 42 | // throw new Error(e) 43 | }) 44 | setFirstLoading(false) 45 | setTotalPages(r?.data?.data?.pages) 46 | setIds(r?.data?.data?.list?.map(({ optionIndex }: { optionIndex: string }) => optionIndex)) 47 | })() 48 | }, [chainId, currentPage, searchParams, blockNumber, errorFunction]) 49 | 50 | return { 51 | data: ids, 52 | firstLoading, 53 | page: { 54 | totalPages, 55 | currentPage, 56 | setCurrentPage 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/pages/Generate/ConfirmAddModalBottom.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text } from 'rebass' 3 | import { ButtonPrimary } from '../../components/Button' 4 | import { DeltaData } from '../../state/market/hooks' 5 | import { Currency, Token } from '@uniswap/sdk' 6 | import { GenerateBar } from '../../components/MarketStrategy/GenerateBar' 7 | import { parseBalance } from '../../utils/marketStrategyUtils' 8 | 9 | export function ConfirmGenerationModalBottom({ 10 | tokenType = '', 11 | delta, 12 | callTyped, 13 | putTyped, 14 | currencyA, 15 | currencyB, 16 | onGenerate, 17 | underlyingToken, 18 | currencyToken 19 | }: { 20 | tokenType?: string 21 | delta?: DeltaData | undefined 22 | callTyped?: string 23 | putTyped?: string 24 | currencyA?: Currency | undefined | null 25 | currencyB?: Currency | undefined | null 26 | onGenerate: () => void 27 | underlyingToken: Token 28 | currencyToken: Token 29 | }) { 30 | return ( 31 | <> 32 | 42 | 50 | 51 | 52 | Confirm Generation 53 | 54 | 55 | 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /src/hooks/useCalculatorCallback.ts: -------------------------------------------------------------------------------- 1 | // import JSBI from 'jsbi' 2 | import { ETHER } from '@uniswap/sdk' 3 | import { useMemo } from 'react' 4 | import { tryParseAmount } from 'state/swap/hooks' 5 | import { useAntimatterContract } from './useContract' 6 | 7 | const parseNumber = (val: string) => { 8 | return tryParseAmount(val, ETHER)?.raw.toString() ?? '0' 9 | } 10 | 11 | export function useCalculatorCallback(): { 12 | callback: null | ((...args: any[]) => Promise) 13 | } { 14 | const contract = useAntimatterContract() 15 | return useMemo(() => { 16 | if (!contract) return { callback: null } 17 | return { 18 | callback: async function onSwap( 19 | price: string, 20 | priceFloor: string, 21 | priceCap: string, 22 | totalCall: string, 23 | totalPut: string 24 | ): Promise<{ 25 | priceCall: string 26 | pricePut: string 27 | totalUnd: string 28 | totalCur: string 29 | totalValue: string 30 | } | null> { 31 | if (!price || !priceFloor || !priceCap || !totalCall || !totalPut) { 32 | return null 33 | } 34 | return contract 35 | .calcPrice( 36 | parseNumber(price), 37 | parseNumber(priceFloor), 38 | parseNumber(priceCap), 39 | parseNumber(totalCall), 40 | parseNumber(totalPut) 41 | ) 42 | .then((response: any) => { 43 | return { 44 | priceCall: response[0], 45 | pricePut: response[1], 46 | totalUnd: response[2], 47 | totalCur: response[3], 48 | totalValue: response[4] 49 | } 50 | }) 51 | .catch((error: any) => { 52 | throw new Error(`Calculator fail: ${error.message}`) 53 | }) 54 | } 55 | } 56 | }, [contract]) 57 | } 58 | -------------------------------------------------------------------------------- /src/pages/MatterToken/ConfirmRedeemModalBottom.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text } from 'rebass' 3 | import { ButtonPrimary } from '../../components/Button' 4 | import { DeltaData } from '../../state/market/hooks' 5 | import { Currency, Token } from '@uniswap/sdk' 6 | import { GenerateBar } from '../../components/MarketStrategy/GenerateBar' 7 | import { parseBalance } from '../../utils/marketStrategyUtils' 8 | import { ZERO_ADDRESS } from '../../constants' 9 | 10 | export function ConfirmRedeemModalBottom({ 11 | tokenType = '', 12 | delta, 13 | callTyped, 14 | putTyped, 15 | currencyA, 16 | currencyB, 17 | onRedeem 18 | }: { 19 | tokenType?: string 20 | delta?: DeltaData | undefined 21 | callTyped?: string 22 | putTyped?: string 23 | currencyA?: Currency | undefined | null 24 | currencyB?: Currency | undefined | null 25 | onRedeem: () => void 26 | }) { 27 | return ( 28 | <> 29 | 37 | 47 | 48 | 49 | Confirm Generation 50 | 51 | 52 | 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /src/pages/AppBody.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { TYPE } from '../theme' 4 | 5 | export const BodyWrapper = styled.div<{ maxWidth?: string; gradient1?: boolean; isCard?: boolean }>` 6 | max-width: ${({ maxWidth }) => maxWidth ?? '480px'}; 7 | width: 100%; 8 | background: ${({ theme, gradient1 }) => (gradient1 ? theme.gradient1 : theme.gradient2)}; 9 | border: 1px solid ${({ theme }) => theme.text5}; 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: 32px; 13 | padding: 1.5rem; 14 | ${({ theme, isCard }) => theme.mediaWidth.upToSmall` 15 | min-height:100%; 16 | padding: 16px 24px; 17 | flex-grow: 1; 18 | ${isCard ? '' : 'border-bottom-left-radius: unset;border-bottom-right-radius: unset;'} 19 | 20 | `} 21 | ` 22 | 23 | /** 24 | * The styled container element that wraps the content of most pages and the tabs. 25 | */ 26 | export default function AppBody({ 27 | children, 28 | style, 29 | maxWidth, 30 | gradient1, 31 | isCard 32 | }: { 33 | children: React.ReactNode 34 | style?: any 35 | maxWidth?: string 36 | gradient1?: boolean 37 | isCard?: boolean 38 | }) { 39 | return ( 40 | 41 | {children} 42 | 43 | ) 44 | } 45 | 46 | const StyledSwapHeader = styled.div` 47 | margin-bottom: -4px; 48 | font-size: 22px; 49 | width: 100%; 50 | max-width: 480px; 51 | font-weight: 500; 52 | color: ${({ theme }) => theme.text1}; 53 | ` 54 | 55 | export function BodyHeader({ title }: { title: string }) { 56 | return ( 57 | 58 | {title} 59 | 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/assets/svg/call_token.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/SearchModal/sorting.ts: -------------------------------------------------------------------------------- 1 | import { Token, TokenAmount } from '@uniswap/sdk' 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?: TokenAmount, balanceB?: TokenAmount) { 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]: TokenAmount | 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 | -------------------------------------------------------------------------------- /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 | bg1: Color 18 | bg2: Color 19 | bg3: Color 20 | bg4: Color 21 | bg5: Color 22 | 23 | modalBG: Color 24 | advancedBG: Color 25 | 26 | //blues 27 | primary1: Color 28 | primary2: Color 29 | primary3: Color 30 | primary4: Color 31 | primary5: Color 32 | 33 | primaryText1: Color 34 | 35 | // pinks 36 | secondary1: Color 37 | secondary2: Color 38 | secondary3: Color 39 | 40 | // other 41 | red1: Color 42 | red2: Color 43 | red3: Color 44 | green1: Color 45 | yellow1: Color 46 | yellow2: Color 47 | blue1: Color 48 | 49 | //gradient 50 | gradient1: Color 51 | gradient2: Color 52 | 53 | //opacity 54 | translucent: Color 55 | } 56 | 57 | export interface Grids { 58 | sm: number 59 | md: number 60 | lg: number 61 | } 62 | 63 | declare module 'styled-components' { 64 | export interface DefaultTheme extends Colors { 65 | grids: Grids 66 | 67 | // shadows 68 | shadow1: string 69 | 70 | // media queries 71 | mediaWidth: { 72 | upToExtraSmall: ThemedCssFunction 73 | upToSmall: ThemedCssFunction 74 | upToMedium: ThemedCssFunction 75 | upToLarge: ThemedCssFunction 76 | } 77 | 78 | // css snippets 79 | flexColumnNoWrap: FlattenSimpleInterpolation 80 | flexRowNoWrap: FlattenSimpleInterpolation 81 | 82 | //rwd 83 | mobile: FlattenSimpleInterpolation 84 | desktop: FlattenSimpleInterpolation 85 | mobileHeaderHeight: string 86 | headerHeight: string 87 | } 88 | } 89 | --------------------------------------------------------------------------------