├── .env.local.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── feature-request.md │ ├── something-else.md │ └── token-request.md └── pull_request_template.md ├── .gitignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── netlify.toml ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── locales │ ├── de.json │ ├── en.json │ ├── es-AR.json │ ├── es-US.json │ ├── ro.json │ ├── ru.json │ ├── zh-CN.json │ └── zh-TW.json └── manifest.json ├── src ├── InjectedConnector.js ├── assets │ ├── images │ │ ├── arrow-down-blue.svg │ │ ├── arrow-down-grey.svg │ │ ├── arrow-right-white.png │ │ ├── arrow-right.svg │ │ ├── blue-loader.svg │ │ ├── circle-grey.svg │ │ ├── circle.svg │ │ ├── coinbaseWalletIcon.svg │ │ ├── dropdown-blue.svg │ │ ├── dropdown.svg │ │ ├── dropup-blue.svg │ │ ├── ethereum-logo.png │ │ ├── ethereum-logo.svg │ │ ├── fortmaticIcon.png │ │ ├── link.svg │ │ ├── magnifying-glass.svg │ │ ├── menu.svg │ │ ├── metamask.png │ │ ├── plus-blue.svg │ │ ├── plus-grey.svg │ │ ├── portisIcon.png │ │ ├── question-mark.svg │ │ ├── question.svg │ │ ├── spinner.svg │ │ ├── trustWallet.png │ │ ├── walletConnectIcon.svg │ │ └── x.svg │ └── svg │ │ ├── QR.svg │ │ ├── SVGArrowDown.js │ │ ├── SVGClose.js │ │ ├── SVGDiscord.js │ │ ├── SVGDiv.js │ │ ├── SVGTelegram.js │ │ ├── lightcircle.svg │ │ ├── logo.svg │ │ ├── logo_pink.svg │ │ ├── logo_white.svg │ │ ├── wordmark.svg │ │ ├── wordmark_pink.svg │ │ └── wordmark_white.svg ├── components │ ├── AccountDetails │ │ ├── Copy.js │ │ ├── Transaction.js │ │ └── index.js │ ├── AddressInputPanel │ │ └── index.js │ ├── Button │ │ └── index.js │ ├── ContextualInfo │ │ └── index.js │ ├── ContextualInfoNew │ │ └── index.js │ ├── CurrencyInputPanel │ │ └── index.js │ ├── ExchangePage │ │ ├── ExchangePage.css │ │ └── index.jsx │ ├── Footer │ │ └── index.js │ ├── Header │ │ └── index.js │ ├── Identicon │ │ └── index.js │ ├── Loader │ │ └── index.js │ ├── Modal │ │ └── index.js │ ├── NavigationTabs │ │ └── index.js │ ├── OrderCard │ │ ├── OrderCard.css │ │ └── index.js │ ├── Orders │ │ └── index.jsx │ ├── OrdersHistory │ │ └── index.js │ ├── OversizedPanel │ │ └── index.js │ ├── PastOrderCard │ │ ├── OrderCard.css │ │ └── index.jsx │ ├── TokenLogo │ │ └── index.js │ ├── TransactionDetails │ │ └── index.js │ ├── WalletModal │ │ ├── Copy.js │ │ ├── Info.js │ │ ├── Option.js │ │ ├── PendingView.js │ │ ├── Transaction.js │ │ └── index.js │ ├── Web3ReactManager │ │ └── index.js │ └── Web3Status │ │ └── index.js ├── connectors │ ├── Fortmatic.js │ ├── NetworkConnector.js │ └── index.js ├── constants │ ├── abis │ │ ├── erc20.json │ │ ├── erc20_bytes32.json │ │ ├── exchange.json │ │ ├── factory.json │ │ ├── factoryV2.json │ │ ├── multicall.json │ │ ├── pair.json │ │ └── uniswapEX.json │ └── index.js ├── contexts │ ├── AllBalances.js │ ├── Allowances.js │ ├── Application.js │ ├── Balances.js │ ├── DefaultTokens.js │ ├── GasPrice.js │ ├── LocalStorage.js │ ├── Tokens.js │ └── Transactions.js ├── hooks │ ├── index.js │ └── trade.js ├── i18n.js ├── index.js ├── module │ └── root │ │ ├── actions.js │ │ └── reducer.js ├── pages │ ├── App.js │ └── Swap │ │ └── index.js ├── state │ ├── index.js │ └── multicall │ │ ├── actions.js │ │ ├── hooks.js │ │ ├── reducer.js │ │ └── updater.jsx ├── theme │ ├── components.js │ └── index.js └── utils │ ├── index.js │ ├── math.js │ ├── price.js │ ├── rate.js │ └── signer.js ├── vercel.json └── yarn.lock /.env.local.example: -------------------------------------------------------------------------------- 1 | REACT_APP_NETWORK_ID="1" 2 | REACT_APP_NETWORK_URL="" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Bug Description** 11 | A clear and concise description of what the bug is. 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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/something-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Something Else 3 | about: Tell us something else 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/token-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Token Request 3 | about: Request a token addition 4 | title: '' 5 | labels: token request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Please provide the following information for your token.** 11 | 12 | Token Address: 13 | Token Name (from contract): 14 | Token Decimals (from contract): 15 | Token Symbol (from contract): 16 | Uniswap Exchange Address of Token: 17 | 18 | Link to the official homepage of the token: 19 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **PLEASE DO NOT SUBMIT TOKEN ADDITIONS AS PULL REQUESTS** 2 | 3 | All token requests should be made via an issue. 4 | -------------------------------------------------------------------------------- /.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 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | /.netlify 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | notes.txt 27 | .idea/ 28 | 29 | .vscode/ 30 | .vercel 31 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | except: 3 | - master 4 | language: node_js 5 | node_js: 6 | - '10' 7 | cache: 8 | directories: 9 | - node_modules 10 | install: yarn 11 | script: 12 | - yarn check:all 13 | - yarn build 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pine Finance 2 | 3 | > Protocol for decentralized & automated orders. 4 | 5 | **TL;DR**: [Traders](#traders) can create orders. [Relayers](#relayers) can earn a fee executing them when the trade conditions can be fulfilled. 6 | 7 | ## Table of Contents 8 | 9 | - [Introduction](#introduction) 10 | - [Contracts](#contracts) 11 | - [Interaction & Order Fulfillment](#interaction-and-order-fulfillment-example) 12 | 13 | ## Contracts 14 | 15 | ### Mainnet 16 | 17 | - [PineCore](https://etherscan.io/address/0xd412054cca18a61278ced6f674a526a6940ebd84#code) 18 | 19 | - [LimitOrdersModule](https://etherscan.io/address/0x037fc8e71445910e1e0bbb2a0896d5e9a7485318#code) 20 | 21 | - [UniswapV1Handler](https://etherscan.io/address/0xf48f47c959951b1a8b0691159a75a035dfed2d1d#code) 22 | 23 | - [UniswapV2Handler](https://etherscan.io/address/0x842a8dea50478814e2bfaff9e5a27dc0d1fdd37c#code) 24 | 25 | ### Rinkeby 26 | 27 | - [PineCore](https://rinkeby.etherscan.io/address/0xd412054cca18a61278ced6f674a526a6940ebd84#code) 28 | 29 | - [LimitOrdersModule](https://rinkeby.etherscan.io/address/0x037fc8e71445910e1e0bbb2a0896d5e9a7485318#code) 30 | 31 | - [UniswapV1Handler](https://rinkeby.etherscan.io/address/0x9d8453c495ac68cf717179d5ad9235f5eebf387d#code) 32 | 33 | - [UniswapV2Handler](https://rinkeby.etherscan.io/address/0xbf95dd8dfbccdba150b4bc3d227a80c53acd3e0f#code) 34 | 35 | ## Introduction 36 | 37 | [Pine Finance](https://pine.finance) is a protocol for automated and decentralized orders exchange powered by Ethereum. 38 | 39 | [Limit orders](https://www.investopedia.com/terms/l/limitorder.asp) give traders complete control over the rate at which their orders will be executed, enabling traders to automate transactions at a specific rate. 40 | 41 | It continues the base commitment to free and decentralized exchange. 42 | 43 | Every token combination is available. There **isn't** a limitation on how you can select what to buy and what to sell (Of course if it is a token living on the Ethereum network). 44 | 45 | An order at Pine Finance can only be canceled by its creator, and it can be executed if the creator receives the desired amount, making the system trustless and independent of a central entity. 46 | 47 | The [smart contract](https://etherscan.io/address/0xd412054cca18a61278ced6f674a526a6940ebd84#code) is validated and can be reviewed by anyone. The code hasn't been audited by a reputable third party yet, and we advise to proceed with caution. 48 | 49 | [More info of how it works](https://medium.com/@pine_eth/pine-finance-an-amm-orders-engine-525fe1f1b1eb) 50 | 51 | ## Interaction and order fulfillment example 52 | 53 | Imagine the current rate of DAI -> ETH is `0.003`. So, based on the market price if the _user_ trades `400 DAI` will receive `1.2 ETH`. **BUT**, a _user_ wants to sell `400 DAI` in exchange for `1.6 ETH` (`desired_output`). 54 | 55 | The _user_ creates a limit order at [pine.finance](https://pine.finance) by sending a transaction with the following values: 56 | 57 | - **Input**: 400 DAI 58 | - **Rate**: 0.004 DAI-ETH 59 | - **Output**: 1.6 ETH 60 | 61 | Once the transaction is confirmed, _relayers_ will start checking if the order can be fulfilled. _Reyalers_ can have their own strategy on how to execute an order. Pine, has a minimum of two relayers running 24/7 with a basic strategy. 62 | 63 | The first thing they do is to check how much _output_, in this case ETH, they will get in exchange for `400 DAI` (`trade_output`). Then, if it is higher or equal than `1.6 ETH` (which is what the _user_ set as the minimum output) they check how much will cost to send the transaction to do the trade (`execution cost`). Once _relayers_ get the `execution_cost`, they check if they can still achieve the output defined by the _user_: 64 | 65 | ``` 66 | desired_output <= (trade_output - execution_cost) 67 | ``` 68 | 69 | `execution_cost` depends on the [Gas Price](https://etherscan.io/gastracker). Higher gas prices, higher `execution_cost`. You can read more about Gas Price [here.](https://www.investopedia.com/terms/g/gas-ethereum.asp#:~:text=On%20the%20ethereum%20blockchain%2C%20gas,with%20are%20worth%200.000000001%20ether) 70 | 71 | Finally, _relayers_ can charge a fee for executing the order (`relayer_fee`). The final formula will be: 72 | 73 | ``` 74 | desired_output <= (trade_output - execution_cost - relayer_fee) 75 | ``` 76 | 77 | _Even the math can match, have in mind that if the amount to trade is high, a price impact will occur depending on the liquidity of the pool used._ 78 | 79 | To continue with real numbers, if the `execution_cost` is 0.04 ETH and the `relayers_fee` is 0.006: 80 | 81 | ``` 82 | 1.6 ETH <= trade_output - 0.04 ETH - 0.006 ETH 83 | 84 | 1.6 ETH <= trade_output - 0.046 ETH 85 | 86 | 1.6 ETH + 0.046 ETH <= trade_output 87 | 88 | 1.646 ETH <= trade_output // Final rate 0.004115 for execution 89 | ``` 90 | 91 | If you want to add your token reach out us. 92 | 93 | - [Discord](https://discord.gg/w6JVcrg) 94 | - [Telegram](https://t.me/UniswapEX) 95 | - [Twitter](https://twitter.com/pine_eth) 96 | 97 | Repo forked and modified from [Uniswap](https://github.com/Uniswap/uniswap-frontend). 98 | 99 | Donate: [Gitcoin](https://gitcoin.co/grants/765/pine-finance-ex-uniswapex-v2) 100 | 101 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | # support SPA setup 2 | [[redirects]] 3 | from = "/*" 4 | to = "/index.html" 5 | status = 200 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pine-finance", 3 | "description": "Decentralized Limit Orders Exchange Protocol", 4 | "version": "2.0.0", 5 | "homepage": "https://pine.finance/", 6 | "private": true, 7 | "dependencies": { 8 | "@ethersproject/constants": "^5.0.2", 9 | "@ethersproject/contracts": "^5.0.2", 10 | "@ethersproject/providers": "^5.0.5", 11 | "@ethersproject/solidity": "^5.0.2", 12 | "@ethersproject/units": "^5.0.2", 13 | "@makerdao/multicall": "^0.10.0-rc.4", 14 | "@reach/dialog": "^0.2.8", 15 | "@reach/tooltip": "^0.2.0", 16 | "@reduxjs/toolkit": "^1.4.0", 17 | "@uniswap/sdk": "^1.0.0-beta.4", 18 | "@web3-react/abstract-connector": "^6.0.7", 19 | "@web3-react/core": "^6.1.1", 20 | "@web3-react/fortmatic-connector": "^6.0.9", 21 | "@web3-react/injected-connector": "^6.0.7", 22 | "@web3-react/portis-connector": "^6.1.1", 23 | "@web3-react/walletconnect-connector": "^6.1.4", 24 | "@web3-react/walletlink-connector": "^6.1.1", 25 | "copy-to-clipboard": "^3.2.0", 26 | "escape-string-regexp": "^2.0.0", 27 | "ethers": "^4.0.33", 28 | "i18next": "^15.0.9", 29 | "i18next-browser-languagedetector": "^3.0.1", 30 | "i18next-xhr-backend": "^2.0.1", 31 | "jazzicon": "^1.5.0", 32 | "local-storage": "^2.0.0", 33 | "now": "^16.2.0", 34 | "polished": "^3.3.2", 35 | "react": "^16.8.6", 36 | "react-device-detect": "^1.6.2", 37 | "react-dom": "^16.8.6", 38 | "react-feather": "^1.1.6", 39 | "react-ga": "^2.5.7", 40 | "react-i18next": "^10.7.0", 41 | "react-redux": "^7.2.1", 42 | "react-router-dom": "^5.0.0", 43 | "react-scripts": "^3.4.3", 44 | "react-spring": "^8.0.20", 45 | "react-switch": "^5.0.1", 46 | "rebass": "^4.0.7", 47 | "redux-localstorage-simple": "^2.3.1", 48 | "styled-components": "^4.2.0", 49 | "uniswap-v2-sdk": "npm:@uniswap/sdk@^3.0.3-beta.1", 50 | "web3": "^1.2.6", 51 | "web3-react": "^5.0.4" 52 | }, 53 | "scripts": { 54 | "start": "react-scripts start", 55 | "build": "react-scripts build", 56 | "test": "react-scripts test --env=jsdom", 57 | "eject": "react-scripts eject", 58 | "lint:base": "yarn eslint './src/**/*.{js,jsx}'", 59 | "format:base": "yarn prettier './src/**/*.{js,jsx,scss}'", 60 | "fix:lint": "yarn lint:base --fix", 61 | "fix:format": "yarn format:base --write", 62 | "fix:all": "yarn fix:lint && yarn fix:format", 63 | "check:lint": "yarn lint:base", 64 | "check:format": "yarn format:base --check", 65 | "check:all": "yarn check:lint && yarn check:format" 66 | }, 67 | "eslintConfig": { 68 | "extends": "react-app" 69 | }, 70 | "browserslist": { 71 | "production": [ 72 | ">0.2%", 73 | "not dead", 74 | "not op_mini all" 75 | ], 76 | "development": [ 77 | "last 1 chrome version", 78 | "last 1 firefox version", 79 | "last 1 safari version" 80 | ] 81 | }, 82 | "license": "GPL-3.0-or-later", 83 | "devDependencies": { 84 | "prettier": "^1.17.0" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pine-finance/pine-interface/4738acd306822c356b1e32981dc77b26b18539b7/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | Pine.finance 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /public/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "noWallet": "Keine Ethereum-Wallet gefunden", 3 | "wrongNetwork": "Du bist auf dem falschen Netzwerk.", 4 | "switchNetwork": "Bitte wechsle zum {{ correctNetwork }}", 5 | "installWeb3MobileBrowser": "Bitte besuche uns mit einem web3-fähigen mobilen Browser wie z.B. Trust Wallet oder Coinbase Wallet.", 6 | "installMetamask": "Bitte besuch uns erneut, nachdem du Metamask oder Brave installiert hast.", 7 | "disconnected": "Nicht verbunden", 8 | "swap": "Tauschen", 9 | "send": "Senden", 10 | "pool": "Pool", 11 | "betaWarning": "Dieses Projekt ist in beta. Nutzung auf eigenes Risiko.", 12 | "input": "Input", 13 | "output": "Output", 14 | "estimated": "geschätzt", 15 | "balance": "Guthaben: {{ balanceInput }}", 16 | "unlock": "Freischalten", 17 | "pending": "hängige", 18 | "selectToken": "Token auswählen", 19 | "searchOrPaste": "Token suchen oder Token Adresse einfügen", 20 | "noExchange": "Exchange nicht gefunden", 21 | "exchangeRate": "Wechselkurs", 22 | "invertedRate": "Invertierter Wechselkurs", 23 | "enterValueCont": "Wert {{ missingCurrencyValue }} eingeben um fortzufahren.", 24 | "selectTokenCont": "Token auswählen um fortzufahren.", 25 | "noLiquidity": "Keine Liquidität.", 26 | "unlockTokenCont": "Token freischalten um fortzufahren.", 27 | "transactionDetails": "Details der Transaktion", 28 | "hideDetails": "Details ausblenden", 29 | "youAreSelling": "Du verkaufst", 30 | "orTransFail": "oder die Transaktion wird fehlschlagen.", 31 | "youWillReceive": "Du erhältst mindestens", 32 | "youAreBuying": "Du kaufst", 33 | "itWillCost": "Es kostet höchstens", 34 | "insufficientBalance": "Guthaben ungenügend", 35 | "inputNotValid": "Eingabewert ungültig", 36 | "differentToken": "Es müssen unterschiedliche Token sein.", 37 | "noRecipient": "Empfängeradresse angeben.", 38 | "invalidRecipient": "Bitte gib eine gültige Empfängeradresse an.", 39 | "recipientAddress": "Adresse des Empfängers", 40 | "youAreSending": "Du schickst", 41 | "willReceive": "erhält mindestens", 42 | "to": "zu", 43 | "addLiquidity": "Liquidität hinzufügen", 44 | "deposit": "Depot", 45 | "currentPoolSize": "Aktuelle Größe des Pools", 46 | "yourPoolShare": "Dein Anteil am Pool", 47 | "noZero": "Wert darf nicht Null sein.", 48 | "mustBeETH": "Einer der Inputs muß ETH sein.", 49 | "enterCurrencyOrLabelCont": "{{ inputCurrency }} oder {{ label }} Wert eingeben um fortzufahren.", 50 | "youAreAdding": "Du fügst zwischen", 51 | "and": "und", 52 | "intoPool": "in den Liquiditätspool.", 53 | "outPool": "vom Liquiditätspool.", 54 | "youWillMint": "Du prägst", 55 | "liquidityTokens": "Liquiditätstokens.", 56 | "totalSupplyIs": "Die gesamte Anzahl Liquiditätstokens ist aktuell", 57 | "youAreSettingExRate": "Du setzt den anfänglichen Wechselkurs auf", 58 | "totalSupplyIs0": "Die gesamte Anzahl Liquiditätstokens ist aktuell 0.", 59 | "tokenWorth": "Zum gegenwärtigen Wechselkurs ist jeder Pool Token so viel Wert", 60 | "firstLiquidity": "Du bist die erste Person die Liquidität bereitstellt!", 61 | "initialExchangeRate": "Der initiale Wechselkurs wird auf deiner Überweisung basieren. Stelle sicher, dass deine ETH und {{ label }} denselben Fiatwert haben.", 62 | "removeLiquidity": "Liquidität entfernen", 63 | "poolTokens": "Pool Tokens", 64 | "enterLabelCont": "{{ label }} Wert eingeben um fortzufahren.", 65 | "youAreRemoving": "Du entfernst zwischen", 66 | "youWillRemove": "Du entfernst", 67 | "createExchange": "Exchange erstellen", 68 | "invalidTokenAddress": "Ungültige Tokenadresse", 69 | "exchangeExists": "{{ label }} Exchange existiert bereits!", 70 | "invalidSymbol": "Symbol ungültig", 71 | "invalidDecimals": "Dezimalstellen ungültig", 72 | "tokenAddress": "Tokenadresse", 73 | "label": "Label", 74 | "decimals": "Dezimalstellen", 75 | "enterTokenCont": "Tokenadresse eingeben um fortzufahren", 76 | "rate": "Rate", 77 | "place": "Poner", 78 | "placeAbove": "Order {{ rateDelta }}% above market", 79 | "placeBelow": "Order {{ rateDelta }}% below market" 80 | } 81 | -------------------------------------------------------------------------------- /public/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "noWallet": "No Ethereum wallet found", 3 | "wrongNetwork": "You are on the wrong network", 4 | "switchNetwork": "Please switch to {{ correctNetwork }}", 5 | "installWeb3MobileBrowser": "Please visit us from a web3-enabled mobile browser such as Trust Wallet or Coinbase Wallet.", 6 | "installMetamask": "Please visit us after installing Metamask on Chrome or Brave.", 7 | "disconnected": "Disconnected", 8 | "swap": "Swap", 9 | "swapAnyway": "Swap Anyway", 10 | "send": "Send", 11 | "sendAnyway": "Send Anyway", 12 | "pool": "Pool", 13 | "betaWarning": "This project is in beta. Use at your own risk.", 14 | "input": "Input", 15 | "output": "Output", 16 | "estimated": "At least", 17 | "balance": "Balance: {{ balanceInput }}", 18 | "unlock": "Unlock", 19 | "pending": "Pending", 20 | "selectToken": "Select a token", 21 | "searchOrPaste": "Search Token Name, Symbol, or Address", 22 | "searchOrPasteMobile": "Name, Symbol, or Address", 23 | "noExchange": "No Exchange Found", 24 | "exchangeRate": "Market Exchange Rate", 25 | "unknownError": "Oops! An unknown error occurred. Please refresh the page, or visit from another browser or device.", 26 | "enterValueCont": "Enter a {{ missingCurrencyValue }} value to continue.", 27 | "selectTokenCont": "Select a token to continue.", 28 | "noLiquidity": "No liquidity.", 29 | "insufficientLiquidity": "Insufficient liquidity.", 30 | "unlockTokenCont": "Please unlock token to continue.", 31 | "transactionDetails": "Advanced Details", 32 | "hideDetails": "Hide Details", 33 | "slippageWarning": "Slippage Warning", 34 | "highSlippageWarning": "This transaction is below market rate", 35 | "youAreSelling": "You are selling", 36 | "orTransFail": "or the transaction will fail.", 37 | "youWillReceive": "You will receive at least", 38 | "youAreBuying": "You are buying", 39 | "itWillCost": "It will cost at most", 40 | "forAtMost": "for at most", 41 | "insufficientBalance": "Insufficient Balance", 42 | "inputNotValid": "Not a valid input value", 43 | "differentToken": "Must be different token.", 44 | "noRecipient": "Enter a wallet address to send to.", 45 | "invalidRecipient": "Please enter a valid wallet address recipient.", 46 | "recipientAddress": "Recipient Address", 47 | "youAreSending": "You are sending", 48 | "willReceive": "will receive at least", 49 | "to": "to", 50 | "addLiquidity": "Add Liquidity", 51 | "deposit": "Deposit", 52 | "currentPoolSize": "Current Pool Size", 53 | "yourPoolShare": "Your Pool Share", 54 | "noZero": "Amount cannot be zero.", 55 | "mustBeETH": "One of the input must be ETH.", 56 | "enterCurrencyOrLabelCont": "Enter a {{ inputCurrency }} or {{ label }} value to continue.", 57 | "youAreAdding": "You are adding", 58 | "and": "and", 59 | "intoPool": "into the liquidity pool.", 60 | "outPool": "from the liquidity pool.", 61 | "youWillMint": "You will mint", 62 | "liquidityTokens": "liquidity tokens.", 63 | "totalSupplyIs": "Current total supply of liquidity tokens is", 64 | "youAreSettingExRate": "You are setting the initial exchange rate to", 65 | "totalSupplyIs0": "Current total supply of liquidity tokens is 0.", 66 | "tokenWorth": "At current exchange rate, each pool token is worth", 67 | "firstLiquidity": "You are the first person to add liquidity!", 68 | "initialExchangeRate": "The initial exchange rate will be set based on your deposits. Please make sure that your ETH and {{ label }} deposits have the same fiat value.", 69 | "removeLiquidity": "Remove Liquidity", 70 | "poolTokens": "Pool Tokens", 71 | "enterLabelCont": "Enter a {{ label }} value to continue.", 72 | "youAreRemoving": "You are removing between", 73 | "youWillRemove": "You will remove", 74 | "createExchange": "Create Exchange", 75 | "invalidTokenAddress": "Not a valid token address", 76 | "exchangeExists": "{{ label }} Exchange already exists!", 77 | "invalidSymbol": "Invalid symbol", 78 | "invalidDecimals": "Invalid decimals", 79 | "tokenAddress": "Token Address", 80 | "label": "Label", 81 | "name": "Name", 82 | "symbol": "Symbol", 83 | "decimals": "Decimals", 84 | "enterTokenCont": "Enter a token address to continue", 85 | "priceChange": "Expected price slippage", 86 | "forAtLeast": "for at least ", 87 | "place": "Place", 88 | "placeAnyway": "Place anyway", 89 | "cancel": "Cancel", 90 | "noOpenOrders": "No open orders", 91 | "fetchingOrders": "Loading open orders", 92 | "enoughAmountToCoverFees": "Insufficient amount to cover the relayer fee: {{ fee }}", 93 | "rate": "Rate", 94 | "placeAbove": "Order {{ rateDelta }}% above market", 95 | "placeBelow": "Order {{ rateDelta }}% below market", 96 | "feeSet": "You will pay a fee of", 97 | "toTheRelayer": "to the relayer for executing the order", 98 | "relayerFee": "The relayer will execute the order with a GAS price of", 99 | "gasPrice": "Gas Price (GWEI)", 100 | "feeFaq": "Every order has a fee which is the payment to the relayer for performing the trade. This fee should be higher than the transaction cost of executing the order. Lowering this, increase the risk of the order to not being executed making inviable to cover the order execution fees.", 101 | "seeHowItWorks": "See how it works", 102 | "customGasPrice": "Custom gas price value (GWEI)", 103 | "notExecuted": "Your order will not be executed", 104 | "mayNotExecuted": "Your order may not be executed" 105 | } 106 | -------------------------------------------------------------------------------- /public/locales/es-AR.json: -------------------------------------------------------------------------------- 1 | { 2 | "noWallet": "No se encontró billetera de Ethereum", 3 | "wrongNetwork": "Te encontrás en la red equivocada", 4 | "switchNetwork": "Por favor cambia a {{ correctNetwork }}", 5 | "installWeb3MobileBrowser": "Por favor ingresá desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.", 6 | "installMetamask": "Por favor visítanos nuevamente luego de instalar Metamask en Chrome o Brave.", 7 | "disconnected": "Desconectado", 8 | "swap": "Intercambiar", 9 | "send": "Enviar", 10 | "pool": "Pool", 11 | "betaWarning": "Este proyecto se encuentra en beta. Usalo bajo tu propio riesgo.", 12 | "input": "Entrada", 13 | "output": "Salida", 14 | "estimated": "estimado", 15 | "balance": "Saldo: {{ balanceInput }}", 16 | "unlock": "Desbloquear", 17 | "pending": "Pendiente", 18 | "selectToken": "Seleccioná un token", 19 | "searchOrPaste": "Buscar Token o Pegar Dirección", 20 | "noExchange": "No se encontró la divisa", 21 | "exchangeRate": "Tasa de Cambio Actual", 22 | "enterValueCont": "Ingresá un valor en {{ missingCurrencyValue }} para continuar.", 23 | "selectTokenCont": "Seleccioná un token para continuar.", 24 | "noLiquidity": "Sin liquidez.", 25 | "unlockTokenCont": "Por favor desbloqueá un token para continuar.", 26 | "transactionDetails": "Detalles de la transacción", 27 | "hideDetails": "Ocultar detalles", 28 | "youAreSelling": "Estás vendiendo", 29 | "orTransFail": "o la transacción fallará.", 30 | "youWillReceive": "Vas a recibir al menos", 31 | "youAreBuying": "Estás comprando", 32 | "itWillCost": "Costará a lo sumo", 33 | "insufficientBalance": "Saldo insuficiente", 34 | "inputNotValid": "No es un valor de entrada válido", 35 | "differentToken": "Debe ser un token distinto.", 36 | "noRecipient": "Ingresá una dirección de billetera para enviar.", 37 | "invalidRecipient": "Por favor ingrese una billetera de destino válida.", 38 | "recipientAddress": "Dirección del recipiente", 39 | "youAreSending": "Estás enviando", 40 | "willReceive": "recibirá al menos", 41 | "to": "a", 42 | "addLiquidity": "Agregar liquidez", 43 | "deposit": "Depositar", 44 | "currentPoolSize": "Tamaño del Pool Actual", 45 | "yourPoolShare": "Tu parte del Pool", 46 | "noZero": "El monto no puede ser cero.", 47 | "mustBeETH": "Una de las entradas debe ser ETH.", 48 | "enterCurrencyOrLabelCont": "Ingresá un valor de {{ inputCurrency }} o de {{ label }} para continuar.", 49 | "youAreAdding": "Estás agregando entre", 50 | "and": "y", 51 | "intoPool": "en el pool de liquidez.", 52 | "outPool": "en el pool de liquidez.", 53 | "youWillMint": "Vas a acuñar", 54 | "liquidityTokens": "tokens de liquidez.", 55 | "totalSupplyIs": "El actual suministro total de tokens de liquidez es", 56 | "youAreSettingExRate": "Está configurando el tipo de cambio inicial a", 57 | "totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.", 58 | "tokenWorth": "Al tipo de cambio actual, cada token del pool vale", 59 | "firstLiquidity": "Sos la primer persona en agregar liquidez!", 60 | "initialExchangeRate": "El tipo de cambio inicial se establecerá en función de tus depósitos. Por favor, asegúrate de que tus depósitos en ETH y {{ label }} tengan el mismo valor fíat.", 61 | "removeLiquidity": "Remover Liquidez", 62 | "poolTokens": "Pool de Tokens", 63 | "enterLabelCont": "Ingresá un valor de {{ label }} para continuar.", 64 | "youAreRemoving": "Estás quitando entre", 65 | "youWillRemove": "Vas a remover", 66 | "createExchange": "Crear divisa", 67 | "invalidTokenAddress": "No es una dirección de token válida", 68 | "exchangeExists": "La divisa {{ label }} ya existe!", 69 | "invalidSymbol": "Símbolo inválido", 70 | "invalidDecimals": "Decimales inválidos", 71 | "tokenAddress": "Dirección de Token", 72 | "label": "Etiqueta", 73 | "decimals": "Decimales", 74 | "enterTokenCont": "Ingresá una dirección de token para continuar", 75 | "place": "Ordenar", 76 | "highSlippageWarning": "Esta transaccion esta por debajo del valor de mercado", 77 | "enoughAmountToCoverFees": "El valor ingresado no alcanza para cubrir el pago al relayer: {{ fee }}", 78 | "rate": "Cambio", 79 | "placeAbove": "Orden {{ rateDelta }}% sobre el mercado", 80 | "placeBelow": "Orden {{ rateDelta }}% debajo del mercado", 81 | "relayerFee": "El relayer va a ejecutar la orden con un precio del GAS de ", 82 | "feeSet": "Va a pagar una tarifa de ", 83 | "toTheRelayer": "al relayer por ejecutar la orden", 84 | "gasPrice": "Precio del GAS (GWEI)", 85 | "feeFaq": "La tarifa es el pago al relayer. Dicha tarifa debe ser mayor al costo de ejecución de la orden. Si se disminuye, incrementa el riesgo de no poder ejecutarse la orden ya que el relayer no podra cubrir el costo de mandar la transacción", 86 | "seeHowItWorks": "Mire como funciona", 87 | "customGasPrice": "Precio del GAS customizado (GWEI)", 88 | "notExecuted": "Tu orden no se ejecutará", 89 | "mayNotExecuted": "Tu orden puede no ejecutarse" 90 | } 91 | -------------------------------------------------------------------------------- /public/locales/es-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "noWallet": "No se ha encontrado billetera de Ethereum", 3 | "wrongNetwork": "Se encuentra en la red equivocada", 4 | "switchNetwork": "Por favor cambie a {{ correctNetwork }}", 5 | "installWeb3MobileBrowser": "Por favor ingrese desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.", 6 | "installMetamask": "Por favor visítenos nuevamente luego de instalar Metamask en Chrome o Brave.", 7 | "disconnected": "Desconectado", 8 | "swap": "Intercambiar", 9 | "send": "Enviar", 10 | "pool": "Pool", 11 | "betaWarning": "Este proyecto se encuentra en beta. Úselo bajo tu propio riesgo.", 12 | "input": "Entrada", 13 | "output": "Salida", 14 | "estimated": "estimado", 15 | "balance": "Saldo: {{ balanceInput }}", 16 | "unlock": "Desbloquear", 17 | "pending": "Pendiente", 18 | "selectToken": "Seleccione un token", 19 | "searchOrPaste": "Buscar Token o Pegar Dirección", 20 | "noExchange": "No se ha encontrado la divisa", 21 | "exchangeRate": "Tasa de Cambio Actual", 22 | "enterValueCont": "Ingrese un valor en {{ missingCurrencyValue }} para continuar.", 23 | "selectTokenCont": "Seleccione un token para continuar.", 24 | "noLiquidity": "Sin liquidez.", 25 | "unlockTokenCont": "Por favor desbloquea un token para continuar.", 26 | "transactionDetails": "Detalles de la transacción", 27 | "hideDetails": "Ocultar detalles", 28 | "youAreSelling": "Está vendiendo", 29 | "orTransFail": "o la transacción fallará.", 30 | "youWillReceive": "Va a recibir al menos", 31 | "youAreBuying": "Está comprando", 32 | "itWillCost": "Costará a lo sumo", 33 | "insufficientBalance": "Saldo insuficiente", 34 | "inputNotValid": "No es un valor de entrada válido", 35 | "differentToken": "Debe ser un token distinto.", 36 | "noRecipient": "Ingrese una dirección de billetera para enviar.", 37 | "invalidRecipient": "Por favor ingrese una billetera de destino válida.", 38 | "recipientAddress": "Dirección del recipiente", 39 | "youAreSending": "Está enviando", 40 | "willReceive": "recibirá al menos", 41 | "to": "a", 42 | "addLiquidity": "Agregar liquidez", 43 | "deposit": "Depositar", 44 | "currentPoolSize": "Tamaño del Pool Actual", 45 | "yourPoolShare": "Su parte del Pool", 46 | "noZero": "El monto no puede ser cero.", 47 | "mustBeETH": "Una de las entradas debe ser ETH.", 48 | "enterCurrencyOrLabelCont": "Ingrese un valor de {{ inputCurrency }} o de {{ label }} para continuar.", 49 | "youAreAdding": "Está agregando entre", 50 | "and": "y", 51 | "intoPool": "en el pool de liquidez.", 52 | "outPool": "en el pool de liquidez.", 53 | "youWillMint": "Va a acuñar", 54 | "liquidityTokens": "tokens de liquidez.", 55 | "totalSupplyIs": "El actual suministro total de tokens de liquidez es", 56 | "youAreSettingExRate": "Está configurando el tipo de cambio inicial a", 57 | "totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.", 58 | "tokenWorth": "Al tipo de cambio actual, cada token del pool vale", 59 | "firstLiquidity": "Es la primer persona en agregar liquidez!", 60 | "initialExchangeRate": "El tipo de cambio inicial se establecerá en función de sus depósitos. Por favor, asegúrese de que sus depósitos en ETH y {{ label }} tengan el mismo valor fíat.", 61 | "removeLiquidity": "Remover Liquidez", 62 | "poolTokens": "Pool de Tokens", 63 | "enterLabelCont": "Ingresa un valor de {{ label }} para continuar.", 64 | "youAreRemoving": "Está quitando entre", 65 | "youWillRemove": "Va a remover", 66 | "createExchange": "Crear tipo de cambio", 67 | "invalidTokenAddress": "No es una dirección de token válida", 68 | "exchangeExists": "El tipo de cambio {{ label }} ya existe!", 69 | "invalidSymbol": "Símbolo inválido", 70 | "invalidDecimals": "Decimales inválidos", 71 | "tokenAddress": "Dirección de Token", 72 | "label": "Etiqueta", 73 | "decimals": "Decimales", 74 | "enterTokenCont": "Ingrese una dirección de token para continuar", 75 | "place": "Ordenar", 76 | "highSlippageWarning": "Esta transaccion esta por debajo del valor de mercado", 77 | "enoughAmountToCoverFees": "El valor ingresado no alcanza para cubrir el pago al relayer: {{ fee }}", 78 | "rate": "Cambio", 79 | "placeAbove": "Orden {{ rateDelta }}% sobre el mercado", 80 | "placeBelow": "Orden {{ rateDelta }}% debajo del mercado", 81 | "relayerFee": "El relayer va a ejecutar la orden con un precio del GAS de ", 82 | "feeSet": "Va a pagar una tarifa de ", 83 | "toTheRelayer": "al relayer por ejecutar la orden", 84 | "gasPrice": "Precio del GAS (GWEI)", 85 | "feeFaq": "La tarifa es el pago al relayer. Dicha tarifa debe ser mayor al costo de ejecución de la orden. Si se disminuye, incrementa el riesgo de no poder ejecutarse la orden ya que el relayer no podra cubrir el costo de mandar la transacción", 86 | "seeHowItWorks": "Mire como funciona", 87 | "customGasPrice": "Precio del GAS customizado (GWEI)", 88 | "notExecuted": "Tu orden no se ejecutará", 89 | "mayNotExecuted": "Tu orden puede no ejecutarse" 90 | } 91 | -------------------------------------------------------------------------------- /public/locales/ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "noWallet": "Niciun portofel Ethereum găsit", 3 | "wrongNetwork": "Nu ești conectat la rețeaua corectă", 4 | "switchNetwork": "Conectează-te te rog la {{ correctNetwork }}", 5 | "installWeb3MobileBrowser": "Incearcă să vizitezi această pagina folosind un browser precum Trust Wallet sau Coinbase Wallet.", 6 | "installMetamask": "Vizitează această pagină din nou după ce instalezi MetaMask în Chrome sau Brave", 7 | "disconnected": "Deconectat", 8 | "swap": "Schimba", 9 | "send": "Trimite", 10 | "pool": "Extrage", 11 | "betaWarning": "Proiectul este încă în versiunea beta. Folosește-l cu grijă, riscul este al tău.", 12 | "input": "Input", 13 | "output": "Output", 14 | "estimated": "estimat", 15 | "balance": "Balanță: {{ balanceInput }}", 16 | "unlock": "Deschide", 17 | "pending": "În Așteptare", 18 | "selectToken": "Selectează un token", 19 | "searchOrPaste": "Caută Token sau Setează Adresă", 20 | "noExchange": "Niciun Exchange Găsit", 21 | "exchangeRate": "Curs Valutar", 22 | "enterValueCont": "Setează o valoare pentru {{ missingCurrencyValue }} pentru a continua.", 23 | "selectTokenCont": "Selectează un token pentru a continua.", 24 | "noLiquidity": "Lichiditate Inexistentă.", 25 | "unlockTokenCont": "Te rog deblochează tokenul pentru a continua.", 26 | "transactionDetails": "Detalii Tranzacții", 27 | "hideDetails": "Ascunde Detaili", 28 | "youAreSelling": "Vinzi", 29 | "orTransFail": "sau tranzacția va eșua.", 30 | "youWillReceive": "Vei primi cel puțin", 31 | "youAreBuying": "Cumperi", 32 | "itWillCost": "Va costa cel mult", 33 | "insufficientBalance": "Balanță Insuficientă", 34 | "inputNotValid": "Valoarea setată nu este validă", 35 | "differentToken": "Trebuie să fie un token diferit.", 36 | "noRecipient": "Setează o adresă de portofel pentru destinatar.", 37 | "invalidRecipient": "Te rog setează o adresă de portofel validă pentru destinatar.", 38 | "recipientAddress": "Adresa Destinatarului", 39 | "youAreSending": "Trimiți", 40 | "willReceive": "vei primi cel puțin", 41 | "to": "spre", 42 | "addLiquidity": "Crește Lichiditatea", 43 | "deposit": "Depozitează", 44 | "currentPoolSize": "Mărimea Actualului Fond Comun", 45 | "yourPoolShare": "Partea Ta Din Fond Comun", 46 | "noZero": "Cantitatea nu poate fi zero.", 47 | "mustBeETH": "Una dintre valori trebuie să fie ETH.", 48 | "enterCurrencyOrLabelCont": "Setează o valoare pentru {{ inputCurrency }} sau {{ label }} pentru a continua. ", 49 | "youAreAdding": "Adaugi între", 50 | "and": "și", 51 | "intoPool": "în fondul comun de lichidatate.", 52 | "outPool": "din fondul comun de lichiditate.", 53 | "youWillMint": "Vei printa", 54 | "liquidityTokens": "tokene disponibile.", 55 | "totalSupplyIs": "Actuala ofertă totală de tokene disponibile este", 56 | "youAreSettingExRate": "Setezi cursul valutar inițial la", 57 | "totalSupplyIs0": "Actuala ofertă totală de tokene disponibile este 0.", 58 | "tokenWorth": "La acest schimb valutar, fiecare token disponibil este estimată la", 59 | "firstLiquidity": "Ești prima persoană care aduce lichiditate!", 60 | "initialExchangeRate": "Schimbul valutar inițial va fi setat în funcție de depozitul tău. Te rog asigură-te că depoziturile ETH și {{ label }} pe care le-ai făcut au aceeași valoare în monezi naționale.", 61 | "removeLiquidity": "Elimină Lichiditate", 62 | "poolTokens": "Extrage Tokene", 63 | "enterLabelCont": "Setează o valoare pentru {{ label }} pentru a continua.", 64 | "youAreRemoving": "Elimini între", 65 | "youWillRemove": "Vei elimina", 66 | "createExchange": "Creează Exchange", 67 | "invalidTokenAddress": "Adresă de token nu este validă", 68 | "exchangeExists": "{{ label }} Exchange există deja!", 69 | "invalidSymbol": "Simbol invalid!", 70 | "invalidDecimals": "Zecimale invalidate", 71 | "tokenAddress": "Adresă Token", 72 | "label": "Denumire", 73 | "decimals": "Zecimale", 74 | "enterTokenCont": "Setează o adresă de token pentru a continua", 75 | "rate": "Rate", 76 | "place": "Place", 77 | "placeAbove": "Order {{ rateDelta }}% above market", 78 | "placeBelow": "Order {{ rateDelta }}% below market" 79 | } 80 | -------------------------------------------------------------------------------- /public/locales/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "noWallet": "Кошелек эфира не найден", 3 | "wrongNetwork": "Некорректная сеть", 4 | "switchNetwork": "Пожалуйста, перейдите в {{ correctNetwork }}", 5 | "installWeb3MobileBrowser": "Пожалуйста, откройте в браузере с поддержкой web3, таком, как Trust Wallet или Coinbase Wallet.", 6 | "installMetamask": "Пожалуйста, откройте в Chrome или Brave с установленным расширением Metamask.", 7 | "disconnected": "Нет подключения", 8 | "swap": "Обменять", 9 | "send": "Отправить", 10 | "pool": "Вложить", 11 | "betaWarning": "Проект находится на стадии бета тестирования.", 12 | "input": "Ввести", 13 | "output": "Вывести", 14 | "estimated": "по оценке", 15 | "balance": "Баланс: {{ balanceInput }}", 16 | "unlock": "Разблокировать", 17 | "pending": "Ожидание", 18 | "selectToken": "Выберите токен", 19 | "searchOrPaste": "Поиск токена или вставить адрес токена", 20 | "noExchange": "Обмен не найден", 21 | "exchangeRate": "Курс обмена", 22 | "enterValueCont": "Введите {{ missingCurrencyValue }}, чтобы продолжить.", 23 | "selectTokenCont": "Введите токен, чтобы продолжить.", 24 | "noLiquidity": "Нет ликвидности.", 25 | "unlockTokenCont": "Пожалуйста, разблокируйте токен, чтобы продолжить.", 26 | "transactionDetails": "Детали транзакции", 27 | "hideDetails": "Скрыть подробности", 28 | "youAreSelling": "Вы продаете", 29 | "orTransFail": "или транзакция будет отклонена.", 30 | "youWillReceive": "Вы получите как минимум", 31 | "youAreBuying": "Вы покупаете", 32 | "itWillCost": "В крайнем случае это будет стоить", 33 | "insufficientBalance": "Недостаточно средств", 34 | "inputNotValid": "Некорректное значение", 35 | "differentToken": "Должны быть разные токены.", 36 | "noRecipient": "Введите адрес кошелька эфира, куда перечислить.", 37 | "invalidRecipient": "Пожалуйста, введите корректный адрес кошелька получателя.", 38 | "recipientAddress": "Адрес получателя", 39 | "youAreSending": "Вы отправляете", 40 | "willReceive": "получит как минимум", 41 | "to": "", 42 | "addLiquidity": "Добавить ликвидность", 43 | "deposit": "Вложить", 44 | "currentPoolSize": "Текущий размер пула", 45 | "yourPoolShare": "Ваша доля в пуле", 46 | "noZero": "Значение не может быть нулевым.", 47 | "mustBeETH": "Одно из значений должно быть ETH.", 48 | "enterCurrencyOrLabelCont": "Введите {{ inputCurrency }} или {{ label }}, чтобы продолжить.", 49 | "youAreAdding": "Вы добавляете от", 50 | "and": "и", 51 | "intoPool": "в пул ликвидности.", 52 | "outPool": "из пула ликвидности.", 53 | "youWillMint": "Вы произведёте", 54 | "liquidityTokens": "токенов ликвидности.", 55 | "totalSupplyIs": "Ваш объем токенов ликвидности", 56 | "youAreSettingExRate": "Вы устанавливаете начальный курс обмена", 57 | "totalSupplyIs0": "Ваш объем токенов ликвидности 0.", 58 | "tokenWorth": "При текущем курсе, каждый токен пула оценивается в", 59 | "firstLiquidity": "Вы первый, кто создаст ликвидность!", 60 | "initialExchangeRate": "Начальный курс обмена будет установлен согласно вашим депозитам. Убедитесь, что ваши депозиты ETH и {{ label }} имеют одинаковое значение в валюте.", 61 | "removeLiquidity": "Убрать ликвидность", 62 | "poolTokens": "Токены пула", 63 | "enterLabelCont": "Введите {{ label }}, чтобы продолжить.", 64 | "youAreRemoving": "Вы убираете в от", 65 | "youWillRemove": "Вы уберёте", 66 | "createExchange": "Создать обмен", 67 | "invalidTokenAddress": "Некорректный адрес токена", 68 | "exchangeExists": "{{ label }} Обмен уже существует!", 69 | "invalidSymbol": "Некорректный символ", 70 | "invalidDecimals": "Некорректное десятичное значение", 71 | "tokenAddress": "Адрес токена", 72 | "label": "Название", 73 | "decimals": "Десятичное значение", 74 | "enterTokenCont": "Чтобы продолжить, введите адрес токена", 75 | "rate": "Rate", 76 | "place": "Place", 77 | "placeAbove": "Order {{ rateDelta }}% above market", 78 | "placeBelow": "Order {{ rateDelta }}% below market" 79 | } 80 | -------------------------------------------------------------------------------- /public/locales/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "noWallet": "未发现以太钱包", 3 | "wrongNetwork": "网络错误", 4 | "switchNetwork": "请切换到 {{ correctNetwork }}", 5 | "installWeb3MobileBrowser": "请从支持web3的移动端浏览器,如 Trust Wallet 或 Coinbase Wallet 访问。", 6 | "installMetamask": "请从安装了 Metamask 插件的 Chrome 或 Brave 访问。", 7 | "disconnected": "未连接", 8 | "swap": "兑换", 9 | "send": "发送", 10 | "pool": "资金池", 11 | "betaWarning": "项目尚处于beta阶段。使用需自行承担风险。", 12 | "input": "输入", 13 | "output": "输出", 14 | "estimated": "估计", 15 | "balance": "余额: {{ balanceInput }}", 16 | "unlock": "解锁", 17 | "pending": "处理中", 18 | "selectToken": "选择通证", 19 | "searchOrPaste": "搜索通证或粘贴地址", 20 | "noExchange": "未找到交易所", 21 | "exchangeRate": "兑换率", 22 | "enterValueCont": "输入{{ missingCurrencyValue }}值并继续。", 23 | "selectTokenCont": "选取通证继续。", 24 | "noLiquidity": "没有流动金。", 25 | "unlockTokenCont": "请解锁通证并继续。", 26 | "transactionDetails": "交易明细", 27 | "hideDetails": "隐藏明细", 28 | "youAreSelling": "你正在出售", 29 | "orTransFail": "或交易失败。", 30 | "youWillReceive": "你将至少收到", 31 | "youAreBuying": "你正在购买", 32 | "itWillCost": "它将至少花费", 33 | "insufficientBalance": "余额不足", 34 | "inputNotValid": "无效的输入值", 35 | "differentToken": "必须是不同的通证。", 36 | "noRecipient": "输入接收钱包地址。", 37 | "invalidRecipient": "请输入有效的收钱地址。", 38 | "recipientAddress": "接收地址", 39 | "youAreSending": "你正在发送", 40 | "willReceive": "将至少收到", 41 | "to": "至", 42 | "addLiquidity": "添加流动金", 43 | "deposit": "存入", 44 | "currentPoolSize": "当前资金池大小", 45 | "yourPoolShare": "你的资金池份额", 46 | "noZero": "金额不能为零。", 47 | "mustBeETH": "输入中必须有一个是 ETH。", 48 | "enterCurrencyOrLabelCont": "输入 {{ inputCurrency }} 或 {{ label }} 值并继续。", 49 | "youAreAdding": "你将添加", 50 | "and": "和", 51 | "intoPool": "入流动资金池。", 52 | "outPool": "出流动资金池。", 53 | "youWillMint": "你将铸造", 54 | "liquidityTokens": "流动通证。", 55 | "totalSupplyIs": "当前流动通证的总量是", 56 | "youAreSettingExRate": "你将初始兑换率设置为", 57 | "totalSupplyIs0": "当前流动通证的总量是0。", 58 | "tokenWorth": "当前兑换率下,每个资金池通证价值", 59 | "firstLiquidity": "你是第一个添加流动金的人!", 60 | "initialExchangeRate": "初始兑换率将由你的存入情况决定。请确保你存入的 ETH 和 {{ label }} 具有相同的总市值。", 61 | "removeLiquidity": "删除流动金", 62 | "poolTokens": "资金池通证", 63 | "enterLabelCont": "输入 {{ label }} 值并继续。", 64 | "youAreRemoving": "你正在移除", 65 | "youWillRemove": "你将移除", 66 | "createExchange": "创建交易所", 67 | "invalidTokenAddress": "通证地址无效", 68 | "exchangeExists": "{{ label }} 交易所已存在!", 69 | "invalidSymbol": "通证符号无效", 70 | "invalidDecimals": "小数位数无效", 71 | "tokenAddress": "通证地址", 72 | "label": "通证符号", 73 | "decimals": "小数位数", 74 | "enterTokenCont": "输入通证地址并继续", 75 | "rate": "Rate", 76 | "place": "Place", 77 | "placeAbove": "Order {{ rateDelta }}% above market", 78 | "placeBelow": "Order {{ rateDelta }}% below market" 79 | } 80 | -------------------------------------------------------------------------------- /public/locales/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "noWallet": "未偵測到以太坊錢包", 3 | "wrongNetwork": "你位在錯誤的網路", 4 | "switchNetwork": "請切換到 {{ correctNetwork }}", 5 | "installWeb3MobileBrowser": "請安裝含有 web3 瀏覽器的手機錢包,如 Trust Wallet 或 Coinbase Wallet。", 6 | "installMetamask": "請使用 Chrome 或 Brave 瀏覽器安裝 Metamask。", 7 | "disconnected": "未連接", 8 | "swap": "兌換", 9 | "send": "發送", 10 | "pool": "資金池", 11 | "betaWarning": "本產品仍在測試階段。使用者需自負風險。", 12 | "input": "輸入", 13 | "output": "輸出", 14 | "estimated": "估計", 15 | "balance": "餘額: {{ balanceInput }}", 16 | "unlock": "解鎖", 17 | "pending": "處理中", 18 | "selectToken": "選擇代幣", 19 | "searchOrPaste": "選擇代幣或輸入地址", 20 | "noExchange": "找不到交易所", 21 | "exchangeRate": "匯率", 22 | "enterValueCont": "輸入 {{ missingCurrencyValue }} 以繼續。", 23 | "selectTokenCont": "選擇代幣以繼續。", 24 | "noLiquidity": "沒有流動性資金。", 25 | "unlockTokenCont": "解鎖代幣以繼續。", 26 | "transactionDetails": "交易明細", 27 | "hideDetails": "隱藏明細", 28 | "youAreSelling": "你正在出售", 29 | "orTransFail": "或交易失敗。", 30 | "youWillReceive": "你將至少收到", 31 | "youAreBuying": "你正在購買", 32 | "itWillCost": "這將花費至多", 33 | "insufficientBalance": "餘額不足", 34 | "inputNotValid": "無效的輸入值", 35 | "differentToken": "必須是不同的代幣。", 36 | "noRecipient": "請輸入收款人錢包地址。", 37 | "invalidRecipient": "請輸入有效的錢包地址。", 38 | "recipientAddress": "收款人錢包地址", 39 | "youAreSending": "你正在發送", 40 | "willReceive": "將至少收到", 41 | "to": "至", 42 | "addLiquidity": "增加流動性資金", 43 | "deposit": "存入", 44 | "currentPoolSize": "目前的資金池總量", 45 | "yourPoolShare": "你在資金池中的佔比", 46 | "noZero": "金額不能為零。", 47 | "mustBeETH": "輸入中必須包含 ETH。", 48 | "enterCurrencyOrLabelCont": "輸入 {{ inputCurrency }} 或 {{ label }} 以繼續。", 49 | "youAreAdding": "你將把", 50 | "and": "和", 51 | "intoPool": "加入資金池。", 52 | "outPool": "領出資金池。", 53 | "youWillMint": "你將產生", 54 | "liquidityTokens": "流動性代幣。", 55 | "totalSupplyIs": "目前流動性代幣供給總量為", 56 | "youAreSettingExRate": "初始的匯率將被設定為", 57 | "totalSupplyIs0": "目前流動性代幣供給為零。", 58 | "tokenWorth": "依據目前的匯率,每個流動性代幣價值", 59 | "firstLiquidity": "您是第一個提供流動性資金的人!", 60 | "initialExchangeRate": "初始的匯率將取決於你存入的資金。請確保存入的 ETH 和 {{ label }} 的價值相等。", 61 | "removeLiquidity": "領出流動性資金", 62 | "poolTokens": "資金池代幣", 63 | "enterLabelCont": "輸入 {{ label }} 以繼續。", 64 | "youAreRemoving": "您正在移除", 65 | "youWillRemove": "您即將移除", 66 | "createExchange": "創建交易所", 67 | "invalidTokenAddress": "無效的代幣地址", 68 | "exchangeExists": "{{ label }} 的交易所已經存在!", 69 | "invalidSymbol": "代幣符號錯誤", 70 | "invalidDecimals": "小數位數錯誤", 71 | "tokenAddress": "代幣地址", 72 | "label": "代幣符號", 73 | "decimals": "小數位數", 74 | "enterTokenCont": "輸入代幣地址", 75 | "rate": "Rate", 76 | "place": "Place", 77 | "placeAbove": "Order {{ rateDelta }}% above market", 78 | "placeBelow": "Order {{ rateDelta }}% below market" 79 | } 80 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Uniswap", 3 | "name": "Uniswap Exchange", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/InjectedConnector.js: -------------------------------------------------------------------------------- 1 | import { Connectors } from 'web3-react' 2 | const { Connector, ErrorCodeMixin } = Connectors 3 | 4 | const InjectedConnectorErrorCodes = ['ETHEREUM_ACCESS_DENIED', 'NO_WEB3', 'UNLOCK_REQUIRED'] 5 | export default class InjectedConnector extends ErrorCodeMixin(Connector, InjectedConnectorErrorCodes) { 6 | constructor(args = {}) { 7 | super(args) 8 | this.runOnDeactivation = [] 9 | 10 | this.networkChangedHandler = this.networkChangedHandler.bind(this) 11 | this.accountsChangedHandler = this.accountsChangedHandler.bind(this) 12 | 13 | const { ethereum } = window 14 | if (ethereum && ethereum.isMetaMask) { 15 | ethereum.autoRefreshOnNetworkChange = false 16 | } 17 | } 18 | 19 | async onActivation() { 20 | const { ethereum, web3 } = window 21 | 22 | if (ethereum) { 23 | await ethereum.enable().catch(error => { 24 | const deniedAccessError = Error(error) 25 | deniedAccessError.code = InjectedConnector.errorCodes.ETHEREUM_ACCESS_DENIED 26 | throw deniedAccessError 27 | }) 28 | 29 | // initialize event listeners 30 | if (ethereum.on) { 31 | ethereum.on('networkChanged', this.networkChangedHandler) 32 | ethereum.on('accountsChanged', this.accountsChangedHandler) 33 | 34 | this.runOnDeactivation.push(() => { 35 | if (ethereum.removeListener) { 36 | ethereum.removeListener('networkChanged', this.networkChangedHandler) 37 | ethereum.removeListener('accountsChanged', this.accountsChangedHandler) 38 | } 39 | }) 40 | } 41 | } else if (web3) { 42 | console.warn('Your web3 provider is outdated, please upgrade to a modern provider.') 43 | } else { 44 | const noWeb3Error = Error('Your browser is not equipped with web3 capabilities.') 45 | noWeb3Error.code = InjectedConnector.errorCodes.NO_WEB3 46 | throw noWeb3Error 47 | } 48 | } 49 | 50 | async getProvider() { 51 | const { ethereum, web3 } = window 52 | return ethereum || web3.currentProvider 53 | } 54 | 55 | async getAccount(provider) { 56 | const account = await super.getAccount(provider) 57 | 58 | if (account === null) { 59 | const unlockRequiredError = Error('Ethereum account locked.') 60 | unlockRequiredError.code = InjectedConnector.errorCodes.UNLOCK_REQUIRED 61 | throw unlockRequiredError 62 | } 63 | 64 | return account 65 | } 66 | 67 | onDeactivation() { 68 | this.runOnDeactivation.forEach(runner => runner()) 69 | this.runOnDeactivation = [] 70 | } 71 | 72 | // event handlers 73 | networkChangedHandler(chainId) { 74 | const chainIdNumber = Number(chainId) 75 | 76 | try { 77 | super._validatechainId(chainIdNumber) 78 | 79 | super._web3ReactUpdateHandler({ 80 | updatechainId: true, 81 | chainId: chainIdNumber 82 | }) 83 | } catch (error) { 84 | super._web3ReactErrorHandler(error) 85 | } 86 | } 87 | 88 | accountsChangedHandler(accounts) { 89 | if (!accounts[0]) { 90 | const unlockRequiredError = Error('Ethereum account locked.') 91 | unlockRequiredError.code = InjectedConnector.errorCodes.UNLOCK_REQUIRED 92 | super._web3ReactErrorHandler(unlockRequiredError) 93 | } else { 94 | super._web3ReactUpdateHandler({ 95 | updateAccount: true, 96 | account: accounts[0] 97 | }) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/assets/images/arrow-down-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/arrow-down-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/arrow-right-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pine-finance/pine-interface/4738acd306822c356b1e32981dc77b26b18539b7/src/assets/images/arrow-right-white.png -------------------------------------------------------------------------------- /src/assets/images/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/blue-loader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/circle-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/dropdown-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/dropup-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/ethereum-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pine-finance/pine-interface/4738acd306822c356b1e32981dc77b26b18539b7/src/assets/images/ethereum-logo.png -------------------------------------------------------------------------------- /src/assets/images/ethereum-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/images/fortmaticIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pine-finance/pine-interface/4738acd306822c356b1e32981dc77b26b18539b7/src/assets/images/fortmaticIcon.png -------------------------------------------------------------------------------- /src/assets/images/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/images/metamask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pine-finance/pine-interface/4738acd306822c356b1e32981dc77b26b18539b7/src/assets/images/metamask.png -------------------------------------------------------------------------------- /src/assets/images/plus-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/plus-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/portisIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pine-finance/pine-interface/4738acd306822c356b1e32981dc77b26b18539b7/src/assets/images/portisIcon.png -------------------------------------------------------------------------------- /src/assets/images/question-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/question.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/images/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/images/trustWallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pine-finance/pine-interface/4738acd306822c356b1e32981dc77b26b18539b7/src/assets/images/trustWallet.png -------------------------------------------------------------------------------- /src/assets/images/x.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/QR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/SVGArrowDown.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SVGArrowDown = props => ( 4 | 5 | 9 | 10 | ) 11 | 12 | export default SVGArrowDown 13 | -------------------------------------------------------------------------------- /src/assets/svg/SVGClose.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SVGDiv = props => ( 4 | 5 | 6 | 7 | 8 | ) 9 | 10 | export default SVGDiv 11 | -------------------------------------------------------------------------------- /src/assets/svg/SVGDiscord.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SVGDiscord = () => ( 4 | 5 | 6 | 7 | 8 | 9 | ) 10 | 11 | export default SVGDiscord 12 | -------------------------------------------------------------------------------- /src/assets/svg/SVGDiv.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SVGClose = props => ( 4 | 5 | 6 | 7 | ) 8 | 9 | export default SVGClose 10 | -------------------------------------------------------------------------------- /src/assets/svg/SVGTelegram.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SVGTelegram = () => ( 4 | 5 | 6 | 7 | ) 8 | 9 | export default SVGTelegram 10 | -------------------------------------------------------------------------------- /src/assets/svg/lightcircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Path 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/AccountDetails/Copy.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { useCopyClipboard } from '../../hooks' 4 | import { Link } from '../../theme' 5 | 6 | import { CheckCircle, Copy } from 'react-feather' 7 | 8 | const CopyIcon = styled(Link)` 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.text2}; 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) { 29 | const [isCopied, setCopied] = useCopyClipboard() 30 | 31 | return ( 32 | setCopied(props.toCopy)}> 33 | {isCopied ? ( 34 | 35 | 36 | Copied 37 | 38 | ) : ( 39 | 40 | 41 | 42 | )} 43 | {isCopied ? '' : props.children} 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/components/AccountDetails/Transaction.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { CheckCircle, Triangle } from 'react-feather' 4 | 5 | import { useActiveWeb3React } from '../../hooks' 6 | import { getEtherscanLink } from '../../utils' 7 | // import { useAllTransactions } from '../../state/transactions/hooks' 8 | // mport { RowFixed } from '../Row' 9 | import Loader from '../Loader' 10 | import { Link } from '../../theme' 11 | 12 | const TransactionWrapper = styled.div`` 13 | 14 | const TransactionStatusText = styled.div` 15 | margin-right: 0.5rem; 16 | display: flex; 17 | align-items: center; 18 | :hover { 19 | text-decoration: underline; 20 | } 21 | ` 22 | 23 | const TransactionState = styled(Link)` 24 | display: flex; 25 | justify-content: space-between; 26 | align-items: center; 27 | text-decoration: none !important; 28 | border-radius: 0.5rem; 29 | padding: 0.25rem 0rem; 30 | font-weight: 500; 31 | font-size: 0.825rem; 32 | color: ${({ theme }) => theme.primary1}; 33 | ` 34 | 35 | const IconWrapper = styled.div` 36 | color: ${({ pending, success, theme }) => (pending ? theme.primary1 : success ? theme.green1 : theme.red1)}; 37 | ` 38 | 39 | export default function Transaction({ hash }) { 40 | const { chainId } = useActiveWeb3React() 41 | // const allTransactions = useAllTransactions() 42 | 43 | // const summary = allTransactions[hash].summary 44 | // const pending = !allTransactions[hash].receipt 45 | // const success = 46 | // !pending && 47 | // (allTransactions[hash].receipt.status === 1 || typeof allTransactions[hash].receipt.status === 'undefined') 48 | 49 | return ( 50 | 51 | {/* 52 | 53 | {summary ? summary : hash} ↗ 54 | 55 | 56 | {pending ? : success ? : } 57 | 58 | */} 59 | 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/components/AddressInputPanel/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import styled from 'styled-components' 3 | import { useTranslation } from 'react-i18next' 4 | import { useWeb3React } from '@web3-react/core' 5 | import { transparentize } from 'polished' 6 | 7 | import { isAddress } from '../../utils' 8 | import { useDebounce } from '../../hooks' 9 | 10 | const InputPanel = styled.div` 11 | ${({ theme }) => theme.flexColumnNoWrap} 12 | box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)}; 13 | position: relative; 14 | border-radius: 1.25rem; 15 | background-color: ${({ theme }) => theme.inputBackground}; 16 | z-index: 1; 17 | ` 18 | 19 | const ContainerRow = styled.div` 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | border-radius: 1.25rem; 24 | border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)}; 25 | 26 | background-color: ${({ theme }) => theme.inputBackground}; 27 | ` 28 | 29 | const InputContainer = styled.div` 30 | flex: 1; 31 | ` 32 | 33 | const LabelRow = styled.div` 34 | ${({ theme }) => theme.flexRowNoWrap} 35 | align-items: center; 36 | color: ${({ theme }) => theme.doveGray}; 37 | font-size: 0.75rem; 38 | line-height: 1rem; 39 | padding: 0.75rem 1rem; 40 | ` 41 | 42 | const LabelContainer = styled.div` 43 | flex: 1 1 auto; 44 | width: 0; 45 | overflow: hidden; 46 | white-space: nowrap; 47 | text-overflow: ellipsis; 48 | ` 49 | 50 | const InputRow = styled.div` 51 | ${({ theme }) => theme.flexRowNoWrap} 52 | align-items: center; 53 | padding: 0.25rem 0.85rem 0.75rem; 54 | ` 55 | 56 | const Input = styled.input` 57 | font-size: 1rem; 58 | outline: none; 59 | border: none; 60 | flex: 1 1 auto; 61 | width: 0; 62 | background-color: ${({ theme }) => theme.inputBackground}; 63 | 64 | color: ${({ error, theme }) => (error ? theme.salmonRed : theme.royalGreen)}; 65 | overflow: hidden; 66 | text-overflow: ellipsis; 67 | 68 | ::placeholder { 69 | color: ${({ theme }) => theme.placeholderGray}; 70 | } 71 | ` 72 | 73 | export default function AddressInputPanel({ title, initialInput = '', onChange = () => {}, onError = () => {} }) { 74 | const { t } = useTranslation() 75 | 76 | const { library } = useWeb3React() 77 | 78 | const [input, setInput] = useState(initialInput) 79 | const debouncedInput = useDebounce(input, 150) 80 | 81 | const [data, setData] = useState({ address: undefined, name: undefined }) 82 | const [error, setError] = useState(false) 83 | 84 | // keep data and errors in sync 85 | useEffect(() => { 86 | onChange({ address: data.address, name: data.name }) 87 | }, [onChange, data.address, data.name]) 88 | useEffect(() => { 89 | onError(error) 90 | }, [onError, error]) 91 | 92 | // run parser on debounced input 93 | useEffect(() => { 94 | let stale = false 95 | 96 | if (isAddress(debouncedInput)) { 97 | try { 98 | library 99 | .lookupAddress(debouncedInput) 100 | .then(name => { 101 | if (!stale) { 102 | // if an ENS name exists, set it as the destination 103 | if (name) { 104 | setInput(name) 105 | } else { 106 | setData({ address: debouncedInput, name: '' }) 107 | setError(null) 108 | } 109 | } 110 | }) 111 | .catch(() => { 112 | if (!stale) { 113 | setData({ address: debouncedInput, name: '' }) 114 | setError(null) 115 | } 116 | }) 117 | } catch { 118 | setData({ address: debouncedInput, name: '' }) 119 | setError(null) 120 | } 121 | } else { 122 | if (debouncedInput !== '') { 123 | try { 124 | library 125 | .resolveName(debouncedInput) 126 | .then(address => { 127 | if (!stale) { 128 | // if the debounced input name resolves to an address 129 | if (address) { 130 | setData({ address: address, name: debouncedInput }) 131 | setError(null) 132 | } else { 133 | setError(true) 134 | } 135 | } 136 | }) 137 | .catch(() => { 138 | if (!stale) { 139 | setError(true) 140 | } 141 | }) 142 | } catch { 143 | setError(true) 144 | } 145 | } 146 | } 147 | 148 | return () => { 149 | stale = true 150 | } 151 | }, [debouncedInput, library, onChange, onError]) 152 | 153 | function onInput(event) { 154 | if (data.address !== undefined || data.name !== undefined) { 155 | setData({ address: undefined, name: undefined }) 156 | } 157 | if (error !== undefined) { 158 | setError() 159 | } 160 | const input = event.target.value 161 | const checksummedInput = isAddress(input) 162 | setInput(checksummedInput || input) 163 | } 164 | 165 | return ( 166 | 167 | 168 | 169 | 170 | 171 | {title || t('recipientAddress')} 172 | 173 | 174 | 175 | 186 | 187 | 188 | 189 | 190 | ) 191 | } 192 | -------------------------------------------------------------------------------- /src/components/ContextualInfo/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg' 6 | import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg' 7 | 8 | const SummaryWrapper = styled.div` 9 | color: ${({ error, theme }) => (error ? theme.salmonRed : theme.doveGray)}; 10 | font-size: 0.75rem; 11 | text-align: center; 12 | margin-top: 1rem; 13 | padding-top: 1rem; 14 | ` 15 | 16 | const Details = styled.div` 17 | background-color: ${({ theme }) => theme.concreteGray}; 18 | padding: 1.5rem; 19 | border-radius: 1rem; 20 | font-size: 0.75rem; 21 | margin-top: 1rem; 22 | ` 23 | 24 | const SummaryWrapperContainer = styled.div` 25 | ${({ theme }) => theme.flexRowNoWrap}; 26 | color: ${({ theme }) => theme.royalGreen}; 27 | text-align: center; 28 | margin-top: 1rem; 29 | padding-top: 1rem; 30 | cursor: pointer; 31 | align-items: center; 32 | justify-content: center; 33 | font-size: 0.75rem; 34 | 35 | span { 36 | margin-right: 12px; 37 | } 38 | 39 | img { 40 | height: 0.75rem; 41 | width: 0.75rem; 42 | } 43 | ` 44 | 45 | const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => 46 | const ColoredDropup = styled(WrappedDropup)` 47 | path { 48 | stroke: ${({ theme }) => theme.royalGreen}; 49 | } 50 | ` 51 | 52 | const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => 53 | const ColoredDropdown = styled(WrappedDropdown)` 54 | path { 55 | stroke: ${({ theme }) => theme.royalGreen}; 56 | } 57 | ` 58 | 59 | class ContextualInfo extends Component { 60 | static propTypes = { 61 | openDetailsText: PropTypes.string, 62 | renderTransactionDetails: PropTypes.func, 63 | contextualInfo: PropTypes.string, 64 | isError: PropTypes.bool 65 | } 66 | 67 | static defaultProps = { 68 | openDetailsText: 'Advanced Details', 69 | closeDetailsText: 'Hide Advanced', 70 | renderTransactionDetails() {}, 71 | contextualInfo: '', 72 | isError: false 73 | } 74 | 75 | state = { 76 | showDetails: false 77 | } 78 | 79 | renderDetails() { 80 | if (!this.state.showDetails) { 81 | return null 82 | } 83 | 84 | return
{this.props.renderTransactionDetails()}
85 | } 86 | 87 | render() { 88 | const { openDetailsText, closeDetailsText, contextualInfo, isError } = this.props 89 | 90 | if (contextualInfo) { 91 | return {contextualInfo} 92 | } 93 | 94 | return ( 95 | <> 96 | 98 | this.setState(prevState => { 99 | return { showDetails: !prevState.showDetails } 100 | }) 101 | } 102 | > 103 | {!this.state.showDetails ? ( 104 | <> 105 | {openDetailsText} 106 | 107 | 108 | ) : ( 109 | <> 110 | {closeDetailsText} 111 | 112 | 113 | )} 114 | 115 | {this.renderDetails()} 116 | 117 | ) 118 | } 119 | } 120 | 121 | export default ContextualInfo 122 | -------------------------------------------------------------------------------- /src/components/ContextualInfoNew/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled, { css } from 'styled-components' 3 | import { transparentize } from 'polished' 4 | 5 | import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg' 6 | import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg' 7 | 8 | const SummaryWrapper = styled.div` 9 | color: ${({ error, theme }) => (error ? theme.salmonRed : theme.doveGray)}; 10 | font-size: 0.75rem; 11 | text-align: center; 12 | margin-top: 1rem; 13 | padding-top: 1rem; 14 | ` 15 | 16 | const SummaryWrapperContainer = styled.div` 17 | ${({ theme }) => theme.flexRowNoWrap}; 18 | color: ${({ theme }) => theme.royalGreen}; 19 | text-align: center; 20 | margin-top: 1rem; 21 | padding-top: 1rem; 22 | cursor: pointer; 23 | align-items: center; 24 | justify-content: center; 25 | font-size: 0.75rem; 26 | 27 | img { 28 | height: 0.75rem; 29 | width: 0.75rem; 30 | } 31 | ` 32 | 33 | const Details = styled.div` 34 | background-color: ${({ theme }) => theme.concreteGray}; 35 | /* padding: 1.25rem 1.25rem 1rem 1.25rem; */ 36 | border-radius: 1rem; 37 | font-size: 0.75rem; 38 | margin: 1rem 0.5rem 0 0.5rem; 39 | ` 40 | 41 | const ErrorSpan = styled.span` 42 | margin-right: 12px; 43 | font-size: 0.75rem; 44 | line-height: 0.75rem; 45 | 46 | color: ${({ isError, theme }) => isError && theme.salmonRed}; 47 | ${({ slippageWarning, highSlippageWarning, theme }) => 48 | highSlippageWarning 49 | ? css` 50 | color: ${theme.salmonRed}; 51 | font-weight: 600; 52 | ` 53 | : slippageWarning && 54 | css` 55 | background-color: ${transparentize(0.6, theme.warningYellow)}; 56 | font-weight: 600; 57 | padding: 0.25rem; 58 | `} 59 | ` 60 | 61 | const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => 62 | const ColoredDropup = styled(WrappedDropup)` 63 | path { 64 | stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalGreen)}; 65 | 66 | ${({ highSlippageWarning, theme }) => 67 | highSlippageWarning && 68 | css` 69 | stroke: ${theme.salmonRed}; 70 | `} 71 | } 72 | ` 73 | 74 | const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => 75 | const ColoredDropdown = styled(WrappedDropdown)` 76 | path { 77 | stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalGreen)}; 78 | 79 | ${({ highSlippageWarning, theme }) => 80 | highSlippageWarning && 81 | css` 82 | stroke: ${theme.salmonRed}; 83 | `} 84 | } 85 | ` 86 | 87 | export default function ContextualInfo({ 88 | openDetailsText = 'Advanced Details', 89 | closeDetailsText = 'Hide Advanced', 90 | contextualInfo = '', 91 | allowExpand = false, 92 | isError = false, 93 | slippageWarning, 94 | highSlippageWarning, 95 | dropDownContent 96 | }) { 97 | const [showDetails, setShowDetails] = useState(false) 98 | return !allowExpand ? ( 99 | {contextualInfo} 100 | ) : ( 101 | <> 102 | setShowDetails(s => !s)}> 103 | <> 104 | 105 | {(slippageWarning || highSlippageWarning) && ( 106 | 107 | ⚠️ 108 | 109 | )} 110 | {contextualInfo ? contextualInfo : showDetails ? closeDetailsText : openDetailsText} 111 | 112 | {showDetails ? ( 113 | 114 | ) : ( 115 | 116 | )} 117 | 118 | 119 | {showDetails &&
{dropDownContent()}
} 120 | 121 | ) 122 | } 123 | -------------------------------------------------------------------------------- /src/components/ExchangePage/ExchangePage.css: -------------------------------------------------------------------------------- 1 | .market-delta-info, 2 | .slippage-warning, 3 | .fee-error { 4 | text-align: center; 5 | color: inherit; 6 | margin-top: -25px; 7 | color: #f96600; 8 | font-size: 14px; 9 | align-items: center; 10 | display: -webkit-box; 11 | display: -webkit-flex; 12 | display: -ms-flexbox; 13 | display: flex; 14 | -webkit-box-pack: center; 15 | -webkit-justify-content: center; 16 | -ms-flex-pack: center; 17 | justify-content: center; 18 | padding: 1rem; 19 | } 20 | 21 | .market-delta-info { 22 | color: #01796f; 23 | } 24 | 25 | .fee-error { 26 | color: #ff6871; 27 | } 28 | 29 | .slippage-warning span { 30 | margin-right: 5px; 31 | } 32 | 33 | .orders-title { 34 | font-size: 24px; 35 | font-weight: 400; 36 | } 37 | 38 | .order p { 39 | font-size: 14px; 40 | color: #808080; 41 | margin: 3px 0; 42 | display: flex; 43 | align-content: center; 44 | } 45 | 46 | .order p img, 47 | .order p svg, 48 | .fee-error svg { 49 | margin-left: 3px; 50 | } 51 | 52 | .tokens { 53 | margin-bottom: 15px; 54 | display: flex; 55 | align-items: center; 56 | } 57 | 58 | .tokens > span { 59 | width: 30px; 60 | margin: 0; 61 | display: flex; 62 | align-items: center; 63 | justify-content: center; 64 | } 65 | 66 | .order .cta { 67 | max-width: 180px; 68 | margin: 15px auto 0; 69 | width: 35%; 70 | padding: 0.7rem 1.7rem 0.7rem 1.7rem; 71 | } 72 | 73 | a { 74 | color: #87c122 75 | } -------------------------------------------------------------------------------- /src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { darken } from 'polished' 4 | 5 | import { Link } from '../../theme' 6 | import SVGDiscord from '../../assets/svg/SVGDiscord' 7 | import SVGTelegram from '../../assets/svg/SVGTelegram' 8 | 9 | const FooterFrame = styled.div` 10 | display: flex; 11 | align-items: center; 12 | justify-content: space-between; 13 | width: 100%; 14 | ` 15 | 16 | const FooterElement = styled.div` 17 | margin: 1.25rem; 18 | display: flex; 19 | min-width: 0; 20 | display: flex; 21 | align-items: center; 22 | ` 23 | 24 | const Title = styled.div` 25 | display: flex; 26 | align-items: center; 27 | color: ${({ theme }) => theme.uniswapPink}; 28 | 29 | :hover { 30 | cursor: pointer; 31 | } 32 | #link { 33 | text-decoration-color: ${({ theme }) => theme.uniswapPink}; 34 | } 35 | 36 | #title { 37 | display: inline; 38 | font-size: 0.825rem; 39 | margin-right: 12px; 40 | font-weight: 400; 41 | color: ${({ theme }) => theme.uniswapPink}; 42 | :hover { 43 | color: ${({ theme }) => darken(0.2, theme.uniswapPink)}; 44 | } 45 | } 46 | ` 47 | 48 | const DiscordImg = styled.div` 49 | height: 18px; 50 | 51 | svg { 52 | fill: ${({ theme }) => theme.uniswapPink}; 53 | height: 28px; 54 | } 55 | ` 56 | 57 | const TelegramImg = styled.div` 58 | height: 18px; 59 | margin-left: 5px; 60 | svg { 61 | fill: ${({ theme }) => theme.uniswapPink}; 62 | height: 22px; 63 | } 64 | ` 65 | 66 | export default function Footer() { 67 | return ( 68 | 69 | 70 | 71 | <Link 72 | id="link" 73 | rel="noopener noreferrer" 74 | target="_blank" 75 | href="https://medium.com/@pine_eth/pine-finance-an-amm-orders-engine-525fe1f1b1eb" 76 | > 77 | <h1 id="title">About</h1> 78 | </Link> 79 | <Link id="link" rel="noopener noreferrer" target="_blank" href="https://github.com/pine-finance/"> 80 | <h1 id="title">Code</h1> 81 | </Link> 82 | <Link id="link" rel="noopener noreferrer" target="_blank" href="https://etherscan.io/address/0xD412054ccA18A61278ceD6F674A526A6940eBd84"> 83 | <h1 id="title">Contract</h1> 84 | </Link> 85 | <Link id="link" rel="noopener noreferrer" target="_blank" href="https://v1.uniswapex.io/"> 86 | <h1 id="title">UniswapEx</h1> 87 | </Link> 88 | <Link id="link" rel="noopener noreferrer" target="_blank" href="https://gitcoin.co/grants/765/uniswapex-v2"> 89 | <h1 id="title">Donate ❤</h1> 90 | </Link> 91 | <Link id="link" rel="noopener noreferrer" target="_blank" href="https://discord.gg/w6JVcrg"> 92 | <DiscordImg> 93 | <SVGDiscord /> 94 | </DiscordImg> 95 | </Link> 96 | <Link id="link" rel="noopener noreferrer" target="_blank" href="https://t.me/UniswapEX"> 97 | <TelegramImg> 98 | <SVGTelegram /> 99 | </TelegramImg> 100 | </Link> 101 | 102 | 103 | 104 | ) 105 | } 106 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import { Link } from '../../theme' 5 | import Web3Status from '../Web3Status' 6 | import { darken } from 'polished' 7 | 8 | const HeaderFrame = styled.div` 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | width: 100%; 13 | ` 14 | 15 | const HeaderElement = styled.div` 16 | margin: 1.25rem; 17 | display: flex; 18 | min-width: 0; 19 | display: flex; 20 | align-items: center; 21 | ` 22 | 23 | const Nod = styled.span` 24 | transform: rotate(0deg); 25 | transition: transform 150ms ease-out; 26 | 27 | :hover { 28 | transform: rotate(-10deg); 29 | } 30 | ` 31 | 32 | const Title = styled.div` 33 | display: flex; 34 | align-items: center; 35 | 36 | :hover { 37 | cursor: pointer; 38 | } 39 | 40 | #link { 41 | text-decoration-color: ${({ theme }) => theme.UniswapPink}; 42 | } 43 | 44 | #title { 45 | display: inline; 46 | font-size: 1rem; 47 | font-weight: 500; 48 | color: ${({ theme }) => theme.wisteriaPurple}; 49 | :hover { 50 | color: ${({ theme }) => darken(0.1, theme.wisteriaPurple)}; 51 | } 52 | } 53 | ` 54 | 55 | export default function Header() { 56 | return ( 57 | 58 | 59 | 60 | <Nod> 61 | <Link id="link" href="https://pine.finance"> 62 | <span role="img" aria-label="pine"> 63 | 🌲{' '} 64 | </span> 65 | </Link> 66 | </Nod> 67 | <Link id="link" href="https://pine.finance"> 68 | <h1 id="title">Pine.finance</h1> 69 | </Link> 70 | 71 | 72 | 73 | 74 | 75 | 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Identicon/index.js: -------------------------------------------------------------------------------- 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 StyledIdenticon = 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 | return 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Loader/index.js: -------------------------------------------------------------------------------- 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` 15 | animation: 2s ${rotate} linear infinite; 16 | height: ${({ size }) => size}; 17 | width: ${({ size }) => size}; 18 | path { 19 | stroke: ${({ stroke, theme }) => (stroke ? 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({ size = '16px', stroke = null, ...rest }) { 28 | return ( 29 | 30 | 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Modal/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { css } from 'styled-components' 3 | import { animated, useTransition } from 'react-spring' 4 | import { DialogOverlay, DialogContent } from '@reach/dialog' 5 | import '@reach/dialog/styles.css' 6 | import { transparentize } from 'polished' 7 | 8 | const AnimatedDialogOverlay = animated(DialogOverlay) 9 | const WrappedDialogOverlay = ({ suppressClassNameWarning, ...rest }) => 10 | const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({ 11 | suppressClassNameWarning: true 12 | })` 13 | &[data-reach-dialog-overlay] { 14 | z-index: 2; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | background-color: ${({ theme }) => theme.modalBackground}; 19 | } 20 | ` 21 | 22 | const FilteredDialogContent = ({ minHeight, maxHeight, ...rest }) => 23 | const StyledDialogContent = styled(FilteredDialogContent)` 24 | &[data-reach-dialog-content] { 25 | margin: 0 0 2rem 0; 26 | border: 1px solid ${({ theme }) => theme.concreteGray}; 27 | background-color: ${({ theme }) => theme.inputBackground}; 28 | box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)}; 29 | ${({ theme }) => theme.mediaWidth.upToMedium`margin: 0;`}; 30 | padding: 0px; 31 | width: 50vw; 32 | max-width: 650px; 33 | ${({ theme }) => theme.mediaWidth.upToMedium`width: 65vw;`} 34 | ${({ theme }) => theme.mediaWidth.upToSmall`width: 85vw;`} 35 | ${({ maxHeight }) => 36 | maxHeight && 37 | css` 38 | max-height: ${maxHeight}vh; 39 | `} 40 | ${({ minHeight }) => 41 | minHeight && 42 | css` 43 | min-height: ${minHeight}vh; 44 | `} 45 | ${({ theme }) => theme.mediaWidth.upToMedium`max-height: 65vh;`} 46 | ${({ theme }) => theme.mediaWidth.upToSmall`max-height: 80vh;`} 47 | display: flex; 48 | overflow: hidden; 49 | border-radius: 10px; 50 | } 51 | ` 52 | 53 | const HiddenCloseButton = styled.button` 54 | margin: 0; 55 | padding: 0; 56 | width: 0; 57 | height: 0; 58 | border: none; 59 | ` 60 | 61 | export default function Modal({ isOpen, onDismiss, minHeight = false, initialFocusRef, children, maxHeight = 50 }) { 62 | const transitions = useTransition(isOpen, null, { 63 | config: { duration: 150 }, 64 | from: { opacity: 0 }, 65 | enter: { opacity: 1 }, 66 | leave: { opacity: 0 } 67 | }) 68 | 69 | return transitions.map( 70 | ({ item, key, props }) => 71 | item && ( 72 | 73 | 77 | 78 | ) 79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /src/components/NavigationTabs/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { withRouter } from 'react-router-dom' 3 | import { useTranslation } from 'react-i18next' 4 | import styled from 'styled-components' 5 | import { transparentize } from 'polished' 6 | 7 | import { Link } from '../../theme' 8 | import { useBodyKeyDown } from '../../hooks' 9 | import { useBetaMessageManager } from '../../contexts/LocalStorage' 10 | 11 | const tabOrder = [ 12 | { 13 | path: '/order', 14 | textKey: 'order', 15 | regex: /\/order/ 16 | } 17 | ] 18 | 19 | const BetaMessage = styled.div` 20 | ${({ theme }) => theme.flexRowNoWrap} 21 | cursor: pointer; 22 | flex: 1 0 auto; 23 | align-items: center; 24 | position: relative; 25 | padding: 0.5rem 1rem; 26 | padding-right: 2rem; 27 | margin-bottom: 1rem; 28 | border: 1px solid ${({ theme }) => transparentize(0.6, theme.wisteriaPurple)}; 29 | background-color: ${({ theme }) => transparentize(0.9, theme.wisteriaPurple)}; 30 | border-radius: 2rem; 31 | font-size: 0.75rem; 32 | line-height: 1rem; 33 | text-align: left; 34 | overflow: hidden; 35 | text-overflow: ellipsis; 36 | white-space: nowrap; 37 | color: ${({ theme }) => theme.wisteriaPurple}; 38 | 39 | &:after { 40 | content: '✕'; 41 | top: 0.5rem; 42 | right: 1rem; 43 | position: absolute; 44 | color: ${({ theme }) => theme.wisteriaPurple}; 45 | } 46 | 47 | .how-it-works { 48 | text-decoration: underline; 49 | margin-left: 5px; 50 | } 51 | ` 52 | 53 | function NavigationTabs({ location: { pathname }, history }) { 54 | const { t } = useTranslation() 55 | 56 | const [showBetaMessage, dismissBetaMessage] = useBetaMessageManager() 57 | 58 | const navigate = useCallback( 59 | direction => { 60 | const tabIndex = tabOrder.findIndex(({ regex }) => pathname.match(regex)) 61 | history.push(tabOrder[(tabIndex + tabOrder.length + direction) % tabOrder.length].path) 62 | }, 63 | [pathname, history] 64 | ) 65 | const navigateRight = useCallback(() => { 66 | navigate(1) 67 | }, [navigate]) 68 | const navigateLeft = useCallback(() => { 69 | navigate(-1) 70 | }, [navigate]) 71 | 72 | useBodyKeyDown('ArrowRight', navigateRight) 73 | useBodyKeyDown('ArrowLeft', navigateLeft) 74 | 75 | return ( 76 | <> 77 | {showBetaMessage && ( 78 | 79 | 80 | 💀 81 | {' '} 82 | {t('betaWarning')} 83 | 84 | {t('seeHowItWorks')} 85 | 86 | 87 | )} 88 | 89 | ) 90 | } 91 | 92 | export default withRouter(NavigationTabs) 93 | -------------------------------------------------------------------------------- /src/components/OrderCard/OrderCard.css: -------------------------------------------------------------------------------- 1 | .order-link { 2 | font-size: 14px; 3 | margin-top: 3px; 4 | } 5 | 6 | .order.executed .order-link { 7 | color: #01796f; 8 | } 9 | 10 | .order.cancelled .order-link { 11 | color: #ee4343; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/OrdersHistory/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { useWeb3React } from '@web3-react/core' 3 | 4 | import { PastOrderCard } from '../PastOrderCard' 5 | import { isAddress } from '../../utils' 6 | import { ORDER_GRAPH } from '../../constants' 7 | 8 | export function OrdersHistory() { 9 | const { account, chainId } = useWeb3React() 10 | const orders = usePastOrders(account, chainId) 11 | return orders.length > 0 ? ( 12 | <> 13 |

History

14 | {orders.map(order => ( 15 | 16 | ))} 17 | 18 | ) : null 19 | } 20 | 21 | function usePastOrders(account, chainId) { 22 | const [state, setState] = useState([]) 23 | 24 | useEffect(() => { 25 | if (account && isAddress(account)) { 26 | fetchUserPastOrders(account, chainId).then(orders => { 27 | setState(orders) 28 | }) 29 | } 30 | }, [account, chainId]) 31 | 32 | return state 33 | } 34 | 35 | async function fetchUserPastOrders(account, chainId) { 36 | const query = ` 37 | query GetOrdersByOwner($owner: String) { 38 | orders(where:{owner:$owner,status_not:open}) { 39 | id 40 | inputToken 41 | outputToken 42 | inputAmount 43 | minReturn 44 | bought 45 | status 46 | cancelledTxHash 47 | executedTxHash 48 | updatedAt 49 | } 50 | }` 51 | try { 52 | const res = await fetch(ORDER_GRAPH[chainId], { 53 | method: 'POST', 54 | headers: { 'Content-Type': 'application/json' }, 55 | body: JSON.stringify({ query, variables: { owner: account.toLowerCase() } }) 56 | }) 57 | 58 | const { data } = await res.json() 59 | return data.orders 60 | } catch (e) { 61 | console.warn('Error loading orders from TheGraph', e) 62 | return [] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/OversizedPanel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Panel = styled.div` 5 | position: relative; 6 | background-color: ${({ theme }) => theme.concreteGray}; 7 | width: calc(100% - 1rem); 8 | margin: 0 auto; 9 | border-radius: 0.625rem; 10 | ` 11 | 12 | const PanelTop = styled.div` 13 | content: ''; 14 | position: absolute; 15 | top: -0.5rem; 16 | left: 0; 17 | height: 1rem; 18 | width: 100%; 19 | background-color: ${({ theme }) => theme.concreteGray}; 20 | ` 21 | 22 | const PanelBottom = styled.div` 23 | position: absolute; 24 | top: 80%; 25 | left: 0; 26 | height: 1rem; 27 | width: 100%; 28 | background-color: ${({ theme }) => theme.concreteGray}; 29 | ` 30 | 31 | export default function OversizedPanel({ hideTop, hideBottom, children }) { 32 | return ( 33 | 34 | {hideTop || } 35 | {children} 36 | {hideBottom || } 37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/components/PastOrderCard/OrderCard.css: -------------------------------------------------------------------------------- 1 | .order-link { 2 | font-size: 14px; 3 | margin-top: 3px; 4 | } 5 | 6 | .order.executed .order-link { 7 | color: #01796f; 8 | } 9 | 10 | .order.cancelled .order-link { 11 | color: #ee4343; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/PastOrderCard/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ethers } from 'ethers' 3 | import styled from 'styled-components' 4 | import { useWeb3React } from '@web3-react/core' 5 | 6 | import { getEtherscanLink } from '../../utils' 7 | import { CurrencySelect, Aligner, StyledTokenName } from '../CurrencyInputPanel' 8 | import TokenLogo from '../TokenLogo' 9 | import ArrowDown from '../../assets/svg/SVGArrowDown' 10 | import { amountFormatter } from '../../utils' 11 | import { useTokenDetails } from '../../contexts/Tokens' 12 | import { ETH_ADDRESS } from '../../constants' 13 | 14 | import './OrderCard.css' 15 | 16 | const Order = styled.div` 17 | display: -webkit-box; 18 | display: -webkit-flex; 19 | display: -ms-flexbox; 20 | display: flex; 21 | -webkit-flex-flow: column nowrap; 22 | -ms-flex-flow: column nowrap; 23 | flex-flow: column nowrap; 24 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05); 25 | position: relative; 26 | border-radius: 1.25rem; 27 | z-index: 1; 28 | padding: 20px; 29 | margin-bottom: 40px; 30 | border: ${({ theme }) => `1px solid ${theme.mercuryGray}`}; 31 | background-color: ${({ theme }) => theme.concreteGray}; 32 | ` 33 | 34 | const Spacer = styled.div` 35 | flex: 1 1 auto; 36 | ` 37 | const WrappedArrowRight = ({ clickable, active, ...rest }) => 38 | 39 | const RightArrow = styled(WrappedArrowRight)` 40 | color: ${({ theme }) => theme.royalGreen}; 41 | width: 0.625rem; 42 | height: 0.625rem; 43 | position: relative; 44 | ` 45 | 46 | export function PastOrderCard(props) { 47 | const { chainId } = useWeb3React() 48 | 49 | const order = props.data 50 | 51 | const inputToken = order.inputToken === ETH_ADDRESS.toLowerCase() ? 'ETH' : ethers.utils.getAddress(order.inputToken) 52 | const outputToken = 53 | order.outputToken === ETH_ADDRESS.toLowerCase() ? 'ETH' : ethers.utils.getAddress(order.outputToken) 54 | 55 | const { symbol: fromSymbol, decimals: fromDecimals } = useTokenDetails(inputToken) 56 | const { symbol: toSymbol, decimals: toDecimals } = useTokenDetails(outputToken) 57 | 58 | const cancelled = order.status === 'cancelled' 59 | const executed = order.status === 'executed' 60 | const bought = ethers.utils.bigNumberify(executed ? order.bought : 0) 61 | const inputAmount = ethers.utils.bigNumberify(order.inputAmount) 62 | const minReturn = ethers.utils.bigNumberify(order.minReturn) 63 | 64 | const explorerLink = getEtherscanLink( 65 | chainId, 66 | cancelled ? order.cancelledTxHash : order.executedTxHash, 67 | 'transaction' 68 | ) 69 | 70 | return ( 71 | 72 |
73 | 74 | 75 | {} 76 | {{fromSymbol}} 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {} 85 | {{toSymbol}} 86 | 87 | 88 | 89 |
90 | {executed && ( 91 | <> 92 |

93 | {`Sold: ${amountFormatter(inputAmount, fromDecimals, 6)}`} {fromSymbol} 94 |

95 |

96 | {`Expected: ${amountFormatter(minReturn, toDecimals, 6)}`} {toSymbol} 97 |

98 |

99 | {`Received: ${amountFormatter(bought, toDecimals, 6)}`} {toSymbol} 100 |

101 |

{`Date: ${new Date(order.updatedAt * 1000).toLocaleDateString()}`}

102 | 103 | Executed 104 | 105 | 106 | )} 107 | {cancelled && ( 108 | <> 109 |

110 | {`Sell: ${amountFormatter(inputAmount, fromDecimals, 6)}`} {fromSymbol} 111 |

112 |

113 | {`Expected: ${amountFormatter(minReturn, toDecimals, 6)}`} {toSymbol} 114 |

115 |

{`Date: ${new Date(order.updatedAt * 1000).toLocaleDateString()}`}

116 | 117 | Cancelled 118 | 119 | 120 | )} 121 |
122 | ) 123 | } 124 | -------------------------------------------------------------------------------- /src/components/TokenLogo/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { isAddress } from '../../utils' 4 | 5 | import { ReactComponent as EthereumLogo } from '../../assets/images/ethereum-logo.svg' 6 | 7 | const TOKEN_ICON_API = address => 8 | `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${isAddress( 9 | address 10 | )}/logo.png` 11 | const BAD_IMAGES = {} 12 | 13 | const Image = styled.img` 14 | width: ${({ size }) => size}; 15 | height: ${({ size }) => size}; 16 | background-color: white; 17 | border-radius: 1rem; 18 | ` 19 | 20 | const Emoji = styled.span` 21 | width: ${({ size }) => size}; 22 | height: ${({ size }) => size}; 23 | ` 24 | 25 | const StyledEthereumLogo = styled(EthereumLogo)` 26 | width: ${({ size }) => size}; 27 | height: ${({ size }) => size}; 28 | ` 29 | 30 | export default function TokenLogo({ address, size = '1rem', ...rest }) { 31 | const [error, setError] = useState(false) 32 | 33 | let path = '' 34 | if (address === 'ETH') { 35 | return 36 | } else if (!error && !BAD_IMAGES[address]) { 37 | path = TOKEN_ICON_API(address.toLowerCase()) 38 | } else { 39 | return ( 40 | 41 | 42 | 🌕 43 | 44 | 45 | ) 46 | } 47 | 48 | return ( 49 | {address} { 55 | BAD_IMAGES[address] = true 56 | setError(true) 57 | }} 58 | /> 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /src/components/WalletModal/Copy.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { useCopyClipboard } from '../../hooks' 4 | 5 | import { Link } from '../../theme' 6 | import { CheckCircle, Copy } from 'react-feather' 7 | 8 | const CopyIcon = styled(Link)` 9 | color: ${({ theme }) => theme.silverGray}; 10 | flex-shrink: 0; 11 | margin-right: 1rem; 12 | margin-left: 0.5rem; 13 | text-decoration: none; 14 | :hover, 15 | :active, 16 | :focus { 17 | text-decoration: none; 18 | color: ${({ theme }) => theme.doveGray}; 19 | } 20 | ` 21 | const TransactionStatusText = styled.span` 22 | margin-left: 0.25rem; 23 | ${({ theme }) => theme.flexRowNoWrap}; 24 | align-items: center; 25 | ` 26 | 27 | export default function CopyHelper({ toCopy }) { 28 | const [isCopied, setCopied] = useCopyClipboard() 29 | 30 | return ( 31 | setCopied(toCopy)}> 32 | {isCopied ? ( 33 | 34 | 35 | Copied 36 | 37 | ) : ( 38 | 39 | 40 | 41 | )} 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/components/WalletModal/Info.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { useWeb3React } from '@web3-react/core' 4 | 5 | import { getEtherscanLink } from '../../utils' 6 | import { Link, Spinner } from '../../theme' 7 | import Copy from './Copy' 8 | 9 | import { Check } from 'react-feather' 10 | import Circle from '../../assets/images/circle.svg' 11 | 12 | import { transparentize } from 'polished' 13 | 14 | const TransactionStatusWrapper = styled.div` 15 | display: flex; 16 | align-items: center; 17 | min-width: 12px; 18 | white-space: nowrap; 19 | overflow: hidden; 20 | text-overflow: ellipsis; 21 | white-space: nowrap; 22 | ` 23 | 24 | const TransactionWrapper = styled.div` 25 | ${({ theme }) => theme.flexRowNoWrap} 26 | justify-content: space-between; 27 | width: 100%; 28 | margin-top: 0.75rem; 29 | a { 30 | /* flex: 1 1 auto; */ 31 | overflow: hidden; 32 | text-overflow: ellipsis; 33 | white-space: nowrap; 34 | min-width: 0; 35 | max-width: 250px; 36 | } 37 | ` 38 | 39 | const TransactionStatusText = styled.span` 40 | margin-left: 0.25rem; 41 | ` 42 | 43 | const TransactionState = styled.div` 44 | background-color: ${({ pending, theme }) => 45 | pending ? transparentize(0.95, theme.royalGreen) : transparentize(0.95, theme.connectedGreen)}; 46 | border-radius: 1.5rem; 47 | padding: 0.5rem 0.75rem; 48 | font-weight: 500; 49 | font-size: 0.75rem; 50 | border: 1px solid; 51 | border-color: ${({ pending, theme }) => 52 | pending ? transparentize(0.75, theme.royalGreen) : transparentize(0.75, theme.connectedGreen)}; 53 | 54 | :hover { 55 | border-color: ${({ pending, theme }) => 56 | pending ? transparentize(0, theme.royalGreen) : transparentize(0, theme.connectedGreen)}; 57 | } 58 | ` 59 | 60 | const ButtonWrapper = styled.div` 61 | a { 62 | color: ${({ pending, theme }) => (pending ? theme.royalGreen : theme.connectedGreen)}; 63 | } 64 | ` 65 | 66 | export default function Info({ hash, pending }) { 67 | const { chainId } = useWeb3React() 68 | 69 | return ( 70 | 71 | 72 | {hash} ↗ 73 | 74 | 75 | {pending ? ( 76 | 77 | 78 | 79 | 80 | Pending 81 | 82 | 83 | 84 | ) : ( 85 | 86 | 87 | 88 | 89 | Confirmed 90 | 91 | 92 | 93 | )} 94 | 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /src/components/WalletModal/Option.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import { Link } from '../../theme' 5 | 6 | const InfoCard = styled.button` 7 | background-color: ${({ theme, active }) => (active ? theme.bg3 : theme.bg2)}; 8 | padding: 1rem; 9 | outline: none; 10 | border: 1px solid; 11 | border-radius: 12px; 12 | width: 100% !important; 13 | &:focus { 14 | box-shadow: 0 0 0 1px ${({ theme }) => theme.primary1}; 15 | } 16 | border-color: ${({ theme, active }) => (active ? 'transparent' : theme.bg3)}; 17 | ` 18 | 19 | const OptionCard = styled(InfoCard)` 20 | display: flex; 21 | flex-direction: row; 22 | align-items: center; 23 | justify-content: space-between; 24 | margin-top: 2rem; 25 | padding: 1rem; 26 | ` 27 | 28 | const OptionCardLeft = styled.div` 29 | ${({ theme }) => theme.flexColumnNoWrap}; 30 | justify-content: center; 31 | height: 100%; 32 | ` 33 | 34 | const OptionCardClickable = styled(OptionCard)` 35 | margin-top: 0; 36 | &:hover { 37 | cursor: ${({ clickable }) => (clickable ? 'pointer' : '')}; 38 | border: yellow; 39 | } 40 | opacity: ${({ disabled }) => (disabled ? '0.5' : '1')}; 41 | ` 42 | 43 | const GreenCircle = styled.div` 44 | ${({ theme }) => theme.flexRowNoWrap} 45 | justify-content: center; 46 | align-items: center; 47 | 48 | &:first-child { 49 | height: 8px; 50 | width: 8px; 51 | margin-right: 8px; 52 | background-color: ${({ theme }) => theme.green1}; 53 | border-radius: 50%; 54 | } 55 | ` 56 | 57 | const CircleWrapper = styled.div` 58 | color: ${({ theme }) => theme.green1}; 59 | display: flex; 60 | justify-content: center; 61 | align-items: center; 62 | ` 63 | 64 | const HeaderText = styled.div` 65 | ${({ theme }) => theme.flexRowNoWrap}; 66 | color: ${props => (props.color === 'blue' ? ({ theme }) => theme.primary1 : ({ theme }) => theme.text1)}; 67 | font-size: 1rem; 68 | font-weight: 500; 69 | ` 70 | 71 | const SubHeader = styled.div` 72 | color: ${({ theme }) => theme.text1}; 73 | margin-top: 10px; 74 | font-size: 12px; 75 | ` 76 | 77 | const IconWrapper = styled.div` 78 | ${({ theme }) => theme.flexColumnNoWrap}; 79 | align-items: center; 80 | justify-content: center; 81 | & > img, 82 | span { 83 | height: ${({ size }) => (size ? size + 'px' : '24px')}; 84 | width: ${({ size }) => (size ? size + 'px' : '24px')}; 85 | } 86 | ${({ theme }) => theme.mediaWidth.upToMedium` 87 | align-items: flex-end; 88 | `}; 89 | ` 90 | 91 | export default function Option({ 92 | link = null, 93 | clickable = true, 94 | size = null, 95 | onClick = null, 96 | color, 97 | header, 98 | subheader = null, 99 | icon, 100 | active = false, 101 | id 102 | }) { 103 | const content = ( 104 | 105 | 106 | 107 | {active ? ( 108 | 109 | 110 |
111 | 112 | 113 | ) : ( 114 | '' 115 | )} 116 | {header} 117 | 118 | {subheader && {subheader}} 119 | 120 | 121 | {'Icon'} 122 | 123 | 124 | ) 125 | if (link) { 126 | return {content} 127 | } 128 | 129 | return content 130 | } 131 | -------------------------------------------------------------------------------- /src/components/WalletModal/PendingView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Option from './Option' 4 | import { SUPPORTED_WALLETS } from '../../constants' 5 | import { injected } from '../../connectors' 6 | import { darken } from 'polished' 7 | import Loader from '../Loader' 8 | 9 | const PendingSection = styled.div` 10 | ${({ theme }) => theme.flexColumnNoWrap}; 11 | align-items: center; 12 | justify-content: center; 13 | width: 100%; 14 | & > * { 15 | width: 100%; 16 | } 17 | ` 18 | 19 | const StyledLoader = styled(Loader)` 20 | margin-right: 1rem; 21 | ` 22 | 23 | const LoadingMessage = styled.div` 24 | ${({ theme }) => theme.flexRowNoWrap}; 25 | align-items: center; 26 | justify-content: flex-start; 27 | border-radius: 12px; 28 | margin-bottom: 20px; 29 | color: ${({ theme, error }) => (error ? theme.red1 : 'inherit')}; 30 | border: 1px solid ${({ theme, error }) => (error ? theme.red1 : theme.text4)}; 31 | 32 | & > * { 33 | padding: 1rem; 34 | } 35 | ` 36 | 37 | const ErrorGroup = styled.div` 38 | ${({ theme }) => theme.flexRowNoWrap}; 39 | align-items: center; 40 | justify-content: flex-start; 41 | ` 42 | 43 | const ErrorButton = styled.div` 44 | border-radius: 8px; 45 | font-size: 12px; 46 | color: ${({ theme }) => theme.text1}; 47 | background-color: ${({ theme }) => theme.bg4}; 48 | margin-left: 1rem; 49 | padding: 0.5rem; 50 | font-weight: 600; 51 | user-select: none; 52 | 53 | &:hover { 54 | cursor: pointer; 55 | background-color: ${({ theme }) => darken(0.1, theme.text4)}; 56 | } 57 | ` 58 | 59 | const LoadingWrapper = styled.div` 60 | ${({ theme }) => theme.flexRowNoWrap}; 61 | align-items: center; 62 | justify-content: center; 63 | ` 64 | 65 | export default function PendingView({ connector, error = false, setPendingError, tryActivation }) { 66 | const isMetamask = window.ethereum && window.ethereum.isMetaMask 67 | 68 | return ( 69 | 70 | 71 | 72 | {error ? ( 73 | 74 |
Error connecting.
75 | { 77 | setPendingError(false) 78 | tryActivation(connector) 79 | }} 80 | > 81 | Try Again 82 | 83 |
84 | ) : ( 85 | <> 86 | 87 | Initializing... 88 | 89 | )} 90 |
91 |
92 | {Object.keys(SUPPORTED_WALLETS).map(key => { 93 | const option = SUPPORTED_WALLETS[key] 94 | if (option.connector === connector) { 95 | if (option.connector === injected) { 96 | if (isMetamask && option.name !== 'MetaMask') { 97 | return null 98 | } 99 | if (!isMetamask && option.name === 'MetaMask') { 100 | return null 101 | } 102 | } 103 | return ( 104 |
118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /src/components/WalletModal/Transaction.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { keyframes } from 'styled-components' 3 | import { useWeb3React } from '@web3-react/core' 4 | import Copy from './Copy' 5 | 6 | import { getEtherscanLink } from '../../utils' 7 | import { Link, Spinner } from '../../theme' 8 | import Circle from '../../assets/images/circle.svg' 9 | import { Check } from 'react-feather' 10 | 11 | import { transparentize } from 'polished' 12 | 13 | const TransactionStatusWrapper = styled.div` 14 | display: flex; 15 | align-items: center; 16 | min-width: 12px; 17 | overflow: hidden; 18 | text-overflow: ellipsis; 19 | white-space: nowrap; 20 | ` 21 | 22 | const TransactionWrapper = styled.div` 23 | ${({ theme }) => theme.flexRowNoWrap} 24 | justify-content: space-between; 25 | width: 100%; 26 | margin-top: 0.75rem; 27 | a { 28 | /* flex: 1 1 auto; */ 29 | overflow: hidden; 30 | text-overflow: ellipsis; 31 | white-space: nowrap; 32 | min-width: 0; 33 | max-width: 250px; 34 | } 35 | ` 36 | 37 | const TransactionStatusText = styled.span` 38 | margin-left: 0.5rem; 39 | ` 40 | 41 | const rotate = keyframes` 42 | from { 43 | transform: rotate(0deg); 44 | } 45 | to { 46 | transform: rotate(360deg); 47 | } 48 | ` 49 | 50 | const TransactionState = styled.div` 51 | display: flex; 52 | background-color: ${({ pending, theme }) => 53 | pending ? transparentize(0.95, theme.royalGreen) : transparentize(0.95, theme.connectedGreen)}; 54 | border-radius: 1.5rem; 55 | padding: 0.5rem 0.75rem; 56 | font-weight: 500; 57 | font-size: 0.75rem; 58 | border: 1px solid; 59 | border-color: ${({ pending, theme }) => 60 | pending ? transparentize(0.75, theme.royalGreen) : transparentize(0.75, theme.connectedGreen)}; 61 | 62 | #pending { 63 | animation: 2s ${rotate} linear infinite; 64 | } 65 | 66 | :hover { 67 | border-color: ${({ pending, theme }) => 68 | pending ? transparentize(0, theme.royalGreen) : transparentize(0, theme.connectedGreen)}; 69 | } 70 | ` 71 | const ButtonWrapper = styled.div` 72 | a { 73 | color: ${({ pending, theme }) => (pending ? theme.royalGreen : theme.connectedGreen)}; 74 | } 75 | ` 76 | 77 | export default function Transaction({ hash, pending }) { 78 | const { chainId } = useWeb3React() 79 | 80 | return ( 81 | 82 | 83 | {hash} ↗ 84 | 85 | 86 | {pending ? ( 87 | 88 | 89 | 90 | 91 | Pending 92 | 93 | 94 | 95 | ) : ( 96 | 97 | 98 | 99 | 100 | Confirmed 101 | 102 | 103 | 104 | )} 105 | 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /src/components/Web3ReactManager/index.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { useWeb3React } from '@web3-react/core' 3 | import { useEagerConnect, useInactiveListener } from '../../hooks' 4 | 5 | import { NetworkContextName } from '../../constants' 6 | import { network } from '../../connectors' 7 | 8 | export default function Web3ReactManager({ children }) { 9 | const { active } = useWeb3React() 10 | const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React(NetworkContextName) 11 | 12 | // try to eagerly connect to an injected provider, if it exists and has granted access already 13 | const triedEager = useEagerConnect() 14 | 15 | // after eagerly trying injected, if the network connect ever isn't active or in an error state, activate itd 16 | useEffect(() => { 17 | if (triedEager && !networkActive && !networkError && !active) { 18 | activateNetwork(network) 19 | } 20 | }, [triedEager, networkActive, networkError, activateNetwork, active]) 21 | 22 | // when there's no account connected, react to logins (broadly speaking) on the injected provider, if it exists 23 | useInactiveListener(!triedEager) 24 | 25 | // handle delayed loader state 26 | const [, setShowLoader] = useState(false) 27 | useEffect(() => { 28 | const timeout = setTimeout(() => { 29 | setShowLoader(true) 30 | }, 600) 31 | 32 | return () => { 33 | clearTimeout(timeout) 34 | } 35 | }, [setShowLoader]) 36 | 37 | // on page load, do nothing until we've tried to connect to the injected connector 38 | if (!triedEager) { 39 | return null 40 | } 41 | 42 | // if the account context isn't active, and there's an error on the network context, it's an irrecoverable error 43 | if (!active && networkError) { 44 | // return ( 45 | // 46 | // {t('unknownError')} 47 | // 48 | // ) 49 | } 50 | 51 | // if neither context is active, spin 52 | // if (!active && !networkActive) { 53 | // return showLoader ? ( 54 | // 55 | // 56 | // 57 | // ) : null 58 | // } 59 | 60 | return children 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Web3Status/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import { useTranslation } from 'react-i18next' 4 | import { useWeb3React } from '@web3-react/core' 5 | import { darken, lighten } from 'polished' 6 | import { Activity } from 'react-feather' 7 | import { useENSName } from '../../hooks' 8 | 9 | import Identicon from '../Identicon' 10 | import PortisIcon from '../../assets/images/portisIcon.png' 11 | import WalletModal from '../WalletModal' 12 | import { ButtonSecondary } from '../Button' 13 | import FortmaticIcon from '../../assets/images/fortmaticIcon.png' 14 | import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg' 15 | import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg' 16 | 17 | import { shortenAddress } from '../../utils' 18 | import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors' 19 | 20 | const IconWrapper = styled.div` 21 | ${({ theme }) => theme.flexColumnNoWrap}; 22 | align-items: center; 23 | justify-content: center; 24 | & > * { 25 | height: ${({ size }) => (size ? size + 'px' : '32px')}; 26 | width: ${({ size }) => (size ? size + 'px' : '32px')}; 27 | } 28 | ` 29 | 30 | const Web3StatusGeneric = styled(ButtonSecondary)` 31 | ${({ theme }) => theme.flexRowNoWrap} 32 | width: 100%; 33 | align-items: center; 34 | padding: 0.5rem; 35 | border-radius: 12px; 36 | cursor: pointer; 37 | user-select: none; 38 | :focus { 39 | outline: none; 40 | } 41 | ` 42 | const Web3StatusError = styled(Web3StatusGeneric)` 43 | background-color: ${({ theme }) => theme.red1}; 44 | border: 1px solid ${({ theme }) => theme.red1}; 45 | color: ${({ theme }) => theme.white}; 46 | font-weight: 500; 47 | :hover, 48 | :focus { 49 | background-color: ${({ theme }) => darken(0.1, theme.red1)}; 50 | } 51 | ` 52 | 53 | const Web3StatusConnect = styled(Web3StatusGeneric)` 54 | background-color: ${({ pending, theme }) => (pending ? theme.primary1 : theme.bg2)}; 55 | border: 1px solid ${({ pending, theme }) => (pending ? theme.primary1 : theme.bg3)}; 56 | color: ${({ pending, theme }) => (pending ? theme.white : theme.text1)}; 57 | font-weight: 500; 58 | 59 | :hover, 60 | :focus { 61 | background-color: ${({ pending, theme }) => (pending ? darken(0.05, theme.primary1) : lighten(0.05, theme.bg2))}; 62 | 63 | :focus { 64 | border: 1px solid ${({ pending, theme }) => (pending ? darken(0.1, theme.primary1) : darken(0.1, theme.bg3))}; 65 | } 66 | } 67 | ` 68 | 69 | const Web3StatusConnected = styled(Web3StatusGeneric)` 70 | background-color: ${({ pending, theme }) => (pending ? theme.primary1 : theme.bg2)}; 71 | border: 1px solid ${({ pending, theme }) => (pending ? theme.primary1 : theme.bg3)}; 72 | color: ${({ pending, theme }) => (pending ? theme.white : theme.text1)}; 73 | font-weight: 500; 74 | :hover, 75 | :focus { 76 | background-color: ${({ pending, theme }) => (pending ? darken(0.05, theme.primary1) : lighten(0.05, theme.bg2))}; 77 | 78 | :focus { 79 | border: 1px solid ${({ pending, theme }) => (pending ? darken(0.1, theme.primary1) : darken(0.1, theme.bg3))}; 80 | } 81 | } 82 | ` 83 | 84 | const Text = styled.p` 85 | flex: 1 1 auto; 86 | overflow: hidden; 87 | text-overflow: ellipsis; 88 | white-space: nowrap; 89 | margin: 0 0.5rem 0 0.25rem; 90 | font-size: 1rem; 91 | width: fit-content; 92 | font-weight: 500; 93 | ` 94 | 95 | const NetworkIcon = styled(Activity)` 96 | margin-left: 0.25rem; 97 | margin-right: 0.5rem; 98 | width: 16px; 99 | height: 16px; 100 | ` 101 | 102 | export default function Web3Status() { 103 | const { t } = useTranslation() 104 | const { account, connector, error } = useWeb3React() 105 | 106 | const { ENSName } = useENSName(account) 107 | 108 | const [showWalletModal, setToggleWalletModal] = useState(false) 109 | 110 | function toggleWalletModal() { 111 | setToggleWalletModal(!showWalletModal) 112 | } 113 | 114 | // handle the logo we want to show with the account 115 | function getStatusIcon() { 116 | if (connector === injected) { 117 | return 118 | } else if (connector === walletconnect) { 119 | return ( 120 | 121 | {''} 122 | 123 | ) 124 | } else if (connector === walletlink) { 125 | return ( 126 | 127 | {''} 128 | 129 | ) 130 | } else if (connector === fortmatic) { 131 | return ( 132 | 133 | {''} 134 | 135 | ) 136 | } else if (connector === portis) { 137 | return ( 138 | 139 | {''} 140 | 141 | ) 142 | } 143 | } 144 | 145 | function getWeb3Status() { 146 | if (account) { 147 | return ( 148 | 149 | <> 150 | {ENSName || shortenAddress(account)} 151 | 152 | {getStatusIcon()} 153 | 154 | ) 155 | } else if (error) { 156 | return ( 157 | 158 | 159 | {error ? 'Wrong Network' : 'Error'} 160 | 161 | ) 162 | } else { 163 | return ( 164 | 165 | {t('Connect to a wallet')} 166 | 167 | ) 168 | } 169 | } 170 | 171 | // if (!contextNetwork.active && !active) { 172 | // return null 173 | // } 174 | 175 | return ( 176 | <> 177 | {getWeb3Status()} 178 | 179 | 180 | ) 181 | } 182 | -------------------------------------------------------------------------------- /src/connectors/Fortmatic.js: -------------------------------------------------------------------------------- 1 | import { FortmaticConnector as FortmaticConnectorCore } from '@web3-react/fortmatic-connector' 2 | 3 | export const OVERLAY_READY = 'OVERLAY_READY' 4 | 5 | const CHAIN_ID_NETWORK_ARGUMENT = { 6 | 1: undefined, 7 | 3: 'ropsten', 8 | 4: 'rinkeby', 9 | 42: 'kovan' 10 | } 11 | 12 | export class FortmaticConnector extends FortmaticConnectorCore { 13 | async activate() { 14 | if (!this.fortmatic) { 15 | const { default: Fortmatic } = await import('fortmatic') 16 | const { apiKey, chainId } = this 17 | if (chainId in CHAIN_ID_NETWORK_ARGUMENT) { 18 | this.fortmatic = new Fortmatic(apiKey, CHAIN_ID_NETWORK_ARGUMENT[chainId]) 19 | } else { 20 | throw new Error(`Unsupported network ID: ${chainId}`) 21 | } 22 | } 23 | 24 | const provider = this.fortmatic.getProvider() 25 | 26 | const pollForOverlayReady = new Promise(resolve => { 27 | const interval = setInterval(() => { 28 | if (provider.overlayReady) { 29 | clearInterval(interval) 30 | this.emit(OVERLAY_READY) 31 | resolve() 32 | } 33 | }, 200) 34 | }) 35 | 36 | const [account] = await Promise.all([provider.enable().then(accounts => accounts[0]), pollForOverlayReady]) 37 | 38 | return { provider: this.fortmatic.getProvider(), chainId: this.chainId, account } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/connectors/NetworkConnector.js: -------------------------------------------------------------------------------- 1 | import { AbstractConnector } from '@web3-react/abstract-connector' 2 | import invariant from 'tiny-invariant' 3 | 4 | class RequestError extends Error { 5 | constructor(message, code, data) { 6 | super(message) 7 | } 8 | } 9 | 10 | class MiniRpcProvider { 11 | isMetaMask = false 12 | chainId 13 | url 14 | host 15 | path 16 | 17 | constructor(chainId, url) { 18 | this.chainId = chainId 19 | this.url = url 20 | const parsed = new URL(url) 21 | this.host = parsed.host 22 | this.path = parsed.pathname 23 | } 24 | 25 | sendAsync = (request, callback) => { 26 | this.request(request.method, request.params) 27 | .then(result => callback(null, { jsonrpc: '2.0', id: request.id, result })) 28 | .catch(error => callback(error, null)) 29 | } 30 | 31 | request = async (method, params) => { 32 | if (typeof method !== 'string') { 33 | return this.request(method.method, method.params) 34 | } 35 | if (method === 'eth_chainId') { 36 | return `0x${this.chainId.toString(16)}` 37 | } 38 | const response = await fetch(this.url, { 39 | method: 'POST', 40 | body: JSON.stringify({ 41 | jsonrpc: '2.0', 42 | id: 1, 43 | method, 44 | params 45 | }) 46 | }) 47 | if (!response.ok) throw new RequestError(`${response.status}: ${response.statusText}`, -32000) 48 | const body = await response.json() 49 | if ('error' in body) { 50 | throw new RequestError(body.error.message, body.error.code, body.error.data) 51 | } else if ('result' in body) { 52 | return body.result 53 | } else { 54 | throw new RequestError(`Received unexpected JSON-RPC response to ${method} request.`, -32000, body) 55 | } 56 | } 57 | } 58 | 59 | export class NetworkConnector extends AbstractConnector { 60 | providers 61 | currentChainId 62 | 63 | constructor({ urls, defaultChainId }) { 64 | invariant(defaultChainId || Object.keys(urls).length === 1, 'defaultChainId is a required argument with >1 url') 65 | super({ supportedChainIds: Object.keys(urls).map(k => Number(k)) }) 66 | 67 | this.currentChainId = defaultChainId || Number(Object.keys(urls)[0]) 68 | this.providers = Object.keys(urls).reduce((accumulator, chainId) => { 69 | accumulator[Number(chainId)] = new MiniRpcProvider(Number(chainId), urls[Number(chainId)]) 70 | return accumulator 71 | }, {}) 72 | } 73 | 74 | async activate() { 75 | return { provider: this.providers[this.currentChainId], chainId: this.currentChainId, account: null } 76 | } 77 | 78 | async getProvider() { 79 | return this.providers[this.currentChainId] 80 | } 81 | 82 | async getChainId() { 83 | return this.currentChainId 84 | } 85 | 86 | async getAccount() { 87 | return null 88 | } 89 | 90 | deactivate() { 91 | return 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/connectors/index.js: -------------------------------------------------------------------------------- 1 | import { InjectedConnector } from '@web3-react/injected-connector' 2 | import { WalletConnectConnector } from '@web3-react/walletconnect-connector' 3 | import { WalletLinkConnector } from '@web3-react/walletlink-connector' 4 | import { PortisConnector } from '@web3-react/portis-connector' 5 | 6 | import { FortmaticConnector } from './Fortmatic' 7 | import { NetworkConnector } from './NetworkConnector' 8 | 9 | const POLLING_INTERVAL = 12500 10 | const NETWORK_URL = process.env.REACT_APP_NETWORK_URL 11 | const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY 12 | const PORTIS_ID = process.env.REACT_APP_PORTIS_ID 13 | 14 | if (typeof NETWORK_URL === 'undefined') { 15 | throw new Error(`REACT_APP_NETWORK_URL must be a defined environment variable`) 16 | } 17 | 18 | export const network = new NetworkConnector({ 19 | urls: { [Number(process.env.REACT_APP_CHAIN_ID)]: NETWORK_URL } 20 | }) 21 | 22 | export const injected = new InjectedConnector({ 23 | supportedChainIds: [1, 3, 4, 5, 42] 24 | }) 25 | 26 | // mainnet only 27 | export const walletconnect = new WalletConnectConnector({ 28 | rpc: { 1: NETWORK_URL }, 29 | bridge: 'https://bridge.walletconnect.org', 30 | qrcode: true, 31 | pollingInterval: POLLING_INTERVAL 32 | }) 33 | 34 | // mainnet only 35 | export const fortmatic = new FortmaticConnector({ 36 | apiKey: FORMATIC_KEY ? FORMATIC_KEY : '', 37 | chainId: 1 38 | }) 39 | 40 | // mainnet only 41 | export const portis = new PortisConnector({ 42 | dAppId: PORTIS_ID ? PORTIS_ID : '', 43 | networks: [1] 44 | }) 45 | 46 | // mainnet only 47 | export const walletlink = new WalletLinkConnector({ 48 | url: NETWORK_URL, 49 | appName: 'Uniswap', 50 | appLogoUrl: 51 | 'https://mpng.pngfly.com/20181202/bex/kisspng-emoji-domain-unicorn-pin-badges-sticker-unicorn-tumblr-emoji-unicorn-iphoneemoji-5c046729264a77.5671679315437924251569.jpg' 52 | }) 53 | -------------------------------------------------------------------------------- /src/constants/abis/erc20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [{ "name": "", "type": "string" }], 7 | "payable": false, 8 | "stateMutability": "view", 9 | "type": "function" 10 | }, 11 | { 12 | "constant": false, 13 | "inputs": [{ "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }], 14 | "name": "approve", 15 | "outputs": [{ "name": "", "type": "bool" }], 16 | "payable": false, 17 | "stateMutability": "nonpayable", 18 | "type": "function" 19 | }, 20 | { 21 | "constant": true, 22 | "inputs": [], 23 | "name": "totalSupply", 24 | "outputs": [{ "name": "", "type": "uint256" }], 25 | "payable": false, 26 | "stateMutability": "view", 27 | "type": "function" 28 | }, 29 | { 30 | "constant": false, 31 | "inputs": [ 32 | { "name": "_from", "type": "address" }, 33 | { "name": "_to", "type": "address" }, 34 | { "name": "_value", "type": "uint256" } 35 | ], 36 | "name": "transferFrom", 37 | "outputs": [{ "name": "", "type": "bool" }], 38 | "payable": false, 39 | "stateMutability": "nonpayable", 40 | "type": "function" 41 | }, 42 | { 43 | "constant": true, 44 | "inputs": [], 45 | "name": "decimals", 46 | "outputs": [{ "name": "", "type": "uint8" }], 47 | "payable": false, 48 | "stateMutability": "view", 49 | "type": "function" 50 | }, 51 | { 52 | "constant": true, 53 | "inputs": [{ "name": "_owner", "type": "address" }], 54 | "name": "balanceOf", 55 | "outputs": [{ "name": "balance", "type": "uint256" }], 56 | "payable": false, 57 | "stateMutability": "view", 58 | "type": "function" 59 | }, 60 | { 61 | "constant": true, 62 | "inputs": [], 63 | "name": "symbol", 64 | "outputs": [{ "name": "", "type": "string" }], 65 | "payable": false, 66 | "stateMutability": "view", 67 | "type": "function" 68 | }, 69 | { 70 | "constant": false, 71 | "inputs": [{ "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" }], 72 | "name": "transfer", 73 | "outputs": [{ "name": "", "type": "bool" }], 74 | "payable": false, 75 | "stateMutability": "nonpayable", 76 | "type": "function" 77 | }, 78 | { 79 | "constant": true, 80 | "inputs": [{ "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" }], 81 | "name": "allowance", 82 | "outputs": [{ "name": "", "type": "uint256" }], 83 | "payable": false, 84 | "stateMutability": "view", 85 | "type": "function" 86 | }, 87 | { "payable": true, "stateMutability": "payable", "type": "fallback" }, 88 | { 89 | "anonymous": false, 90 | "inputs": [ 91 | { "indexed": true, "name": "owner", "type": "address" }, 92 | { "indexed": true, "name": "spender", "type": "address" }, 93 | { "indexed": false, "name": "value", "type": "uint256" } 94 | ], 95 | "name": "Approval", 96 | "type": "event" 97 | }, 98 | { 99 | "anonymous": false, 100 | "inputs": [ 101 | { "indexed": true, "name": "from", "type": "address" }, 102 | { "indexed": true, "name": "to", "type": "address" }, 103 | { "indexed": false, "name": "value", "type": "uint256" } 104 | ], 105 | "name": "Transfer", 106 | "type": "event" 107 | } 108 | ] 109 | -------------------------------------------------------------------------------- /src/constants/abis/erc20_bytes32.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [{ "name": "", "type": "bytes32" }], 7 | "payable": false, 8 | "stateMutability": "view", 9 | "type": "function" 10 | }, 11 | { 12 | "constant": false, 13 | "inputs": [{ "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }], 14 | "name": "approve", 15 | "outputs": [{ "name": "", "type": "bool" }], 16 | "payable": false, 17 | "stateMutability": "nonpayable", 18 | "type": "function" 19 | }, 20 | { 21 | "constant": true, 22 | "inputs": [], 23 | "name": "totalSupply", 24 | "outputs": [{ "name": "", "type": "uint256" }], 25 | "payable": false, 26 | "stateMutability": "view", 27 | "type": "function" 28 | }, 29 | { 30 | "constant": false, 31 | "inputs": [ 32 | { "name": "_from", "type": "address" }, 33 | { "name": "_to", "type": "address" }, 34 | { "name": "_value", "type": "uint256" } 35 | ], 36 | "name": "transferFrom", 37 | "outputs": [{ "name": "", "type": "bool" }], 38 | "payable": false, 39 | "stateMutability": "nonpayable", 40 | "type": "function" 41 | }, 42 | { 43 | "constant": true, 44 | "inputs": [], 45 | "name": "decimals", 46 | "outputs": [{ "name": "", "type": "uint8" }], 47 | "payable": false, 48 | "stateMutability": "view", 49 | "type": "function" 50 | }, 51 | { 52 | "constant": true, 53 | "inputs": [{ "name": "_owner", "type": "address" }], 54 | "name": "balanceOf", 55 | "outputs": [{ "name": "balance", "type": "uint256" }], 56 | "payable": false, 57 | "stateMutability": "view", 58 | "type": "function" 59 | }, 60 | { 61 | "constant": true, 62 | "inputs": [], 63 | "name": "symbol", 64 | "outputs": [{ "name": "", "type": "bytes32" }], 65 | "payable": false, 66 | "stateMutability": "view", 67 | "type": "function" 68 | }, 69 | { 70 | "constant": false, 71 | "inputs": [{ "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" }], 72 | "name": "transfer", 73 | "outputs": [{ "name": "", "type": "bool" }], 74 | "payable": false, 75 | "stateMutability": "nonpayable", 76 | "type": "function" 77 | }, 78 | { 79 | "constant": true, 80 | "inputs": [{ "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" }], 81 | "name": "allowance", 82 | "outputs": [{ "name": "", "type": "uint256" }], 83 | "payable": false, 84 | "stateMutability": "view", 85 | "type": "function" 86 | }, 87 | { "payable": true, "stateMutability": "payable", "type": "fallback" }, 88 | { 89 | "anonymous": false, 90 | "inputs": [ 91 | { "indexed": true, "name": "owner", "type": "address" }, 92 | { "indexed": true, "name": "spender", "type": "address" }, 93 | { "indexed": false, "name": "value", "type": "uint256" } 94 | ], 95 | "name": "Approval", 96 | "type": "event" 97 | }, 98 | { 99 | "anonymous": false, 100 | "inputs": [ 101 | { "indexed": true, "name": "from", "type": "address" }, 102 | { "indexed": true, "name": "to", "type": "address" }, 103 | { "indexed": false, "name": "value", "type": "uint256" } 104 | ], 105 | "name": "Transfer", 106 | "type": "event" 107 | } 108 | ] 109 | -------------------------------------------------------------------------------- /src/constants/abis/factory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "NewExchange", 4 | "inputs": [ 5 | { "type": "address", "name": "token", "indexed": true }, 6 | { "type": "address", "name": "exchange", "indexed": true } 7 | ], 8 | "anonymous": false, 9 | "type": "event" 10 | }, 11 | { 12 | "name": "initializeFactory", 13 | "outputs": [], 14 | "inputs": [{ "type": "address", "name": "template" }], 15 | "constant": false, 16 | "payable": false, 17 | "type": "function", 18 | "gas": 35725 19 | }, 20 | { 21 | "name": "createExchange", 22 | "outputs": [{ "type": "address", "name": "out" }], 23 | "inputs": [{ "type": "address", "name": "token" }], 24 | "constant": false, 25 | "payable": false, 26 | "type": "function", 27 | "gas": 187911 28 | }, 29 | { 30 | "name": "getExchange", 31 | "outputs": [{ "type": "address", "name": "out" }], 32 | "inputs": [{ "type": "address", "name": "token" }], 33 | "constant": true, 34 | "payable": false, 35 | "type": "function", 36 | "gas": 715 37 | }, 38 | { 39 | "name": "getToken", 40 | "outputs": [{ "type": "address", "name": "out" }], 41 | "inputs": [{ "type": "address", "name": "exchange" }], 42 | "constant": true, 43 | "payable": false, 44 | "type": "function", 45 | "gas": 745 46 | }, 47 | { 48 | "name": "getTokenWithId", 49 | "outputs": [{ "type": "address", "name": "out" }], 50 | "inputs": [{ "type": "uint256", "name": "token_id" }], 51 | "constant": true, 52 | "payable": false, 53 | "type": "function", 54 | "gas": 736 55 | }, 56 | { 57 | "name": "exchangeTemplate", 58 | "outputs": [{ "type": "address", "name": "out" }], 59 | "inputs": [], 60 | "constant": true, 61 | "payable": false, 62 | "type": "function", 63 | "gas": 633 64 | }, 65 | { 66 | "name": "tokenCount", 67 | "outputs": [{ "type": "uint256", "name": "out" }], 68 | "inputs": [], 69 | "constant": true, 70 | "payable": false, 71 | "type": "function", 72 | "gas": 663 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /src/constants/abis/factoryV2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_feeToSetter", 7 | "type": "address" 8 | } 9 | ], 10 | "payable": false, 11 | "stateMutability": "nonpayable", 12 | "type": "constructor" 13 | }, 14 | { 15 | "anonymous": false, 16 | "inputs": [ 17 | { 18 | "indexed": true, 19 | "internalType": "address", 20 | "name": "token0", 21 | "type": "address" 22 | }, 23 | { 24 | "indexed": true, 25 | "internalType": "address", 26 | "name": "token1", 27 | "type": "address" 28 | }, 29 | { 30 | "indexed": false, 31 | "internalType": "address", 32 | "name": "pair", 33 | "type": "address" 34 | }, 35 | { 36 | "indexed": false, 37 | "internalType": "uint256", 38 | "name": "", 39 | "type": "uint256" 40 | } 41 | ], 42 | "name": "PairCreated", 43 | "type": "event" 44 | }, 45 | { 46 | "constant": true, 47 | "inputs": [ 48 | { 49 | "internalType": "uint256", 50 | "name": "", 51 | "type": "uint256" 52 | } 53 | ], 54 | "name": "allPairs", 55 | "outputs": [ 56 | { 57 | "internalType": "address", 58 | "name": "", 59 | "type": "address" 60 | } 61 | ], 62 | "payable": false, 63 | "stateMutability": "view", 64 | "type": "function" 65 | }, 66 | { 67 | "constant": true, 68 | "inputs": [], 69 | "name": "allPairsLength", 70 | "outputs": [ 71 | { 72 | "internalType": "uint256", 73 | "name": "", 74 | "type": "uint256" 75 | } 76 | ], 77 | "payable": false, 78 | "stateMutability": "view", 79 | "type": "function" 80 | }, 81 | { 82 | "constant": false, 83 | "inputs": [ 84 | { 85 | "internalType": "address", 86 | "name": "tokenA", 87 | "type": "address" 88 | }, 89 | { 90 | "internalType": "address", 91 | "name": "tokenB", 92 | "type": "address" 93 | } 94 | ], 95 | "name": "createPair", 96 | "outputs": [ 97 | { 98 | "internalType": "address", 99 | "name": "pair", 100 | "type": "address" 101 | } 102 | ], 103 | "payable": false, 104 | "stateMutability": "nonpayable", 105 | "type": "function" 106 | }, 107 | { 108 | "constant": true, 109 | "inputs": [], 110 | "name": "feeTo", 111 | "outputs": [ 112 | { 113 | "internalType": "address", 114 | "name": "", 115 | "type": "address" 116 | } 117 | ], 118 | "payable": false, 119 | "stateMutability": "view", 120 | "type": "function" 121 | }, 122 | { 123 | "constant": true, 124 | "inputs": [], 125 | "name": "feeToSetter", 126 | "outputs": [ 127 | { 128 | "internalType": "address", 129 | "name": "", 130 | "type": "address" 131 | } 132 | ], 133 | "payable": false, 134 | "stateMutability": "view", 135 | "type": "function" 136 | }, 137 | { 138 | "constant": true, 139 | "inputs": [ 140 | { 141 | "internalType": "address", 142 | "name": "", 143 | "type": "address" 144 | }, 145 | { 146 | "internalType": "address", 147 | "name": "", 148 | "type": "address" 149 | } 150 | ], 151 | "name": "getPair", 152 | "outputs": [ 153 | { 154 | "internalType": "address", 155 | "name": "", 156 | "type": "address" 157 | } 158 | ], 159 | "payable": false, 160 | "stateMutability": "view", 161 | "type": "function" 162 | }, 163 | { 164 | "constant": false, 165 | "inputs": [ 166 | { 167 | "internalType": "address", 168 | "name": "_feeTo", 169 | "type": "address" 170 | } 171 | ], 172 | "name": "setFeeTo", 173 | "outputs": [], 174 | "payable": false, 175 | "stateMutability": "nonpayable", 176 | "type": "function" 177 | }, 178 | { 179 | "constant": false, 180 | "inputs": [ 181 | { 182 | "internalType": "address", 183 | "name": "_feeToSetter", 184 | "type": "address" 185 | } 186 | ], 187 | "name": "setFeeToSetter", 188 | "outputs": [], 189 | "payable": false, 190 | "stateMutability": "nonpayable", 191 | "type": "function" 192 | } 193 | ] -------------------------------------------------------------------------------- /src/constants/abis/multicall.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "getCurrentBlockTimestamp", 6 | "outputs": [ 7 | { 8 | "name": "timestamp", 9 | "type": "uint256" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [ 19 | { 20 | "components": [ 21 | { 22 | "name": "target", 23 | "type": "address" 24 | }, 25 | { 26 | "name": "callData", 27 | "type": "bytes" 28 | } 29 | ], 30 | "name": "calls", 31 | "type": "tuple[]" 32 | } 33 | ], 34 | "name": "aggregate", 35 | "outputs": [ 36 | { 37 | "name": "blockNumber", 38 | "type": "uint256" 39 | }, 40 | { 41 | "name": "returnData", 42 | "type": "bytes[]" 43 | } 44 | ], 45 | "payable": false, 46 | "stateMutability": "view", 47 | "type": "function" 48 | }, 49 | { 50 | "constant": true, 51 | "inputs": [], 52 | "name": "getLastBlockHash", 53 | "outputs": [ 54 | { 55 | "name": "blockHash", 56 | "type": "bytes32" 57 | } 58 | ], 59 | "payable": false, 60 | "stateMutability": "view", 61 | "type": "function" 62 | }, 63 | { 64 | "constant": true, 65 | "inputs": [ 66 | { 67 | "name": "addr", 68 | "type": "address" 69 | } 70 | ], 71 | "name": "getEthBalance", 72 | "outputs": [ 73 | { 74 | "name": "balance", 75 | "type": "uint256" 76 | } 77 | ], 78 | "payable": false, 79 | "stateMutability": "view", 80 | "type": "function" 81 | }, 82 | { 83 | "constant": true, 84 | "inputs": [], 85 | "name": "getCurrentBlockDifficulty", 86 | "outputs": [ 87 | { 88 | "name": "difficulty", 89 | "type": "uint256" 90 | } 91 | ], 92 | "payable": false, 93 | "stateMutability": "view", 94 | "type": "function" 95 | }, 96 | { 97 | "constant": true, 98 | "inputs": [], 99 | "name": "getCurrentBlockGasLimit", 100 | "outputs": [ 101 | { 102 | "name": "gaslimit", 103 | "type": "uint256" 104 | } 105 | ], 106 | "payable": false, 107 | "stateMutability": "view", 108 | "type": "function" 109 | }, 110 | { 111 | "constant": true, 112 | "inputs": [], 113 | "name": "getCurrentBlockCoinbase", 114 | "outputs": [ 115 | { 116 | "name": "coinbase", 117 | "type": "address" 118 | } 119 | ], 120 | "payable": false, 121 | "stateMutability": "view", 122 | "type": "function" 123 | }, 124 | { 125 | "constant": true, 126 | "inputs": [ 127 | { 128 | "name": "blockNumber", 129 | "type": "uint256" 130 | } 131 | ], 132 | "name": "getBlockHash", 133 | "outputs": [ 134 | { 135 | "name": "blockHash", 136 | "type": "bytes32" 137 | } 138 | ], 139 | "payable": false, 140 | "stateMutability": "view", 141 | "type": "function" 142 | } 143 | ] -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | import { Token, ChainId, WETH } from 'uniswap-v2-sdk' 2 | import { ethers } from 'ethers' 3 | 4 | // @TODO: we should test walletconnect, walletlink before adding 5 | import { injected, fortmatic, portis } from '../connectors' 6 | 7 | export const ETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 8 | 9 | export const ORDER_GRAPH = { 10 | 1: 'https://api.thegraph.com/subgraphs/name/pine-finance/pine_orders', 11 | 4: 'https://api.thegraph.com/subgraphs/name/pine-finance/pine_orders_rinkeby' 12 | } 13 | 14 | export const FACTORY_ADDRESSES = { 15 | 1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95', 16 | 3: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351', 17 | 4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36', 18 | 42: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30' 19 | } 20 | 21 | export const MULTICALL_ADDRESS = { 22 | 1: '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', 23 | 4: '0x751c5b6c24ee8687c0d5f1c26813c4a09406c904' 24 | } 25 | 26 | export const UNISWAPEX_ADDRESSES = { 27 | [ChainId.MAINNET]: '0xD412054ccA18A61278ceD6F674A526A6940eBd84', 28 | [ChainId.ROPSTEN]: '', 29 | [ChainId.RINKEBY]: '0xD412054ccA18A61278ceD6F674A526A6940eBd84' 30 | } 31 | 32 | export const LIMIT_ORDER_MODULE_ADDRESSES = { 33 | [ChainId.MAINNET]: '0x037fc8e71445910e1E0bBb2a0896d5e9A7485318', 34 | [ChainId.RINKEBY]: '0x037fc8e71445910e1E0bBb2a0896d5e9A7485318' 35 | } 36 | 37 | export const UNISWAPV2_ADDRESSES = { 38 | 1: { 39 | FACTORY: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f' 40 | }, 41 | 3: { 42 | FACTORY: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f' 43 | } 44 | } 45 | 46 | export const GENERIC_GAS_LIMIT_ORDER_EXECUTE = ethers.utils.bigNumberify(400000) 47 | 48 | export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' 49 | 50 | export const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin') 51 | export const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C') 52 | export const USDT = new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'Tether USD') 53 | export const COMP = new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound') 54 | export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker') 55 | 56 | const WETH_ONLY = { 57 | [ChainId.MAINNET]: [WETH[ChainId.MAINNET]], 58 | [ChainId.ROPSTEN]: [WETH[ChainId.ROPSTEN]], 59 | [ChainId.RINKEBY]: [WETH[ChainId.RINKEBY]], 60 | [ChainId.GÖRLI]: [WETH[ChainId.GÖRLI]], 61 | [ChainId.KOVAN]: [WETH[ChainId.KOVAN]] 62 | } 63 | 64 | // used to construct intermediary pairs for trading 65 | export const BASES_TO_CHECK_TRADES_AGAINST = { 66 | ...WETH_ONLY, 67 | [ChainId.MAINNET]: [...WETH_ONLY[ChainId.MAINNET], DAI, USDC, USDT, COMP, MKR] 68 | } 69 | 70 | export const NetworkContextName = 'NETWORK' 71 | 72 | const TESTNET_CAPABLE_WALLETS = { 73 | INJECTED: { 74 | connector: injected, 75 | name: 'Injected', 76 | iconName: 'arrow-right.svg', 77 | description: 'Injected web3 provider.', 78 | href: null, 79 | color: '#010101', 80 | primary: true 81 | }, 82 | METAMASK: { 83 | connector: injected, 84 | name: 'MetaMask', 85 | iconName: 'metamask.png', 86 | description: 'Easy-to-use browser extension.', 87 | href: null, 88 | color: '#E8831D' 89 | } 90 | } 91 | 92 | export const SUPPORTED_WALLETS = { 93 | ...TESTNET_CAPABLE_WALLETS, 94 | ...{ 95 | // WALLET_CONNECT: { 96 | // connector: walletconnect, 97 | // name: 'WalletConnect', 98 | // iconName: 'walletConnectIcon.svg', 99 | // description: 'Connect to Trust Wallet, Rainbow Wallet and more...', 100 | // href: null, 101 | // color: '#4196FC', 102 | // mobile: true 103 | // }, 104 | // WALLET_LINK: { 105 | // connector: walletlink, 106 | // name: 'Coinbase Wallet', 107 | // iconName: 'coinbaseWalletIcon.svg', 108 | // description: 'Use Coinbase Wallet app on mobile device', 109 | // href: null, 110 | // color: '#315CF5' 111 | // }, 112 | // COINBASE_LINK: { 113 | // name: 'Open in Coinbase Wallet', 114 | // iconName: 'coinbaseWalletIcon.svg', 115 | // description: 'Open in Coinbase Wallet app.', 116 | // href: 'https://go.cb-w.com/mtUDhEZPy1', 117 | // color: '#315CF5', 118 | // mobile: true, 119 | // mobileOnly: true 120 | // }, 121 | Portis: { 122 | connector: portis, 123 | name: 'Portis', 124 | iconName: 'portisIcon.png', 125 | description: 'Login using Portis hosted wallet', 126 | href: null, 127 | color: '#4A6C9B', 128 | mobile: true 129 | }, 130 | FORTMATIC: { 131 | connector: fortmatic, 132 | name: 'Fortmatic', 133 | iconName: 'fortmaticIcon.png', 134 | description: 'Login using Fortmatic hosted wallet', 135 | href: null, 136 | color: '#6748FF', 137 | mobile: true 138 | } 139 | } 140 | } 141 | 142 | export const MULTICALL_NETWORKS = { 143 | [ChainId.MAINNET]: '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441', 144 | [ChainId.ROPSTEN]: '0x53C43764255c17BD724F74c4eF150724AC50a3ed', 145 | [ChainId.KOVAN]: '0x2cc8688C5f75E365aaEEb4ea8D6a480405A48D2A', 146 | [ChainId.RINKEBY]: '0x42Ad527de7d4e9d9d011aC45B31D8551f8Fe9821', 147 | [ChainId.GÖRLI]: '0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e' 148 | } 149 | -------------------------------------------------------------------------------- /src/contexts/AllBalances.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useReducer, useMemo, useCallback } from 'react' 2 | import { BigNumber } from '@uniswap/sdk' 3 | import { useWeb3React } from '@web3-react/core' 4 | 5 | import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils' 6 | import { useAllTokenDetails } from './Tokens' 7 | 8 | const ONE = new BigNumber(1) 9 | 10 | const UPDATE = 'UPDATE' 11 | 12 | const AllBalancesContext = createContext() 13 | 14 | function useAllBalancesContext() { 15 | return useContext(AllBalancesContext) 16 | } 17 | 18 | function reducer(state, { type, payload }) { 19 | switch (type) { 20 | case UPDATE: { 21 | const { allBalanceData, chainId, address } = payload 22 | return { 23 | ...state, 24 | [chainId]: { 25 | ...(safeAccess(state, [chainId]) || {}), 26 | [address]: { 27 | ...(safeAccess(state, [chainId, address]) || {}), 28 | allBalanceData 29 | } 30 | } 31 | } 32 | } 33 | default: { 34 | throw Error(`Unexpected action type in AllBalancesContext reducer: '${type}'.`) 35 | } 36 | } 37 | } 38 | 39 | export default function Provider({ children }) { 40 | const [state, dispatch] = useReducer(reducer, {}) 41 | 42 | const update = useCallback((allBalanceData, chainId, address) => { 43 | dispatch({ type: UPDATE, payload: { allBalanceData, chainId, address } }) 44 | }, []) 45 | 46 | return ( 47 | [state, { update }], [state, update])}> 48 | {children} 49 | 50 | ) 51 | } 52 | 53 | export function useFetchAllBalances() { 54 | const { account, chainId, library } = useWeb3React() 55 | 56 | const allTokens = useAllTokenDetails() 57 | 58 | const [state, { update }] = useAllBalancesContext() 59 | 60 | const { allBalanceData } = safeAccess(state, [chainId, account]) || {} 61 | 62 | const getData = async () => { 63 | if (!!library && !!account) { 64 | const newBalances = {} 65 | await Promise.all( 66 | Object.keys(allTokens).map(async k => { 67 | let balance = null 68 | let ethRate = null 69 | 70 | if (isAddress(k) || k === 'ETH') { 71 | if (k === 'ETH') { 72 | balance = await getEtherBalance(account, library).catch(() => null) 73 | ethRate = ONE 74 | } else { 75 | balance = await getTokenBalance(k, account, library).catch(e => { 76 | console.error(e.message) 77 | return null 78 | }) 79 | } 80 | 81 | return (newBalances[k] = { balance, ethRate }) 82 | } 83 | }) 84 | ) 85 | update(newBalances, chainId, account) 86 | } 87 | } 88 | 89 | useMemo(getData, [account]) 90 | 91 | return allBalanceData 92 | } 93 | -------------------------------------------------------------------------------- /src/contexts/Allowances.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' 2 | import { useWeb3React } from '@web3-react/core' 3 | 4 | import { safeAccess, isAddress, getTokenAllowance } from '../utils' 5 | import { useBlockNumber } from './Application' 6 | 7 | const UPDATE = 'UPDATE' 8 | 9 | const AllowancesContext = createContext() 10 | 11 | function useAllowancesContext() { 12 | return useContext(AllowancesContext) 13 | } 14 | 15 | function reducer(state, { type, payload }) { 16 | switch (type) { 17 | case UPDATE: { 18 | const { chainId, address, tokenAddress, spenderAddress, value, blockNumber } = payload 19 | return { 20 | ...state, 21 | [chainId]: { 22 | ...(safeAccess(state, [chainId]) || {}), 23 | [address]: { 24 | ...(safeAccess(state, [chainId, address]) || {}), 25 | [tokenAddress]: { 26 | ...(safeAccess(state, [chainId, address, tokenAddress]) || {}), 27 | [spenderAddress]: { 28 | value, 29 | blockNumber 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | default: { 37 | throw Error(`Unexpected action type in AllowancesContext reducer: '${type}'.`) 38 | } 39 | } 40 | } 41 | 42 | export default function Provider({ children }) { 43 | const [state, dispatch] = useReducer(reducer, {}) 44 | 45 | const update = useCallback((chainId, address, tokenAddress, spenderAddress, value, blockNumber) => { 46 | dispatch({ type: UPDATE, payload: { chainId, address, tokenAddress, spenderAddress, value, blockNumber } }) 47 | }, []) 48 | 49 | return ( 50 | [state, { update }], [state, update])}> 51 | {children} 52 | 53 | ) 54 | } 55 | 56 | export function useAddressAllowance(address, tokenAddress, spenderAddress) { 57 | const { chainId, library } = useWeb3React() 58 | 59 | const globalBlockNumber = useBlockNumber() 60 | 61 | const [state, { update }] = useAllowancesContext() 62 | const { value, blockNumber } = safeAccess(state, [chainId, address, tokenAddress, spenderAddress]) || {} 63 | 64 | useEffect(() => { 65 | if ( 66 | isAddress(address) && 67 | isAddress(tokenAddress) && 68 | isAddress(spenderAddress) && 69 | (value === undefined || blockNumber !== globalBlockNumber) && 70 | (chainId || chainId === 0) && 71 | library 72 | ) { 73 | let stale = false 74 | 75 | getTokenAllowance(address, tokenAddress, spenderAddress, library) 76 | .then(value => { 77 | if (!stale) { 78 | update(chainId, address, tokenAddress, spenderAddress, value, globalBlockNumber) 79 | } 80 | }) 81 | .catch(() => { 82 | if (!stale) { 83 | update(chainId, address, tokenAddress, spenderAddress, null, globalBlockNumber) 84 | } 85 | }) 86 | 87 | return () => { 88 | stale = true 89 | } 90 | } 91 | }, [address, tokenAddress, spenderAddress, value, blockNumber, globalBlockNumber, chainId, library, update]) 92 | 93 | return value 94 | } 95 | -------------------------------------------------------------------------------- /src/contexts/Application.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' 2 | import { useWeb3React } from '@web3-react/core' 3 | 4 | import { safeAccess } from '../utils' 5 | import { getUSDPrice } from '../utils/price' 6 | 7 | const BLOCK_NUMBER = 'BLOCK_NUMBER' 8 | const USD_PRICE = 'USD_PRICE' 9 | 10 | const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER' 11 | const UPDATE_USD_PRICE = 'UPDATE_USD_PRICE' 12 | 13 | const ApplicationContext = createContext() 14 | 15 | function useApplicationContext() { 16 | return useContext(ApplicationContext) 17 | } 18 | 19 | function reducer(state, { type, payload }) { 20 | switch (type) { 21 | case UPDATE_BLOCK_NUMBER: { 22 | const { chainId, blockNumber } = payload 23 | return { 24 | ...state, 25 | [BLOCK_NUMBER]: { 26 | ...(safeAccess(state, [BLOCK_NUMBER]) || {}), 27 | [chainId]: blockNumber 28 | } 29 | } 30 | } 31 | case UPDATE_USD_PRICE: { 32 | const { chainId, USDPrice } = payload 33 | return { 34 | ...state, 35 | [USD_PRICE]: { 36 | ...(safeAccess(state, [USD_PRICE]) || {}), 37 | [chainId]: USDPrice 38 | } 39 | } 40 | } 41 | default: { 42 | throw Error(`Unexpected action type in ApplicationContext reducer: '${type}'.`) 43 | } 44 | } 45 | } 46 | 47 | export default function Provider({ children }) { 48 | const [state, dispatch] = useReducer(reducer, { 49 | [BLOCK_NUMBER]: {}, 50 | [USD_PRICE]: {} 51 | }) 52 | 53 | const updateBlockNumber = useCallback((chainId, blockNumber) => { 54 | dispatch({ type: UPDATE_BLOCK_NUMBER, payload: { chainId, blockNumber } }) 55 | }, []) 56 | 57 | const updateUSDPrice = useCallback((chainId, USDPrice) => { 58 | dispatch({ type: UPDATE_USD_PRICE, payload: { chainId, USDPrice } }) 59 | }, []) 60 | 61 | return ( 62 | [state, { updateBlockNumber, updateUSDPrice }], [state, updateBlockNumber, updateUSDPrice])} 64 | > 65 | {children} 66 | 67 | ) 68 | } 69 | 70 | export function Updater() { 71 | const { chainId, library, connectorName } = useWeb3React() 72 | 73 | const globalBlockNumber = useBlockNumber() 74 | const [, { updateBlockNumber, updateUSDPrice }] = useApplicationContext() 75 | 76 | // slow down polling interval 77 | useEffect(() => { 78 | if (library) { 79 | if (connectorName === 'Network') { 80 | library.polling = false 81 | } else { 82 | library.pollingInterval = 5 83 | } 84 | } 85 | }, [library, connectorName]) 86 | 87 | // update usd price 88 | // @TODO: remove this 89 | useEffect(() => { 90 | if (library) { 91 | let stale = false 92 | 93 | getUSDPrice(library.provider) 94 | .then(([price]) => { 95 | if (!stale) { 96 | updateUSDPrice(chainId, price) 97 | } 98 | }) 99 | .catch(() => { 100 | if (!stale) { 101 | updateUSDPrice(chainId, null) 102 | } 103 | }) 104 | } 105 | }, [globalBlockNumber, library, chainId, updateUSDPrice]) 106 | 107 | // update block number 108 | useEffect(() => { 109 | if (library) { 110 | let stale = false 111 | 112 | function update() { 113 | library 114 | .getBlockNumber() 115 | .then(blockNumber => { 116 | if (!stale) { 117 | updateBlockNumber(chainId, blockNumber) 118 | } 119 | }) 120 | .catch(() => { 121 | if (!stale) { 122 | updateBlockNumber(chainId, null) 123 | } 124 | }) 125 | } 126 | 127 | update() 128 | library.on('block', update) 129 | 130 | return () => { 131 | stale = true 132 | library.removeListener('block', update) 133 | } 134 | } 135 | }, [chainId, library, updateBlockNumber]) 136 | 137 | return null 138 | } 139 | 140 | export function useBlockNumber() { 141 | const { chainId } = useWeb3React() 142 | 143 | const [state] = useApplicationContext() 144 | 145 | return safeAccess(state, [BLOCK_NUMBER, chainId]) 146 | } 147 | 148 | export function useUSDPrice() { 149 | const { chainId } = useWeb3React() 150 | 151 | const [state] = useApplicationContext() 152 | 153 | return safeAccess(state, [USD_PRICE, chainId]) 154 | } 155 | -------------------------------------------------------------------------------- /src/contexts/Balances.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' 2 | import { useWeb3React } from '@web3-react/core' 3 | 4 | import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils' 5 | import { useBlockNumber } from './Application' 6 | 7 | const UPDATE = 'UPDATE' 8 | 9 | const BalancesContext = createContext() 10 | 11 | function useBalancesContext() { 12 | return useContext(BalancesContext) 13 | } 14 | 15 | function reducer(state, { type, payload }) { 16 | switch (type) { 17 | case UPDATE: { 18 | const { chainId, address, tokenAddress, value, blockNumber } = payload 19 | return { 20 | ...state, 21 | [chainId]: { 22 | ...(safeAccess(state, [chainId]) || {}), 23 | [address]: { 24 | ...(safeAccess(state, [chainId, address]) || {}), 25 | [tokenAddress]: { 26 | value, 27 | blockNumber 28 | } 29 | } 30 | } 31 | } 32 | } 33 | default: { 34 | throw Error(`Unexpected action type in BalancesContext reducer: '${type}'.`) 35 | } 36 | } 37 | } 38 | 39 | export default function Provider({ children }) { 40 | const [state, dispatch] = useReducer(reducer, {}) 41 | 42 | const update = useCallback((chainId, address, tokenAddress, value, blockNumber) => { 43 | dispatch({ type: UPDATE, payload: { chainId, address, tokenAddress, value, blockNumber } }) 44 | }, []) 45 | 46 | return ( 47 | [state, { update }], [state, update])}> 48 | {children} 49 | 50 | ) 51 | } 52 | 53 | export function useAddressBalance(address, tokenAddress) { 54 | const { chainId, library } = useWeb3React() 55 | 56 | const globalBlockNumber = useBlockNumber() 57 | 58 | const [state, { update }] = useBalancesContext() 59 | const { value, blockNumber } = safeAccess(state, [chainId, address, tokenAddress]) || {} 60 | 61 | useEffect(() => { 62 | if ( 63 | isAddress(address) && 64 | (tokenAddress === 'ETH' || isAddress(tokenAddress)) && 65 | (value === undefined || blockNumber !== globalBlockNumber) && 66 | (chainId || chainId === 0) && 67 | library 68 | ) { 69 | let stale = false 70 | ;(tokenAddress === 'ETH' ? getEtherBalance(address, library) : getTokenBalance(tokenAddress, address, library)) 71 | .then(value => { 72 | if (!stale) { 73 | update(chainId, address, tokenAddress, value, globalBlockNumber) 74 | } 75 | }) 76 | .catch(() => { 77 | if (!stale) { 78 | update(chainId, address, tokenAddress, null, globalBlockNumber) 79 | } 80 | }) 81 | return () => { 82 | stale = true 83 | } 84 | } 85 | }, [address, tokenAddress, value, blockNumber, globalBlockNumber, chainId, library, update]) 86 | 87 | return value 88 | } 89 | -------------------------------------------------------------------------------- /src/contexts/DefaultTokens.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_TOKENS_EXTRA = [ 2 | { 3 | name: 'yearn.finance', 4 | address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e', 5 | symbol: 'YFI', 6 | decimals: 18, 7 | chainId: 1, 8 | logoURI: 9 | 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e/logo.png' 10 | }, 11 | { 12 | name: 'BASED', 13 | address: '0x68A118Ef45063051Eac49c7e647CE5Ace48a68a5', 14 | symbol: 'BASED', 15 | decimals: 18, 16 | chainId: 1, 17 | logoURI: 18 | 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x68A118Ef45063051Eac49c7e647CE5Ace48a68a5/logo.png' 19 | }, 20 | { 21 | name: 'HEX', 22 | address: '0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39', 23 | symbol: 'HEX', 24 | decimals: 8, 25 | chainId: 1, 26 | logoURI: 27 | 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png' 28 | }, 29 | { 30 | name: 'Infinite Test Token', 31 | address: '0xbc5F6e2907Fc117268a36DaAA99A9863B5e64b7b', 32 | symbol: 'TEST', 33 | decimals: 18, 34 | chainId: 4, 35 | logoURI: 36 | 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x03fB52D4eE633ab0D06C833E32EFdd8D388f3E6a/logo.png' 37 | }, 38 | { 39 | name: 'SushiToken', 40 | symbol: 'SUSHI', 41 | address: '0x6B3595068778DD592e39A122f4f5a5cF09C90fE2', 42 | decimals: 18, 43 | chainId: 1 44 | }, 45 | { 46 | name: 'PickleToken', 47 | symbol: 'PICKLE', 48 | address: '0x429881672B9AE42b8EbA0E26cD9C73711b891Ca5', 49 | decimals: 18, 50 | chainId: 1 51 | }, 52 | { 53 | name: 'Aavegotchi GHST Token', 54 | symbol: 'GHST', 55 | address: '0x3f382dbd960e3a9bbceae22651e88158d2791550', 56 | decimals: 18, 57 | chainId: 1 58 | }, 59 | { 60 | name: 'Uniswap (UNI)', 61 | symbol: 'UNI', 62 | address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', 63 | decimals: 18, 64 | chainId: 1 65 | } 66 | ] 67 | 68 | export const DISABLED_TOKENS = {} 69 | -------------------------------------------------------------------------------- /src/contexts/GasPrice.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState, useEffect, useMemo } from 'react' 2 | import { ethers } from 'ethers' 3 | import { useBlockNumber } from './Application' 4 | 5 | const GasContext = createContext() 6 | 7 | function useGasContext() { 8 | return useContext(GasContext) 9 | } 10 | 11 | export default function Provider({ children }) { 12 | const [gasPrice, setGasPrice] = useState() 13 | 14 | const globalBlockNumber = useBlockNumber() 15 | 16 | useEffect(() => { 17 | fetch("https://www.gasnow.org/api/v3/gas/price?utm_source=pine").then((res) => { 18 | res.json().then(gasInfo => { 19 | try { 20 | setGasPrice(ethers.utils.bigNumberify(gasInfo.data.fast)) 21 | } catch {} 22 | }) 23 | }) 24 | }, [globalBlockNumber]) 25 | 26 | return ( 27 | [gasPrice, { setGasPrice }], [gasPrice, setGasPrice])}> 28 | {children} 29 | 30 | ) 31 | } 32 | 33 | export function useGasPrice() { 34 | const [gasPrice] = useGasContext() 35 | return gasPrice 36 | } 37 | -------------------------------------------------------------------------------- /src/contexts/LocalStorage.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' 2 | 3 | const UNISWAPEX = 'UNISWAPEX' 4 | 5 | const VERSION = 'VERSION' 6 | const CURRENT_VERSION = 0 7 | const LAST_SAVED = 'LAST_SAVED' 8 | 9 | const BETA_MESSAGE_DISMISSED = 'BETA_MESSAGE_DISMISSED' 10 | const DARK_MODE = 'DARK_MODE' 11 | 12 | const UPDATABLE_KEYS = [BETA_MESSAGE_DISMISSED, DARK_MODE] 13 | 14 | const UPDATE_KEY = 'UPDATE_KEY' 15 | 16 | const LocalStorageContext = createContext() 17 | 18 | function useLocalStorageContext() { 19 | return useContext(LocalStorageContext) 20 | } 21 | 22 | function reducer(state, { type, payload }) { 23 | switch (type) { 24 | case UPDATE_KEY: { 25 | const { key, value } = payload 26 | if (!UPDATABLE_KEYS.some(k => k === key)) { 27 | throw Error(`Unexpected key in LocalStorageContext reducer: '${key}'.`) 28 | } else { 29 | return { 30 | ...state, 31 | [key]: value 32 | } 33 | } 34 | } 35 | default: { 36 | throw Error(`Unexpected action type in LocalStorageContext reducer: '${type}'.`) 37 | } 38 | } 39 | } 40 | 41 | function init() { 42 | const defaultLocalStorage = { 43 | [VERSION]: CURRENT_VERSION, 44 | [BETA_MESSAGE_DISMISSED]: false, 45 | [DARK_MODE]: true 46 | } 47 | 48 | try { 49 | const parsed = JSON.parse(window.localStorage.getItem(UNISWAPEX)) 50 | if (parsed[VERSION] !== CURRENT_VERSION) { 51 | // this is where we could run migration logic 52 | return defaultLocalStorage 53 | } else { 54 | return { ...defaultLocalStorage, ...parsed } 55 | } 56 | } catch { 57 | return defaultLocalStorage 58 | } 59 | } 60 | 61 | export default function Provider({ children }) { 62 | const [state, dispatch] = useReducer(reducer, undefined, init) 63 | 64 | const updateKey = useCallback((key, value) => { 65 | dispatch({ type: UPDATE_KEY, payload: { key, value } }) 66 | }, []) 67 | 68 | return ( 69 | [state, { updateKey }], [state, updateKey])}> 70 | {children} 71 | 72 | ) 73 | } 74 | 75 | export function Updater() { 76 | const [state] = useLocalStorageContext() 77 | 78 | useEffect(() => { 79 | window.localStorage.setItem(UNISWAPEX, JSON.stringify({ ...state, [LAST_SAVED]: Math.floor(Date.now() / 1000) })) 80 | }) 81 | 82 | return null 83 | } 84 | 85 | export function useBetaMessageManager() { 86 | const [state, { updateKey }] = useLocalStorageContext() 87 | 88 | const dismissBetaMessage = useCallback(() => { 89 | updateKey(BETA_MESSAGE_DISMISSED, true) 90 | }, [updateKey]) 91 | 92 | return [!state[BETA_MESSAGE_DISMISSED], dismissBetaMessage] 93 | } 94 | 95 | export function useDarkModeManager() { 96 | const [state, { updateKey }] = useLocalStorageContext() 97 | 98 | const isDarkMode = state[DARK_MODE] 99 | 100 | const toggleDarkMode = useCallback(() => { 101 | updateKey(DARK_MODE, !isDarkMode) 102 | }, [updateKey, isDarkMode]) 103 | 104 | // TODO: Remove all dark mode related logic 105 | // or re-enable dark mode 106 | return [false, toggleDarkMode] 107 | } 108 | -------------------------------------------------------------------------------- /src/contexts/Tokens.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' 2 | import { useWeb3React } from '@web3-react/core' 3 | import { ChainId } from 'uniswap-v2-sdk' 4 | 5 | import { isAddress, getTokenName, getTokenSymbol, getTokenDecimals, safeAccess } from '../utils' 6 | import { DEFAULT_TOKENS_EXTRA, DISABLED_TOKENS } from './DefaultTokens' 7 | 8 | const NAME = 'name' 9 | const SYMBOL = 'symbol' 10 | const DECIMALS = 'decimals' 11 | const EXCHANGE_ADDRESS = 'exchangeAddress' 12 | 13 | // the Uniswap Default token list lives here 14 | export const DEFAULT_TOKEN_LIST_URL = 'https://unpkg.com/@uniswap/default-token-list@latest' 15 | 16 | const UPDATE = 'UPDATE' 17 | const SET_LIST = 'SET_LIST' 18 | 19 | const ETH = { 20 | ETH: { 21 | [NAME]: 'Ethereum', 22 | [SYMBOL]: 'ETH', 23 | [DECIMALS]: 18, 24 | [EXCHANGE_ADDRESS]: null 25 | } 26 | } 27 | 28 | export const WETH = { 29 | 1: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', 30 | 3: '0xc778417e063141139fce010982780140aa0cd5ab' 31 | } 32 | 33 | const EMPTY_LIST = { 34 | [ChainId.KOVAN]: {}, 35 | [ChainId.RINKEBY]: {}, 36 | [ChainId.ROPSTEN]: {}, 37 | [ChainId.GÖRLI]: {}, 38 | [ChainId.MAINNET]: {} 39 | } 40 | 41 | const TokensContext = createContext() 42 | 43 | function useTokensContext() { 44 | return useContext(TokensContext) 45 | } 46 | 47 | function reducer(state, { type, payload }) { 48 | switch (type) { 49 | case UPDATE: { 50 | const { chainId, tokenAddress, name, symbol, decimals } = payload 51 | return { 52 | ...state, 53 | [chainId]: { 54 | ...(safeAccess(state, [chainId]) || {}), 55 | [tokenAddress]: { 56 | [NAME]: name, 57 | [SYMBOL]: symbol, 58 | [DECIMALS]: decimals 59 | } 60 | } 61 | } 62 | } 63 | case SET_LIST: { 64 | return payload 65 | } 66 | default: { 67 | throw Error(`Unexpected action type in TokensContext reducer: '${type}'.`) 68 | } 69 | } 70 | } 71 | 72 | export default function Provider({ children }) { 73 | const [state, dispatch] = useReducer(reducer, EMPTY_LIST) 74 | 75 | useEffect(() => { 76 | fetch(DEFAULT_TOKEN_LIST_URL) 77 | .then(res => 78 | res.json().then(list => { 79 | const tokenList = list.tokens 80 | .filter(token => !DISABLED_TOKENS[token.symbol]) 81 | .concat(DEFAULT_TOKENS_EXTRA) 82 | .reduce( 83 | (tokenMap, token) => { 84 | if (tokenMap[token.chainId][token.address] !== undefined) { 85 | console.warn('Duplicate tokens.') 86 | return tokenMap 87 | } 88 | 89 | return { 90 | ...tokenMap, 91 | [token.chainId]: { 92 | ...tokenMap[token.chainId], 93 | [token.address]: token 94 | } 95 | } 96 | }, 97 | { ...EMPTY_LIST } 98 | ) 99 | dispatch({ type: SET_LIST, payload: tokenList }) 100 | }) 101 | ) 102 | .catch(e => console.error(e.message)) 103 | }, []) 104 | 105 | const update = useCallback((chainId, tokenAddress, name, symbol, decimals) => { 106 | dispatch({ type: UPDATE, payload: { chainId, tokenAddress, name, symbol, decimals } }) 107 | }, []) 108 | 109 | return ( 110 | [state, { update }], [state, update])}> 111 | {children} 112 | 113 | ) 114 | } 115 | 116 | export function useTokenDetails(tokenAddress) { 117 | const { chainId, library } = useWeb3React() 118 | 119 | const [state, { update }] = useTokensContext() 120 | const allTokensInNetwork = { ...ETH, ...(safeAccess(state, [chainId]) || {}) } 121 | const { [NAME]: name, [SYMBOL]: symbol, [DECIMALS]: decimals } = safeAccess(allTokensInNetwork, [tokenAddress]) || {} 122 | 123 | useEffect(() => { 124 | if ( 125 | isAddress(tokenAddress) && 126 | (name === undefined || symbol === undefined || decimals === undefined) && 127 | (chainId || chainId === 0) && 128 | library 129 | ) { 130 | let stale = false 131 | 132 | const namePromise = getTokenName(tokenAddress, library).catch(() => null) 133 | const symbolPromise = getTokenSymbol(tokenAddress, library).catch(() => null) 134 | const decimalsPromise = getTokenDecimals(tokenAddress, library).catch(() => null) 135 | 136 | Promise.all([namePromise, symbolPromise, decimalsPromise]).then( 137 | ([resolvedName, resolvedSymbol, resolvedDecimals]) => { 138 | if (!stale) { 139 | update(chainId, tokenAddress, resolvedName, resolvedSymbol, resolvedDecimals) 140 | } 141 | } 142 | ) 143 | return () => { 144 | stale = true 145 | } 146 | } 147 | }, [tokenAddress, name, symbol, decimals, chainId, library, update]) 148 | 149 | return { name, symbol, decimals, chainId } 150 | } 151 | 152 | export function useAllTokenDetails(r) { 153 | const { chainId } = useWeb3React() 154 | 155 | const [state] = useTokensContext() 156 | const tokenDetails = { ...ETH, ...(safeAccess(state, [chainId]) || {}) } 157 | 158 | return tokenDetails 159 | } 160 | -------------------------------------------------------------------------------- /src/i18n.js: -------------------------------------------------------------------------------- 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/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import ReactGA from 'react-ga' 4 | import { Web3Provider } from '@ethersproject/providers' 5 | import { Provider } from 'react-redux' 6 | 7 | import { createWeb3ReactRoot, Web3ReactProvider } from '@web3-react/core' 8 | 9 | import ThemeProvider, { GlobalStyle } from './theme' 10 | import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage' 11 | import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application' 12 | import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions' 13 | import TokensContextProvider from './contexts/Tokens' 14 | import BalancesContextProvider from './contexts/Balances' 15 | import AllowancesContextProvider from './contexts/Allowances' 16 | import AllBalancesContextProvider from './contexts/AllBalances' 17 | import GasPricesContextProvider from './contexts/GasPrice' 18 | import MulticallUpdater from './state/multicall/updater' 19 | import { NetworkContextName } from './constants' 20 | 21 | import App from './pages/App' 22 | import store from './state' 23 | 24 | import './i18n' 25 | 26 | const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName) 27 | 28 | if (process.env.NODE_ENV === 'production') { 29 | ReactGA.initialize('UA-148036712-1') 30 | } else { 31 | ReactGA.initialize('test', { testMode: true }) 32 | } 33 | ReactGA.pageview(window.location.pathname + window.location.search) 34 | 35 | function getLibrary(provider) { 36 | const library = new Web3Provider(provider) 37 | library.pollingInterval = 15000 38 | return library 39 | } 40 | 41 | function ContextProviders({ children }) { 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {children} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ) 61 | } 62 | 63 | function Updaters() { 64 | return ( 65 | <> 66 | 67 | 68 | 69 | 70 | 71 | ) 72 | } 73 | 74 | ReactDOM.render( 75 | 76 | 77 | 78 | 79 | 80 | 81 | <> 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | , 90 | document.getElementById('root') 91 | ) 92 | -------------------------------------------------------------------------------- /src/module/root/actions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export const updateBlockNumber = createAction('updateBlockNumber') 4 | export const toggleWalletModal = createAction('toggleWalletModal') 5 | export const toggleSettingsMenu = createAction('toggleSettingsMenu') 6 | export const addPopup = createAction('addPopup') 7 | export const removePopup = createAction('removePopup') 8 | -------------------------------------------------------------------------------- /src/module/root/reducer.js: -------------------------------------------------------------------------------- 1 | import { createReducer, nanoid } from '@reduxjs/toolkit' 2 | import { addPopup, removePopup, toggleWalletModal, toggleSettingsMenu, updateBlockNumber } from './actions' 3 | 4 | const initialState = { 5 | blockNumber: {}, 6 | popupList: [], 7 | walletModalOpen: false, 8 | settingsMenuOpen: false 9 | } 10 | 11 | export default createReducer(initialState, builder => 12 | builder 13 | .addCase(updateBlockNumber, (state, action) => { 14 | const { chainId, blockNumber } = action.payload 15 | if (typeof state.blockNumber[chainId] !== 'number') { 16 | state.blockNumber[chainId] = blockNumber 17 | } else { 18 | state.blockNumber[chainId] = Math.max(blockNumber, state.blockNumber[chainId]) 19 | } 20 | }) 21 | .addCase(toggleWalletModal, state => { 22 | state.walletModalOpen = !state.walletModalOpen 23 | }) 24 | .addCase(toggleSettingsMenu, state => { 25 | state.settingsMenuOpen = !state.settingsMenuOpen 26 | }) 27 | .addCase(addPopup, (state, { payload: { content, key } }) => { 28 | state.popupList = (key ? state.popupList.filter(popup => popup.key !== key) : state.popupList).concat([ 29 | { 30 | key: key || nanoid(), 31 | show: true, 32 | content 33 | } 34 | ]) 35 | }) 36 | .addCase(removePopup, (state, { payload: { key } }) => { 37 | state.popupList.forEach(p => { 38 | if (p.key === key) { 39 | p.show = false 40 | } 41 | }) 42 | }) 43 | ) 44 | -------------------------------------------------------------------------------- /src/pages/App.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy } from 'react' 2 | import styled from 'styled-components' 3 | import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom' 4 | 5 | import Web3ReactManager from '../components/Web3ReactManager' 6 | import Header from '../components/Header' 7 | import Footer from '../components/Footer' 8 | import NavigationTabs from '../components/NavigationTabs' 9 | 10 | import { isAddress } from '../utils' 11 | 12 | const Swap = lazy(() => import('./Swap')) 13 | 14 | const AppWrapper = styled.div` 15 | display: flex; 16 | flex-flow: column; 17 | align-items: flex-start; 18 | height: 100vh; 19 | ` 20 | 21 | const HeaderWrapper = styled.div` 22 | ${({ theme }) => theme.flexRowNoWrap} 23 | width: 100%; 24 | justify-content: space-between; 25 | ` 26 | const FooterWrapper = styled.div` 27 | width: 100%; 28 | min-height: 30px; 29 | align-self: flex-end; 30 | ` 31 | 32 | const BodyWrapper = styled.div` 33 | display: flex; 34 | flex-direction: column; 35 | width: 100%; 36 | justify-content: flex-start; 37 | align-items: center; 38 | flex: 1; 39 | overflow: inherit; 40 | ` 41 | 42 | const Body = styled.div` 43 | max-width: 29rem; 44 | width: 90%; 45 | /* margin: 0 1.25rem 1.25rem 1.25rem; */ 46 | ` 47 | 48 | export default function App() { 49 | return ( 50 | <> 51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | {/* this Suspense is for route code-splitting */} 61 | 62 | 63 | 64 | 65 | { 70 | if (isAddress(match.params.tokenAddress)) { 71 | return 72 | } else { 73 | return 74 | } 75 | }} 76 | /> 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |