├── .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 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/arrow-down-grey.svg:
--------------------------------------------------------------------------------
1 |
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 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/blue-loader.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/circle-grey.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/circle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/dropdown-blue.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/dropdown.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/dropup-blue.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/ethereum-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pine-finance/pine-interface/4738acd306822c356b1e32981dc77b26b18539b7/src/assets/images/ethereum-logo.png
--------------------------------------------------------------------------------
/src/assets/images/ethereum-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
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 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/plus-grey.svg:
--------------------------------------------------------------------------------
1 |
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 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/question.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/spinner.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
4 |
--------------------------------------------------------------------------------
/src/assets/svg/SVGArrowDown.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const SVGArrowDown = props => (
4 |
10 | )
11 |
12 | export default SVGArrowDown
13 |
--------------------------------------------------------------------------------
/src/assets/svg/SVGClose.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const SVGDiv = props => (
4 |
8 | )
9 |
10 | export default SVGDiv
11 |
--------------------------------------------------------------------------------
/src/assets/svg/SVGDiscord.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const SVGDiscord = () => (
4 |
9 | )
10 |
11 | export default SVGDiscord
12 |
--------------------------------------------------------------------------------
/src/assets/svg/SVGDiv.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const SVGClose = props => (
4 |
7 | )
8 |
9 | export default SVGClose
10 |
--------------------------------------------------------------------------------
/src/assets/svg/SVGTelegram.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const SVGTelegram = () => (
4 |
7 | )
8 |
9 | export default SVGTelegram
10 |
--------------------------------------------------------------------------------
/src/assets/svg/lightcircle.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
77 | About
78 |
79 |
80 | Code
81 |
82 |
83 | Contract
84 |
85 |
86 | UniswapEx
87 |
88 |
89 | Donate ❤
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
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 |
61 |
62 |
63 | 🌲{' '}
64 |
65 |
66 |
67 |
68 | Pine.finance
69 |
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 |
74 |
75 | {children}
76 |
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 | {
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 |
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 |
113 | )
114 | }
115 | return null
116 | })}
117 |
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 |
86 |
87 |
88 |
89 | >
90 | )
91 | }
92 |
--------------------------------------------------------------------------------
/src/pages/Swap/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ExchangePage from '../../components/ExchangePage'
3 | import Orders from '../../components/Orders'
4 |
5 | export default function Swap({ initialCurrency }) {
6 | return <>
7 |
8 |
9 | >
10 | }
11 |
--------------------------------------------------------------------------------
/src/state/index.js:
--------------------------------------------------------------------------------
1 | import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
2 | import { save, load } from 'redux-localstorage-simple'
3 |
4 | import root from '../module/root/reducer'
5 | import multicall from './multicall/reducer'
6 | // import user from './user/reducer'
7 | // import transactions from './transactions/reducer'
8 | // import swap from './swap/reducer'
9 | // import mint from './mint/reducer'
10 | // import lists from './lists/reducer'
11 | // import burn from './burn/reducer'
12 |
13 | // import { updateVersion } from './user/actions'
14 |
15 | const PERSISTED_KEYS = ['user', 'transactions', 'lists']
16 |
17 | const store = configureStore({
18 | reducer: {
19 | root,
20 | multicall
21 | //application,
22 | // user,
23 | // transactions,
24 | // swap,
25 | // mint,
26 | // burn,
27 | // lists
28 | },
29 | middleware: [...getDefaultMiddleware(), save({ states: PERSISTED_KEYS })],
30 | preloadedState: load({ states: PERSISTED_KEYS })
31 | })
32 |
33 | // store.dispatch(updateVersion())
34 |
35 | export default store
36 |
--------------------------------------------------------------------------------
/src/state/multicall/actions.js:
--------------------------------------------------------------------------------
1 | import { createAction } from '@reduxjs/toolkit'
2 |
3 | const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/
4 | const LOWER_HEX_REGEX = /^0x[a-f0-9]*$/
5 | export function toCallKey(call) {
6 | if (!ADDRESS_REGEX.test(call.address)) {
7 | throw new Error(`Invalid address: ${call.address}`)
8 | }
9 | if (!LOWER_HEX_REGEX.test(call.callData)) {
10 | throw new Error(`Invalid hex: ${call.callData}`)
11 | }
12 | return `${call.address}-${call.callData}`
13 | }
14 |
15 | export function parseCallKey(callKey) {
16 | const pcs = callKey.split('-')
17 | if (pcs.length !== 2) {
18 | throw new Error(`Invalid call key: ${callKey}`)
19 | }
20 | return {
21 | address: pcs[0],
22 | callData: pcs[1]
23 | }
24 | }
25 |
26 | export const addMulticallListeners = createAction('addMulticallListeners')
27 | export const removeMulticallListeners = createAction('removeMulticallListeners')
28 | export const fetchingMulticallResults = createAction('fetchingMulticallResults')
29 | export const errorFetchingMulticallResults = createAction('errorFetchingMulticallResults')
30 | export const updateMulticallResults = createAction('updateMulticallResults')
31 |
--------------------------------------------------------------------------------
/src/state/multicall/hooks.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { useActiveWeb3React, useBlockNumber } from '../../hooks'
4 |
5 | import { addMulticallListeners, removeMulticallListeners, parseCallKey, toCallKey } from './actions'
6 |
7 | function isMethodArg(x) {
8 | return ['string', 'number'].indexOf(typeof x) !== -1
9 | }
10 |
11 | function isValidMethodArgs(x) {
12 | return (
13 | x === undefined || (Array.isArray(x) && x.every(y => isMethodArg(y) || (Array.isArray(y) && y.every(isMethodArg))))
14 | )
15 | }
16 |
17 | const INVALID_RESULT = { valid: false, blockNumber: undefined, data: undefined }
18 | const INVALID_CALL_STATE = { valid: false, result: undefined, loading: false, syncing: false, error: false }
19 | const LOADING_CALL_STATE = { valid: true, result: undefined, loading: true, syncing: true, error: false }
20 |
21 | function toCallState(callResult, contractInterface, fragment, latestBlockNumber) {
22 | if (!callResult) return INVALID_CALL_STATE
23 | const { valid, data, blockNumber } = callResult
24 | if (!valid) return INVALID_CALL_STATE
25 | if (valid && !blockNumber) return LOADING_CALL_STATE
26 | if (!contractInterface || !fragment || !latestBlockNumber) return LOADING_CALL_STATE
27 | const success = data && data.length > 2
28 | const syncing = (blockNumber ? blockNumber : 0) < latestBlockNumber
29 | let result = undefined
30 | if (success && data) {
31 | try {
32 | result = contractInterface.decodeFunctionResult(fragment, data)
33 | } catch (error) {
34 | console.debug('Result data parsing failed', fragment, data)
35 | return {
36 | valid: true,
37 | loading: false,
38 | error: true,
39 | syncing,
40 | result
41 | }
42 | }
43 | }
44 | return {
45 | valid: true,
46 | loading: false,
47 | syncing,
48 | result: result,
49 | error: !success
50 | }
51 | }
52 |
53 | // the lowest level call for subscribing to contract data
54 | function useCallsData(calls, options) {
55 | const { chainId } = useActiveWeb3React()
56 | const callResults = useSelector(state => state.multicall.callResults)
57 | const dispatch = useDispatch()
58 |
59 | const serializedCallKeys = useMemo(() => {
60 | try {
61 | const res = calls
62 | .filter(c => Boolean(c))
63 | .map(toCallKey)
64 | .sort()
65 |
66 | return JSON.stringify(res ? res : [])
67 | } catch (e) {
68 | console.log('error ', e.message)
69 | return undefined
70 | }
71 | }, [calls])
72 |
73 | // update listeners when there is an actual change that persists for at least 100ms
74 | useEffect(() => {
75 | if (!serializedCallKeys) {
76 | return
77 | }
78 | const callKeys = JSON.parse(serializedCallKeys)
79 | if (!chainId || callKeys.length === 0) return
80 | const calls = callKeys.map(key => parseCallKey(key))
81 | dispatch(
82 | addMulticallListeners({
83 | chainId,
84 | calls,
85 | options
86 | })
87 | )
88 |
89 | return () => {
90 | dispatch(
91 | removeMulticallListeners({
92 | chainId,
93 | calls,
94 | options
95 | })
96 | )
97 | }
98 | }, [chainId, dispatch, options, serializedCallKeys])
99 |
100 | return useMemo(
101 | () =>
102 | calls.map(call => {
103 | if (!chainId || !call) return INVALID_RESULT
104 |
105 | const result = callResults[chainId] ? callResults[chainId][toCallKey(call)] : undefined
106 | let data
107 | if (result && result.data && result.data !== '0x') {
108 | data = result.data
109 | }
110 |
111 | return { valid: true, data, blockNumber: result ? result.blockNumber : undefined }
112 | }),
113 | [callResults, calls, chainId]
114 | )
115 | }
116 |
117 | export function useMultipleContractSingleData(addresses, contractInterface, methodName, callInputs, options) {
118 | const fragment = useMemo(() => contractInterface.getFunction(methodName), [contractInterface, methodName])
119 | const callData = useMemo(
120 | () =>
121 | fragment && isValidMethodArgs(callInputs)
122 | ? contractInterface.encodeFunctionData(fragment, callInputs)
123 | : undefined,
124 | [callInputs, contractInterface, fragment]
125 | )
126 |
127 | const calls = useMemo(
128 | () =>
129 | fragment && addresses && addresses.length > 0 && callData
130 | ? addresses.map(address => {
131 | return address && callData
132 | ? {
133 | address,
134 | callData
135 | }
136 | : undefined
137 | })
138 | : [],
139 | [addresses, callData, fragment]
140 | )
141 |
142 | const results = useCallsData(calls, options)
143 |
144 | const latestBlockNumber = useBlockNumber()
145 |
146 | return useMemo(() => {
147 | return results.map(result => toCallState(result, contractInterface, fragment, latestBlockNumber))
148 | }, [fragment, results, contractInterface, latestBlockNumber])
149 | }
150 |
--------------------------------------------------------------------------------
/src/state/multicall/reducer.js:
--------------------------------------------------------------------------------
1 | import { createReducer } from '@reduxjs/toolkit'
2 | import {
3 | addMulticallListeners,
4 | errorFetchingMulticallResults,
5 | fetchingMulticallResults,
6 | removeMulticallListeners,
7 | toCallKey,
8 | updateMulticallResults
9 | } from './actions'
10 |
11 | const initialState = {
12 | callResults: {}
13 | }
14 |
15 | export default createReducer(initialState, builder =>
16 | builder
17 | .addCase(addMulticallListeners, (state, { payload: { calls, chainId, options: { blocksPerFetch = 1 } = {} } }) => {
18 | const listeners = state.callListeners ? state.callListeners : (state.callListeners = {})
19 | listeners[chainId] = listeners[chainId] ? listeners[chainId] : {}
20 | calls.forEach(call => {
21 | const callKey = toCallKey(call)
22 | listeners[chainId][callKey] = listeners[chainId][callKey] ? listeners[chainId][callKey] : {}
23 | listeners[chainId][callKey][blocksPerFetch] =
24 | (listeners[chainId][callKey][blocksPerFetch] ? listeners[chainId][callKey][blocksPerFetch] : 0) + 1
25 | })
26 | })
27 | .addCase(
28 | removeMulticallListeners,
29 | (state, { payload: { chainId, calls, options: { blocksPerFetch = 1 } = {} } }) => {
30 | const listeners = state.callListeners ? state.callListeners : (state.callListeners = {})
31 |
32 | if (!listeners[chainId]) return
33 | calls.forEach(call => {
34 | const callKey = toCallKey(call)
35 | if (!listeners[chainId][callKey]) return
36 | if (!listeners[chainId][callKey][blocksPerFetch]) return
37 |
38 | if (listeners[chainId][callKey][blocksPerFetch] === 1) {
39 | delete listeners[chainId][callKey][blocksPerFetch]
40 | } else {
41 | listeners[chainId][callKey][blocksPerFetch]--
42 | }
43 | })
44 | }
45 | )
46 | .addCase(fetchingMulticallResults, (state, { payload: { chainId, fetchingBlockNumber, calls } }) => {
47 | state.callResults[chainId] = state.callResults[chainId] ? state.callResults[chainId] : {}
48 | calls.forEach(call => {
49 | const callKey = toCallKey(call)
50 | const current = state.callResults[chainId][callKey]
51 | if (!current) {
52 | state.callResults[chainId][callKey] = {
53 | fetchingBlockNumber
54 | }
55 | } else {
56 | if ((current.fetchingBlockNumber ? current.fetchingBlockNumber : 0) >= fetchingBlockNumber) return
57 | state.callResults[chainId][callKey].fetchingBlockNumber = fetchingBlockNumber
58 | }
59 | })
60 | })
61 | .addCase(errorFetchingMulticallResults, (state, { payload: { fetchingBlockNumber, chainId, calls } }) => {
62 | state.callResults[chainId] = state.callResults[chainId] ? state.callResults[chainId] : {}
63 | calls.forEach(call => {
64 | const callKey = toCallKey(call)
65 | const current = state.callResults[chainId][callKey]
66 | if (!current) return // only should be dispatched if we are already fetching
67 | if (current.fetchingBlockNumber === fetchingBlockNumber) {
68 | delete current.fetchingBlockNumber
69 | current.data = null
70 | current.blockNumber = fetchingBlockNumber
71 | }
72 | })
73 | })
74 | .addCase(updateMulticallResults, (state, { payload: { chainId, results, blockNumber } }) => {
75 | state.callResults[chainId] = state.callResults[chainId] ? state.callResults[chainId] : {}
76 | Object.keys(results).forEach(callKey => {
77 | const current = state.callResults[chainId][callKey]
78 | if ((current ? (current.blockNumber ? current.blockNumber : 0) : undefined) > blockNumber) return
79 | state.callResults[chainId][callKey] = {
80 | data: results[callKey],
81 | blockNumber
82 | }
83 | })
84 | })
85 | )
86 |
--------------------------------------------------------------------------------
/src/theme/components.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes } from 'styled-components'
2 | import { darken } from 'polished'
3 |
4 | export const Button = styled.button.attrs(({ warning, theme }) => ({
5 | backgroundColor: warning ? theme.salmonRed : theme.royalGreen
6 | }))`
7 | padding: 1rem 2rem 1rem 2rem;
8 | border-radius: 3rem;
9 | cursor: pointer;
10 | user-select: none;
11 | font-size: 1rem;
12 | border: none;
13 | outline: none;
14 | background-color: ${({ backgroundColor }) => backgroundColor};
15 | color: ${({ theme }) => theme.white};
16 | width: 100%;
17 |
18 | :hover,
19 | :focus {
20 | background-color: ${({ backgroundColor }) => darken(0.05, backgroundColor)};
21 | }
22 |
23 | :active {
24 | background-color: ${({ backgroundColor }) => darken(0.1, backgroundColor)};
25 | }
26 |
27 | :disabled {
28 | background-color: ${({ theme }) => theme.concreteGray};
29 | color: ${({ theme }) => theme.silverGray};
30 | cursor: auto;
31 | }
32 | `
33 |
34 | export const Link = styled.a.attrs({
35 | target: '_blank',
36 | rel: 'noopener noreferrer'
37 | })`
38 | text-decoration: none;
39 | cursor: pointer;
40 | color: ${({ theme }) => theme.royalGreen};
41 |
42 | :focus {
43 | outline: none;
44 | text-decoration: underline;
45 | }
46 |
47 | :active {
48 | text-decoration: none;
49 | }
50 | `
51 |
52 | export const BorderlessInput = styled.input`
53 | color: ${({ theme }) => theme.textColor};
54 | font-size: 1rem;
55 | outline: none;
56 | border: none;
57 | flex: 1 1 auto;
58 | width: 0;
59 | background-color: ${({ theme }) => theme.inputBackground};
60 |
61 | [type='number'] {
62 | -moz-appearance: textfield;
63 | }
64 |
65 | ::-webkit-outer-spin-button,
66 | ::-webkit-inner-spin-button {
67 | -webkit-appearance: none;
68 | }
69 |
70 | ::placeholder {
71 | color: ${({ theme }) => theme.chaliceGray};
72 | }
73 | `
74 |
75 | const rotate = keyframes`
76 | from {
77 | transform: rotate(0deg);
78 | }
79 | to {
80 | transform: rotate(360deg);
81 | }
82 | `
83 |
84 | export const Spinner = styled.img`
85 | animation: 2s ${rotate} linear infinite;
86 | width: 16px;
87 | height: 16px;
88 | `
89 |
--------------------------------------------------------------------------------
/src/theme/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle, css } from 'styled-components'
3 | import { useDarkModeManager } from '../contexts/LocalStorage'
4 |
5 | export * from './components'
6 |
7 | const MEDIA_WIDTHS = {
8 | upToSmall: 600,
9 | upToMedium: 960,
10 | upToLarge: 1280
11 | }
12 |
13 | const mediaWidthTemplates = Object.keys(MEDIA_WIDTHS).reduce((accumulator, size) => {
14 | accumulator[size] = (...args) => css`
15 | @media (max-width: ${MEDIA_WIDTHS[size]}px) {
16 | ${css(...args)}
17 | }
18 | `
19 | return accumulator
20 | }, {})
21 |
22 | const flexColumnNoWrap = css`
23 | display: flex;
24 | flex-flow: column nowrap;
25 | `
26 |
27 | const flexRowNoWrap = css`
28 | display: flex;
29 | flex-flow: row nowrap;
30 | `
31 |
32 | const white = '#FFFFFF'
33 | const black = '#000000'
34 |
35 | const theme = darkMode => ({
36 | white,
37 | black,
38 | textColor: darkMode ? white : '#010101',
39 |
40 | // for setting css on
41 | backgroundColor: darkMode ? '#2b2b2b' : white,
42 |
43 | modalBackground: darkMode ? 'rgba(0,0,0,0.6)' : 'rgba(0,0,0,0.3)',
44 | inputBackground: darkMode ? '#171717' : white,
45 | placeholderGray: darkMode ? '#5F5F5F' : '#E1E1E1',
46 | shadowColor: darkMode ? '#000' : '#2F80ED',
47 |
48 | // grays
49 | concreteGray: darkMode ? '#222222' : '#FAFAFA',
50 | mercuryGray: darkMode ? '#333333' : '#E1E1E1',
51 | silverGray: darkMode ? '#737373' : '#C4C4C4',
52 | chaliceGray: darkMode ? '#7B7B7B' : '#AEAEAE',
53 | doveGray: darkMode ? '#C4C4C4' : '#737373',
54 | mineshaftGray: darkMode ? '#E1E1E1' : '#2B2B2B',
55 | buttonOutlineGrey: darkMode ? '#FAFAFA' : '#F2F2F2',
56 | tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
57 | //blacks
58 | charcoalBlack: darkMode ? '#F2F2F2' : '#404040',
59 | // blues
60 | zumthorBlue: darkMode ? '#262626' : '#eeffeb',
61 | malibuGreen: darkMode ? '#87c122' : '#01796f',
62 | royalGreen: darkMode ? '#87c122' : '#01796f',
63 | loadingBlue: darkMode ? '#e4f0ff' : '#e4f0ff',
64 |
65 | // purples
66 | wisteriaPurple: darkMode ? '#87c122' : '#01796f',
67 | // reds
68 | salmonRed: '#FF6871',
69 | // orange
70 | pizazzOrange: '#FF8F05',
71 | // yellows
72 | warningYellow: '#FFE270',
73 | // pink
74 | uniswapPink: darkMode ? '#87c122' : '#01796f',
75 | connectedGreen: '#27AE60',
76 |
77 | //specific
78 | textHover: darkMode ? theme.uniswapPink : theme.doveGray,
79 |
80 | // media queries
81 | mediaWidth: mediaWidthTemplates,
82 | // css snippets
83 | flexColumnNoWrap,
84 | flexRowNoWrap,
85 |
86 | // text
87 | text1: darkMode ? '#FFFFFF' : '#000000',
88 | text2: darkMode ? '#C3C5CB' : '#565A69',
89 | text3: darkMode ? '#6C7284' : '#888D9B',
90 | text4: darkMode ? '#565A69' : '#C3C5CB',
91 | text5: darkMode ? '#2C2F36' : '#EDEEF2',
92 |
93 | // backgrounds / greys
94 | bg1: darkMode ? '#212429' : '#FFFFFF',
95 | bg2: darkMode ? '#1a1a1a' : '#F7F8FA',
96 | bg3: darkMode ? '#40444F' : '#EDEEF2',
97 | bg4: darkMode ? '#565A69' : '#CED0D9',
98 | bg5: darkMode ? '#565A69' : '#888D9B',
99 |
100 | //specialty colors
101 | modalBG: darkMode ? 'rgba(0,0,0,0.85)' : 'rgba(0,0,0,0.6)',
102 | advancedBG: darkMode ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.6)',
103 |
104 | //primary colors
105 | primary1: darkMode ? '#2172E5' : '#01796f',
106 | primary2: darkMode ? '#3680E7' : '#FF8CC3',
107 | primary3: darkMode ? '#4D8FEA' : '#FF99C9',
108 | primary4: darkMode ? '#ffffff' : '#ffffff',
109 | primary5: darkMode ? '#153d6f70' : '#FDEAF1',
110 |
111 | // color text
112 | primaryText1: darkMode ? '#6da8ff' : '#01796f',
113 |
114 | // secondary colors
115 | secondary1: darkMode ? '#2172E5' : '#01796f',
116 | secondary2: darkMode ? '#17000b26' : '#F6DDE8',
117 | secondary3: darkMode ? '#17000b26' : '#FDEAF1',
118 |
119 | // other
120 | red1: '#FF6871',
121 | green1: '#27AE60',
122 | yellow1: '#FFE270',
123 | yellow2: '#F3841E'
124 | })
125 |
126 | export default function ThemeProvider({ children }) {
127 | const [darkMode] = useDarkModeManager()
128 |
129 | return {children}
130 | }
131 |
132 | export const GlobalStyle = createGlobalStyle`
133 | @import url('https://rsms.me/inter/inter.css');
134 | html { font-family: 'Inter', sans-serif; }
135 | @supports (font-variation-settings: normal) {
136 | html { font-family: 'Inter var', sans-serif; }
137 | }
138 |
139 | html,
140 | body {
141 | margin: 0;
142 | padding: 0;
143 | width: 100%;
144 | height: 100%;
145 | overflow: hidden;
146 | }
147 |
148 | body > div {
149 | height: 100%;
150 | overflow: scroll;
151 | -webkit-overflow-scrolling: touch;
152 | }
153 |
154 | html {
155 | font-size: 16px;
156 | font-variant: none;
157 | color: ${({ theme }) => theme.textColor};
158 | background-color: ${({ theme }) => theme.backgroundColor};
159 | -webkit-font-smoothing: antialiased;
160 | -moz-osx-font-smoothing: grayscale;
161 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
162 | }
163 | `
164 |
--------------------------------------------------------------------------------
/src/utils/math.js:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@uniswap/sdk'
2 |
3 | // returns a deep copied + sorted list of values, as well as a sortmap
4 | export function sortBigNumbers(values) {
5 | const valueMap = values.map((value, i) => ({ value, i }))
6 |
7 | valueMap.sort((a, b) => {
8 | if (a.value.isGreaterThan(b.value)) {
9 | return 1
10 | } else if (a.value.isLessThan(b.value)) {
11 | return -1
12 | } else {
13 | return 0
14 | }
15 | })
16 |
17 | return [
18 | valueMap.map(element => values[element.i]),
19 | values.map((_, i) => valueMap.findIndex(element => element.i === i))
20 | ]
21 | }
22 |
23 | export function getMedian(values) {
24 | const [sortedValues, sortMap] = sortBigNumbers(values)
25 | if (values.length % 2 === 0) {
26 | const middle = values.length / 2
27 | const indices = [middle - 1, middle]
28 | return [
29 | sortedValues[middle - 1].plus(sortedValues[middle]).dividedBy(2),
30 | sortMap.map(element => (indices.includes(element) ? new BigNumber(0.5) : new BigNumber(0)))
31 | ]
32 | } else {
33 | const middle = Math.floor(values.length / 2)
34 | return [sortedValues[middle], sortMap.map(element => (element === middle ? new BigNumber(1) : new BigNumber(0)))]
35 | }
36 | }
37 |
38 | export function getMean(values, _weights) {
39 | const weights = _weights ? _weights : values.map(() => new BigNumber(1))
40 |
41 | const weightedValues = values.map((value, i) => value.multipliedBy(weights[i]))
42 | const numerator = weightedValues.reduce(
43 | (accumulator, currentValue) => accumulator.plus(currentValue),
44 | new BigNumber(0)
45 | )
46 | const denominator = weights.reduce((accumulator, currentValue) => accumulator.plus(currentValue), new BigNumber(0))
47 |
48 | return [numerator.dividedBy(denominator), weights.map(weight => weight.dividedBy(denominator))]
49 | }
50 |
--------------------------------------------------------------------------------
/src/utils/price.js:
--------------------------------------------------------------------------------
1 | import { getTokenReserves, getMarketDetails } from '@uniswap/sdk'
2 | import { getMedian, getMean } from './math'
3 |
4 | const DAI = 'DAI'
5 | const USDC = 'USDC'
6 | const TUSD = 'TUSD'
7 |
8 | const USD_STABLECOINS = [DAI, USDC, TUSD]
9 |
10 | const USD_STABLECOIN_ADDRESSES = [
11 | '0x6B175474E89094C44Da98b954EedeAC495271d0F',
12 | '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
13 | '0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E'
14 | ]
15 |
16 | function forEachStablecoin(runner) {
17 | return USD_STABLECOINS.map((stablecoin, index) => runner(index, stablecoin))
18 | }
19 |
20 | export async function getUSDPrice(library) {
21 | return Promise.all(forEachStablecoin(i => getTokenReserves(USD_STABLECOIN_ADDRESSES[i], library))).then(reserves => {
22 | const ethReserves = forEachStablecoin(i => reserves[i].ethReserve.amount)
23 | const marketDetails = forEachStablecoin(i => getMarketDetails(reserves[i], undefined))
24 |
25 | const ethPrices = forEachStablecoin(i => marketDetails[i].marketRate.rateInverted)
26 |
27 | const [median, medianWeights] = getMedian(ethPrices)
28 | const [mean, meanWeights] = getMean(ethPrices)
29 | const [weightedMean, weightedMeanWeights] = getMean(ethPrices, ethReserves)
30 |
31 | const ethPrice = getMean([median, mean, weightedMean])[0]
32 | const _stablecoinWeights = [
33 | getMean([medianWeights[0], meanWeights[0], weightedMeanWeights[0]])[0],
34 | getMean([medianWeights[1], meanWeights[1], weightedMeanWeights[1]])[0],
35 | getMean([medianWeights[2], meanWeights[2], weightedMeanWeights[2]])[0]
36 | ]
37 | const stablecoinWeights = forEachStablecoin((i, stablecoin) => ({
38 | [stablecoin]: _stablecoinWeights[i]
39 | })).reduce((accumulator, currentValue) => ({ ...accumulator, ...currentValue }), {})
40 |
41 | return [ethPrice, stablecoinWeights]
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/src/utils/rate.js:
--------------------------------------------------------------------------------
1 | import { ethers } from 'ethers'
2 |
3 | export function getExchangeRate(inputValue, inputDecimals, outputValue, outputDecimals, invert = false) {
4 | try {
5 | if (
6 | inputValue &&
7 | (inputDecimals || inputDecimals === 0) &&
8 | outputValue &&
9 | (outputDecimals || outputDecimals === 0)
10 | ) {
11 | const factor = ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(18))
12 |
13 | if (invert) {
14 | return inputValue
15 | .mul(factor)
16 | .div(outputValue)
17 | .mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(outputDecimals)))
18 | .div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(inputDecimals)))
19 | } else {
20 | return outputValue
21 | .mul(factor)
22 | .div(inputValue)
23 | .mul(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(inputDecimals)))
24 | .div(ethers.utils.bigNumberify(10).pow(ethers.utils.bigNumberify(outputDecimals)))
25 | }
26 | }
27 | } catch {}
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/signer.js:
--------------------------------------------------------------------------------
1 | import * as ethers from 'ethers'
2 |
3 | export default class UncheckedJsonRpcSigner extends ethers.Signer {
4 | constructor(signer) {
5 | super()
6 | ethers.utils.defineReadOnly(this, 'signer', signer)
7 | ethers.utils.defineReadOnly(this, 'provider', signer.provider)
8 | }
9 |
10 | getAddress() {
11 | return this.signer.getAddress()
12 | }
13 |
14 | sendTransaction(transaction) {
15 | return this.signer.sendUncheckedTransaction(transaction).then(hash => {
16 | return {
17 | hash: hash,
18 | nonce: null,
19 | gasLimit: null,
20 | gasPrice: null,
21 | data: null,
22 | value: null,
23 | chainId: null,
24 | confirmations: 0,
25 | from: null,
26 | wait: confirmations => {
27 | return this.signer.provider.waitForTransaction(hash, confirmations)
28 | }
29 | }
30 | })
31 | }
32 |
33 | signMessage(message) {
34 | return this.signer.signMessage(message)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "build": {
4 | "src": "package.json",
5 | "use": "@now/static-build",
6 | "env": {
7 | "REACT_APP_NETWORK_URL": "https://mainnet.infura.io/v3/a9bca4ddb8784b349f6ec65f04aadfea",
8 | "REACT_APP_PORTIS_ID": "64731eda-0ddb-4a02-a69a-32264802fac3",
9 | "REACT_APP_FORTMATIC_KEY": "pk_live_85AE47FD4F283564"
10 | }
11 | },
12 | "routes": [
13 | {
14 | "src": "^/favicon.ico",
15 | "dest": "/favicon.ico"
16 | },
17 | {
18 | "src": "^/manifest.json",
19 | "dest": "/manifest.json"
20 | },
21 | {
22 | "src": "^/service-worker.json",
23 | "dest": "/service-worker.json"
24 | },
25 | {
26 | "src": "^/asset-manifest.json",
27 | "dest": "/asset-manifest.json"
28 | },
29 | {
30 | "src": "^/asset-manifest.json",
31 | "dest": "/asset-manifest.json"
32 | },
33 | {
34 | "src": "^/static/(.*)",
35 | "dest": "/static/$1"
36 | },
37 | {
38 | "src": "^/locales/(.*)",
39 | "dest": "/locales/$1"
40 | },
41 | {
42 | "src": "^/(.*)",
43 | "dest": "/index.html"
44 | }
45 | ]
46 | }
--------------------------------------------------------------------------------